Files
openmaic/OpenMAIC/scripts/smoke-test-package-tarballs.mjs
2026-08-16 14:58:47 +08:00

207 lines
8.5 KiB
JavaScript

import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { dirname, join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { INTERNAL_DEPENDENTS, OPENMAIC_PACKAGES, readManifest } from './openmaic-packages.mjs';
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..');
const args = process.argv.slice(2);
if (args[0] === '--') args.shift();
const artifactDirectoryArgument = args[0];
assert(
artifactDirectoryArgument,
'Usage: node smoke-test-package-tarballs.mjs <artifact-directory>',
);
assert.equal(args.length, 1, 'Unexpected command-line arguments');
const artifactDirectory = resolve(root, artifactDirectoryArgument);
const temporaryDirectory = mkdtempSync(join(tmpdir(), 'openmaic-package-smoke-'));
function run(command, args, options = {}) {
return execFileSync(command, args, {
cwd: options.cwd ?? root,
encoding: 'utf8',
stdio: options.stdio ?? 'inherit',
});
}
/** The manifest as the registry will see it, read out of the tarball itself. */
function packedManifest(tarball) {
return JSON.parse(
execFileSync('tar', ['-x', '-z', '-O', '-f', tarball, 'package/package.json'], {
encoding: 'utf8',
}),
);
}
/**
* The dependents declare `@openmaic/dsl` as `workspace:^`, which pnpm publishes
* as `^<dsl version>`. That range is what lets a consumer installing several
* @openmaic packages together resolve ONE copy of the dsl. An exact pin — which
* is what the `workspace:*` form publishes — gives each dependent its own copy,
* and since the dsl carries the schema, the validators and the version
* constants, two copies mean a document produced against one instance can be
* validated by the other's schema revision.
*
* The caret in `dependencies` is only half of it. `peerDependencies` and
* `optionalDependencies` are published constraints too, so an exact entry in
* either would reintroduce the duplicate while `dependencies` still read
* correctly. An owned package must therefore appear exactly once, here.
*
* KNOWN LIMITATION: `^` deduplicates within one 0.x line only. A consumer that
* mixes an older dependent requiring `^0.5.0` with a newer one requiring
* `^0.6.0` still ends up with two dsl copies. Removing that possibility
* entirely would mean making the dsl a peer dependency of all three dependents,
* which changes their public installation contract and is a separate decision.
*/
function assertDeduplicableDslRange(name, manifest, dslVersion, ownedPackages) {
const range = manifest.dependencies?.['@openmaic/dsl'];
assert(
range !== undefined,
`@openmaic/${name} no longer declares @openmaic/dsl; update this check if that is intended`,
);
assert.equal(
range,
`^${dslVersion}`,
`@openmaic/${name} publishes @openmaic/dsl as ${JSON.stringify(range)} rather than ` +
`"^${dslVersion}". An exact pin (what "workspace:*" publishes) forces consumers to ` +
'install a second copy of the dsl alongside its siblings.',
);
for (const field of ['peerDependencies', 'optionalDependencies']) {
const declared = Object.keys(manifest[field] ?? {}).filter((dependency) =>
ownedPackages.has(dependency),
);
assert.deepEqual(
declared,
[],
`@openmaic/${name} publishes ${field} entries for owned packages ` +
`(${declared.join(', ')}). Those are published constraints as much as ` +
'`dependencies` is, so an exact one there pins the dsl regardless of the caret above.',
);
}
console.log(`@openmaic/${name} publishes @openmaic/dsl as ${range}, and nowhere else.`);
}
try {
const packageNames = OPENMAIC_PACKAGES;
const localPackages = new Set(packageNames.map((name) => `@openmaic/${name}`));
const tarballs = Object.fromEntries(
packageNames.map((name) => {
const { version } = readManifest(name);
return [name, join(artifactDirectory, `openmaic-${name}-${version}.tgz`)];
}),
);
const packedManifests = Object.fromEntries(
packageNames.map((name) => [name, packedManifest(tarballs[name])]),
);
// The smoke program executes the renderer root, whose optional chart and
// highlighting peers are imported by that entry. Other optional peers remain
// optional unless the smoke program starts executing their owning entry.
const installOptionalPeersFor = new Set(['renderer']);
const peerDependencies = {};
for (const name of packageNames) {
const manifest = packedManifests[name];
for (const [peer, range] of Object.entries(manifest.peerDependencies ?? {})) {
if (localPackages.has(peer)) continue;
const isOptional = manifest.peerDependenciesMeta?.[peer]?.optional === true;
if (isOptional && !installOptionalPeersFor.has(name)) continue;
const existing = peerDependencies[peer];
// Keep peer declarations aligned across the package family. Attempting to
// synthesize an intersection here is unsafe for ranges containing `||`.
assert(
existing === undefined || existing === range,
`@openmaic packages declare different ${peer} peer ranges: ${existing} and ${range}`,
);
peerDependencies[peer] = range;
}
}
const dslVersion = packedManifests.dsl.version;
for (const name of Object.keys(INTERNAL_DEPENDENTS)) {
assertDeduplicableDslRange(name, packedManifests[name], dslVersion, localPackages);
}
const consumerDirectory = join(temporaryDirectory, 'consumer');
mkdirSync(consumerDirectory);
writeFileSync(
join(consumerDirectory, 'package.json'),
JSON.stringify(
{
private: true,
type: 'module',
dependencies: {
...peerDependencies,
...Object.fromEntries(
packageNames.map((name) => [`@openmaic/${name}`, `file:${tarballs[name]}`]),
),
},
},
null,
2,
),
);
run('npm', ['install', '--ignore-scripts', '--no-audit', '--no-fund'], {
cwd: consumerDirectory,
});
writeFileSync(
join(consumerDirectory, 'smoke.mjs'),
`import assert from 'node:assert/strict';
import { stat } from 'node:fs/promises';
import { createRequire } from 'node:module';
import { fileURLToPath } from 'node:url';
import { RUNTIME_DSL_VERSION, validateRuntimeSession } from '@openmaic/dsl';
import { PROMPT_IDS, buildPrompt } from '@openmaic/generation';
import { DOCUMENT_PG_SCHEMA } from '@openmaic/storage';
import { SlideCanvas } from '@openmaic/renderer';
import { createEditorTransaction as createEditorTransactionFromRoot } from '@openmaic/editor';
import { createEditorTransaction } from '@openmaic/editor/core';
import { EMPTY_SELECTION } from '@openmaic/editor/react';
import { EditableSlideCanvasWithUI } from '@openmaic/editor/ui';
assert.equal(typeof RUNTIME_DSL_VERSION, 'string');
assert.equal(typeof validateRuntimeSession, 'function');
assert.match(DOCUMENT_PG_SCHEMA, /CREATE TABLE IF NOT EXISTS document_stages/);
assert.equal(typeof SlideCanvas, 'function');
assert.equal(typeof createEditorTransactionFromRoot, 'function');
assert.equal(typeof createEditorTransaction, 'function');
assert.equal(typeof EMPTY_SELECTION, 'object');
assert.equal(typeof EditableSlideCanvasWithUI, 'function');
// requirements-to-outlines references three snippets (inside media conditionals),
// so this asserts the packaged snippets/ directory is present and resolvable, not
// just templates/: snippet inlining runs before conditional pruning, so a missing
// snippets/ directory throws here even for pruned blocks.
const generationPrompt = buildPrompt(PROMPT_IDS.REQUIREMENTS_TO_OUTLINES, { mediaEnabled: true });
assert(generationPrompt);
assert(generationPrompt.system.length > 100);
assert.match(generationPrompt.system, /Content Safety Guidelines for Generation Prompts/);
assert.doesNotMatch(generationPrompt.system, /\{\{snippet:/);
assert.doesNotMatch(generationPrompt.user, /\{\{snippet:/);
const importerEntry = fileURLToPath(import.meta.resolve('@openmaic/importer'));
assert((await stat(importerEntry)).isFile());
const importerRequireEntry = createRequire(import.meta.url).resolve('@openmaic/importer');
assert((await stat(importerRequireEntry)).isFile());
for (const subpath of [
'runtime/http',
'document/http',
'document/pg',
'runtime/pg',
'server',
'server/reference',
]) {
await import(\`@openmaic/storage/\${subpath}\`);
}
`,
);
run('node', ['smoke.mjs'], { cwd: consumerDirectory });
console.log('Validated @openmaic package tarball imports passed.');
} finally {
rmSync(temporaryDirectory, { recursive: true, force: true });
}