feat(scripts): serve locally built Official bundles from a loopback registry

A packaged application installs an Official on-demand bundle as
`name@<running DSH version>`, and the plugin manager asks a custom registry
alone, so the unsigned test DMG cannot install a locally built provider before
its release reaches npm: each packed manifest still names the rest of the
repository by version, and ordinary bundle tarballs therefore resolve against
a registry that does not have them yet.

`pnpm run dev:bundle-registry` validates the Official catalog and the workspace
inventory, builds the official artifacts, packs the catalog's complete local
dependency closure with the release families' own payload and manifest checks,
records the run in `dist/test-bundles/<run-id>/bundle-registry.json` as its
completion marker, and only then serves it from 127.0.0.1 on an allocated port
under a fresh namespace. Repository-owned names are answered from this run or
refused locally; third-party packages keep npm's real metadata and bytes
through a fixed redirect. The run publishes nothing, changes no global
npm/pnpm configuration, and leaves DMG packaging untouched.

The packed installer rehearsal now installs that closure into an isolated
profile with no `file:` override, checkout link, ignore list, or scoped
registry standing in for a package the registry should answer, and it asserts a
transitive repository package served only by this run.
This commit is contained in:
Tianyi Cui committed 2026-10-07 14:07:27 +08:00
1 parent c4bda67252
commit 73d853a9ae
9 files changed
+1646 -107

No files matched your search

+171
View File
@@ -0,0 +1,171 @@
/**
* Build, pack, and serve the Official on-demand bundles' local dependency
* closure as a loopback registry, so a packaged test app can install them
* before their release reaches npm.
*
* The run is a build/test utility, not a Harness application launcher: it
* starts no agent and needs no credentials. It shares only the artifact
* transport with the release path; packing, manifests, and validation stay with
* the owners that already perform them.
*/
import { mkdirSync } from 'node:fs'
import { randomUUID } from 'node:crypto'
import { join, resolve } from 'node:path'
import { execa } from 'execa'
import { getDshRuntimeVersion } from '../packages/boot/app-boot/src/plugin-compatibility.ts'
import { generateOfficialBundleCatalog } from './gen-official-bundle-catalog.ts'
import {
REGISTRY_MANIFEST_FILE,
REGISTRY_OUTPUT_ROOT,
createRegistryManifest,
hostPlatform,
packBundleArtifacts,
pnpmInvocationFor,
readReleaseMembers,
readWorkspacePackages,
selectBundleDependencies,
sourceEvidence,
startBundleRegistry,
verifyBuiltBundles,
verifyReleaseVersions,
writeRegistryManifest,
type BundleArtifact,
} from './local-bundle-registry.ts'
/** The preparation step a signal stopped, so the failure is not reported as a command's. */
class Interrupted extends Error {
/**
* @param stage - the step that was running when the signal arrived.
*/
constructor(readonly stage: string) {
super(`interrupted during ${stage}`)
}
}
/**
* Run one package script in the repository root, propagating its exit status.
* @param root - repository root the command runs in.
* @param args - pnpm arguments.
* @param signal - abort signal that stops the child on SIGINT/SIGTERM.
* @param label - step name used in the failure message.
*/
async function runStep(root: string, args: readonly string[], signal: AbortSignal, label: string): Promise<void> {
const invocation = pnpmInvocationFor(args)
let result
try {
result = await execa(invocation.command, invocation.args, {
cwd: root, stdio: 'inherit', reject: false, killDescendants: true, cancelSignal: signal,
})
} catch (error) {
if (signal.aborted) throw new Interrupted(label)
throw error
}
if (result.isCanceled || signal.aborted) throw new Interrupted(label)
if (result.exitCode !== 0) throw new Error(`${label} exited with ${String(result.exitCode ?? result.signal)}`)
}
/**
* Print the installation summary the developer copies from.
* @param options - run directory, registry URL, packed artifacts, source evidence, and foreign-target members.
*/
function printSummary(options: {
readonly directory: string
readonly url: string
readonly artifacts: readonly BundleArtifact[]
readonly source: ReturnType<typeof sourceEvidence>
readonly foreignTargets: readonly string[]
}): void {
process.stdout.write(`Source: ${options.source.commit}${options.source.dirty ? ' (dirty worktree)' : ''}, DSH ${options.source.dshVersion}\n`)
if (options.foreignTargets.length > 0) {
process.stdout.write(`Other platforms: ${options.foreignTargets.join(', ')} (served; pnpm skips them on this host)\n`)
}
process.stdout.write(`Artifacts: ${options.directory}\n`)
process.stdout.write(`Registry: ${options.url}\n`)
process.stdout.write('Packages:\n')
for (const artifact of options.artifacts) process.stdout.write(` ${artifact.name}@${artifact.version}\n`)
process.stdout.write('In Plugins > Add plugin, use an exact package above and this custom registry URL.\n')
process.stdout.write('Keep this process running during package operations. Ctrl+C stops the registry.\n')
}
/** Build one run directory name: sortable by time and unique per invocation. */
function runId(): string {
return `${new Date().toISOString().replace(/[:.]/gu, '-')}-${randomUUID().slice(0, 8)}`
}
/** Build, pack, and serve the local bundle closure until a signal stops the run. */
async function main(): Promise<void> {
const root = resolve(import.meta.dirname, '..')
if (!process.features.typescript) {
throw new Error('dev:bundle-registry: Node.js TypeScript type stripping is unavailable; remove --no-experimental-strip-types from NODE_OPTIONS')
}
const abort = new AbortController()
// A persistent listener, so a second signal during shutdown cannot fall back to the default kill;
// the second signal is an explicit forced exit instead.
let stopping = false
const onSignal = (signal: NodeJS.Signals): void => {
if (stopping) {
process.exit(130)
return
}
stopping = true
abort.abort(signal)
}
process.on('SIGINT', onSignal)
process.on('SIGTERM', onSignal)
let registry
try {
// 1. The catalog is the only root list; the inventory is how a local package is distinguished from an external one.
const entries = generateOfficialBundleCatalog(root).map(entry => entry.packageName)
const packages = readWorkspacePackages(root)
const selection = selectBundleDependencies({ entries, packages, platform: hostPlatform() })
const release = readReleaseMembers(root)
verifyReleaseVersions(root)
// 2. Current official artifacts, so the run never serves an earlier build.
await runStep(root, ['run', 'build:official'], abort.signal, 'build:official')
verifyBuiltBundles({ entries, packages })
// 3. One immutable, run-owned directory; a later run never overwrites these bytes.
const buildId = runId()
const directory = join(root, REGISTRY_OUTPUT_ROOT, buildId)
mkdirSync(directory, { recursive: true })
const artifacts = await packBundleArtifacts({
root, destination: directory, members: selection.members, release, signal: abort.signal,
})
// 4. The manifest is the completion marker: only a fully packed run has one.
const source = sourceEvidence(root, getDshRuntimeVersion())
const manifest = createRegistryManifest({
buildId, source, entries, workspaceNames: selection.workspaceNames, artifacts,
})
await writeRegistryManifest(join(directory, REGISTRY_MANIFEST_FILE), manifest)
// 5. Serve, then announce: the summary is never printed for a registry that did not listen.
registry = await startBundleRegistry({
directory, artifacts, workspaceNames: selection.workspaceNames,
})
printSummary({ directory, url: registry.url, artifacts, source, foreignTargets: selection.foreignTargets })
await new Promise<void>((resolveStop) => {
if (abort.signal.aborted) resolveStop()
else abort.signal.addEventListener('abort', () => { resolveStop() }, { once: true })
})
await registry.close()
registry = undefined
process.stdout.write('Registry stopped. Artifacts and installed packages stay on disk.\n')
} catch (error) {
if (registry !== undefined) await registry.close()
if (error instanceof Interrupted) {
process.stderr.write(`dev:bundle-registry: ${error.message}\n`)
process.exitCode = 130
return
}
throw error
} finally {
process.off('SIGINT', onSignal)
process.off('SIGTERM', onSignal)
}
}
await main()
+456
View File
@@ -0,0 +1,456 @@
/** Behavior of the local Official-bundle registry: closure selection, archive validation, HTTP, and shutdown. */
import { createHash } from 'node:crypto'
import { mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { execa } from 'execa'
import { describe, expect, it, onTestFinished } from 'vitest'
import { ON_DEMAND_BUNDLES } from '../packages/boot/app-boot/src/official-bundle-packages.ts'
import { generateOfficialBundleCatalog } from './gen-official-bundle-catalog.ts'
import {
createRegistryManifest,
hostPlatform,
pnpmInvocationFor,
readPackedArtifacts,
readReleaseMembers,
readWorkspacePackages,
selectBundleDependencies,
startBundleRegistry,
writeRegistryManifest,
type BundleArtifact,
type ReleaseEntry,
type WorkspacePackage,
} from './local-bundle-registry.ts'
import { releaseFamily, type ReleaseMember } from './release/families.ts'
import { capture } from './release/process.ts'
const root = fileURLToPath(new URL('..', import.meta.url))
/** A private temporary root this spec owns, removed with the test. */
function temporaryDirectory(prefix: string): string {
const directory = realpathSync(mkdtempSync(join(tmpdir(), prefix)))
onTestFinished(() => { rmSync(directory, { recursive: true, force: true }) })
return directory
}
/** One fabricated workspace package with a `1.0.0` version line. */
function workspacePackage(name: string, manifest: Record<string, unknown>): WorkspacePackage {
return { name, directory: `/workspace/${name.replace('@', '').replace('/', '-')}`, manifest: { name, version: '1.0.0', ...manifest } }
}
/** An inventory of fabricated packages, keyed the way discovery keys them. */
function inventory(...members: WorkspacePackage[]): Map<string, WorkspacePackage> {
return new Map(members.map(member => [member.name, member]))
}
/** One fabricated package's archive together with the identities needed to read it back. */
interface Fixture {
readonly member: WorkspacePackage
readonly entry: ReleaseEntry
}
/**
* Pack one fabricated package archive into a run directory.
* @param options - destination, identity, source text, dependency ranges, and an optional declared bundle patch.
* @returns The workspace identity and the release entry its tarball must satisfy.
*/
function packFixture(options: {
readonly destination: string
readonly name: string
readonly version: string
readonly source: string
readonly dependencies?: Record<string, string>
readonly patch?: string
}): Fixture {
const staging = join(options.destination, `${options.name.replace('@', '').replace('/', '-')}-staging`)
const manifest: Record<string, unknown> = {
name: options.name,
version: options.version,
main: 'lib/index.js',
...options.dependencies === undefined ? {} : { dependencies: options.dependencies },
...options.patch === undefined ? {} : { dsh: { bundle: { patch: options.patch } } },
}
mkdirSync(join(staging, 'package', 'lib'), { recursive: true })
writeFileSync(join(staging, 'package', 'package.json'), JSON.stringify(manifest))
writeFileSync(join(staging, 'package', 'lib', 'index.js'), options.source)
if (options.patch !== undefined) writeFileSync(join(staging, 'package', options.patch), '[]\n')
const file = `${options.name.replace('@', '').replace('/', '-')}-${options.version}.tgz`
capture('tar', ['-czf', join(options.destination, file), '-C', staging, 'package'])
const releaseMember: ReleaseMember = { directory: staging, name: options.name, version: options.version, manifest }
return {
member: workspacePackage(options.name, { version: options.version, ...manifest }),
entry: { family: releaseFamily('vendor'), member: releaseMember },
}
}
/**
* Pack a fixture archive and read it back through the real archive validation.
* @param options - the fixture to pack.
* @returns The servable artifact and the identities it was packed from.
*/
function fabricate(options: Parameters<typeof packFixture>[0]): Fixture & { readonly artifact: BundleArtifact } {
const fixture = packFixture(options)
const [artifact] = readPackedArtifacts({
destination: options.destination,
members: [fixture.member],
release: new Map([[fixture.member.name, fixture.entry]]),
})
if (artifact === undefined) throw new Error('fabricated artifact was not read back')
return { ...fixture, artifact }
}
/** Start a registry for one fabricated run, closed with the test. */
async function servingRun(options: {
readonly destination: string
readonly artifacts: readonly BundleArtifact[]
readonly workspaceNames: readonly string[]
}): Promise<string> {
const registry = await startBundleRegistry({
directory: options.destination, artifacts: options.artifacts, workspaceNames: options.workspaceNames,
})
onTestFinished(() => registry.close())
return registry.url
}
describe('catalog and local closure', () => {
it('selects the real catalog closure from this checkout', () => {
const entries = generateOfficialBundleCatalog(root).map(entry => entry.packageName)
expect(entries).toEqual([...ON_DEMAND_BUNDLES])
const packages = readWorkspacePackages(root)
const release = readReleaseMembers(root)
const selection = selectBundleDependencies({ entries, packages, platform: hostPlatform() })
expect(selection.members.map(member => member.name)).toEqual(expect.arrayContaining([...entries]))
for (const member of selection.members) {
expect(member.manifest.private, member.name).not.toBe(true)
expect(release.has(member.name), member.name).toBe(true)
}
expect(selection.workspaceNames).toEqual(expect.arrayContaining(selection.members.map(member => member.name)))
// A vendor package keeps its own version line, so a run must not assume one shared version.
expect(packages.get('@deepseek-ai/cordis')?.manifest.version)
.not.toBe(packages.get('@deepseek-ai/dsh-brand')?.manifest.version)
// Nothing in this closure is built for another platform, on any host.
expect(selection.foreignTargets).toEqual([])
})
it('follows install, optional, and required peer edges but not optional peers', () => {
const selection = selectBundleDependencies({
entries: ['@scope/root'],
packages: inventory(
workspacePackage('@scope/root', {
dependencies: { '@scope/required': 'workspace:*' },
optionalDependencies: { '@scope/optional': 'workspace:*' },
peerDependencies: { '@scope/peer': 'workspace:*', '@scope/unused-peer': 'workspace:*' },
peerDependenciesMeta: { '@scope/unused-peer': { optional: true } },
}),
workspacePackage('@scope/required', {}),
workspacePackage('@scope/optional', { dependencies: { '@scope/transitive': 'workspace:*' } }),
workspacePackage('@scope/peer', {}),
workspacePackage('@scope/transitive', {}),
workspacePackage('@scope/unused-peer', {}),
workspacePackage('@scope/unreferenced', {}),
),
platform: { os: ['darwin'] },
})
expect(selection.members.map(member => member.name)).toEqual([
'@scope/optional', '@scope/peer', '@scope/required', '@scope/root', '@scope/transitive',
])
})
it('leaves a dependency that is not a workspace package to its own registry', () => {
const selection = selectBundleDependencies({
entries: ['@scope/root'],
packages: inventory(workspacePackage('@scope/root', {
dependencies: { '@other/external': '^1.0.0', 'plain-external': '^2.0.0' },
})),
platform: {},
})
expect(selection.members.map(member => member.name)).toEqual(['@scope/root'])
})
it('fails when a catalog root is not a workspace package', () => {
expect(() => selectBundleDependencies({ entries: ['@scope/missing'], packages: inventory(), platform: {} }))
.toThrow(/cannot resolve workspace package @scope\/missing \(catalog entry @scope\/missing\)/u)
})
it('rejects an empty or duplicate catalog', () => {
expect(() => selectBundleDependencies({ entries: [], packages: inventory(), platform: {} }))
.toThrow(/no on-demand entries/u)
expect(() => selectBundleDependencies({ entries: ['@scope/a', '@scope/a'], packages: inventory(), platform: {} }))
.toThrow(/must be distinct/u)
})
it('fails on a required edge that targets another platform, naming the path', () => {
expect(() => selectBundleDependencies({
entries: ['@scope/root'],
packages: inventory(
workspacePackage('@scope/root', { dependencies: { '@scope/native-win': 'workspace:*' } }),
workspacePackage('@scope/native-win', { os: ['win32'] }),
),
platform: { os: ['darwin'], cpu: ['arm64'] },
})).toThrow(/@scope\/root -> @scope\/native-win requires @scope\/native-win, which targets os=win32; this host is os=darwin cpu=arm64/u)
})
it('keeps an optional-only foreign-target member and reports it', () => {
const selection = selectBundleDependencies({
entries: ['@scope/root'],
packages: inventory(
workspacePackage('@scope/root', { optionalDependencies: { '@scope/native-win': 'workspace:*' } }),
workspacePackage('@scope/native-win', { os: ['win32'] }),
),
platform: { os: ['darwin'] },
})
expect(selection.members.map(member => member.name)).toEqual(['@scope/native-win', '@scope/root'])
expect(selection.foreignTargets).toEqual(['@scope/native-win'])
})
})
describe('packed archives', () => {
it('describes exactly the bytes it will serve', () => {
const destination = temporaryDirectory('dsh-local-registry-archive-')
const { artifact } = fabricate({ destination, name: '@scope/pkg', version: '1.0.0', source: 'export default 1\n' })
const bytes = readFileSync(artifact.archive)
expect(artifact.bytes).toBe(bytes.byteLength)
expect(artifact.integrity).toBe(`sha512-${createHash('sha512').update(bytes).digest('base64')}`)
expect(artifact.shasum).toBe(createHash('sha1').update(bytes).digest('hex'))
expect(artifact.file).toBe('scope-pkg-1.0.0.tgz')
expect(artifact.platform).toEqual({})
expect(artifact.manifest.version).toBe('1.0.0')
})
it('rejects an archive that lacks the main it declares', () => {
const destination = temporaryDirectory('dsh-local-registry-payload-')
const staging = join(destination, 'staging')
mkdirSync(join(staging, 'package'), { recursive: true })
writeFileSync(join(staging, 'package', 'package.json'), JSON.stringify({
name: '@scope/pkg', version: '1.0.0', main: 'lib/index.js', dsh: { bundle: { patch: 'cordis.patch.yml' } },
}))
capture('tar', ['-czf', join(destination, 'scope-pkg-1.0.0.tgz'), '-C', staging, 'package'])
const member = workspacePackage('@scope/pkg', { version: '1.0.0' })
const entry: ReleaseEntry = {
family: releaseFamily('vendor'), member: { directory: staging, name: '@scope/pkg', version: '1.0.0', manifest: member.manifest },
}
expect(() => readPackedArtifacts({ destination, members: [member], release: new Map([['@scope/pkg', entry]]) }))
.toThrow(/@scope\/pkg: packed archive lacks its declared main lib\/index\.js/u)
})
it('rejects a bundle whose patch is missing from its archive', () => {
const destination = temporaryDirectory('dsh-local-registry-patch-')
const { member, entry } = packFixture({
destination, name: '@scope/bundle', version: '1.0.0', source: 'export default 1\n', patch: 'cordis.patch.yml',
})
const archive = join(destination, 'scope-bundle-1.0.0.tgz')
const stripped = join(destination, 'stripped')
mkdirSync(join(stripped, 'package'), { recursive: true })
writeFileSync(join(stripped, 'package', 'package.json'), JSON.stringify({
name: '@scope/bundle', version: '1.0.0', main: 'lib/index.js', dsh: { bundle: { patch: 'cordis.patch.yml' } },
}))
mkdirSync(join(stripped, 'package', 'lib'), { recursive: true })
writeFileSync(join(stripped, 'package', 'lib', 'index.js'), 'export default 1\n')
capture('tar', ['-czf', archive, '-C', stripped, 'package'])
expect(() => readPackedArtifacts({ destination, members: [member], release: new Map([['@scope/bundle', entry]]) }))
.toThrow(/@scope\/bundle: packed archive lacks its declared bundle patch cordis\.patch\.yml/u)
})
it('rejects a missing archive and a package that is not a public release member', () => {
const destination = temporaryDirectory('dsh-local-registry-missing-')
const missing = workspacePackage('@scope/pkg', { version: '1.0.0' })
const entry: ReleaseEntry = {
family: releaseFamily('vendor'), member: { directory: destination, name: '@scope/pkg', version: '1.0.0', manifest: missing.manifest },
}
expect(() => readPackedArtifacts({ destination, members: [missing], release: new Map([['@scope/pkg', entry]]) }))
.toThrow(/@scope\/pkg produced no tarball/u)
fabricate({ destination, name: '@scope/pkg', version: '1.0.0', source: 'export default 1\n' })
expect(() => readPackedArtifacts({ destination, members: [missing], release: new Map() }))
.toThrow(/not a public release member/u)
})
it('rejects a packed manifest that keeps a workspace protocol', () => {
const destination = temporaryDirectory('dsh-local-registry-protocol-')
const { member, entry } = packFixture({
destination, name: '@scope/pkg', version: '1.0.0', source: 'export default 1\n',
dependencies: { '@scope/dependency': 'workspace:*' },
})
// The fixture writes the manifest verbatim, so a protocol a real pack rewrites is still observable here.
expect(() => readPackedArtifacts({ destination, members: [member], release: new Map([['@scope/pkg', entry]]) }))
.toThrow(/packed dependencies\.@scope\/dependency still declares workspace:\*/u)
})
it('rejects a packed local dependency this run does not serve at the required version', () => {
const destination = temporaryDirectory('dsh-local-registry-version-')
const consumer = packFixture({
destination, name: '@scope/pkg', version: '1.0.0', source: 'export default 1\n',
dependencies: { '@scope/dependency': '^2.0.0' },
})
const dependency = packFixture({ destination, name: '@scope/dependency', version: '1.0.0', source: 'export default 2\n' })
const release = new Map([[consumer.member.name, consumer.entry], [dependency.member.name, dependency.entry]])
expect(() => readPackedArtifacts({ destination, members: [consumer.member, dependency.member], release }))
.toThrow(/@scope\/pkg: packed dependencies\.@scope\/dependency requires \^2\.0\.0, but this run serves 1\.0\.0/u)
})
})
describe('registry HTTP', () => {
it('answers scoped packuments, version documents, and dist tags for its own artifacts', async () => {
const destination = temporaryDirectory('dsh-local-registry-http-')
const { artifact } = fabricate({ destination, name: '@scope/local', version: '1.2.3-alpha.1', source: 'export default 1\n' })
const url = await servingRun({ destination, artifacts: [artifact], workspaceNames: ['@scope/local', '@scope/absent'] })
// pnpm encodes the scope separator; an unencoded path names the same package.
for (const path of ['@scope%2flocal', '@scope/local']) {
const response = await fetch(`${url}${path}`)
expect(response.status, path).toBe(200)
const packument = await response.json() as {
name: string
'dist-tags': Record<string, string>
versions: Record<string, { dist: { tarball: string; integrity: string } }>
time: Record<string, string>
}
expect(packument.name).toBe('@scope/local')
expect(packument['dist-tags']).toEqual({ latest: '1.2.3-alpha.1', next: '1.2.3-alpha.1' })
expect(packument.versions['1.2.3-alpha.1']?.dist.tarball).toBe(`${url}tarballs/scope-local-1.2.3-alpha.1.tgz`)
expect(packument.versions['1.2.3-alpha.1']?.dist.integrity).toBe(artifact.integrity)
// The only publication time a run can claim is when its own archives were packed.
expect(packument.time['1.2.3-alpha.1']).toBe(artifact.packedAt)
}
for (const path of ['@scope%2flocal/1.2.3-alpha.1', '@scope/local/latest', '@scope/local/next']) {
const response = await fetch(`${url}${path}`)
expect(response.status, path).toBe(200)
expect(await response.json()).toMatchObject({ name: '@scope/local', version: '1.2.3-alpha.1' })
}
const unknown = await fetch(`${url}@scope%2flocal/9.9.9`)
expect(unknown.status).toBe(404)
expect(await unknown.json()).toMatchObject({ reason: 'this run serves @scope/[email protected] only' })
})
it('serves exactly the archived bytes, and no body for HEAD', async () => {
const destination = temporaryDirectory('dsh-local-registry-tarball-')
const { artifact } = fabricate({ destination, name: '@scope/local', version: '1.0.0', source: 'export default 1\n' })
const url = await servingRun({ destination, artifacts: [artifact], workspaceNames: ['@scope/local'] })
const response = await fetch(`${url}tarballs/${artifact.file}`)
expect(response.status).toBe(200)
expect(Buffer.from(await response.arrayBuffer())).toEqual(readFileSync(artifact.archive))
const head = await fetch(`${url}tarballs/${artifact.file}`, { method: 'HEAD' })
expect(head.status).toBe(200)
expect(head.headers.get('content-length')).toBe(String(artifact.bytes))
expect(await head.text()).toBe('')
expect((await fetch(`${url}tarballs/no-such-package-1.0.0.tgz`)).status).toBe(404)
})
it('keeps a repository-owned miss local and redirects only external packages', async () => {
const destination = temporaryDirectory('dsh-local-registry-miss-')
const { artifact } = fabricate({ destination, name: '@scope/local', version: '1.0.0', source: 'export default 1\n' })
const url = await servingRun({
destination, artifacts: [artifact], workspaceNames: ['@scope/local', '@deepseek-ai/dsh-private-thing'],
})
const owned = await fetch(`${url}@deepseek-ai%2fdsh-private-thing`)
expect(owned.status).toBe(404)
const refusal = await owned.json() as { reason?: string }
expect(refusal.reason ?? '').toContain('repository-owned')
// A repository-owned name that is not even in the closure must never reach npm.
expect((await fetch(`${url}@deepseek-ai%2fdsh-private-thing/1.0.0`)).status).toBe(404)
const external = await fetch(`${url}@anthropic-ai%2fclaude-agent-sdk`, { redirect: 'manual' })
expect(external.status).toBe(302)
expect(external.headers.get('location')).toBe('https://registry.npmjs.org/@anthropic-ai%2fclaude-agent-sdk')
})
it('rejects out-of-namespace, malformed, and non-GET requests, then stops listening', async () => {
const destination = temporaryDirectory('dsh-local-registry-reject-')
const { artifact } = fabricate({ destination, name: '@scope/local', version: '1.0.0', source: 'export default 1\n' })
const registry = await startBundleRegistry({ directory: destination, artifacts: [artifact], workspaceNames: ['@scope/local'] })
onTestFinished(() => registry.close())
const { origin, pathname } = new URL(registry.url)
expect((await fetch(`${origin}/@scope%2flocal`)).status).toBe(404)
expect((await fetch(`${origin}${pathname}%zz`)).status).toBe(404)
expect((await fetch(`${registry.url}@scope%2flocal/%2e%2e%2f%2e%2e%2fetc%2fpasswd`)).status).toBe(404)
expect((await fetch(`${registry.url}@scope%2flocal/1.0.0/extra`)).status).toBe(404)
expect((await fetch(`${registry.url}tarballs/%2e%2e%2f%2e%2e%2fetc%2fpasswd`)).status).toBe(404)
expect((await fetch(`${registry.url}-/ping`)).status).toBe(404)
expect((await fetch(registry.url, { method: 'POST', body: 'x' })).status).toBe(405)
const other = await startBundleRegistry({ directory: destination, artifacts: [artifact], workspaceNames: ['@scope/local'] })
onTestFinished(() => other.close())
expect(other.url).not.toBe(registry.url)
await registry.close()
await expect(fetch(`${registry.url}@scope%2flocal`)).rejects.toThrow()
})
})
describe('run record', () => {
it('names the run, its membership, and the integrity of every served archive', async () => {
const destination = temporaryDirectory('dsh-local-registry-manifest-')
const { artifact } = fabricate({ destination, name: '@scope/local', version: '1.0.0', source: 'export default 1\n' })
const manifest = createRegistryManifest({
buildId: 'run-1',
source: { commit: 'a'.repeat(40), dirty: true, dshVersion: '0.0.0-test' },
entries: ['@scope/local'],
workspaceNames: ['@scope/local', '@scope/absent'],
artifacts: [artifact],
})
expect(manifest).toEqual({
schemaVersion: 1,
buildId: 'run-1',
source: { commit: 'a'.repeat(40), dirty: true, dshVersion: '0.0.0-test' },
entries: ['@scope/local'],
workspaceNames: ['@scope/local', '@scope/absent'],
packages: [{ name: '@scope/local', version: '1.0.0', file: artifact.file, bytes: artifact.bytes, integrity: artifact.integrity }],
})
const path = join(destination, 'bundle-registry.json')
await writeRegistryManifest(path, manifest)
expect(JSON.parse(readFileSync(path, 'utf8'))).toEqual(manifest)
expect(readFileSync(path, 'utf8').endsWith('}\n')).toBe(true)
})
})
describe('package cache freshness', () => {
it('installs a rebuilt archive at the same version through a retained pnpm store', { timeout: 300_000 }, async () => {
const temporary = temporaryDirectory('dsh-local-registry-cache-')
const store = join(temporary, 'store')
const cache = join(temporary, 'cache')
const userConfig = join(temporary, 'empty.npmrc')
writeFileSync(userConfig, '')
// A retained store, metadata cache, and npm configuration, so the second installation cannot pass by starting empty.
const environment = { ...process.env, npm_config_userconfig: userConfig }
const install = async (run: string, source: string): Promise<{ payload: string; settings: string }> => {
const destination = join(temporary, run, 'artifacts')
mkdirSync(destination, { recursive: true })
const { artifact } = fabricate({ destination, name: '@dsh-local/fixture', version: '1.0.0', source })
const registry = await startBundleRegistry({
directory: destination, artifacts: [artifact], workspaceNames: ['@dsh-local/fixture'],
})
onTestFinished(() => registry.close())
const consumer = join(temporary, run, 'consumer')
mkdirSync(consumer, { recursive: true })
writeFileSync(join(consumer, 'package.json'), JSON.stringify({ name: `consumer-${run}`, private: true, dependencies: {} }))
writeFileSync(join(consumer, 'pnpm-workspace.yaml'), 'packages:\n - .\n\nnodeLinker: hoisted\nautoInstallPeers: false\n')
const invocation = pnpmInvocationFor([
'add', '@dsh-local/[email protected]', `--registry=${registry.url}`,
'--store-dir', store, '--cache-dir', cache, '--config.lockfile=false',
])
const result = await execa(invocation.command, invocation.args, {
cwd: consumer, env: environment, extendEnv: false, reject: false, timeout: 240_000,
})
expect(result.timedOut, `${result.stdout}\n${result.stderr}`).toBe(false)
expect(result.exitCode, `${result.stdout}\n${result.stderr}`).toBe(0)
await registry.close()
return {
payload: readFileSync(join(consumer, 'node_modules', '@dsh-local', 'fixture', 'lib', 'index.js'), 'utf8'),
settings: readFileSync(join(consumer, 'pnpm-workspace.yaml'), 'utf8'),
}
}
const first = await install('first', 'export default "first build"\n')
expect(first.payload).toContain('first build')
// pnpm 11's default minimumReleaseAge is 24 hours, and its non-strict mode records the exemption it granted
// instead of faking an older publication time for a locally packed artifact.
expect(first.settings).toContain('minimumReleaseAgeExclude')
expect(first.settings).toContain('@dsh-local/[email protected]')
const second = await install('second', 'export default "second build"\n')
expect(second.payload).toContain('second build')
})
})
+910
View File
@@ -0,0 +1,910 @@
/**
* Pack the local dependency closure of the Official on-demand bundles and serve
* it as a loopback npm registry.
*
* A packaged application installs an Official bundle as `name@<running DSH
* version>`, and the plugin manager asks a custom registry *alone*
* ([registry plan](../../packages/boot/plugin-manager/src/registry.ts)). Every
* transitive dependency of the packed bundles must therefore be answerable by
* this process: repository-owned packages come from the run's own artifacts,
* and genuinely external packages keep npm's real metadata and bytes through a
* fixed redirect. Publication is untouched; only one development run's
* transport is local.
*/
import { createHash, randomUUID } from 'node:crypto'
import { createReadStream, existsSync, globSync, readFileSync, statSync } from 'node:fs'
import { mkdir, writeFile } from 'node:fs/promises'
import { createServer, type Server, type ServerResponse } from 'node:http'
import { join, resolve } from 'node:path'
import { execa } from 'execa'
import yaml from 'js-yaml'
import semver from 'semver'
import { pnpmInvocation } from './pnpm-invocation.ts'
import { capture, pnpmCommand } from './release/process.ts'
import { releaseFamily, tarballName, type ReleaseFamily, type ReleaseMember } from './release/families.ts'
import { packedManifest, tarballFiles } from './release/tarball.ts'
/** Run directories a registry invocation owns, relative to the repository root. */
export const REGISTRY_OUTPUT_ROOT = 'dist/test-bundles'
/** Manifest filename whose presence marks a run directory as complete. */
export const REGISTRY_MANIFEST_FILE = 'bundle-registry.json'
/** Registry every external package is redirected to, with the request path preserved. */
export const UPSTREAM_REGISTRY = 'https://registry.npmjs.org/'
/** Installation edges a consumer cannot load without. */
const REQUIRED_SECTIONS = ['dependencies'] as const
/** Edges pnpm may skip on this host; their packages still need local metadata. */
const OPTIONAL_SECTIONS = ['optionalDependencies'] as const
/** Edges that constrain an installation without installing on the package's behalf. */
const PEER_SECTIONS = ['peerDependencies'] as const
/** One workspace package available to pack and serve. */
export interface WorkspacePackage {
/** Package name exactly as it is declared. */
readonly name: string
/** Absolute package directory. */
readonly directory: string
/** Parsed `package.json`. */
readonly manifest: Record<string, unknown>
}
/** Platform constraints a package declares for its own artifacts. */
export interface PlatformConstraint {
/** Accepted `process.platform` values, which may be `!value` negations. */
readonly os?: readonly string[]
/** Accepted `process.arch` values, which may be `!value` negations. */
readonly cpu?: readonly string[]
/** Accepted libc values on Linux, which may be `!value` negations. */
readonly libc?: readonly string[]
}
/** One packed package archive the registry answers with. */
export interface BundleArtifact {
/** Package name from the packed manifest. */
readonly name: string
/** Packed version, served exactly. */
readonly version: string
/** Absolute archive path in the run directory. */
readonly archive: string
/** Tarball filename inside the run directory. */
readonly file: string
/** Archive size in bytes, as served. */
readonly bytes: number
/** SHA-512 subresource integrity of the served bytes. */
readonly integrity: string
/** SHA-1 hex digest of the served bytes. */
readonly shasum: string
/** Manifest contained in the archive, which the registry serves as the version metadata. */
readonly manifest: Record<string, unknown>
/** Platform constraints the packed manifest declares for itself. */
readonly platform: PlatformConstraint
/** Dist tag this version publishes under, from its release family. */
readonly distTag?: string
/** When this run packed the archive; the only publication time the run can honestly claim. */
readonly packedAt: string
}
/** The catalog roots together with the local packages an ordinary installation needs. */
export interface BundleSelection {
/** Catalog roots, in admission order. */
readonly entries: readonly string[]
/** Local closure, sorted by package name. */
readonly members: readonly WorkspacePackage[]
/** Every repository-owned name, so a request for one is a local miss rather than an upstream redirect. */
readonly workspaceNames: readonly string[]
/** Members built for another platform, kept so pnpm reads their metadata and applies its own skip. */
readonly foreignTargets: readonly string[]
}
/** What one run records beside its artifacts; the file is written only once the run is complete. */
export interface RegistryManifest {
/** Manifest layout version. */
readonly schemaVersion: 1
/** Unique run directory name. */
readonly buildId: string
/** Which bytes these artifacts were packed from. */
readonly source: {
/** Full Git object id of the packed checkout. */
readonly commit: string
/** Whether the checkout had uncommitted changes, so the commit alone is not the snapshot. */
readonly dirty: boolean
/** Running DSH version the catalog installation target names. */
readonly dshVersion: string
}
/** Catalog roots in admission order. */
readonly entries: readonly string[]
/** Every repository-owned name. */
readonly workspaceNames: readonly string[]
/** Packed packages, sorted by name. */
readonly packages: readonly {
readonly name: string
readonly version: string
readonly file: string
readonly bytes: number
readonly integrity: string
}[]
}
/** A running loopback registry serving one immutable run directory. */
export interface BundleRegistry {
/** Registry URL ending in the run's fresh namespace and a slash, as `--registry` takes it. */
readonly url: string
/** Stop dispatching, drop open connections, and resolve once the listener is gone. */
close(): Promise<void>
}
/** One public release member and the family that owns its payload rules. */
export interface ReleaseEntry {
/** Family owning this member's payload and version rules. */
readonly family: ReleaseFamily
/** The member as its family publishes it. */
readonly member: ReleaseMember
}
/**
* Resolve pnpm for a shell-free child process.
*
* A package script's own pnpm is authoritative, because it owns the workspace
* lockfile and store; a direct invocation (`pnpm exec`, a test runner) has no
* lifecycle environment, so the workspace's own pnpm dependency runs instead.
* @param args - pnpm arguments.
* @returns A command and argument array suitable for `spawn` without a shell.
*/
export function pnpmInvocationFor(args: readonly string[]): { command: string; args: string[] } {
const entrypoint = process.env.npm_execpath
if (entrypoint === undefined || entrypoint === '') {
const [command, ...prefix] = pnpmCommand()
return { command, args: [...prefix, ...args] }
}
return pnpmInvocation(args)
}
/**
* Read the pnpm workspace inventory.
* @param root - repository root containing `pnpm-workspace.yaml`.
* @returns Workspace packages indexed by name.
*/
export function readWorkspacePackages(root: string): Map<string, WorkspacePackage> {
const settings: unknown = yaml.load(readFileSync(join(root, 'pnpm-workspace.yaml'), 'utf8'))
if (!isRecord(settings)) throw new Error('pnpm-workspace.yaml must be a mapping')
const patterns = settings.packages
if (!Array.isArray(patterns) || patterns.length === 0 || !patterns.every(value => typeof value === 'string')) {
throw new Error('pnpm-workspace.yaml must list package patterns')
}
const packages = new Map<string, WorkspacePackage>()
const paths = globSync(patterns.map(pattern => `${pattern}/package.json`), { cwd: root })
.map(path => path.replaceAll('\\', '/'))
.sort()
for (const path of paths) {
const manifest = manifestAt(join(root, path))
const name = manifest.name
if (typeof name !== 'string' || name === '') throw new Error(`${path} must declare a string name`)
if (packages.has(name)) throw new Error(`duplicate workspace package ${name}`)
packages.set(name, { name, directory: resolve(root, path, '..'), manifest })
}
if (packages.size === 0) throw new Error('the pnpm workspace inventory is empty')
return packages
}
/**
* Index the release families' public members by package name.
* @param root - repository root containing the release families.
* @returns Public release members keyed by package name.
*/
export function readReleaseMembers(root: string): Map<string, ReleaseEntry> {
const members = new Map<string, ReleaseEntry>()
for (const id of ['dsh', 'vendor']) {
const family = releaseFamily(id)
for (const member of family.members(root)) members.set(member.name, { family, member })
}
return members
}
/**
* Assert each family's own version baseline, which is what one tag publishes.
* @param root - repository root containing the release families.
*/
export function verifyReleaseVersions(root: string): void {
for (const id of ['dsh', 'vendor']) {
const family = releaseFamily(id)
family.verifyVersions(family.members(root))
}
}
/**
* Require every catalog root to be a built, patch-carrying bundle in this checkout.
*
* The catalog generator already checks admission metadata; this checks the
* artifacts a packed consumer would actually load, so a stale or partial build
* fails before any archive is served.
* @param options - catalog roots in admission order and the workspace inventory.
*/
export function verifyBuiltBundles(options: {
readonly entries: readonly string[]
readonly packages: ReadonlyMap<string, WorkspacePackage>
}): void {
for (const name of options.entries) {
const member = options.packages.get(name)
if (member === undefined) throw new Error(`catalog entry ${name} is not a workspace package`)
const main = member.manifest.main
if (typeof main !== 'string' || main === '') throw new Error(`${name} declares no package main to load`)
const entry = resolve(member.directory, main)
if (!existsSync(entry) || !statSync(entry).isFile()) {
throw new Error(`${name}: built runtime is missing at ${entry}; run pnpm run build:official first`)
}
const dsh = member.manifest.dsh
if (!isRecord(dsh) || !isRecord(dsh.bundle)) throw new Error(`${name} declares no dsh.bundle`)
const patches = declaredBundlePatches(member.manifest)
if (patches.length === 0) throw new Error(`${name}: dsh.bundle.patch must be a file path or a list of file paths`)
for (const patch of patches) {
const path = join(member.directory, patch)
if (!existsSync(path) || !statSync(path).isFile()) throw new Error(`${name}: bundle patch ${path} is missing`)
}
}
}
/**
* Read the platform constraints a manifest declares for its own artifacts.
* @param manifest - package manifest.
* @returns The declared `os`, `cpu`, and `libc` lists.
*/
export function platformConstraint(manifest: Record<string, unknown>): PlatformConstraint {
const lists: { os?: readonly string[]; cpu?: readonly string[]; libc?: readonly string[] } = {}
for (const field of ['os', 'cpu', 'libc'] as const) {
const value = manifest[field]
if (Array.isArray(value) && value.every(entry => typeof entry === 'string')) lists[field] = value
}
return lists
}
/**
* This host's platform values, for comparing against a package's own constraints.
* @returns Host `os`, `cpu`, and libc.
*/
export function hostPlatform(): PlatformConstraint {
if (process.platform !== 'linux') return { os: [process.platform], cpu: [process.arch] }
const report = process.report.getReport() as { header?: { glibcVersionRuntime?: string } }
return { os: [process.platform], cpu: [process.arch], libc: [report.header?.glibcVersionRuntime === undefined ? 'musl' : 'glibc'] }
}
/**
* Whether a package's declared platform accepts this host.
* @param constraint - declared constraints.
* @param host - host `os`, `cpu`, and libc values.
* @returns True when the package may run here.
*/
export function matchesPlatform(constraint: PlatformConstraint, host: PlatformConstraint): boolean {
return allowsValue(constraint.os, host.os?.[0])
&& allowsValue(constraint.cpu, host.cpu?.[0])
&& allowsValue(constraint.libc, host.libc?.[0])
}
/**
* Select the catalog roots and the local packages an ordinary installation needs.
*
* Every dependency naming a workspace package is followed, whatever its section
* or protocol, so a locally owned requirement is never left to npm. A required
* edge naming a package built for another platform fails here, because the run
* cannot supply the artifact that installer would need; an optional edge is
* kept so pnpm still reads its metadata and applies its own platform skip.
* @param options - catalog roots, workspace inventory, and the host to judge platform edges against.
* @returns The closure, the repository-owned names, and the foreign-target members it kept.
*/
export function selectBundleDependencies(options: {
readonly entries: readonly string[]
readonly packages: ReadonlyMap<string, WorkspacePackage>
readonly platform: PlatformConstraint
}): BundleSelection {
const { entries, packages, platform } = options
if (entries.length === 0) throw new Error('the Official catalog has no on-demand entries')
if (new Set(entries).size !== entries.length) throw new Error('Official catalog entries must be distinct')
const selected = new Map<string, WorkspacePackage>()
const parents = new Map<string, string>()
const queued = new Set<string>()
const queue: string[] = []
const enqueue = (name: string, parent: string | undefined): void => {
if (selected.has(name) || queued.has(name)) return
queued.add(name)
if (parent !== undefined) parents.set(name, parent)
queue.push(name)
}
for (const entry of entries) enqueue(entry, undefined)
for (let index = 0; index < queue.length; index += 1) {
const name = queue[index] as string
const member = packages.get(name)
if (member === undefined) {
throw new Error(`local bundle closure cannot resolve workspace package ${name} (${dependencyPath(name, parents, entries)})`)
}
selected.set(name, member)
for (const name of installedEdges(member.manifest)) {
if (packages.has(name)) enqueue(name, member.name)
}
}
const foreign: string[] = []
for (const [name, member] of selected) {
if (matchesPlatform(platformConstraint(member.manifest), platform)) continue
if (reachedByRequiredEdge(name, entries, selected)) {
throw new Error(
`${dependencyPath(name, parents, entries)} requires ${name}, which targets`
+ ` ${describePlatform(platformConstraint(member.manifest))}; this host is ${describePlatform(platform)}.`
+ ' This run cannot supply a matching artifact for that package.',
)
}
foreign.push(name)
}
return {
entries: [...entries],
members: [...selected.values()].sort((left, right) => left.name.localeCompare(right.name)),
workspaceNames: [...packages.keys()].sort(),
foreignTargets: foreign.sort(),
}
}
/**
* Pack the selected packages into one run directory and validate every archive
* against the release family that owns it.
* @param options - repository root, destination, selected members, and the public release index.
* @returns Packed artifacts sorted by package name.
*/
export async function packBundleArtifacts(options: {
readonly root: string
readonly destination: string
readonly members: readonly WorkspacePackage[]
readonly release: ReadonlyMap<string, ReleaseEntry>
readonly signal?: AbortSignal
}): Promise<BundleArtifact[]> {
const { root, destination, members, release } = options
if (members.length === 0) throw new Error('no local packages were selected to pack')
await mkdir(destination, { recursive: true })
const invocation = pnpmInvocationFor([
'pack', '--recursive', `--workspace-concurrency=${String(Math.min(4, members.length))}`,
...members.map(member => `--filter=${member.name}`), '--pack-destination', destination,
])
const result = await execa(invocation.command, invocation.args, {
cwd: root, reject: false, killDescendants: true,
...options.signal === undefined ? {} : { cancelSignal: options.signal },
})
if (result.isCanceled) throw new Error('packing was cancelled')
if (result.exitCode !== 0) {
throw new Error(`pnpm pack exited with ${String(result.exitCode ?? result.signal)}:\n${result.stdout}\n${result.stderr}`)
}
return readPackedArtifacts({ destination, members, release })
}
/**
* Validate already packed archives and describe them as servable artifacts.
* @param options - run directory, the members that were packed into it, and the public release index.
* @returns Packed artifacts sorted by package name.
*/
export function readPackedArtifacts(options: {
readonly destination: string
readonly members: readonly WorkspacePackage[]
readonly release: ReadonlyMap<string, ReleaseEntry>
}): BundleArtifact[] {
const { destination, members, release } = options
const packed = members.map((member) => {
const entry = release.get(member.name)
if (entry === undefined) {
throw new Error(`${member.name}: not a public release member; the local registry does not republish a private or unreleased package`)
}
const file = tarballName(entry.member)
const archive = join(destination, file)
if (!existsSync(archive)) throw new Error(`${member.name} produced no tarball at ${archive}`)
const contained = tarballFiles(archive)
entry.family.validatePayload(entry.member, contained)
const manifest = packedManifest(archive)
entry.family.validatePackedManifest(entry.member, manifest)
const main = manifest.main
if (typeof main === 'string' && main !== '' && !contained.includes(`package/${payloadPath(main)}`)) {
throw new Error(`${entry.member.name}: packed archive lacks its declared main ${main}; rebuild before serving`)
}
// A bundle whose patch is missing installs and then never loads its rows.
for (const patch of declaredBundlePatches(manifest)) {
if (!contained.includes(`package/${payloadPath(patch)}`)) {
throw new Error(`${entry.member.name}: packed archive lacks its declared bundle patch ${patch}`)
}
}
const bytes = readFileSync(archive)
const distTag = entry.family.distTagForVersion(entry.member.version)
return {
name: entry.member.name,
version: entry.member.version,
archive,
file,
bytes: bytes.byteLength,
integrity: `sha512-${createHash('sha512').update(bytes).digest('base64')}`,
shasum: createHash('sha1').update(bytes).digest('hex'),
manifest,
platform: platformConstraint(manifest),
...distTag === undefined ? {} : { distTag },
packedAt: new Date().toISOString(),
}
})
verifyPackedClosure(packed)
return packed.sort((left, right) => left.name.localeCompare(right.name))
}
/**
* Compose the record a completed run directory carries.
* @param options - run identity, selection, artifacts, and source evidence.
* @returns The manifest to write last.
*/
export function createRegistryManifest(options: {
readonly buildId: string
readonly source: RegistryManifest['source']
readonly entries: readonly string[]
readonly workspaceNames: readonly string[]
readonly artifacts: readonly BundleArtifact[]
}): RegistryManifest {
return {
schemaVersion: 1,
buildId: options.buildId,
source: options.source,
entries: [...options.entries],
workspaceNames: [...options.workspaceNames],
packages: [...options.artifacts]
.sort((left, right) => left.name.localeCompare(right.name))
.map(artifact => ({
name: artifact.name,
version: artifact.version,
file: artifact.file,
bytes: artifact.bytes,
integrity: artifact.integrity,
})),
}
}
/**
* Write the completion marker into a run directory.
* @param path - absolute manifest path inside the run directory.
* @param manifest - the record to serialize.
*/
export async function writeRegistryManifest(path: string, manifest: RegistryManifest): Promise<void> {
await writeFile(path, `${JSON.stringify(manifest, undefined, 2)}\n`, { mode: 0o600 })
}
/**
* Record which checkout produced a run.
* @param root - repository root.
* @param dshVersion - running DSH version the catalog installation target names.
* @returns The commit, whether the tree was dirty, and the version served.
*/
export function sourceEvidence(root: string, dshVersion: string): RegistryManifest['source'] {
return {
commit: capture('git', ['rev-parse', 'HEAD'], { cwd: root }),
dirty: capture('git', ['status', '--porcelain'], { cwd: root }) !== '',
dshVersion,
}
}
/**
* Start the loopback registry for one run's artifacts.
*
* The listener binds `127.0.0.1` on an allocated port and answers under a fresh
* namespace, so two runs never share a URL even when the port is reused.
* @param options - the run's artifacts and directory, repository-owned names, and upstream registry.
* @returns The registry URL and its close operation.
*/
export async function startBundleRegistry(options: {
readonly directory: string
readonly artifacts: readonly BundleArtifact[]
readonly workspaceNames: readonly string[]
readonly upstream?: string
}): Promise<BundleRegistry> {
if (options.artifacts.length === 0) throw new Error('the local registry was started without artifacts')
const upstream = options.upstream ?? UPSTREAM_REGISTRY
if (!upstream.endsWith('/')) throw new Error(`upstream registry must end with a slash: ${upstream}`)
const index = new Map(options.artifacts.map(artifact => [artifact.name, artifact]))
const owned = new Set(options.workspaceNames)
const namespace = randomUUID()
// Assigned once the listener reports its allocated port; requests cannot arrive before then.
let origin = ''
const server = createServer((request, response) => {
const method = request.method ?? 'GET'
const head = method === 'HEAD'
if (method !== 'GET' && method !== 'HEAD') {
respond(response, 405, { error: 'Method Not Allowed', reason: `${method} is not served` }, false)
return
}
const raw = request.url ?? '/'
const query = raw.indexOf('?')
const path = query === -1 ? raw : raw.slice(0, query)
const prefix = `/${namespace}/`
if (!path.startsWith(prefix)) {
respond(response, 404, { error: 'Not found', reason: 'request is outside this run\'s registry namespace' }, head)
return
}
const suffix = path.slice(prefix.length)
if (suffix.startsWith('tarballs/')) {
serveTarball(suffix.slice('tarballs/'.length), response, head, options.directory, index)
return
}
const requested = selection(suffix)
if (requested === undefined) {
respond(response, 404, { error: 'Not found', reason: 'malformed package path' }, head)
return
}
const artifact = index.get(requested.name)
if (artifact !== undefined) {
serveMetadata(artifact, requested.version, response, head, origin)
return
}
if (owned.has(requested.name)) {
respond(response, 404, {
error: 'Not found',
reason: `${requested.name} is repository-owned but not part of this run's local closure; rebuild the registry after changing the catalog`,
}, head)
return
}
// Only package publication is local: third-party metadata and payloads keep npm's real bytes.
response.writeHead(302, { location: `${upstream}${suffix}`, 'cache-control': 'no-store' })
response.end()
})
await new Promise<void>((resolveListen, rejectListen) => {
server.once('error', rejectListen)
server.listen(0, '127.0.0.1', () => {
server.off('error', rejectListen)
resolveListen()
})
})
const address = server.address()
if (address === null || typeof address === 'string') {
await closeServer(server)
throw new Error('the local registry has no TCP address')
}
origin = `http://127.0.0.1:${String(address.port)}/${namespace}/`
let closing: Promise<void> | undefined
return {
url: origin,
close: () => {
closing ??= closeServer(server)
return closing
},
}
}
/**
* Read a JSON manifest from disk.
* @param path - absolute `package.json` path.
* @returns The parsed object.
*/
function manifestAt(path: string): Record<string, unknown> {
const parsed: unknown = JSON.parse(readFileSync(path, 'utf8'))
if (!isRecord(parsed)) throw new Error(`${path} is not a JSON object`)
return parsed
}
/**
* Whether a parsed value is a JSON object.
* @param value - value to inspect.
* @returns True for a non-null, non-array object.
*/
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value)
}
/**
* Read one dependency section as declared ranges.
* @param manifest - package manifest.
* @param section - section name.
* @returns Name and range pairs, with malformed entries rejected.
*/
function dependencyRanges(manifest: Record<string, unknown>, section: string): [string, string][] {
const value = manifest[section]
if (value === undefined) return []
if (!isRecord(value)) throw new Error(`${section} must be a mapping`)
return Object.entries(value).map(([name, range]) => {
if (typeof range !== 'string') throw new Error(`${section}.${name} must be a string range`)
return [name, range]
})
}
/**
* Every package this manifest installs on a consumer: required, optional, and
* required peers.
* @param manifest - package manifest.
* @returns Dependency names in section order.
*/
function installedEdges(manifest: Record<string, unknown>): string[] {
const names = [...REQUIRED_SECTIONS, ...OPTIONAL_SECTIONS].flatMap(section => dependencyRanges(manifest, section).map(([name]) => name))
for (const section of PEER_SECTIONS) {
for (const [name] of dependencyRanges(manifest, section)) {
if (!optionalPeer(manifest, name)) names.push(name)
}
}
return names
}
/**
* Whether a package opts out of a peer it declares.
* @param manifest - package manifest.
* @param name - peer package name.
* @returns True when the peer is optional.
*/
function optionalPeer(manifest: Record<string, unknown>, name: string): boolean {
const metadata = manifest.peerDependenciesMeta
if (!isRecord(metadata)) return false
const entry = metadata[name]
return isRecord(entry) && entry.optional === true
}
/**
* Whether one constrained value accepts the host's own value.
* @param constraint - accepted values, which may be only `!value` negations.
* @param current - the host's value, or undefined when the field does not apply here.
* @returns True when the host value is allowed.
*/
function allowsValue(constraint: readonly string[] | undefined, current: string | undefined): boolean {
if (constraint === undefined || constraint.length === 0) return true
// A field the host does not have (libc off Linux) constrains nothing here.
if (current === undefined) return true
const positives = constraint.filter(value => !value.startsWith('!'))
if (positives.length > 0 && !positives.includes('any') && !positives.includes(current)) return false
return !constraint.includes(`!${current}`)
}
/**
* Describe a platform constraint for a failure message.
* @param constraint - declared or host constraints.
* @returns A compact description.
*/
function describePlatform(constraint: PlatformConstraint): string {
const parts = (['os', 'cpu', 'libc'] as const).flatMap(field => (constraint[field] ?? []).map(value => `${field}=${value}`))
return parts.length === 0 ? 'any platform' : parts.join(' ')
}
/**
* Whether any required edge reaches a package, ignoring optional and peer edges.
* @param target - package to look for.
* @param entries - catalog roots.
* @param selected - the whole closure.
* @returns True when a required dependency edge reaches the target.
*/
function reachedByRequiredEdge(target: string, entries: readonly string[], selected: ReadonlyMap<string, WorkspacePackage>): boolean {
const visited = new Set<string>()
const pending = [...entries]
for (let index = 0; index < pending.length; index += 1) {
const name = pending[index] as string
if (visited.has(name)) continue
visited.add(name)
const member = selected.get(name)
if (member === undefined) continue
for (const section of REQUIRED_SECTIONS) {
for (const [dependency] of dependencyRanges(member.manifest, section)) {
if (dependency === target) return true
if (selected.has(dependency)) pending.push(dependency)
}
}
}
return false
}
/**
* Render the dependency path that reached a package, for a failure message.
* @param name - the package being explained.
* @param parents - the first edge that reached each package.
* @param entries - catalog roots, which have no parent.
* @returns A `root -> ... -> name` chain.
*/
function dependencyPath(name: string, parents: ReadonlyMap<string, string>, entries: readonly string[]): string {
const chain = [name]
let current = parents.get(name)
while (current !== undefined) {
chain.unshift(current)
current = parents.get(current)
}
return chain.length === 1 && entries.includes(name) ? `catalog entry ${name}` : chain.join(' -> ')
}
/**
* Normalize a manifest path or tarball member to its package-relative form, the
* same rule the publication payload check applies.
* @param file - manifest path or archive member.
* @returns The package-relative path.
*/
function payloadPath(file: string): string {
return file.replaceAll('\\', '/').replace(/^\.\/+/u, '').replace(/^package\//u, '')
}
/**
* The bundle patch files a manifest declares, as written.
* @param manifest - packed or declared package manifest.
* @returns Package-relative patch paths, or nothing when the package is not a bundle.
*/
function declaredBundlePatches(manifest: Record<string, unknown>): string[] {
const dsh = manifest.dsh
if (!isRecord(dsh) || !isRecord(dsh.bundle)) return []
const declared = dsh.bundle.patch
if (typeof declared === 'string') return [declared]
return Array.isArray(declared) && declared.every(entry => typeof entry === 'string') ? declared : []
}
/**
* Reject packed manifests that would send installation back to the checkout or
* to a package this run does not serve.
* @param artifacts - packed artifacts of one run.
*/
function verifyPackedClosure(artifacts: readonly BundleArtifact[]): void {
const served = new Map(artifacts.map(artifact => [artifact.name, artifact]))
for (const artifact of artifacts) {
for (const section of [...REQUIRED_SECTIONS, ...OPTIONAL_SECTIONS, ...PEER_SECTIONS]) {
for (const [name, range] of dependencyRanges(artifact.manifest, section)) {
if (/^(?:workspace|file|link|portal):/u.test(range)) {
throw new Error(`${artifact.name}: packed ${section}.${name} still declares ${range}`)
}
const local = served.get(name)
if (local !== undefined && !semver.satisfies(local.version, range, { includePrerelease: true })) {
throw new Error(`${artifact.name}: packed ${section}.${name} requires ${range}, but this run serves ${local.version}`)
}
}
}
}
}
/** The package a registry request path selects. */
interface PackageRequest {
/** Package name, with its scope when it has one. */
readonly name: string
/** Exact version or dist tag the path asks for, absent for a packument request. */
readonly version?: string
}
/**
* Split a request path into the package name and version it selects.
* @param suffix - the raw request path after the namespace.
* @returns The selected package, or undefined when the path is not a package path.
*/
function selection(suffix: string): PackageRequest | undefined {
const decoded = decodeSegment(suffix)
if (decoded === undefined) return undefined
const segments = decoded.split('/')
// Reject traversal, registry administration paths, and empty segments before any name is considered.
if (segments.some(segment => segment === '' || segment === '.' || segment === '..') || segments[0]?.startsWith('-') === true) {
return undefined
}
if (segments.length === 1 && segments[0] !== undefined) return { name: segments[0] }
if (!decoded.startsWith('@')) {
const [name, version] = segments
return name !== undefined && version !== undefined && segments.length === 2 ? { name, version } : undefined
}
if (segments.length === 2) return { name: decoded }
if (segments.length === 3) {
const version = segments[2]
return version === undefined ? undefined : { name: segments.slice(0, 2).join('/'), version }
}
return undefined
}
/**
* Decode one percent-encoded path.
* @param value - raw path text.
* @returns The decoded text, or undefined when the encoding is malformed.
*/
function decodeSegment(value: string): string | undefined {
try {
return decodeURIComponent(value)
} catch {
return undefined
}
}
/**
* Serve one archive's bytes from the artifact index.
* @param file - raw requested tarball filename.
* @param response - the response to write.
* @param head - whether this is a HEAD request, whose body is omitted.
* @param directory - the run directory holding the archives.
* @param index - the run's artifact index.
*/
function serveTarball(
file: string,
response: ServerResponse,
head: boolean,
directory: string,
index: ReadonlyMap<string, BundleArtifact>,
): void {
const decoded = decodeSegment(file)
const artifact = decoded === undefined ? undefined : [...index.values()].find(entry => entry.file === decoded)
if (artifact === undefined) {
respond(response, 404, { error: 'Not found', reason: 'no archive with that name in this run' }, head)
return
}
response.writeHead(200, {
'content-type': 'application/octet-stream',
'content-length': String(artifact.bytes),
'cache-control': 'no-store',
})
if (head) {
response.end()
return
}
const stream = createReadStream(join(directory, artifact.file))
// A client that disconnects mid-archive must not fail an unrelated request.
response.on('close', () => stream.destroy())
stream.on('error', (error) => { response.destroy(error) })
stream.pipe(response)
}
/**
* Serve the packument, a version document, or a dist-tag document for one artifact.
* @param artifact - the artifact being described.
* @param version - requested version or dist tag, or undefined for the whole packument.
* @param response - the response to write.
* @param head - whether this is a HEAD request, whose body is omitted.
* @param origin - the run's registry URL.
*/
function serveMetadata(
artifact: BundleArtifact,
version: string | undefined,
response: ServerResponse,
head: boolean,
origin: string,
): void {
const tags: Record<string, string> = { latest: artifact.version }
if (artifact.distTag !== undefined && artifact.distTag !== 'latest') tags[artifact.distTag] = artifact.version
const document = {
...artifact.manifest,
dist: {
tarball: `${origin}tarballs/${encodeURIComponent(artifact.file)}`,
integrity: artifact.integrity,
shasum: artifact.shasum,
},
}
if (version === undefined) {
respond(response, 200, {
name: artifact.name,
'dist-tags': tags,
versions: { [artifact.version]: document },
time: { created: artifact.packedAt, modified: artifact.packedAt, [artifact.version]: artifact.packedAt },
}, head)
return
}
if (version !== artifact.version && !Object.hasOwn(tags, version)) {
respond(response, 404, {
error: 'Not found',
reason: `this run serves ${artifact.name}@${artifact.version} only`,
}, head)
return
}
respond(response, 200, document, head)
}
/**
* Write one JSON response.
* @param response - the response to write.
* @param status - HTTP status.
* @param body - JSON body.
* @param head - whether this is a HEAD request, whose body is omitted.
*/
function respond(response: ServerResponse, status: number, body: unknown, head: boolean): void {
const text = `${JSON.stringify(body)}\n`
response.writeHead(status, {
'content-type': 'application/json',
'content-length': String(Buffer.byteLength(text)),
'cache-control': 'no-store',
})
response.end(head ? undefined : text)
}
/**
* Close a server and everything it is still serving.
* @param server - the listener to close.
* @returns Resolves once the listener is gone.
*/
function closeServer(server: Server): Promise<void> {
return new Promise<void>((resolveClose, rejectClose) => {
server.closeAllConnections()
server.close((error) => {
if (error === undefined) resolveClose()
else rejectClose(error)
})
})
}