| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299 |
- import { existsSync } from 'node:fs'
- import { mkdir, rm, writeFile } from 'node:fs/promises'
- import { dirname, join, relative, resolve, sep } from 'node:path'
- import { spawnSync } from 'node:child_process'
- import { fileURLToPath } from 'node:url'
- import {
- collectPackageEntrypoints,
- loadEcosystemReleaseContext
- } from './lib/ecosystem-packages.mjs'
- const scriptDir = dirname(fileURLToPath(import.meta.url))
- const sourceRoot = resolve(scriptDir, '..')
- const args = process.argv.slice(2)
- const pluginEnginePackages = new Set([
- '@file-viewer/pptx',
- '@file-viewer/eda-layout',
- '@file-viewer/eda-orcad',
- '@file-viewer/geometry-engine'
- ])
- const readArg = (name, fallback) => {
- const index = args.indexOf(name)
- return index >= 0 ? args[index + 1] : fallback
- }
- const selectedRenderers = args
- .filter(arg => arg.startsWith('--renderer='))
- .map(arg => arg.slice('--renderer='.length))
- const smokeAllRenderers = args.includes('--all')
- const smokeRoot = resolve(
- sourceRoot,
- readArg(
- '--tmp-dir',
- process.env.FILE_VIEWER_RENDERER_STANDALONE_SMOKE_DIR || '.release/renderer-standalone-smoke'
- )
- )
- const keepTemp = args.includes('--keep-temp')
- const commandTimeoutMs = Number(
- readArg('--timeout-ms', process.env.FILE_VIEWER_RENDERER_STANDALONE_TIMEOUT_MS || '300000')
- )
- const { entries } = await loadEcosystemReleaseContext(sourceRoot)
- const entryByPackageName = new Map(entries.map(entry => [entry.packageName, entry]))
- const entryById = new Map(entries.map(entry => [entry.id, entry]))
- const pluginRendererEntries = entries.filter(
- entry => entry.kind === 'renderer' && !pluginEnginePackages.has(entry.packageName)
- )
- function assert(condition, message) {
- if (!condition) {
- throw new Error(message)
- }
- }
- function run(command, commandArgs, cwd = sourceRoot, options = {}) {
- console.log(`$ ${[command, ...commandArgs].join(' ')}`)
- const result = spawnSync(command, commandArgs, {
- cwd,
- encoding: 'utf8',
- stdio: options.capture ? 'pipe' : 'inherit',
- timeout: options.timeoutMs ?? commandTimeoutMs,
- killSignal: 'SIGTERM',
- env: {
- ...process.env,
- npm_config_loglevel: 'error'
- }
- })
- if (result.error) {
- throw new Error(
- [
- `Command failed in ${cwd}: ${command} ${commandArgs.join(' ')}`,
- result.error.message,
- result.stdout,
- result.stderr
- ].filter(Boolean).join('\n')
- )
- }
- if (result.status !== 0) {
- throw new Error(
- [
- `Command failed in ${cwd}: ${command} ${commandArgs.join(' ')}`,
- result.stdout,
- result.stderr
- ].filter(Boolean).join('\n')
- )
- }
- return result.stdout?.trim() || ''
- }
- async function assertBuiltEntrypoints(entry) {
- for (const entrypoint of collectPackageEntrypoints(entry.packageJson)) {
- const absoluteEntrypoint = join(entry.absoluteDir, entrypoint)
- assert(
- existsSync(absoluteEntrypoint),
- `${entry.packageName} is missing ${entrypoint}. Build it before renderer standalone smoke.`
- )
- }
- }
- function localDependencyNames(packageJson) {
- const blocks = [
- packageJson.dependencies,
- packageJson.devDependencies,
- packageJson.optionalDependencies,
- packageJson.peerDependencies
- ].filter(Boolean)
- const names = new Set()
- for (const block of blocks) {
- for (const name of Object.keys(block)) {
- if (entryByPackageName.has(name)) {
- names.add(name)
- }
- }
- }
- return names
- }
- function collectLocalDependencyClosure(entry, collected = new Set()) {
- for (const dependencyName of localDependencyNames(entry.packageJson)) {
- if (collected.has(dependencyName)) {
- continue
- }
- const dependencyEntry = entryByPackageName.get(dependencyName)
- if (!dependencyEntry) {
- continue
- }
- collected.add(dependencyName)
- collectLocalDependencyClosure(dependencyEntry, collected)
- }
- return collected
- }
- function resolveRendererEntry(idOrName) {
- const normalized = idOrName.startsWith('renderer-') ? idOrName : `renderer-${idOrName}`
- const byId = entryById.get(normalized)
- if (byId?.kind === 'renderer' && !pluginEnginePackages.has(byId.packageName)) {
- return byId
- }
- const byPackageName = entryByPackageName.get(idOrName)
- if (byPackageName?.kind === 'renderer' && !pluginEnginePackages.has(byPackageName.packageName)) {
- return byPackageName
- }
- throw new Error(`Unknown renderer smoke target: ${idOrName}`)
- }
- function toFileSpec(fromDir, targetFile) {
- const relativePath = relative(fromDir, targetFile).split(sep).join('/')
- return `file:${relativePath.startsWith('.') ? relativePath : `./${relativePath}`}`
- }
- const coreEntry = entryByPackageName.get('@file-viewer/core')
- const vitePluginEntry = entryByPackageName.get('@file-viewer/vite-plugin')
- assert(coreEntry, 'Missing @file-viewer/core entry')
- assert(vitePluginEntry, 'Missing @file-viewer/vite-plugin entry')
- const rendererInputs = smokeAllRenderers
- ? pluginRendererEntries.map(entry => entry.renderer.id)
- : (selectedRenderers.length ? selectedRenderers : ['pdf'])
- const rendererEntries = [...new Map(rendererInputs.map(input => {
- const entry = resolveRendererEntry(input)
- return [entry.packageName, entry]
- })).values()]
- assert(rendererEntries.length, 'No renderer smoke targets were selected')
- const requiredEntries = new Map()
- for (const entry of [coreEntry, vitePluginEntry, ...rendererEntries]) {
- requiredEntries.set(entry.packageName, entry)
- collectLocalDependencyClosure(entry).forEach(packageName => {
- const dependencyEntry = entryByPackageName.get(packageName)
- if (dependencyEntry) {
- requiredEntries.set(dependencyEntry.packageName, dependencyEntry)
- }
- })
- }
- const tarballDir = join(smokeRoot, 'tarballs')
- const caseRoot = join(smokeRoot, 'case')
- if (!keepTemp) {
- await rm(smokeRoot, { recursive: true, force: true })
- }
- await mkdir(tarballDir, { recursive: true })
- await mkdir(caseRoot, { recursive: true })
- const tarballByPackageName = new Map()
- for (const entry of requiredEntries.values()) {
- await assertBuiltEntrypoints(entry)
- run('pnpm', ['-C', entry.packageDir, 'pack', '--pack-destination', tarballDir], sourceRoot, {
- capture: true
- })
- const tarball = join(tarballDir, entry.tarballName)
- assert(existsSync(tarball), `Expected tarball was not created: ${tarball}`)
- tarballByPackageName.set(entry.packageName, tarball)
- console.log(`Packed ${entry.packageName} -> ${entry.tarballName}`)
- }
- const dependencies = Object.fromEntries(
- [...requiredEntries.values()].map(entry => [
- entry.packageName,
- toFileSpec(caseRoot, tarballByPackageName.get(entry.packageName))
- ])
- )
- const smokeTargets = rendererEntries.map(entry => ({
- id: entry.renderer.id,
- packageName: entry.packageName
- }))
- await writeFile(
- join(caseRoot, 'package.json'),
- `${JSON.stringify(
- {
- name: 'file-viewer-renderer-standalone-smoke',
- private: true,
- type: 'module',
- scripts: {
- smoke: 'node smoke.mjs'
- },
- dependencies
- },
- null,
- 2
- )}\n`
- )
- await writeFile(
- join(caseRoot, 'smoke.mjs'),
- `import assert from 'node:assert/strict'
- import {
- createRendererRegistry,
- installFileViewerRendererPlugins
- } from '@file-viewer/core'
- import { fileViewerRenderers, resolveFileViewerRendererSelection } from '@file-viewer/vite-plugin'
- ${rendererEntries.map((entry, index) => `import renderer${index} from '${entry.packageName}'`).join('\n')}
- const rendererPlugins = [${rendererEntries.map((_entry, index) => `renderer${index}`).join(', ')}]
- const smokeTargets = ${JSON.stringify(smokeTargets, null, 2)}
- const registry = createRendererRegistry([])
- const handlers = new Map()
- await installFileViewerRendererPlugins({
- registry,
- plugins: rendererPlugins,
- registerHandler(registration) {
- handlers.set(registration.rendererId, registration.handler)
- }
- })
- assert.equal(rendererPlugins.length, smokeTargets.length)
- assert.equal(registry.hasExtension('docx'), smokeTargets.some(target => target.id === 'word'))
- for (const target of smokeTargets) {
- const selection = resolveFileViewerRendererSelection({ renderers: [target.id] }, process.cwd())
- assert.deepEqual(
- selection.renderers.map(item => item.packageName),
- [target.packageName],
- target.id + ' must map to its own standalone renderer package'
- )
- const rendererIds = selection.renderers.flatMap(item => item.rendererIds)
- assert.ok(rendererIds.length, target.id + ' must expose at least one core renderer id')
- for (const rendererId of rendererIds) {
- assert.ok(registry.getById(rendererId), target.packageName + ' must register definition ' + rendererId)
- assert.ok(handlers.has(rendererId), target.packageName + ' must register handler ' + rendererId)
- }
- const vitePlugin = fileViewerRenderers({ renderers: [target.id] })
- await vitePlugin.buildStart?.()
- const resolvedId = vitePlugin.resolveId?.('virtual:file-viewer-renderers')
- assert.equal(typeof resolvedId, 'string', target.id + ' virtual module must resolve')
- const virtualCode = await vitePlugin.load?.(resolvedId)
- assert.ok(
- virtualCode.includes(target.packageName),
- target.id + ' virtual module must import ' + target.packageName
- )
- for (const otherTarget of smokeTargets) {
- if (otherTarget.packageName !== target.packageName) {
- assert.ok(
- !virtualCode.includes(otherTarget.packageName),
- target.id + ' virtual module must not import unrelated package ' + otherTarget.packageName
- )
- }
- }
- }
- console.log('[renderer-standalone-smoke] Renderer plugin installation passed for ' + smokeTargets.map(target => target.packageName).join(', ') + '.')
- `
- )
- run('npm', [
- 'install',
- '--ignore-scripts',
- '--no-audit',
- '--no-fund',
- '--prefer-online',
- '--legacy-peer-deps',
- '--install-strategy=shallow'
- ], caseRoot)
- run('npm', ['run', 'smoke'], caseRoot)
- console.log(
- `[renderer-standalone-smoke] Verified ${rendererEntries.map(entry => entry.packageName).join(', ')} in an isolated install.`
- )
|