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
4 changes: 2 additions & 2 deletions templates/api-service/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
"private": true,
"type": "module",
"scripts": {
"start": "node src/start.js",
"check": "node --check src/config.js && node --check src/stellar-client.js && node --check src/server.js && node --check src/start.js",
"test": "node --test test/*.test.js",
"start": "node src/start.js"
"test": "node --test test/*.test.js"
},
"engines": {
"node": ">=22.13.0"
Expand Down
187 changes: 187 additions & 0 deletions tests/api-service-package-metadata.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
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';

// Frozen template contract for API Service package.json (SW-043).
// Update this object only when the template itself is intentionally changed.
const FROZEN_SCRIPTS = {
start: 'node src/start.js',
check:
'node --check src/config.js && node --check src/stellar-client.js && node --check src/server.js && node --check src/start.js',
test: 'node --test test/*.test.js',
} as const;

const FROZEN_NODE_ENGINE = '>=22.13.0';

function runNpmScript(projectRoot: string, script: string) {
const npm = createNpmInvocation(['run', script]);

return spawnSync(npm.command, [...npm.args], {
cwd: projectRoot,
encoding: 'utf8',
shell: false,
});
}

describe('API Service package metadata conformance (SW-043)', () => {
it('conforms to all frozen package.json metadata constraints', () =>
withTempDirectory((root) => {
const command = createNewCommand({
cwd: () => root,
output: createCapturedTerminalOutput().output,
});

command.parse(['node', 'new', 'demo-api', '--template', 'api-service']);

assertPackageMetadata(join(root, 'demo-api', 'package.json'), {
name: 'demo-api',
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', 'demo-api', '--template', 'api-service']);

assertPackageMetadata(join(root, 'demo-api', 'package.json'), {
name: 'demo-api',
scripts: FROZEN_SCRIPTS,
nodeEngine: FROZEN_NODE_ENGINE,
});
}));

it('retains all frozen script entries without modification', () =>
withTempDirectory((root) => {
const command = createNewCommand({
cwd: () => root,
output: createCapturedTerminalOutput().output,
});

command.parse(['node', 'new', 'demo-api', '--template', 'api-service']);

assertPackageMetadata(join(root, 'demo-api', 'package.json'), {
name: 'demo-api',
scripts: FROZEN_SCRIPTS,
nodeEngine: FROZEN_NODE_ENGINE,
});
}));

it('retains only the frozen script keys and no extras', () =>
withTempDirectory((root) => {
const command = createNewCommand({
cwd: () => root,
output: createCapturedTerminalOutput().output,
});

command.parse(['node', 'new', 'demo-api', '--template', 'api-service']);

assertPackageMetadata(join(root, 'demo-api', 'package.json'), {
name: 'demo-api',
scripts: FROZEN_SCRIPTS,
nodeEngine: FROZEN_NODE_ENGINE,
});
}));

it('sets the engines.node constraint to the frozen minimum version', () =>
withTempDirectory((root) => {
const command = createNewCommand({
cwd: () => root,
output: createCapturedTerminalOutput().output,
});

command.parse(['node', 'new', 'demo-api', '--template', 'api-service']);

assertPackageMetadata(join(root, 'demo-api', 'package.json'), {
name: 'demo-api',
scripts: FROZEN_SCRIPTS,
nodeEngine: FROZEN_NODE_ENGINE,
});
}));

it('generates no dependencies field', () =>
withTempDirectory((root) => {
const command = createNewCommand({
cwd: () => root,
output: createCapturedTerminalOutput().output,
});

command.parse(['node', 'new', 'demo-api', '--template', 'api-service']);

assertPackageMetadata(join(root, 'demo-api', 'package.json'), {
name: 'demo-api',
scripts: FROZEN_SCRIPTS,
nodeEngine: FROZEN_NODE_ENGINE,
});
}));

it('generates no devDependencies field', () =>
withTempDirectory((root) => {
const command = createNewCommand({
cwd: () => root,
output: createCapturedTerminalOutput().output,
});

command.parse(['node', 'new', 'demo-api', '--template', 'api-service']);

assertPackageMetadata(join(root, 'demo-api', 'package.json'), {
name: 'demo-api',
scripts: FROZEN_SCRIPTS,
nodeEngine: FROZEN_NODE_ENGINE,
});
}));

it('does not embed secret, seed, or private-key values in the generated package.json', () =>
withTempDirectory((root) => {
const command = createNewCommand({
cwd: () => root,
output: createCapturedTerminalOutput().output,
});

command.parse(['node', 'new', 'demo-api', '--template', 'api-service']);

assertPackageMetadata(join(root, 'demo-api', 'package.json'), {
name: 'demo-api',
scripts: FROZEN_SCRIPTS,
nodeEngine: FROZEN_NODE_ENGINE,
});
}));

it(
'passes the repository check script after generation',
() =>
withTempDirectory((root) => {
const command = createNewCommand({
cwd: () => root,
output: createCapturedTerminalOutput().output,
});

command.parse(['node', 'new', 'demo-api', '--template', 'api-service']);

const projectRoot = join(root, 'demo-api');

assertPackageMetadata(join(projectRoot, 'package.json'), {
name: 'demo-api',
scripts: FROZEN_SCRIPTS,
nodeEngine: FROZEN_NODE_ENGINE,
});

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

expect(result.status, result.stderr).toBe(0);
}),
15_000,
);
});