@blackbelt-technology/pi-agent-dashboard 0.3.0 → 0.4.1
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/AGENTS.md +87 -114
- package/README.md +408 -430
- package/docs/architecture.md +465 -12
- package/package.json +10 -5
- package/packages/extension/package.json +14 -4
- package/packages/extension/src/__tests__/ask-user-tool.test.ts +40 -8
- package/packages/extension/src/__tests__/bridge-entry-id-pi-070.test.ts +174 -0
- package/packages/extension/src/__tests__/enrich-model-metadata.test.ts +201 -0
- package/packages/extension/src/__tests__/event-forwarder.test.ts +30 -0
- package/packages/extension/src/__tests__/fork-entryid-timing.test.ts +64 -76
- package/packages/extension/src/__tests__/git-info.test.ts +67 -55
- package/packages/extension/src/__tests__/multiselect-list.test.ts +137 -0
- package/packages/extension/src/__tests__/no-session-replacement-calls.test.ts +99 -0
- package/packages/extension/src/__tests__/openspec-poller.test.ts +101 -96
- package/packages/extension/src/__tests__/process-scanner-kill.test.ts +61 -0
- package/packages/extension/src/__tests__/provider-register-reload.test.ts +394 -0
- package/packages/extension/src/__tests__/server-auto-start.test.ts +95 -4
- package/packages/extension/src/__tests__/server-launcher.test.ts +16 -0
- package/packages/extension/src/ask-user-tool.ts +5 -4
- package/packages/extension/src/bridge.ts +171 -17
- package/packages/extension/src/dev-build.ts +1 -1
- package/packages/extension/src/git-info.ts +9 -19
- package/packages/extension/src/multiselect-list.ts +146 -0
- package/packages/extension/src/multiselect-polyfill.ts +43 -0
- package/packages/extension/src/pi-env.d.ts +1 -0
- package/packages/extension/src/process-scanner.ts +72 -38
- package/packages/extension/src/provider-register.ts +304 -16
- package/packages/extension/src/server-auto-start.ts +27 -1
- package/packages/extension/src/server-launcher.ts +83 -27
- package/packages/server/package.json +16 -2
- package/packages/server/src/__tests__/bootstrap-queue.test.ts +120 -0
- package/packages/server/src/__tests__/bootstrap-routes.test.ts +125 -0
- package/packages/server/src/__tests__/bootstrap-state.test.ts +119 -0
- package/packages/server/src/__tests__/browse-endpoint.test.ts +17 -0
- package/packages/server/src/__tests__/cli-parse.test.ts +11 -0
- package/packages/server/src/__tests__/concurrent-launch.test.ts +110 -0
- package/packages/server/src/__tests__/config-api.test.ts +68 -0
- package/packages/server/src/__tests__/crash-recovery.test.ts +88 -0
- package/packages/server/src/__tests__/directory-service.test.ts +234 -8
- package/packages/server/src/__tests__/editor-registry.test.ts +28 -15
- package/packages/server/src/__tests__/extension-register-appimage.test.ts +5 -1
- package/packages/server/src/__tests__/extension-register.test.ts +3 -1
- package/packages/server/src/__tests__/find-port-holders.test.ts +94 -0
- package/packages/server/src/__tests__/fixtures/fork-jsonl-roundtrip.jsonl +8 -0
- package/packages/server/src/__tests__/force-kill-handler.test.ts +57 -8
- package/packages/server/src/__tests__/fork-jsonl-roundtrip.test.ts +49 -0
- package/packages/server/src/__tests__/home-lock-escape-hatch.test.ts +60 -0
- package/packages/server/src/__tests__/home-lock-release.test.ts +85 -0
- package/packages/server/src/__tests__/home-lock.test.ts +308 -0
- package/packages/server/src/__tests__/is-pi-process.test.ts +36 -0
- package/packages/server/src/__tests__/node-guard.test.ts +85 -0
- package/packages/server/src/__tests__/package-manager-wrapper-resolve.test.ts +5 -1
- package/packages/server/src/__tests__/package-manager-wrapper.test.ts +45 -10
- package/packages/server/src/__tests__/pi-version-skew.test.ts +237 -0
- package/packages/server/src/__tests__/preferences-store.test.ts +73 -4
- package/packages/server/src/__tests__/process-manager.test.ts +45 -18
- package/packages/server/src/__tests__/provider-probe.test.ts +287 -0
- package/packages/server/src/__tests__/provider-test-route.test.ts +149 -0
- package/packages/server/src/__tests__/restart-helper.test.ts +111 -0
- package/packages/server/src/__tests__/session-action-handler-headless-reload.test.ts +467 -0
- package/packages/server/src/__tests__/session-action-handler-reload-predicate.test.ts +73 -0
- package/packages/server/src/__tests__/session-action-handler-spawn-error.test.ts +74 -0
- package/packages/server/src/__tests__/terminal-manager.test.ts +41 -1
- package/packages/server/src/__tests__/tool-routes.test.ts +277 -0
- package/packages/server/src/__tests__/trusted-networks-config.test.ts +19 -0
- package/packages/server/src/__tests__/trusted-networks-no-oauth-roundtrip.test.ts +126 -0
- package/packages/server/src/__tests__/tunnel-cleanup.test.ts +90 -0
- package/packages/server/src/__tests__/tunnel.test.ts +13 -7
- package/packages/server/src/__tests__/wsl-tmux-probe-cache.test.ts +44 -0
- package/packages/server/src/bootstrap-queue.ts +130 -0
- package/packages/server/src/bootstrap-state.ts +131 -0
- package/packages/server/src/browse.ts +8 -3
- package/packages/server/src/browser-handlers/directory-handler.ts +23 -8
- package/packages/server/src/browser-handlers/session-action-handler.ts +213 -79
- package/packages/server/src/browser-handlers/session-action-helpers.ts +36 -0
- package/packages/server/src/cli.ts +310 -39
- package/packages/server/src/config-api.ts +16 -0
- package/packages/server/src/directory-service.ts +270 -39
- package/packages/server/src/editor-detection.ts +12 -9
- package/packages/server/src/editor-manager.ts +19 -4
- package/packages/server/src/editor-pid-registry.ts +9 -8
- package/packages/server/src/editor-registry.ts +22 -25
- package/packages/server/src/git-operations.ts +1 -1
- package/packages/server/src/headless-pid-registry.ts +7 -20
- package/packages/server/src/home-lock-release.ts +72 -0
- package/packages/server/src/home-lock.ts +389 -0
- package/packages/server/src/node-guard.ts +52 -0
- package/packages/server/src/package-manager-wrapper.ts +207 -47
- package/packages/server/src/pi-core-checker.ts +1 -1
- package/packages/server/src/pi-core-updater.ts +7 -1
- package/packages/server/src/pi-resource-scanner.ts +5 -8
- package/packages/server/src/pi-version-skew.ts +207 -0
- package/packages/server/src/preferences-store.ts +17 -3
- package/packages/server/src/process-manager.ts +403 -222
- package/packages/server/src/provider-probe.ts +234 -0
- package/packages/server/src/restart-helper.ts +141 -0
- package/packages/server/src/routes/bootstrap-routes.ts +88 -0
- package/packages/server/src/routes/openspec-routes.ts +25 -1
- package/packages/server/src/routes/pi-core-routes.ts +24 -1
- package/packages/server/src/routes/provider-auth-routes.ts +8 -8
- package/packages/server/src/routes/provider-routes.ts +43 -0
- package/packages/server/src/routes/recommended-routes.ts +10 -12
- package/packages/server/src/routes/system-routes.ts +20 -33
- package/packages/server/src/routes/tool-routes.ts +153 -0
- package/packages/server/src/server-pid.ts +5 -9
- package/packages/server/src/server.ts +211 -10
- package/packages/server/src/session-api.ts +77 -8
- package/packages/server/src/session-bootstrap.ts +17 -3
- package/packages/server/src/session-diff.ts +21 -21
- package/packages/server/src/terminal-manager.ts +61 -20
- package/packages/server/src/tunnel.ts +42 -28
- package/packages/shared/package.json +10 -3
- package/packages/shared/src/__tests__/{tool-resolver.test.ts → binary-lookup.test.ts} +32 -12
- package/packages/shared/src/__tests__/bootstrap/README.md +133 -0
- package/packages/shared/src/__tests__/bootstrap/__snapshots__/cube.test.ts.snap +370 -0
- package/packages/shared/src/__tests__/bootstrap/assertions.ts +136 -0
- package/packages/shared/src/__tests__/bootstrap/cube.test.ts +47 -0
- package/packages/shared/src/__tests__/bootstrap/cube.ts +66 -0
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/a-electron.test.ts.snap +83 -0
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/b-npm-global.test.ts.snap +89 -0
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/c-dev-monorepo.test.ts.snap +33 -0
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/d-overrides.test.ts.snap +20 -0
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/e-stale-partial.test.ts.snap +61 -0
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/f-cwd-variants.test.ts.snap +33 -0
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/g-windows-specifics.test.ts.snap +46 -0
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/j-path-gui-minimal.test.ts.snap +12 -0
- package/packages/shared/src/__tests__/bootstrap/families/a-electron.test.ts +156 -0
- package/packages/shared/src/__tests__/bootstrap/families/b-npm-global.test.ts +157 -0
- package/packages/shared/src/__tests__/bootstrap/families/c-dev-monorepo.test.ts +102 -0
- package/packages/shared/src/__tests__/bootstrap/families/d-overrides.test.ts +76 -0
- package/packages/shared/src/__tests__/bootstrap/families/e-stale-partial.test.ts +94 -0
- package/packages/shared/src/__tests__/bootstrap/families/f-cwd-variants.test.ts +87 -0
- package/packages/shared/src/__tests__/bootstrap/families/g-windows-specifics.test.ts +143 -0
- package/packages/shared/src/__tests__/bootstrap/families/h-home-drift.test.ts +64 -0
- package/packages/shared/src/__tests__/bootstrap/families/i-malformed-settings.test.ts +77 -0
- package/packages/shared/src/__tests__/bootstrap/families/index.ts +19 -0
- package/packages/shared/src/__tests__/bootstrap/families/j-path-gui-minimal.test.ts +61 -0
- package/packages/shared/src/__tests__/bootstrap/families/k-dashboard-absent.test.ts +50 -0
- package/packages/shared/src/__tests__/bootstrap/families/l-instance-coordination.test.ts +272 -0
- package/packages/shared/src/__tests__/bootstrap/fixtures/dev-monorepo.ts +58 -0
- package/packages/shared/src/__tests__/bootstrap/fixtures/electron-layout.ts +84 -0
- package/packages/shared/src/__tests__/bootstrap/fixtures/index.ts +9 -0
- package/packages/shared/src/__tests__/bootstrap/fixtures/managed-install.ts +85 -0
- package/packages/shared/src/__tests__/bootstrap/fixtures/npm-global-layout.ts +122 -0
- package/packages/shared/src/__tests__/bootstrap/fixtures/pi-versions.ts +36 -0
- package/packages/shared/src/__tests__/bootstrap/fixtures/settings-json.ts +39 -0
- package/packages/shared/src/__tests__/bootstrap/harness.smoke.test.ts +220 -0
- package/packages/shared/src/__tests__/bootstrap/harness.ts +413 -0
- package/packages/shared/src/__tests__/bootstrap/scenarios-skipped.ts +125 -0
- package/packages/shared/src/__tests__/bootstrap/scenarios.ts +132 -0
- package/packages/shared/src/__tests__/bridge-register.test.ts +29 -6
- package/packages/shared/src/__tests__/config-openspec.test.ts +106 -0
- package/packages/shared/src/__tests__/config.test.ts +56 -0
- package/packages/shared/src/__tests__/detached-spawn.test.ts +243 -0
- package/packages/shared/src/__tests__/managed-paths.test.ts +60 -0
- package/packages/shared/src/__tests__/no-direct-child-process.test.ts +112 -0
- package/packages/shared/src/__tests__/no-direct-platform-branch.test.ts +174 -0
- package/packages/shared/src/__tests__/no-direct-process-kill.test.ts +105 -0
- package/packages/shared/src/__tests__/no-hardcoded-node-modules-paths.test.ts +176 -0
- package/packages/shared/src/__tests__/no-raw-node-import.test.ts +146 -0
- package/packages/shared/src/__tests__/node-spawn.test.ts +210 -0
- package/packages/shared/src/__tests__/platform-commands.test.ts +108 -0
- package/packages/shared/src/__tests__/platform-exec.test.ts +103 -0
- package/packages/shared/src/__tests__/platform-git.test.ts +194 -0
- package/packages/shared/src/__tests__/platform-npm.test.ts +137 -0
- package/packages/shared/src/__tests__/platform-openspec.test.ts +92 -0
- package/packages/shared/src/__tests__/platform-paths.test.ts +284 -0
- package/packages/shared/src/__tests__/platform-process-scan.test.ts +55 -0
- package/packages/shared/src/__tests__/platform-process.test.ts +160 -0
- package/packages/shared/src/__tests__/platform-runner.test.ts +173 -0
- package/packages/shared/src/__tests__/platform-shell.test.ts +74 -0
- package/packages/shared/src/__tests__/process-identify.test.ts +113 -0
- package/packages/shared/src/__tests__/recommended-extensions.test.ts +40 -7
- package/packages/shared/src/__tests__/resolve-jiti.test.ts +43 -7
- package/packages/shared/src/__tests__/resolve-tool-cli.test.ts +105 -0
- package/packages/shared/src/__tests__/semaphore.test.ts +119 -0
- package/packages/shared/src/__tests__/spawn-mechanism.test.ts +131 -0
- package/packages/shared/src/__tests__/state-replay-entry-id.test.ts +69 -0
- package/packages/shared/src/__tests__/tool-registry-definitions.test.ts +239 -0
- package/packages/shared/src/__tests__/tool-registry-overrides.test.ts +137 -0
- package/packages/shared/src/__tests__/tool-registry-registry.test.ts +343 -0
- package/packages/shared/src/bootstrap-install.ts +212 -0
- package/packages/shared/src/bridge-register.ts +87 -20
- package/packages/shared/src/browser-protocol.ts +71 -1
- package/packages/shared/src/config.ts +87 -15
- package/packages/shared/src/managed-paths.ts +31 -4
- package/packages/shared/src/openspec-poller.ts +63 -46
- package/packages/shared/src/{tool-resolver.ts → platform/binary-lookup.ts} +125 -25
- package/packages/shared/src/platform/commands.ts +100 -0
- package/packages/shared/src/platform/detached-spawn.ts +305 -0
- package/packages/shared/src/platform/exec.ts +220 -0
- package/packages/shared/src/platform/git.ts +155 -0
- package/packages/shared/src/platform/index.ts +16 -0
- package/packages/shared/src/platform/node-spawn.ts +154 -0
- package/packages/shared/src/platform/npm.ts +162 -0
- package/packages/shared/src/platform/openspec.ts +91 -0
- package/packages/shared/src/platform/paths.ts +276 -0
- package/packages/shared/src/platform/process-identify.ts +126 -0
- package/packages/shared/src/platform/process-scan.ts +94 -0
- package/packages/shared/src/platform/process.ts +168 -0
- package/packages/shared/src/platform/runner.ts +369 -0
- package/packages/shared/src/platform/shell.ts +44 -0
- package/packages/shared/src/platform/spawn-mechanism.ts +124 -0
- package/packages/shared/src/platform/subprocess-adapter.ts +124 -0
- package/packages/shared/src/protocol.ts +23 -0
- package/packages/shared/src/recommended-extensions.ts +18 -2
- package/packages/shared/src/resolve-jiti.ts +62 -3
- package/packages/shared/src/rest-api.ts +26 -0
- package/packages/shared/src/semaphore.ts +83 -0
- package/packages/shared/src/state-replay.ts +9 -0
- package/packages/shared/src/tool-registry/definitions.ts +434 -0
- package/packages/shared/src/tool-registry/index.ts +56 -0
- package/packages/shared/src/tool-registry/overrides.ts +118 -0
- package/packages/shared/src/tool-registry/registry.ts +262 -0
- package/packages/shared/src/tool-registry/strategies.ts +198 -0
- package/packages/shared/src/tool-registry/types.ts +180 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Safe child-process wrappers that always set `windowsHide: true`.
|
|
3
|
+
*
|
|
4
|
+
* Rationale
|
|
5
|
+
* ─────────
|
|
6
|
+
* On Windows, spawning a `.cmd` shim (or anything node.exe wraps via cmd.exe)
|
|
7
|
+
* flashes a cmd-prompt window unless `windowsHide: true` is passed. This is
|
|
8
|
+
* a universal source of visible-UI bugs in this project: bridge process
|
|
9
|
+
* scanners, git polls, openspec polls, terminal subprocess checks, etc.
|
|
10
|
+
* Every spawn site needed to remember to set the flag, and we kept missing
|
|
11
|
+
* some (session-diff, git-operations, update-checker, doctor, tunnel, ...).
|
|
12
|
+
*
|
|
13
|
+
* Rather than fixing this per call site forever, this module wraps the
|
|
14
|
+
* Node `child_process` API with `windowsHide: true` as the default. Callers
|
|
15
|
+
* can still override by explicitly passing `windowsHide: false` if they
|
|
16
|
+
* genuinely want a visible console (none of our callers do).
|
|
17
|
+
*
|
|
18
|
+
* **Every spawn in packages/*\/src SHOULD import from here** instead of
|
|
19
|
+
* directly from `node:child_process`. A repo-level check can fail if
|
|
20
|
+
* direct imports sneak back in. See change: consolidate-platform-handlers.
|
|
21
|
+
*/
|
|
22
|
+
import {
|
|
23
|
+
execSync as nodeExecSync,
|
|
24
|
+
exec as nodeExec,
|
|
25
|
+
execFile as nodeExecFile,
|
|
26
|
+
spawnSync as nodeSpawnSync,
|
|
27
|
+
spawn as nodeSpawn,
|
|
28
|
+
type ExecSyncOptions,
|
|
29
|
+
type ExecOptions,
|
|
30
|
+
type ExecFileOptions,
|
|
31
|
+
type SpawnSyncOptions,
|
|
32
|
+
type SpawnOptions,
|
|
33
|
+
type ChildProcess,
|
|
34
|
+
type SpawnSyncReturns,
|
|
35
|
+
} from "node:child_process";
|
|
36
|
+
import { promisify } from "node:util";
|
|
37
|
+
|
|
38
|
+
// ── Argv safety (Windows .cmd / .bat handling) ─────────────────────────────
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Build a spawn-safe argv for ANY command on ANY platform.
|
|
42
|
+
*
|
|
43
|
+
* The canonical way to invoke subprocesses without flashing cmd.exe
|
|
44
|
+
* console windows on Windows. Handles three cases:
|
|
45
|
+
*
|
|
46
|
+
* 1. Windows + `.cmd` / `.bat` shim → explicit `cmd.exe /c <cmd> <args>`.
|
|
47
|
+
* This is the ONLY reliable way to invoke `.cmd` files without the
|
|
48
|
+
* flashing-console bug (Node issue #21825, which happens when
|
|
49
|
+
* `shell: true` is combined with `.cmd` + `detached` + `windowsHide`).
|
|
50
|
+
* cmd.exe respects `windowsHide: true` on its own console directly.
|
|
51
|
+
*
|
|
52
|
+
* 2. Windows + native binary (`.exe`) → direct argv.
|
|
53
|
+
*
|
|
54
|
+
* 3. Unix (any binary or shell script) → direct argv.
|
|
55
|
+
*
|
|
56
|
+
* Always returns `{ shell: false, windowsHide: true }` — NEVER uses
|
|
57
|
+
* `shell: true`. Callers pass these spawn options along with the argv.
|
|
58
|
+
*
|
|
59
|
+
* Example:
|
|
60
|
+
* const { argv, spawnOptions } = buildSafeArgv("npm.cmd", ["root", "-g"]);
|
|
61
|
+
* spawnSync(argv[0], argv.slice(1), { cwd, env, ...spawnOptions });
|
|
62
|
+
*
|
|
63
|
+
* See change: consolidate-windows-spawn-and-platform-handlers.
|
|
64
|
+
*/
|
|
65
|
+
export interface SafeArgv {
|
|
66
|
+
argv: string[];
|
|
67
|
+
spawnOptions: { shell: false; windowsHide: true };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function buildSafeArgv(
|
|
71
|
+
cmd: string,
|
|
72
|
+
args: readonly string[] = [],
|
|
73
|
+
platform: NodeJS.Platform = process.platform,
|
|
74
|
+
): SafeArgv {
|
|
75
|
+
if (platform === "win32") {
|
|
76
|
+
// Route through cmd.exe for TWO cases:
|
|
77
|
+
// 1. Explicit .cmd/.bat shim — Node can't spawn these directly
|
|
78
|
+
// with shell:false (CVE-2024-27980 fix in Node >= 20.12).
|
|
79
|
+
// 2. Extensionless name (e.g. "npm", "pi", "git") — Windows
|
|
80
|
+
// resolves these via PATHEXT, but only shells do. Without
|
|
81
|
+
// cmd.exe, spawn("npm") returns ENOENT because there's no
|
|
82
|
+
// literal "npm" binary — just "npm.cmd".
|
|
83
|
+
// Native .exe / absolute paths bypass cmd.exe (no PATHEXT needed).
|
|
84
|
+
//
|
|
85
|
+
// /d = skip AutoRun, /s = treat quoted first token as command
|
|
86
|
+
// (preserves spaces), /c = run and exit. cmd.exe honors
|
|
87
|
+
// windowsHide on its console, so inner .cmd's node.exe inherits an
|
|
88
|
+
// invisible console — no flash.
|
|
89
|
+
const isShim = /\.(cmd|bat)$/i.test(cmd);
|
|
90
|
+
const hasExtension = /\.[A-Za-z0-9]+$/.test(cmd);
|
|
91
|
+
if (isShim || !hasExtension) {
|
|
92
|
+
return {
|
|
93
|
+
argv: ["cmd.exe", "/d", "/s", "/c", cmd, ...args],
|
|
94
|
+
spawnOptions: { shell: false, windowsHide: true },
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
argv: [cmd, ...args],
|
|
100
|
+
spawnOptions: { shell: false, windowsHide: true },
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ── Option helpers ──────────────────────────────────────────────────────────
|
|
105
|
+
|
|
106
|
+
type AnyOptions = { windowsHide?: boolean } | undefined;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Merge caller options with `windowsHide: true` as the default.
|
|
110
|
+
* Explicit `windowsHide: false` from the caller is honored (for the rare
|
|
111
|
+
* case where a visible console is actually desired).
|
|
112
|
+
*/
|
|
113
|
+
function withHide<T extends AnyOptions>(opts: T): T & { windowsHide: boolean } {
|
|
114
|
+
const hide = opts?.windowsHide ?? true;
|
|
115
|
+
return { ...(opts ?? {}), windowsHide: hide } as T & { windowsHide: boolean };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ── Synchronous wrappers ────────────────────────────────────────────────────
|
|
119
|
+
|
|
120
|
+
/** Wrapped `execSync`. Always `windowsHide: true` unless overridden. */
|
|
121
|
+
export function execSync(command: string, options: ExecSyncOptions & { encoding: BufferEncoding }): string;
|
|
122
|
+
export function execSync(command: string, options?: ExecSyncOptions): Buffer | string;
|
|
123
|
+
export function execSync(
|
|
124
|
+
command: string,
|
|
125
|
+
options?: ExecSyncOptions,
|
|
126
|
+
): Buffer | string {
|
|
127
|
+
return nodeExecSync(command, withHide(options));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Wrapped `spawnSync`. Always `windowsHide: true` unless overridden. */
|
|
131
|
+
export function spawnSync<T extends string | Buffer = Buffer>(
|
|
132
|
+
command: string,
|
|
133
|
+
args?: readonly string[],
|
|
134
|
+
options?: SpawnSyncOptions,
|
|
135
|
+
): SpawnSyncReturns<T> {
|
|
136
|
+
return nodeSpawnSync(command, args ?? [], withHide(options)) as SpawnSyncReturns<T>;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ── Asynchronous (callback) wrappers ────────────────────────────────────────
|
|
140
|
+
|
|
141
|
+
/** Wrapped `exec` (callback form). */
|
|
142
|
+
export function exec(
|
|
143
|
+
command: string,
|
|
144
|
+
callback?: (err: Error | null, stdout: string, stderr: string) => void,
|
|
145
|
+
): ChildProcess;
|
|
146
|
+
export function exec(
|
|
147
|
+
command: string,
|
|
148
|
+
options: ExecOptions,
|
|
149
|
+
callback?: (err: Error | null, stdout: string | Buffer, stderr: string | Buffer) => void,
|
|
150
|
+
): ChildProcess;
|
|
151
|
+
export function exec(
|
|
152
|
+
command: string,
|
|
153
|
+
optionsOrCallback?: ExecOptions | ((err: Error | null, stdout: any, stderr: any) => void),
|
|
154
|
+
maybeCallback?: (err: Error | null, stdout: any, stderr: any) => void,
|
|
155
|
+
): ChildProcess {
|
|
156
|
+
if (typeof optionsOrCallback === "function") {
|
|
157
|
+
return nodeExec(command, withHide(undefined) as ExecOptions, optionsOrCallback);
|
|
158
|
+
}
|
|
159
|
+
return nodeExec(command, withHide(optionsOrCallback) as ExecOptions, maybeCallback);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Wrapped `execFile` (callback form). */
|
|
163
|
+
export function execFile(
|
|
164
|
+
file: string,
|
|
165
|
+
args: readonly string[] | undefined,
|
|
166
|
+
options: ExecFileOptions,
|
|
167
|
+
callback?: (err: Error | null, stdout: string | Buffer, stderr: string | Buffer) => void,
|
|
168
|
+
): ChildProcess;
|
|
169
|
+
export function execFile(
|
|
170
|
+
file: string,
|
|
171
|
+
args?: readonly string[],
|
|
172
|
+
callback?: (err: Error | null, stdout: string, stderr: string) => void,
|
|
173
|
+
): ChildProcess;
|
|
174
|
+
export function execFile(
|
|
175
|
+
file: string,
|
|
176
|
+
args?: readonly string[],
|
|
177
|
+
optionsOrCallback?: ExecFileOptions | ((err: Error | null, stdout: any, stderr: any) => void),
|
|
178
|
+
maybeCallback?: (err: Error | null, stdout: any, stderr: any) => void,
|
|
179
|
+
): ChildProcess {
|
|
180
|
+
if (typeof optionsOrCallback === "function") {
|
|
181
|
+
return nodeExecFile(file, args ?? [], withHide(undefined) as ExecFileOptions, optionsOrCallback);
|
|
182
|
+
}
|
|
183
|
+
return nodeExecFile(file, args ?? [], withHide(optionsOrCallback) as ExecFileOptions, maybeCallback);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Wrapped `spawn`. Always `windowsHide: true` unless overridden. */
|
|
187
|
+
export function spawn(
|
|
188
|
+
command: string,
|
|
189
|
+
args?: readonly string[],
|
|
190
|
+
options?: SpawnOptions,
|
|
191
|
+
): ChildProcess {
|
|
192
|
+
return nodeSpawn(command, args ?? [], withHide(options));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ── Promise-returning variants ──────────────────────────────────────────────
|
|
196
|
+
|
|
197
|
+
/** Promise-returning exec. */
|
|
198
|
+
export const execAsync = promisify(exec) as unknown as (
|
|
199
|
+
command: string,
|
|
200
|
+
options?: ExecOptions,
|
|
201
|
+
) => Promise<{ stdout: string | Buffer; stderr: string | Buffer }>;
|
|
202
|
+
|
|
203
|
+
/** Promise-returning execFile. */
|
|
204
|
+
export const execFileAsync = promisify(execFile) as unknown as (
|
|
205
|
+
file: string,
|
|
206
|
+
args?: readonly string[],
|
|
207
|
+
options?: ExecFileOptions,
|
|
208
|
+
) => Promise<{ stdout: string | Buffer; stderr: string | Buffer }>;
|
|
209
|
+
|
|
210
|
+
// ── Types pass-through for convenience ──────────────────────────────────────
|
|
211
|
+
|
|
212
|
+
export type {
|
|
213
|
+
ExecSyncOptions,
|
|
214
|
+
ExecOptions,
|
|
215
|
+
ExecFileOptions,
|
|
216
|
+
SpawnSyncOptions,
|
|
217
|
+
SpawnOptions,
|
|
218
|
+
ChildProcess,
|
|
219
|
+
SpawnSyncReturns,
|
|
220
|
+
};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git tool module — Recipe-based API for git operations the dashboard runs
|
|
3
|
+
* from multiple call sites (session-diff, git-info extension, doctor).
|
|
4
|
+
*
|
|
5
|
+
* Every function in this file is a thin wrapper over `run(recipe, input)`:
|
|
6
|
+
* no `child_process` imports, no `process.platform` branches, no inline
|
|
7
|
+
* shell-escape logic. The Recipe objects describe *what* git invocation
|
|
8
|
+
* to run; the runner handles *how* to spawn it safely.
|
|
9
|
+
*
|
|
10
|
+
* Exit codes:
|
|
11
|
+
* `git diff` exits 0 when there's a diff, 1 when there's nothing to
|
|
12
|
+
* show (we tolerate 1).
|
|
13
|
+
* Other commands exit 0 on success and non-zero on real errors.
|
|
14
|
+
*
|
|
15
|
+
* See change: platform-command-executor.
|
|
16
|
+
*/
|
|
17
|
+
import { run, unwrap, type Recipe, type Result } from "./runner.js";
|
|
18
|
+
|
|
19
|
+
// ── Recipes (pure data) ─────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
const GIT_TIMEOUT = 15_000;
|
|
22
|
+
|
|
23
|
+
interface WithCwd {
|
|
24
|
+
cwd: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const GIT_IS_REPO: Recipe<WithCwd, boolean> = {
|
|
28
|
+
argv: () => ["git", "rev-parse", "--is-inside-work-tree"],
|
|
29
|
+
parse: (out) => out.trim() === "true",
|
|
30
|
+
timeout: GIT_TIMEOUT,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const GIT_CURRENT_BRANCH: Recipe<WithCwd, string | undefined> = {
|
|
34
|
+
argv: () => ["git", "rev-parse", "--abbrev-ref", "HEAD"],
|
|
35
|
+
parse: (out) => out.trim() || undefined,
|
|
36
|
+
timeout: GIT_TIMEOUT,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const GIT_HEAD_SHA: Recipe<WithCwd & { short?: boolean }, string | undefined> = {
|
|
40
|
+
argv: ({ short }) => short ? ["git", "rev-parse", "--short", "HEAD"] : ["git", "rev-parse", "HEAD"],
|
|
41
|
+
parse: (out) => out.trim() || undefined,
|
|
42
|
+
timeout: GIT_TIMEOUT,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const GIT_REMOTE_URL: Recipe<WithCwd & { remote?: string }, string | undefined> = {
|
|
46
|
+
argv: ({ remote }) => ["git", "remote", "get-url", remote ?? "origin"],
|
|
47
|
+
parse: (out) => out.trim() || undefined,
|
|
48
|
+
timeout: GIT_TIMEOUT,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const GIT_DIFF: Recipe<WithCwd & { path: string; ref?: string }, string> = {
|
|
52
|
+
argv: ({ path, ref }) => ["git", "diff", ref ?? "HEAD", "--", path],
|
|
53
|
+
parse: (out) => out,
|
|
54
|
+
timeout: GIT_TIMEOUT,
|
|
55
|
+
// git diff exits 1 when --exit-code is set or in some configurations;
|
|
56
|
+
// no diff is not an error for our callers.
|
|
57
|
+
tolerate: [1],
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const GIT_STATUS_PORCELAIN: Recipe<WithCwd & { path?: string }, string> = {
|
|
61
|
+
argv: ({ path }) =>
|
|
62
|
+
path === undefined
|
|
63
|
+
? ["git", "status", "--porcelain"]
|
|
64
|
+
: ["git", "status", "--porcelain", "--", path],
|
|
65
|
+
parse: (out) => out,
|
|
66
|
+
timeout: GIT_TIMEOUT,
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* `gh pr view --json number -q .number` — requires the `gh` CLI.
|
|
71
|
+
* Returns undefined when there is no PR for the current branch (gh exits 1).
|
|
72
|
+
*/
|
|
73
|
+
export const GH_PR_NUMBER: Recipe<WithCwd, number | undefined> = {
|
|
74
|
+
argv: () => ["gh", "pr", "view", "--json", "number", "-q", ".number"],
|
|
75
|
+
parse: (out) => {
|
|
76
|
+
const n = parseInt(out.trim(), 10);
|
|
77
|
+
return Number.isFinite(n) ? n : undefined;
|
|
78
|
+
},
|
|
79
|
+
timeout: GIT_TIMEOUT,
|
|
80
|
+
tolerate: [1], // gh exits 1 when no PR exists — not an error
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// ── Registry (for lint / docs / enumeration) ────────────────────────────────
|
|
84
|
+
|
|
85
|
+
export const GIT_RECIPES = {
|
|
86
|
+
GIT_IS_REPO,
|
|
87
|
+
GIT_CURRENT_BRANCH,
|
|
88
|
+
GIT_HEAD_SHA,
|
|
89
|
+
GIT_REMOTE_URL,
|
|
90
|
+
GIT_DIFF,
|
|
91
|
+
GIT_STATUS_PORCELAIN,
|
|
92
|
+
GH_PR_NUMBER,
|
|
93
|
+
} as const;
|
|
94
|
+
|
|
95
|
+
// ── Public API — typed functions (use Result for explicit control) ──────────
|
|
96
|
+
|
|
97
|
+
export function isGitRepo(input: WithCwd): Result<boolean> {
|
|
98
|
+
return run(GIT_IS_REPO, input, { cwd: input.cwd });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function currentBranch(input: WithCwd): Result<string | undefined> {
|
|
102
|
+
return run(GIT_CURRENT_BRANCH, input, { cwd: input.cwd });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function headSha(input: WithCwd & { short?: boolean }): Result<string | undefined> {
|
|
106
|
+
return run(GIT_HEAD_SHA, input, { cwd: input.cwd });
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function remoteUrl(input: WithCwd & { remote?: string }): Result<string | undefined> {
|
|
110
|
+
return run(GIT_REMOTE_URL, input, { cwd: input.cwd });
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function diff(input: WithCwd & { path: string; ref?: string }): Result<string> {
|
|
114
|
+
return run(GIT_DIFF, input, { cwd: input.cwd });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function statusPorcelain(input: WithCwd & { path?: string }): Result<string> {
|
|
118
|
+
return run(GIT_STATUS_PORCELAIN, input, { cwd: input.cwd });
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function prNumber(input: WithCwd): Result<number | undefined> {
|
|
122
|
+
return run(GH_PR_NUMBER, input, { cwd: input.cwd });
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ── Best-effort convenience wrappers (swallow errors → default) ─────────────
|
|
126
|
+
// Callers that only want "the value or a default" without dealing with Result
|
|
127
|
+
// discriminants can use these instead.
|
|
128
|
+
|
|
129
|
+
export function isGitRepoOr(input: WithCwd, fallback = false): boolean {
|
|
130
|
+
return unwrap(isGitRepo(input), fallback);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function currentBranchOr(input: WithCwd, fallback?: string): string | undefined {
|
|
134
|
+
return unwrap(currentBranch(input), fallback);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function headShaOr(input: WithCwd & { short?: boolean }, fallback?: string): string | undefined {
|
|
138
|
+
return unwrap(headSha(input), fallback);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function remoteUrlOr(input: WithCwd & { remote?: string }, fallback?: string): string | undefined {
|
|
142
|
+
return unwrap(remoteUrl(input), fallback);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function diffOr(input: WithCwd & { path: string; ref?: string }, fallback = ""): string {
|
|
146
|
+
return unwrap(diff(input), fallback);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function statusPorcelainOr(input: WithCwd & { path?: string }, fallback = ""): string {
|
|
150
|
+
return unwrap(statusPorcelain(input), fallback);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function prNumberOr(input: WithCwd, fallback?: number): number | undefined {
|
|
154
|
+
return unwrap(prNumber(input), fallback);
|
|
155
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export * from "./binary-lookup.js";
|
|
2
|
+
export * from "./process.js";
|
|
3
|
+
export * from "./process-scan.js";
|
|
4
|
+
export * from "./shell.js";
|
|
5
|
+
export * from "./commands.js";
|
|
6
|
+
export * from "./exec.js";
|
|
7
|
+
export * from "./runner.js";
|
|
8
|
+
export * from "./detached-spawn.js";
|
|
9
|
+
export * from "./spawn-mechanism.js";
|
|
10
|
+
export * from "./process-identify.js";
|
|
11
|
+
export * from "./subprocess-adapter.js";
|
|
12
|
+
export * from "./node-spawn.js";
|
|
13
|
+
export * as git from "./git.js";
|
|
14
|
+
export * as openspec from "./openspec.js";
|
|
15
|
+
export * as npm from "./npm.js";
|
|
16
|
+
export * as paths from "./paths.js";
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical helper for spawning `node --import <loader> <entry>` argv.
|
|
3
|
+
*
|
|
4
|
+
* Node ≥ 20's ESM loader parses BOTH the `--import` loader position AND
|
|
5
|
+
* the entry-script position as URLs. Raw Windows paths like
|
|
6
|
+
* `B:\Dev\foo.ts` URL-parse to scheme `b:`, which is not in the ESM
|
|
7
|
+
* loader's allowlist (file, data, node) → the process crashes with
|
|
8
|
+
* `ERR_UNSUPPORTED_ESM_URL_SCHEME` before any filesystem access.
|
|
9
|
+
*
|
|
10
|
+
* Node's internal drive-letter heuristic catches the common cases
|
|
11
|
+
* (`C:\`, `D:\`) but has known gaps for `A:`, `B:`, and other letters.
|
|
12
|
+
* Rather than relying on the heuristic, we wrap the loader position
|
|
13
|
+
* with `file://` unconditionally.
|
|
14
|
+
*
|
|
15
|
+
* The entry-script position needs a more nuanced rule. Node's default
|
|
16
|
+
* resolver AND jiti's ESM hook both accept `file://` URL entries. But
|
|
17
|
+
* **tsx's ESM hook rejects `file://` URLs as entries** — tsx's resolver
|
|
18
|
+
* treats the entry as a user-typed specifier and attempts bare-import
|
|
19
|
+
* / relative-path resolution, producing `<cwd>/file:/...` errors.
|
|
20
|
+
* Since tsx is used as the jiti fallback on dev machines without pi
|
|
21
|
+
* installed (the most common Linux dev path), we must NOT URL-wrap
|
|
22
|
+
* the entry when the loader is tsx. Detection: the loader path
|
|
23
|
+
* contains `/tsx/` (every tsx install ships its hook under a `tsx/`
|
|
24
|
+
* directory; jiti's hook is under `jiti/`).
|
|
25
|
+
*
|
|
26
|
+
* This module is the canonical chokepoint. The repo-level lint test
|
|
27
|
+
* `no-raw-node-import.test.ts` refuses any other call site that
|
|
28
|
+
* passes a raw path to `--import` / `--loader`.
|
|
29
|
+
*
|
|
30
|
+
* See change: fix-windows-entry-script-url.
|
|
31
|
+
*/
|
|
32
|
+
import path from "node:path";
|
|
33
|
+
import { pathToFileURL } from "node:url";
|
|
34
|
+
import type { SpawnOptions, ChildProcess } from "node:child_process"; // ban:child_process-ok — types only
|
|
35
|
+
import { spawn as execSpawn } from "./exec.js";
|
|
36
|
+
|
|
37
|
+
export interface SpawnNodeScriptOptions {
|
|
38
|
+
/** Path to node.exe / node (raw OS path — binary, not ESM-loaded). */
|
|
39
|
+
nodeBin?: string;
|
|
40
|
+
|
|
41
|
+
/** Path to the script Node will run. Raw path OR file:// URL. */
|
|
42
|
+
entry: string;
|
|
43
|
+
|
|
44
|
+
/** Optional ESM loader for --import. Raw path OR file:// URL. */
|
|
45
|
+
loader?: string;
|
|
46
|
+
|
|
47
|
+
/** Arguments passed to the script (after entry). */
|
|
48
|
+
args?: string[];
|
|
49
|
+
|
|
50
|
+
/** Standard spawn options (cwd, env, stdio, detached, etc.). */
|
|
51
|
+
spawnOptions?: SpawnOptions;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Detect whether a loader (file:// URL or raw path) is tsx.
|
|
56
|
+
*
|
|
57
|
+
* tsx's ESM hook rejects `file://` URLs at the entry-script position,
|
|
58
|
+
* so the caller must pass a raw OS path for the entry when this
|
|
59
|
+
* returns true. jiti and Node's default resolver both accept URL
|
|
60
|
+
* entries.
|
|
61
|
+
*
|
|
62
|
+
* Heuristic: every tsx install places its hook under a `tsx/` package
|
|
63
|
+
* directory (e.g. `.../node_modules/tsx/dist/esm/index.mjs`). The
|
|
64
|
+
* check is tolerant of `file://` URLs, raw POSIX paths, and raw
|
|
65
|
+
* Windows paths with either slash direction.
|
|
66
|
+
*/
|
|
67
|
+
export function isTsxLoader(loader: string | null | undefined): boolean {
|
|
68
|
+
if (!loader) return false;
|
|
69
|
+
// Normalize backslashes so the `/tsx/` probe works on Windows paths.
|
|
70
|
+
const normalized = loader.replace(/\\/g, "/");
|
|
71
|
+
return /\/tsx\//i.test(normalized);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Convert a path-or-url string to a file:// URL.
|
|
76
|
+
*
|
|
77
|
+
* Pure and idempotent. Safe to call on strings that are already
|
|
78
|
+
* file:// URLs — returns them unchanged.
|
|
79
|
+
*
|
|
80
|
+
* Handles Windows-style input (drive letter + backslash) regardless of
|
|
81
|
+
* host OS, so unit tests on Linux/macOS can exercise the Windows path
|
|
82
|
+
* contract. Mirrors the pattern in
|
|
83
|
+
* `packages/shared/src/resolve-jiti.ts::buildJitiRegisterUrl`.
|
|
84
|
+
*/
|
|
85
|
+
export function toFileUrl(pathOrUrl: string): string {
|
|
86
|
+
if (pathOrUrl.startsWith("file:")) return pathOrUrl;
|
|
87
|
+
|
|
88
|
+
const isWindowsStyle = /^[A-Za-z]:[\\/]/.test(pathOrUrl);
|
|
89
|
+
if (isWindowsStyle) {
|
|
90
|
+
// pathToFileURL on POSIX hosts URL-encodes backslashes rather than
|
|
91
|
+
// treating them as separators. Build the URL manually so tests on
|
|
92
|
+
// Linux produce the same result a Windows host would.
|
|
93
|
+
return `file:///${pathOrUrl.replace(/\\/g, "/")}`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Use path.resolve to ensure absolute path on the host OS, then
|
|
97
|
+
// let Node's pathToFileURL handle any host-specific quirks.
|
|
98
|
+
const absolute = path.isAbsolute(pathOrUrl) ? pathOrUrl : path.resolve(pathOrUrl);
|
|
99
|
+
return pathToFileURL(absolute).href;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Decide whether the entry-script position needs `file://` URL wrapping.
|
|
104
|
+
*
|
|
105
|
+
* Rule:
|
|
106
|
+
* - tsx loader: always raw path (tsx rejects file:// entries on every OS)
|
|
107
|
+
* - non-tsx (jiti / Node default) on POSIX: raw path
|
|
108
|
+
* (POSIX has no drive-letter / URL-scheme collision; jiti's resolver
|
|
109
|
+
* actively MISBEHAVES when handed `file://` URL entries — it
|
|
110
|
+
* normalises away the triple-slash and then treats `file:/...` as
|
|
111
|
+
* a relative specifier, producing `<cwd>/file:/...` ENOENT errors.)
|
|
112
|
+
* - non-tsx on Windows: file:// URL
|
|
113
|
+
* (Node parses drive letters like `B:` / `A:` as URL schemes in argv
|
|
114
|
+
* before loaders run, throwing ERR_UNSUPPORTED_ESM_URL_SCHEME.
|
|
115
|
+
* Wrapping with `file://` sidesteps the parse.)
|
|
116
|
+
*
|
|
117
|
+
* Keeps a `platform` parameter for testability so unit tests on a POSIX
|
|
118
|
+
* host can exercise the Windows branch without mutating `process.platform`.
|
|
119
|
+
*/
|
|
120
|
+
export function shouldUrlWrapEntry(
|
|
121
|
+
loader: string | null | undefined,
|
|
122
|
+
platform: NodeJS.Platform = process.platform,
|
|
123
|
+
): boolean {
|
|
124
|
+
if (isTsxLoader(loader)) return false;
|
|
125
|
+
return platform === "win32";
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Spawn `node` with an optional `--import` loader and a script entry.
|
|
130
|
+
*
|
|
131
|
+
* The loader position is always URL-wrapped (Node's ESM loader
|
|
132
|
+
* requires `file://` on Windows drive letters outside the heuristic).
|
|
133
|
+
*
|
|
134
|
+
* The entry position follows `shouldUrlWrapEntry(loader, platform)` —
|
|
135
|
+
* URL on Windows + non-tsx, raw everywhere else.
|
|
136
|
+
*
|
|
137
|
+
* Delegates actual spawning to `platform/exec.ts::spawn` so the
|
|
138
|
+
* `windowsHide: true` default and other safe-spawn invariants are
|
|
139
|
+
* preserved. Does not import `node:child_process` directly (the type
|
|
140
|
+
* imports above are annotated with the opt-out marker).
|
|
141
|
+
*/
|
|
142
|
+
export function spawnNodeScript(opts: SpawnNodeScriptOptions): ChildProcess {
|
|
143
|
+
const nodeBin = opts.nodeBin ?? process.execPath;
|
|
144
|
+
const wrapEntry = shouldUrlWrapEntry(opts.loader);
|
|
145
|
+
|
|
146
|
+
const argv: string[] = [];
|
|
147
|
+
if (opts.loader) {
|
|
148
|
+
argv.push("--import", toFileUrl(opts.loader));
|
|
149
|
+
}
|
|
150
|
+
argv.push(wrapEntry ? toFileUrl(opts.entry) : opts.entry);
|
|
151
|
+
if (opts.args) argv.push(...opts.args);
|
|
152
|
+
|
|
153
|
+
return execSpawn(nodeBin, argv, opts.spawnOptions ?? {});
|
|
154
|
+
}
|