@linxin666/dsh-remote-web-ui 0.1.13 → 0.1.14
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/README.i18n.yaml +2 -2
- package/README.md +5 -2
- package/README.zh.md +1 -1
- package/lib/client.js +2 -2
- package/lib/client.js.map +1 -1
- package/lib/index.js +114 -27
- package/lib/types/client/locales.js +2 -2
- package/lib/types/update.d.ts +5 -1
- package/lib/types/update.d.ts.map +1 -1
- package/lib/types/update.js +122 -26
- package/package.json +1 -1
- package/src/client/locales.ts +2 -2
- package/src/update.ts +116 -26
package/lib/index.js
CHANGED
|
@@ -1715,30 +1715,85 @@ async function checkUpdates(deps) {
|
|
|
1715
1715
|
/** Cap on captured pnpm output (keeps error payloads bounded). */
|
|
1716
1716
|
const OUTPUT_CAP = 16 * 1024;
|
|
1717
1717
|
/**
|
|
1718
|
-
*
|
|
1718
|
+
* Windows cmd command-not-found stderr. With shell:true a missing shim
|
|
1719
|
+
* exits with code 1 (cmd cannot report ENOENT), so the fallback chain
|
|
1720
|
+
* detects this message instead of the spawn error event.
|
|
1721
|
+
*/
|
|
1722
|
+
const WIN_CMD_MISSING_RE = /not recognized as an internal or external command/i;
|
|
1723
|
+
/**
|
|
1724
|
+
* Run the update inside the profile directory. Tries pnpm first, falls back
|
|
1725
|
+
* to corepack and then npx when the previous command is missing (ENOENT);
|
|
1726
|
+
* all candidates share one hard timeout and keep accumulating output.
|
|
1719
1727
|
* @param deps - profile dir, package list, and spawn/timeout seams.
|
|
1720
1728
|
* @returns the outcome with captured output.
|
|
1721
1729
|
*/
|
|
1722
1730
|
function runUpdate(deps) {
|
|
1723
1731
|
return new Promise((resolve) => {
|
|
1724
|
-
const
|
|
1732
|
+
const spawnImpl = deps.spawnImpl ?? spawn;
|
|
1733
|
+
const packages = deps.packages;
|
|
1734
|
+
const platform = deps.platform ?? process.platform;
|
|
1735
|
+
const spawnOptions = {
|
|
1725
1736
|
cwd: deps.profileDir,
|
|
1726
1737
|
stdio: [
|
|
1727
1738
|
"ignore",
|
|
1728
1739
|
"pipe",
|
|
1729
1740
|
"pipe"
|
|
1730
|
-
]
|
|
1731
|
-
|
|
1741
|
+
],
|
|
1742
|
+
...platform === "win32" ? { shell: true } : {}
|
|
1743
|
+
};
|
|
1744
|
+
const candidates = [
|
|
1745
|
+
{
|
|
1746
|
+
command: "pnpm",
|
|
1747
|
+
args: ["update", ...packages]
|
|
1748
|
+
},
|
|
1749
|
+
{
|
|
1750
|
+
command: "corepack",
|
|
1751
|
+
args: [
|
|
1752
|
+
"pnpm",
|
|
1753
|
+
"update",
|
|
1754
|
+
...packages
|
|
1755
|
+
]
|
|
1756
|
+
},
|
|
1757
|
+
{
|
|
1758
|
+
command: "npx",
|
|
1759
|
+
args: [
|
|
1760
|
+
"--yes",
|
|
1761
|
+
"pnpm",
|
|
1762
|
+
"update",
|
|
1763
|
+
...packages
|
|
1764
|
+
]
|
|
1765
|
+
}
|
|
1766
|
+
];
|
|
1732
1767
|
let output = "";
|
|
1768
|
+
let currentOutput = "";
|
|
1733
1769
|
const append = (chunk) => {
|
|
1734
1770
|
output += chunk.toString("utf8");
|
|
1735
1771
|
if (output.length > OUTPUT_CAP) output = output.slice(output.length - OUTPUT_CAP);
|
|
1772
|
+
currentOutput += chunk.toString("utf8");
|
|
1773
|
+
if (currentOutput.length > OUTPUT_CAP) currentOutput = currentOutput.slice(currentOutput.length - OUTPUT_CAP);
|
|
1774
|
+
};
|
|
1775
|
+
let currentChild;
|
|
1776
|
+
let finished = false;
|
|
1777
|
+
const finish = (result) => {
|
|
1778
|
+
if (finished) return;
|
|
1779
|
+
finished = true;
|
|
1780
|
+
clearTimeout(timer);
|
|
1781
|
+
resolve(result);
|
|
1736
1782
|
};
|
|
1737
|
-
child.stdout?.on("data", append);
|
|
1738
|
-
child.stderr?.on("data", append);
|
|
1739
1783
|
const timer = setTimeout(() => {
|
|
1740
|
-
|
|
1741
|
-
|
|
1784
|
+
if (finished) return;
|
|
1785
|
+
if (platform === "win32") {
|
|
1786
|
+
const pid = currentChild?.pid;
|
|
1787
|
+
if (pid !== void 0 && pid > 0) try {
|
|
1788
|
+
spawnImpl("taskkill", [
|
|
1789
|
+
"/pid",
|
|
1790
|
+
String(pid),
|
|
1791
|
+
"/t",
|
|
1792
|
+
"/f"
|
|
1793
|
+
], { stdio: "ignore" });
|
|
1794
|
+
} catch {}
|
|
1795
|
+
} else currentChild?.kill("SIGTERM");
|
|
1796
|
+
finish({
|
|
1742
1797
|
ok: false,
|
|
1743
1798
|
exitCode: null,
|
|
1744
1799
|
output,
|
|
@@ -1746,27 +1801,59 @@ function runUpdate(deps) {
|
|
|
1746
1801
|
errorCode: "timeout"
|
|
1747
1802
|
});
|
|
1748
1803
|
}, deps.timeoutMs ?? 10 * 6e4);
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1804
|
+
const runCandidate = (index) => {
|
|
1805
|
+
if (finished) return;
|
|
1806
|
+
if (index >= candidates.length) {
|
|
1807
|
+
finish({
|
|
1808
|
+
ok: false,
|
|
1809
|
+
exitCode: null,
|
|
1810
|
+
output,
|
|
1811
|
+
error: "pnpm not found on PATH (tried pnpm, corepack, npx); install pnpm and restart the app",
|
|
1812
|
+
errorCode: "pnpm-missing"
|
|
1813
|
+
});
|
|
1814
|
+
return;
|
|
1815
|
+
}
|
|
1816
|
+
const candidate = candidates[index];
|
|
1817
|
+
currentOutput = "";
|
|
1818
|
+
const child = spawnImpl(candidate.command, candidate.args, spawnOptions);
|
|
1819
|
+
currentChild = child;
|
|
1820
|
+
let settled = false;
|
|
1821
|
+
const once = (fn) => {
|
|
1822
|
+
if (settled) return;
|
|
1823
|
+
settled = true;
|
|
1824
|
+
fn();
|
|
1825
|
+
};
|
|
1826
|
+
child.stdout?.on("data", append);
|
|
1827
|
+
child.stderr?.on("data", append);
|
|
1828
|
+
child.on("error", (error) => {
|
|
1829
|
+
once(() => {
|
|
1830
|
+
if (error.code === "ENOENT") runCandidate(index + 1);
|
|
1831
|
+
else finish({
|
|
1832
|
+
ok: false,
|
|
1833
|
+
exitCode: null,
|
|
1834
|
+
output,
|
|
1835
|
+
error: error.message,
|
|
1836
|
+
errorCode: void 0
|
|
1837
|
+
});
|
|
1838
|
+
});
|
|
1758
1839
|
});
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1840
|
+
child.on("close", (code) => {
|
|
1841
|
+
if (settled || finished) return;
|
|
1842
|
+
if (platform === "win32" && code !== 0 && WIN_CMD_MISSING_RE.test(currentOutput)) {
|
|
1843
|
+
runCandidate(index + 1);
|
|
1844
|
+
return;
|
|
1845
|
+
}
|
|
1846
|
+
settled = true;
|
|
1847
|
+
finish({
|
|
1848
|
+
ok: code === 0,
|
|
1849
|
+
exitCode: code,
|
|
1850
|
+
output,
|
|
1851
|
+
error: code === 0 ? void 0 : "pnpm exited with code " + String(code),
|
|
1852
|
+
...code === 0 ? {} : { errorCode: "pnpm-failed" }
|
|
1853
|
+
});
|
|
1768
1854
|
});
|
|
1769
|
-
}
|
|
1855
|
+
};
|
|
1856
|
+
runCandidate(0);
|
|
1770
1857
|
});
|
|
1771
1858
|
}
|
|
1772
1859
|
//#endregion
|
|
@@ -96,7 +96,7 @@ export const zh = {
|
|
|
96
96
|
'update.packages': '组件版本',
|
|
97
97
|
'update.retry': '重新检查',
|
|
98
98
|
'update.close': '关闭更新面板',
|
|
99
|
-
'update.error.pnpmMissing': '未找到 pnpm
|
|
99
|
+
'update.error.pnpmMissing': '未找到 pnpm,已尝试 pnpm / corepack / npx 仍不可用。请安装 pnpm 后重启应用(Windows: npm install -g pnpm 或 corepack enable;macOS: brew install pnpm 或 corepack enable)。',
|
|
100
100
|
'update.error.timeout': '更新超时,已终止安装进程,请重试。',
|
|
101
101
|
'update.error.notFound': '未找到 dsh-web-ui 聚合包安装,无法更新。',
|
|
102
102
|
'update.error.link': '当前为本地开发模式(链接安装),无法自动更新。',
|
|
@@ -200,7 +200,7 @@ export const en = {
|
|
|
200
200
|
'update.packages': 'Component versions',
|
|
201
201
|
'update.retry': 'Check again',
|
|
202
202
|
'update.close': 'Close update panel',
|
|
203
|
-
'update.error.pnpmMissing': 'pnpm was not found. Install
|
|
203
|
+
'update.error.pnpmMissing': 'pnpm was not found (tried pnpm / corepack / npx). Install pnpm and restart the app (Windows: npm install -g pnpm or corepack enable; macOS: brew install pnpm or corepack enable).',
|
|
204
204
|
'update.error.timeout': 'The update timed out and the install process was killed; try again.',
|
|
205
205
|
'update.error.notFound': 'The dsh-web-ui aggregate package is not installed; nothing to update.',
|
|
206
206
|
'update.error.link': 'Local development mode (link install) — automatic update is unavailable.',
|
package/lib/types/update.d.ts
CHANGED
|
@@ -180,9 +180,13 @@ export interface UpdateRunDeps {
|
|
|
180
180
|
spawnImpl?: typeof spawn;
|
|
181
181
|
/** Hard timeout; the child is killed on expiry. */
|
|
182
182
|
timeoutMs?: number;
|
|
183
|
+
/** Platform override (defaults to process.platform; test seam). */
|
|
184
|
+
platform?: NodeJS.Platform;
|
|
183
185
|
}
|
|
184
186
|
/**
|
|
185
|
-
* Run the update
|
|
187
|
+
* Run the update inside the profile directory. Tries pnpm first, falls back
|
|
188
|
+
* to corepack and then npx when the previous command is missing (ENOENT);
|
|
189
|
+
* all candidates share one hard timeout and keep accumulating output.
|
|
186
190
|
* @param deps - profile dir, package list, and spawn/timeout seams.
|
|
187
191
|
* @returns the outcome with captured output.
|
|
188
192
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../src/update.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../src/update.ts"],"names":[],"mappings":"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"}
|
package/lib/types/update.js
CHANGED
|
@@ -286,48 +286,144 @@ export async function checkUpdates(deps) {
|
|
|
286
286
|
/** Cap on captured pnpm output (keeps error payloads bounded). */
|
|
287
287
|
const OUTPUT_CAP = 16 * 1024;
|
|
288
288
|
/**
|
|
289
|
-
*
|
|
289
|
+
* Windows cmd command-not-found stderr. With shell:true a missing shim
|
|
290
|
+
* exits with code 1 (cmd cannot report ENOENT), so the fallback chain
|
|
291
|
+
* detects this message instead of the spawn error event.
|
|
292
|
+
*/
|
|
293
|
+
const WIN_CMD_MISSING_RE = /not recognized as an internal or external command/i;
|
|
294
|
+
/**
|
|
295
|
+
* Run the update inside the profile directory. Tries pnpm first, falls back
|
|
296
|
+
* to corepack and then npx when the previous command is missing (ENOENT);
|
|
297
|
+
* all candidates share one hard timeout and keep accumulating output.
|
|
290
298
|
* @param deps - profile dir, package list, and spawn/timeout seams.
|
|
291
299
|
* @returns the outcome with captured output.
|
|
292
300
|
*/
|
|
293
301
|
export function runUpdate(deps) {
|
|
294
302
|
return new Promise((resolve) => {
|
|
295
|
-
const
|
|
303
|
+
const spawnImpl = deps.spawnImpl ?? spawn;
|
|
304
|
+
const packages = deps.packages;
|
|
305
|
+
const platform = deps.platform ?? process.platform;
|
|
306
|
+
// Windows ships pnpm/corepack/npx as .cmd shims; Node's spawn cannot
|
|
307
|
+
// start .cmd files directly (ENOENT even when installed), so route
|
|
308
|
+
// them through cmd.exe there. POSIX spawns stay shell-free.
|
|
309
|
+
const spawnOptions = {
|
|
296
310
|
cwd: deps.profileDir,
|
|
297
311
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
298
|
-
|
|
312
|
+
...(platform === 'win32' ? { shell: true } : {}),
|
|
313
|
+
};
|
|
314
|
+
// Ordered fallback chain: each is tried only when the previous one is
|
|
315
|
+
// missing on PATH (ENOENT on the spawn error event, never on close).
|
|
316
|
+
const candidates = [
|
|
317
|
+
{ command: 'pnpm', args: ['update', ...packages] },
|
|
318
|
+
{ command: 'corepack', args: ['pnpm', 'update', ...packages] },
|
|
319
|
+
{ command: 'npx', args: ['--yes', 'pnpm', 'update', ...packages] },
|
|
320
|
+
];
|
|
321
|
+
// `output` accumulates across candidates for UI display; `currentOutput`
|
|
322
|
+
// is reset per candidate and carries only that candidate's own diagnostics
|
|
323
|
+
// (its tail is subject to OUTPUT_CAP). The win32 missing-command test must
|
|
324
|
+
// run against `currentOutput` so a previous candidate's 'not recognized'
|
|
325
|
+
// stderr cannot misclassify a real failure in the next one.
|
|
299
326
|
let output = '';
|
|
327
|
+
let currentOutput = '';
|
|
300
328
|
const append = (chunk) => {
|
|
301
329
|
output += chunk.toString('utf8');
|
|
302
330
|
if (output.length > OUTPUT_CAP)
|
|
303
331
|
output = output.slice(output.length - OUTPUT_CAP);
|
|
332
|
+
currentOutput += chunk.toString('utf8');
|
|
333
|
+
if (currentOutput.length > OUTPUT_CAP)
|
|
334
|
+
currentOutput = currentOutput.slice(currentOutput.length - OUTPUT_CAP);
|
|
335
|
+
};
|
|
336
|
+
let currentChild;
|
|
337
|
+
// Terminal guard: once a result is produced the promise is settled and no
|
|
338
|
+
// further candidate is started (a killed child's late close must not
|
|
339
|
+
// respawn anything after a timeout resolution).
|
|
340
|
+
let finished = false;
|
|
341
|
+
const finish = (result) => {
|
|
342
|
+
if (finished)
|
|
343
|
+
return;
|
|
344
|
+
finished = true;
|
|
345
|
+
clearTimeout(timer);
|
|
346
|
+
resolve(result);
|
|
304
347
|
};
|
|
305
|
-
child.stdout?.on('data', append);
|
|
306
|
-
child.stderr?.on('data', append);
|
|
307
348
|
const timer = setTimeout(() => {
|
|
308
|
-
|
|
309
|
-
|
|
349
|
+
if (finished)
|
|
350
|
+
return;
|
|
351
|
+
if (platform === 'win32') {
|
|
352
|
+
// cmd.exe under shell:true only kills the shell wrapper; kill the whole
|
|
353
|
+
// process tree best-effort so pnpm/npx do not keep running.
|
|
354
|
+
const pid = currentChild?.pid;
|
|
355
|
+
if (pid !== undefined && pid > 0) {
|
|
356
|
+
try {
|
|
357
|
+
spawnImpl('taskkill', ['/pid', String(pid), '/t', '/f'], { stdio: 'ignore' });
|
|
358
|
+
}
|
|
359
|
+
catch {
|
|
360
|
+
// Best-effort kill; fall through to the timeout result.
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
else {
|
|
365
|
+
currentChild?.kill('SIGTERM');
|
|
366
|
+
}
|
|
367
|
+
finish({ ok: false, exitCode: null, output, error: 'update timed out; install process killed', errorCode: 'timeout' });
|
|
310
368
|
}, deps.timeoutMs ?? 10 * 60_000);
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
369
|
+
const runCandidate = (index) => {
|
|
370
|
+
if (finished)
|
|
371
|
+
return;
|
|
372
|
+
if (index >= candidates.length) {
|
|
373
|
+
finish({
|
|
374
|
+
ok: false,
|
|
375
|
+
exitCode: null,
|
|
376
|
+
output,
|
|
377
|
+
error: 'pnpm not found on PATH (tried pnpm, corepack, npx); install pnpm and restart the app',
|
|
378
|
+
errorCode: 'pnpm-missing',
|
|
379
|
+
});
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
const candidate = candidates[index];
|
|
383
|
+
currentOutput = '';
|
|
384
|
+
const child = spawnImpl(candidate.command, candidate.args, spawnOptions);
|
|
385
|
+
currentChild = child;
|
|
386
|
+
let settled = false;
|
|
387
|
+
const once = (fn) => {
|
|
388
|
+
if (settled)
|
|
389
|
+
return;
|
|
390
|
+
settled = true;
|
|
391
|
+
fn();
|
|
392
|
+
};
|
|
393
|
+
child.stdout?.on('data', append);
|
|
394
|
+
child.stderr?.on('data', append);
|
|
395
|
+
child.on('error', (error) => {
|
|
396
|
+
once(() => {
|
|
397
|
+
if (error.code === 'ENOENT') {
|
|
398
|
+
// Command missing: try the next candidate in the chain.
|
|
399
|
+
runCandidate(index + 1);
|
|
400
|
+
}
|
|
401
|
+
else {
|
|
402
|
+
finish({ ok: false, exitCode: null, output, error: error.message, errorCode: undefined });
|
|
403
|
+
}
|
|
404
|
+
});
|
|
320
405
|
});
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
406
|
+
child.on('close', (code) => {
|
|
407
|
+
if (settled || finished)
|
|
408
|
+
return;
|
|
409
|
+
// Windows + shell: a missing command reports exit 1 with
|
|
410
|
+
// 'not recognized' instead of ENOENT — keep the chain going. Checked
|
|
411
|
+
// against this candidate's own output so a prior fallback's stderr
|
|
412
|
+
// cannot misclassify a real failure here.
|
|
413
|
+
if (platform === 'win32' && code !== 0 && WIN_CMD_MISSING_RE.test(currentOutput)) {
|
|
414
|
+
runCandidate(index + 1);
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
settled = true;
|
|
418
|
+
finish({
|
|
419
|
+
ok: code === 0,
|
|
420
|
+
exitCode: code,
|
|
421
|
+
output,
|
|
422
|
+
error: code === 0 ? undefined : 'pnpm exited with code ' + String(code),
|
|
423
|
+
...(code === 0 ? {} : { errorCode: 'pnpm-failed' }),
|
|
424
|
+
});
|
|
330
425
|
});
|
|
331
|
-
}
|
|
426
|
+
};
|
|
427
|
+
runCandidate(0);
|
|
332
428
|
});
|
|
333
429
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linxin666/dsh-remote-web-ui",
|
|
3
3
|
"description": "Mobile remote control for the dsh web GUI: scan-to-pair QR entry beside the settings button, one-time pairing tokens, live device status, and revocable mobile sessions",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.14",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": "^22.19.0 || >=24.0.0"
|
package/src/client/locales.ts
CHANGED
|
@@ -97,7 +97,7 @@ export const zh = {
|
|
|
97
97
|
'update.packages': '组件版本',
|
|
98
98
|
'update.retry': '重新检查',
|
|
99
99
|
'update.close': '关闭更新面板',
|
|
100
|
-
'update.error.pnpmMissing': '未找到 pnpm
|
|
100
|
+
'update.error.pnpmMissing': '未找到 pnpm,已尝试 pnpm / corepack / npx 仍不可用。请安装 pnpm 后重启应用(Windows: npm install -g pnpm 或 corepack enable;macOS: brew install pnpm 或 corepack enable)。',
|
|
101
101
|
'update.error.timeout': '更新超时,已终止安装进程,请重试。',
|
|
102
102
|
'update.error.notFound': '未找到 dsh-web-ui 聚合包安装,无法更新。',
|
|
103
103
|
'update.error.link': '当前为本地开发模式(链接安装),无法自动更新。',
|
|
@@ -208,7 +208,7 @@ export const en = {
|
|
|
208
208
|
'update.packages': 'Component versions',
|
|
209
209
|
'update.retry': 'Check again',
|
|
210
210
|
'update.close': 'Close update panel',
|
|
211
|
-
'update.error.pnpmMissing': 'pnpm was not found. Install
|
|
211
|
+
'update.error.pnpmMissing': 'pnpm was not found (tried pnpm / corepack / npx). Install pnpm and restart the app (Windows: npm install -g pnpm or corepack enable; macOS: brew install pnpm or corepack enable).',
|
|
212
212
|
'update.error.timeout': 'The update timed out and the install process was killed; try again.',
|
|
213
213
|
'update.error.notFound': 'The dsh-web-ui aggregate package is not installed; nothing to update.',
|
|
214
214
|
'update.error.link': 'Local development mode (link install) — automatic update is unavailable.',
|
package/src/update.ts
CHANGED
|
@@ -383,53 +383,143 @@ export interface UpdateRunDeps {
|
|
|
383
383
|
spawnImpl?: typeof spawn
|
|
384
384
|
/** Hard timeout; the child is killed on expiry. */
|
|
385
385
|
timeoutMs?: number
|
|
386
|
+
/** Platform override (defaults to process.platform; test seam). */
|
|
387
|
+
platform?: NodeJS.Platform
|
|
386
388
|
}
|
|
387
389
|
|
|
388
390
|
/** Cap on captured pnpm output (keeps error payloads bounded). */
|
|
389
391
|
const OUTPUT_CAP = 16 * 1024
|
|
390
392
|
|
|
391
393
|
/**
|
|
392
|
-
*
|
|
394
|
+
* Windows cmd command-not-found stderr. With shell:true a missing shim
|
|
395
|
+
* exits with code 1 (cmd cannot report ENOENT), so the fallback chain
|
|
396
|
+
* detects this message instead of the spawn error event.
|
|
397
|
+
*/
|
|
398
|
+
const WIN_CMD_MISSING_RE = /not recognized as an internal or external command/i
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Run the update inside the profile directory. Tries pnpm first, falls back
|
|
402
|
+
* to corepack and then npx when the previous command is missing (ENOENT);
|
|
403
|
+
* all candidates share one hard timeout and keep accumulating output.
|
|
393
404
|
* @param deps - profile dir, package list, and spawn/timeout seams.
|
|
394
405
|
* @returns the outcome with captured output.
|
|
395
406
|
*/
|
|
396
407
|
export function runUpdate(deps: UpdateRunDeps): Promise<UpdateRunResult> {
|
|
397
408
|
return new Promise((resolve) => {
|
|
398
|
-
const
|
|
409
|
+
const spawnImpl = deps.spawnImpl ?? spawn
|
|
410
|
+
const packages = deps.packages
|
|
411
|
+
const platform = deps.platform ?? process.platform
|
|
412
|
+
// Windows ships pnpm/corepack/npx as .cmd shims; Node's spawn cannot
|
|
413
|
+
// start .cmd files directly (ENOENT even when installed), so route
|
|
414
|
+
// them through cmd.exe there. POSIX spawns stay shell-free.
|
|
415
|
+
const spawnOptions: Record<string, unknown> = {
|
|
399
416
|
cwd: deps.profileDir,
|
|
400
417
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
401
|
-
|
|
418
|
+
...(platform === 'win32' ? { shell: true } : {}),
|
|
419
|
+
}
|
|
420
|
+
// Ordered fallback chain: each is tried only when the previous one is
|
|
421
|
+
// missing on PATH (ENOENT on the spawn error event, never on close).
|
|
422
|
+
const candidates: ReadonlyArray<{ command: string; args: string[] }> = [
|
|
423
|
+
{ command: 'pnpm', args: ['update', ...packages] },
|
|
424
|
+
{ command: 'corepack', args: ['pnpm', 'update', ...packages] },
|
|
425
|
+
{ command: 'npx', args: ['--yes', 'pnpm', 'update', ...packages] },
|
|
426
|
+
]
|
|
427
|
+
// `output` accumulates across candidates for UI display; `currentOutput`
|
|
428
|
+
// is reset per candidate and carries only that candidate's own diagnostics
|
|
429
|
+
// (its tail is subject to OUTPUT_CAP). The win32 missing-command test must
|
|
430
|
+
// run against `currentOutput` so a previous candidate's 'not recognized'
|
|
431
|
+
// stderr cannot misclassify a real failure in the next one.
|
|
402
432
|
let output = ''
|
|
433
|
+
let currentOutput = ''
|
|
403
434
|
const append = (chunk: Buffer): void => {
|
|
404
435
|
output += chunk.toString('utf8')
|
|
405
436
|
if (output.length > OUTPUT_CAP) output = output.slice(output.length - OUTPUT_CAP)
|
|
437
|
+
currentOutput += chunk.toString('utf8')
|
|
438
|
+
if (currentOutput.length > OUTPUT_CAP) currentOutput = currentOutput.slice(currentOutput.length - OUTPUT_CAP)
|
|
439
|
+
}
|
|
440
|
+
let currentChild: ReturnType<typeof spawnImpl> | undefined
|
|
441
|
+
// Terminal guard: once a result is produced the promise is settled and no
|
|
442
|
+
// further candidate is started (a killed child's late close must not
|
|
443
|
+
// respawn anything after a timeout resolution).
|
|
444
|
+
let finished = false
|
|
445
|
+
const finish = (result: UpdateRunResult): void => {
|
|
446
|
+
if (finished) return
|
|
447
|
+
finished = true
|
|
448
|
+
clearTimeout(timer)
|
|
449
|
+
resolve(result)
|
|
406
450
|
}
|
|
407
|
-
child.stdout?.on('data', append)
|
|
408
|
-
child.stderr?.on('data', append)
|
|
409
451
|
const timer = setTimeout(() => {
|
|
410
|
-
|
|
411
|
-
|
|
452
|
+
if (finished) return
|
|
453
|
+
if (platform === 'win32') {
|
|
454
|
+
// cmd.exe under shell:true only kills the shell wrapper; kill the whole
|
|
455
|
+
// process tree best-effort so pnpm/npx do not keep running.
|
|
456
|
+
const pid = (currentChild as { pid?: number } | undefined)?.pid
|
|
457
|
+
if (pid !== undefined && pid > 0) {
|
|
458
|
+
try {
|
|
459
|
+
spawnImpl('taskkill', ['/pid', String(pid), '/t', '/f'], { stdio: 'ignore' })
|
|
460
|
+
} catch {
|
|
461
|
+
// Best-effort kill; fall through to the timeout result.
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
} else {
|
|
465
|
+
currentChild?.kill('SIGTERM')
|
|
466
|
+
}
|
|
467
|
+
finish({ ok: false, exitCode: null, output, error: 'update timed out; install process killed', errorCode: 'timeout' })
|
|
412
468
|
}, deps.timeoutMs ?? 10 * 60_000)
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
469
|
+
const runCandidate = (index: number): void => {
|
|
470
|
+
if (finished) return
|
|
471
|
+
if (index >= candidates.length) {
|
|
472
|
+
finish({
|
|
473
|
+
ok: false,
|
|
474
|
+
exitCode: null,
|
|
475
|
+
output,
|
|
476
|
+
error: 'pnpm not found on PATH (tried pnpm, corepack, npx); install pnpm and restart the app',
|
|
477
|
+
errorCode: 'pnpm-missing',
|
|
478
|
+
})
|
|
479
|
+
return
|
|
480
|
+
}
|
|
481
|
+
const candidate = candidates[index]
|
|
482
|
+
currentOutput = ''
|
|
483
|
+
const child = spawnImpl(candidate.command, candidate.args, spawnOptions)
|
|
484
|
+
currentChild = child
|
|
485
|
+
let settled = false
|
|
486
|
+
const once = (fn: () => void): void => {
|
|
487
|
+
if (settled) return
|
|
488
|
+
settled = true
|
|
489
|
+
fn()
|
|
490
|
+
}
|
|
491
|
+
child.stdout?.on('data', append)
|
|
492
|
+
child.stderr?.on('data', append)
|
|
493
|
+
child.on('error', (error: Error) => {
|
|
494
|
+
once(() => {
|
|
495
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
496
|
+
// Command missing: try the next candidate in the chain.
|
|
497
|
+
runCandidate(index + 1)
|
|
498
|
+
} else {
|
|
499
|
+
finish({ ok: false, exitCode: null, output, error: error.message, errorCode: undefined })
|
|
500
|
+
}
|
|
501
|
+
})
|
|
422
502
|
})
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
503
|
+
child.on('close', (code: number | null) => {
|
|
504
|
+
if (settled || finished) return
|
|
505
|
+
// Windows + shell: a missing command reports exit 1 with
|
|
506
|
+
// 'not recognized' instead of ENOENT — keep the chain going. Checked
|
|
507
|
+
// against this candidate's own output so a prior fallback's stderr
|
|
508
|
+
// cannot misclassify a real failure here.
|
|
509
|
+
if (platform === 'win32' && code !== 0 && WIN_CMD_MISSING_RE.test(currentOutput)) {
|
|
510
|
+
runCandidate(index + 1)
|
|
511
|
+
return
|
|
512
|
+
}
|
|
513
|
+
settled = true
|
|
514
|
+
finish({
|
|
515
|
+
ok: code === 0,
|
|
516
|
+
exitCode: code,
|
|
517
|
+
output,
|
|
518
|
+
error: code === 0 ? undefined : 'pnpm exited with code ' + String(code),
|
|
519
|
+
...(code === 0 ? {} : { errorCode: 'pnpm-failed' as const }),
|
|
520
|
+
})
|
|
432
521
|
})
|
|
433
|
-
}
|
|
522
|
+
}
|
|
523
|
+
runCandidate(0)
|
|
434
524
|
})
|
|
435
525
|
}
|