From e85f524698e746c3bfb0bb9dac882edfc4cb9f39 Mon Sep 17 00:00:00 2001 From: Ahmed Mahmud Date: Fri, 13 Nov 2026 19:00:54 +0100 Subject: [PATCH 01/11] fix(diagnostics): enforce SemVer version suffix grammar --- src/diagnostics/stellar.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/diagnostics/stellar.ts b/src/diagnostics/stellar.ts index ea406ba..60cc081 100644 --- a/src/diagnostics/stellar.ts +++ b/src/diagnostics/stellar.ts @@ -15,8 +15,13 @@ const STELLAR_CLI_INSTALL_URL = function parseStellarVersion(output: string): string | null { const value = output.trim(); + // Accept an optional `v` prefix for compatibility, but require the version + // itself to follow SemVer 2.0.0 identifier rules. In particular, suffix + // identifiers must be non-empty and numeric prerelease identifiers cannot + // contain leading zeroes. This prevents malformed CLI output from passing + // merely because its numeric core is above the supported baseline. const match = - /^v?(\d+\.\d+\.\d+)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.exec(value); + /^v?(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-(?:0|[1-9]\\d*|\\d*[A-Za-z-][0-9A-Za-z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[A-Za-z-][0-9A-Za-z-]*))*)?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?$/.exec(value); return match?.[1] ?? null; } From a5ce7fe368f8b2c7586d214678194155ad21b3c1 Mon Sep 17 00:00:00 2001 From: Ahmed Mahmud Date: Fri, 13 Nov 2026 19:01:02 +0100 Subject: [PATCH 02/11] fix(diagnostics): correct SemVer matcher escapes --- src/diagnostics/stellar.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diagnostics/stellar.ts b/src/diagnostics/stellar.ts index 60cc081..57bc55d 100644 --- a/src/diagnostics/stellar.ts +++ b/src/diagnostics/stellar.ts @@ -21,7 +21,7 @@ function parseStellarVersion(output: string): string | null { // contain leading zeroes. This prevents malformed CLI output from passing // merely because its numeric core is above the supported baseline. const match = - /^v?(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-(?:0|[1-9]\\d*|\\d*[A-Za-z-][0-9A-Za-z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[A-Za-z-][0-9A-Za-z-]*))*)?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?$/.exec(value); + /^v?(0|[1-9]\d*)\\.(0|[1-9]\d*)\\.(0|[1-9]\d*)(?:-(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*)(?:\\.(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*))*)?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?$/.exec(value); return match?.[1] ?? null; } From f72b41226615f2da9ea293453f101c2dc4db828f Mon Sep 17 00:00:00 2001 From: Ahmed Mahmud Date: Fri, 13 Nov 2026 19:01:19 +0100 Subject: [PATCH 03/11] fix(diagnostics): finalize SemVer matcher --- src/diagnostics/stellar.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diagnostics/stellar.ts b/src/diagnostics/stellar.ts index 57bc55d..016f934 100644 --- a/src/diagnostics/stellar.ts +++ b/src/diagnostics/stellar.ts @@ -21,7 +21,7 @@ function parseStellarVersion(output: string): string | null { // contain leading zeroes. This prevents malformed CLI output from passing // merely because its numeric core is above the supported baseline. const match = - /^v?(0|[1-9]\d*)\\.(0|[1-9]\d*)\\.(0|[1-9]\d*)(?:-(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*)(?:\\.(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*))*)?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?$/.exec(value); + /^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*))*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/.exec(value); return match?.[1] ?? null; } From fabe03e01c07f1f0aba4b9a08131798a897a1770 Mon Sep 17 00:00:00 2001 From: Ahmed Mahmud Date: Fri, 13 Nov 2026 19:01:25 +0100 Subject: [PATCH 04/11] fix(diagnostics): preserve parsed semantic version core --- src/diagnostics/stellar.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diagnostics/stellar.ts b/src/diagnostics/stellar.ts index 016f934..698e996 100644 --- a/src/diagnostics/stellar.ts +++ b/src/diagnostics/stellar.ts @@ -22,7 +22,7 @@ function parseStellarVersion(output: string): string | null { // merely because its numeric core is above the supported baseline. const match = /^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*))*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/.exec(value); - return match?.[1] ?? null; + return match ? `${match[1]}.${match[2]}.${match[3]}` : null; } export function createStellarDiagnostic( From 17f8ba060e24f4aa1dbe5aad75f5934e5403f8ca Mon Sep 17 00:00:00 2001 From: Ahmed Mahmud Date: Fri, 13 Nov 2026 19:01:34 +0100 Subject: [PATCH 05/11] test(diagnostics): cover strict SemVer identifier grammar --- tests/stellar-diagnostic-semver.test.ts | 53 +++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 tests/stellar-diagnostic-semver.test.ts diff --git a/tests/stellar-diagnostic-semver.test.ts b/tests/stellar-diagnostic-semver.test.ts new file mode 100644 index 0000000..6a9f4da --- /dev/null +++ b/tests/stellar-diagnostic-semver.test.ts @@ -0,0 +1,53 @@ +import { describe, expect, it } from 'vitest'; +import type { RunCommand } from '../src/diagnostics/process.js'; +import { createStellarDiagnostic } from '../src/diagnostics/stellar.js'; + +function runVersion(output: string) { + const execute: RunCommand = () => ({ + status: 0, + stdout: output, + stderr: '', + }); + + return createStellarDiagnostic(execute).run(); +} + +describe('Stellar CLI semantic-version grammar', () => { + it.each([ + '27.1.0', + 'v27.1.0', + '27.1.0-rc.1', + '27.1.0+build.42', + '27.1.0-rc.1+sha.abcdef', + '28.0.0-preview.3', + '27.1.0-0', + '27.1.0+001', + ])('accepts valid SemVer output %s', (output) => { + const diagnostic = runVersion(output); + + expect(diagnostic.status).toBe('pass'); + expect(diagnostic.message).toBe('Supported version 27.1.0 detected.'); + }); + + it.each([ + '027.1.0', + '27.01.0', + '27.1.00', + '27.1.0-', + '27.1.0+', + '27.1.0-..', + '27.1.0-alpha..beta', + '27.1.0+build..7', + '27.1.0-01', + '27.1.0-alpha.01', + '27.1.0-alpha+', + '27.1.0+build+extra', + ])('rejects malformed SemVer output %s', (output) => { + const diagnostic = runVersion(output); + + expect(diagnostic.status).toBe('fail'); + expect(diagnostic.message).toBe( + 'Stellar CLI returned an unrecognized version.', + ); + }); +}); From ad8cc5252d59b0a163306de26b5eb56064fb074a Mon Sep 17 00:00:00 2001 From: Ahmed Mahmud Date: Fri, 13 Nov 2026 19:01:41 +0100 Subject: [PATCH 06/11] test(diagnostics): assert parsed version per fixture --- tests/stellar-diagnostic-semver.test.ts | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/tests/stellar-diagnostic-semver.test.ts b/tests/stellar-diagnostic-semver.test.ts index 6a9f4da..618c69d 100644 --- a/tests/stellar-diagnostic-semver.test.ts +++ b/tests/stellar-diagnostic-semver.test.ts @@ -14,19 +14,21 @@ function runVersion(output: string) { describe('Stellar CLI semantic-version grammar', () => { it.each([ - '27.1.0', - 'v27.1.0', - '27.1.0-rc.1', - '27.1.0+build.42', - '27.1.0-rc.1+sha.abcdef', - '28.0.0-preview.3', - '27.1.0-0', - '27.1.0+001', - ])('accepts valid SemVer output %s', (output) => { + ['27.1.0', '27.1.0'], + ['v27.1.0', '27.1.0'], + ['27.1.0-rc.1', '27.1.0'], + ['27.1.0+build.42', '27.1.0'], + ['27.1.0-rc.1+sha.abcdef', '27.1.0'], + ['28.0.0-preview.3', '28.0.0'], + ['27.1.0-0', '27.1.0'], + ['27.1.0+001', '27.1.0'], + ])('accepts valid SemVer output %s', (output, expectedVersion) => { const diagnostic = runVersion(output); expect(diagnostic.status).toBe('pass'); - expect(diagnostic.message).toBe('Supported version 27.1.0 detected.'); + expect(diagnostic.message).toBe( + `Supported version ${expectedVersion} detected.`, + ); }); it.each([ From f48335332fb40b15c97025829680a7b34baa4a2c Mon Sep 17 00:00:00 2001 From: Ahmed Mahmud Date: Fri, 13 Nov 2026 19:02:27 +0100 Subject: [PATCH 07/11] test(diagnostics): cover Stellar CLI failure matrix --- .../stellar-diagnostic-failure-matrix.test.ts | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 tests/stellar-diagnostic-failure-matrix.test.ts diff --git a/tests/stellar-diagnostic-failure-matrix.test.ts b/tests/stellar-diagnostic-failure-matrix.test.ts new file mode 100644 index 0000000..3d72243 --- /dev/null +++ b/tests/stellar-diagnostic-failure-matrix.test.ts @@ -0,0 +1,94 @@ +import { describe, expect, it } from 'vitest'; +import type { RunCommand } from '../src/diagnostics/process.js'; +import { createStellarDiagnostic } from '../src/diagnostics/stellar.js'; + +interface CommandResultFixture { + readonly status: number | null; + readonly stdout?: string; + readonly stderr?: string; + readonly error?: Error; +} + +function runDiagnostic(fixture: CommandResultFixture) { + const execute: RunCommand = () => ({ + status: fixture.status, + stdout: fixture.stdout ?? '', + stderr: fixture.stderr ?? '', + ...(fixture.error === undefined ? {} : { error: fixture.error }), + }); + + return createStellarDiagnostic(execute).run(); +} + +describe('Stellar CLI diagnostic failure matrix', () => { + it.each([ + ['27.0.9', '27.0.9'], + ['26.0.0', '26.0.0'], + ['20.0.0', '20.0.0'], + ['0.1.0', '0.1.0'], + ['v27.0.1', '27.0.1'], + ['27.0.0-rc.1', '27.0.0'], + ])('rejects supported-floor predecessor %s', (output, extractedVersion) => { + const diagnostic = runDiagnostic({ status: 0, stdout: output }); + + expect(diagnostic.status).toBe('fail'); + expect(diagnostic.message).toBe( + `Unsupported Stellar CLI version ${extractedVersion}.`, + ); + expect(diagnostic.remediation).toContain('>=27.1.0'); + }); + + it.each([ + '{"version":"27.1.0"}', + 'version: 27.1.0', + '502 Bad Gateway', + ])('rejects structured or proxy-error output %s', (output) => { + const diagnostic = runDiagnostic({ status: 0, stdout: output }); + + expect(diagnostic.status).toBe('fail'); + expect(diagnostic.message).toBe( + 'Stellar CLI returned an unrecognized version.', + ); + }); + + it.each([ + { status: 2, stderr: 'unrecognized option' }, + { status: 126, stderr: 'permission denied' }, + { status: 137, stderr: 'process killed' }, + { + status: 1, + stdout: '27.1.0', + stderr: 'panic: internal failure', + }, + ])('fails closed for non-zero execution result $status', (fixture) => { + const diagnostic = runDiagnostic(fixture); + + expect(diagnostic.status).toBe('fail'); + expect(diagnostic.message).toBe( + 'Stellar CLI is unavailable or could not be executed.', + ); + expect(diagnostic.remediation).toContain( + 'https://developers.stellar.org/docs/tools/cli/install-cli', + ); + }); + + it.each([ + new Error('spawn stellar ENOENT SECRET_PASSPHRASE=do-not-leak'), + new Error('spawn stellar EACCES SENSITIVE_KEY=do-not-leak'), + ])('does not expose executable errors or stderr details', (error) => { + const diagnostic = runDiagnostic({ + status: null, + stderr: 'SENSITIVE_KEY=do-not-leak', + error, + }); + const serialized = JSON.stringify(diagnostic); + + expect(diagnostic.status).toBe('fail'); + expect(diagnostic.message).toBe( + 'Stellar CLI is unavailable or could not be executed.', + ); + expect(serialized).not.toContain('SECRET_PASSPHRASE'); + expect(serialized).not.toContain('SENSITIVE_KEY'); + expect(serialized).not.toContain('do-not-leak'); + }); +}); From 54e81dd3497938bf01de65936336a66ea69afb83 Mon Sep 17 00:00:00 2001 From: Ahmed Mahmud Date: Fri, 13 Nov 2026 20:43:39 +0100 Subject: [PATCH 08/11] fix(diagnostics): satisfy formatting for SemVer parser --- src/diagnostics/stellar.ts | 68 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 66 insertions(+), 2 deletions(-) diff --git a/src/diagnostics/stellar.ts b/src/diagnostics/stellar.ts index 698e996..9e1bef8 100644 --- a/src/diagnostics/stellar.ts +++ b/src/diagnostics/stellar.ts @@ -20,8 +20,72 @@ function parseStellarVersion(output: string): string | null { // identifiers must be non-empty and numeric prerelease identifiers cannot // contain leading zeroes. This prevents malformed CLI output from passing // merely because its numeric core is above the supported baseline. - const match = - /^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*))*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/.exec(value); + const versionPattern = [ + '^v?(0|[1-9]\\d*)', + '\\.(0|[1-9]\\d*)', + '\\.(0|[1-9]\\d*)', + '(?:-(?:0|[1-9]\\d*|\\d*[A-Za-z-][0-9A-Za-z-]*)', + '(?:\\.(?:0|[1-9]\\d*|\\d*[A-Za-z-][0-9A-Za-z-]*))*)?', + '(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)? + return match ? `${match[1]}.${match[2]}.${match[3]}` : null; +} + +export function createStellarDiagnostic( + execute: RunCommand = runCommand, +): DiagnosticCheck { + return { + id: 'stellar', + label: 'Stellar CLI', + run(): DiagnosticResult { + // `stellar version --only-version` is the official machine-friendly + // version command. It does not inspect identities, keys, or config. + const result = execute('stellar', ['version', '--only-version']); + + if (classifyCommandResult(result) !== 'ok') { + return { + id: 'stellar', + label: 'Stellar CLI', + status: 'fail', + message: 'Stellar CLI is unavailable or could not be executed.', + remediation: `Install or update the official Stellar CLI (${STELLAR_CLI_INSTALL_URL}), then ensure \`stellar\` is on PATH and \`stellar version --only-version\` reports a version.`, + }; + } + + const version = parseStellarVersion(result.stdout); + const parsed = version ? parseNumericVersion(version) : null; + + if (!version || !parsed) { + return { + id: 'stellar', + label: 'Stellar CLI', + status: 'fail', + message: 'Stellar CLI returned an unrecognized version.', + remediation: `Run \`stellar version --only-version\`; if it does not report a semantic version, reinstall or update the official Stellar CLI (${STELLAR_CLI_INSTALL_URL}).`, + }; + } + + if (!isAtLeast(parsed, MINIMUM_STELLAR_CLI)) { + return { + id: 'stellar', + label: 'Stellar CLI', + status: 'fail', + message: `Unsupported Stellar CLI version ${version}.`, + remediation: `Update the official Stellar CLI to >=27.1.0 (${STELLAR_CLI_INSTALL_URL}), then verify \`stellar version --only-version\` reports a supported version.`, + }; + } + + return { + id: 'stellar', + label: 'Stellar CLI', + status: 'pass', + message: `Supported version ${version} detected.`, + }; + }, + }; +} +, + ].join(''); + const match = new RegExp(versionPattern).exec(value); return match ? `${match[1]}.${match[2]}.${match[3]}` : null; } From cae84972fba18d2718dc8b5d9a243934aaad6d40 Mon Sep 17 00:00:00 2001 From: Ahmed Mahmud Date: Fri, 13 Nov 2026 20:44:50 +0100 Subject: [PATCH 09/11] fix(diagnostics): restore strict SemVer parser formatting --- src/diagnostics/stellar.ts | 75 ++------------------------------------ 1 file changed, 3 insertions(+), 72 deletions(-) diff --git a/src/diagnostics/stellar.ts b/src/diagnostics/stellar.ts index 9e1bef8..4ea5489 100644 --- a/src/diagnostics/stellar.ts +++ b/src/diagnostics/stellar.ts @@ -15,78 +15,9 @@ const STELLAR_CLI_INSTALL_URL = function parseStellarVersion(output: string): string | null { const value = output.trim(); - // Accept an optional `v` prefix for compatibility, but require the version - // itself to follow SemVer 2.0.0 identifier rules. In particular, suffix - // identifiers must be non-empty and numeric prerelease identifiers cannot - // contain leading zeroes. This prevents malformed CLI output from passing - // merely because its numeric core is above the supported baseline. - const versionPattern = [ - '^v?(0|[1-9]\\d*)', - '\\.(0|[1-9]\\d*)', - '\\.(0|[1-9]\\d*)', - '(?:-(?:0|[1-9]\\d*|\\d*[A-Za-z-][0-9A-Za-z-]*)', - '(?:\\.(?:0|[1-9]\\d*|\\d*[A-Za-z-][0-9A-Za-z-]*))*)?', - '(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)? - return match ? `${match[1]}.${match[2]}.${match[3]}` : null; -} - -export function createStellarDiagnostic( - execute: RunCommand = runCommand, -): DiagnosticCheck { - return { - id: 'stellar', - label: 'Stellar CLI', - run(): DiagnosticResult { - // `stellar version --only-version` is the official machine-friendly - // version command. It does not inspect identities, keys, or config. - const result = execute('stellar', ['version', '--only-version']); - - if (classifyCommandResult(result) !== 'ok') { - return { - id: 'stellar', - label: 'Stellar CLI', - status: 'fail', - message: 'Stellar CLI is unavailable or could not be executed.', - remediation: `Install or update the official Stellar CLI (${STELLAR_CLI_INSTALL_URL}), then ensure \`stellar\` is on PATH and \`stellar version --only-version\` reports a version.`, - }; - } - - const version = parseStellarVersion(result.stdout); - const parsed = version ? parseNumericVersion(version) : null; - - if (!version || !parsed) { - return { - id: 'stellar', - label: 'Stellar CLI', - status: 'fail', - message: 'Stellar CLI returned an unrecognized version.', - remediation: `Run \`stellar version --only-version\`; if it does not report a semantic version, reinstall or update the official Stellar CLI (${STELLAR_CLI_INSTALL_URL}).`, - }; - } - - if (!isAtLeast(parsed, MINIMUM_STELLAR_CLI)) { - return { - id: 'stellar', - label: 'Stellar CLI', - status: 'fail', - message: `Unsupported Stellar CLI version ${version}.`, - remediation: `Update the official Stellar CLI to >=27.1.0 (${STELLAR_CLI_INSTALL_URL}), then verify \`stellar version --only-version\` reports a supported version.`, - }; - } - - return { - id: 'stellar', - label: 'Stellar CLI', - status: 'pass', - message: `Supported version ${version} detected.`, - }; - }, - }; -} -, - ].join(''); - const match = new RegExp(versionPattern).exec(value); - return match ? `${match[1]}.${match[2]}.${match[3]}` : null; + // prettier-ignore + const match = /^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*))*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/.exec(value); + return match ? match[1] + '.' + match[2] + '.' + match[3] : null; } export function createStellarDiagnostic( From c8d8a4d1f921ecab9e6fcf0a34780e658900c57d Mon Sep 17 00:00:00 2001 From: Ahmed Mahmud Date: Fri, 13 Nov 2026 20:46:41 +0100 Subject: [PATCH 10/11] test(diagnostics): reject unsafe numeric version components --- tests/stellar-diagnostic-semver.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/stellar-diagnostic-semver.test.ts b/tests/stellar-diagnostic-semver.test.ts index 618c69d..05f2084 100644 --- a/tests/stellar-diagnostic-semver.test.ts +++ b/tests/stellar-diagnostic-semver.test.ts @@ -33,6 +33,7 @@ describe('Stellar CLI semantic-version grammar', () => { it.each([ '027.1.0', + '9007199254740992.0.0', '27.01.0', '27.1.00', '27.1.0-', From 22e09bb7db2f0879111e99b520d689f766e8e49d Mon Sep 17 00:00:00 2001 From: Ahmed Mahmud Date: Fri, 13 Nov 2026 20:49:45 +0100 Subject: [PATCH 11/11] test(diagnostics): complete remaining malformed version fixtures --- tests/stellar-diagnostic-semver.test.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/stellar-diagnostic-semver.test.ts b/tests/stellar-diagnostic-semver.test.ts index 05f2084..31651c4 100644 --- a/tests/stellar-diagnostic-semver.test.ts +++ b/tests/stellar-diagnostic-semver.test.ts @@ -20,6 +20,8 @@ describe('Stellar CLI semantic-version grammar', () => { ['27.1.0+build.42', '27.1.0'], ['27.1.0-rc.1+sha.abcdef', '27.1.0'], ['28.0.0-preview.3', '28.0.0'], + ['27.10.25', '27.10.25'], + ['100.0.0', '100.0.0'], ['27.1.0-0', '27.1.0'], ['27.1.0+001', '27.1.0'], ])('accepts valid SemVer output %s', (output, expectedVersion) => { @@ -45,6 +47,11 @@ describe('Stellar CLI semantic-version grammar', () => { '27.1.0-alpha.01', '27.1.0-alpha+', '27.1.0+build+extra', + '27.1.0a', + '27.1.0.0', + '27.1.0 (built 2026-01-01)', + '27.1.0 extra-tokens', + 'vA.B.C', ])('rejects malformed SemVer output %s', (output) => { const diagnostic = runVersion(output);