silgi 0.43.18 → 0.43.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,7 +7,7 @@ import { runCommand } from "citty";
7
7
  const command = {
8
8
  meta: {
9
9
  name: "run",
10
- description: "[DEPRECATED] Use \"silgi watch <command>\" instead",
10
+ description: "Run a command with silgi watch (shortcut for \"silgi watch npm run <command>\")",
11
11
  version
12
12
  },
13
13
  args: { command: {
@@ -15,8 +15,13 @@ const command = {
15
15
  description: "your application start command",
16
16
  required: false
17
17
  } },
18
- async run({ rawArgs }) {
19
- consola.warn("The \"run\" command is deprecated. Please use \"silgi watch <command>\" instead.");
18
+ async run({ args, rawArgs }) {
19
+ const command$1 = args.command || rawArgs[0];
20
+ if (command$1 && !command$1.includes(" ")) {
21
+ const fullCommand = `npm run ${command$1}`;
22
+ consola.info(`Running: ${fullCommand}`);
23
+ return runCommand(watch_default, { rawArgs: [fullCommand] });
24
+ }
20
25
  return runCommand(watch_default, { rawArgs });
21
26
  }
22
27
  };
@@ -1,14 +1,52 @@
1
1
  import { watchDev } from "../build/dev.mjs";
2
2
  import { commonArgs } from "../utils/common.mjs";
3
3
  import prepare_default from "./prepare.mjs";
4
+ import { aggressiveCleanup, killPortProcesses, killProcessTree } from "../utils/processManager.mjs";
4
5
  import consola from "consola";
5
6
  import { version } from "silgi/meta";
6
7
  import { defineCommand, runCommand } from "citty";
7
- import { execa } from "execa";
8
- import psTree from "ps-tree";
9
- import treeKill from "tree-kill";
8
+ import { spawn } from "node:child_process";
10
9
 
11
10
  //#region src/cli/commands/watch.ts
11
+ const runningProcesses = new Map();
12
+ const openPorts = new Set();
13
+ let watchHandle = null;
14
+ let isShuttingDown = false;
15
+ const isWindows = process.platform === "win32";
16
+ function detectPort(data) {
17
+ const patterns = [
18
+ /localhost:(\d+)/i,
19
+ /127\.0\.0\.1:(\d+)/,
20
+ /0\.0\.0\.0:(\d+)/,
21
+ /\[::1\]:(\d+)/,
22
+ /Local:\s*http:\/\/[^:]+:(\d+)/i,
23
+ /āžœ\s+Local:\s+http:\/\/[^:]+:(\d+)/i
24
+ ];
25
+ for (const pattern of patterns) {
26
+ const match = data.match(pattern);
27
+ if (match && match[1]) return Number.parseInt(match[1], 10);
28
+ }
29
+ return null;
30
+ }
31
+ async function shutdown() {
32
+ if (isShuttingDown) return;
33
+ isShuttingDown = true;
34
+ consola.info("\nšŸ›‘ Shutting down...");
35
+ for (const [pid, proc] of runningProcesses) if (!proc.killed) {
36
+ consola.info(`Terminating process ${pid}...`);
37
+ await killProcessTree(pid);
38
+ }
39
+ runningProcesses.clear();
40
+ for (const port of openPorts) await killPortProcesses(port);
41
+ openPorts.clear();
42
+ if (openPorts.size > 0 || runningProcesses.size > 0) {
43
+ consola.warn("Some processes may still be running, performing aggressive cleanup...");
44
+ await aggressiveCleanup();
45
+ }
46
+ if (watchHandle?.close) await watchHandle.close();
47
+ consola.success("āœ… Shutdown complete\n");
48
+ process.exit(0);
49
+ }
12
50
  const command = defineCommand({
13
51
  meta: {
14
52
  name: "dev",
@@ -38,9 +76,8 @@ const command = defineCommand({
38
76
  "--dev",
39
77
  "true"
40
78
  ] });
41
- const watch = await watchDev();
79
+ watchHandle = await watchDev();
42
80
  const userCommand = args.command || rawArgs[0];
43
- let childProcess = null;
44
81
  if (userCommand) {
45
82
  consola.withTag("silgi").info(`Starting: ${userCommand}`);
46
83
  const [cmd, ...cmdArgs] = userCommand.split(" ");
@@ -48,82 +85,68 @@ const command = defineCommand({
48
85
  consola.error("Invalid command provided");
49
86
  process.exit(1);
50
87
  }
51
- childProcess = execa(cmd, cmdArgs, {
52
- stdio: "inherit",
88
+ const childProcess = spawn(cmd, cmdArgs, {
89
+ stdio: [
90
+ "inherit",
91
+ "pipe",
92
+ "pipe"
93
+ ],
53
94
  cwd: process.cwd(),
54
- cleanup: true,
55
- detached: false
95
+ shell: isWindows,
96
+ detached: !isWindows,
97
+ env: { ...process.env }
98
+ });
99
+ if (childProcess.pid) {
100
+ runningProcesses.set(childProcess.pid, childProcess);
101
+ consola.debug(`Started process ${childProcess.pid}`);
102
+ }
103
+ childProcess.stdout?.on("data", (data) => {
104
+ const output = data.toString();
105
+ process.stdout.write(output);
106
+ const port = detectPort(output);
107
+ if (port && !openPorts.has(port)) {
108
+ openPorts.add(port);
109
+ consola.info(`Detected server on port ${port}`);
110
+ }
111
+ });
112
+ childProcess.stderr?.on("data", (data) => {
113
+ process.stderr.write(data);
56
114
  });
57
- childProcess.on("exit", (code) => {
58
- if (code !== 0) consola.error(`Process exited with code ${code}`);
59
- process.exit(code ?? 0);
115
+ childProcess.on("exit", (code, signal) => {
116
+ if (childProcess.pid) runningProcesses.delete(childProcess.pid);
117
+ if (!isShuttingDown) {
118
+ if (code !== null && code !== 0) consola.error(`Process exited with code ${code}`);
119
+ else if (signal) consola.error(`Process killed by signal ${signal}`);
120
+ process.exit(code ?? 1);
121
+ }
60
122
  });
61
123
  childProcess.on("error", (err) => {
62
124
  consola.error("Child process error:", err);
63
- process.exit(1);
125
+ if (!isShuttingDown) process.exit(1);
64
126
  });
65
127
  }
66
- let isShuttingDown = false;
67
- const shutdown = async (signal) => {
68
- if (isShuttingDown) return;
69
- isShuttingDown = true;
70
- consola.withTag("silgi").info(`Received ${signal}, shutting down...`);
71
- try {
72
- if (childProcess && childProcess.pid) {
73
- const children = await new Promise((resolve) => {
74
- psTree(childProcess.pid, (err, children$1) => {
75
- if (err) resolve([]);
76
- else resolve(children$1);
77
- });
78
- });
79
- if (children.length > 0) {
80
- consola.info("Terminating process tree:");
81
- children.forEach((child) => {
82
- consola.info(` - ${child.COMMAND} (PID: ${child.PID})`);
83
- });
84
- }
85
- const processCount = children.length + 1;
86
- consola.info(`Terminating ${processCount} processes...`);
87
- await new Promise((resolve) => {
88
- treeKill(childProcess.pid, "SIGTERM", (err) => {
89
- if (err) consola.withTag("silgi").error("Error killing process tree:", err);
90
- resolve();
91
- });
92
- });
93
- await new Promise((resolve) => setTimeout(resolve, 2e3));
94
- if (childProcess.pid) try {
95
- treeKill(childProcess.pid, "SIGKILL");
96
- } catch {}
97
- }
98
- if (watch?.close) await watch.close();
99
- consola.success("All processes terminated successfully");
100
- } catch (error) {
101
- consola.withTag("silgi").error("Error during shutdown:", error);
102
- }
103
- process.exit(0);
104
- };
105
- process.on("SIGINT", () => shutdown("SIGINT"));
106
- process.on("SIGTERM", () => shutdown("SIGTERM"));
107
- process.on("SIGHUP", () => shutdown("SIGHUP"));
108
- process.on("SIGQUIT", () => shutdown("SIGQUIT"));
109
- process.on("exit", () => {
110
- if (childProcess && childProcess.pid) try {
111
- treeKill(childProcess.pid, "SIGKILL");
112
- } catch {}
113
- });
114
- process.on("uncaughtException", (error) => {
115
- consola.withTag("silgi").error("Uncaught exception:", error);
116
- shutdown("uncaughtException");
117
- });
118
- process.on("unhandledRejection", (reason, promise) => {
119
- consola.withTag("silgi").error("Unhandled rejection at:", promise, "reason:", reason);
120
- shutdown("unhandledRejection");
121
- });
122
128
  consola.withTag("silgi").success("Prepare completed");
123
129
  consola.withTag("silgi").info("Process is still running. Press Ctrl+C to exit.");
124
- if (!childProcess) await new Promise(() => {});
130
+ await new Promise(() => {});
125
131
  }
126
132
  });
133
+ process.on("SIGINT", shutdown);
134
+ process.on("SIGTERM", shutdown);
135
+ process.on("SIGHUP", shutdown);
136
+ process.on("SIGQUIT", shutdown);
137
+ process.on("exit", () => {
138
+ if (!isShuttingDown) for (const [pid] of runningProcesses) try {
139
+ process.kill(pid, "SIGKILL");
140
+ } catch {}
141
+ });
142
+ process.on("uncaughtException", (err) => {
143
+ consola.error("Uncaught exception:", err);
144
+ shutdown();
145
+ });
146
+ process.on("unhandledRejection", (err) => {
147
+ consola.error("Unhandled rejection:", err);
148
+ shutdown();
149
+ });
127
150
  var watch_default = command;
128
151
 
129
152
  //#endregion
@@ -0,0 +1,170 @@
1
+ import consola from "consola";
2
+ import { execSync } from "node:child_process";
3
+
4
+ //#region src/cli/utils/processManager.ts
5
+ const isWindows = process.platform === "win32";
6
+ /**
7
+ * Get all processes using a specific port
8
+ */
9
+ async function getProcessesOnPort(port) {
10
+ const processes = [];
11
+ try {
12
+ if (isWindows) {
13
+ const output = execSync("netstat -ano", { encoding: "utf8" });
14
+ const lines = output.split("\n");
15
+ for (const line of lines) if (line.includes(`:${port}`) && line.includes("LISTENING")) {
16
+ const parts = line.trim().split(/\s+/);
17
+ const pidStr = parts[parts.length - 1];
18
+ if (!pidStr) continue;
19
+ const pid = Number.parseInt(pidStr);
20
+ if (pid && pid !== 0 && !Number.isNaN(pid)) try {
21
+ const taskList = execSync(`tasklist /FI "PID eq ${pid}" /FO CSV`, { encoding: "utf8" });
22
+ const lines$1 = taskList.split("\n");
23
+ const processLine = lines$1[1];
24
+ if (processLine) {
25
+ const namePart = processLine.split(",")[0];
26
+ if (namePart) {
27
+ const name = namePart.replace(/"/g, "");
28
+ processes.push({
29
+ pid,
30
+ name,
31
+ port
32
+ });
33
+ }
34
+ }
35
+ } catch {
36
+ processes.push({
37
+ pid,
38
+ name: "unknown",
39
+ port
40
+ });
41
+ }
42
+ }
43
+ } else try {
44
+ const output = execSync(`lsof -i:${port} -P -n`, { encoding: "utf8" });
45
+ const lines = output.split("\n").slice(1);
46
+ for (const line of lines) if (line.trim()) {
47
+ const parts = line.trim().split(/\s+/);
48
+ const name = parts[0];
49
+ const pidStr = parts[1];
50
+ if (!name || !pidStr) continue;
51
+ const pid = Number.parseInt(pidStr);
52
+ if (pid && !Number.isNaN(pid) && !processes.some((p) => p.pid === pid)) processes.push({
53
+ pid,
54
+ name,
55
+ port
56
+ });
57
+ }
58
+ } catch {}
59
+ } catch (error) {
60
+ consola.debug(`Failed to get processes on port ${port}:`, error);
61
+ }
62
+ return processes;
63
+ }
64
+ /**
65
+ * Kill a process and all its children
66
+ */
67
+ async function killProcessTree(pid, signal = "SIGKILL") {
68
+ try {
69
+ if (isWindows) {
70
+ execSync(`taskkill /F /T /PID ${pid}`, { stdio: "ignore" });
71
+ return true;
72
+ } else try {
73
+ process.kill(-pid, signal);
74
+ return true;
75
+ } catch {
76
+ process.kill(pid, signal);
77
+ return true;
78
+ }
79
+ } catch (error) {
80
+ consola.debug(`Failed to kill process ${pid}:`, error);
81
+ return false;
82
+ }
83
+ }
84
+ /**
85
+ * Kill all processes on a specific port
86
+ */
87
+ async function killPortProcesses(port) {
88
+ const processes = await getProcessesOnPort(port);
89
+ let killedCount = 0;
90
+ for (const proc of processes) {
91
+ consola.info(`Killing ${proc.name} (PID: ${proc.pid}) on port ${port}`);
92
+ if (await killProcessTree(proc.pid)) killedCount++;
93
+ }
94
+ return killedCount;
95
+ }
96
+ /**
97
+ * Find and kill all Node.js processes in a directory
98
+ */
99
+ async function killNodeProcessesInDirectory(directory) {
100
+ let killedCount = 0;
101
+ try {
102
+ if (isWindows) {
103
+ const output = execSync("wmic process where \"name='node.exe'\" get ProcessId,CommandLine /FORMAT:CSV", {
104
+ encoding: "utf8",
105
+ stdio: [
106
+ "ignore",
107
+ "pipe",
108
+ "ignore"
109
+ ]
110
+ });
111
+ const lines = output.split("\n");
112
+ for (const line of lines) if (line.includes(directory)) {
113
+ const parts = line.split(",");
114
+ const pidStr = parts[parts.length - 1];
115
+ if (!pidStr) continue;
116
+ const pid = Number.parseInt(pidStr);
117
+ if (pid && !Number.isNaN(pid)) {
118
+ consola.info(`Killing node process ${pid} in ${directory}`);
119
+ if (await killProcessTree(pid)) killedCount++;
120
+ }
121
+ }
122
+ } else {
123
+ const output = execSync("ps aux", { encoding: "utf8" });
124
+ const lines = output.split("\n");
125
+ for (const line of lines) if (line.includes("node") && line.includes(directory)) {
126
+ const parts = line.trim().split(/\s+/);
127
+ const pidStr = parts[1];
128
+ if (!pidStr) continue;
129
+ const pid = Number.parseInt(pidStr);
130
+ if (pid && !Number.isNaN(pid)) {
131
+ consola.info(`Killing node process ${pid} in ${directory}`);
132
+ if (await killProcessTree(pid)) killedCount++;
133
+ }
134
+ }
135
+ }
136
+ } catch (error) {
137
+ consola.debug("Failed to find node processes:", error);
138
+ }
139
+ return killedCount;
140
+ }
141
+ /**
142
+ * Aggressive cleanup: kill all processes on common dev ports
143
+ */
144
+ async function aggressiveCleanup() {
145
+ const commonPorts = [
146
+ 3e3,
147
+ 3001,
148
+ 4e3,
149
+ 5e3,
150
+ 5173,
151
+ 8080,
152
+ 8e3,
153
+ 9e3
154
+ ];
155
+ consola.info("Performing aggressive cleanup...");
156
+ for (const port of commonPorts) {
157
+ const processes = await getProcessesOnPort(port);
158
+ if (processes.length > 0) {
159
+ consola.info(`Found ${processes.length} process(es) on port ${port}`);
160
+ await killPortProcesses(port);
161
+ }
162
+ }
163
+ const cwd = process.cwd();
164
+ const killedInDir = await killNodeProcessesInDirectory(cwd);
165
+ if (killedInDir > 0) consola.info(`Killed ${killedInDir} node process(es) in ${cwd}`);
166
+ consola.success("Aggressive cleanup complete");
167
+ }
168
+
169
+ //#endregion
170
+ export { aggressiveCleanup, killPortProcesses, killProcessTree };
package/dist/package.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  //#region package.json
2
2
  var name = "silgi";
3
3
  var type = "module";
4
- var version = "0.43.18";
4
+ var version = "0.43.20";
5
5
  var private$1 = false;
6
6
  var packageManager = "pnpm@10.12.4";
7
7
  var sideEffects = false;
@@ -107,7 +107,6 @@ var dependencies = {
107
107
  "perfect-debounce": "^1.0.0",
108
108
  "picocolors": "catalog:",
109
109
  "pkg-types": "catalog:",
110
- "ps-tree": "^1.2.0",
111
110
  "rfc6902": "^5.1.2",
112
111
  "rou3": "^0.7.3",
113
112
  "scule": "catalog:",
@@ -115,7 +114,6 @@ var dependencies = {
115
114
  "srvx": "^0.8.2",
116
115
  "std-env": "catalog:",
117
116
  "tinyglobby": "^0.2.14",
118
- "tree-kill": "^1.2.2",
119
117
  "ufo": "catalog:",
120
118
  "unadapter": "^0.1.2",
121
119
  "unctx": "catalog:",
@@ -130,11 +128,10 @@ var devDependencies = {
130
128
  "@silgi/ecosystem": "catalog:",
131
129
  "@types/micromatch": "^4.0.9",
132
130
  "@types/node": "catalog:",
133
- "@types/ps-tree": "^1.1.6",
134
131
  "@types/semver": "catalog:",
135
132
  "@vitest/coverage-v8": "catalog:",
136
133
  "eslint": "catalog:",
137
- "h3": "^2.0.0-beta.1",
134
+ "h3": "catalog:",
138
135
  "nitropack": "catalog:",
139
136
  "nuxt": "catalog:",
140
137
  "tsdown": "^0.12.9",
@@ -1,9 +1,9 @@
1
1
  import { SilgiCLI } from "./silgiCLI.mjs";
2
2
  import { BuildSilgi } from "./silgi.mjs";
3
3
  import { ESMCodeGenOptions, ESMImport } from "knitwork";
4
- import * as h33 from "h3";
4
+ import * as h32 from "h3";
5
5
  import { PresetName } from "silgi/presets";
6
- import * as nitropack_types1 from "nitropack/types";
6
+ import * as nitropack_types0 from "nitropack/types";
7
7
 
8
8
  //#region src/types/kits.d.ts
9
9
  interface SilgiCommands {}
@@ -24,7 +24,7 @@ interface GenImport {
24
24
  imports: ESMImport | ESMImport[];
25
25
  options?: ESMCodeGenOptions;
26
26
  }
27
- type Framework<T extends PresetName> = T extends "nitro" ? nitropack_types1.NitroApp : T extends "nuxt" ? nitropack_types1.NitroApp : T extends "h3" ? h33.Router : never;
27
+ type Framework<T extends PresetName> = T extends "nitro" ? nitropack_types0.NitroApp : T extends "nuxt" ? nitropack_types0.NitroApp : T extends "h3" ? h32.Router : never;
28
28
  interface DefineFrameworkOptions<T extends PresetName> extends Omit<BuildSilgi, "framework"> {
29
29
  framework: Framework<T>;
30
30
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "silgi",
3
3
  "type": "module",
4
- "version": "0.43.18",
4
+ "version": "0.43.20",
5
5
  "private": false,
6
6
  "sideEffects": false,
7
7
  "exports": {
@@ -116,7 +116,6 @@
116
116
  "perfect-debounce": "^1.0.0",
117
117
  "picocolors": "^1.1.1",
118
118
  "pkg-types": "^2.2.0",
119
- "ps-tree": "^1.2.0",
120
119
  "rfc6902": "^5.1.2",
121
120
  "rou3": "^0.7.3",
122
121
  "scule": "^1.3.0",
@@ -124,7 +123,6 @@
124
123
  "srvx": "^0.8.2",
125
124
  "std-env": "^3.9.0",
126
125
  "tinyglobby": "^0.2.14",
127
- "tree-kill": "^1.2.2",
128
126
  "ufo": "^1.6.1",
129
127
  "unadapter": "^0.1.2",
130
128
  "unctx": "^2.4.1",
@@ -139,11 +137,10 @@
139
137
  "@silgi/ecosystem": "^0.7.3",
140
138
  "@types/micromatch": "^4.0.9",
141
139
  "@types/node": "^24.0.10",
142
- "@types/ps-tree": "^1.1.6",
143
140
  "@types/semver": "^7.7.0",
144
141
  "@vitest/coverage-v8": "3.0.5",
145
142
  "eslint": "^9.30.1",
146
- "h3": "^2.0.0-beta.1",
143
+ "h3": "^1.15.3",
147
144
  "nitropack": "^2.11.13",
148
145
  "nuxt": "^3.17.6",
149
146
  "tsdown": "^0.12.9",