From cfcfaf3e1fafeec7ac32edb5c43a9d9219aaf204 Mon Sep 17 00:00:00 2001 From: "Namhyeon, Go" Date: Sun, 2 Nov 2025 18:09:59 +0900 Subject: [PATCH 01/18] Add WinForms interface for WebSocket management --- .../WelsonJS.Launcher/MainForm.Designer.cs | 13 +- .../WelsonJS.Launcher/MainForm.cs | 5 + .../WebSocketManagerForm.Designer.cs | 278 +++++++++++++++++ .../WelsonJS.Launcher/WebSocketManagerForm.cs | 288 ++++++++++++++++++ .../WebSocketManagerForm.resx | 15 + .../WelsonJS.Launcher.csproj | 9 + 6 files changed, 606 insertions(+), 2 deletions(-) create mode 100644 WelsonJS.Toolkit/WelsonJS.Launcher/WebSocketManagerForm.Designer.cs create mode 100644 WelsonJS.Toolkit/WelsonJS.Launcher/WebSocketManagerForm.cs create mode 100644 WelsonJS.Toolkit/WelsonJS.Launcher/WebSocketManagerForm.resx diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/MainForm.Designer.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/MainForm.Designer.cs index 76ba271..be45706 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/MainForm.Designer.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/MainForm.Designer.cs @@ -135,6 +135,7 @@ this.runAsAdministratorToolStripMenuItem, this.globalSettingsToolStripMenuItem, this.startTheEditorToolStripMenuItem, + this.webSocketManagerToolStripMenuItem, this.openCopilotToolStripMenuItem}); this.settingsToolStripMenuItem.Name = "settingsToolStripMenuItem"; this.settingsToolStripMenuItem.Size = new System.Drawing.Size(62, 20); @@ -174,9 +175,16 @@ this.startTheEditorToolStripMenuItem.Size = new System.Drawing.Size(196, 22); this.startTheEditorToolStripMenuItem.Text = "Start the editor..."; this.startTheEditorToolStripMenuItem.Click += new System.EventHandler(this.startCodeEditorToolStripMenuItem_Click); - // + // + // webSocketManagerToolStripMenuItem + // + this.webSocketManagerToolStripMenuItem.Name = "webSocketManagerToolStripMenuItem"; + this.webSocketManagerToolStripMenuItem.Size = new System.Drawing.Size(196, 22); + this.webSocketManagerToolStripMenuItem.Text = "WebSocket manager..."; + this.webSocketManagerToolStripMenuItem.Click += new System.EventHandler(this.webSocketManagerToolStripMenuItem_Click); + // // openCopilotToolStripMenuItem - // + // this.openCopilotToolStripMenuItem.Name = "openCopilotToolStripMenuItem"; this.openCopilotToolStripMenuItem.Size = new System.Drawing.Size(196, 22); this.openCopilotToolStripMenuItem.Text = "Open the Copilot..."; @@ -291,6 +299,7 @@ private System.Windows.Forms.ToolStripMenuItem openCodeEditorToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem openLauncherToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem openCopilotToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem webSocketManagerToolStripMenuItem; private System.Windows.Forms.SaveFileDialog saveFileDialog1; private System.Windows.Forms.Button btnStartTheEditor; private System.Windows.Forms.Button btnJoinTheCommunity; diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/MainForm.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/MainForm.cs index f55da4b..e3ce7dd 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/MainForm.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/MainForm.cs @@ -335,6 +335,11 @@ namespace WelsonJS.Launcher Program.OpenWebBrowser(Program.GetAppConfig("CopilotUrl")); } + private void webSocketManagerToolStripMenuItem_Click(object sender, EventArgs e) + { + (new WebSocketManagerForm()).Show(); + } + private void btnJoinTheCommunity_Click(object sender, EventArgs e) { Program.OpenWebBrowser(Program.GetAppConfig("RepositoryUrl")); diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/WebSocketManagerForm.Designer.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/WebSocketManagerForm.Designer.cs new file mode 100644 index 0000000..df51939 --- /dev/null +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/WebSocketManagerForm.Designer.cs @@ -0,0 +1,278 @@ +// WebSocketManagerForm.Designer.cs +// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-FileCopyrightText: 2025 Catswords OSS and WelsonJS Contributors +// https://github.com/gnh1201/welsonjs +// +namespace WelsonJS.Launcher +{ + partial class WebSocketManagerForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.lvConnections = new System.Windows.Forms.ListView(); + this.chHost = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.chPort = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.chPath = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.chStatus = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.chUpdated = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.btnRefresh = new System.Windows.Forms.Button(); + this.gbNewConnection = new System.Windows.Forms.GroupBox(); + this.btnConnect = new System.Windows.Forms.Button(); + this.txtPath = new System.Windows.Forms.TextBox(); + this.lblPath = new System.Windows.Forms.Label(); + this.nudPort = new System.Windows.Forms.NumericUpDown(); + this.lblPort = new System.Windows.Forms.Label(); + this.txtHost = new System.Windows.Forms.TextBox(); + this.lblHost = new System.Windows.Forms.Label(); + this.btnDisconnect = new System.Windows.Forms.Button(); + this.btnClose = new System.Windows.Forms.Button(); + this.statusTimer = new System.Windows.Forms.Timer(this.components); + this.gbNewConnection.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.nudPort)).BeginInit(); + this.SuspendLayout(); + // + // lvConnections + // + this.lvConnections.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { + this.chHost, + this.chPort, + this.chPath, + this.chStatus, + this.chUpdated}); + this.lvConnections.FullRowSelect = true; + this.lvConnections.HideSelection = false; + this.lvConnections.Location = new System.Drawing.Point(12, 12); + this.lvConnections.MultiSelect = false; + this.lvConnections.Name = "lvConnections"; + this.lvConnections.Size = new System.Drawing.Size(601, 188); + this.lvConnections.TabIndex = 0; + this.lvConnections.UseCompatibleStateImageBehavior = false; + this.lvConnections.View = System.Windows.Forms.View.Details; + this.lvConnections.SelectedIndexChanged += new System.EventHandler(this.lvConnections_SelectedIndexChanged); + // + // chHost + // + this.chHost.Text = "Host"; + this.chHost.Width = 150; + // + // chPort + // + this.chPort.Text = "Port"; + this.chPort.Width = 80; + // + // chPath + // + this.chPath.Text = "Path"; + this.chPath.Width = 150; + // + // chStatus + // + this.chStatus.Text = "Status"; + this.chStatus.Width = 120; + // + // chUpdated + // + this.chUpdated.Text = "Last Updated"; + this.chUpdated.Width = 180; + // + // btnRefresh + // + this.btnRefresh.Location = new System.Drawing.Point(518, 206); + this.btnRefresh.Name = "btnRefresh"; + this.btnRefresh.Size = new System.Drawing.Size(95, 23); + this.btnRefresh.TabIndex = 1; + this.btnRefresh.Text = "Refresh"; + this.btnRefresh.UseVisualStyleBackColor = true; + this.btnRefresh.Click += new System.EventHandler(this.btnRefresh_Click); + // + // gbNewConnection + // + this.gbNewConnection.Controls.Add(this.btnConnect); + this.gbNewConnection.Controls.Add(this.txtPath); + this.gbNewConnection.Controls.Add(this.lblPath); + this.gbNewConnection.Controls.Add(this.nudPort); + this.gbNewConnection.Controls.Add(this.lblPort); + this.gbNewConnection.Controls.Add(this.txtHost); + this.gbNewConnection.Controls.Add(this.lblHost); + this.gbNewConnection.Location = new System.Drawing.Point(12, 235); + this.gbNewConnection.Name = "gbNewConnection"; + this.gbNewConnection.Size = new System.Drawing.Size(601, 100); + this.gbNewConnection.TabIndex = 2; + this.gbNewConnection.TabStop = false; + this.gbNewConnection.Text = "New connection"; + // + // btnConnect + // + this.btnConnect.Location = new System.Drawing.Point(470, 56); + this.btnConnect.Name = "btnConnect"; + this.btnConnect.Size = new System.Drawing.Size(112, 27); + this.btnConnect.TabIndex = 6; + this.btnConnect.Text = "Connect"; + this.btnConnect.UseVisualStyleBackColor = true; + this.btnConnect.Click += new System.EventHandler(this.btnConnect_Click); + // + // txtPath + // + this.txtPath.Location = new System.Drawing.Point(70, 60); + this.txtPath.Name = "txtPath"; + this.txtPath.Size = new System.Drawing.Size(318, 21); + this.txtPath.TabIndex = 5; + this.txtPath.TextChanged += new System.EventHandler(this.ConnectionInputChanged); + // + // lblPath + // + this.lblPath.AutoSize = true; + this.lblPath.Location = new System.Drawing.Point(15, 63); + this.lblPath.Name = "lblPath"; + this.lblPath.Size = new System.Drawing.Size(31, 12); + this.lblPath.TabIndex = 4; + this.lblPath.Text = "Path"; + // + // nudPort + // + this.nudPort.Location = new System.Drawing.Point(308, 26); + this.nudPort.Maximum = new decimal(new int[] { + 65535, + 0, + 0, + 0}); + this.nudPort.Minimum = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.nudPort.Name = "nudPort"; + this.nudPort.Size = new System.Drawing.Size(80, 21); + this.nudPort.TabIndex = 3; + this.nudPort.Value = new decimal(new int[] { + 80, + 0, + 0, + 0}); + this.nudPort.ValueChanged += new System.EventHandler(this.ConnectionInputChanged); + // + // lblPort + // + this.lblPort.AutoSize = true; + this.lblPort.Location = new System.Drawing.Point(268, 29); + this.lblPort.Name = "lblPort"; + this.lblPort.Size = new System.Drawing.Size(29, 12); + this.lblPort.TabIndex = 2; + this.lblPort.Text = "Port"; + // + // txtHost + // + this.txtHost.Location = new System.Drawing.Point(70, 26); + this.txtHost.Name = "txtHost"; + this.txtHost.Size = new System.Drawing.Size(180, 21); + this.txtHost.TabIndex = 1; + this.txtHost.TextChanged += new System.EventHandler(this.ConnectionInputChanged); + // + // lblHost + // + this.lblHost.AutoSize = true; + this.lblHost.Location = new System.Drawing.Point(15, 29); + this.lblHost.Name = "lblHost"; + this.lblHost.Size = new System.Drawing.Size(31, 12); + this.lblHost.TabIndex = 0; + this.lblHost.Text = "Host"; + // + // btnDisconnect + // + this.btnDisconnect.Location = new System.Drawing.Point(12, 341); + this.btnDisconnect.Name = "btnDisconnect"; + this.btnDisconnect.Size = new System.Drawing.Size(126, 27); + this.btnDisconnect.TabIndex = 3; + this.btnDisconnect.Text = "Disconnect"; + this.btnDisconnect.UseVisualStyleBackColor = true; + this.btnDisconnect.Click += new System.EventHandler(this.btnDisconnect_Click); + // + // btnClose + // + this.btnClose.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.btnClose.Location = new System.Drawing.Point(487, 341); + this.btnClose.Name = "btnClose"; + this.btnClose.Size = new System.Drawing.Size(126, 27); + this.btnClose.TabIndex = 4; + this.btnClose.Text = "Close"; + this.btnClose.UseVisualStyleBackColor = true; + this.btnClose.Click += new System.EventHandler(this.btnClose_Click); + // + // statusTimer + // + this.statusTimer.Interval = 2000; + this.statusTimer.Tick += new System.EventHandler(this.statusTimer_Tick); + // + // WebSocketManagerForm + // + this.AcceptButton = this.btnConnect; + this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 12F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.btnClose; + this.ClientSize = new System.Drawing.Size(625, 380); + this.Controls.Add(this.btnClose); + this.Controls.Add(this.btnDisconnect); + this.Controls.Add(this.gbNewConnection); + this.Controls.Add(this.btnRefresh); + this.Controls.Add(this.lvConnections); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "WebSocketManagerForm"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "WebSocket Manager"; + this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.WebSocketManagerForm_FormClosed); + this.Load += new System.EventHandler(this.WebSocketManagerForm_Load); + this.gbNewConnection.ResumeLayout(false); + this.gbNewConnection.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.nudPort)).EndInit(); + this.ResumeLayout(false); + } + + #endregion + + private System.Windows.Forms.ListView lvConnections; + private System.Windows.Forms.ColumnHeader chHost; + private System.Windows.Forms.ColumnHeader chPort; + private System.Windows.Forms.ColumnHeader chPath; + private System.Windows.Forms.ColumnHeader chStatus; + private System.Windows.Forms.ColumnHeader chUpdated; + private System.Windows.Forms.Button btnRefresh; + private System.Windows.Forms.GroupBox gbNewConnection; + private System.Windows.Forms.Button btnConnect; + private System.Windows.Forms.TextBox txtPath; + private System.Windows.Forms.Label lblPath; + private System.Windows.Forms.NumericUpDown nudPort; + private System.Windows.Forms.Label lblPort; + private System.Windows.Forms.TextBox txtHost; + private System.Windows.Forms.Label lblHost; + private System.Windows.Forms.Button btnDisconnect; + private System.Windows.Forms.Button btnClose; + private System.Windows.Forms.Timer statusTimer; + } +} diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/WebSocketManagerForm.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/WebSocketManagerForm.cs new file mode 100644 index 0000000..ea3e65e --- /dev/null +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/WebSocketManagerForm.cs @@ -0,0 +1,288 @@ +// WebSocketManagerForm.cs +// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-FileCopyrightText: 2025 Catswords OSS and WelsonJS Contributors +// https://github.com/gnh1201/welsonjs +// +using System; +using System.Collections.Concurrent; +using System.Net.WebSockets; +using System.Windows.Forms; + +namespace WelsonJS.Launcher +{ + public partial class WebSocketManagerForm : Form + { + private sealed class ConnectionInfo + { + public string Host; + public int Port; + public string Path; + } + + private sealed class ConnectionRecord + { + public ConnectionInfo Info; + public ClientWebSocket Socket; + public string LastStatus; + } + + private readonly WebSocketManager _manager; + private readonly ConcurrentDictionary _records; + private readonly string _dateTimeFormat; + + public WebSocketManagerForm() + { + InitializeComponent(); + + _manager = new WebSocketManager(); + _records = new ConcurrentDictionary(); + + string format = Program.GetAppConfig("DateTimeFormat"); + if (string.IsNullOrWhiteSpace(format)) + { + format = "yyyy-MM-dd HH:mm:ss"; + } + + _dateTimeFormat = format; + btnConnect.Enabled = false; + } + + private void WebSocketManagerForm_Load(object sender, EventArgs e) + { + statusTimer.Start(); + UpdateButtons(); + } + + private void WebSocketManagerForm_FormClosed(object sender, FormClosedEventArgs e) + { + statusTimer.Stop(); + } + + private void ConnectionInputChanged(object sender, EventArgs e) + { + btnConnect.Enabled = !string.IsNullOrWhiteSpace(txtHost.Text); + } + + private async void btnConnect_Click(object sender, EventArgs e) + { + string host = txtHost.Text.Trim(); + string path = NormalizePath(txtPath.Text); + int port = (int)nudPort.Value; + + if (string.IsNullOrEmpty(host)) + { + MessageBox.Show("Host is required.", "WebSocket Manager", MessageBoxButtons.OK, MessageBoxIcon.Warning); + txtHost.Focus(); + return; + } + + btnConnect.Enabled = false; + + try + { + ConnectionInfo info = new ConnectionInfo + { + Host = host, + Port = port, + Path = path + }; + + ClientWebSocket socket = await _manager.GetOrCreateAsync(info.Host, info.Port, info.Path); + + string key = BuildKey(info.Host, info.Port, info.Path); + ConnectionRecord record = _records.GetOrAdd(key, k => new ConnectionRecord { Info = info }); + record.Info = info; + record.Socket = socket; + record.LastStatus = socket.State.ToString(); + + AddOrUpdateListViewItem(record, record.LastStatus); + RefreshStatuses(); + } + catch (Exception ex) + { + MessageBox.Show("Failed to connect: " + ex.Message, "WebSocket Manager", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + finally + { + btnConnect.Enabled = !string.IsNullOrWhiteSpace(txtHost.Text); + } + } + + private void btnDisconnect_Click(object sender, EventArgs e) + { + ConnectionRecord record = GetSelectedRecord(); + if (record == null) + { + return; + } + + try + { + _manager.Remove(record.Info.Host, record.Info.Port, record.Info.Path); + + if (record.Socket != null) + { + try + { + record.Socket.Dispose(); + } + catch + { + // Ignore dispose errors from socket + } + record.Socket = null; + } + + record.LastStatus = "Closed"; + AddOrUpdateListViewItem(record, record.LastStatus); + } + catch (Exception ex) + { + MessageBox.Show("Failed to disconnect: " + ex.Message, "WebSocket Manager", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + + private void btnRefresh_Click(object sender, EventArgs e) + { + RefreshStatuses(); + } + + private void btnClose_Click(object sender, EventArgs e) + { + Close(); + } + + private void statusTimer_Tick(object sender, EventArgs e) + { + RefreshStatuses(); + } + + private void lvConnections_SelectedIndexChanged(object sender, EventArgs e) + { + UpdateButtons(); + } + + private void UpdateButtons() + { + btnDisconnect.Enabled = lvConnections.SelectedItems.Count > 0; + } + + private void RefreshStatuses() + { + foreach (ListViewItem item in lvConnections.Items) + { + ConnectionRecord record = item.Tag as ConnectionRecord; + if (record == null) + { + continue; + } + + string status = record.LastStatus ?? "Unknown"; + if (record.Socket != null) + { + try + { + status = record.Socket.State.ToString(); + record.LastStatus = status; + } + catch (ObjectDisposedException) + { + record.Socket = null; + status = "Disposed"; + record.LastStatus = status; + } + } + + if (item.SubItems.Count >= 5) + { + item.SubItems[3].Text = status; + item.SubItems[4].Text = DateTime.Now.ToString(_dateTimeFormat); + } + } + } + + private void AddOrUpdateListViewItem(ConnectionRecord record, string status) + { + string key = BuildKey(record.Info.Host, record.Info.Port, record.Info.Path); + string formattedPath = FormatPath(record.Info.Path); + string timestamp = DateTime.Now.ToString(_dateTimeFormat); + + ListViewItem item; + if (lvConnections.Items.ContainsKey(key)) + { + item = lvConnections.Items[key]; + item.SubItems[0].Text = record.Info.Host; + item.SubItems[1].Text = record.Info.Port.ToString(); + item.SubItems[2].Text = formattedPath; + item.SubItems[3].Text = status; + item.SubItems[4].Text = timestamp; + item.Tag = record; + } + else + { + item = new ListViewItem(new string[] + { + record.Info.Host, + record.Info.Port.ToString(), + formattedPath, + status, + timestamp + }); + item.Name = key; + item.Tag = record; + lvConnections.Items.Add(item); + } + } + + private ConnectionRecord GetSelectedRecord() + { + if (lvConnections.SelectedItems.Count == 0) + { + return null; + } + + return lvConnections.SelectedItems[0].Tag as ConnectionRecord; + } + + private static string NormalizePath(string path) + { + if (string.IsNullOrEmpty(path)) + { + return string.Empty; + } + + string trimmed = path.Trim(); + while (trimmed.StartsWith("/", StringComparison.Ordinal)) + { + trimmed = trimmed.Substring(1); + } + + return trimmed; + } + + private static string FormatPath(string path) + { + if (string.IsNullOrEmpty(path)) + { + return "/"; + } + + return path.StartsWith("/", StringComparison.Ordinal) ? path : "/" + path; + } + + private static string BuildKey(string host, int port, string path) + { + if (host == null) + { + host = string.Empty; + } + + if (path == null) + { + path = string.Empty; + } + + return host.ToLowerInvariant() + ":" + port + "/" + path; + } + } +} diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/WebSocketManagerForm.resx b/WelsonJS.Toolkit/WelsonJS.Launcher/WebSocketManagerForm.resx new file mode 100644 index 0000000..1510323 --- /dev/null +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/WebSocketManagerForm.resx @@ -0,0 +1,15 @@ + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj b/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj index 5bc8b61..c5a314f 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj @@ -127,6 +127,12 @@ GlobalSettingsForm.cs + + Form + + + WebSocketManagerForm.cs + @@ -147,6 +153,9 @@ GlobalSettingsForm.cs + + WebSocketManagerForm.cs + SettingsSingleFileGenerator From ba7b3e368592f10c4d468c2678caea489c509212 Mon Sep 17 00:00:00 2001 From: "Namhyeon, Go" Date: Sun, 2 Nov 2025 18:17:59 +0900 Subject: [PATCH 02/18] Revert "Merge pull request #340 from gnh1201/codex/create-winforms-for-websocket-management" This reverts commit d71adb800182f7e749aca4202060c7ae9bb39146, reversing changes made to 83b8e453f0cc0450f64fdeed00cc28b80f4ab69c. --- .../WelsonJS.Launcher/MainForm.Designer.cs | 13 +- .../WelsonJS.Launcher/MainForm.cs | 5 - .../WebSocketManagerForm.Designer.cs | 278 ----------------- .../WelsonJS.Launcher/WebSocketManagerForm.cs | 288 ------------------ .../WebSocketManagerForm.resx | 15 - .../WelsonJS.Launcher.csproj | 9 - 6 files changed, 2 insertions(+), 606 deletions(-) delete mode 100644 WelsonJS.Toolkit/WelsonJS.Launcher/WebSocketManagerForm.Designer.cs delete mode 100644 WelsonJS.Toolkit/WelsonJS.Launcher/WebSocketManagerForm.cs delete mode 100644 WelsonJS.Toolkit/WelsonJS.Launcher/WebSocketManagerForm.resx diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/MainForm.Designer.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/MainForm.Designer.cs index be45706..76ba271 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/MainForm.Designer.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/MainForm.Designer.cs @@ -135,7 +135,6 @@ this.runAsAdministratorToolStripMenuItem, this.globalSettingsToolStripMenuItem, this.startTheEditorToolStripMenuItem, - this.webSocketManagerToolStripMenuItem, this.openCopilotToolStripMenuItem}); this.settingsToolStripMenuItem.Name = "settingsToolStripMenuItem"; this.settingsToolStripMenuItem.Size = new System.Drawing.Size(62, 20); @@ -175,16 +174,9 @@ this.startTheEditorToolStripMenuItem.Size = new System.Drawing.Size(196, 22); this.startTheEditorToolStripMenuItem.Text = "Start the editor..."; this.startTheEditorToolStripMenuItem.Click += new System.EventHandler(this.startCodeEditorToolStripMenuItem_Click); - // - // webSocketManagerToolStripMenuItem - // - this.webSocketManagerToolStripMenuItem.Name = "webSocketManagerToolStripMenuItem"; - this.webSocketManagerToolStripMenuItem.Size = new System.Drawing.Size(196, 22); - this.webSocketManagerToolStripMenuItem.Text = "WebSocket manager..."; - this.webSocketManagerToolStripMenuItem.Click += new System.EventHandler(this.webSocketManagerToolStripMenuItem_Click); - // + // // openCopilotToolStripMenuItem - // + // this.openCopilotToolStripMenuItem.Name = "openCopilotToolStripMenuItem"; this.openCopilotToolStripMenuItem.Size = new System.Drawing.Size(196, 22); this.openCopilotToolStripMenuItem.Text = "Open the Copilot..."; @@ -299,7 +291,6 @@ private System.Windows.Forms.ToolStripMenuItem openCodeEditorToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem openLauncherToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem openCopilotToolStripMenuItem; - private System.Windows.Forms.ToolStripMenuItem webSocketManagerToolStripMenuItem; private System.Windows.Forms.SaveFileDialog saveFileDialog1; private System.Windows.Forms.Button btnStartTheEditor; private System.Windows.Forms.Button btnJoinTheCommunity; diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/MainForm.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/MainForm.cs index e3ce7dd..f55da4b 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/MainForm.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/MainForm.cs @@ -335,11 +335,6 @@ namespace WelsonJS.Launcher Program.OpenWebBrowser(Program.GetAppConfig("CopilotUrl")); } - private void webSocketManagerToolStripMenuItem_Click(object sender, EventArgs e) - { - (new WebSocketManagerForm()).Show(); - } - private void btnJoinTheCommunity_Click(object sender, EventArgs e) { Program.OpenWebBrowser(Program.GetAppConfig("RepositoryUrl")); diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/WebSocketManagerForm.Designer.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/WebSocketManagerForm.Designer.cs deleted file mode 100644 index df51939..0000000 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/WebSocketManagerForm.Designer.cs +++ /dev/null @@ -1,278 +0,0 @@ -// WebSocketManagerForm.Designer.cs -// SPDX-License-Identifier: GPL-3.0-or-later -// SPDX-FileCopyrightText: 2025 Catswords OSS and WelsonJS Contributors -// https://github.com/gnh1201/welsonjs -// -namespace WelsonJS.Launcher -{ - partial class WebSocketManagerForm - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - this.components = new System.ComponentModel.Container(); - this.lvConnections = new System.Windows.Forms.ListView(); - this.chHost = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.chPort = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.chPath = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.chStatus = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.chUpdated = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.btnRefresh = new System.Windows.Forms.Button(); - this.gbNewConnection = new System.Windows.Forms.GroupBox(); - this.btnConnect = new System.Windows.Forms.Button(); - this.txtPath = new System.Windows.Forms.TextBox(); - this.lblPath = new System.Windows.Forms.Label(); - this.nudPort = new System.Windows.Forms.NumericUpDown(); - this.lblPort = new System.Windows.Forms.Label(); - this.txtHost = new System.Windows.Forms.TextBox(); - this.lblHost = new System.Windows.Forms.Label(); - this.btnDisconnect = new System.Windows.Forms.Button(); - this.btnClose = new System.Windows.Forms.Button(); - this.statusTimer = new System.Windows.Forms.Timer(this.components); - this.gbNewConnection.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.nudPort)).BeginInit(); - this.SuspendLayout(); - // - // lvConnections - // - this.lvConnections.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { - this.chHost, - this.chPort, - this.chPath, - this.chStatus, - this.chUpdated}); - this.lvConnections.FullRowSelect = true; - this.lvConnections.HideSelection = false; - this.lvConnections.Location = new System.Drawing.Point(12, 12); - this.lvConnections.MultiSelect = false; - this.lvConnections.Name = "lvConnections"; - this.lvConnections.Size = new System.Drawing.Size(601, 188); - this.lvConnections.TabIndex = 0; - this.lvConnections.UseCompatibleStateImageBehavior = false; - this.lvConnections.View = System.Windows.Forms.View.Details; - this.lvConnections.SelectedIndexChanged += new System.EventHandler(this.lvConnections_SelectedIndexChanged); - // - // chHost - // - this.chHost.Text = "Host"; - this.chHost.Width = 150; - // - // chPort - // - this.chPort.Text = "Port"; - this.chPort.Width = 80; - // - // chPath - // - this.chPath.Text = "Path"; - this.chPath.Width = 150; - // - // chStatus - // - this.chStatus.Text = "Status"; - this.chStatus.Width = 120; - // - // chUpdated - // - this.chUpdated.Text = "Last Updated"; - this.chUpdated.Width = 180; - // - // btnRefresh - // - this.btnRefresh.Location = new System.Drawing.Point(518, 206); - this.btnRefresh.Name = "btnRefresh"; - this.btnRefresh.Size = new System.Drawing.Size(95, 23); - this.btnRefresh.TabIndex = 1; - this.btnRefresh.Text = "Refresh"; - this.btnRefresh.UseVisualStyleBackColor = true; - this.btnRefresh.Click += new System.EventHandler(this.btnRefresh_Click); - // - // gbNewConnection - // - this.gbNewConnection.Controls.Add(this.btnConnect); - this.gbNewConnection.Controls.Add(this.txtPath); - this.gbNewConnection.Controls.Add(this.lblPath); - this.gbNewConnection.Controls.Add(this.nudPort); - this.gbNewConnection.Controls.Add(this.lblPort); - this.gbNewConnection.Controls.Add(this.txtHost); - this.gbNewConnection.Controls.Add(this.lblHost); - this.gbNewConnection.Location = new System.Drawing.Point(12, 235); - this.gbNewConnection.Name = "gbNewConnection"; - this.gbNewConnection.Size = new System.Drawing.Size(601, 100); - this.gbNewConnection.TabIndex = 2; - this.gbNewConnection.TabStop = false; - this.gbNewConnection.Text = "New connection"; - // - // btnConnect - // - this.btnConnect.Location = new System.Drawing.Point(470, 56); - this.btnConnect.Name = "btnConnect"; - this.btnConnect.Size = new System.Drawing.Size(112, 27); - this.btnConnect.TabIndex = 6; - this.btnConnect.Text = "Connect"; - this.btnConnect.UseVisualStyleBackColor = true; - this.btnConnect.Click += new System.EventHandler(this.btnConnect_Click); - // - // txtPath - // - this.txtPath.Location = new System.Drawing.Point(70, 60); - this.txtPath.Name = "txtPath"; - this.txtPath.Size = new System.Drawing.Size(318, 21); - this.txtPath.TabIndex = 5; - this.txtPath.TextChanged += new System.EventHandler(this.ConnectionInputChanged); - // - // lblPath - // - this.lblPath.AutoSize = true; - this.lblPath.Location = new System.Drawing.Point(15, 63); - this.lblPath.Name = "lblPath"; - this.lblPath.Size = new System.Drawing.Size(31, 12); - this.lblPath.TabIndex = 4; - this.lblPath.Text = "Path"; - // - // nudPort - // - this.nudPort.Location = new System.Drawing.Point(308, 26); - this.nudPort.Maximum = new decimal(new int[] { - 65535, - 0, - 0, - 0}); - this.nudPort.Minimum = new decimal(new int[] { - 1, - 0, - 0, - 0}); - this.nudPort.Name = "nudPort"; - this.nudPort.Size = new System.Drawing.Size(80, 21); - this.nudPort.TabIndex = 3; - this.nudPort.Value = new decimal(new int[] { - 80, - 0, - 0, - 0}); - this.nudPort.ValueChanged += new System.EventHandler(this.ConnectionInputChanged); - // - // lblPort - // - this.lblPort.AutoSize = true; - this.lblPort.Location = new System.Drawing.Point(268, 29); - this.lblPort.Name = "lblPort"; - this.lblPort.Size = new System.Drawing.Size(29, 12); - this.lblPort.TabIndex = 2; - this.lblPort.Text = "Port"; - // - // txtHost - // - this.txtHost.Location = new System.Drawing.Point(70, 26); - this.txtHost.Name = "txtHost"; - this.txtHost.Size = new System.Drawing.Size(180, 21); - this.txtHost.TabIndex = 1; - this.txtHost.TextChanged += new System.EventHandler(this.ConnectionInputChanged); - // - // lblHost - // - this.lblHost.AutoSize = true; - this.lblHost.Location = new System.Drawing.Point(15, 29); - this.lblHost.Name = "lblHost"; - this.lblHost.Size = new System.Drawing.Size(31, 12); - this.lblHost.TabIndex = 0; - this.lblHost.Text = "Host"; - // - // btnDisconnect - // - this.btnDisconnect.Location = new System.Drawing.Point(12, 341); - this.btnDisconnect.Name = "btnDisconnect"; - this.btnDisconnect.Size = new System.Drawing.Size(126, 27); - this.btnDisconnect.TabIndex = 3; - this.btnDisconnect.Text = "Disconnect"; - this.btnDisconnect.UseVisualStyleBackColor = true; - this.btnDisconnect.Click += new System.EventHandler(this.btnDisconnect_Click); - // - // btnClose - // - this.btnClose.DialogResult = System.Windows.Forms.DialogResult.Cancel; - this.btnClose.Location = new System.Drawing.Point(487, 341); - this.btnClose.Name = "btnClose"; - this.btnClose.Size = new System.Drawing.Size(126, 27); - this.btnClose.TabIndex = 4; - this.btnClose.Text = "Close"; - this.btnClose.UseVisualStyleBackColor = true; - this.btnClose.Click += new System.EventHandler(this.btnClose_Click); - // - // statusTimer - // - this.statusTimer.Interval = 2000; - this.statusTimer.Tick += new System.EventHandler(this.statusTimer_Tick); - // - // WebSocketManagerForm - // - this.AcceptButton = this.btnConnect; - this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 12F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.CancelButton = this.btnClose; - this.ClientSize = new System.Drawing.Size(625, 380); - this.Controls.Add(this.btnClose); - this.Controls.Add(this.btnDisconnect); - this.Controls.Add(this.gbNewConnection); - this.Controls.Add(this.btnRefresh); - this.Controls.Add(this.lvConnections); - this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; - this.MaximizeBox = false; - this.MinimizeBox = false; - this.Name = "WebSocketManagerForm"; - this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; - this.Text = "WebSocket Manager"; - this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.WebSocketManagerForm_FormClosed); - this.Load += new System.EventHandler(this.WebSocketManagerForm_Load); - this.gbNewConnection.ResumeLayout(false); - this.gbNewConnection.PerformLayout(); - ((System.ComponentModel.ISupportInitialize)(this.nudPort)).EndInit(); - this.ResumeLayout(false); - } - - #endregion - - private System.Windows.Forms.ListView lvConnections; - private System.Windows.Forms.ColumnHeader chHost; - private System.Windows.Forms.ColumnHeader chPort; - private System.Windows.Forms.ColumnHeader chPath; - private System.Windows.Forms.ColumnHeader chStatus; - private System.Windows.Forms.ColumnHeader chUpdated; - private System.Windows.Forms.Button btnRefresh; - private System.Windows.Forms.GroupBox gbNewConnection; - private System.Windows.Forms.Button btnConnect; - private System.Windows.Forms.TextBox txtPath; - private System.Windows.Forms.Label lblPath; - private System.Windows.Forms.NumericUpDown nudPort; - private System.Windows.Forms.Label lblPort; - private System.Windows.Forms.TextBox txtHost; - private System.Windows.Forms.Label lblHost; - private System.Windows.Forms.Button btnDisconnect; - private System.Windows.Forms.Button btnClose; - private System.Windows.Forms.Timer statusTimer; - } -} diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/WebSocketManagerForm.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/WebSocketManagerForm.cs deleted file mode 100644 index ea3e65e..0000000 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/WebSocketManagerForm.cs +++ /dev/null @@ -1,288 +0,0 @@ -// WebSocketManagerForm.cs -// SPDX-License-Identifier: GPL-3.0-or-later -// SPDX-FileCopyrightText: 2025 Catswords OSS and WelsonJS Contributors -// https://github.com/gnh1201/welsonjs -// -using System; -using System.Collections.Concurrent; -using System.Net.WebSockets; -using System.Windows.Forms; - -namespace WelsonJS.Launcher -{ - public partial class WebSocketManagerForm : Form - { - private sealed class ConnectionInfo - { - public string Host; - public int Port; - public string Path; - } - - private sealed class ConnectionRecord - { - public ConnectionInfo Info; - public ClientWebSocket Socket; - public string LastStatus; - } - - private readonly WebSocketManager _manager; - private readonly ConcurrentDictionary _records; - private readonly string _dateTimeFormat; - - public WebSocketManagerForm() - { - InitializeComponent(); - - _manager = new WebSocketManager(); - _records = new ConcurrentDictionary(); - - string format = Program.GetAppConfig("DateTimeFormat"); - if (string.IsNullOrWhiteSpace(format)) - { - format = "yyyy-MM-dd HH:mm:ss"; - } - - _dateTimeFormat = format; - btnConnect.Enabled = false; - } - - private void WebSocketManagerForm_Load(object sender, EventArgs e) - { - statusTimer.Start(); - UpdateButtons(); - } - - private void WebSocketManagerForm_FormClosed(object sender, FormClosedEventArgs e) - { - statusTimer.Stop(); - } - - private void ConnectionInputChanged(object sender, EventArgs e) - { - btnConnect.Enabled = !string.IsNullOrWhiteSpace(txtHost.Text); - } - - private async void btnConnect_Click(object sender, EventArgs e) - { - string host = txtHost.Text.Trim(); - string path = NormalizePath(txtPath.Text); - int port = (int)nudPort.Value; - - if (string.IsNullOrEmpty(host)) - { - MessageBox.Show("Host is required.", "WebSocket Manager", MessageBoxButtons.OK, MessageBoxIcon.Warning); - txtHost.Focus(); - return; - } - - btnConnect.Enabled = false; - - try - { - ConnectionInfo info = new ConnectionInfo - { - Host = host, - Port = port, - Path = path - }; - - ClientWebSocket socket = await _manager.GetOrCreateAsync(info.Host, info.Port, info.Path); - - string key = BuildKey(info.Host, info.Port, info.Path); - ConnectionRecord record = _records.GetOrAdd(key, k => new ConnectionRecord { Info = info }); - record.Info = info; - record.Socket = socket; - record.LastStatus = socket.State.ToString(); - - AddOrUpdateListViewItem(record, record.LastStatus); - RefreshStatuses(); - } - catch (Exception ex) - { - MessageBox.Show("Failed to connect: " + ex.Message, "WebSocket Manager", MessageBoxButtons.OK, MessageBoxIcon.Error); - } - finally - { - btnConnect.Enabled = !string.IsNullOrWhiteSpace(txtHost.Text); - } - } - - private void btnDisconnect_Click(object sender, EventArgs e) - { - ConnectionRecord record = GetSelectedRecord(); - if (record == null) - { - return; - } - - try - { - _manager.Remove(record.Info.Host, record.Info.Port, record.Info.Path); - - if (record.Socket != null) - { - try - { - record.Socket.Dispose(); - } - catch - { - // Ignore dispose errors from socket - } - record.Socket = null; - } - - record.LastStatus = "Closed"; - AddOrUpdateListViewItem(record, record.LastStatus); - } - catch (Exception ex) - { - MessageBox.Show("Failed to disconnect: " + ex.Message, "WebSocket Manager", MessageBoxButtons.OK, MessageBoxIcon.Error); - } - } - - private void btnRefresh_Click(object sender, EventArgs e) - { - RefreshStatuses(); - } - - private void btnClose_Click(object sender, EventArgs e) - { - Close(); - } - - private void statusTimer_Tick(object sender, EventArgs e) - { - RefreshStatuses(); - } - - private void lvConnections_SelectedIndexChanged(object sender, EventArgs e) - { - UpdateButtons(); - } - - private void UpdateButtons() - { - btnDisconnect.Enabled = lvConnections.SelectedItems.Count > 0; - } - - private void RefreshStatuses() - { - foreach (ListViewItem item in lvConnections.Items) - { - ConnectionRecord record = item.Tag as ConnectionRecord; - if (record == null) - { - continue; - } - - string status = record.LastStatus ?? "Unknown"; - if (record.Socket != null) - { - try - { - status = record.Socket.State.ToString(); - record.LastStatus = status; - } - catch (ObjectDisposedException) - { - record.Socket = null; - status = "Disposed"; - record.LastStatus = status; - } - } - - if (item.SubItems.Count >= 5) - { - item.SubItems[3].Text = status; - item.SubItems[4].Text = DateTime.Now.ToString(_dateTimeFormat); - } - } - } - - private void AddOrUpdateListViewItem(ConnectionRecord record, string status) - { - string key = BuildKey(record.Info.Host, record.Info.Port, record.Info.Path); - string formattedPath = FormatPath(record.Info.Path); - string timestamp = DateTime.Now.ToString(_dateTimeFormat); - - ListViewItem item; - if (lvConnections.Items.ContainsKey(key)) - { - item = lvConnections.Items[key]; - item.SubItems[0].Text = record.Info.Host; - item.SubItems[1].Text = record.Info.Port.ToString(); - item.SubItems[2].Text = formattedPath; - item.SubItems[3].Text = status; - item.SubItems[4].Text = timestamp; - item.Tag = record; - } - else - { - item = new ListViewItem(new string[] - { - record.Info.Host, - record.Info.Port.ToString(), - formattedPath, - status, - timestamp - }); - item.Name = key; - item.Tag = record; - lvConnections.Items.Add(item); - } - } - - private ConnectionRecord GetSelectedRecord() - { - if (lvConnections.SelectedItems.Count == 0) - { - return null; - } - - return lvConnections.SelectedItems[0].Tag as ConnectionRecord; - } - - private static string NormalizePath(string path) - { - if (string.IsNullOrEmpty(path)) - { - return string.Empty; - } - - string trimmed = path.Trim(); - while (trimmed.StartsWith("/", StringComparison.Ordinal)) - { - trimmed = trimmed.Substring(1); - } - - return trimmed; - } - - private static string FormatPath(string path) - { - if (string.IsNullOrEmpty(path)) - { - return "/"; - } - - return path.StartsWith("/", StringComparison.Ordinal) ? path : "/" + path; - } - - private static string BuildKey(string host, int port, string path) - { - if (host == null) - { - host = string.Empty; - } - - if (path == null) - { - path = string.Empty; - } - - return host.ToLowerInvariant() + ":" + port + "/" + path; - } - } -} diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/WebSocketManagerForm.resx b/WelsonJS.Toolkit/WelsonJS.Launcher/WebSocketManagerForm.resx deleted file mode 100644 index 1510323..0000000 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/WebSocketManagerForm.resx +++ /dev/null @@ -1,15 +0,0 @@ - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj b/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj index c5a314f..5bc8b61 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj @@ -127,12 +127,6 @@ GlobalSettingsForm.cs - - Form - - - WebSocketManagerForm.cs - @@ -153,9 +147,6 @@ GlobalSettingsForm.cs - - WebSocketManagerForm.cs - SettingsSingleFileGenerator From 4fb7b95c0e0a396e2ae91b3059c00c667ab25002 Mon Sep 17 00:00:00 2001 From: "Namhyeon, Go" Date: Mon, 10 Nov 2025 09:47:17 +0900 Subject: [PATCH 03/18] Enable COM interop registration in project config Added True to all build configurations in WelsonJS.Cryptography.vbproj to support COM interop registration for both Debug and Release builds. --- .../WelsonJS.Cryptography/WelsonJS.Cryptography.vbproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/WelsonJS.Toolkit/WelsonJS.Cryptography/WelsonJS.Cryptography.vbproj b/WelsonJS.Toolkit/WelsonJS.Cryptography/WelsonJS.Cryptography.vbproj index 71bb12b..e1480c0 100644 --- a/WelsonJS.Toolkit/WelsonJS.Cryptography/WelsonJS.Cryptography.vbproj +++ b/WelsonJS.Toolkit/WelsonJS.Cryptography/WelsonJS.Cryptography.vbproj @@ -17,18 +17,22 @@ True + True True + True True + True True + True From c677c6907aaf1fdc76c5634581ceac1f60e5d3d3 Mon Sep 17 00:00:00 2001 From: "Namhyeon, Go" Date: Sat, 15 Nov 2025 18:08:47 +0900 Subject: [PATCH 04/18] Add CLI support for running script and zip files Launcher now accepts a --file argument to run .js or .zip files directly from the command line. MainForm and Program.cs were refactored to support this, including new registry entries in setup.iss for file associations with WelsonJS Script extensions. --- .../WelsonJS.Launcher/MainForm.cs | 25 +- WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs | 215 ++++++++++++++++-- setup.iss | 12 + 3 files changed, 225 insertions(+), 27 deletions(-) diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/MainForm.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/MainForm.cs index f55da4b..1ccdd06 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/MainForm.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/MainForm.cs @@ -20,6 +20,7 @@ namespace WelsonJS.Launcher private readonly string _dateTimeFormat; private readonly ICompatibleLogger _logger; + private string _filePath; private string _workingDirectory; private string _instanceId; private string _scriptName; @@ -122,6 +123,16 @@ namespace WelsonJS.Launcher private void btnRunFromZipFile_Click(object sender, EventArgs e) { + if (!String.IsNullOrEmpty(_filePath)) + { + string fileExtension = Path.GetExtension(_filePath); + if (fileExtension != null && fileExtension.Equals(".zip", StringComparison.OrdinalIgnoreCase)) + { + DisableUI(); + Task.Run(() => RunAppPackageFile()); + } + } + using (var openFileDialog = new OpenFileDialog()) { openFileDialog.Filter = "zip files (*.zip)|*.zip|All files (*.*)|*.*"; @@ -130,15 +141,15 @@ namespace WelsonJS.Launcher if (openFileDialog.ShowDialog() == DialogResult.OK) { - string filePath = openFileDialog.FileName; + _filePath = openFileDialog.FileName; DisableUI(); - Task.Run(() => RunAppPackageFile(filePath)); + Task.Run(() => RunAppPackageFile()); } } } - private void RunAppPackageFile(string filePath) + private void RunAppPackageFile() { _instanceId = Guid.NewGuid().ToString(); _workingDirectory = Program.GetWorkingDirectory(_instanceId); @@ -153,7 +164,7 @@ namespace WelsonJS.Launcher } // try to extract ZIP file - ZipFile.ExtractToDirectory(filePath, _workingDirectory); + ZipFile.ExtractToDirectory(_filePath, _workingDirectory); // record the first deploy time RecordFirstDeployTime(_workingDirectory, _instanceId); @@ -339,5 +350,11 @@ namespace WelsonJS.Launcher { Program.OpenWebBrowser(Program.GetAppConfig("RepositoryUrl")); } + + public void RunFromZipFile(string filePath) + { + _filePath = filePath; + btnRunFromZipFile.PerformClick(); + } } } diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs index 616e1a5..fb6a04b 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs @@ -41,8 +41,22 @@ namespace WelsonJS.Launcher } [STAThread] - static void Main() + static void Main(string[] args) { + // if set the target file path + string targetFilePath = GetTargetFilePath(args); + if (!string.IsNullOrEmpty(targetFilePath)) + { + try { + HandleTargetFilePath(targetFilePath); + } + catch (Exception e) + { + _logger.Error($"Initialization failed: {e}"); + } + return; + } + // create the mutex _mutex = new Mutex(true, "WelsonJS.Launcher", out bool createdNew); if (!createdNew) @@ -56,14 +70,165 @@ namespace WelsonJS.Launcher Application.SetCompatibleTextRenderingDefault(false); Application.Run(new MainForm(_logger)); - // destory the mutex - try { + // release the mutex + try + { _mutex.ReleaseMutex(); - } catch { /* ignore if not owned */ } + } + catch { /* ignore if not owned */ } _mutex.Dispose(); } - public static void RunCommandPrompt(string workingDirectory, string entryFileName, string scriptName, bool isConsoleApplication = false, bool isInteractiveServiceAapplication = false) + private static string GetTargetFilePath(string[] args) + { + if (args == null || args.Length == 0) return null; + + for (int i = 0; i < args.Length; i++) + { + string token = args[i]; + if (string.Equals(token, "--file", StringComparison.OrdinalIgnoreCase) || + string.Equals(token, "/file", StringComparison.OrdinalIgnoreCase)) + { + if (i + 1 < args.Length) + { + return args[i + 1]; + } + } + + if (token.StartsWith("--file=", StringComparison.OrdinalIgnoreCase)) + { + return token.Substring("--file=".Length).Trim('"'); + } + } + + return null; + } + + private static void HandleTargetFilePath(string filePath) + { + string fileExtension = Path.GetExtension(filePath); + + if (String.IsNullOrEmpty(fileExtension)) + { + throw new ArgumentException("The file extension is null or empty"); + } + + if (fileExtension.Equals(".zip", StringComparison.OrdinalIgnoreCase)) + { + var mainForm = new MainForm(_logger); + mainForm.Show(); + mainForm.RunFromZipFile(filePath); + return; + } + + if (fileExtension.Equals(".js", StringComparison.OrdinalIgnoreCase)) + { + string workingDirectory = CreateInstanceDirectory(); + + string appRoot = GetAppRootDirectory(); + string appBaseSource = Path.Combine(appRoot, "app.js"); + if (!File.Exists(appBaseSource)) + { + throw new FileNotFoundException("app.js not found in application root.", appBaseSource); + } + + string appBaseDestination = Path.Combine(workingDirectory, "app.js"); + File.Copy(appBaseSource, appBaseDestination, overwrite: true); + + string assetsSource = Path.Combine(appRoot, "app", "assets", "js"); + string assetsDestination = Path.Combine(workingDirectory, "app", "assets", "js"); + CopyDirectoryRecursive(assetsSource, assetsDestination); + + string entrypointDestination = Path.Combine(workingDirectory, "bootstrap.js"); + File.Copy(filePath, entrypointDestination, overwrite: true); + + RunCommandPrompt( + workingDirectory: workingDirectory, + entryFileName: "app.js", + scriptName: "bootstrap", + isConsoleApplication: true, + isInteractiveServiceApplication: false + ); + return; + } + + throw new NotSupportedException($"Unsupported file type: {fileExtension}"); + } + + private static string GetAppRootDirectory() + { + string[] candidates = new[] + { + GetAppConfig("AppRootDirectory"), + AppDomain.CurrentDomain.BaseDirectory, + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "WelsonJS"), + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "WelsonJS"), + }; + + foreach (string dir in candidates) + { + if (string.IsNullOrEmpty(dir)) + continue; + + string appJs = Path.Combine(dir, "app.js"); + if (File.Exists(appJs)) + return dir; + } + + throw new FileNotFoundException("Could not locate app.js in any known application root directory."); + } + + private static string CreateInstanceDirectory() + { + string instanceId = Guid.NewGuid().ToString(); + string workingDirectory = GetWorkingDirectory(instanceId); + + try + { + // check if the working directory exists + if (Directory.Exists(workingDirectory)) + { + throw new InvalidOperationException("GUID validation failed. Directory already exists."); + } + + Directory.CreateDirectory(workingDirectory); + } + catch + { + throw new Exception("Instance Initialization failed"); + } + + return workingDirectory; + } + + private static void CopyDirectoryRecursive(string sourceDir, string destDir) + { + if (!Directory.Exists(sourceDir)) + { + throw new DirectoryNotFoundException("Source directory not found: " + sourceDir); + } + + Directory.CreateDirectory(destDir); + + foreach (var file in Directory.GetFiles(sourceDir, "*", SearchOption.AllDirectories)) + { + string relativePath = file.Substring(sourceDir.Length).TrimStart( + Path.DirectorySeparatorChar, + Path.AltDirectorySeparatorChar + ); + + string targetPath = Path.Combine(destDir, relativePath); + string targetDir = Path.GetDirectoryName(targetPath); + if (!Directory.Exists(targetDir)) + { + Directory.CreateDirectory(targetDir); + } + + File.Copy(file, targetPath, overwrite: true); + } + } + + public static void RunCommandPrompt(string workingDirectory, string entryFileName, string scriptName, bool isConsoleApplication = false, bool isInteractiveServiceApplication = false) { if (!isConsoleApplication) { @@ -93,33 +258,37 @@ namespace WelsonJS.Launcher }; process.Start(); - process.StandardInput.WriteLine("pushd " + workingDirectory); - process.StandardInput.WriteLine(); - process.StandardInput.Flush(); - process.StandardOutput.ReadLine(); + StreamWriter input = process.StandardInput; + StreamReader output = process.StandardOutput; - if (isInteractiveServiceAapplication) + input.WriteLine("pushd " + workingDirectory); + input.WriteLine(); + input.Flush(); + output.ReadLine(); + + if (isInteractiveServiceApplication) { - process.StandardInput.WriteLine($"start cmd /c startInteractiveService.bat"); - process.StandardInput.WriteLine(); - process.StandardInput.Flush(); - process.StandardOutput.ReadLine(); + input.WriteLine($"start cmd /c startInteractiveService.bat"); + input.WriteLine(); + input.Flush(); + output.ReadLine(); } else if (!isConsoleApplication) { - process.StandardInput.WriteLine(entryFileName); - process.StandardInput.WriteLine(); - process.StandardInput.Flush(); - process.StandardOutput.ReadLine(); + input.WriteLine(entryFileName); + input.WriteLine(); + input.Flush(); + output.ReadLine(); } else { - process.StandardInput.WriteLine($"start cmd /c cscript app.js {scriptName}"); - process.StandardInput.WriteLine(); - process.StandardInput.Flush(); - process.StandardOutput.ReadLine(); + input.WriteLine($"start cmd /c cscript app.js {scriptName}"); + input.WriteLine(); + input.Flush(); + output.ReadLine(); } - process.StandardInput.Close(); + input.Close(); + process.WaitForExit(); } diff --git a/setup.iss b/setup.iss index 3469c46..205ea96 100644 --- a/setup.iss +++ b/setup.iss @@ -25,12 +25,24 @@ DisableWelcomePage=yes DisableDirPage=yes DisableProgramGroupPage=yes LicenseFile=SECURITY.MD +ChangesAssociations=yes ; [Registry] ; Root: HKCR; Subkey: "welsonjs"; ValueType: "string"; ValueData: "URL:{cm:AppName}"; Flags: uninsdeletekey ; Root: HKCR; Subkey: "welsonjs"; ValueType: "string"; ValueName: "URL Protocol"; ValueData: "" ; Root: HKCR; Subkey: "welsonjs\DefaultIcon"; ValueType: "string"; ValueData: "{app}\app\favicon.ico,0" ; Root: HKCR; Subkey: "welsonjs\shell\open\command"; ValueType: "string"; ValueData: "cscript ""{app}\app.js"" uriloader ""%1""" +Root: HKCR; Subkey: "WelsonJS.Script"; ValueType: string; ValueData: "WelsonJS Script"; Flags: uninsdeletekey +Root: HKCR; Subkey: "WelsonJS.Script\DefaultIcon"; ValueType: string; ValueData: "{app}\app\favicon.ico,0"; Flags: uninsdeletekey +Root: HKCR; Subkey: "WelsonJS.Script\shell"; ValueType: string; ValueData: "open"; Flags: uninsdeletevalue +Root: HKCR; Subkey: "WelsonJS.Script\shell\open"; ValueType: string; ValueData: "Run with WelsonJS"; Flags: uninsdeletevalue +Root: HKCR; Subkey: "WelsonJS.Script\shell\open\command"; ValueType: string; ValueData: """{app}\bin\x86\WelsonJS.Launcher.exe"" --file ""%1"""; Flags: uninsdeletevalue +Root: HKCR; Subkey: ".js"; ValueType: string; ValueData: "WelsonJS.Script"; Flags: uninsdeletevalue +Root: HKCR; Subkey: ".ts"; ValueType: string; ValueData: "WelsonJS.Script"; Flags: uninsdeletevalue +Root: HKCR; Subkey: ".re"; ValueType: string; ValueData: "WelsonJS.Script"; Flags: uninsdeletevalue +Root: HKCR; Subkey: ".res"; ValueType: string; ValueData: "WelsonJS.Script"; Flags: uninsdeletevalue +Root: HKCR; Subkey: ".ls"; ValueType: string; ValueData: "WelsonJS.Script"; Flags: uninsdeletevalue +Root: HKCR; Subkey: ".coffee"; ValueType: string; ValueData: "WelsonJS.Script"; Flags: uninsdeletevalue [Files] Source: "app.js"; DestDir: "{app}"; From a03ea7f3b3bfae5a2b9d31c79912cdf8cc3e228e Mon Sep 17 00:00:00 2001 From: "Namhyeon, Go" Date: Sat, 15 Nov 2025 18:23:02 +0900 Subject: [PATCH 05/18] Update WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> --- WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs index fb6a04b..99de06f 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs @@ -193,9 +193,18 @@ namespace WelsonJS.Launcher Directory.CreateDirectory(workingDirectory); } - catch + catch (IOException ex) { - throw new Exception("Instance Initialization failed"); + throw new Exception("Instance Initialization failed due to an IO error.", ex); + } + catch (UnauthorizedAccessException ex) + { + throw new Exception("Instance Initialization failed due to insufficient permissions.", ex); + } + catch (InvalidOperationException) + { + // Let InvalidOperationException bubble up as it is thrown intentionally above + throw; } return workingDirectory; From c392b6a08ad68997b2bc2df926bb1baa9be25f87 Mon Sep 17 00:00:00 2001 From: "Namhyeon, Go" Date: Sat, 15 Nov 2025 20:39:11 +0900 Subject: [PATCH 06/18] Update WelsonJS.Toolkit/WelsonJS.Launcher/MainForm.cs Co-authored-by: qodo-merge-pro[bot] <151058649+qodo-merge-pro[bot]@users.noreply.github.com> --- WelsonJS.Toolkit/WelsonJS.Launcher/MainForm.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/MainForm.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/MainForm.cs index 1ccdd06..2f8d849 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/MainForm.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/MainForm.cs @@ -130,6 +130,7 @@ namespace WelsonJS.Launcher { DisableUI(); Task.Run(() => RunAppPackageFile()); + return; } } From daea458f64c2f46c40a07a43ab22fe29246299a7 Mon Sep 17 00:00:00 2001 From: "Namhyeon, Go" Date: Sun, 16 Nov 2025 18:58:48 +0900 Subject: [PATCH 07/18] Update WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs Co-authored-by: qodo-merge-pro[bot] <151058649+qodo-merge-pro[bot]@users.noreply.github.com> --- WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs index 99de06f..d143460 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs @@ -207,25 +207,21 @@ namespace WelsonJS.Launcher throw; } - return workingDirectory; - } - private static void CopyDirectoryRecursive(string sourceDir, string destDir) - { - if (!Directory.Exists(sourceDir)) - { - throw new DirectoryNotFoundException("Source directory not found: " + sourceDir); - } + var sourceDirInfo = new DirectoryInfo(sourceDir); - Directory.CreateDirectory(destDir); - - foreach (var file in Directory.GetFiles(sourceDir, "*", SearchOption.AllDirectories)) - { - string relativePath = file.Substring(sourceDir.Length).TrimStart( - Path.DirectorySeparatorChar, - Path.AltDirectorySeparatorChar - ); + // Create all subdirectories + foreach (DirectoryInfo dir in sourceDirInfo.GetDirectories("*", SearchOption.AllDirectories)) + { + Directory.CreateDirectory(Path.Combine(destDir, dir.FullName.Substring(sourceDirInfo.FullName.Length + 1))); + } + // Copy all files + foreach (FileInfo file in sourceDirInfo.GetFiles("*", SearchOption.AllDirectories)) + { + string targetPath = Path.Combine(destDir, file.FullName.Substring(sourceDirInfo.FullName.Length + 1)); + file.CopyTo(targetPath, true); + } string targetPath = Path.Combine(destDir, relativePath); string targetDir = Path.GetDirectoryName(targetPath); if (!Directory.Exists(targetDir)) From 9c9f8650263298597fdfa070a38e7ebb003bf2dc Mon Sep 17 00:00:00 2001 From: "Namhyeon, Go" Date: Wed, 19 Nov 2025 10:19:57 +0900 Subject: [PATCH 08/18] Add Korea business area code JSON data Introduces a new data file containing business area codes for various regions in Korea. This will be used for region-based business logic or lookups. --- data/korea_business_areacode.json | 173 ++++++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 data/korea_business_areacode.json diff --git a/data/korea_business_areacode.json b/data/korea_business_areacode.json new file mode 100644 index 0000000..c8721c6 --- /dev/null +++ b/data/korea_business_areacode.json @@ -0,0 +1,173 @@ +{ + "seoul": [ + 100, + 101, + 104, + 105, + 106, + 107, + 108, + 109, + 110, + 113, + 114, + 117, + 119, + 120, + 145, + 146, + 147, + 201, + 204, + 206, + 209, + 210, + 211, + 212, + 214, + 215, + 217, + 220, + 230 + ], + "incheon": [ + 121, + 122, + 131, + 137, + 150, + 234, + 800 + ], + "busan": [ + 600, + 602, + 603, + 605, + 606, + 607, + 617, + 621, + 623 + ], + "daegu": [ + 500, + 502, + 503, + 504, + 514, + 516 + ], + "gwangju": [ + 400, + 408, + 409, + 410, + 419 + ], + "daejeon": [ + 300, + 305, + 314, + 318 + ], + "ulsan": [ + 610, + 620 + ], + "sejong": [ + 320 + ], + "gyeonggi": [ + 123, + 124, + 125, + 126, + 127, + 128, + 129, + 130, + 132, + 134, + 135, + 138, + 140, + 141, + 142, + 143, + 144, + 149, + 151, + 152, + 200, + 231, + 232, + 233, + 235, + 236 + ], + "gangwon": [ + 221, + 222, + 223, + 224, + 225, + 226, + 227 + ], + "chungbuk": [ + 301, + 302, + 303, + 304, + 317 + ], + "chungnam": [ + 307, + 308, + 310, + 311, + 312, + 313, + 316, + 319 + ], + "jeonbuk": [ + 401, + 402, + 403, + 404, + 407, + 418 + ], + "jeonnam": [ + 411, + 412, + 415, + 416, + 417 + ], + "gyeongbuk": [ + 505, + 506, + 507, + 508, + 510, + 511, + 512, + 513, + 515 + ], + "gyeongnam": [ + 608, + 609, + 611, + 612, + 613, + 615, + 624 + ], + "jeju": [ + 616 + ] +} From f86ea078c646b33d714857f04ef464b95adc2c22 Mon Sep 17 00:00:00 2001 From: "Namhyeon, Go" Date: Wed, 19 Nov 2025 10:26:53 +0900 Subject: [PATCH 09/18] Add new file to .gitignore Added do_not_push_production_on_friday.js to .gitignore to prevent accidental commits of this file. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 317edf6..ceb65af 100644 --- a/.gitignore +++ b/.gitignore @@ -551,3 +551,4 @@ settings.ini defaultService.js lib/*.private.js data/python313.zip +do_not_push_production_on_friday.js From d156e40e6b76374aff43aa1f1d5864911f446a88 Mon Sep 17 00:00:00 2001 From: "Namhyeon, Go" Date: Wed, 19 Nov 2025 12:23:22 +0900 Subject: [PATCH 10/18] Add Korea business area code checker example Refactored korea_business_areacode.json to include metadata and expanded area code data. Added korea_biz_area_checker.js example script to read Excel files and match business numbers to area names using the updated area code data. --- data/korea_business_areacode.json | 400 +++++++++++++++++------------ examples/korea_biz_area_checker.js | 57 ++++ 2 files changed, 286 insertions(+), 171 deletions(-) create mode 100644 examples/korea_biz_area_checker.js diff --git a/data/korea_business_areacode.json b/data/korea_business_areacode.json index c8721c6..26c7420 100644 --- a/data/korea_business_areacode.json +++ b/data/korea_business_areacode.json @@ -1,173 +1,231 @@ { - "seoul": [ - 100, - 101, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 113, - 114, - 117, - 119, - 120, - 145, - 146, - 147, - 201, - 204, - 206, - 209, - 210, - 211, - 212, - 214, - 215, - 217, - 220, - 230 - ], - "incheon": [ - 121, - 122, - 131, - 137, - 150, - 234, - 800 - ], - "busan": [ - 600, - 602, - 603, - 605, - 606, - 607, - 617, - 621, - 623 - ], - "daegu": [ - 500, - 502, - 503, - 504, - 514, - 516 - ], - "gwangju": [ - 400, - 408, - 409, - 410, - 419 - ], - "daejeon": [ - 300, - 305, - 314, - 318 - ], - "ulsan": [ - 610, - 620 - ], - "sejong": [ - 320 - ], - "gyeonggi": [ - 123, - 124, - 125, - 126, - 127, - 128, - 129, - 130, - 132, - 134, - 135, - 138, - 140, - 141, - 142, - 143, - 144, - 149, - 151, - 152, - 200, - 231, - 232, - 233, - 235, - 236 - ], - "gangwon": [ - 221, - 222, - 223, - 224, - 225, - 226, - 227 - ], - "chungbuk": [ - 301, - 302, - 303, - 304, - 317 - ], - "chungnam": [ - 307, - 308, - 310, - 311, - 312, - 313, - 316, - 319 - ], - "jeonbuk": [ - 401, - 402, - 403, - 404, - 407, - 418 - ], - "jeonnam": [ - 411, - 412, - 415, - 416, - 417 - ], - "gyeongbuk": [ - 505, - 506, - 507, - 508, - 510, - 511, - 512, - 513, - 515 - ], - "gyeongnam": [ - 608, - 609, - 611, - 612, - 613, - 615, - 624 - ], - "jeju": [ - 616 - ] + "title": "Korea Business Number Area Code", + "author": "Namhyeon Go , WelsonJS OSS team", + "website": "https://github.com/gnh1201/welsonjs", + "created_on": "2025-11-19", + "data": { + "seoul": [ + 100, + 101, + 104, + 105, + 106, + 107, + 108, + 109, + 110, + 113, + 114, + 117, + 119, + 120, + 145, + 146, + 147, + 201, + 204, + 206, + 209, + 210, + 211, + 212, + 214, + 215, + 217, + 220, + 230 + ], + "incheon": [ + 121, + 122, + 131, + 137, + 150, + 234, + 800 + ], + "busan": [ + 600, + 602, + 603, + 605, + 606, + 607, + 617, + 621, + 623 + ], + "daegu": [ + 500, + 502, + 503, + 504, + 514, + 516 + ], + "gwangju": [ + 400, + 408, + 409, + 410, + 419 + ], + "daejeon": [ + 300, + 305, + 314, + 318 + ], + "ulsan": [ + 610, + 620 + ], + "sejong": [ + 320 + ], + "gyeonggi": [ + 123, + 124, + 125, + 126, + 127, + 128, + 129, + 130, + 132, + 134, + 135, + 138, + 140, + 141, + 142, + 143, + 144, + 149, + 151, + 152, + 200, + 231, + 232, + 233, + 235, + 236 + ], + "gangwon": [ + 221, + 222, + 223, + 224, + 225, + 226, + 227 + ], + "chungbuk": [ + 301, + 302, + 303, + 304, + 317 + ], + "chungnam": [ + 307, + 308, + 310, + 311, + 312, + 313, + 316, + 319 + ], + "jeonbuk": [ + 401, + 402, + 403, + 404, + 407, + 418 + ], + "jeonnam": [ + 411, + 412, + 415, + 416, + 417 + ], + "gyeongbuk": [ + 505, + 506, + 507, + 508, + 510, + 511, + 512, + 513, + 515 + ], + "gyeongnam": [ + 608, + 609, + 611, + 612, + 613, + 615, + 624 + ], + "jeju": [ + 616 + ], + "p_seoul": [ + 116, + 775 + ], + "p_incheon": [ + 172, + 139 + ], + "p_gyeonggi": [ + 696, + 276, + 585, + 133, + 687, + 509, + 581, + 557, + 405, + 727, + 514, + 371 + ], + "p_busan": [ + 825, + 644, + 449 + ], + "p_daejeon": [ + 306 + ], + "p_sejong": [ + 330, + 698 + ], + "p_gwangju": [ + 256 + ], + "p_gyeongbuk": [ + 568 + ], + "p_chungnam": [ + 709, + 294 + ], + "p_chungbuk": [ + 203, + 136 + ], + "p_jeonbuk": [ + 586, + 704 + ] + } } diff --git a/examples/korea_biz_area_checker.js b/examples/korea_biz_area_checker.js new file mode 100644 index 0000000..96b3f17 --- /dev/null +++ b/examples/korea_biz_area_checker.js @@ -0,0 +1,57 @@ +// korea_biz_area_checker.js +// Namhyeon Go , WelsonJS OSS team +// https://github.com/gnh1201/welsonjs +// +var FILE = require("lib/file"); +var Office = require("lib/msoffice"); + +function main(args) { + // Business Area Code in Korea + console.log("> Business Area Code in Korea"); + var data_biz_areacode = JSON.parse(FILE.readFile("data/korea_business_areacode.json")).data; + console.log(JSON.stringify(data_biz_areacode)); + + // Open the Microsoft Excel file + console.log("> Open the Microsoft Excel file"); + var excel = new Office.Excel(); + excel.open("data\\example.xlsx"); + + // Retrieve the business area code + for (var i = 3; i < 1233; i++) { + try { + // check the biz area name + console.log(">> check the biz area name"); + var areaname = excel.getCellByPosition(i, 16).getValue(); + if (!!areaname) + continue; + + // check the biz number + console.log(">> check the biz number"); + var biznumber = excel.getCellByPosition(i, 8).getValue(); + if (!biznumber) + continue; + + // match the biznumber to biz area code data + console.log(">> match the biznumber to biz area code data: " + biznumber); + var matched_areaname = "unknown"; + for (var k in data_biz_areacode) { + var areacode = parseInt(biznumber.substring(0, 3)); + if (data_biz_areacode[k].indexOf(areacode) > -1) { + matched_areaname = k; + break; + } + } + + console.log(">> write the matched area name: " + matched_areaname); + excel.getCellByPosition(i, 16).setValue(matched_areaname); + } catch (e) { + excel.getCellByPosition(i, 16).setValue("unknown"); + } + } + + excel.saveAs("example_edited.xlsx"); + + excel.close(); +} + +exports.main = main; \ No newline at end of file From 6f172aafe1ea5d3db8e81ef1198c6e121d6de484 Mon Sep 17 00:00:00 2001 From: "Namhyeon, Go" Date: Thu, 20 Nov 2025 14:56:59 +0900 Subject: [PATCH 11/18] Revert "Enable COM interop registration in project config" This reverts commit 4fb7b95c0e0a396e2ae91b3059c00c667ab25002. --- .../WelsonJS.Cryptography/WelsonJS.Cryptography.vbproj | 4 ---- 1 file changed, 4 deletions(-) diff --git a/WelsonJS.Toolkit/WelsonJS.Cryptography/WelsonJS.Cryptography.vbproj b/WelsonJS.Toolkit/WelsonJS.Cryptography/WelsonJS.Cryptography.vbproj index e1480c0..71bb12b 100644 --- a/WelsonJS.Toolkit/WelsonJS.Cryptography/WelsonJS.Cryptography.vbproj +++ b/WelsonJS.Toolkit/WelsonJS.Cryptography/WelsonJS.Cryptography.vbproj @@ -17,22 +17,18 @@ True - True True - True True - True True - True From 33c64d50c9e09a07503a9af0f507006f915ce473 Mon Sep 17 00:00:00 2001 From: "Namhyeon, Go" Date: Thu, 20 Nov 2025 16:44:20 +0900 Subject: [PATCH 12/18] Add afterInstall.ps1 and update tool paths to APPDATA Introduces afterInstall.ps1 for post-install setup, downloading and extracting required tools to the APPDATA\WelsonJS directory. Updates code in http.js, python3.js, ovftool.js, wamr.js, and system.js to reference binaries from the new APPDATA location. Modifies setup.iss to run the PowerShell script after installation. --- afterInstall.ps1 | 297 +++++++++++++++++++++++++++++++++++++++++++++++ lib/http.js | 21 ++-- lib/ovftool.js | 7 +- lib/python3.js | 19 +-- lib/system.js | 7 +- lib/wamr.js | 11 +- setup.iss | 1 + 7 files changed, 341 insertions(+), 22 deletions(-) create mode 100644 afterInstall.ps1 diff --git a/afterInstall.ps1 b/afterInstall.ps1 new file mode 100644 index 0000000..fcaf232 --- /dev/null +++ b/afterInstall.ps1 @@ -0,0 +1,297 @@ +# ================================ +# CONFIGURATION +# ================================ +$AppName = "welsonjs" +$TargetDir = Join-Path $env:APPDATA $AppName +$TmpDir = Join-Path $env:TEMP "$AppName-downloads" + +Write-Host "" +Write-Host "[*] Target directory : $TargetDir" +Write-Host "[*] Temporary directory: $TmpDir" +Write-Host "" + +# Ensure base directories exist +New-Item -ItemType Directory -Path $TargetDir -Force | Out-Null +New-Item -ItemType Directory -Path $TmpDir -Force | Out-Null + + +# ================================ +# ARCHITECTURE DETECTION +# ================================ +$arch = "x86" +if ($env:PROCESSOR_ARCHITECTURE -eq "AMD64") { $arch = "x64" } +elseif ($env:PROCESSOR_ARCHITECTURE -eq "ARM64") { $arch = "arm64" } +if ($env:PROCESSOR_ARCHITEW6432) { $arch = "x64" } + +Write-Host "[*] Detected architecture: $arch" +Write-Host "" + + +# ================================ +# HELPER FUNCTIONS +# ================================ + +function Ensure-EmptyDirectory { + param( + [Parameter(Mandatory=$true)] + [string]$Path + ) + + # If a file exists at the path, remove it + if (Test-Path $Path -PathType Leaf) { + Write-Host "[WARN] A file exists at path '$Path'. Deleting it..." + Remove-Item -Path $Path -Force + } + + # Ensure directory exists + if (-not (Test-Path $Path -PathType Container)) { + Write-Host "[*] Creating directory: $Path" + New-Item -ItemType Directory -Path $Path -Force | Out-Null + } +} + +function Download-File { + param( + [Parameter(Mandatory=$true)] + [string]$Url, + [Parameter(Mandatory=$true)] + [string]$Destination + ) + + Write-Host "[*] Downloading file..." + Write-Host " URL : $Url" + Write-Host " OUT : $Destination" + + try { + Invoke-WebRequest -Uri $Url -OutFile $Destination -UseBasicParsing -ErrorAction Stop + Write-Host "[OK] Download completed." + Write-Host "" + } + catch { + Write-Host "[ERROR] Failed to download: $Url" + Write-Host $_.Exception.Message + throw + } +} + +function Extract-Zip { + param( + [Parameter(Mandatory=$true)] + [string]$ZipPath, + [Parameter(Mandatory=$true)] + [string]$DestDir + ) + + Write-Host "[*] Extracting ZIP:" + Write-Host " $ZipPath" + Write-Host " -> $DestDir" + + # Make sure destination directory exists and is a directory + Ensure-EmptyDirectory -Path $DestDir + + # Temporary extraction folder inside destination + $TmpExtract = Join-Path $DestDir "__tmp_extract__" + Ensure-EmptyDirectory -Path $TmpExtract + + Write-Host "[DEBUG] PowerShell command:" + Write-Host " Expand-Archive -LiteralPath `"$ZipPath`" -DestinationPath `"$TmpExtract`" -Force" + + try { + Expand-Archive -LiteralPath $ZipPath -DestinationPath $TmpExtract -Force -ErrorAction Stop + } + catch { + Write-Host "[ERROR] Failed to extract ZIP with Expand-Archive: $ZipPath" + Write-Host $_.Exception.Message + throw + } + + # Check extracted entries + $entries = Get-ChildItem -LiteralPath $TmpExtract + + if (-not $entries -or $entries.Count -eq 0) { + Write-Host "[ERROR] No entries were extracted from ZIP: $ZipPath" + throw "ZIP appears to be empty or extraction failed." + } + + if ($entries.Count -eq 1 -and $entries[0].PSIsContainer) { + Write-Host "[*] Detected single root directory inside ZIP. Flattening..." + + # Move children of the single root directory into destination + $innerItems = Get-ChildItem -LiteralPath $entries[0].FullName + foreach ($item in $innerItems) { + Move-Item -LiteralPath $item.FullName -Destination $DestDir -Force + } + } + else { + Write-Host "[*] ZIP has multiple top-level entries. Copying all..." + + foreach ($item in $entries) { + Move-Item -LiteralPath $item.FullName -Destination $DestDir -Force + } + } + + # Clean up temp extraction folder + Remove-Item -LiteralPath $TmpExtract -Recurse -Force + + Write-Host "[OK] ZIP extraction completed." + Write-Host "" +} + +function Extract-TarGz { + param( + [Parameter(Mandatory=$true)] + [string]$TarGzPath, + [Parameter(Mandatory=$true)] + [string]$DestDir + ) + + Write-Host "[*] Extracting TAR.GZ:" + Write-Host " $TarGzPath" + Write-Host " -> $DestDir" + + Ensure-EmptyDirectory -Path $DestDir + + # Modern Windows ships with tar, but we validate its presence + $tarCmd = Get-Command tar -ErrorAction SilentlyContinue + if (-not $tarCmd) { + Write-Host "[ERROR] 'tar' command not found. Cannot extract TAR.GZ." + throw "tar command not found." + } + + Write-Host "[DEBUG] tar command:" + Write-Host " tar -xzf `"$TarGzPath`" -C `"$DestDir`"" + + try { + & tar -xzf "$TarGzPath" -C "$DestDir" + if ($LASTEXITCODE -ne 0) { + throw "tar exit code $LASTEXITCODE" + } + Write-Host "[OK] TAR.GZ extraction completed." + Write-Host "" + } + catch { + Write-Host "[ERROR] Failed to extract TAR.GZ: $TarGzPath" + Write-Host $_.Exception.Message + throw + } +} + + +# ================================ +# SET DOWNLOAD URLS BASED ON ARCH +# ================================ +$PythonUrl = $null +$CurlUrl = $null +$YaraUrl = $null +$WamrUrl = $null + +switch ($arch) { + "x64" { + # Python embeddable (x64) + $PythonUrl = "https://www.python.org/ftp/python/3.13.9/python-3.13.9-embeddable-amd64.zip" + + # curl (x64, mingw) + $CurlUrl = "https://curl.se/windows/latest.cgi?p=win64-mingw.zip" + + # YARA (x64, GitHub — as you specified) + $YaraUrl = "https://github.com/VirusTotal/yara/releases/download/v4.5.5/yara-4.5.5-2368-win64.zip" + + # WAMR (x64 only) + $WamrUrl = "https://github.com/bytecodealliance/wasm-micro-runtime/releases/download/WAMR-2.4.3/iwasm-2.4.3-x86_64-windows-2022.tar.gz" + } + + "arm64" { + # Python embeddable (ARM64) + $PythonUrl = "https://www.python.org/ftp/python/3.13.9/python-3.13.9-embeddable-arm64.zip" + + # curl (ARM64) + $CurlUrl = "https://curl.se/windows/latest.cgi?p=win64a-mingw.zip" + + # DO NOT install YARA/WAMR on ARM64 + $YaraUrl = $null + $WamrUrl = $null + } + + default { + # Treat anything else as x86 + # Python embeddable (x86) + $PythonUrl = "https://www.python.org/ftp/python/3.13.9/python-3.13.9-embeddable-win32.zip" + + # curl (x86) + $CurlUrl = "https://downloads.sourceforge.net/project/muldersoft/cURL/curl-8.17.0-win-x86-full.2025-11-09.zip"; + + # Do NOT install YARA/WAMR on x86 (same policy as before) + $YaraUrl = $null + $WamrUrl = $null + } +} + +Write-Host "[*] Python URL: $PythonUrl" +Write-Host "[*] curl URL : $CurlUrl" +if ($YaraUrl) { + Write-Host "[*] YARA URL : $YaraUrl" +} else { + Write-Host "[*] YARA : skipped on this architecture" +} +if ($WamrUrl) { + Write-Host "[*] WAMR URL : $WamrUrl" +} else { + Write-Host "[*] WAMR : skipped on this architecture" +} +Write-Host "" + + +# ================================ +# DOWNLOAD FILES +# ================================ +$PythonZip = Join-Path $TmpDir "python.zip" +$CurlZip = Join-Path $TmpDir "curl.zip" +$YaraZip = Join-Path $TmpDir "yara.zip" +$WamrTgz = Join-Path $TmpDir "wamr.tar.gz" + +try { + Download-File -Url $PythonUrl -Destination $PythonZip + Download-File -Url $CurlUrl -Destination $CurlZip + + if ($YaraUrl) { + Download-File -Url $YaraUrl -Destination $YaraZip + } + + if ($WamrUrl) { + Download-File -Url $WamrUrl -Destination $WamrTgz + } +} +catch { + Write-Host "[FATAL] Download phase failed." + exit 1 +} + + +# ================================ +# EXTRACT FILES +# ================================ +try { + Extract-Zip -ZipPath $PythonZip -DestDir (Join-Path $TargetDir "python") + Extract-Zip -ZipPath $CurlZip -DestDir (Join-Path $TargetDir "curl") + + if ($YaraUrl) { + Extract-Zip -ZipPath $YaraZip -DestDir (Join-Path $TargetDir "yara") + } + + if ($WamrUrl) { + Extract-TarGz -TarGzPath $WamrTgz -DestDir (Join-Path $TargetDir "wamr") + } +} +catch { + Write-Host "[FATAL] Extraction phase failed." + exit 1 +} + + +# ================================ +# FINISH +# ================================ +Write-Host "[*] All tools installed successfully." +Write-Host "[*] Installed into: $TargetDir" +Write-Host "" +exit 0 diff --git a/lib/http.js b/lib/http.js index 47605e9..cdebb6b 100644 --- a/lib/http.js +++ b/lib/http.js @@ -119,16 +119,21 @@ var HTTPObject = function(engine) { } else if (this.engine == "CURL") { this._interface = SHELL.create(); - // the location of cURL binary - var arch = SYS.getArch(); - if (arch.toLowerCase().indexOf("arm") > -1) { - this.setBinPath("bin\\arm64\\curl-" + this.curlVersion + "-win64a-mingw\\bin\\curl.exe"); - } else if (arch.indexOf("64") > -1) { - this.setBinPath("bin\\x64\\curl-" + this.curlVersion + "-win64-mingw\\bin\\curl.exe"); + // set cURL executable file location + var default_curl_path = SYS.getAppDataDir() + "\\curl\\curl.exe"; + if (FILE.fileExists(default_curl_path)) { + this.setBinPath(default_curl_path); } else { - this.setBinPath("bin\\x86\\curl-" + this.curlVersion + "-win32-mingw\\bin\\curl.exe"); + var arch = SYS.getArch(); + if (arch.toLowerCase().indexOf("arm") > -1) { + this.setBinPath("bin\\arm64\\curl-" + this.curlVersion + "-win64a-mingw\\bin\\curl.exe"); + } else if (arch.indexOf("64") > -1) { + this.setBinPath("bin\\x64\\curl-" + this.curlVersion + "-win64-mingw\\bin\\curl.exe"); + } else { + this.setBinPath("bin\\x86\\curl-" + this.curlVersion + "-win32-mingw\\bin\\curl.exe"); + } } - + // do not clear after calling the `exec` this._interface.setIsPreventClear(true); } else if (this.engine == "BITS") { diff --git a/lib/ovftool.js b/lib/ovftool.js index 311b6f0..679d542 100644 --- a/lib/ovftool.js +++ b/lib/ovftool.js @@ -1,16 +1,17 @@ // ovftool.js -// Copyright 2019-2025, Namhyeon Go and the WelsonJS contributors. +// Namhyeon Go and the WelsonJS contributors. // SPDX-License-Identifier: GPL-3.0-or-later // https://github.com/gnh1201/welsonjs // // Download OVFTool (Open Virtualization Format (OVF) Tool): // https://developer.broadcom.com/tools/open-virtualization-format-ovf-tool/latest // +var SYS = require("lib/system"); var SHELL = require("lib/shell"); var CRED = require("lib/credentials"); function OVFObject() { - this.binPath = "bin\\x64\\VMware-ovftool-4.6.3-24031167-win.x86_64\\ovftool\\ovftool.exe"; + this.binPath = SYS.getAppDataDir() + "\\ovftool\\ovftool.exe"; this.hostname = ""; this.port = 443; this.resourceName = ""; @@ -66,7 +67,7 @@ function create() { exports.setCredential = setCredential; exports.create = create; -exports.VERSIONINFO = "Broadcom/VMware OVF Tool interface (ovftool.js) version 0.1.2"; +exports.VERSIONINFO = "Broadcom/VMware OVF Tool interface (ovftool.js) version 0.1.3"; exports.AUTHOR = "gnh1201@catswords.re.kr"; exports.global = global; exports.require = global.require; diff --git a/lib/python3.js b/lib/python3.js index 37635c0..5231bfe 100644 --- a/lib/python3.js +++ b/lib/python3.js @@ -13,13 +13,18 @@ function PythonObject() { this.version = "3.13.2"; this.create = function() { - var arch = SYS.getArch(); - if (arch.toLowerCase().indexOf("arm") > -1) { - this.setBinPath("bin\\arm64\\python-" + this.version + "-embed-arm64\\python.exe"); - } else if (arch.indexOf("64") > -1) { - this.setBinPath("bin\\x64\\python-" + this.version + "-embed-amd64\\python.exe"); + var default_python3_path = SYS.getAppDataDir() + "\\python\\python.exe"; + if (FILE.fileExists(default_python3_path)) { + this.setBinPath(default_python3_path); } else { - this.setBinPath("bin\\x86\\python-" + this.version + "-embed-win32\\python.exe"); + var arch = SYS.getArch(); + if (arch.toLowerCase().indexOf("arm") > -1) { + this.setBinPath("bin\\arm64\\python-" + this.version + "-embed-arm64\\python.exe"); + } else if (arch.indexOf("64") > -1) { + this.setBinPath("bin\\x64\\python-" + this.version + "-embed-amd64\\python.exe"); + } else { + this.setBinPath("bin\\x86\\python-" + this.version + "-embed-win32\\python.exe"); + } } }; @@ -57,6 +62,6 @@ exports.execScript = function(scriptName, args) { return (new PythonObject()).execScript(scriptName, args); }; -exports.VERSIONINFO = "Python Interface (python3.js) version 0.2.1"; +exports.VERSIONINFO = "Python Interface (python3.js) version 0.2.2"; exports.global = global; exports.require = global.require; diff --git a/lib/system.js b/lib/system.js index 37f8a0f..cd4fb90 100644 --- a/lib/system.js +++ b/lib/system.js @@ -37,6 +37,10 @@ function getEnvString(envName) { })(envName.toUpperCase()); } +function getAppDataDir() { + return getEnvString("APPDATA") + "\\WelsonJS"; +} + function get32BitFolder() { var base = getEnvString("WINDIR"); var syswow64 = base + "\\SysWOW64\\"; @@ -198,6 +202,7 @@ function getProcessVersion() { exports.createProcess = createProcess; exports.getEnvString = getEnvString; +exports.getAppDataDir = getAppDataDir; exports.get32BitFolder = get32BitFolder; exports.isElevated = isElevated; exports.getOS = getOS; @@ -219,6 +224,6 @@ exports.createShortcut = createShortcut; exports.ping = ping; exports.getProcessVersion = getProcessVersion; -exports.VERSIONINFO = "System Module (system.js) version 0.1.5"; +exports.VERSIONINFO = "System Module (system.js) version 0.1.6"; exports.global = global; exports.require = global.require; diff --git a/lib/wamr.js b/lib/wamr.js index 83d2573..0e3b550 100644 --- a/lib/wamr.js +++ b/lib/wamr.js @@ -1,15 +1,16 @@ // wamr.js -// Copyright 2019-2025, Namhyeon Go and the WelsonJS contributors. +// Namhyeon Go and the WelsonJS contributors. // SPDX-License-Identifier: GPL-3.0-or-later // https://github.com/gnh1201/welsonjs // -// WAMR(WebAssembly Micro Runtime) integration for WelsonJS framework +// WAMR(WebAssembly Micro Runtime) integration // https://github.com/bytecodealliance/wasm-micro-runtime // +var SYS = require("lib/system"); var SHELL = require("lib/shell"); var WAMRObject = function() { - this.binPath = "bin\\iwasm"; + this.binPath = SYS.getAppDataDir() + "\\wamr\\iwasm.exe"; this.verbose = 0; this.stackSize = 0; @@ -60,3 +61,7 @@ var WAMRObject = function() { exports.create = function() { return new WAMRObject(); }; + +exports.VERSIONINFO = "WAMR(WebAssembly Micro Runtime) integration (wamr.js) version 0.1.1"; +exports.global = global; +exports.require = global.require; diff --git a/setup.iss b/setup.iss index 205ea96..51fc7e9 100644 --- a/setup.iss +++ b/setup.iss @@ -84,6 +84,7 @@ Name: "{group}\Uninstall {cm:AppName}"; Filename: "{uninstallexe}"; AfterInstall ; Filename: {app}\bin\gtk2-runtime-2.24.33-2021-01-30-ts-win64.exe; ; Filename: {app}\bin\nmap-7.92\VC_redist.x86.exe; ; Filename: {app}\bin\nmap-7.92\npcap-1.50.exe; +Filename: "powershell.exe"; Parameters: "-ExecutionPolicy Bypass -NoProfile -File ""{app}\afterInstall.ps1"""; WorkingDir: "{app}"; Flags: nowait Filename: {app}\installService.bat; Flags: nowait Filename: {app}\bin\x86\WelsonJS.Launcher.exe; Flags: nowait From afe7d6213a38a25535a8dc736c2d710ca1f1027d Mon Sep 17 00:00:00 2001 From: "Namhyeon, Go" Date: Thu, 20 Nov 2025 17:09:43 +0900 Subject: [PATCH 13/18] Refactor deploy time recording and improve instance setup Moved RecordFirstDeployTime from MainForm to Program for better separation of concerns. Updated instance directory creation and asset copying logic in Program.cs to support JS entrypoints, including library copying and first deploy time recording. Added a sleep call to helloworld.js for demonstration purposes. --- .../WelsonJS.Launcher/MainForm.cs | 36 +------ WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs | 99 +++++++++++++------ helloworld.js | 2 + 3 files changed, 71 insertions(+), 66 deletions(-) diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/MainForm.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/MainForm.cs index 2f8d849..cc4cd79 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/MainForm.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/MainForm.cs @@ -168,7 +168,7 @@ namespace WelsonJS.Launcher ZipFile.ExtractToDirectory(_filePath, _workingDirectory); // record the first deploy time - RecordFirstDeployTime(_workingDirectory, _instanceId); + Program.RecordFirstDeployTime(_workingDirectory, _instanceId); // follow the sub-directory _workingDirectory = Program.GetWorkingDirectory(_instanceId, true); @@ -207,40 +207,6 @@ namespace WelsonJS.Launcher return Program._resourceServer.IsRunning(); } - private void RecordFirstDeployTime(string directory, string instanceId) - { - // get current time - DateTime now = DateTime.Now; - - // record to the metadata database - InstancesForm instancesForm = new InstancesForm(); - try - { - instancesForm.GetDatabaseInstance().Insert(new Dictionary - { - ["InstanceId"] = instanceId, - ["FirstDeployTime"] = now - }, out _); - } - catch (Exception ex) - { - _logger.Error($"Failed to record first deploy time: {ex.Message}"); - } - instancesForm.Dispose(); - - // record to the instance directory - try - { - string filePath = Path.Combine(directory, ".welsonjs_first_deploy_time"); - string text = now.ToString(_dateTimeFormat); - File.WriteAllText(filePath, text); - } - catch (Exception ex) - { - _logger.Error($"Failed to record first deploy time: {ex.Message}"); - } - } - private bool IsInAdministrator() { try diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs index d143460..40eb3f8 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs @@ -4,12 +4,13 @@ // https://github.com/gnh1201/welsonjs // using System; +using System.Collections.Generic; +using System.Configuration; using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; using System.Windows.Forms; -using System.Configuration; namespace WelsonJS.Launcher { @@ -19,9 +20,13 @@ namespace WelsonJS.Launcher public static Mutex _mutex; public static ResourceServer _resourceServer; + public static string _dateTimeFormat; static Program() { + // get the date time format + _dateTimeFormat = GetAppConfig("DateTimeFormat"); + // set up logger _logger = new TraceLogger(); @@ -79,6 +84,40 @@ namespace WelsonJS.Launcher _mutex.Dispose(); } + public static void RecordFirstDeployTime(string directory, string instanceId) + { + // get current time + DateTime now = DateTime.Now; + + // record to the metadata database + InstancesForm instancesForm = new InstancesForm(); + try + { + instancesForm.GetDatabaseInstance().Insert(new Dictionary + { + ["InstanceId"] = instanceId, + ["FirstDeployTime"] = now + }, out _); + } + catch (Exception ex) + { + _logger.Error($"Failed to record first deploy time: {ex.Message}"); + } + instancesForm.Dispose(); + + // record to the instance directory + try + { + string filePath = Path.Combine(directory, ".welsonjs_first_deploy_time"); + string text = now.ToString(_dateTimeFormat); + File.WriteAllText(filePath, text); + } + catch (Exception ex) + { + _logger.Error($"Failed to record first deploy time: {ex.Message}"); + } + } + private static string GetTargetFilePath(string[] args) { if (args == null || args.Length == 0) return null; @@ -115,15 +154,13 @@ namespace WelsonJS.Launcher if (fileExtension.Equals(".zip", StringComparison.OrdinalIgnoreCase)) { - var mainForm = new MainForm(_logger); - mainForm.Show(); - mainForm.RunFromZipFile(filePath); - return; + throw new NotImplementedException("Not implemented yet."); } if (fileExtension.Equals(".js", StringComparison.OrdinalIgnoreCase)) { - string workingDirectory = CreateInstanceDirectory(); + string instanceId = Guid.NewGuid().ToString(); + string workingDirectory = CreateInstanceDirectory(instanceId); string appRoot = GetAppRootDirectory(); string appBaseSource = Path.Combine(appRoot, "app.js"); @@ -139,9 +176,15 @@ namespace WelsonJS.Launcher string assetsDestination = Path.Combine(workingDirectory, "app", "assets", "js"); CopyDirectoryRecursive(assetsSource, assetsDestination); + string libSource = Path.Combine(appRoot, "lib"); + string libDestination = Path.Combine(workingDirectory, "lib"); + CopyDirectoryRecursive(libSource, libDestination); + string entrypointDestination = Path.Combine(workingDirectory, "bootstrap.js"); File.Copy(filePath, entrypointDestination, overwrite: true); + RecordFirstDeployTime(workingDirectory, instanceId); + RunCommandPrompt( workingDirectory: workingDirectory, entryFileName: "app.js", @@ -178,9 +221,8 @@ namespace WelsonJS.Launcher throw new FileNotFoundException("Could not locate app.js in any known application root directory."); } - private static string CreateInstanceDirectory() + private static string CreateInstanceDirectory(string instanceId) { - string instanceId = Guid.NewGuid().ToString(); string workingDirectory = GetWorkingDirectory(instanceId); try @@ -193,35 +235,30 @@ namespace WelsonJS.Launcher Directory.CreateDirectory(workingDirectory); } - catch (IOException ex) + catch { - throw new Exception("Instance Initialization failed due to an IO error.", ex); - } - catch (UnauthorizedAccessException ex) - { - throw new Exception("Instance Initialization failed due to insufficient permissions.", ex); - } - catch (InvalidOperationException) - { - // Let InvalidOperationException bubble up as it is thrown intentionally above - throw; + throw new Exception("Instance Initialization failed"); } + return workingDirectory; + } - var sourceDirInfo = new DirectoryInfo(sourceDir); + private static void CopyDirectoryRecursive(string sourceDir, string destDir) + { + if (!Directory.Exists(sourceDir)) + { + throw new DirectoryNotFoundException("Source directory not found: " + sourceDir); + } - // Create all subdirectories - foreach (DirectoryInfo dir in sourceDirInfo.GetDirectories("*", SearchOption.AllDirectories)) - { - Directory.CreateDirectory(Path.Combine(destDir, dir.FullName.Substring(sourceDirInfo.FullName.Length + 1))); - } + Directory.CreateDirectory(destDir); + + foreach (var file in Directory.GetFiles(sourceDir, "*", SearchOption.AllDirectories)) + { + string relativePath = file.Substring(sourceDir.Length).TrimStart( + Path.DirectorySeparatorChar, + Path.AltDirectorySeparatorChar + ); - // Copy all files - foreach (FileInfo file in sourceDirInfo.GetFiles("*", SearchOption.AllDirectories)) - { - string targetPath = Path.Combine(destDir, file.FullName.Substring(sourceDirInfo.FullName.Length + 1)); - file.CopyTo(targetPath, true); - } string targetPath = Path.Combine(destDir, relativePath); string targetDir = Path.GetDirectoryName(targetPath); if (!Directory.Exists(targetDir)) diff --git a/helloworld.js b/helloworld.js index 4829b83..ecd65a0 100644 --- a/helloworld.js +++ b/helloworld.js @@ -14,6 +14,8 @@ function main(args) { } catch (e) { console.error("lib/http: Something wrong"); } + + sleep(100000); //Toolkit.create(); } From b3b00771c5607fe706ae731086d599101a05739b Mon Sep 17 00:00:00 2001 From: "Namhyeon, Go" Date: Thu, 20 Nov 2025 17:14:49 +0900 Subject: [PATCH 14/18] Refactor zip file run logic in MainForm Removed the _filePath field and refactored zip file handling to pass file paths directly as parameters. Simplified the RunAppPackageFile workflow and eliminated redundant methods for improved clarity and maintainability. --- .../WelsonJS.Launcher/MainForm.cs | 31 ++++--------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/MainForm.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/MainForm.cs index cc4cd79..739acf7 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/MainForm.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/MainForm.cs @@ -20,7 +20,6 @@ namespace WelsonJS.Launcher private readonly string _dateTimeFormat; private readonly ICompatibleLogger _logger; - private string _filePath; private string _workingDirectory; private string _instanceId; private string _scriptName; @@ -123,17 +122,6 @@ namespace WelsonJS.Launcher private void btnRunFromZipFile_Click(object sender, EventArgs e) { - if (!String.IsNullOrEmpty(_filePath)) - { - string fileExtension = Path.GetExtension(_filePath); - if (fileExtension != null && fileExtension.Equals(".zip", StringComparison.OrdinalIgnoreCase)) - { - DisableUI(); - Task.Run(() => RunAppPackageFile()); - return; - } - } - using (var openFileDialog = new OpenFileDialog()) { openFileDialog.Filter = "zip files (*.zip)|*.zip|All files (*.*)|*.*"; @@ -142,15 +130,15 @@ namespace WelsonJS.Launcher if (openFileDialog.ShowDialog() == DialogResult.OK) { - _filePath = openFileDialog.FileName; + string filePath = openFileDialog.FileName; DisableUI(); - Task.Run(() => RunAppPackageFile()); + Task.Run(() => RunAppPackageFile(filePath)); } } } - private void RunAppPackageFile() + private void RunAppPackageFile(string filePath) { _instanceId = Guid.NewGuid().ToString(); _workingDirectory = Program.GetWorkingDirectory(_instanceId); @@ -165,7 +153,7 @@ namespace WelsonJS.Launcher } // try to extract ZIP file - ZipFile.ExtractToDirectory(_filePath, _workingDirectory); + ZipFile.ExtractToDirectory(filePath, _workingDirectory); // record the first deploy time Program.RecordFirstDeployTime(_workingDirectory, _instanceId); @@ -178,7 +166,7 @@ namespace WelsonJS.Launcher } catch (Exception ex) { - SafeInvoke(() => MessageBox.Show($"Extraction failed: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)); + SafeInvoke(() => MessageBox.Show($"Extraction failed: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)); } // Enable UI @@ -206,7 +194,6 @@ namespace WelsonJS.Launcher return Program._resourceServer.IsRunning(); } - private bool IsInAdministrator() { try @@ -317,11 +304,5 @@ namespace WelsonJS.Launcher { Program.OpenWebBrowser(Program.GetAppConfig("RepositoryUrl")); } - - public void RunFromZipFile(string filePath) - { - _filePath = filePath; - btnRunFromZipFile.PerformClick(); - } } -} +} \ No newline at end of file From 1a75388a6954921e5918cf1c92274f740ed27e73 Mon Sep 17 00:00:00 2001 From: "Namhyeon, Go" Date: Thu, 20 Nov 2025 17:21:47 +0900 Subject: [PATCH 15/18] Fix relative path calculation in file copy loop Normalizes source directory path before calculating relative file paths to ensure correct substring indexing and prevent off-by-one errors when copying files. --- WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs index 40eb3f8..a9a9c56 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs @@ -254,11 +254,11 @@ namespace WelsonJS.Launcher foreach (var file in Directory.GetFiles(sourceDir, "*", SearchOption.AllDirectories)) { - string relativePath = file.Substring(sourceDir.Length).TrimStart( + string normalizedSource = sourceDir.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + string relativePath = file.Substring(normalizedSource.Length + 1).TrimStart( Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar ); - string targetPath = Path.Combine(destDir, relativePath); string targetDir = Path.GetDirectoryName(targetPath); if (!Directory.Exists(targetDir)) From 186561639ec17f21ebb48cf404c29d3128a0c3b7 Mon Sep 17 00:00:00 2001 From: "Namhyeon, Go" Date: Thu, 20 Nov 2025 17:24:26 +0900 Subject: [PATCH 16/18] Add FILE module import and init PythonObject Imported the FILE module in python3.js and updated PythonObject to initialize the default Python binary path upon creation. Also removed the copyright year range from the file header. --- lib/python3.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/python3.js b/lib/python3.js index 5231bfe..8dba945 100644 --- a/lib/python3.js +++ b/lib/python3.js @@ -1,5 +1,5 @@ // python3.js -// Copyright 2019-2025, Namhyeon Go and the WelsonJS contributors. +// Namhyeon Go and the WelsonJS contributors. // SPDX-License-Identifier: GPL-3.0-or-later // https://github.com/gnh1201/welsonjs // @@ -7,6 +7,7 @@ // var SYS = require("lib/system"); var SHELL = require("lib/shell"); +var FILE = require("lib/file"); function PythonObject() { this.binPath = null; @@ -50,6 +51,9 @@ function PythonObject() { scriptName ].concat(args)); }; + + // Initialize default Python binary path based on current version and system arch + this.create(); } exports.PythonObject = PythonObject; From 4bd05fd2a2972119df63e5ac7d828c6a3049ddfe Mon Sep 17 00:00:00 2001 From: "Namhyeon, Go" Date: Thu, 20 Nov 2025 17:26:11 +0900 Subject: [PATCH 17/18] Update afterInstall.ps1 Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- afterInstall.ps1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/afterInstall.ps1 b/afterInstall.ps1 index fcaf232..03117f9 100644 --- a/afterInstall.ps1 +++ b/afterInstall.ps1 @@ -21,7 +21,8 @@ New-Item -ItemType Directory -Path $TmpDir -Force | Out-Null $arch = "x86" if ($env:PROCESSOR_ARCHITECTURE -eq "AMD64") { $arch = "x64" } elseif ($env:PROCESSOR_ARCHITECTURE -eq "ARM64") { $arch = "arm64" } -if ($env:PROCESSOR_ARCHITEW6432) { $arch = "x64" } +if ($env:PROCESSOR_ARCHITEW6432 -eq "AMD64") { $arch = "x64" } +elseif ($env:PROCESSOR_ARCHITEW6432 -eq "ARM64") { $arch = "arm64" } Write-Host "[*] Detected architecture: $arch" Write-Host "" From 2e92255a3c0d7a7495e09bfe860220c41e3f2ce8 Mon Sep 17 00:00:00 2001 From: "Namhyeon, Go" Date: Thu, 20 Nov 2025 17:31:50 +0900 Subject: [PATCH 18/18] Use 'using' statement for InstancesForm disposal Replaces manual disposal of InstancesForm with a 'using' statement to ensure proper resource management and exception safety when recording to the metadata database. --- WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs index a9a9c56..bad2a37 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs @@ -90,20 +90,21 @@ namespace WelsonJS.Launcher DateTime now = DateTime.Now; // record to the metadata database - InstancesForm instancesForm = new InstancesForm(); - try + using (InstancesForm instancesForm = new InstancesForm()) { - instancesForm.GetDatabaseInstance().Insert(new Dictionary + try { - ["InstanceId"] = instanceId, - ["FirstDeployTime"] = now - }, out _); + instancesForm.GetDatabaseInstance().Insert(new Dictionary + { + ["InstanceId"] = instanceId, + ["FirstDeployTime"] = now + }, out _); + } + catch (Exception ex) + { + _logger.Error($"Failed to record first deploy time: {ex.Message}"); + } } - catch (Exception ex) - { - _logger.Error($"Failed to record first deploy time: {ex.Message}"); - } - instancesForm.Dispose(); // record to the instance directory try