openpencil/tools/package-quality/src/smoke.ts
Danila Poyarkov 2bd2304bcb refactor(fig): own instance interpretation
- Move component properties, symbol overrides, derived symbol data, and clone synchronization into @open-pencil/fig\n- Add the public instance-overrides subpath with package tests and packed smoke coverage\n- Remove cross-package forwarding modules and enforce direct owner imports in architecture checks
2026-07-18 02:06:34 +03:00

133 lines
6.8 KiB
TypeScript

import { mkdtempSync, rmSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { basename, join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { publicPackageDirs } from './packages'
const rootDir = fileURLToPath(new URL('../../..', import.meta.url))
function run(command: string[], cwd = rootDir): string {
const proc = Bun.spawnSync(command, { cwd, stdout: 'pipe', stderr: 'pipe' })
const stdout = proc.stdout.toString()
const stderr = proc.stderr.toString()
if (!proc.success) {
console.error(`$ ${command.join(' ')}`)
if (stdout) console.error(stdout)
if (stderr) console.error(stderr)
process.exit(proc.exitCode || 1)
}
return stdout.trim()
}
function nodeEval(code: string, cwd: string): void {
run(['node', '--input-type=module', '--eval', code], cwd)
}
const tempDir = mkdtempSync(join(tmpdir(), 'open-pencil-package-smoke-'))
try {
run(['bun', 'run', 'build:packages'])
const tarballs: string[] = []
for (const packageDir of publicPackageDirs) {
const output = run(
['bun', 'pm', 'pack', '--destination', tempDir, '--quiet'],
join(rootDir, packageDir)
)
const filename = output
.split('\n')
.map((line) => line.trim())
.findLast((line) => line.length > 0)
if (!filename) throw new Error(`No tarball produced for ${packageDir}`)
const tarball = filename.startsWith('/') ? filename : join(tempDir, filename)
tarballs.push(tarball)
const contents = run(['tar', '-tf', tarball])
const runtimeTs = contents
.split('\n')
.filter((entry) => /package\/src\/.*\.ts$/.test(entry) && !entry.endsWith('.d.ts'))
if (runtimeTs.length > 0) {
console.error(`${basename(tarball)} includes runtime TypeScript:\n${runtimeTs.join('\n')}`)
process.exit(1)
}
}
run(['npm', 'init', '-y'], tempDir)
run(['npm', 'install', '--ignore-scripts', '--no-audit', '--no-fund', ...tarballs], tempDir)
nodeEval("await import('@open-pencil/kiwi')", tempDir)
nodeEval("await import('@open-pencil/kiwi/schema-runtime')", tempDir)
nodeEval("await import('@open-pencil/kiwi/fig')", tempDir)
nodeEval("await import('@open-pencil/kiwi/fig/codec')", tempDir)
nodeEval("await import('@open-pencil/kiwi/fig/container')", tempDir)
nodeEval("await import('@open-pencil/kiwi/fig/guid')", tempDir)
nodeEval("await import('@open-pencil/kiwi/fig/parse')", tempDir)
nodeEval("await import('@open-pencil/fig')", tempDir)
nodeEval("await import('@open-pencil/scene-graph')", tempDir)
nodeEval("await import('@open-pencil/scene-graph/copy')", tempDir)
nodeEval("await import('@open-pencil/scene-graph/coordinate')", tempDir)
nodeEval("await import('@open-pencil/scene-graph/geometry')", tempDir)
nodeEval("await import('@open-pencil/scene-graph/images')", tempDir)
nodeEval("await import('@open-pencil/scene-graph/matrix')", tempDir)
nodeEval("await import('@open-pencil/scene-graph/parse-path')", tempDir)
nodeEval("await import('@open-pencil/scene-graph/primitives')", tempDir)
nodeEval("await import('@open-pencil/pen')", tempDir)
nodeEval("await import('@open-pencil/core')", tempDir)
nodeEval("await import('@open-pencil/dom-css')", tempDir)
nodeEval("await import('@open-pencil/dom-css/browser')", tempDir)
nodeEval("await import('@open-pencil/dom-css/jsx-runtime')", tempDir)
nodeEval("await import('@open-pencil/dom-css/jsx-dev-runtime')", tempDir)
nodeEval("await import('@open-pencil/vue')", tempDir)
nodeEval("await import('@open-pencil/mcp')", tempDir)
nodeEval(
"const { guidToString } = await import('@open-pencil/kiwi/fig/guid'); if (guidToString({ sessionID: 1, localID: 2 }) !== '1:2') throw new Error('Kiwi GUID subpath failed')",
tempDir
)
nodeEval(
"const { buildFigKiwi, parseFigKiwiChunks } = await import('@open-pencil/kiwi/fig/container'); const chunks = parseFigKiwiChunks(buildFigKiwi(new Uint8Array([1]), new Uint8Array([2]))); if (chunks?.length !== 2) throw new Error('Kiwi container subpath failed')",
tempDir
)
nodeEval(
"const { FIG_PACKAGE_STATUS, parseFigBuffer, writeFigArchive, readFigContainer, writeFigContainer } = await import('@open-pencil/fig'); if (FIG_PACKAGE_STATUS !== 'archive-api' || typeof parseFigBuffer !== 'function' || typeof writeFigArchive !== 'function') throw new Error('Fig package status smoke failed'); const document = readFigContainer(writeFigContainer({ schemaDeflated: new Uint8Array([1]), dataRaw: new Uint8Array([2]) })); if (document.dataRaw[0] !== 2) throw new Error('Fig container smoke failed')",
tempDir
)
nodeEval(
"const { convertLineHeight } = await import('@open-pencil/fig/node-change'); if (convertLineHeight({ value: 120, units: 'PERCENT' }, 20) !== 24) throw new Error('Fig NodeChange subpath failed')",
tempDir
)
nodeEval(
"const { populateAndApplyOverrides } = await import('@open-pencil/fig/instance-overrides'); if (typeof populateAndApplyOverrides !== 'function') throw new Error('Fig instance override subpath failed')",
tempDir
)
nodeEval(
"const { SceneGraph } = await import('@open-pencil/scene-graph'); const graph = new SceneGraph(); if (graph.getPages().length !== 1) throw new Error('SceneGraph package smoke failed')",
tempDir
)
nodeEval(
"const { parsePenFile } = await import('@open-pencil/pen'); const graph = parsePenFile(JSON.stringify({ version: '1', children: [{ id: 'frame', type: 'frame', width: 100, height: 50 }] })); if (graph.getPages()[0].childIds.length !== 1) throw new Error('Pen package smoke failed')",
tempDir
)
nodeEval(
"const { htmlToSceneGraph } = await import('@open-pencil/dom-css'); const graph = await htmlToSceneGraph('<div class=card>OpenPencil</div>', { cssText: '.card { width: 320px; }' }); if (graph.getPages()[0].width !== 320) throw new Error('DOM/CSS scene graph smoke failed')",
tempDir
)
nodeEval(
"const browser = await import('@open-pencil/dom-css/browser'); for (const key of ['browserHTMLToDesignDocument', 'browserHTMLToSceneGraph', 'browserTailwindJSXToSceneGraph']) if (typeof browser[key] !== 'function') throw new Error('DOM/CSS browser export missing: ' + key)",
tempDir
)
nodeEval(
"const { jsx, jsxToDesignDocument } = await import('@open-pencil/dom-css/jsx-runtime'); const document = await jsxToDesignDocument(jsx('section', { class: 'card', style: { width: '120px' }, children: 'OpenPencil' })); const node = document.children[0]; if (node?.type !== 'element' || node.inlineStyle?.width !== '120px') throw new Error('DOM/CSS JSX runtime smoke failed')",
tempDir
)
run(['node', 'node_modules/.bin/openpencil', '--help'], tempDir)
run(['node', 'node_modules/.bin/openpencil-mcp', '--help'], tempDir)
run(['node', 'node_modules/.bin/openpencil-mcp-http', '--help'], tempDir)
console.log('Packed package smoke tests passed.')
} finally {
rmSync(tempDir, { recursive: true, force: true })
}