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 ', ); 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 `^`. 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 }); }