| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253 |
- import { dirname, resolve } from 'node:path'
- import { fileURLToPath } from 'node:url'
- import { loadEcosystemReleaseContext } from './lib/ecosystem-packages.mjs'
- import { rendererModularizationLines } from './renderer-dependency-plan.mjs'
- const scriptDir = dirname(fileURLToPath(import.meta.url))
- const sourceRoot = resolve(scriptDir, '..')
- const { entries } = await loadEcosystemReleaseContext(sourceRoot)
- const entriesByPackageName = new Map(entries.map(entry => [entry.packageName, entry]))
- const rendererEntries = entries.filter(entry => entry.kind === 'renderer')
- const wrapperEntries = entries.filter(entry => entry.kind === 'standard-wrapper')
- const compatibilityEntries = entries.filter(entry => entry.kind === 'compatibility')
- const presetEntries = entries.filter(entry => entry.kind === 'preset')
- const coreEntry = entries.find(entry => entry.kind === 'core')
- const rendererPackageNames = new Set(rendererEntries.map(entry => entry.packageName))
- const wrapperPackageNames = new Set(wrapperEntries.map(entry => entry.packageName))
- const compatibilityPackageNames = new Set(compatibilityEntries.map(entry => entry.packageName))
- const presetPackageNames = new Set(presetEntries.map(entry => entry.packageName))
- const highLevelPackageNames = new Set([
- ...rendererPackageNames,
- ...wrapperPackageNames,
- ...compatibilityPackageNames,
- ...presetPackageNames
- ])
- const pluginEnginePackageNames = new Set([
- '@file-viewer/pptx',
- '@file-viewer/eda-layout',
- '@file-viewer/eda-orcad',
- '@file-viewer/geometry-engine'
- ])
- const pluginRendererPackageNames = new Set(
- rendererEntries
- .map(entry => entry.packageName)
- .filter(packageName => !pluginEnginePackageNames.has(packageName))
- )
- const rendererPackageNamesByRendererId = new Map(
- rendererEntries.map(entry => [entry.renderer?.id, entry.packageName])
- )
- const presetRendererAllowList = {
- lite: ['text', 'image', 'media'],
- office: ['pdf', 'word', 'spreadsheet', 'presentation', 'ofd'],
- engineering: ['cad', 'model', 'drawing', 'mindmap', 'geo', 'typst', 'archive', 'data', 'eda']
- }
- function assert(condition, message) {
- if (!condition) {
- throw new Error(message)
- }
- }
- function dependencyNames(packageJson) {
- return {
- dependencies: Object.keys(packageJson.dependencies || {}),
- optionalDependencies: Object.keys(packageJson.optionalDependencies || {}),
- peerDependencies: Object.keys(packageJson.peerDependencies || {})
- }
- }
- function assertNoPackages(entry, packageNames, message) {
- for (const [field, names] of Object.entries(dependencyNames(entry.packageJson))) {
- const blocked = names.filter(name => packageNames.has(name))
- assert(
- blocked.length === 0,
- `${entry.packageName} ${field} must not include ${message}: ${blocked.join(', ')}`
- )
- }
- }
- function assertOnlyAllowedWorkspaceDeps(entry, allowedPackageNames, reason) {
- for (const [field, names] of Object.entries(dependencyNames(entry.packageJson))) {
- const blocked = names.filter(name => highLevelPackageNames.has(name) && !allowedPackageNames.has(name))
- assert(
- blocked.length === 0,
- `${entry.packageName} ${field} has invalid File Viewer dependency for ${reason}: ${blocked.join(', ')}`
- )
- }
- }
- assert(coreEntry, 'Missing @file-viewer/core ecosystem entry')
- assertNoPackages(
- coreEntry,
- highLevelPackageNames,
- 'renderer, preset, wrapper, or compatibility packages'
- )
- for (const wrapperEntry of wrapperEntries) {
- assertOnlyAllowedWorkspaceDeps(
- wrapperEntry,
- new Set(['@file-viewer/core']),
- 'standard component packages'
- )
- }
- for (const compatibilityEntry of compatibilityEntries) {
- const targetPackage = compatibilityEntry.compatibilityPackage?.targetPackage
- assert(targetPackage, `${compatibilityEntry.packageName} must declare a compatibility targetPackage`)
- assert(
- entriesByPackageName.has(targetPackage),
- `${compatibilityEntry.packageName} target package ${targetPackage} is not part of the ecosystem manifest`
- )
- assertOnlyAllowedWorkspaceDeps(
- compatibilityEntry,
- new Set([targetPackage]),
- 'compatibility alias packages'
- )
- assert(
- compatibilityEntry.packageJson.dependencies?.[targetPackage],
- `${compatibilityEntry.packageName} must depend on its target package ${targetPackage}`
- )
- }
- for (const presetEntry of presetEntries) {
- const dependencies = new Set(Object.keys(presetEntry.packageJson.dependencies || {}))
- if (presetEntry.packageName === '@file-viewer/preset-all') {
- assert(
- dependencies.has('@file-viewer/core'),
- '@file-viewer/preset-all must depend on @file-viewer/core'
- )
- for (const rendererPackageName of pluginRendererPackageNames) {
- assert(
- dependencies.has(rendererPackageName),
- `@file-viewer/preset-all must aggregate ${rendererPackageName}`
- )
- }
- assertNoPackages(
- presetEntry,
- new Set([...wrapperPackageNames, ...compatibilityPackageNames]),
- 'wrapper or compatibility packages'
- )
- } else if (presetRendererAllowList[presetEntry.preset.id]) {
- const allowedRendererPackages = new Set(
- presetRendererAllowList[presetEntry.preset.id].map(rendererId => {
- const packageName = rendererPackageNamesByRendererId.get(rendererId)
- assert(
- packageName,
- `${presetEntry.packageName} allow-list renderer ${rendererId} is not in ecosystem/wrappers.json`
- )
- return packageName
- })
- )
- assert(
- dependencies.has('@file-viewer/core'),
- `${presetEntry.packageName} must depend on @file-viewer/core`
- )
- for (const rendererPackageName of allowedRendererPackages) {
- assert(
- dependencies.has(rendererPackageName),
- `${presetEntry.packageName} must aggregate ${rendererPackageName}`
- )
- }
- assertOnlyAllowedWorkspaceDeps(
- presetEntry,
- new Set(['@file-viewer/core', ...allowedRendererPackages]),
- `${presetEntry.preset.id} preset packages`
- )
- assertNoPackages(
- presetEntry,
- new Set([...wrapperPackageNames, ...compatibilityPackageNames]),
- 'wrapper or compatibility packages'
- )
- } else {
- assertOnlyAllowedWorkspaceDeps(
- presetEntry,
- new Set(['@file-viewer/core']),
- 'automation/plugin packages'
- )
- }
- }
- for (const rendererEntry of rendererEntries) {
- const allowedPackages = new Set(['@file-viewer/core'])
- for (const line of rendererModularizationLines) {
- if (line.targetPackage !== rendererEntry.packageName) {
- continue
- }
- for (const dependency of line.dependencies) {
- if (pluginEnginePackageNames.has(dependency)) {
- allowedPackages.add(dependency)
- }
- }
- }
- assertOnlyAllowedWorkspaceDeps(rendererEntry, allowedPackages, 'renderer packages')
- }
- const coreDependencies = new Set(Object.keys(coreEntry.packageJson.dependencies || {}))
- const linesByDependency = new Map()
- for (const line of rendererModularizationLines) {
- for (const dependency of line.dependencies) {
- const lines = linesByDependency.get(dependency) || []
- lines.push(line)
- linesByDependency.set(dependency, lines)
- }
- }
- for (const line of rendererModularizationLines) {
- if (line.status === 'retained') {
- for (const dependency of line.dependencies) {
- assert(
- coreDependencies.has(dependency),
- `${dependency} is marked retained in core but is not declared by @file-viewer/core`
- )
- }
- continue
- }
- if (line.status !== 'planned') {
- assert(
- entriesByPackageName.has(line.targetPackage),
- `${line.id} target package ${line.targetPackage} is missing from ecosystem/wrappers.json`
- )
- }
- if (line.status === 'extracted') {
- const leakedDependencies = line.dependencies.filter(dependency => {
- if (!coreDependencies.has(dependency)) {
- return false
- }
- const owners = linesByDependency.get(dependency) || []
- return owners.every(owner => owner.status === 'extracted')
- })
- assert(
- leakedDependencies.length === 0,
- `${line.id} is extracted but @file-viewer/core still depends on ${leakedDependencies.join(', ')}`
- )
- }
- }
- const linesByTargetPackage = new Map()
- for (const line of rendererModularizationLines) {
- if (line.targetPackage === '@file-viewer/core') {
- continue
- }
- const lines = linesByTargetPackage.get(line.targetPackage) || []
- lines.push(line)
- linesByTargetPackage.set(line.targetPackage, lines)
- }
- for (const rendererEntry of rendererEntries) {
- if (pluginEnginePackageNames.has(rendererEntry.packageName)) {
- continue
- }
- assert(
- linesByTargetPackage.has(rendererEntry.packageName),
- `${rendererEntry.packageName} is a renderer package but has no renderer-dependency-plan entry`
- )
- }
- console.log(
- `[on-demand-boundaries] Verified ${wrapperEntries.length} wrappers, ${compatibilityEntries.length} compatibility aliases, ${presetEntries.length} presets, ${rendererEntries.length} renderer packages, and ${rendererModularizationLines.length} renderer dependency lines.`
- )
|