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/sw-039-metadata-conformance-helper.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@diginodes/stellarforge-cli': patch
---

Add `assertPackageMetadata` and `readPackageJson` to `tests/helpers` (SW-039). The new reusable conformance helper centralises all generated-project package.json assertions — name, scripts, engines.node, absence of dependencies, and absence of embedded secrets — so template tests no longer duplicate that boilerplate. Adopted in `basic-app-package-metadata.test.ts`.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"npm": ">=10.9.0"
},
"scripts": {
"audit:workflow-permissions": "node scripts/audit-workflow-permissions.mjs"
"audit:workflow-permissions": "node scripts/audit-workflow-permissions.mjs",
"build": "tsc -p tsconfig.build.json",
"typecheck": "tsc -p tsconfig.json --noEmit",
"lint": "eslint .",
Expand Down
30 changes: 30 additions & 0 deletions src/diagnostics/bounds.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* Bounds constants and utilities for diagnostic text rendering.
*
* These values constrain the maximum size of message and remediation text
* written to the terminal so a failing tool cannot flood the output or reflect
* secret-like content through unbounded text.
*/

/** Maximum character length for a single diagnostic message. */
export const MAX_DIAGNOSTIC_MESSAGE_LENGTH = 200;

/** Maximum character length for a single diagnostic remediation hint. */
export const MAX_DIAGNOSTIC_REMEDIATION_LENGTH = 200;

/**
* Truncates `text` to `maxLength` characters, appending an ellipsis when the
* text is longer than the limit. Returns the empty string when `text` is empty.
*/
export function boundDiagnosticText(text: string, maxLength: number): string {
if (!text) {
return '';
}

if (text.length <= maxLength) {
return text;
}

const ellipsis = '...';
return `${text.slice(0, Math.max(0, maxLength - ellipsis.length))}${ellipsis}`;
}
124 changes: 81 additions & 43 deletions tests/basic-app-package-metadata.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { readFileSync } from 'node:fs';
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 {
assertPackageMetadata,
createCapturedTerminalOutput,
withTempDirectory,
} from './helpers/index.js';
Expand All @@ -30,7 +30,7 @@ function runNpmScript(projectRoot: string, script: string) {
}

describe('Basic App package metadata conformance (SW-041)', () => {
it('sets the package name to the generated project name', () =>
it('conforms to all frozen package.json metadata constraints', () =>
withTempDirectory((root) => {
const command = createNewCommand({
cwd: () => root,
Expand All @@ -39,11 +39,33 @@ describe('Basic App package metadata conformance (SW-041)', () => {

command.parse(['node', 'new', 'my-stellar-app']);

const packageJson = JSON.parse(
readFileSync(join(root, 'my-stellar-app', 'package.json'), 'utf8'),
) as Record<string, unknown>;
assertPackageMetadata(
join(root, 'my-stellar-app', 'package.json'),
{
name: 'my-stellar-app',
scripts: FROZEN_SCRIPTS,
nodeEngine: FROZEN_NODE_ENGINE,
},
);
}));

it('sets the package name to the generated project name', () =>
withTempDirectory((root) => {
const command = createNewCommand({
cwd: () => root,
output: createCapturedTerminalOutput().output,
});

command.parse(['node', 'new', 'my-stellar-app']);

expect(packageJson['name']).toBe('my-stellar-app');
assertPackageMetadata(
join(root, 'my-stellar-app', 'package.json'),
{
name: 'my-stellar-app',
scripts: FROZEN_SCRIPTS,
nodeEngine: FROZEN_NODE_ENGINE,
},
);
}));

it('retains all frozen script entries without modification', () =>
Expand All @@ -55,11 +77,14 @@ describe('Basic App package metadata conformance (SW-041)', () => {

command.parse(['node', 'new', 'my-stellar-app']);

const packageJson = JSON.parse(
readFileSync(join(root, 'my-stellar-app', 'package.json'), 'utf8'),
) as Record<string, unknown>;

expect(packageJson['scripts']).toEqual(FROZEN_SCRIPTS);
assertPackageMetadata(
join(root, 'my-stellar-app', 'package.json'),
{
name: 'my-stellar-app',
scripts: FROZEN_SCRIPTS,
nodeEngine: FROZEN_NODE_ENGINE,
},
);
}));

it('retains only the frozen script keys and no extras', () =>
Expand All @@ -71,14 +96,13 @@ describe('Basic App package metadata conformance (SW-041)', () => {

command.parse(['node', 'new', 'my-stellar-app']);

const packageJson = JSON.parse(
readFileSync(join(root, 'my-stellar-app', 'package.json'), 'utf8'),
) as Record<string, unknown>;

const scripts = packageJson['scripts'] as Record<string, unknown>;

expect(Object.keys(scripts).sort()).toEqual(
Object.keys(FROZEN_SCRIPTS).sort(),
assertPackageMetadata(
join(root, 'my-stellar-app', 'package.json'),
{
name: 'my-stellar-app',
scripts: FROZEN_SCRIPTS,
nodeEngine: FROZEN_NODE_ENGINE,
},
);
}));

Expand All @@ -91,14 +115,14 @@ describe('Basic App package metadata conformance (SW-041)', () => {

command.parse(['node', 'new', 'my-stellar-app']);

const packageJson = JSON.parse(
readFileSync(join(root, 'my-stellar-app', 'package.json'), 'utf8'),
) as Record<string, unknown>;

const engines = packageJson['engines'] as Record<string, unknown>;

expect(engines).toBeDefined();
expect(engines['node']).toBe(FROZEN_NODE_ENGINE);
assertPackageMetadata(
join(root, 'my-stellar-app', 'package.json'),
{
name: 'my-stellar-app',
scripts: FROZEN_SCRIPTS,
nodeEngine: FROZEN_NODE_ENGINE,
},
);
}));

it('generates no dependencies field', () =>
Expand All @@ -110,11 +134,14 @@ describe('Basic App package metadata conformance (SW-041)', () => {

command.parse(['node', 'new', 'my-stellar-app']);

const packageJson = JSON.parse(
readFileSync(join(root, 'my-stellar-app', 'package.json'), 'utf8'),
) as Record<string, unknown>;

expect(packageJson['dependencies']).toBeUndefined();
assertPackageMetadata(
join(root, 'my-stellar-app', 'package.json'),
{
name: 'my-stellar-app',
scripts: FROZEN_SCRIPTS,
nodeEngine: FROZEN_NODE_ENGINE,
},
);
}));

it('generates no devDependencies field', () =>
Expand All @@ -126,11 +153,14 @@ describe('Basic App package metadata conformance (SW-041)', () => {

command.parse(['node', 'new', 'my-stellar-app']);

const packageJson = JSON.parse(
readFileSync(join(root, 'my-stellar-app', 'package.json'), 'utf8'),
) as Record<string, unknown>;

expect(packageJson['devDependencies']).toBeUndefined();
assertPackageMetadata(
join(root, 'my-stellar-app', 'package.json'),
{
name: 'my-stellar-app',
scripts: FROZEN_SCRIPTS,
nodeEngine: FROZEN_NODE_ENGINE,
},
);
}));

it('does not embed secret, seed, or private-key values in the generated package.json', () =>
Expand All @@ -142,14 +172,14 @@ describe('Basic App package metadata conformance (SW-041)', () => {

command.parse(['node', 'new', 'my-stellar-app']);

const raw = readFileSync(
assertPackageMetadata(
join(root, 'my-stellar-app', 'package.json'),
'utf8',
{
name: 'my-stellar-app',
scripts: FROZEN_SCRIPTS,
nodeEngine: FROZEN_NODE_ENGINE,
},
);

expect(raw).not.toMatch(/SECRET|SEED|PRIVATE_KEY/);
// Stellar secret key strkeys start with 'S' and are 56 uppercase base32 chars
expect(raw).not.toMatch(/S[A-Z0-9]{55}/);
}));

it(
Expand All @@ -164,6 +194,14 @@ describe('Basic App package metadata conformance (SW-041)', () => {
command.parse(['node', 'new', 'my-stellar-app']);

const projectRoot = join(root, 'my-stellar-app');

// Confirm metadata is still conformant, then exercise the check script.
assertPackageMetadata(join(projectRoot, 'package.json'), {
name: 'my-stellar-app',
scripts: FROZEN_SCRIPTS,
nodeEngine: FROZEN_NODE_ENGINE,
});

const result = runNpmScript(projectRoot, 'check');

expect(result.status, result.stderr).toBe(0);
Expand Down
6 changes: 6 additions & 0 deletions tests/helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,9 @@ export {
SECRET_STRKEY_FIXTURE,
TOKEN_FIXTURE,
} from './secret-fixtures.js';
export {
assertPackageMetadata,
readPackageJson,
type ExpectedScripts,
type PackageMetadataOptions,
} from './package-metadata.js';
Loading