/** * Workspace package invariant checks for package-manager-independent quality * gates. * * Run: `tsx scripts/check-workspace-constraints.ts`. */ import { existsSync, readdirSync, readFileSync } from 'node:fs' import { join, relative, resolve } from 'node:path' import { hasTypertRemoteNavigation, isForbiddenPublicationFile } from './publication-payload.ts' import { collectProjectReferenceFaceViolations } from './project-reference-faces.ts' const root = resolve(import.meta.dirname, '..') // vendor/* is single-level; packages// nests one level deeper // (the group dirs — core/llm/shell/… — are pure containers with no manifest). const workspaceGlobs = [ { dir: 'vendor', depth: 1 }, { dir: 'packages', depth: 2 }, { dir: 'native', depth: 1 }, { dir: 'native/landlock-run/packages', depth: 1 }, { dir: 'apps', depth: 1 }, ] as const const vendoredPackages = new Set([ '@deepseek-ai/cordis', '@deepseek-ai/cosmokit', '@deepseek-ai/schemastery', '@deepseek-ai/cordis-plugin-loader', '@deepseek-ai/cordis-plugin-include', '@deepseek-ai/cordis-plugin-group', '@deepseek-ai/cordis-plugin-timer', '@deepseek-ai/cordis-plugin-hmr', '@deepseek-ai/cordis-plugin-logger-console', ]) const publicLandlockPackages = new Set([ '@deepseek-ai/node-addon-landlock-run', '@deepseek-ai/node-addon-landlock-run-linux-arm64', '@deepseek-ai/node-addon-landlock-run-linux-x64', ]) /** Deliberate source payloads whose exact bytes are part of the package's audit surface. */ const publicationSourceAllowlist: Readonly> = { '@deepseek-ai/node-addon-landlock-run': ['src/main.c'], } const repositoryUrl = 'git+https://github.com/deepseek-harness/deepseek-harness.git' /** * Source home the published packages point consumers at. It differs from * {@link repositoryUrl}, which the Landlock packages keep because npm resolves * their trusted publishing against the repository that runs the workflow. */ const publishedRepositoryUrl = 'git+https://github.com/deepseek-ai/deepseek-harness.git' /** Directories whose packages this repository publishes: one release member each. */ const releaseMemberDirectory = /^(?:packages\/[^/]+\/[^/]+|apps\/[^/]+|vendor\/[^/]+)$/ const localArtifactDirs = new Set(['node_modules']) const appPackageFiles: Readonly> = { '@deepseek-ai/dsh': ['lib/*.js', 'config'], // The Web build emits sourcemaps for browser debugging; publishing them is // what the payload policy forbids, so the bundle ships without them. '@deepseek-ai/dsh-web-frontend': ['dist', '!dist/**/*.map'], } /** The subset of package.json fields this constraint check cares about. */ interface PackageManifest { name?: string version?: string private?: boolean type?: string main?: string types?: string bin?: string | Record exports?: Record< string, | string | { types?: string default?: string } | null | undefined > files?: string[] publishConfig?: { access?: string } repository?: { type?: string; url?: string; directory?: string } peerDependencies?: Record devDependencies?: Record dependencies?: Record optionalDependencies?: Record } /** One workspace manifest and its repo-relative path. */ interface WorkspaceManifest { dir: string manifest: PackageManifest } function readJson(path: string): PackageManifest { return JSON.parse(readFileSync(path, 'utf8')) as PackageManifest } const rootManifest = readJson(join(root, 'package.json')) const repositoryVersion = rootManifest.version const landlockWorkspaceManifest = readJson(join(root, 'native/landlock-run/package.json')) const landlockVersion = landlockWorkspaceManifest.version /** Repo-relative dirs holding a package.json, walked to the configured depth. */ function packageDirs(base: string, depth: number): string[] { if (depth === 1) { return readdirSync(join(root, base), { withFileTypes: true }) .filter(entry => entry.isDirectory()) .filter(entry => !localArtifactDirs.has(entry.name)) .filter(entry => existsSync(join(root, base, entry.name, 'package.json'))) .map(entry => `${base}/${entry.name}`) } return readdirSync(join(root, base), { withFileTypes: true }) .filter(entry => entry.isDirectory()) .filter(entry => !localArtifactDirs.has(entry.name)) .flatMap(group => packageDirs(`${base}/${group.name}`, depth - 1)) } function workspaceManifests(): WorkspaceManifest[] { const manifests: WorkspaceManifest[] = [ { dir: '.', manifest: rootManifest }, ] for (const { dir: base, depth } of workspaceGlobs) { for (const dir of packageDirs(base, depth)) { manifests.push({ dir, manifest: readJson(join(root, dir, 'package.json')) }) } } return manifests } const packageFileExtras: Readonly> = { // Profile bundles publish their dsh.bundle.patch layer beside the lib. '@deepseek-ai/dsh-base': ['cordis.patch.yml'], '@deepseek-ai/dsh-web-app': ['cordis.patch.yml'], '@deepseek-ai/dsh-headless': ['cordis.patch.yml'], '@deepseek-ai/dsh-client-ui-theme': ['lib/styles'], // The Python runtime uses a distinct closed-resolution bin; the public CLI // keeps config-owned bare-package resolution through lib/bin.js. '@deepseek-ai/dsh-sdk-jsonrpc-demo': ['lib/packaged-bin.js'], // The argv-prefix runner entry ships beside the lib as its own bundle; // sandbox-local resolves it through the package's ./runner export. tsdown // also shares its generated FFI code through a hashed runtime chunk. '@deepseek-ai/dsh-sandbox-windows-acl': ['lib/runner.js', 'lib/types-*.js'], '@deepseek-ai/dsh-skill-badge': ['assets'], '@deepseek-ai/dsh-subprocess-local': ['scripts/ensure-spawn-helper.mjs'], } function sameStringList(actual: readonly string[] | undefined, expected: readonly string[]): boolean { return !!actual && actual.length === expected.length && actual.every((value, index) => value === expected[index]) } function expectedDshPackageFiles(manifest: PackageManifest): readonly string[] { const extras = manifest.name ? packageFileExtras[manifest.name] ?? [] : [] return [ 'lib/index.js', // Every package publishes its invariant ownership companion as a separate // bundle; the package-invariant gate validates the companion itself. 'lib/invariant.js', ...manifest.bin ? ['lib/bin.js'] : [], ...manifest.exports?.['./worker'] ? ['lib/worker.cjs'] : [], // UI plugin packages ship their browser bundle beside the node lib // (single-artifact ruling: dist/ retired, ./client resolves lib/client.js). // Keyed on the artifact path, not the subpath name: apiproxy's ./client is // a browser-safe source channel, not a bundle. ...exportDefault(manifest, './client') === './lib/client.js' ? ['lib/client.js'] : [], // runtime's shell-held loader subpath ships as its own bundle beside the client half. ...exportDefault(manifest, './loader') === './lib/loader.js' ? ['lib/loader.js'] : [], // web-react's store subpath ships its own bundle (single-entry builds; no shared chunk). ...exportDefault(manifest, './store') === './lib/store/index.js' ? ['lib/store/index.js'] : [], // A surface bundle's startup row is its own bundle: the Loader imports it // as a row module, so it cannot ride inside the package entry. ...exportDefault(manifest, './startup') === './lib/startup.js' ? ['lib/startup.js'] : [], ...extras, // Subpaths whose runtime default is the tsc-emitted tree (lib/types/*.js — // browser-safe source channels rehomed off src so plain Node can import // them without type stripping) publish the emitted JS alongside the // declarations. ...usesEmittedTreeDefaults(manifest) ? ['lib/types/**/*.js'] : [], 'lib/types/**/*.d.ts', ...hasExportPair(manifest, './typert', './lib/typert.host.d.ts', './lib/typert.host.js') ? ['lib/typert.host.js', 'lib/typert.host.d.ts'] : [], ...hasExportPair(manifest, './client/typert', './lib/typert.client.d.ts', './lib/typert.client.js') ? ['lib/typert.client.js', 'lib/typert.client.d.ts'] : [], ...hasTypertRemoteNavigation(manifest) ? ['lib/typert.remote-client.js', 'lib/typert.remote-client.d.ts'] : [], ] } /** Whether one conditional export exactly names the generated runtime and declaration pair. */ function hasExportPair( manifest: PackageManifest, subpath: string, types: string, runtime: string, ): boolean { const entry = manifest.exports?.[subpath] return typeof entry === 'object' && entry !== null && entry.types === types && entry.default === runtime } /** Runtime target of an export entry: conditional `default`, or the bare-string shorthand. */ function exportDefault(manifest: PackageManifest, subpath: string): string | undefined { const entry = manifest.exports?.[subpath] if (typeof entry === 'string') return entry if (typeof entry === 'object' && entry !== null) return entry.default return undefined } /** Whether any export's runtime default points into the tsc-emitted lib/types tree. */ function usesEmittedTreeDefaults(manifest: PackageManifest): boolean { return Object.keys(manifest.exports ?? {}).some(subpath => exportDefault(manifest, subpath)?.startsWith('./lib/types/') === true) } function checkWorkspace({ dir, manifest }: WorkspaceManifest): string[] { const errors: string[] = [] const label = manifest.name ?? dir const isLandlockPackageDir = dir.startsWith('native/landlock-run/packages/') const isPublicLandlockPackage = isLandlockPackageDir && manifest.name !== undefined && publicLandlockPackages.has(manifest.name) if (isPublicLandlockPackage) { if (manifest.private === true) { errors.push(`${label}: published Landlock package must not set "private": true`) } if (manifest.publishConfig?.access !== 'public') { errors.push(`${label}: published Landlock package must set publishConfig.access to "public"`) } const expectedDirectory = dir if (manifest.repository?.type !== 'git' || manifest.repository.url !== repositoryUrl || manifest.repository.directory !== expectedDirectory) { errors.push(`${label}: published Landlock package repository must use ${repositoryUrl} with directory ${expectedDirectory} for trusted publishing`) } } else if (releaseMemberDirectory.test(dir)) { // Release members state that they are publishable: npm refuses a private // package, and the repository field is how a consumer finds the source of // the package it installed. // // Access is per release sequence, not per scope: the vendored framework and // the Landlock packages publish publicly because outside consumers install // them, while the dsh family stays restricted until its own sequence goes // public. A mixed scope is why no publish path passes `--access` — one flag // cannot serve both, so each packed manifest decides // ([rationale](../.agents/notes/implemented/process/2026-08-13-public-vendor-and-native-sequences.md)). const expectedAccess = dir.startsWith('vendor/') ? 'public' : 'restricted' if (manifest.private === true) { errors.push(`${label}: release member must not set "private": true`) } if (manifest.publishConfig?.access !== expectedAccess) { errors.push(`${label}: release member must set publishConfig.access to "${expectedAccess}"`) } if (manifest.repository?.type !== 'git' || manifest.repository.url !== publishedRepositoryUrl || manifest.repository.directory !== dir) { errors.push(`${label}: release member repository must use ${publishedRepositoryUrl} with directory ${dir}`) } } else if (manifest.private !== true) { errors.push(`${label}: package.json must set "private": true`) } if (manifest.name && vendoredPackages.has(manifest.name)) { return errors } if (manifest.name?.startsWith('@deepseek-ai/')) { const allowedSources = publicationSourceAllowlist[manifest.name] ?? [] for (const file of manifest.files ?? []) { if (isForbiddenPublicationFile(file) && !allowedSources.includes(file)) { errors.push(`${label}: package.json files must not publish ${JSON.stringify(file)}`) } } } if (dir.startsWith('apps/') && manifest.name?.startsWith('@deepseek-ai/')) { const expectedFiles = appPackageFiles[manifest.name] if (expectedFiles === undefined) { errors.push(`${label}: app package has no publication files policy`) } else if (!sameStringList(manifest.files, expectedFiles)) { errors.push(`${label}: package.json files must be ${JSON.stringify(expectedFiles)}`) } } if (isLandlockPackageDir) { if (!isPublicLandlockPackage) { errors.push(`${label}: unexpected package in the public Landlock package family`) } if (manifest.version !== landlockVersion) { errors.push(`${label}: package.json version must match Landlock workspace version ${landlockVersion ?? '(missing)'}`) } } if (dir.startsWith('packages/') && manifest.name?.startsWith('@deepseek-ai/dsh-')) { const peer = manifest.peerDependencies?.['@deepseek-ai/cordis'] const dev = manifest.devDependencies?.['@deepseek-ai/cordis'] if (!peer) errors.push(`${label}: @deepseek-ai/cordis must be a peerDependency`) if (!dev) errors.push(`${label}: @deepseek-ai/cordis must also be a devDependency`) if (peer && dev && peer !== dev) { errors.push(`${label}: @deepseek-ai/cordis peer (${peer}) and dev (${dev}) ranges must match`) } if (manifest.version !== repositoryVersion) { errors.push(`${label}: package.json version must match root version ${repositoryVersion ?? '(missing)'}`) } if (manifest.type !== 'module') { errors.push(`${label}: package.json must set "type": "module"`) } if (manifest.main !== 'lib/index.js') { errors.push(`${label}: package.json must set "main": "lib/index.js"`) } if (manifest.types !== 'lib/types/index.d.ts') { errors.push(`${label}: package.json must set "types": "lib/types/index.d.ts"`) } const rootExport = manifest.exports?.['.'] const rootEntry = typeof rootExport === 'object' && rootExport !== null ? rootExport : undefined if (rootEntry?.types !== './lib/types/index.d.ts') { errors.push(`${label}: package.json exports["."].types must be "./lib/types/index.d.ts"`) } if (rootEntry?.default !== './lib/index.js') { errors.push(`${label}: package.json exports["."].default must be "./lib/index.js"`) } const invariantRaw = manifest.exports?.['./invariant'] const invariantExport = typeof invariantRaw === 'object' && invariantRaw !== null ? invariantRaw : undefined if (invariantExport?.types !== undefined && invariantExport.types !== './lib/types/invariant.d.ts') { errors.push(`${label}: package.json exports["./invariant"].types must be "./lib/types/invariant.d.ts"`) } if (invariantExport?.default !== undefined && invariantExport.default !== './lib/invariant.js') { errors.push(`${label}: package.json exports["./invariant"].default must be "./lib/invariant.js"`) } if (invariantExport && (invariantExport.types === undefined || invariantExport.default === undefined)) { errors.push(`${label}: package.json exports["./invariant"] must declare both types and default targets`) } const expectedFiles = expectedDshPackageFiles(manifest) if (!sameStringList(manifest.files, expectedFiles)) { errors.push(`${label}: package.json files must be ${JSON.stringify(expectedFiles)}`) } } return errors.map(error => `${relative(root, join(root, dir, 'package.json'))}: ${error}`) } /** * Enforce `packages//`: groups are open-named containers without a * package.json, and packages may be neither flat nor more deeply nested. */ function checkHierarchyShape(): string[] { const errors: string[] = [] const packagesRoot = join(root, 'packages') for (const group of readdirSync(packagesRoot, { withFileTypes: true })) { if (!group.isDirectory()) continue const groupRel = join('packages', group.name) if (existsSync(join(packagesRoot, group.name, 'package.json'))) { errors.push(`${groupRel}: a group dir must not contain a package.json — packages live at packages//, not directly under packages/`) continue } for (const pkg of readdirSync(join(packagesRoot, group.name), { withFileTypes: true })) { if (!pkg.isDirectory()) continue if (localArtifactDirs.has(pkg.name)) continue const pkgRel = join(groupRel, pkg.name) if (!existsSync(join(packagesRoot, group.name, pkg.name, 'package.json'))) { errors.push(`${pkgRel}: expected a package here (no package.json found) — the hierarchy is exactly packages//, no deeper nesting`) } } } return errors } function checkRepositoryVersion(): string[] { // The root carries the dsh release family's version, so a prerelease such as // 0.0.1-rc.1 is a valid state between `release:dsh` and its publication. if (repositoryVersion && /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(repositoryVersion)) return [] return ['package.json: version must be X.Y.Z with an optional prerelease segment'] } /** Dependency sections whose ranges reach a published tarball or a local install. */ const dependencySections = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'] as const /** * Require the `workspace:` protocol for every reference to a workspace member. * * A hand-written range says nothing about the version the workspace actually * carries, and `pnpm pack` leaves it alone: `^0.0.1` published from version * `0.0.2` names a version that does not exist. The protocol makes pack * substitute the member's real version, so no release step rewrites ranges. * @param manifests - every workspace manifest. * @returns One error per reference that names a workspace member without the protocol. */ function checkWorkspaceProtocol(manifests: readonly WorkspaceManifest[]): string[] { const members = new Set(manifests.map(entry => entry.manifest.name).filter(name => name !== undefined)) const errors: string[] = [] for (const { dir, manifest } of manifests) { for (const section of dependencySections) { for (const [name, range] of Object.entries(manifest[section] ?? {})) { if (!members.has(name) || range.startsWith('workspace:')) continue errors.push(`${manifest.name ?? dir}: ${section}.${name} must use the workspace: protocol, got ${range}`) } } } return errors } const manifests = workspaceManifests() const errors = [ ...checkRepositoryVersion(), ...manifests.flatMap(checkWorkspace), ...checkWorkspaceProtocol(manifests), ...checkHierarchyShape(), ...collectProjectReferenceFaceViolations(root), ] if (errors.length > 0) { console.error(errors.join('\n')) process.exitCode = 1 }