168 lines
5.1 KiB
TypeScript
168 lines
5.1 KiB
TypeScript
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';
|
|
|
|
describe('FP-30 mx lifecycle commands', () => {
|
|
const tempDirs: string[] = [];
|
|
|
|
function createLocalPackage(rootDir: string): { packageDir: string; packageName: string } {
|
|
const packageName = '@acme/weather-actor';
|
|
const packageDir = path.join(rootDir, 'weather-actor');
|
|
fs.mkdirSync(path.join(packageDir, 'src'), { recursive: true });
|
|
fs.writeFileSync(
|
|
path.join(packageDir, 'matrix.json'),
|
|
JSON.stringify(
|
|
{
|
|
name: packageName,
|
|
version: '1.0.0',
|
|
runtime: { language: 'typescript', entry: 'src/index.ts' },
|
|
components: [
|
|
{
|
|
type: 'WeatherActor',
|
|
export: 'WeatherActor',
|
|
mount: 'weather.actor',
|
|
autoStart: true,
|
|
},
|
|
],
|
|
permissions: { fsPolicy: 'none' },
|
|
},
|
|
null,
|
|
2
|
|
),
|
|
'utf8'
|
|
);
|
|
fs.writeFileSync(path.join(packageDir, 'package.json'), JSON.stringify({ name: packageName, version: '1.0.0' }, null, 2));
|
|
fs.writeFileSync(path.join(packageDir, 'src', 'index.ts'), 'export class WeatherActor {}\n', 'utf8');
|
|
return { packageDir, packageName };
|
|
}
|
|
|
|
async function loadMxCli(): Promise<{ createProgram: () => import('commander').Command }> {
|
|
const mxCliUrl = new URL('../../src/index.ts', import.meta.url).href;
|
|
return await import(`${mxCliUrl}?test=${Date.now()}-${Math.random().toString(16).slice(2)}`);
|
|
}
|
|
|
|
async function runMxCli(argv: string[]): Promise<string[]> {
|
|
const logs: string[] = [];
|
|
const originalLog = console.log;
|
|
const originalExit = process.exit;
|
|
const { createProgram } = await loadMxCli();
|
|
console.log = (...args: unknown[]) => {
|
|
logs.push(args.map((arg) => String(arg)).join(' '));
|
|
};
|
|
(process as unknown as { exit: (code?: number) => never }).exit = ((code?: number) => {
|
|
throw new Error(`process.exit(${code ?? 0})`);
|
|
}) as never;
|
|
|
|
try {
|
|
const program = createProgram();
|
|
await program.parseAsync(argv);
|
|
return logs;
|
|
} finally {
|
|
console.log = originalLog;
|
|
(process as unknown as { exit: typeof process.exit }).exit = originalExit;
|
|
}
|
|
}
|
|
|
|
function parseLastJson(logs: string[]): unknown {
|
|
for (let i = logs.length - 1; i >= 0; i--) {
|
|
const value = logs[i].trim();
|
|
if (!value) continue;
|
|
try {
|
|
return JSON.parse(value);
|
|
} catch {
|
|
// Continue scanning log lines until JSON is found.
|
|
}
|
|
}
|
|
throw new Error(`No JSON payload found in logs:\n${logs.join('\n')}`);
|
|
}
|
|
|
|
afterEach(() => {
|
|
while (tempDirs.length > 0) {
|
|
const dir = tempDirs.pop();
|
|
if (dir) {
|
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
}
|
|
});
|
|
|
|
it('installs, lists, inspects, validates, and uninstalls local packages', async () => {
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'mx-fp30-lifecycle-'));
|
|
tempDirs.push(root);
|
|
const cwd = path.join(root, 'workspace');
|
|
const packagesDir = path.join(cwd, '.matrix', 'packages');
|
|
fs.mkdirSync(cwd, { recursive: true });
|
|
|
|
const { packageDir, packageName } = createLocalPackage(root);
|
|
|
|
await runMxCli([
|
|
'node',
|
|
'mx',
|
|
'install',
|
|
packageDir,
|
|
'--packages-dir',
|
|
packagesDir,
|
|
'--json',
|
|
]);
|
|
|
|
const installedPath = path.join(packagesDir, 'node_modules', '@acme', 'weather-actor');
|
|
assert.equal(fs.existsSync(installedPath), true);
|
|
|
|
const listedAfterInstall = parseLastJson(await runMxCli([
|
|
'node',
|
|
'mx',
|
|
'list',
|
|
'--packages-dir',
|
|
packagesDir,
|
|
'--json',
|
|
])) as string[];
|
|
assert.equal(Array.isArray(listedAfterInstall), true);
|
|
assert.equal(listedAfterInstall.includes(packageName), true);
|
|
|
|
const infoPayload = parseLastJson(await runMxCli([
|
|
'node',
|
|
'mx',
|
|
'info',
|
|
packageName,
|
|
'--packages-dir',
|
|
packagesDir,
|
|
'--json',
|
|
])) as { packageName: string; installPath: string };
|
|
assert.equal(infoPayload.packageName, packageName);
|
|
assert.equal(path.resolve(infoPayload.installPath), path.resolve(installedPath));
|
|
|
|
const validatePayload = parseLastJson(await runMxCli([
|
|
'node',
|
|
'mx',
|
|
'validate',
|
|
path.join(packageDir, 'matrix.json'),
|
|
'--json',
|
|
])) as { packageName: string; valid: boolean };
|
|
assert.equal(validatePayload.packageName, packageName);
|
|
assert.equal(validatePayload.valid, true);
|
|
|
|
await runMxCli([
|
|
'node',
|
|
'mx',
|
|
'uninstall',
|
|
packageName,
|
|
'--packages-dir',
|
|
packagesDir,
|
|
'--json',
|
|
]);
|
|
assert.equal(fs.existsSync(installedPath), false);
|
|
|
|
const listedAfterUninstall = parseLastJson(await runMxCli([
|
|
'node',
|
|
'mx',
|
|
'list',
|
|
'--packages-dir',
|
|
packagesDir,
|
|
'--json',
|
|
])) as string[];
|
|
assert.equal(Array.isArray(listedAfterUninstall), true);
|
|
assert.equal(listedAfterUninstall.includes(packageName), false);
|
|
});
|
|
});
|