From 321316d3ec2ebc2bc639e4f205f925d5e29a1ca3 Mon Sep 17 00:00:00 2001 From: Ahmed Mahmud Date: Fri, 11 Sep 2026 11:03:48 +0100 Subject: [PATCH 01/18] ci: add cross-platform validation matrix --- .github/workflows/ci.yml | 43 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e24c0b9..47fcc75 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,3 +48,46 @@ jobs: - name: Build run: npm run build + + platform: + name: Platform / ${{ matrix.name }} + strategy: + fail-fast: false + matrix: + include: + - name: Linux / Node 22 + os: ubuntu-latest + node: '22.13.0' + - name: Linux / Node 24 + os: ubuntu-latest + node: '24' + - name: macOS / Node 22 + os: macos-latest + node: '22.13.0' + - name: Windows / Node 22 + os: windows-latest + node: '22.13.0' + + runs-on: ${{ matrix.os }} + timeout-minutes: 15 + + steps: + - name: Checkout repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Setup Node.js + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: ${{ matrix.node }} + package-manager-cache: false + + - name: Install from committed lockfile + run: npm ci --ignore-scripts --no-audit --no-fund + + - name: Run platform test suite + run: npm test + + - name: Build platform artifact + run: npm run build From 06056fc685746577131eb1d6b091ed0d93745b6a Mon Sep 17 00:00:00 2001 From: Ahmed Mahmud Date: Fri, 11 Sep 2026 11:03:52 +0100 Subject: [PATCH 02/18] docs: define supported platform matrix --- docs/reference/platform-support.md | 57 ++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 docs/reference/platform-support.md diff --git a/docs/reference/platform-support.md b/docs/reference/platform-support.md new file mode 100644 index 0000000..6130b50 --- /dev/null +++ b/docs/reference/platform-support.md @@ -0,0 +1,57 @@ +# Platform Support + +StellarForge CLI treats supported-platform failures as release blockers unless a limitation is explicitly documented and reviewed. + +## Supported MVP matrix + +| Operating system | Runtime | Validation level | +| --- | --- | --- | +| Linux (GitHub-hosted Ubuntu) | Node.js 22.13+ LTS | Full quality gate, tests, build | +| Linux (GitHub-hosted Ubuntu) | Node.js 24 LTS | Tests and build | +| macOS (GitHub-hosted macOS) | Node.js 22.13+ LTS | Tests and build | +| Windows (GitHub-hosted Windows) | Node.js 22.13+ LTS | Tests and build | + +The package engine range remains `Node.js >=22.13.0 <25`. Node.js 22 and Node.js 24 are the supported LTS majors for the current MVP. Intermediate non-LTS Node releases are not release-validation targets. + +## What the platform matrix exercises + +The platform jobs run the repository test suite and production build. This includes coverage for: + +- compiled CLI startup, help, version, and parser exit behavior; +- project path validation and generation safety; +- generated Basic App, Full Stack, Smart Contract, and API Service smoke checks; +- platform-specific executable selection such as `npm.cmd`, `cargo.exe`, and `stellar.exe` on Windows; +- subprocess supervision and failure propagation; +- project test/development workflow resolution; +- project configuration loading and security validation. + +The Linux quality job additionally runs type checking, linting, and Prettier validation. + +## Platform differences + +StellarForge code should expose platform differences deliberately instead of hiding them behind shell behavior. + +### Windows + +- npm subprocesses use `npm.cmd`; +- Cargo subprocesses use `cargo.exe`; +- Stellar deployment subprocesses use `stellar.exe`; +- path tests must use Node path APIs rather than assuming POSIX separators; +- commands must not rely on Bash being the default shell. + +### macOS and Linux + +- npm, Cargo, and Stellar executables use their ordinary executable names; +- generated projects and CLI subprocesses still use argument arrays rather than shell command concatenation. + +## External tools + +A green platform job validates StellarForge's code and tests on that OS; it does not guarantee every optional external tool is preinstalled on every runner. + +`stellarforge doctor` is responsible for reporting the actual local availability of Git, Rust, Cargo, Stellar CLI, Docker, and supported runtimes. Tests accept either a healthy or diagnostic-failure result where runner tool availability is intentionally variable. + +## Release policy + +A failing Linux, macOS, or Windows matrix job blocks merge/release for changes that affect the supported CLI surface. A failure may be waived only through an explicit project decision that documents the affected platform, reason, user impact, and follow-up plan. + +Platform-specific failures must not be hidden with `continue-on-error` in the supported matrix. From 2f4fb59af70d29f43c0d3d3407b396f7e994a1f4 Mon Sep 17 00:00:00 2001 From: Ahmed Mahmud Date: Fri, 11 Sep 2026 11:03:58 +0100 Subject: [PATCH 03/18] docs: link installation to platform support --- docs/guides/installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/installation.md b/docs/guides/installation.md index 49051cb..bd93bb1 100644 --- a/docs/guides/installation.md +++ b/docs/guides/installation.md @@ -10,7 +10,7 @@ The CLI foundation supports maintained Node.js LTS lines covered by the package - npm `>=10.9.0` - repository toolchain metadata records npm `10.9.2` -Node.js 22 and Node.js 24 are the intended supported LTS majors for the current foundation. The minimum Node.js 22 patch level is aligned with the CLI's current linting/tooling dependency requirements. Do not use an end-of-life Node release for development or CI. +Node.js 22 and Node.js 24 are the supported LTS majors for the current MVP. The minimum Node.js 22 patch level is aligned with the CLI's current linting/tooling dependency requirements. Linux, macOS, and Windows are exercised by the repository platform matrix. See [Platform Support](../reference/platform-support.md). Do not use an end-of-life Node release for development or CI. ## Contributor Setup From 995d24d19ce52c4d4cf57f2329be9b9f60041352 Mon Sep 17 00:00:00 2001 From: Ahmed Mahmud Date: Fri, 11 Sep 2026 11:06:46 +0100 Subject: [PATCH 04/18] fix: add shell-free cross-platform npm invocation --- src/process/npm.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/process/npm.ts diff --git a/src/process/npm.ts b/src/process/npm.ts new file mode 100644 index 0000000..65eeeeb --- /dev/null +++ b/src/process/npm.ts @@ -0,0 +1,20 @@ +export interface NpmInvocation { + readonly command: string; + readonly args: readonly string[]; +} + +export function createNpmInvocation( + args: readonly string[], +): NpmInvocation { + if (process.platform === 'win32') { + return { + command: 'cmd.exe', + args: ['/d', '/s', '/c', 'npm', ...args], + }; + } + + return { + command: 'npm', + args: [...args], + }; +} From 389a21dcc939ae16b7310be5629031f729b6bd61 Mon Sep 17 00:00:00 2001 From: Ahmed Mahmud Date: Fri, 11 Sep 2026 11:07:04 +0100 Subject: [PATCH 05/18] fix: use cross-platform npm invocation for dev --- src/dev/project.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/dev/project.ts b/src/dev/project.ts index 4534d1b..14ceb69 100644 --- a/src/dev/project.ts +++ b/src/dev/project.ts @@ -1,6 +1,7 @@ import { existsSync, readFileSync } from 'node:fs'; import { resolve } from 'node:path'; import { ValidationCliError } from '../errors/errors.js'; +import { createNpmInvocation } from '../process/npm.js'; export interface DevProcessSpec { readonly label: string; @@ -64,11 +65,13 @@ export function resolveDevProcessPlan(cwd: string): readonly DevProcessSpec[] { ? projectPackage.name : 'app'; + const npm = createNpmInvocation(['run', script]); + return [ { label, - command: process.platform === 'win32' ? 'npm.cmd' : 'npm', - args: ['run', script], + command: npm.command, + args: npm.args, cwd: projectRoot, }, ]; From 30fdedaf823a14400d8216baf05f54a7f84c14ae Mon Sep 17 00:00:00 2001 From: Ahmed Mahmud Date: Fri, 11 Sep 2026 11:07:09 +0100 Subject: [PATCH 06/18] fix: use cross-platform npm invocation for test --- src/testing/project.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/testing/project.ts b/src/testing/project.ts index 18e2cfc..49595e2 100644 --- a/src/testing/project.ts +++ b/src/testing/project.ts @@ -2,6 +2,7 @@ import { existsSync, readFileSync } from 'node:fs'; import { resolve } from 'node:path'; import type { DevProcessSpec } from '../dev/project.js'; import { ValidationCliError } from '../errors/errors.js'; +import { createNpmInvocation } from '../process/npm.js'; interface ProjectPackage { readonly name?: unknown; @@ -46,10 +47,12 @@ function resolveNodeTestPlan(projectRoot: string): DevProcessSpec | undefined { ? projectPackage.name : 'app'; + const npm = createNpmInvocation(['run', 'test']); + return { label: `${projectName}:test`, - command: process.platform === 'win32' ? 'npm.cmd' : 'npm', - args: ['run', 'test'], + command: npm.command, + args: npm.args, cwd: projectRoot, }; } From 491726e2cc94568821a6635c57df351a592a75e0 Mon Sep 17 00:00:00 2001 From: Ahmed Mahmud Date: Fri, 11 Sep 2026 11:07:20 +0100 Subject: [PATCH 07/18] fix: run npm diagnostic cross-platform --- src/diagnostics/runtime.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/diagnostics/runtime.ts b/src/diagnostics/runtime.ts index 8df6d7a..428ebf6 100644 --- a/src/diagnostics/runtime.ts +++ b/src/diagnostics/runtime.ts @@ -1,3 +1,4 @@ +import { createNpmInvocation } from '../process/npm.js'; import type { DiagnosticCheck, DiagnosticResult } from './types.js'; import { runCommand, type RunCommand } from './process.js'; import { @@ -67,7 +68,8 @@ export function createNpmDiagnostic( id: 'npm', label: 'npm', run(): DiagnosticResult { - const result = execute('npm', ['--version']); + const npm = createNpmInvocation(['--version']); + const result = execute(npm.command, npm.args); if (result.error || result.status !== 0) { return failure( From 4e7b035bddfbcf6951a8dab452f2ebfb86e0d4b6 Mon Sep 17 00:00:00 2001 From: Ahmed Mahmud Date: Fri, 11 Sep 2026 11:07:40 +0100 Subject: [PATCH 08/18] test: use cross-platform npm launcher in template smoke checks --- tests/basic-app-template.test.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/tests/basic-app-template.test.ts b/tests/basic-app-template.test.ts index 943d068..fa6b99e 100644 --- a/tests/basic-app-template.test.ts +++ b/tests/basic-app-template.test.ts @@ -3,21 +3,20 @@ import { join } from 'node:path'; import { spawnSync } from 'node:child_process'; import { describe, expect, it } from 'vitest'; import { createNewCommand } from '../src/commands/new.js'; +import { createNpmInvocation } from '../src/process/npm.js'; import { createCapturedTerminalOutput, withTempDirectory, } from './helpers/index.js'; function runNpmScript(projectRoot: string, script: string) { - return spawnSync( - process.platform === 'win32' ? 'npm.cmd' : 'npm', - ['run', script], - { - cwd: projectRoot, - encoding: 'utf8', - shell: false, - }, - ); + const npm = createNpmInvocation(['run', script]); + + return spawnSync(npm.command, [...npm.args], { + cwd: projectRoot, + encoding: 'utf8', + shell: false, + }); } describe('Basic App template', () => { From 61da0ad2e4b2c4f7027fe393f0b8f64b09fa19ef Mon Sep 17 00:00:00 2001 From: Ahmed Mahmud Date: Fri, 11 Sep 2026 11:07:49 +0100 Subject: [PATCH 09/18] test: use cross-platform npm launcher in template smoke checks --- tests/api-service-template.test.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/tests/api-service-template.test.ts b/tests/api-service-template.test.ts index 097c621..9b6c780 100644 --- a/tests/api-service-template.test.ts +++ b/tests/api-service-template.test.ts @@ -3,21 +3,20 @@ import { existsSync, readFileSync } from 'node:fs'; import { join } from 'node:path'; import { describe, expect, it } from 'vitest'; import { createNewCommand } from '../src/commands/new.js'; +import { createNpmInvocation } from '../src/process/npm.js'; import { createCapturedTerminalOutput, withTempDirectory, } from './helpers/index.js'; function runNpmScript(projectRoot: string, script: string) { - return spawnSync( - process.platform === 'win32' ? 'npm.cmd' : 'npm', - ['run', script], - { - cwd: projectRoot, - encoding: 'utf8', - shell: false, - }, - ); + const npm = createNpmInvocation(['run', script]); + + return spawnSync(npm.command, [...npm.args], { + cwd: projectRoot, + encoding: 'utf8', + shell: false, + }); } describe('API Service template', () => { From e95a088b6afd66b300b6381c334b5ae5bd3d8009 Mon Sep 17 00:00:00 2001 From: Ahmed Mahmud Date: Fri, 11 Sep 2026 11:07:56 +0100 Subject: [PATCH 10/18] test: use cross-platform npm launcher in template smoke checks --- tests/full-stack-template.test.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/tests/full-stack-template.test.ts b/tests/full-stack-template.test.ts index d49c0ff..c90e7b9 100644 --- a/tests/full-stack-template.test.ts +++ b/tests/full-stack-template.test.ts @@ -3,21 +3,20 @@ import { join } from 'node:path'; import { spawnSync } from 'node:child_process'; import { describe, expect, it } from 'vitest'; import { createNewCommand } from '../src/commands/new.js'; +import { createNpmInvocation } from '../src/process/npm.js'; import { createCapturedTerminalOutput, withTempDirectory, } from './helpers/index.js'; function runNpmScript(projectRoot: string, script: string) { - return spawnSync( - process.platform === 'win32' ? 'npm.cmd' : 'npm', - ['run', script], - { - cwd: projectRoot, - encoding: 'utf8', - shell: false, - }, - ); + const npm = createNpmInvocation(['run', script]); + + return spawnSync(npm.command, [...npm.args], { + cwd: projectRoot, + encoding: 'utf8', + shell: false, + }); } describe('Full Stack template', () => { From 73b60c47ff4cfbaea62bab17bcea922bd7760b4b Mon Sep 17 00:00:00 2001 From: Ahmed Mahmud Date: Fri, 11 Sep 2026 11:08:04 +0100 Subject: [PATCH 11/18] test: assert platform-aware dev npm invocation --- tests/dev-project.test.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/dev-project.test.ts b/tests/dev-project.test.ts index a1736b0..5292082 100644 --- a/tests/dev-project.test.ts +++ b/tests/dev-project.test.ts @@ -2,6 +2,7 @@ import { mkdirSync, writeFileSync } from 'node:fs'; import { join } from 'node:path'; import { describe, expect, it } from 'vitest'; import { ValidationCliError } from '../src/errors/errors.js'; +import { createNpmInvocation } from '../src/process/npm.js'; import { resolveDevProcessPlan } from '../src/dev/project.js'; import { withTempDirectory } from './helpers/index.js'; @@ -19,10 +20,11 @@ describe('development project plan', () => { const plan = resolveDevProcessPlan(root); expect(plan).toHaveLength(1); + const npm = createNpmInvocation(['run', 'start']); expect(plan[0]).toEqual({ label: 'demo', - command: process.platform === 'win32' ? 'npm.cmd' : 'npm', - args: ['run', 'start'], + command: npm.command, + args: npm.args, cwd: root, }); })); From 29b0b0f4ca3ca87204a2fdcbfef4116a7228d8ec Mon Sep 17 00:00:00 2001 From: Ahmed Mahmud Date: Fri, 11 Sep 2026 11:08:12 +0100 Subject: [PATCH 12/18] test: assert platform-aware test npm invocation --- tests/test-project.test.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test-project.test.ts b/tests/test-project.test.ts index 186c282..7b18a36 100644 --- a/tests/test-project.test.ts +++ b/tests/test-project.test.ts @@ -2,6 +2,7 @@ import { writeFileSync } from 'node:fs'; import { join } from 'node:path'; import { describe, expect, it } from 'vitest'; import { ValidationCliError } from '../src/errors/errors.js'; +import { createNpmInvocation } from '../src/process/npm.js'; import { resolveTestProcessPlan } from '../src/testing/project.js'; import { withTempDirectory } from './helpers/index.js'; @@ -16,11 +17,12 @@ describe('project test workflow resolution', () => { }), ); + const npm = createNpmInvocation(['run', 'test']); expect(resolveTestProcessPlan(root)).toEqual([ { label: 'demo:test', - command: process.platform === 'win32' ? 'npm.cmd' : 'npm', - args: ['run', 'test'], + command: npm.command, + args: npm.args, cwd: root, }, ]); From ae163c720e0f12d127f4d87a29b123161c6f18e3 Mon Sep 17 00:00:00 2001 From: Ahmed Mahmud Date: Fri, 11 Sep 2026 11:08:19 +0100 Subject: [PATCH 13/18] test: assert platform-aware npm diagnostic --- tests/runtime-diagnostics.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/runtime-diagnostics.test.ts b/tests/runtime-diagnostics.test.ts index db00c93..c4789cc 100644 --- a/tests/runtime-diagnostics.test.ts +++ b/tests/runtime-diagnostics.test.ts @@ -4,6 +4,7 @@ import { createNpmDiagnostic, } from '../src/diagnostics/runtime.js'; import type { RunCommand } from '../src/diagnostics/process.js'; +import { createNpmInvocation } from '../src/process/npm.js'; function commandResult( status: number | null, @@ -59,7 +60,8 @@ describe('npm diagnostic', () => { const result = createNpmDiagnostic(execute).run(); - expect(calls).toEqual([{ executable: 'npm', args: ['--version'] }]); + const npm = createNpmInvocation(['--version']); + expect(calls).toEqual([{ executable: npm.command, args: npm.args }]); expect(result.status).toBe('pass'); expect(result.message).toContain('10.9.2'); }); From b1ddae7dd427333ba3e4fad06b1e1e2519a4f0f9 Mon Sep 17 00:00:00 2001 From: Ahmed Mahmud Date: Fri, 11 Sep 2026 11:08:39 +0100 Subject: [PATCH 14/18] test: allow cross-platform static tool startup time --- tests/static-quality.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/static-quality.test.ts b/tests/static-quality.test.ts index aa01750..f116ab1 100644 --- a/tests/static-quality.test.ts +++ b/tests/static-quality.test.ts @@ -54,7 +54,7 @@ describe('static quality enforcement', () => { expect(result.status).not.toBe(0); expect(`${result.stdout}${result.stderr}`).toContain('no-unused-vars'); - }); + }, 15_000); it('rejects a formatting violation with the repository Prettier configuration', async () => { await withTempDirectory((directory) => { @@ -73,7 +73,7 @@ describe('static quality enforcement', () => { 'Code style issues found', ); }); - }); + }, 15_000); it('rejects a type violation with strict repository compiler options', async () => { await withTempDirectory((directory) => { @@ -98,5 +98,5 @@ describe('static quality enforcement', () => { expect(result.status).not.toBe(0); expect(`${result.stdout}${result.stderr}`).toContain('TS2322'); }); - }); + }, 15_000); }); From 1b7bdfe3bbccd8062e5f2a856b9a299d5128034d Mon Sep 17 00:00:00 2001 From: Ahmed Mahmud Date: Fri, 11 Sep 2026 11:09:03 +0100 Subject: [PATCH 15/18] docs: document Windows npm execution boundary --- docs/reference/platform-support.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/reference/platform-support.md b/docs/reference/platform-support.md index 6130b50..be8950f 100644 --- a/docs/reference/platform-support.md +++ b/docs/reference/platform-support.md @@ -20,7 +20,7 @@ The platform jobs run the repository test suite and production build. This inclu - compiled CLI startup, help, version, and parser exit behavior; - project path validation and generation safety; - generated Basic App, Full Stack, Smart Contract, and API Service smoke checks; -- platform-specific executable selection such as `npm.cmd`, `cargo.exe`, and `stellar.exe` on Windows; +- platform-specific executable selection such as the Windows npm wrapper, `cargo.exe`, and `stellar.exe`; - subprocess supervision and failure propagation; - project test/development workflow resolution; - project configuration loading and security validation. @@ -33,7 +33,7 @@ StellarForge code should expose platform differences deliberately instead of hid ### Windows -- npm subprocesses use `npm.cmd`; +- npm subprocesses use a fixed `cmd.exe /d /s /c npm ...` wrapper because Windows `.cmd` shims are not directly executable under the CLI's `shell: false` policy; - Cargo subprocesses use `cargo.exe`; - Stellar deployment subprocesses use `stellar.exe`; - path tests must use Node path APIs rather than assuming POSIX separators; From f15b324821d27a914e9a088e72a9d375d4c5662e Mon Sep 17 00:00:00 2001 From: Ahmed Mahmud Date: Fri, 11 Sep 2026 11:10:22 +0100 Subject: [PATCH 16/18] style: format npm invocation helper --- src/process/npm.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/process/npm.ts b/src/process/npm.ts index 65eeeeb..d730e96 100644 --- a/src/process/npm.ts +++ b/src/process/npm.ts @@ -3,9 +3,7 @@ export interface NpmInvocation { readonly args: readonly string[]; } -export function createNpmInvocation( - args: readonly string[], -): NpmInvocation { +export function createNpmInvocation(args: readonly string[]): NpmInvocation { if (process.platform === 'win32') { return { command: 'cmd.exe', From 9c8a84250c79fade7ba45e74385ee724f86b5b28 Mon Sep 17 00:00:00 2001 From: Ahmed Mahmud Date: Fri, 11 Sep 2026 11:24:32 +0100 Subject: [PATCH 17/18] test: assert platform-safe npm dev invocation --- tests/dev-project.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/dev-project.test.ts b/tests/dev-project.test.ts index 5292082..371a203 100644 --- a/tests/dev-project.test.ts +++ b/tests/dev-project.test.ts @@ -44,7 +44,8 @@ describe('development project plan', () => { const [spec] = resolveDevProcessPlan(root); - expect(spec?.args).toEqual(['run', 'dev']); + const npm = createNpmInvocation(['run', 'dev']); + expect(spec).toMatchObject({ command: npm.command, args: npm.args }); expect(spec?.args).not.toContain( 'node dev.js --flag && echo unsafe-if-shell-expanded', ); From 1caee0b4ae657b5bb4eb5da0b2627f5255a410fa Mon Sep 17 00:00:00 2001 From: Ahmed Mahmud Date: Fri, 11 Sep 2026 11:24:39 +0100 Subject: [PATCH 18/18] fix: bound diagnostic subprocess duration --- src/diagnostics/process.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/diagnostics/process.ts b/src/diagnostics/process.ts index 93a5431..6f6494d 100644 --- a/src/diagnostics/process.ts +++ b/src/diagnostics/process.ts @@ -1,5 +1,7 @@ import { spawnSync } from 'node:child_process'; +export const DIAGNOSTIC_COMMAND_TIMEOUT_MS = 5_000; + export interface CommandResult { readonly status: number | null; readonly stdout: string; @@ -16,6 +18,7 @@ export const runCommand: RunCommand = (executable, args) => { const result = spawnSync(executable, [...args], { encoding: 'utf8', shell: false, + timeout: DIAGNOSTIC_COMMAND_TIMEOUT_MS, }); return {