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: 0 additions & 5 deletions .changeset/git-doctor-diagnostic.md

This file was deleted.

5 changes: 0 additions & 5 deletions .changeset/protected-release-automation.md

This file was deleted.

5 changes: 0 additions & 5 deletions .changeset/runtime-doctor-diagnostics.md

This file was deleted.

5 changes: 0 additions & 5 deletions .changeset/rust-cargo-doctor-diagnostics.md

This file was deleted.

28 changes: 23 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,30 @@ All notable changes to StellarForge CLI will be documented in this file.

The project follows [Semantic Versioning](https://semver.org/) and uses Changesets to capture release intent and generate version/changelog updates.

## [Unreleased]
## 0.1.0 — 2026-09-11

### Added

- Initial public repository foundation.
- Product and architecture planning for the CLI MVP.
- Contributor, security, and release-management foundations.
- Initial public StellarForge CLI MVP foundation and command architecture.
- `stellarforge new` with controlled Basic App, Full Stack, Smart Contract, and API Service templates.
- `stellarforge doctor` diagnostics for Node.js, npm, Git, Rust, Cargo, Stellar CLI, and Docker.
- `stellarforge dev` safe local process orchestration.
- `stellarforge test` unified Node/Cargo test execution.
- `stellarforge deploy` guarded Stellar Testnet smart-contract deployment.
- Versioned `stellarforge.config.json` project configuration with secret-safe validation.
- Cross-platform Linux, macOS, and Windows validation.
- Deterministic linked-CLI end-to-end smoke workflow.
- Changesets-based release management and protected npm/GitHub release automation.
- CodeQL, dependency review configuration, Dependabot, CODEOWNERS, OpenSSF Scorecard, threat modeling, and secure contributor workflows.

### Security

- Uses argument-array subprocess execution with `shell: false`.
- Rejects path traversal, unsafe overwrite targets, raw Stellar StrKeys, secret-like configuration fields, and unsupported deployment networks.
- Keeps signing material in Stellar CLI identity management rather than project configuration.
- Bounds diagnostic subprocess execution time.
- Publishes through npm Trusted Publishing/OIDC once registry bootstrap is completed.

## [Unreleased]

> Release entries below this section will be generated and refined as versions are published. Documentation-only and internal maintenance changes may not require a changeset unless they materially affect users or release communication.
No unreleased user-facing changes yet.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@stellarforge/cli",
"version": "0.0.0",
"version": "0.1.0",
"private": false,
"description": "Command-line interface for building production-ready Stellar applications.",
"type": "module",
Expand Down
5 changes: 3 additions & 2 deletions tests/executable.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
} from './helpers/cli-process.js';

const packageJsonPath = resolve(repositoryRoot, 'package.json');
const DOCTOR_TEST_TIMEOUT_MS = 25_000;

beforeAll(() => {
buildCli();
Expand Down Expand Up @@ -57,13 +58,13 @@ describe('StellarForge CLI executable', () => {
expect(result.stdout).toContain('[PASS] npm:');
expect(result.stdout).toContain('[PASS] Git:');
expect(result.stdout).toContain('Rust:');
expect(result.stdout).toContain('[PASS] Cargo:');
expect(result.stdout).toContain('Cargo:');
expect(result.stdout).toContain('Stellar CLI:');
expect(result.stdout).toContain('Docker:');
expect(result.stdout).toMatch(
/Summary: \d+ passed, \d+ warnings, \d+ failed\./,
);
}, 15_000);
}, DOCTOR_TEST_TIMEOUT_MS);

it('prints the package version from the built executable', () => {
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')) as {
Expand Down
122 changes: 70 additions & 52 deletions tests/static-quality.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,75 +28,93 @@ const typescriptCliPath = resolve(
);
const eslintConfigPath = resolve(repositoryRoot, 'eslint.config.js');
const tsconfigPath = resolve(repositoryRoot, 'tsconfig.json');
const TOOL_TIMEOUT_MS = 25_000;
const TEST_TIMEOUT_MS = 30_000;

function runLocalTool(binaryPath: string, args: string[], input?: string) {
return spawnSync(process.execPath, [binaryPath, ...args], {
cwd: repositoryRoot,
encoding: 'utf8',
input,
shell: false,
timeout: TOOL_TIMEOUT_MS,
});
}

describe('static quality enforcement', () => {
it('rejects a lint violation with the repository ESLint configuration', () => {
const result = runLocalTool(
eslintCliPath,
[
'--config',
eslintConfigPath,
'--stdin',
'--stdin-filename',
'src/main.ts',
],
'const unused = 1;\n',
);
it(
'rejects a lint violation with the repository ESLint configuration',
() => {
const result = runLocalTool(
eslintCliPath,
[
'--config',
eslintConfigPath,
'--stdin',
'--stdin-filename',
'src/main.ts',
],
'const unused = 1;\n',
);

expect(result.status).not.toBe(0);
expect(`${result.stdout}${result.stderr}`).toContain('no-unused-vars');
}, 15_000);
expect(result.status).not.toBe(0);
expect(`${result.stdout}${result.stderr}`).toContain('no-unused-vars');
},
TEST_TIMEOUT_MS,
);

it('rejects a formatting violation with the repository Prettier configuration', async () => {
await withTempDirectory((directory) => {
const fixturePath = join(directory, 'bad-format.ts');
writeFileSync(fixturePath, 'const value={answer:42}\n');
it(
'rejects a formatting violation with the repository Prettier configuration',
async () => {
await withTempDirectory((directory) => {
const fixturePath = join(directory, 'bad-format.ts');
writeFileSync(fixturePath, 'const value={answer:42}\n');

const result = runLocalTool(prettierCliPath, [
'--check',
'--config',
resolve(repositoryRoot, '.prettierrc'),
fixturePath,
]);
const result = runLocalTool(prettierCliPath, [
'--check',
'--config',
resolve(repositoryRoot, '.prettierrc'),
fixturePath,
]);

expect(result.status).not.toBe(0);
expect(`${result.stdout}${result.stderr}`).toContain(
'Code style issues found',
);
});
}, 15_000);
expect(result.status).not.toBe(0);
expect(`${result.stdout}${result.stderr}`).toContain(
'Code style issues found',
);
});
},
TEST_TIMEOUT_MS,
);

it('rejects a type violation with strict repository compiler options', async () => {
await withTempDirectory((directory) => {
const fixturePath = join(directory, 'bad-types.ts');
const fixtureConfigPath = join(directory, 'tsconfig.json');
it(
'rejects a type violation with strict repository compiler options',
async () => {
await withTempDirectory((directory) => {
const fixturePath = join(directory, 'bad-types.ts');
const fixtureConfigPath = join(directory, 'tsconfig.json');

writeFileSync(fixturePath, "const count: number = 'not-a-number';\n");
writeFileSync(
fixtureConfigPath,
JSON.stringify({
extends: tsconfigPath,
compilerOptions: {
types: [],
noEmit: true,
},
include: ['./bad-types.ts'],
}),
);
writeFileSync(fixturePath, "const count: number = 'not-a-number';\n");
writeFileSync(
fixtureConfigPath,
JSON.stringify({
extends: tsconfigPath,
compilerOptions: {
types: [],
noEmit: true,
},
include: ['./bad-types.ts'],
}),
);

const result = runLocalTool(typescriptCliPath, ['-p', fixtureConfigPath]);
const result = runLocalTool(typescriptCliPath, [
'-p',
fixtureConfigPath,
]);

expect(result.status).not.toBe(0);
expect(`${result.stdout}${result.stderr}`).toContain('TS2322');
});
}, 15_000);
expect(result.status).not.toBe(0);
expect(`${result.stdout}${result.stderr}`).toContain('TS2322');
});
},
TEST_TIMEOUT_MS,
);
});
Loading