/** * Verify that the Python single-exe deploy manifest explicitly supplies every * required workspace peer of every workspace package in its dependency graph. * * `pnpm deploy --config.auto-install-peers=false` cannot repair an incomplete * runtime root. Keeping the peer at the root also prevents a successful build * from producing an executable that fails only when Cordis loads the plugin. */ import { readFile, readdir } from 'node:fs/promises' import { join, resolve } from 'node:path' import { parseArgs } from 'node:util' interface PackageManifest { name?: string dependencies?: Record optionalDependencies?: Record peerDependencies?: Record peerDependenciesMeta?: Record } interface WorkspacePackage { path: string manifest: PackageManifest } const root = resolve(import.meta.dirname, '..') const { values } = parseArgs({ args: process.argv.slice(2), options: { manifest: { type: 'string' } }, }) const runtimeManifestPath = resolve(root, values.manifest ?? 'python/sdk-runtime/package.json') const runtimeManifest = await loadManifest(runtimeManifestPath) const runtimeName = runtimeManifest.name ?? 'python/sdk-runtime' const workspace = await loadWorkspacePackages() const runtimeDependencies = runtimeManifest.dependencies ?? {} const parents = new Map() const queue: string[] = [] for (const dependency of Object.keys(runtimeDependencies).sort()) { if (!workspace.has(dependency)) continue parents.set(dependency, undefined) queue.push(dependency) } const failures: string[] = [] for (let index = 0; index < queue.length; index += 1) { const packageName = queue[index] if (packageName === undefined) continue const current = workspace.get(packageName) if (current === undefined) continue const peers = current.manifest.peerDependencies ?? {} const peerMeta = current.manifest.peerDependenciesMeta ?? {} for (const peer of Object.keys(peers).sort()) { if (!workspace.has(peer) || peerMeta[peer]?.optional === true) continue if (runtimeDependencies[peer]?.startsWith('workspace:') === true) continue failures.push(`${formatChain(runtimeName, packageName, parents)} -> ${peer}`) } const dependencies = { ...current.manifest.dependencies, ...current.manifest.optionalDependencies, } for (const dependency of Object.keys(dependencies).sort()) { if (!workspace.has(dependency) || parents.has(dependency)) continue parents.set(dependency, packageName) queue.push(dependency) } } if (failures.length > 0) { console.error('verify-runtime-closure: required workspace peers are missing from python/sdk-runtime dependencies:') for (const failure of failures) console.error(` ${failure}`) process.exit(1) } console.log(`verify-runtime-closure: ${queue.length} workspace packages form a closed runtime dependency graph.`) async function loadWorkspacePackages(): Promise> { const paths: string[] = [] for (const group of await childDirectories(join(root, 'packages'))) { for (const packageDir of await childDirectories(join(root, 'packages', group))) { paths.push(join(root, 'packages', group, packageDir, 'package.json')) } } for (const packageDir of await childDirectories(join(root, 'vendor'))) { paths.push(join(root, 'vendor', packageDir, 'package.json')) } const result = new Map() for (const path of paths) { const manifest = await loadManifest(path) if (manifest.name !== undefined) result.set(manifest.name, { path, manifest }) } return result } async function childDirectories(path: string): Promise { const entries = await readdir(path, { withFileTypes: true }) return entries.filter(entry => entry.isDirectory()).map(entry => entry.name).sort() } async function loadManifest(path: string): Promise { return JSON.parse(await readFile(path, 'utf8')) as PackageManifest } function formatChain( runtimeName: string, packageName: string, parents: ReadonlyMap, ): string { const chain = [packageName] let parent = parents.get(packageName) while (parent !== undefined) { chain.unshift(parent) parent = parents.get(parent) } return [runtimeName, ...chain].join(' -> ') }