* fix(packages): make packed exports runtime-safe Make checked-in package manifests truthful for ordinary npm and Bun packing, and verify installed artifacts under both runtimes. Centralize package discovery, artifact inspection, and bounded process execution so CI and release publication share the same contracts. * ci: build package dependencies before checks Keep workspace jobs independent of ignored dist output now that public package exports consistently resolve built artifacts. * ci: preserve source-first engine tests Keep the broader dependency build for package and repository validation, but retain Core-only setup for engine shards so workspace tests continue exercising source modules. * ci: build engine shard dependencies Build the seven workspace packages imported by engine tests in dependency order. This preserves a single module instance per package and keeps each clean CI shard independent of ignored dist output. * ci: restore established package build boundaries Keep the original repository and engine job setup, and add installed artifact verification only after the existing package build. Avoid changing which module copies unrelated tests execute. * fix(packages): preserve Bun source identity in tarballs Retain source-first workspace resolution and ship complete source trees for Bun conditions. Verify clean installed consumers without overlaying archives, reuse consumer validation before release publication, and repair Bun 1.3.10 private source alias resolution. * refactor(tooling): reuse package and process utilities Use pkg-types for manifest I/O and types, tinyexec for subprocess lifecycle, and npm's pack listing for release staging. Preserve archive verification and project release invariants rather than reimplementing package-manager file selection. * refactor(tooling): resolve workspace roots at CLI boundaries Discover and validate the nearest workspace once, support explicit roots, and pass roots to reusable checks. Replace subprocess entrypoint dispatch with direct calls and preserve aggregate package diagnostics without adding arbitrary test timeout increases. * fix(release): enforce publication boundaries Use root version alignment and shared validated npm output parsing. Enforce the public npm registry policy and test verification-before-publication, mismatched artifacts, and partial retries without registry writes. * refactor(tooling): validate package responses with Valibot Express npm pack and manifest identity contracts as schemas, infer parsed output types, and preserve contextual failures and relative-path safety. Document Valibot as the first-party validation convention while retaining Zod at SDK boundaries. * refactor(tooling): validate manifests at input boundaries Share Valibot schemas for consumed manifest fields, recursive exports, supported workspace declarations, and npm registry responses. Infer domain types and reject malformed metadata instead of silently skipping it downstream. * refactor(tooling): group package helpers by ownership Colocate manifest and workspace contracts, separate npm response parsing from generic JSON handling, and split smoke packing, installation, and runtime checks. Remove the release tarball forwarding shim and consolidate its coverage in package-artifacts. Preserve public tooling exports and CLI commands.
131 lines
4.7 KiB
TypeScript
131 lines
4.7 KiB
TypeScript
import { afterEach, describe, expect, test } from 'bun:test'
|
|
import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises'
|
|
import { tmpdir } from 'node:os'
|
|
import { join } from 'node:path'
|
|
|
|
import { runCommand } from '@open-pencil/package-artifacts'
|
|
import { inspectTarball } from '@open-pencil/package-artifacts/tarball'
|
|
|
|
async function createPackageFixture(
|
|
root: string,
|
|
bunTarget: string,
|
|
files = ['dist']
|
|
): Promise<string> {
|
|
const packageDirectory = join(root, 'package')
|
|
await mkdir(join(packageDirectory, 'dist'), { recursive: true })
|
|
await mkdir(join(packageDirectory, 'src'), { recursive: true })
|
|
await writeFile(join(packageDirectory, 'dist/index.js'), 'export const ready = true\n')
|
|
await writeFile(join(packageDirectory, 'dist/index.d.ts'), 'export declare const ready: true\n')
|
|
await writeFile(
|
|
join(packageDirectory, 'src/index.ts'),
|
|
"export { ready } from '#fixture/value'\n"
|
|
)
|
|
await writeFile(join(packageDirectory, 'src/value.ts'), 'export const ready = true\n')
|
|
await writeFile(
|
|
join(packageDirectory, 'package.json'),
|
|
`${JSON.stringify(
|
|
{
|
|
name: '@fixture/resolution',
|
|
version: '1.0.0',
|
|
type: 'module',
|
|
imports: { '#fixture/*': './src/*.ts' },
|
|
files,
|
|
exports: {
|
|
'.': {
|
|
types: './dist/index.d.ts',
|
|
bun: bunTarget,
|
|
import: './dist/index.js',
|
|
default: './dist/index.js'
|
|
}
|
|
}
|
|
},
|
|
null,
|
|
2
|
|
)}\n`
|
|
)
|
|
return packageDirectory
|
|
}
|
|
|
|
import { evaluateRuntime } from '../src/smoke/runtime'
|
|
|
|
const temporaryDirectories: string[] = []
|
|
|
|
afterEach(async () => {
|
|
await Promise.all(
|
|
temporaryDirectories.splice(0).map((path) => rm(path, { recursive: true, force: true }))
|
|
)
|
|
})
|
|
|
|
async function packAndInstall(bunTarget: string, files = ['dist']) {
|
|
const root = await mkdtemp(join(tmpdir(), 'open-pencil-package-resolution-'))
|
|
temporaryDirectories.push(root)
|
|
const packageDirectory = await createPackageFixture(root, bunTarget, files)
|
|
const artifacts = join(root, 'artifacts')
|
|
const consumer = join(root, 'consumer')
|
|
await mkdir(artifacts)
|
|
await mkdir(consumer)
|
|
const packed = await runCommand({
|
|
command: 'npm',
|
|
args: ['pack', '--json', '--pack-destination', artifacts],
|
|
cwd: packageDirectory
|
|
})
|
|
const filename = (JSON.parse(packed.stdout) as Array<{ filename: string }>)[0]?.filename
|
|
if (!filename) throw new Error('Fixture pack did not produce a tarball')
|
|
const tarball = join(artifacts, filename)
|
|
await runCommand({ command: 'npm', args: ['init', '-y'], cwd: consumer })
|
|
await runCommand({
|
|
command: 'npm',
|
|
args: ['install', '--ignore-scripts', '--no-audit', '--no-fund', tarball],
|
|
cwd: consumer
|
|
})
|
|
return { consumer, tarball }
|
|
}
|
|
|
|
describe('packed runtime resolution', () => {
|
|
test('imports the same packed artifact under Node and Bun', async () => {
|
|
const { consumer, tarball } = await packAndInstall('./dist/index.js')
|
|
expect((await inspectTarball(tarball)).diagnostics).toEqual([])
|
|
for (const runtime of ['node', 'bun'] as const) {
|
|
await evaluateRuntime(
|
|
runtime,
|
|
"const value = await import('@fixture/resolution'); if (!value.ready) throw new Error('not ready')",
|
|
consumer
|
|
)
|
|
}
|
|
})
|
|
|
|
test('ships transitive source imports with the source directory', async () => {
|
|
const { consumer, tarball } = await packAndInstall('./src/index.ts', ['dist', 'src'])
|
|
expect((await inspectTarball(tarball)).diagnostics).toEqual([])
|
|
for (const runtime of ['node', 'bun'] as const) {
|
|
await evaluateRuntime(
|
|
runtime,
|
|
"const { ready } = await import('@fixture/resolution'); if (!ready) throw new Error('missing value')",
|
|
consumer
|
|
)
|
|
}
|
|
}, 120_000)
|
|
|
|
test('an entrypoint-only file list does not satisfy transitive imports', async () => {
|
|
const { consumer, tarball } = await packAndInstall('./src/index.ts', ['dist', 'src/index.ts'])
|
|
expect((await inspectTarball(tarball)).diagnostics).toEqual([])
|
|
await expect(
|
|
evaluateRuntime('bun', "await import('@fixture/resolution')", consumer)
|
|
).rejects.toThrow()
|
|
}, 120_000)
|
|
|
|
test('detects the source-only Bun condition from issue 663', async () => {
|
|
const { consumer, tarball } = await packAndInstall('./src/index.ts')
|
|
expect((await inspectTarball(tarball)).diagnostics).toEqual([
|
|
expect.objectContaining({
|
|
field: 'exports["."].bun',
|
|
message: 'target is missing (./src/index.ts)'
|
|
})
|
|
])
|
|
await evaluateRuntime('node', "await import('@fixture/resolution')", consumer)
|
|
await expect(
|
|
evaluateRuntime('bun', "await import('@fixture/resolution')", consumer)
|
|
).rejects.toThrow('failed: exit code 1')
|
|
})
|
|
})
|