@yunsoft/yuncms 0.1.24 → 0.1.26

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yunsoft/yuncms",
3
- "version": "0.1.24",
3
+ "version": "0.1.26",
4
4
  "description": "Command-line setup and runtime launcher for YunCMS.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -30,8 +30,8 @@
30
30
  "test": "node --test"
31
31
  },
32
32
  "dependencies": {
33
- "@yunsoft/yuncms-api": "0.1.24",
34
- "@yunsoft/yuncms-core": "0.1.24"
33
+ "@yunsoft/yuncms-api": "0.1.26",
34
+ "@yunsoft/yuncms-core": "0.1.26"
35
35
  },
36
36
  "funding": {
37
37
  "type": "github",
@@ -1,7 +1,10 @@
1
- import { spawn } from 'node:child_process';
1
+ import { execFileSync, spawn } from 'node:child_process';
2
2
  import { resolve } from 'node:path';
3
3
 
4
4
  import { MAINTENANCE_BYPASS_ENV } from '@yunsoft/yuncms-core';
5
+ import { ATTACHED_RUNTIME_ENV } from './start-command.js';
6
+
7
+ export const DEFAULT_PROBE_SHUTDOWN_GRACE_MS = 12_000;
5
8
 
6
9
  function delay(ms) {
7
10
  return new Promise((resolveDelay) => setTimeout(resolveDelay, ms));
@@ -9,8 +12,10 @@ function delay(ms) {
9
12
 
10
13
  function probeError(code, message, details = {}) {
11
14
  const error = new Error(message);
15
+ const { code: exitCode, ...otherDetails } = details;
16
+ Object.assign(error, otherDetails);
17
+ if (Object.hasOwn(details, 'code')) error.exitCode = exitCode;
12
18
  error.code = code;
13
- Object.assign(error, details);
14
19
  return error;
15
20
  }
16
21
 
@@ -22,7 +27,7 @@ export async function verifyInstalledRuntime({
22
27
  fetchFn = globalThis.fetch,
23
28
  spawnProcess = spawn,
24
29
  timeoutMs = 15_000,
25
- shutdownGraceMs = 3_000,
30
+ shutdownGraceMs = DEFAULT_PROBE_SHUTDOWN_GRACE_MS,
26
31
  } = {}) {
27
32
  if (!Number.isInteger(port) || port < 1 || port > 65535) {
28
33
  throw probeError('UPDATE_PROBE_PORT_INVALID', `Invalid probe port: ${port}`);
@@ -48,6 +53,7 @@ export async function verifyInstalledRuntime({
48
53
  PORT: String(port),
49
54
  STUDIO_ORIGIN: origin,
50
55
  AUTH_PUBLIC_URL: origin,
56
+ [ATTACHED_RUNTIME_ENV]: '1',
51
57
  };
52
58
  if (maintenanceBypassToken !== null) childEnv[MAINTENANCE_BYPASS_ENV] = maintenanceBypassToken;
53
59
 
@@ -55,6 +61,7 @@ export async function verifyInstalledRuntime({
55
61
  cwd,
56
62
  env: childEnv,
57
63
  stdio: ['ignore', 'ignore', 'pipe'],
64
+ detached: process.platform !== 'win32',
58
65
  });
59
66
 
60
67
  let stderr = '';
@@ -65,6 +72,7 @@ export async function verifyInstalledRuntime({
65
72
 
66
73
  let settled = false;
67
74
  let shutdownAttempted = false;
75
+ let readyAndStopped = false;
68
76
  const exitPromise = new Promise((resolveExit, rejectExit) => {
69
77
  child.once('error', (error) => {
70
78
  if (settled) return;
@@ -81,9 +89,18 @@ export async function verifyInstalledRuntime({
81
89
 
82
90
  function requestKill(signal) {
83
91
  try {
92
+ if (signal === 'SIGKILL' && Number.isInteger(child.pid)) {
93
+ if (process.platform === 'win32') {
94
+ execFileSync('taskkill', ['/pid', String(child.pid), '/T', '/F'], { stdio: 'ignore', timeout: 1000 });
95
+ } else {
96
+ process.kill(-child.pid, signal);
97
+ }
98
+ return;
99
+ }
84
100
  child.kill(signal);
85
101
  } catch {
86
- // The bounded shutdown path still escalates and reports a timeout.
102
+ // ESRCH is expected for an exited group; keep a bounded direct-child fallback.
103
+ try { child.kill(signal); } catch {}
87
104
  }
88
105
  }
89
106
 
@@ -145,6 +162,7 @@ export async function verifyInstalledRuntime({
145
162
  if (result.code !== 0 && result.signal !== 'SIGTERM') {
146
163
  throw probeError('UPDATE_PROBE_SHUTDOWN_FAILED', 'Updated runtime did not stop cleanly', result);
147
164
  }
165
+ readyAndStopped = true;
148
166
  return { ready: true, origin };
149
167
  }
150
168
  }
@@ -161,5 +179,6 @@ export async function verifyInstalledRuntime({
161
179
  if (!settled && !shutdownAttempted) {
162
180
  await stopProbe({ strict: false }).catch(() => null);
163
181
  }
182
+ if (settled && !readyAndStopped && Number.isInteger(child.pid)) requestKill('SIGKILL');
164
183
  }
165
184
  }
@@ -3,6 +3,9 @@ import { fileURLToPath } from 'node:url';
3
3
 
4
4
  import { assertMaintenanceStartupAllowed } from '@yunsoft/yuncms-core';
5
5
 
6
+ // Internal updater transport flag: the probe owns the entire process group.
7
+ export const ATTACHED_RUNTIME_ENV = 'YUNCMS_CLI_ATTACHED_RUNTIME';
8
+
6
9
  export async function runStartCommand({
7
10
  env = process.env,
8
11
  cwd = process.cwd(),
@@ -21,7 +24,7 @@ export async function runStartCommand({
21
24
  cwd,
22
25
  env: { ...env },
23
26
  stdio: 'inherit',
24
- detached: process.platform !== 'win32',
27
+ detached: process.platform !== 'win32' && env[ATTACHED_RUNTIME_ENV] !== '1',
25
28
  });
26
29
 
27
30
  return new Promise((resolve, reject) => {