hivecast-sdk/packages/cli/tests/integration/fp30-install-lifecycle.spec.ts

147 lines
4.6 KiB
TypeScript
Raw Permalink Normal View History

import { afterEach, describe, it } from 'node:test';
import { strict as assert } from 'node:assert';
import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
import { runMxCli, parseLastJson, assertExitCode } from '../helpers/cli-harness.js';
function createLifecyclePackage(rootDir: string, packageName: string, scriptBody: string): string {
const packageDir = path.join(rootDir, packageName.replace(/^@/, '').replace('/', '-'));
fs.mkdirSync(path.join(packageDir, 'dist'), { recursive: true });
fs.writeFileSync(
path.join(packageDir, 'matrix.json'),
JSON.stringify(
{
name: packageName,
version: '1.0.0',
runtime: { language: 'javascript', entry: 'dist/index.js' },
components: [],
install: {
seed: {
script: 'dist/seed.js',
description: 'seed test hook',
},
},
permissions: { fsPolicy: 'none' },
},
null,
2,
),
'utf8',
);
fs.writeFileSync(
path.join(packageDir, 'package.json'),
JSON.stringify(
{
name: packageName,
version: '1.0.0',
type: 'module',
main: 'dist/index.js',
},
null,
2,
),
'utf8',
);
fs.writeFileSync(path.join(packageDir, 'dist', 'index.js'), 'export const ready = true;\n', 'utf8');
fs.writeFileSync(path.join(packageDir, 'dist', 'seed.js'), scriptBody, 'utf8');
return packageDir;
}
describe('FP-30 mx install lifecycle', () => {
const tempDirs: string[] = [];
afterEach(() => {
delete process.env.MX_TEST_MARKER;
while (tempDirs.length > 0) {
const dir = tempDirs.pop();
if (dir) {
fs.rmSync(dir, { recursive: true, force: true });
}
}
});
it('runs install lifecycle hooks and exposes install env', async () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'mx-fp30-install-lifecycle-'));
tempDirs.push(root);
const workspaceDir = path.join(root, 'workspace');
fs.mkdirSync(workspaceDir, { recursive: true });
const packagesDir = path.join(root, '.matrix', 'packages');
const markerPath = path.join(root, 'seed-marker.json');
process.env.MX_TEST_MARKER = markerPath;
const packageDir = createLifecyclePackage(
root,
'@acme/install-lifecycle',
`import { writeFileSync } from 'node:fs';
const marker = process.env.MX_TEST_MARKER;
if (!marker) throw new Error('missing marker');
writeFileSync(marker, JSON.stringify({
phase: process.env.MATRIX_INSTALL_PHASE,
packagesDir: process.env.MATRIX_PACKAGES_DIR,
packageDir: process.env.MATRIX_PACKAGE_DIR,
packageName: process.env.MATRIX_PACKAGE_NAME
}), 'utf8');
`,
);
const result = await runMxCli([
'node',
'mx',
'install',
packageDir,
'--packages-dir',
packagesDir,
'--json',
], { cwd: workspaceDir });
assertExitCode(result, 0);
const payload = parseLastJson(result.logs) as {
targetDir: string;
lifecyclePhases: string[];
};
assert.deepEqual(payload.lifecyclePhases, ['seed']);
assert.equal(fs.existsSync(payload.targetDir), true);
assert.equal(fs.existsSync(markerPath), true);
const marker = JSON.parse(fs.readFileSync(markerPath, 'utf8')) as Record<string, string>;
assert.equal(marker.phase, 'seed');
assert.equal(path.resolve(marker.packagesDir), path.resolve(packagesDir));
assert.equal(path.resolve(marker.packageDir), path.resolve(payload.targetDir));
assert.equal(marker.packageName, '@acme/install-lifecycle');
});
it('rolls back package install when a lifecycle hook fails', async () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'mx-fp30-install-lifecycle-fail-'));
tempDirs.push(root);
const workspaceDir = path.join(root, 'workspace');
fs.mkdirSync(workspaceDir, { recursive: true });
const packagesDir = path.join(root, '.matrix', 'packages');
const packageDir = createLifecyclePackage(
root,
'@acme/install-lifecycle-fail',
`throw new Error('seed failed');\n`,
);
const result = await runMxCli([
'node',
'mx',
'install',
packageDir,
'--packages-dir',
packagesDir,
'--json',
], { cwd: workspaceDir });
assertExitCode(result, 1);
assert.equal(
result.errors.some((line) => line.includes('install.seed failed')),
true,
`Expected lifecycle failure in stderr, got:\n${result.errors.join('\n')}`,
);
const installedPath = path.join(packagesDir, 'node_modules', '@acme', 'install-lifecycle-fail');
assert.equal(fs.existsSync(installedPath), false);
});
});