hivecast-sdk/archive/mx-cli-runtime-host-tests/actor-command.host-workspace.spec.ts

172 lines
8.6 KiB
TypeScript

import { 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 { spawnSync } from 'node:child_process';
import { fileURLToPath } from 'node:url';
import { actorCommand } from '../../src/commands/actor.js';
import { installOpenMatrixCoreStub } from '../support/installCoreStub.ts';
import { loadMatrixServiceManifest } from '../../src/utils/service-manifest.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const repoRoot = path.resolve(__dirname, '../../../../');
describe('mx actor command', () => {
it('creates a compilable actor scaffold package', async () => {
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'mx-actor-'));
const outputDir = path.join(tmpRoot, 'my-weather-service');
try {
const result = await actorCommand('my-weather-service', {
cwd: tmpRoot,
outputDir,
dev: true,
});
assert.equal(result.rootDir, outputDir);
assert.equal(result.workspacePath, path.join(tmpRoot, '.matrix', 'workspace.json'));
assert.equal(fs.existsSync(path.join(outputDir, 'matrix.json')), true);
assert.equal(fs.existsSync(path.join(outputDir, 'src', `${result.className}.ts`)), true);
assert.equal(fs.existsSync(path.join(outputDir, 'src', 'index.ts')), true);
assert.equal(fs.existsSync(path.join(outputDir, 'examples', 'get-status.mxq')), true);
assert.equal(fs.existsSync(path.join(outputDir, 'skills', 'my-weather-service.skill.md')), true);
assert.equal(fs.existsSync(path.join(outputDir, 'tests', 'my-weather-service.spec.ts')), true);
assert.equal(fs.existsSync(path.join(outputDir, 'package.json')), true, 'package.json should be generated');
assert.equal(fs.existsSync(path.join(outputDir, 'tsconfig.json')), true, 'tsconfig.json should be generated');
assert.equal(fs.existsSync(path.join(outputDir, 'matrix.service.json')), false, 'standalone factory is declared in matrix.json');
assert.equal(
fs.existsSync(path.join(outputDir, 'src', `create-standalone-${result.className}.ts`)),
true,
'standalone service factory should be generated',
);
const pkg = JSON.parse(fs.readFileSync(path.join(outputDir, 'package.json'), 'utf8'));
assert.equal(pkg.type, 'module', 'package.json should have type: module');
assert.ok(Array.isArray(pkg.files), 'package.json should have files array');
assert.equal(pkg.files.includes('matrix.service.json'), false, 'package files should not ship a second service manifest');
assert.equal(pkg.scripts?.build, 'tsc -p tsconfig.json');
assert.equal(pkg.scripts?.['build:watch'], 'tsc -w -p tsconfig.json --preserveWatchOutput');
assert.equal(pkg.scripts?.dev, 'tsc -w -p tsconfig.json --preserveWatchOutput');
assert.equal(pkg.scripts?.test, 'node --import tsx --test tests/**/*.spec.ts');
assert.equal(pkg.dependencies?.['@open-matrix/core'], '*', 'actor service package should declare runtime SDK dependency');
assert.equal(pkg.devDependencies?.['@open-matrix/core'], undefined, 'actor service package should not hide runtime SDK in devDependencies');
const tsconfig = JSON.parse(fs.readFileSync(path.join(outputDir, 'tsconfig.json'), 'utf8'));
assert.ok(tsconfig.compilerOptions, 'tsconfig.json should have compilerOptions');
assert.equal(tsconfig.compilerOptions.target, 'ES2022');
assert.equal(tsconfig.compilerOptions.incremental, true);
assert.equal(tsconfig.compilerOptions.tsBuildInfoFile, '.tsbuildinfo');
assert.equal(tsconfig.compilerOptions.sourceMap, true);
const manifest = JSON.parse(fs.readFileSync(path.join(outputDir, 'matrix.json'), 'utf8')) as {
name: string;
runtime?: {
factory?: {
kind?: string;
export?: string;
instance?: {
rootKind?: string;
componentType?: string;
autoStart?: boolean;
};
};
};
components: Array<{ mount?: string; type?: string }>;
};
assert.equal(manifest.name, '@matrix/my-weather-service');
assert.equal(manifest.components[0]?.mount, 'my-weather-service');
assert.equal(manifest.components[0]?.type, result.className);
assert.equal(manifest.runtime?.factory?.kind, 'factory');
assert.equal(manifest.runtime?.factory?.export, `createStandalone${result.className}`);
assert.equal(manifest.runtime?.factory?.instance?.rootKind, 'component');
assert.equal(manifest.runtime?.factory?.instance?.componentType, result.className);
assert.equal(manifest.runtime?.factory?.instance?.autoStart, true);
// Install a minimal runtime stub so the scaffolded actor can resolve
// the published package import shape without depending on a built workspace copy.
installOpenMatrixCoreStub(outputDir);
const compile = spawnSync(
process.execPath,
[
path.resolve(repoRoot, 'node_modules/tsx/dist/cli.mjs'),
path.join(outputDir, 'src', `${result.className}.ts`),
],
{ cwd: outputDir, encoding: 'utf8' }
);
assert.equal(
compile.status,
0,
`expected generated actor to compile, stderr: ${compile.stderr || '(none)'}`
);
const build = spawnSync(
process.execPath,
[
path.resolve(repoRoot, 'node_modules/typescript/bin/tsc'),
'-p',
path.join(outputDir, 'tsconfig.json'),
],
{ cwd: outputDir, encoding: 'utf8' },
);
assert.equal(build.status, 0, `expected generated package to build, stderr: ${build.stderr || '(none)'}`);
const loadedService = loadMatrixServiceManifest(outputDir);
assert.equal(loadedService.manifestPath, path.join(outputDir, 'matrix.json'));
assert.equal(loadedService.exportName, `createStandalone${result.className}`);
assert.equal(loadedService.entryPath, path.join(outputDir, 'dist', 'index.js'));
assert.equal(loadedService.serviceInstance.rootKind, 'component');
assert.equal(loadedService.serviceInstance.mount, 'my-weather-service');
const workspace = JSON.parse(fs.readFileSync(result.workspacePath!, 'utf8'));
assert.equal(workspace.home, '.matrix/home');
assert.deepEqual(workspace.default, ['system', 'host-control', 'my-weather-service']);
assert.deepEqual(workspace.services?.system, {
package: '@open-matrix/system',
store: 'system',
});
assert.deepEqual(workspace.services?.['host-control'], {
package: '@open-matrix/host-control',
store: 'system',
});
assert.deepEqual(workspace.services?.['my-weather-service'], {
path: './my-weather-service',
});
} finally {
fs.rmSync(tmpRoot, { recursive: true, force: true });
}
});
it('adds the source checkout system service before the actor in dev workspaces', async () => {
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'mx-actor-system-dev-'));
const systemDir = path.join(tmpRoot, 'projects', 'matrix-3', 'packages', 'system');
const hostControlDir = path.join(tmpRoot, 'projects', 'matrix-3', 'packages', 'host-control');
try {
fs.mkdirSync(systemDir, { recursive: true });
fs.writeFileSync(path.join(systemDir, 'package.json'), JSON.stringify({ name: '@open-matrix/system' }), 'utf8');
fs.writeFileSync(path.join(systemDir, 'matrix.json'), JSON.stringify({ name: '@open-matrix/system' }), 'utf8');
fs.mkdirSync(hostControlDir, { recursive: true });
fs.writeFileSync(path.join(hostControlDir, 'package.json'), JSON.stringify({ name: '@open-matrix/host-control' }), 'utf8');
fs.writeFileSync(path.join(hostControlDir, 'matrix.json'), JSON.stringify({ name: '@open-matrix/host-control' }), 'utf8');
const result = await actorCommand('dev-loop-demo', {
cwd: tmpRoot,
outputDir: path.join(tmpRoot, 'dev-loop-demo'),
dev: true,
});
const workspace = JSON.parse(fs.readFileSync(result.workspacePath!, 'utf8'));
assert.equal(workspace.home, '.matrix/home');
assert.deepEqual(workspace.default, ['system', 'host-control', 'dev-loop-demo']);
assert.deepEqual(workspace.services?.system, { path: './projects/matrix-3/packages/system' });
assert.deepEqual(workspace.services?.['host-control'], { path: './projects/matrix-3/packages/host-control' });
assert.deepEqual(workspace.services?.['dev-loop-demo'], { path: './dev-loop-demo' });
} finally {
fs.rmSync(tmpRoot, { recursive: true, force: true });
}
});
});