Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/tidy-stars-tell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@diginodes/stellarforge-cli": patch
---

Clarify Stellar CLI diagnostic remediation for unavailable, malformed, and unsupported versions.
8 changes: 5 additions & 3 deletions src/diagnostics/stellar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { isAtLeast, parseNumericVersion } from './version.js';
// software-version matrix. Keep this compatibility floor explicit and review
// it when StellarForge changes its supported network/tooling baseline.
const MINIMUM_STELLAR_CLI = { major: 27, minor: 1, patch: 0 } as const;
const STELLAR_CLI_INSTALL_URL =
'https://developers.stellar.org/docs/tools/cli/install-cli';

function parseStellarVersion(output: string): string | null {
const value = output.trim();
Expand All @@ -35,7 +37,7 @@ export function createStellarDiagnostic(
status: 'fail',
message: 'Stellar CLI is unavailable or could not be executed.',
remediation:
'Install the current Stellar CLI and ensure `stellar` is available on PATH. See https://developers.stellar.org/docs/tools/cli/install-cli.',
`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.`,
};
}

Expand All @@ -49,7 +51,7 @@ export function createStellarDiagnostic(
status: 'fail',
message: 'Stellar CLI returned an unrecognized version.',
remediation:
'Verify `stellar version --only-version` works and reinstall Stellar CLI if necessary.',
`Run \`stellar version --only-version\`; if it does not report a semantic version, reinstall or update the official Stellar CLI (${STELLAR_CLI_INSTALL_URL}).`,
};
}

Expand All @@ -60,7 +62,7 @@ export function createStellarDiagnostic(
status: 'fail',
message: `Unsupported Stellar CLI version ${version}.`,
remediation:
'Upgrade to Stellar CLI >=27.1.0 before using StellarForge Stellar workflows.',
`Update the official Stellar CLI to >=27.1.0 (${STELLAR_CLI_INSTALL_URL}), then verify \`stellar version --only-version\` reports a supported version.`,
};
}

Expand Down
32 changes: 29 additions & 3 deletions tests/template-generation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,14 @@ describe('template generation', () => {
withTempDirectory((root) => {
const templateRoot = join(root, 'template');
mkdirSync(templateRoot);
writeFileSync(join(templateRoot, 'good.txt'), 'created first');
writeFileSync(join(templateRoot, 'bad.txt'), '{{unsupportedToken}}');
writeFileSync(join(templateRoot, 'a-good.txt'), 'created first');
writeFileSync(join(templateRoot, 'z-bad.txt'), '{{unsupportedToken}}');

expect(() =>
generateProjectFromTemplate(context(root), { templateRoot }),
).toThrow(ValidationCliError);
).toThrow(
"Template contains unsupported substitution token '{{unsupportedToken}}'.",
);

expect(existsSync(join(root, 'demo-app'))).toBe(false);
expect(
Expand All @@ -95,6 +97,30 @@ describe('template generation', () => {
).toEqual([]);
}));

it('preserves pre-existing user content and an empty destination after partial failure', () =>
withTempDirectory((root) => {
const templateRoot = join(root, 'template');
const destination = join(root, 'demo-app');
const userFile = join(root, 'user-notes.txt');
mkdirSync(templateRoot);
mkdirSync(destination);
writeFileSync(userFile, 'keep this content');
writeFileSync(join(templateRoot, 'a-good.txt'), 'created first');
writeFileSync(join(templateRoot, 'z-bad.txt'), '{{unsupportedToken}}');

expect(() =>
generateProjectFromTemplate(context(root), { templateRoot }),
).toThrow(ValidationCliError);

expect(readdirSync(destination)).toEqual([]);
expect(readFileSync(userFile, 'utf8')).toBe('keep this content');
expect(
readdirSync(root).filter((name) =>
name.startsWith('.stellarforge-demo-app-'),
),
).toEqual([]);
}));

it('refuses to overwrite a non-empty destination', () =>
withTempDirectory((root) => {
const templateRoot = join(root, 'template');
Expand Down