@orkestrel/scaffold 0.0.49 → 0.0.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/dist/bin/main.js +229 -88
- package/dist/bin/main.js.map +1 -1
- package/dist/host/AGENTS.md +2 -2
- package/dist/host/agents/orchestration.md +42 -15
- package/dist/host/claude/agents/orkestrel.md +54 -54
- package/dist/host/claude/rules/architecture.md +6 -6
- package/dist/host/claude/rules/tests.md +10 -4
- package/dist/host/claude/rules/workspace.md +1 -1
- package/dist/host/codex/config.toml +1 -1
- package/dist/host/guides/scaffold.md +353 -117
- package/dist/host/manifest.json +10 -10
- package/dist/host/scripts/codex.sh +0 -0
- package/dist/host/scripts/cursor.sh +0 -0
- package/dist/host/scripts/deps.sh +0 -0
- package/dist/host/scripts/ollama.sh +0 -0
- package/dist/host/tests/config.test.ts +83 -5
- package/dist/src/core/index.cjs +1683 -431
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +222 -55
- package/dist/src/core/index.d.ts +222 -55
- package/dist/src/core/index.js +1678 -432
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +47 -15
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +42 -15
- package/dist/src/server/index.d.ts +42 -15
- package/dist/src/server/index.js +48 -17
- package/dist/src/server/index.js.map +1 -1
- package/package.json +9 -9
package/dist/src/core/index.d.ts
CHANGED
|
@@ -95,6 +95,15 @@ export declare const ARTIFACT_TEMPLATES: Readonly<{
|
|
|
95
95
|
global: "export function setup(): void {}\n";
|
|
96
96
|
entry: "import * as entry from {{specifier}}\nimport { describe, expect, it } from 'vitest'\n\ndescribe({{label}}, () => {\n\tit('has no starter exports', () => {\n\t\texpect(Object.keys(entry)).toStrictEqual([])\n\t})\n})\n";
|
|
97
97
|
bin: "import { describe, expect, it } from 'vitest'\n\ndescribe('bin entry', () => {\n\tit('has no starter exports', async () => {\n{{import}}\n\t\texpect(Object.keys(entry)).toStrictEqual([])\n\t})\n})\n";
|
|
98
|
+
distribution: Readonly<{
|
|
99
|
+
proof: "// The artifact a consumer installs, measured rather than described. This workspace\n// is packed and installed into a throwaway consumer, and every following claim is read\n// off that installed tree: the exports map it publishes, the declarations it ships,\n// and the module objects a real runtime hands a consumer. Nothing here names this\n// package, one of its exports, or how many there are, so the proof stays true as\n// the published surface moves.\n{{types}}import type { SpawnSyncReturns } from 'node:child_process'\nimport type { TestContext } from 'vitest'\nimport { spawnSync } from 'node:child_process'\nimport {\n\texistsSync,\n\tmkdirSync,\n\tmkdtempSync,\n\treaddirSync,\n\treadFileSync,\n\trmSync,\n\tstatSync,\n\twriteFileSync,\n} from 'node:fs'\n{{transport}}import { tmpdir } from 'node:os'\nimport { dirname, join, resolve } from 'node:path'\nimport { fileURLToPath } from 'node:url'\n{{launcher}}import ts from 'typescript'\nimport { afterAll, describe, expect, it } from 'vitest'\n\nconst ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')\nconst NPM = process.platform === 'win32' ? 'npm.cmd' : 'npm'\n// Windows needs a shell to launch a `.cmd`: Node refuses one directly since the\n// batch-argument hardening, and `spawnSync` returns `EINVAL` with a null status\n// rather than an exit code a caller can read. Every following argument is a literal or\n// a path this file built, so the shell has nothing to escape.\nconst SHELL = process.platform === 'win32'\n// `prepublishOnly` runs this proof as `npm run test:distribution -- --mode release`.\n// Release is the publish gate, so evidence it cannot obtain fails there and skips\n// everywhere else: a gate that passes on missing evidence proves nothing.\nconst RELEASE = import.meta.env.MODE === 'release'\n// The built output directory convention a browser face may publish from. Every\n// selection reads this prefix off the export target and never off the subpath name. A\n// workspace whose only published face is the browser one publishes that face at the\n// root subpath, so a rule keyed on the subpath name drives a browser bundle through\n// Node and the miss is silent.\nconst BROWSER_OUTPUT = './dist/src/browser/'\nconst ABSENT_SUBPATH = '/no-subpath-is-published-under-this-name'\nconst PING = ['ping', '--fetch-retries=0', '--fetch-timeout=5000', '--loglevel=silent']\nconst ESM_DRIVER = 'drive.mjs'\nconst CJS_DRIVER = 'drive.cjs'\nconst CONSUMER_MANIFEST = `{ \"name\": \"distribution-consumer\", \"private\": true, \"type\": \"module\" }\\n`\nconst ESM_DRIVER_SOURCE = `const entry = await import(process.argv[2])\nprocess.stdout.write(JSON.stringify(Object.keys(entry).sort()))\n`\nconst CJS_DRIVER_SOURCE = `const entry = require(process.argv[2])\nprocess.stdout.write(JSON.stringify(Object.keys(entry).sort()))\n`\n\n// The extensions a JavaScript handler loads as modules. Node loads a native addon\n// through its addon handler instead, so that extension is named separately.\nconst MODULE_EXTENSIONS = ['.js', '.mjs', '.cjs']\nconst ADDON_EXTENSION = '.node'\n// The extensions a declaration file carries. A `require` condition declares\n// `.d.cts` and an ESM-only one `.d.mts`, so the `.d.ts` spelling alone does not\n// name them.\nconst DECLARATION_EXTENSIONS = ['.d.ts', '.d.cts', '.d.mts']\ntype Format = 'module' | 'commonjs'\n\n// The Node import target is resolved with the conditions that driver supplies. The\n// CommonJS compile probe is selected from its declaration's format, and its runtime\n// drive loads the same subpath through Node's require resolver. Vite's production\n// client build enables its module and browser conditions.\nconst RUNTIME_CONDITIONS = Object.freeze({\n\tmodule: Object.freeze(['node-addons', 'node', 'import', 'module-sync']),\n\tcommonjs: Object.freeze(['node-addons', 'node', 'require', 'module-sync']),\n\tbrowser: Object.freeze(['module', 'browser', 'production', 'import']),\n})\n// TypeScript's Node resolutions add `node` to the format condition. Its bundler\n// resolution does not, so a browser drive compares against the declaration a bundler\n// consumer reads rather than borrowing the Node declaration.\nconst BUNDLER_CONDITIONS = Object.freeze({\n\tmodule: ['types', 'import'],\n\tcommonjs: ['types', 'require'],\n})\nconst DECLARATION_CONDITIONS = Object.freeze({\n\tmodule: ['types', 'node', 'import'],\n\tcommonjs: ['types', 'node', 'require'],\n\tbrowser: BUNDLER_CONDITIONS.module,\n})\n\ninterface Resolution {\n\treadonly label: string\n\treadonly resolution: ts.ModuleResolutionKind\n\treadonly module: ts.ModuleKind\n\treadonly conditions: Readonly<Record<Format, readonly string[]>>\n}\n\ninterface TargetResolution {\n\treadonly target: string\n}\n\n// Each compile driver carries the conditions TypeScript applies for its resolution\n// and importing format. A `require`-only subpath therefore stays in each CommonJS\n// probe that can resolve it.\nconst RESOLUTIONS: readonly Resolution[] = [\n\t{\n\t\tlabel: 'node16',\n\t\tresolution: ts.ModuleResolutionKind.Node16,\n\t\tmodule: ts.ModuleKind.Node16,\n\t\tconditions: DECLARATION_CONDITIONS,\n\t},\n\t{\n\t\tlabel: 'nodenext',\n\t\tresolution: ts.ModuleResolutionKind.NodeNext,\n\t\tmodule: ts.ModuleKind.NodeNext,\n\t\tconditions: DECLARATION_CONDITIONS,\n\t},\n\t{\n\t\tlabel: 'bundler',\n\t\tresolution: ts.ModuleResolutionKind.Bundler,\n\t\tmodule: ts.ModuleKind.ESNext,\n\t\tconditions: BUNDLER_CONDITIONS,\n\t},\n]\n\nconst FORMATS: ReadonlyArray<readonly [extension: string, format: Format]> = [\n\t['ts', 'module'],\n\t['cts', 'commonjs'],\n]\n\n// One published subpath, resolved to what this proof can drive: the specifier a\n// consumer writes, the declarations its consumer formats name, whether its target\n// is a browser bundle, and whether it answers `import` and `require` at all.\ninterface Entry {\n\treadonly subpath: string\n\treadonly specifier: string\n\treadonly mapping: unknown\n\treadonly declaration: {\n\t\treadonly module: string | undefined\n\t\treadonly commonjs: string | undefined\n\t\treadonly browser: string | undefined\n\t}\n\treadonly browser: boolean\n\treadonly module: boolean\n\treadonly commonjs: boolean\n\treadonly required: boolean\n}\n\n// The installed tree every claim is read from. Every subpath the exports map names\n// lands in exactly one of `entries`, `undeclared`, and `excluded`, so a subpath this\n// proof cannot drive is reported rather than dropped.\ninterface Stage {\n\treadonly consumer: string\n\treadonly installed: string\n\treadonly archives: readonly string[]\n\treadonly entries: readonly Entry[]\n\treadonly subpaths: readonly string[]\n\treadonly undeclared: readonly string[]\n\treadonly excluded: readonly string[]\n\treadonly targets: readonly string[]\n}\n\nfunction isRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n\treturn typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction isNames(value: unknown): value is readonly string[] {\n\treturn Array.isArray(value) && value.every((name) => typeof name === 'string')\n}\n\n// A fallback list, which is what Node reads an array in an exports entry as. The\n// narrowing is what the following walkers need: `Array.isArray` widens an `unknown`\n// member to `any`, and an entry read that way is not read at all.\nfunction isList(value: unknown): value is readonly unknown[] {\n\treturn Array.isArray(value)\n}\n\n// Whether a string is a valid package target. Node rejects a target outside the\n// package and a target containing a dot, parent, or node_modules segment during\n// package-target resolution. A later module-resolution failure is not the same\n// thing: an array falls through the former and keeps the latter.\nfunction isPackageTarget(target: string): boolean {\n\tif (!target.startsWith('./')) return false\n\tfor (const segment of target.slice(2).split(/[\\\\/]/u)) {\n\t\tlet decoded = segment\n\t\ttry {\n\t\t\tdecoded = decodeURIComponent(segment)\n\t\t} catch {}\n\t\tconst normalized = decoded.toLowerCase()\n\t\tif (normalized === '.' || normalized === '..' || normalized === 'node_modules') return false\n\t}\n\treturn true\n}\n\nfunction readJson(path: string): unknown {\n\tconst parsed: unknown = JSON.parse(readFileSync(path, 'utf8'))\n\treturn parsed\n}\n\nfunction readManifestName(path: string): string {\n\tconst manifest = readJson(path)\n\tif (!isRecord(manifest) || typeof manifest.name !== 'string') {\n\t\tthrow new Error(`The manifest at ${path} declares no package name`)\n\t}\n\treturn manifest.name\n}\n\nfunction writeFile(path: string, content: string): void {\n\tmkdirSync(dirname(path), { recursive: true })\n\twriteFileSync(path, content)\n}\n\nfunction readOutput(result: SpawnSyncReturns<string>): string {\n\treturn `${result.stdout ?? ''}${result.stderr ?? ''}`.trim()\n}\n\nfunction runNpm(args: readonly string[], cwd: string): SpawnSyncReturns<string> {\n\treturn spawnSync(NPM, [...args], {\n\t\tcwd,\n\t\tencoding: 'utf8',\n\t\tenv: { ...process.env, npm_config_cache: CACHE },\n\t\tshell: SHELL,\n\t\twindowsHide: true,\n\t})\n}\n\nfunction runNode(args: readonly string[], cwd: string): SpawnSyncReturns<string> {\n\treturn spawnSync(process.execPath, [...args], { cwd, encoding: 'utf8', windowsHide: true })\n}\n\n// Node's own condition matching, read in declaration order.\nfunction resolvePackageTarget(\n\tentry: unknown,\n\tconditions: readonly string[],\n): TargetResolution | undefined {\n\tif (typeof entry === 'string') return { target: entry }\n\tif (isList(entry)) {\n\t\tfor (const member of entry) {\n\t\t\tconst resolved = resolvePackageTarget(member, conditions)\n\t\t\tif (resolved !== undefined && isPackageTarget(resolved.target)) return resolved\n\t\t}\n\t\treturn undefined\n\t}\n\tif (!isRecord(entry)) return undefined\n\tfor (const [condition, nested] of Object.entries(entry)) {\n\t\tif (condition !== 'default' && !conditions.includes(condition)) continue\n\t\tconst resolved = resolvePackageTarget(nested, conditions)\n\t\tif (resolved !== undefined) return resolved\n\t}\n\treturn undefined\n}\n\n// A flat entry, a condition-nested entry, and a fallback list all resolve through\n// one walker. An entry may declare `types` beside `default` at its top level\n// rather than inside `import`, so a fixed `entry.import.types` lookup is not\n// equivalent to condition resolution.\nfunction resolveTarget(entry: unknown, conditions: readonly string[]): string | undefined {\n\treturn resolvePackageTarget(entry, conditions)?.target\n}\n\n// Whether a path is a physical file. TypeScript's file-existence check refuses a\n// directory at the same spelling and continues to the outer package scope.\nfunction matchesFile(path: string): boolean {\n\ttry {\n\t\treturn statSync(path).isFile()\n\t} catch {\n\t\treturn false\n\t}\n}\n\n// TypeScript resolves a declaration target by accepting an existing declaration\n// directly or by substituting beside a JavaScript target. A missing target leaves\n// the containing condition or fallback list unresolved, so the walk continues.\nfunction targetToDeclaration(target: string, installed: string): string | undefined {\n\tif (!isPackageTarget(target)) return undefined\n\tlet declaration = target\n\tif (target.endsWith('.cjs')) declaration = `${target.slice(0, -4)}.d.cts`\n\telse if (target.endsWith('.mjs')) declaration = `${target.slice(0, -4)}.d.mts`\n\telse if (target.endsWith('.js')) declaration = `${target.slice(0, -3)}.d.ts`\n\telse if (!isDeclaration(target)) return undefined\n\treturn matchesFile(join(installed, declaration)) ? declaration : undefined\n}\n\n// The declaration TypeScript resolves through one importing format's conditions.\n// Condition objects keep manifest order, and arrays keep fallback order.\nfunction resolveDeclaration(\n\tentry: unknown,\n\tconditions: readonly string[],\n\tinstalled: string,\n): string | undefined {\n\tif (typeof entry === 'string') return targetToDeclaration(entry, installed)\n\tif (isList(entry)) {\n\t\tfor (const member of entry) {\n\t\t\tconst resolved = resolveDeclaration(member, conditions, installed)\n\t\t\tif (resolved !== undefined) return resolved\n\t\t}\n\t\treturn undefined\n\t}\n\tif (!isRecord(entry)) return undefined\n\tfor (const [condition, nested] of Object.entries(entry)) {\n\t\tif (condition !== 'default' && !conditions.includes(condition)) continue\n\t\tconst resolved = resolveDeclaration(nested, conditions, installed)\n\t\tif (resolved !== undefined) return resolved\n\t}\n\treturn undefined\n}\n\n// The nearest package scope that decides a `.d.ts` declaration's module format. A\n// physical nested manifest starts a scope even when it omits `type` or cannot be\n// parsed. A directory at that spelling is not a manifest, so the walk continues.\nfunction readPackageType(installed: string, target: string): unknown {\n\tlet directory = dirname(join(installed, target))\n\twhile (true) {\n\t\tconst path = join(directory, 'package.json')\n\t\tif (matchesFile(path)) {\n\t\t\ttry {\n\t\t\t\tconst manifest = readJson(path)\n\t\t\t\treturn isRecord(manifest) ? manifest.type : undefined\n\t\t\t} catch {\n\t\t\t\treturn undefined\n\t\t\t}\n\t\t}\n\t\tif (directory === installed) return undefined\n\t\tconst parent = dirname(directory)\n\t\tif (parent === directory) return undefined\n\t\tdirectory = parent\n\t}\n}\n\nfunction resolvesBrowser(entry: unknown): boolean {\n\tconst module = resolveTarget(entry, RUNTIME_CONDITIONS.browser)\n\tif (module !== undefined && module.startsWith(BROWSER_OUTPUT)) return true\n\tif (module === undefined) return false\n\tconst imported = resolveTarget(entry, RUNTIME_CONDITIONS.module)\n\tconst required = resolveTarget(entry, RUNTIME_CONDITIONS.commonjs)\n\treturn module !== imported && module !== required\n}\n\n// Whether the target selected by Node's CommonJS conditions is a module require can\n// load. A JavaScript target takes its own nearest package scope. Native addons and\n// extensionless targets have their own CommonJS handlers.\nfunction resolvesCommonJS(entry: unknown, installed: string): boolean {\n\tconst target = resolveTarget(entry, RUNTIME_CONDITIONS.commonjs)\n\tif (target === undefined) return false\n\tconst name = target.slice(target.lastIndexOf('/') + 1)\n\tif (name.endsWith('.cjs')) return true\n\tif (name.endsWith('.mjs')) return false\n\tif (name.endsWith('.node')) return true\n\tif (!name.includes('.')) return true\n\treturn name.endsWith('.js') && readPackageType(installed, target) !== 'module'\n}\n\n// Whether the declaration selected by a typed CommonJS consumer admits that entry.\n// A `.d.cts` declaration admits and a `.d.mts` declaration refuses. A `.d.ts`\n// declaration takes its own nearest package scope.\nfunction declaresCommonJS(entry: unknown, installed: string): boolean {\n\tconst declaration = resolveDeclaration(entry, DECLARATION_CONDITIONS.commonjs, installed)\n\tif (declaration === undefined) return false\n\tif (declaration.endsWith('.d.cts')) return true\n\tif (declaration.endsWith('.d.mts')) return false\n\treturn declaration.endsWith('.d.ts') && readPackageType(installed, declaration) !== 'module'\n}\n\n// Every target an entry names under any condition. A fallback list omits members\n// Node rejects during package-target validation, because no reader can take them.\nfunction collectTargets(entry: unknown): readonly string[] {\n\tif (typeof entry === 'string') return [entry]\n\tif (isList(entry)) return entry.flatMap(collectTargets).filter(isPackageTarget)\n\tif (!isRecord(entry)) return []\n\treturn Object.values(entry).flatMap((nested) => collectTargets(nested))\n}\n\n// Whether a target is a file a runtime loads for its names, which is what a\n// declaration is owed for. The extension on the target's own file name decides it,\n// and a name carrying no extension is code: `require` reads such a file through its\n// JavaScript handler, so an extensionless target loads and publishes names. Node\n// loads `.node` through its native-addon handler. Every other extension is an asset\n// a consumer reads rather than imports — a stylesheet, a WebAssembly binary, the\n// `\"./package.json\"` manifest pointer, and a declaration alike.\n// The cost is an extensionless file published for a reader, such as a `LICENSE`:\n// that target reports undeclared until it is given an extension or a declaration.\nfunction isModule(target: string): boolean {\n\tconst name = target.slice(target.lastIndexOf('/') + 1)\n\tconst dot = name.lastIndexOf('.')\n\tif (name.endsWith(ADDON_EXTENSION)) return true\n\treturn dot === -1 || MODULE_EXTENSIONS.includes(name.slice(dot))\n}\n\n// Whether a resolved target is a declaration rather than the JavaScript a\n// `default` branch answers with when the entry declares no `types` condition.\nfunction isDeclaration(target: string): boolean {\n\treturn DECLARATION_EXTENSIONS.some((extension) => target.endsWith(extension))\n}\n\n// The declarations the Node module, Node CommonJS, and browser drives compare\n// against. Each field uses the conditions of the TypeScript consumer paired with\n// that runtime. A JavaScript target resolves through TypeScript's adjacent\n// declaration substitution rather than standing in for the declaration itself.\nfunction readDeclaration(entry: unknown, installed: string): Entry['declaration'] {\n\treturn {\n\t\tmodule: resolveDeclaration(entry, DECLARATION_CONDITIONS.module, installed),\n\t\tcommonjs: resolveDeclaration(entry, DECLARATION_CONDITIONS.commonjs, installed),\n\t\tbrowser: resolveDeclaration(entry, DECLARATION_CONDITIONS.browser, installed),\n\t}\n}\n\n// The entries one compile driver can resolve under its own conditions.\nfunction selectEntries(entries: readonly Entry[], conditions: readonly string[]): readonly Entry[] {\n\treturn entries.filter(\n\t\t(entry) =>\n\t\t\tresolveTarget(entry.mapping, conditions) !== undefined &&\n\t\t\t(!conditions.includes('require') || entry.commonjs),\n\t)\n}\n\n// Require-loadable entries that declare CommonJS support but a typed CommonJS\n// consumer cannot compile against. A default branch resolving under the require\n// condition set makes no CommonJS claim.\nfunction selectUntypable(entries: readonly Entry[], installed: string): readonly Entry[] {\n\treturn entries.filter(\n\t\t(entry) =>\n\t\t\tentry.required &&\n\t\t\tisRecord(entry.mapping) &&\n\t\t\tObject.hasOwn(entry.mapping, 'require') &&\n\t\t\t!declaresCommonJS(entry.mapping, installed),\n\t)\n}\n\n// The value exports a declaration publishes, read through the compiler's checker\n// over the module symbol rather than off the declaration text. An alias resolves to\n// what it names, so a re-export counts as the thing it re-exports, and a type-only\n// symbol is dropped because no runtime publishes one.\nfunction readDeclaredExports(declaration: string): readonly string[] {\n\tconst program = ts.createProgram([declaration], {\n\t\tmodule: ts.ModuleKind.ESNext,\n\t\tmoduleResolution: ts.ModuleResolutionKind.Bundler,\n\t\tnoEmit: true,\n\t\tskipLibCheck: true,\n\t\ttarget: ts.ScriptTarget.ESNext,\n\t})\n\tconst source = program.getSourceFile(declaration)\n\tif (source === undefined) throw new Error(`The declaration ${declaration} was not read`)\n\tconst checker = program.getTypeChecker()\n\tconst symbol = checker.getSymbolAtLocation(source)\n\tif (symbol === undefined) throw new Error(`${declaration} declares no module symbol`)\n\tconst values: string[] = []\n\tfor (const exported of checker.getExportsOfModule(symbol)) {\n\t\tconst direct = (exported.flags & ts.SymbolFlags.Alias) === 0\n\t\tconst resolved = direct ? exported : checker.getAliasedSymbol(exported)\n\t\tif ((resolved.flags & ts.SymbolFlags.Value) !== 0) values.push(exported.getName())\n\t}\n\treturn [...values].sort()\n}\n\n// The diagnostics a consumer compiling against the installed declarations reports,\n// flattened to their messages so a failure names what the consumer could not do.\nfunction compileConsumer(\n\tentry: string,\n\tresolution: ts.ModuleResolutionKind,\n\tmodule: ts.ModuleKind,\n): readonly string[] {\n\tconst program = ts.createProgram([entry], {\n\t\tmodule,\n\t\tmoduleResolution: resolution,\n\t\tnoEmit: true,\n\t\tskipLibCheck: true,\n\t\tstrict: true,\n\t\ttarget: ts.ScriptTarget.ESNext,\n\t})\n\treturn ts\n\t\t.getPreEmitDiagnostics(program)\n\t\t.map((diagnostic) => ts.flattenDiagnosticMessageText(diagnostic.messageText, ' '))\n}\n\n// One consumer module importing every installed entry, written where its own\n// resolution finds the installed package.\nfunction writeConsumerProbe(stage: Stage, path: string, specifiers: readonly string[]): string {\n\tconst names: string[] = []\n\tconst bindings: string[] = []\n\tfor (const [index, specifier] of specifiers.entries()) {\n\t\tconst binding = `entry${String(index)}`\n\t\tnames.push(binding)\n\t\tbindings.push(`import * as ${binding} from ${JSON.stringify(specifier)}`)\n\t}\n\tconst target = join(stage.consumer, path)\n\twriteFile(target, `${bindings.join('\\n')}\\nexport const surface = [${names.join(', ')}]\\n`)\n\treturn target\n}\n\n// The runtime key set a real process reads off one installed entry under one\n// condition. The driver is a file rather than an `--eval` string, so the specifier\n// travels as an argument and nothing needs escaping.\nfunction driveRuntime(stage: Stage, specifier: string, driver: string): readonly string[] {\n\tconst result = runNode([join(stage.consumer, driver), specifier], stage.consumer)\n\tif (result.status !== 0) {\n\t\tthrow new Error(`Loading ${specifier} from the consumer failed: ${readOutput(result)}`)\n\t}\n\tconst published: unknown = JSON.parse(result.stdout)\n\tif (!isNames(published)) throw new Error(`The driver printed no name list for ${specifier}`)\n\treturn published\n}\n{{helpers}}\n// Pack this workspace, install the archive into an isolated consumer, and read the\n// published surface back off the installed tree. Every later claim reads this\n// result, so a failure here is raised where it happens rather than once per entry.\nfunction buildStage(): Stage {\n\tconst packed = join(SCRATCH, 'packed')\n\tconst consumer = join(SCRATCH, 'consumer')\n\tmkdirSync(packed, { recursive: true })\n\tconst pack = runNpm(['pack', '--ignore-scripts', '--pack-destination', packed], ROOT)\n\tif (pack.status !== 0) throw new Error(`npm pack refused this workspace: ${readOutput(pack)}`)\n\tconst archives = readdirSync(packed).filter((name) => name.endsWith('.tgz'))\n\tconst archive = archives[0]\n\tif (archives.length !== 1 || archive === undefined) {\n\t\tthrow new Error(`npm pack wrote no single archive: ${archives.join(', ')}`)\n\t}\n\twriteFile(join(consumer, 'package.json'), CONSUMER_MANIFEST)\n\twriteFile(join(consumer, ESM_DRIVER), ESM_DRIVER_SOURCE)\n\twriteFile(join(consumer, CJS_DRIVER), CJS_DRIVER_SOURCE)\n\tconst install = runNpm(\n\t\t['install', '--ignore-scripts', '--no-audit', '--no-fund', join(packed, archive)],\n\t\tconsumer,\n\t)\n\tif (install.status !== 0) {\n\t\tthrow new Error(`Installing the packed archive failed: ${readOutput(install)}`)\n\t}\n\tconst name = readManifestName(join(ROOT, 'package.json'))\n\tconst installed = join(consumer, 'node_modules', ...name.split('/'))\n\tconst manifest = readJson(join(installed, 'package.json'))\n\tif (!isRecord(manifest) || !isRecord(manifest.exports)) {\n\t\tthrow new Error('The installed manifest publishes no exports map')\n\t}\n\tconst entries: Entry[] = []\n\tconst targets: string[] = []\n\tconst subpaths: string[] = []\n\tconst undeclared: string[] = []\n\tconst excluded: string[] = []\n\tfor (const [subpath, entry] of Object.entries(manifest.exports)) {\n\t\tconst files = collectTargets(entry)\n\t\ttargets.push(...files)\n\t\tsubpaths.push(subpath)\n\t\tconst declaration = readDeclaration(entry, installed)\n\t\t// A subpath resolving no declaration is partitioned rather than dropped. It is a\n\t\t// defect when a runtime loads one of its targets for names, because a consumer\n\t\t// importing it compiles against nothing under `node16`. It is an excluded\n\t\t// publication otherwise: the `\"./package.json\"` manifest pointer and a stylesheet\n\t\t// are published for a reader rather than an importer.\n\t\tif (\n\t\t\tdeclaration.module === undefined &&\n\t\t\tdeclaration.commonjs === undefined &&\n\t\t\tdeclaration.browser === undefined\n\t\t) {\n\t\t\tif (files.some(isModule)) undeclared.push(subpath)\n\t\t\telse excluded.push(subpath)\n\t\t\tcontinue\n\t\t}\n\t\tconst imported = resolveTarget(entry, RUNTIME_CONDITIONS.module)\n\t\tconst requiredTarget = resolveTarget(entry, RUNTIME_CONDITIONS.commonjs)\n\t\tconst browserTarget = resolveTarget(entry, RUNTIME_CONDITIONS.browser)\n\t\tconst browser = resolvesBrowser(entry)\n\t\tconst required = requiredTarget !== undefined && !(browser && requiredTarget === browserTarget)\n\t\tconst commonjs = required && resolvesCommonJS(entry, installed)\n\t\tentries.push({\n\t\t\tsubpath,\n\t\t\tspecifier: subpath === '.' ? name : `${name}${subpath.slice(1)}`,\n\t\t\tmapping: entry,\n\t\t\tdeclaration: {\n\t\t\t\tmodule: declaration.module === undefined ? undefined : join(installed, declaration.module),\n\t\t\t\tcommonjs:\n\t\t\t\t\tdeclaration.commonjs === undefined ? undefined : join(installed, declaration.commonjs),\n\t\t\t\tbrowser:\n\t\t\t\t\tdeclaration.browser === undefined ? undefined : join(installed, declaration.browser),\n\t\t\t},\n\t\t\tbrowser,\n\t\t\tmodule: imported !== undefined && !(browser && imported === browserTarget),\n\t\t\tcommonjs,\n\t\t\trequired,\n\t\t})\n\t}\n\treturn { consumer, installed, archives, entries, subpaths, undeclared, excluded, targets }\n}\n\nconst SCRATCH = mkdtempSync(join(tmpdir(), 'distribution-'))\nconst CACHE = join(SCRATCH, 'cache')\nmkdirSync(CACHE, { recursive: true })\n// The scratch tree holds the npm cache, the packed archive, and the installed\n// consumer, so its removal is registered before the first thing that can throw.\nafterAll(() => {\n\trmSync(SCRATCH, { force: true, recursive: true })\n})\n\n// Installing the packed archive resolves its own runtime dependencies, so an\n// unreachable registry leaves nothing to measure. Under release that is the gate\n// failing; anywhere else the suite skips and names the mechanism it wanted.\n//\n// A module that throws while loading never reaches the `afterAll` it registered,\n// so every throw here removes the scratch tree on its way out.\nfunction openStage(): Stage | undefined {\n\ttry {\n\t\tif (runNpm(PING, ROOT).status !== 0) {\n\t\t\tif (!RELEASE) return undefined\n\t\t\tthrow new Error(\n\t\t\t\t'The release gate requires a reachable npm registry, and npm ping did not answer',\n\t\t\t)\n\t\t}\n\t\treturn buildStage()\n\t} catch (error) {\n\t\trmSync(SCRATCH, { force: true, recursive: true })\n\t\tthrow error\n\t}\n}\n\nconst STAGE = openStage()\nconst STAGED = STAGE !== undefined\n\ndescribe('distribution classifiers', () => {\n\tit('classifies synthetic export mappings without a registry stage', () => {\n\t\tconst root = join(SCRATCH, 'classifiers')\n\t\twriteFile(\n\t\t\tjoin(root, 'package.json'),\n\t\t\tJSON.stringify({\n\t\t\t\ttype: 'commonjs',\n\t\t\t\texports: {\n\t\t\t\t\tcondition: { browser: './b.js', default: './n.js' },\n\t\t\t\t\tconvention: { default: './dist/src/browser/index.js' },\n\t\t\t\t\tuniversal: { default: './shared.js' },\n\t\t\t\t\t'import-shared': {\n\t\t\t\t\t\tbrowser: './shared.mjs',\n\t\t\t\t\t\timport: './shared.mjs',\n\t\t\t\t\t\tdefault: './node.js',\n\t\t\t\t\t},\n\t\t\t\t\t'require-shared': {\n\t\t\t\t\t\tbrowser: './shared.cjs',\n\t\t\t\t\t\trequire: './shared.cjs',\n\t\t\t\t\t\tdefault: './node.js',\n\t\t\t\t\t},\n\t\t\t\t\tnode: { node: './node.js', default: './node.js' },\n\t\t\t\t\tsilent: { 'module-sync': './x.cjs', import: './x.mjs' },\n\t\t\t\t\tmodule: { require: './x.mjs' },\n\t\t\t\t\t'nested-module': { require: './module/x.js' },\n\t\t\t\t\t'nested-commonjs': { require: './commonjs/x.js' },\n\t\t\t\t\tesm: { import: './x.mjs' },\n\t\t\t\t},\n\t\t\t}),\n\t\t)\n\t\twriteFile(join(root, 'module/package.json'), '{ \"type\": \"module\" }\\n')\n\t\twriteFile(join(root, 'commonjs/package.json'), '{ \"type\": \"commonjs\" }\\n')\n\t\tconst manifest = readJson(join(root, 'package.json'))\n\t\tif (!isRecord(manifest) || !isRecord(manifest.exports)) {\n\t\t\tthrow new Error('The classifier fixture declares no exports map')\n\t\t}\n\t\tconst mappings = manifest.exports\n\t\texpect({\n\t\t\tcondition: resolvesBrowser(mappings.condition),\n\t\t\tconvention: resolvesBrowser(mappings.convention),\n\t\t\tuniversal: resolvesBrowser(mappings.universal),\n\t\t\timport: resolvesBrowser(mappings['import-shared']),\n\t\t\trequire: resolvesBrowser(mappings['require-shared']),\n\t\t\tnode: resolvesBrowser(mappings.node),\n\t\t}).toStrictEqual({\n\t\t\tcondition: true,\n\t\t\tconvention: true,\n\t\t\tuniversal: false,\n\t\t\timport: false,\n\t\t\trequire: false,\n\t\t\tnode: false,\n\t\t})\n\t\texpect({\n\t\t\tsilent: resolvesCommonJS(mappings.silent, root),\n\t\t\tmodule: resolvesCommonJS(mappings.module, root),\n\t\t\tnestedModule: resolvesCommonJS(mappings['nested-module'], root),\n\t\t\tnestedCommonJS: resolvesCommonJS(mappings['nested-commonjs'], root),\n\t\t\tesm: resolvesCommonJS(mappings.esm, root),\n\t\t}).toStrictEqual({\n\t\t\tsilent: true,\n\t\t\tmodule: false,\n\t\t\tnestedModule: false,\n\t\t\tnestedCommonJS: true,\n\t\t\tesm: false,\n\t\t})\n\t})\n})\n\n// The staged consumer, or a skip naming what the run could not reach. `it.skipIf`\n// carries no reason, so the gate sits here where the test context can state one.\nfunction requireStage(context: TestContext): Stage {\n\tif (!STAGED) {\n\t\treturn context.skip('`npm ping` did not answer, so nothing was packed or installed')\n\t}\n\treturn STAGE\n}\n\ndescribe('installed package consumer', () => {\n\tit('packs one archive and installs it in isolation [requires the registry]', (context) => {\n\t\tconst stage = requireStage(context)\n\t\texpect(stage.archives).toHaveLength(1)\n\t\texpect(existsSync(join(stage.installed, 'package.json'))).toBe(true)\n\t\texpect(stage.entries.length).toBeGreaterThan(0)\n\t})\n\n\tit('ships every relative target its exports map names [requires the registry]', (context) => {\n\t\tconst stage = requireStage(context)\n\t\tconst relative = stage.targets.filter((target) => target.startsWith('./'))\n\t\texpect(relative).not.toStrictEqual([])\n\t\texpect(relative.filter((target) => !existsSync(join(stage.installed, target)))).toStrictEqual(\n\t\t\t[],\n\t\t)\n\t})\n\n\t// Every published subpath is driven, excluded by name, or reported here. A dropped\n\t// one leaves no trace: no runtime test, no declaration comparison, and no place in\n\t// the resolution compile, so the run reports success for a subpath it never\n\t// measured.\n\tit('declares types for every module it publishes [requires the registry]', (context) => {\n\t\tconst stage = requireStage(context)\n\t\tconst partitioned = [\n\t\t\t...stage.entries.map((entry) => entry.subpath),\n\t\t\t...stage.undeclared,\n\t\t\t...stage.excluded,\n\t\t]\n\t\texpect(stage.undeclared).toStrictEqual([])\n\t\texpect(partitioned.sort()).toStrictEqual([...stage.subpaths].sort())\n\t\t// A driven subpath answers a runtime condition. One resolving a declaration and\n\t\t// no Node or browser target compiles for a consumer and throws when that consumer\n\t\t// loads it. Each later drive retires itself for that entry, so this assertion names\n\t\t// the subpath rather than counting it as driven.\n\t\tconst unreachable = stage.entries.filter(\n\t\t\t(entry) => !entry.module && !entry.required && !entry.browser,\n\t\t)\n\t\texpect(unreachable.map((entry) => entry.subpath)).toStrictEqual([])\n\t\tconst untypable = selectUntypable(stage.entries, stage.installed)\n\t\texpect(untypable.map((entry) => entry.subpath)).toStrictEqual([])\n\t})\n\n\tit('refuses a subpath its exports map does not name [requires the registry]', (context) => {\n\t\tconst stage = requireStage(context)\n\t\tconst name = readManifestName(join(stage.installed, 'package.json'))\n\t\tconst driver = join(stage.consumer, ESM_DRIVER)\n\t\tconst result = runNode([driver, `${name}${ABSENT_SUBPATH}`], stage.consumer)\n\t\texpect(result.status).not.toBe(0)\n\t\texpect(readOutput(result)).toContain('ERR_PACKAGE_PATH_NOT_EXPORTED')\n\t})\n\n\t// The absent subpath is the firing control: a resolution that reports nothing\n\t// for every published entry has not been shown to resolve anything at all. Each\n\t// module format carries its own control, because a format that resolves nothing\n\t// is silent for the same reason a resolution that resolves nothing is.\n\tit('compiles a consumer under every module resolution [requires the registry]', (context) => {\n\t\tconst stage = requireStage(context)\n\t\tconst name = readManifestName(join(stage.installed, 'package.json'))\n\t\tconst reported: string[] = []\n\t\tconst silent: string[] = []\n\t\tfor (const driver of RESOLUTIONS) {\n\t\t\tfor (const [extension, format] of FORMATS) {\n\t\t\t\tconst written = selectEntries(stage.entries, driver.conditions[format])\n\t\t\t\tif (written.length === 0) continue\n\t\t\t\tconst specifiers = written.map((entry) => entry.specifier)\n\t\t\t\tconst probe = writeConsumerProbe(stage, `probe.${driver.label}.${extension}`, specifiers)\n\t\t\t\tfor (const message of compileConsumer(probe, driver.resolution, driver.module)) {\n\t\t\t\t\treported.push(`${driver.label}.${extension}: ${message}`)\n\t\t\t\t}\n\t\t\t\tconst absent = [`${name}${ABSENT_SUBPATH}`]\n\t\t\t\tconst control = writeConsumerProbe(stage, `control.${driver.label}.${extension}`, absent)\n\t\t\t\tif (compileConsumer(control, driver.resolution, driver.module).length === 0) {\n\t\t\t\t\tsilent.push(`${driver.label}.${extension}`)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\texpect(reported).toStrictEqual([])\n\t\texpect(silent).toStrictEqual([])\n\t})\n{{guard}}})\n\nfor (const entry of STAGE?.entries ?? []) {\n\tdescribe(`installed entry ${entry.subpath}`, () => {\n\t\tit.runIf(entry.module)(\n\t\t\t'publishes what it declares to a Node import, and no more',\n\t\t\t(context) => {\n\t\t\t\tconst declaration = entry.declaration.module\n\t\t\t\tif (declaration === undefined) {\n\t\t\t\t\tthrow new Error(`${entry.subpath} publishes no import declaration`)\n\t\t\t\t}\n\t\t\t\tconst published = driveRuntime(requireStage(context), entry.specifier, ESM_DRIVER)\n\t\t\t\texpect(published).toStrictEqual(readDeclaredExports(declaration))\n\t\t\t},\n\t\t)\n\n\t\tit.runIf(entry.required)(\n\t\t\t'publishes what it declares to a Node require, and no more',\n\t\t\t(context) => {\n\t\t\t\tconst declaration = entry.declaration.commonjs\n\t\t\t\tif (declaration === undefined) {\n\t\t\t\t\tthrow new Error(`${entry.subpath} publishes no require declaration`)\n\t\t\t\t}\n\t\t\t\tconst published = driveRuntime(requireStage(context), entry.specifier, CJS_DRIVER)\n\t\t\t\texpect(published).toStrictEqual(readDeclaredExports(declaration))\n\t\t\t},\n\t\t)\n{{drive}}\t})\n}\n";
|
|
100
|
+
transport: "import { createServer } from 'node:http'\n";
|
|
101
|
+
types: "import type { PlaywrightProviderOptions } from '@vitest/browser-playwright'\nimport type { Browser } from 'playwright'\n";
|
|
102
|
+
launcher: "import { chromium } from 'playwright'\nimport { build } from 'vite'\nimport { resolveBrowser, resolvePinnedBrowser } from '../configs/browsers.js'\n";
|
|
103
|
+
helpers: "\nconst BROWSER_PAGE = `<!doctype html>\n<html lang=\"en\">\n\t<head>\n\t\t<meta charset=\"UTF-8\" />\n\t\t<title>Distribution</title>\n\t</head>\n\t<body>\n\t\t<script type=\"module\" src=\"./main.js\"></script>\n\t</body>\n</html>\n`\n\nfunction readContentType(path: string): string {\n\tif (path.endsWith('.html')) return 'text/html'\n\tif (path.endsWith('.js')) return 'text/javascript'\n\tif (path.endsWith('.css')) return 'text/css'\n\tif (path.endsWith('.json') || path.endsWith('.map')) return 'application/json'\n\treturn 'application/octet-stream'\n}\n\n// `resolveBrowser` answers with provider options and never reports absence: its\n// last resort is a channel nothing verified. So the launch is attempted and its\n// rejection classified, rather than probed for and ruled on.\nfunction describeBrowser(options: PlaywrightProviderOptions): string {\n\tconst endpoint = options.connectOptions?.wsEndpoint\n\tif (endpoint !== undefined) return `the browser server at ${endpoint}`\n\tconst executable = options.launchOptions?.executablePath\n\tif (executable !== undefined) return `the executable at ${executable}`\n\tconst channel = options.launchOptions?.channel\n\tif (channel !== undefined) return `the ${channel} channel`\n\treturn 'the Chromium Playwright installed for itself'\n}\n\nasync function launchBrowser(options: PlaywrightProviderOptions): Promise<Browser> {\n\tconst endpoint = options.connectOptions?.wsEndpoint\n\tif (endpoint !== undefined) return chromium.connect(endpoint)\n\treturn chromium.launch({ ...options.launchOptions, headless: true })\n}\n\n// A consumer of one installed browser entry, bundled by the Vite toolchain this\n// workspace already declares. Nothing is stubbed: the bundle resolves the installed\n// package and its whole transitive graph as an application consuming it would.\nasync function bundleEntry(stage: Stage, entry: Entry): Promise<string> {\n\tconst page = join(stage.consumer, 'pages', entry.subpath.replaceAll(/[^\\w]+/gu, '-'))\n\tconst specifier = JSON.stringify(entry.specifier)\n\twriteFile(join(page, 'index.html'), BROWSER_PAGE)\n\twriteFile(\n\t\tjoin(page, 'main.js'),\n\t\t`import * as entry from ${specifier}\\nglobalThis.subject = Object.keys(entry).sort()\\n`,\n\t)\n\tawait build({\n\t\tbase: './',\n\t\tbuild: { emptyOutDir: true, outDir: 'bundle' },\n\t\tconfigFile: false,\n\t\tlogLevel: 'error',\n\t\troot: page,\n\t})\n\treturn join(page, 'bundle')\n}\n\n// The key set the bundled module publishes in a real browser, read off the page\n// once it has loaded over a loopback server. A module that never evaluated\n// publishes nothing, and a page error is raised rather than compared away.\nasync function readBrowserExports(browser: Browser, bundle: string): Promise<readonly string[]> {\n\tconst server = createServer((request, response) => {\n\t\tconst asked = request.url === undefined || request.url === '/' ? '/index.html' : request.url\n\t\tconst path = join(bundle, decodeURIComponent(asked))\n\t\tif (!path.startsWith(bundle) || !existsSync(path)) {\n\t\t\tresponse.writeHead(404)\n\t\t\tresponse.end()\n\t\t\treturn\n\t\t}\n\t\tresponse.writeHead(200, { 'content-type': readContentType(path) })\n\t\tresponse.end(readFileSync(path))\n\t})\n\ttry {\n\t\tawait new Promise<void>((settle) => {\n\t\t\tserver.listen(0, '127.0.0.1', settle)\n\t\t})\n\t\tconst address = server.address()\n\t\tif (address === null || typeof address === 'string') {\n\t\t\tthrow new Error('The bundle server bound no port')\n\t\t}\n\t\tconst page = await browser.newPage()\n\t\tconst failures: string[] = []\n\t\tpage.on('pageerror', (error) => failures.push(String(error)))\n\t\tawait page.goto(`http://127.0.0.1:${String(address.port)}/`, { waitUntil: 'load' })\n\t\tconst published: unknown = await page.evaluate('globalThis.subject')\n\t\tif (failures.length > 0) throw new Error(`The bundle raised ${failures.join(' | ')}`)\n\t\tif (!isNames(published)) throw new Error('The bundled module published no name list')\n\t\treturn published\n\t} finally {\n\t\tserver.close()\n\t}\n}\n";
|
|
104
|
+
drive: "\n\t\tit.runIf(entry.browser)(\n\t\t\t'publishes what it declares to a real browser, and no more [requires a browser]',\n\t\t\tasync (context) => {\n\t\t\t\tconst stage = requireStage(context)\n\t\t\t\tconst declaration = entry.declaration.browser\n\t\t\t\tif (declaration === undefined) {\n\t\t\t\t\tthrow new Error(`${entry.subpath} publishes no browser declaration`)\n\t\t\t\t}\n\t\t\t\tconst options = resolveBrowser(resolvePinnedBrowser(), process.platform, process.env)\n\t\t\t\tconst browser = await launchBrowser(options).catch((error: unknown) => {\n\t\t\t\t\tconst cause = `${describeBrowser(options)} was rejected: ${String(error)}`\n\t\t\t\t\tif (RELEASE) throw new Error(`The release gate requires a browser, and ${cause}`)\n\t\t\t\t\treturn context.skip(`No browser launched. ${cause}`)\n\t\t\t\t})\n\t\t\t\ttry {\n\t\t\t\t\tconst bundle = await bundleEntry(stage, entry)\n\t\t\t\t\texpect(await readBrowserExports(browser, bundle)).toStrictEqual(\n\t\t\t\t\t\treadDeclaredExports(declaration),\n\t\t\t\t\t)\n\t\t\t\t} finally {\n\t\t\t\t\tawait browser.close()\n\t\t\t\t}\n\t\t\t},\n\t\t)\n";
|
|
105
|
+
guard: "\n\t// This proof drives a Node import and a Node require and carries no browser\n\t// branch: the workspace published no browser face when it was written, and the\n\t// browser drive measures the packed artifact, so only a published face is owed\n\t// one. A private browser application does not select this branch. It declares the\n\t// browser launcher and its Vitest browser provider and gets the generated browser\n\t// configuration module beside it, but installed browser tooling does not stand for\n\t// a published browser face. `vite` selects nothing either, though the branch\n\t// imports it: scaffold puts `vite` in every workspace's base development\n\t// dependencies, whatever that workspace publishes. The later Node\n\t// `it.runIf` predicates retire each matching Node drive for a face published\n\t// later, which leaves nothing measuring it. So it reddens here and names the\n\t// subpath a browser branch is owed for. A workspace that gains one deletes this\n\t// file and runs the `repair` verb, which writes the variant carrying that branch.\n\tit('publishes no browser face this proof cannot drive [requires the registry]', (context) => {\n\t\tconst stage = requireStage(context)\n\t\tconst faces = stage.entries.filter((entry) => entry.browser)\n\t\texpect(faces.map((entry) => entry.subpath)).toStrictEqual([])\n\t})\n";
|
|
106
|
+
}>;
|
|
98
107
|
integration: "{{imports}}import { describe, expect, it } from 'vitest'\n\ndescribe('workspace integration', () => {\n\tit('loads every selected public barrel together as a composition seed', () => {\n\t\t// Replace this empty-barrel seed with one observable flow that passes one\n\t\t// environment's public result into another.\n\t\t{{actual}}{{expected}})\n\t})\n})\n";
|
|
99
108
|
}>;
|
|
100
109
|
docs: Readonly<{
|
|
@@ -236,8 +245,8 @@ export declare const BIN_ENTRY_PATH = "src/bin/main.ts";
|
|
|
236
245
|
* are runtime `@orkestrel/*` packages. A peer in the `@orkestrel` scope is a
|
|
237
246
|
* fleet pin; every other peer is a floor. `extras` are package-specific
|
|
238
247
|
* development dependencies and may carry any valid npm name.
|
|
239
|
-
* `bin`, `setup`, `guides`, `
|
|
240
|
-
* `
|
|
248
|
+
* `bin`, `setup`, `guides`, `integration`, `conformance`, `service`,
|
|
249
|
+
* `vendors`, `global`, and `showcase` are structural facts: each is
|
|
241
250
|
* set only when the workspace physically ships the directory or exact-case file
|
|
242
251
|
* that defines it, never because of the workspace's name and never because a
|
|
243
252
|
* sibling fact is set.
|
|
@@ -267,7 +276,6 @@ export declare interface Blueprint {
|
|
|
267
276
|
readonly bin: boolean;
|
|
268
277
|
readonly setup: boolean;
|
|
269
278
|
readonly guides: boolean;
|
|
270
|
-
readonly distribution: boolean;
|
|
271
279
|
readonly integration: boolean;
|
|
272
280
|
readonly conformance: boolean;
|
|
273
281
|
readonly service: boolean;
|
|
@@ -524,13 +532,15 @@ export declare function blueprintToRootVite(blueprint: Blueprint): string;
|
|
|
524
532
|
* proofs every workspace can pass before it has a public API, and one build per
|
|
525
533
|
* target that actually builds.
|
|
526
534
|
*
|
|
527
|
-
* A publishing workspace isolates distribution and live-service proofs from
|
|
528
|
-
* `test` and runs them from `prepublishOnly` instead.
|
|
529
|
-
*
|
|
530
|
-
*
|
|
531
|
-
*
|
|
532
|
-
*
|
|
533
|
-
*
|
|
535
|
+
* A publishing workspace isolates its distribution and live-service proofs from
|
|
536
|
+
* `test` and runs them from `prepublishOnly` instead. Publishing is what selects
|
|
537
|
+
* the distribution proof: what that proof measures is the packed tarball, so a
|
|
538
|
+
* workspace that packs no published source has nothing for it to read. A private
|
|
539
|
+
* workspace therefore omits it and runs a live-service proof from `test`.
|
|
540
|
+
* Integration and conformance stay in `test` because they neither pack nor
|
|
541
|
+
* install the workspace and drive no external service. A conformance run may
|
|
542
|
+
* start a server, but it starts its own and reaches it over loopback, so the run
|
|
543
|
+
* stays hermetic.
|
|
534
544
|
*
|
|
535
545
|
* The configuration paths interpolated here are the same ones `SRC_MATRIX` and
|
|
536
546
|
* `APP_MATRIX` list as each environment's configuration files, so a rename in
|
|
@@ -591,6 +601,13 @@ export declare function blueprintToSourceArtifacts(blueprint: Blueprint): readon
|
|
|
591
601
|
* readiness setup alone, because the root configuration names that module by
|
|
592
602
|
* path.
|
|
593
603
|
*
|
|
604
|
+
* The distribution proof is emitted, and the same test separates it from those
|
|
605
|
+
* two: its subject is the packed tarball rather than anything only the package
|
|
606
|
+
* knows, so every assertion derives from the installed tree at run time and
|
|
607
|
+
* nothing has to be named. It follows the published source it packs, and it is
|
|
608
|
+
* the one artifact here claimed by presence: a target lacking it reports as
|
|
609
|
+
* drift, and a package that replaced it with a better proof keeps that proof.
|
|
610
|
+
*
|
|
594
611
|
* @example
|
|
595
612
|
* ```ts
|
|
596
613
|
* import { blueprintToTestArtifacts, createBlueprint } from '@orkestrel/scaffold'
|
|
@@ -602,6 +619,37 @@ export declare function blueprintToSourceArtifacts(blueprint: Blueprint): readon
|
|
|
602
619
|
*/
|
|
603
620
|
export declare function blueprintToTestArtifacts(blueprint: Blueprint): readonly ContentArtifact[];
|
|
604
621
|
|
|
622
|
+
/**
|
|
623
|
+
* Project a blueprint into the manifest scripts a region write may replace.
|
|
624
|
+
*
|
|
625
|
+
* @param blueprint - The workspace specification.
|
|
626
|
+
* @returns One entry per writable script.
|
|
627
|
+
*
|
|
628
|
+
* @remarks
|
|
629
|
+
* Every direct `test:<project>` script is writable, together with the probe and
|
|
630
|
+
* benchmark workbench scripts. Publishing adds the pack and publication
|
|
631
|
+
* lifecycle scripts. Aggregate test scripts and maintainer-owned gate chains
|
|
632
|
+
* stay outside the region.
|
|
633
|
+
*
|
|
634
|
+
* `accepted` carries each generated predecessor the region can replace. The
|
|
635
|
+
* pack hook accepts the build chain emitted before it delegated to `build`. The
|
|
636
|
+
* publication hook accepts the same gate chain without
|
|
637
|
+
* {@link RELEASE_PROOF_COMMAND}. The value being written is always writable, so
|
|
638
|
+
* it is not repeated there. Any other value is a script the workspace author
|
|
639
|
+
* customized, and {@link replaceManifestScripts} retains it while writing the
|
|
640
|
+
* other named scripts independently.
|
|
641
|
+
*
|
|
642
|
+
* @example
|
|
643
|
+
* ```ts
|
|
644
|
+
* import { blueprintToWritableScripts, createBlueprint } from '@orkestrel/scaffold'
|
|
645
|
+
*
|
|
646
|
+
* const blueprint = createBlueprint('router', { src: ['core'] })
|
|
647
|
+
*
|
|
648
|
+
* blueprintToWritableScripts(blueprint)[0]?.name // 'test:src:core'
|
|
649
|
+
* ```
|
|
650
|
+
*/
|
|
651
|
+
export declare function blueprintToWritableScripts(blueprint: Blueprint): readonly ManifestScript[];
|
|
652
|
+
|
|
605
653
|
/** One module format a published library environment builds. */
|
|
606
654
|
export declare type BuildFormat = 'es' | 'cjs';
|
|
607
655
|
|
|
@@ -1039,29 +1087,29 @@ export declare class Compiler implements CompilerInterface {
|
|
|
1039
1087
|
export declare const CONFIG_TEMPLATES: Readonly<{
|
|
1040
1088
|
root: Readonly<{
|
|
1041
1089
|
tsconfig: "{\n\t\"compilerOptions\": {\n\t\t\"target\": \"ESNext\",\n\t\t\"module\": \"ESNext\",\n\t\t\"moduleResolution\": \"bundler\",\n\t\t\"allowImportingTsExtensions\": true,\n\t\t\"lib\": [\"ESNext\", \"DOM\", \"DOM.Iterable\"],\n\t\t\"types\": [\"node\", \"vite/client\", \"vitest/globals\"],\n\t\t\"moduleDetection\": \"force\",\n\t\t\"resolveJsonModule\": true,\n\t\t\"strict\": true,\n\t\t\"verbatimModuleSyntax\": true,\n\t\t\"noUncheckedIndexedAccess\": true,\n\t\t\"noUncheckedSideEffectImports\": true,\n\t\t\"exactOptionalPropertyTypes\": true,\n\t\t\"noUnusedLocals\": true,\n\t\t\"noUnusedParameters\": true,\n\t\t\"noImplicitOverride\": true,\n\t\t\"noFallthroughCasesInSwitch\": true,\n\t\t\"forceConsistentCasingInFileNames\": true,\n\t\t\"skipLibCheck\": true,\n\t\t\"noEmit\": true,\n\t\t\"paths\": {\n{{paths}}\n\t\t}\n\t},\n\t\"exclude\": [\"node_modules\", \"dist\", \"tmp\"]\n}\n";
|
|
1042
|
-
vite: "import type { {{viteTypes}} } from 'vite'\n{{imports}}import { defineConfig
|
|
1090
|
+
vite: "import type { {{viteTypes}} } from 'vite'\n{{imports}}import { defineConfig } from 'vitest/config'\nimport manifest from './package.json' with { type: 'json' }\nimport tsconfig from './tsconfig.json' with { type: 'json' }\n{{helpers}}{{browsers}}import { fileURLToPath, URL } from 'node:url'\n\n{{options}}export function resolveWorkspacePath(relativePath: string): string {\n\treturn fileURLToPath(new URL(relativePath, import.meta.url))\n}\n\nconst peerDependencies = 'peerDependencies' in manifest ? manifest.peerDependencies : undefined\nif (\n\tpeerDependencies !== undefined &&\n\t(typeof peerDependencies !== 'object' ||\n\t\tpeerDependencies === null ||\n\t\tArray.isArray(peerDependencies))\n) {\n\tthrow new Error('package peerDependencies must be an object')\n}\nexport const peers: readonly string[] =\n\tpeerDependencies === undefined ? [] : Object.keys(peerDependencies)\n\nconst resolve = {\n\talias: Object.entries(tsconfig.compilerOptions.paths).reduce((aliases, [key, values]) => {\n\t\tconst [path] = values\n\t\tif (path === undefined) throw new Error('tsconfig path alias ' + key + ' has no target')\n\t\treturn Object.assign(aliases, { [key]: resolveWorkspacePath(path) })\n\t}, {}),\n}\n\n{{factories}}export default defineConfig({\n\tresolve,\n\ttest: {\n{{projects}}\n\t},\n})\n";
|
|
1043
1091
|
}>;
|
|
1044
1092
|
factories: Readonly<{
|
|
1045
1093
|
src: Readonly<{
|
|
1046
|
-
core: "export const srcCore = (
|
|
1047
|
-
browser: "export const srcBrowser = (
|
|
1048
|
-
server: "export const srcServer = (
|
|
1049
|
-
bin: "export const srcBin = (
|
|
1094
|
+
core: "export const srcCore = (): UserConfig => ({\n\tresolve,\n\tpublicDir: false,\n\tbuild: {\n\t\temptyOutDir: true,\n\t\tsourcemap: true,\n\t\tminify: false,\n\t\trolldownOptions: { onLog: enforceBuildLog },\n\t},\n\ttest: {\n\t\tname: { label: 'src:core', color: 'magenta' },\n\t\tinclude: ['tests/src/core/**/*.test.ts'],\n\t\tsetupFiles: ['./tests/setup.ts'],\n\t\tenvironment: 'node',\n\t\tbrowser: { enabled: false },\n\t},\n})\n";
|
|
1095
|
+
browser: "export const srcBrowser = (): UserConfig => ({\n\tresolve,\n\tpublicDir: false,\n\tplugins: [outputBoundary('dist/src/browser'), environmentBoundary('src/browser')],\n\tbuild: {\n\t\temptyOutDir: true,\n\t\tsourcemap: true,\n\t\tminify: false,\n\t\tlib: {\n\t\t\tentry: resolveWorkspacePath('src/browser/index.ts'),\n\t\t\tformats: ['es'],\n\t\t\tfileName: () => 'index.js',\n\t\t},\n\t\toutDir: 'dist/src/browser',\n\t\trolldownOptions: {\n\t\t\tonLog: enforceBuildLog,\n\t\t\t{{external}}\n{{output}}\n\t\t},\n\t},\n\ttest: {\n\t\tname: { label: 'src:browser', color: 'yellow' },\n\t\tinclude: ['tests/src/browser/**/*.test.ts'],\n{{exclude}}\t\tsetupFiles: ['./tests/setup.ts', './tests/setupBrowser.ts'],\n{{global}}\n\t\tbrowser: {\n\t\t\tenabled: true,\n\t\t\tprovider: playwright(browserOptions),\n\t\t\tinstances: [{ browser: 'chromium', headless: true }],\n\t\t},\n\t\tfileParallelism: false,\n\t},\n})\n";
|
|
1096
|
+
server: "export const srcServer = (): UserConfig => ({\n\tresolve,\n\tpublicDir: false,\n\tplugins: [outputBoundary('dist/src/server'), environmentBoundary('src/server')],\n\tbuild: {\n\t\temptyOutDir: true,\n\t\tsourcemap: true,\n\t\tminify: false,\n\t\tlib: {\n\t\t\tentry: resolveWorkspacePath('src/server/index.ts'),\n\t\t\tformats: ['es', 'cjs'],\n\t\t\tfileName: (format: string) => (format === 'es' ? 'index.js' : 'index.cjs'),\n\t\t},\n\t\toutDir: 'dist/src/server',\n\t\ttarget: 'node22',\n\t\trolldownOptions: {\n\t\t\tonLog: enforceBuildLog,\n\t\t\tplatform: 'node',\n\t\t\t{{external}}\n{{output}}\n\t\t},\n\t},\n\ttest: {\n\t\tname: { label: 'src:server', color: 'red' },\n\t\tinclude: ['tests/src/server/**/*.test.ts'],\n{{exclude}}\t\tsetupFiles: ['./tests/setup.ts', './tests/setupServer.ts'],\n\t\tenvironment: 'node',\n\t\tbrowser: { enabled: false },\n\t},\n})\n";
|
|
1097
|
+
bin: "export const srcBin = (): UserConfig => ({\n\tresolve,\n\tpublicDir: false,\n\tplugins: [outputBoundary('dist/bin')],\n\tbuild: {\n\t\temptyOutDir: true,\n\t\tsourcemap: true,\n\t\tminify: false,\n\t\tlib: {\n\t\t\tentry: resolveWorkspacePath('src/bin/main.ts'),\n\t\t\tformats: ['es'],\n\t\t\tfileName: () => 'main.js',\n\t\t},\n\t\toutDir: 'dist/bin',\n\t\ttarget: 'node22',\n\t\trolldownOptions: {\n\t\t\tonLog: enforceBuildLog,\n\t\t\texternal: (id: string) =>\n\t\t\t\tid.startsWith('node:') ||\n\t\t\t\tid.startsWith('@orkestrel/') ||\n\t\t\t\tid.startsWith('@src/') ||\n\t\t\t\tpeers.some((peer) => id === peer || id.startsWith(peer + '/')),\n\t\t},\n\t},\n\ttest: {\n\t\tname: { label: 'src:bin', color: 'yellow' },\n\t\tinclude: ['tests/src/bin/**/*.test.ts'],\n\t\tsetupFiles: ['./tests/setup.ts', './tests/setupServer.ts'],\n\t\tenvironment: 'node',\n\t\tbrowser: { enabled: false },\n\t\t// A bin test drives the real executable over a real temporary repository, so it\n\t\t// spends seconds in process startup and filesystem work rather than milliseconds.\n\t\t// Vitest's five-second default clears one alone and times out under a full suite.\n\t\ttestTimeout: 15_000,\n\t},\n})\n";
|
|
1050
1098
|
}>;
|
|
1051
1099
|
app: Readonly<{
|
|
1052
|
-
core: "export const appCore = (
|
|
1100
|
+
core: "export const appCore = (): UserConfig => ({\n\tresolve,\n\tpublicDir: false,\n\tplugins: [environmentBoundary('app/core')],\n\ttest: {\n\t\tname: { label: 'app:core', color: 'cyan' },\n\t\tinclude: ['tests/app/core/**/*.test.ts'],\n\t\tsetupFiles: ['./tests/setup.ts'],\n\t\tenvironment: 'node',\n\t\tbrowser: { enabled: false },\n\t},\n})\n";
|
|
1053
1101
|
browser: "function applicationBrowser(showcase: boolean): UserConfig {\n\tconst output = showcase ? 'dist/showcase' : 'dist/app/browser'\n{{showcasePlugins}}\treturn {\n\t\tresolve,\n{{plugins}}\t\troot: resolveWorkspacePath('app/browser'),\n\t\tpublicDir: false,\n\t\tbuild: {\n{{showcaseBuild}}\t\t\temptyOutDir: true,\n\t\t\toutDir: resolveWorkspacePath(output),\n\t\t\trolldownOptions: {\n\t\t\t\tonLog: enforceBuildLog,\n\t\t\t\tinput: resolveWorkspacePath('app/browser/index.html'),\n\t\t\t},\n\t\t},\n\t\ttest: {\n\t\t\tname: { label: 'app:browser', color: 'blue' },\n\t\t\troot: resolveWorkspacePath('.'),\n\t\t\tdir: resolveWorkspacePath('.'),\n\t\t\tinclude: ['tests/app/browser/**/*.test.ts'],\n\t\t\tsetupFiles: ['./tests/setup.ts', './tests/setupBrowser.ts'],\n\t\t\tbrowser: {\n\t\t\t\tenabled: true,\n\t\t\t\tprovider: playwright(browserOptions),\n\t\t\t\tinstances: [{ browser: 'chromium', headless: true }],\n\t\t\t},\n\t\t\tfileParallelism: false,\n\t\t},\n\t}\n}\n\nexport function appBrowser(): UserConfig {\n\treturn applicationBrowser(false)\n}\n{{showcaseFactory}}";
|
|
1054
|
-
server: "export const appServer = (
|
|
1102
|
+
server: "export const appServer = (): UserConfig => ({\n\tresolve,\n\tpublicDir: false,\n\tplugins: [outputBoundary('dist/app/server'), environmentBoundary('app/server')],\n\tbuild: {\n\t\temptyOutDir: true,\n\t\tlib: {\n\t\t\tentry: resolveWorkspacePath('app/server/main.ts'),\n\t\t\tformats: ['cjs'],\n\t\t\tfileName: () => 'main.cjs',\n\t\t},\n\t\toutDir: resolveWorkspacePath('dist/app/server'),\n\t\ttarget: 'node22',\n\t\trolldownOptions: {\n\t\t\tonLog: enforceBuildLog,\n\t\t\texternal: (id: string) => id.startsWith('node:'),\n\t\t},\n\t},\n\ttest: {\n\t\tname: { label: 'app:server', color: 'green' },\n\t\tinclude: ['tests/app/server/**/*.test.ts'],\n\t\tsetupFiles: ['./tests/setup.ts', './tests/setupServer.ts'],\n\t\tenvironment: 'node',\n\t\tbrowser: { enabled: false },\n\t},\n})\n";
|
|
1055
1103
|
}>;
|
|
1056
|
-
policy: "export const policy = (
|
|
1057
|
-
config: "export const config = (
|
|
1058
|
-
setup: "export const setup = (
|
|
1059
|
-
guides: "export const guides = (
|
|
1060
|
-
conformance: "// Where this package drifts from the official tooling it stays compatible with.\n// The subject is this package, so the proof is hermetic and stays in `npm test`.\nexport const conformance = (
|
|
1061
|
-
service: "// The live external services this package drives. It starts nothing itself:\n// `scripts/service.sh` provisions, `tests/setupService.ts` proves readiness, and\n// the project stays out of `npm test` because a real service answers it.\nexport const service = (
|
|
1062
|
-
distribution: "export const distribution = (
|
|
1063
|
-
probe: "// A workbench, not a proof. No gate selects this project. Run in test mode by the\n// `test:probe` script, it collects `tmp/probe/**/*.test.ts`. Run in benchmark mode by the\n// `test:bench` script, the same workbench also collects `tests/**/*.test.ts` for a `bench` block,\n// so a suite may carry a bench beside its ordinary tests without a second project. The mode\n// guard around each `bench` call keeps it out of test mode, so it never executes there.\nexport const probe = (
|
|
1064
|
-
integration: "export const integration = (
|
|
1104
|
+
policy: "export const policy = (): UserConfig => ({\n\tresolve,\n\ttest: {\n\t\tname: { label: 'policy', color: 'white' },\n\t\tinclude: ['tests/policy.test.ts'],\n\t\tsetupFiles: ['./tests/setup.ts'],\n\t\tenvironment: 'node',\n\t\tbrowser: { enabled: false },\n\t},\n})\n";
|
|
1105
|
+
config: "export const config = (): UserConfig => ({\n\tresolve,\n\ttest: {\n\t\tname: { label: 'config', color: 'yellow' },\n\t\tinclude: ['tests/config.test.ts'],\n\t\tsetupFiles: ['./tests/setup.ts'],\n\t\tenvironment: 'node',\n\t\tbrowser: { enabled: false },\n\t\t// A config test validates every target wrapper and runs the real linter twice with\n\t\t// 15-second child caps, so this budget clears both caps and reports their diagnostics.\n\t\ttestTimeout: 45_000,\n\t},\n})\n";
|
|
1106
|
+
setup: "export const setup = (): UserConfig => ({\n\tresolve,\n\ttest: {\n\t\tname: { label: 'setup', color: 'white' },\n\t\tinclude: ['tests/setup*.test.ts'],\n\t\tsetupFiles: ['./tests/setup.ts'],\n\t\tenvironment: 'node',\n\t\tbrowser: { enabled: false },\n\t},\n})\n";
|
|
1107
|
+
guides: "export const guides = (): UserConfig => ({\n\tresolve,\n\ttest: {\n\t\tname: { label: 'guides', color: 'green' },\n\t\tinclude: ['tests/guides.test.ts'],\n\t\texclude: ['tests/src/**/*.test.ts', 'tests/app/**/*.test.ts', 'tests/setup.test.ts'],\n\t\tsetupFiles: ['./tests/setup.ts'],\n\t\tenvironment: 'node',\n\t\tbrowser: { enabled: false },\n\t},\n})\n";
|
|
1108
|
+
conformance: "// Where this package drifts from the official tooling it stays compatible with.\n// The subject is this package, so the proof is hermetic and stays in `npm test`.\nexport const conformance = (): UserConfig => ({\n\tresolve,\n\ttest: {\n\t\tname: { label: 'conformance', color: 'magenta' },\n\t\tinclude: ['tests/conformance.test.ts'],\n\t\tsetupFiles: ['./tests/setup.ts'],\n\t\tenvironment: 'node',\n\t\tbrowser: { enabled: false },\n\t},\n})\n";
|
|
1109
|
+
service: "// The live external services this package drives. It starts nothing itself:\n// `scripts/service.sh` provisions, `tests/setupService.ts` proves readiness, and\n// the project stays out of `npm test` because a real service answers it.\nexport const service = (): UserConfig => ({\n\tresolve,\n\ttest: {\n\t\tname: { label: 'service', color: 'red' },\n\t\tinclude: ['tests/service/**/*.test.ts'],\n\t\tsetupFiles: ['./tests/setup.ts', './tests/setupService.ts'],\n\t\tenvironment: 'node',\n\t\tbrowser: { enabled: false },\n\t\ttestTimeout: 120_000,\n\t\thookTimeout: 120_000,\n\t\tfileParallelism: false,\n\t},\n})\n";
|
|
1110
|
+
distribution: "export const distribution = (): UserConfig => ({\n\tresolve,\n\ttest: {\n\t\tname: { label: 'distribution', color: 'cyan' },\n\t\tinclude: ['tests/distribution.test.ts'],\n\t\tsetupFiles: ['./tests/setup.ts'],\n\t\tenvironment: 'node',\n\t\ttestTimeout: 120_000,\n\t\thookTimeout: 120_000,\n\t\tfileParallelism: false,\n\t},\n})\n";
|
|
1111
|
+
probe: "// A workbench, not a proof. No gate selects this project. Run in test mode by the\n// `test:probe` script, it collects `tmp/probe/**/*.test.ts`. Run in benchmark mode by the\n// `test:bench` script, the same workbench also collects `tests/**/*.test.ts` for a `bench` block,\n// so a suite may carry a bench beside its ordinary tests without a second project. The mode\n// guard around each `bench` call keeps it out of test mode, so it never executes there.\nexport const probe = (): UserConfig => ({\n\tresolve,\n\ttest: {\n\t\tname: { label: 'probe', color: 'black' },\n\t\tinclude: ['tmp/probe/**/*.test.ts'],\n\t\tsetupFiles: ['./tests/setup.ts'],\n\t\tenvironment: 'node',\n\t\tbrowser: { enabled: false },\n\t\tfileParallelism: false,\n\t\tpool: 'threads',\n\t\tbenchmark: { include: ['tmp/probe/**/*.test.ts', 'tests/**/*.test.ts'] },\n\t},\n})\n";
|
|
1112
|
+
integration: "export const integration = (): UserConfig => ({\n\tresolve,\n\ttest: {\n\t\tname: { label: 'integration', color: 'blue' },\n\t\tinclude: ['tests/integration.test.ts'],\n\t\tsetupFiles: ['./tests/setup.ts'],\n{{global}}\t\tenvironment: 'node',\n\t},\n})\n";
|
|
1065
1113
|
}>;
|
|
1066
1114
|
tsconfigs: Readonly<{
|
|
1067
1115
|
src: Readonly<{
|
|
@@ -1078,11 +1126,11 @@ export declare class Compiler implements CompilerInterface {
|
|
|
1078
1126
|
}>;
|
|
1079
1127
|
vites: Readonly<{
|
|
1080
1128
|
src: Readonly<{
|
|
1081
|
-
core: "import { defineConfig } from 'vite'\nimport dts from 'vite-plugin-dts'\nimport { environmentBoundary, outputBoundary } from '../helpers.js'\nimport { peers, srcCore, resolveWorkspacePath } from '../../vite.config.ts'\n\nexport default defineConfig(\n\
|
|
1082
|
-
browser: "import { defineConfig } from 'vite'\nimport dts from 'vite-plugin-dts'\nimport { srcBrowser, resolveWorkspacePath } from '../../vite.config.ts'\n\n// vite-plugin-dts rolls this face into one declaration, and the roll-up reaches\n// src/core through a relative source path the tarball does not carry. The path\n// keeps each source module's own depth, so a module in a browser subfolder emits\n// one that leaves dist/src entirely. The rewrite
|
|
1083
|
-
server: "import { defineConfig } from 'vite'\nimport dts from 'vite-plugin-dts'\nimport { srcServer, resolveWorkspacePath } from '../../vite.config.ts'\n\n// vite-plugin-dts rolls this face into one declaration, and the roll-up reaches\n// src/core through a relative source path the tarball does not carry. The
|
|
1129
|
+
core: "import { defineConfig, mergeConfig } from 'vite'\nimport dts from 'vite-plugin-dts'\nimport { environmentBoundary, outputBoundary } from '../helpers.js'\nimport { peers, srcCore, resolveWorkspacePath } from '../../vite.config.ts'\n\nexport default defineConfig(\n\tmergeConfig(srcCore(), {\n\t\tpublicDir: false,\n\t\tplugins: [\n\t\t\toutputBoundary('dist/src/core'),\n\t\t\tenvironmentBoundary('src/core'),\n\t\t\tdts({\n\t\t\t\ttsconfigPath: resolveWorkspacePath('configs/src/tsconfig.core.json'),\n\t\t\t\tbundleTypes: {\n\t\t\t\t\textractorConfig: {\n\t\t\t\t\t\tcompiler: {\n\t\t\t\t\t\t\toverrideTsconfig: {\n\t\t\t\t\t\t\t\tcompilerOptions: { types: ['node'] },\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t],\n\t\tbuild: {\n\t\t\tlib: {\n\t\t\t\tentry: resolveWorkspacePath('src/core/index.ts'),\n\t\t\t\tformats: ['es', 'cjs'],\n\t\t\t\tfileName: (format: string) => (format === 'es' ? 'index.js' : 'index.cjs'),\n\t\t\t},\n\t\t\toutDir: 'dist/src/core',\n\t\t\trolldownOptions: {\n\t\t\t\texternal: (id: string) =>\n\t\t\t\t\tid.startsWith('node:') ||\n\t\t\t\t\tid.startsWith('@orkestrel/') ||\n\t\t\t\t\tpeers.some((peer) => id === peer || id.startsWith(peer + '/')),\n\t\t\t},\n\t\t},\n\t}),\n)\n";
|
|
1130
|
+
browser: "import { defineConfig, mergeConfig } from 'vite'\nimport dts from 'vite-plugin-dts'\nimport { srcBrowser, resolveWorkspacePath } from '../../vite.config.ts'\n\n// vite-plugin-dts rolls this face into one declaration, and the roll-up reaches\n// src/core through a relative source path the tarball does not carry. The path\n// keeps each source module's own depth, so a module in a browser subfolder emits\n// one that leaves dist/src entirely. The following rewrite externalizes core\n// through the package's own published root export, on the final roll-up only.\nexport default defineConfig(\n\tmergeConfig(srcBrowser(), {\n\t\tplugins: [\n\t\t\tdts({\n\t\t\t\ttsconfigPath: resolveWorkspacePath('configs/src/tsconfig.browser.json'),\n\t\t\t\tbundleTypes: true,\n\t\t\t\tbeforeWriteFile: (path, content) => ({\n\t\t\t\t\tcontent: /[\\\\/]dist[\\\\/]src[\\\\/]browser[\\\\/]index\\.d\\.ts$/.test(path)\n{{replacement}}\n\t\t\t\t\t\t: content,\n\t\t\t\t}),\n\t\t\t}),\n\t\t],\n\t}),\n)\n";
|
|
1131
|
+
server: "import { defineConfig, mergeConfig } from 'vite'\nimport dts from 'vite-plugin-dts'\nimport { srcServer, resolveWorkspacePath } from '../../vite.config.ts'\n\n// vite-plugin-dts rolls this face into one declaration, and the roll-up reaches\n// src/core through a relative source path the tarball does not carry. The\n// following rewrite externalizes core through the package's own published root\n// export, on the final roll-up only.\nexport default defineConfig(\n\tmergeConfig(srcServer(), {\n\t\tplugins: [\n\t\t\tdts({\n\t\t\t\ttsconfigPath: resolveWorkspacePath('configs/src/tsconfig.server.json'),\n\t\t\t\tbundleTypes: true,\n\t\t\t\tbeforeWriteFile: (path, content) => ({\n\t\t\t\t\tcontent: /[\\\\/]dist[\\\\/]src[\\\\/]server[\\\\/]index\\.d\\.ts$/.test(path)\n{{replacement}}\n\t\t\t\t\t\t: content,\n\t\t\t\t}),\n\t\t\t}),\n\t\t],\n\t}),\n)\n";
|
|
1084
1132
|
}>;
|
|
1085
|
-
bin: "import { defineConfig } from 'vite'\nimport { srcBin } from '../../vite.config.ts'\n\n// The `scaffold` executable build — a single ESM lib file, no declarations (an\n// executable ships no types), with the `#!/usr/bin/env node` shebang re-emitted through\n// `output.banner` (rolldown strips shebangs from source during bundling), and\n// `output.paths` rewriting the externalized `@src/*` specifiers to the built sibling\n// src environments (relative to `dist/bin/`), so the emitted bin resolves at runtime.\nexport default defineConfig(\n\
|
|
1133
|
+
bin: "import { defineConfig, mergeConfig } from 'vite'\nimport { srcBin } from '../../vite.config.ts'\n\n// The `scaffold` executable build — a single ESM lib file, no declarations (an\n// executable ships no types), with the `#!/usr/bin/env node` shebang re-emitted through\n// `output.banner` (rolldown strips shebangs from source during bundling), and\n// `output.paths` rewriting the externalized `@src/*` specifiers to the built sibling\n// src environments (relative to `dist/bin/`), so the emitted bin resolves at runtime.\nexport default defineConfig(\n\tmergeConfig(srcBin(), {\n\t\tbuild: {\n\t\t\trolldownOptions: {\n\t\t\t\toutput: {\n\t\t\t\t\tbanner: '#!/usr/bin/env node',\n{{paths}}\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}),\n)\n";
|
|
1086
1134
|
app: Readonly<{
|
|
1087
1135
|
browser: "import { defineConfig } from 'vite'\nimport { appBrowser } from '../../vite.config.ts'\n\nexport default defineConfig(appBrowser())\n";
|
|
1088
1136
|
server: "import { defineConfig } from 'vite'\nimport { appServer } from '../../vite.config.ts'\n\nexport default defineConfig(appServer())\n";
|
|
@@ -1235,7 +1283,13 @@ export declare class Compiler implements CompilerInterface {
|
|
|
1235
1283
|
*/
|
|
1236
1284
|
export declare const DEPENDENCY_NAME_PATTERN: RegExp;
|
|
1237
1285
|
|
|
1238
|
-
/** The
|
|
1286
|
+
/** The dependency sections a range-writing operation may change. */
|
|
1287
|
+
export declare interface DependencyPinSet {
|
|
1288
|
+
readonly runtime: readonly Dependency[];
|
|
1289
|
+
readonly development: readonly Dependency[];
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
/** The generated packed-package proof every publishing workspace is planned at. */
|
|
1239
1293
|
export declare const DISTRIBUTION_TEST_PATH = "tests/distribution.test.ts";
|
|
1240
1294
|
|
|
1241
1295
|
/**
|
|
@@ -1820,6 +1874,25 @@ export declare class Compiler implements CompilerInterface {
|
|
|
1820
1874
|
*/
|
|
1821
1875
|
export declare const isHex: Guard<string>;
|
|
1822
1876
|
|
|
1877
|
+
/**
|
|
1878
|
+
* Narrow a value to a {@link ManifestScript}.
|
|
1879
|
+
*
|
|
1880
|
+
* @remarks
|
|
1881
|
+
* Structural and bounded, exactly as {@link isDependency} is: a script name
|
|
1882
|
+
* and a script command are free text a manifest may carry, and which values a
|
|
1883
|
+
* region writer is willing to overwrite is the caller's decision rather than
|
|
1884
|
+
* this guard's.
|
|
1885
|
+
*
|
|
1886
|
+
* @example
|
|
1887
|
+
* ```ts
|
|
1888
|
+
* import { isManifestScript } from '@orkestrel/scaffold'
|
|
1889
|
+
*
|
|
1890
|
+
* isManifestScript({ name: 'test', command: 'vitest run', accepted: [] }) // true
|
|
1891
|
+
* isManifestScript({ name: 'test', command: 'vitest run' }) // false
|
|
1892
|
+
* ```
|
|
1893
|
+
*/
|
|
1894
|
+
export declare const isManifestScript: Guard<ManifestScript>;
|
|
1895
|
+
|
|
1823
1896
|
/**
|
|
1824
1897
|
* Narrow a value to a {@link Mirror}.
|
|
1825
1898
|
*
|
|
@@ -1878,7 +1951,9 @@ export declare class Compiler implements CompilerInterface {
|
|
|
1878
1951
|
* @remarks
|
|
1879
1952
|
* A plan reaches the writer, and the writer has no question channel, so this
|
|
1880
1953
|
* carries the whole law of the value: every artifact path, every claimed byte,
|
|
1881
|
-
* and the blueprint it was compiled from.
|
|
1954
|
+
* and the blueprint it was compiled from. An artifact at {@link MANIFEST_PATH}
|
|
1955
|
+
* must carry `birth` ownership. A plan claiming `content` or `presence` there
|
|
1956
|
+
* is refused because the compiler emits the manifest only as birth-owned.
|
|
1882
1957
|
*/
|
|
1883
1958
|
export declare const isPlan: Guard<Plan>;
|
|
1884
1959
|
|
|
@@ -1947,20 +2022,58 @@ export declare class Compiler implements CompilerInterface {
|
|
|
1947
2022
|
*/
|
|
1948
2023
|
export declare type Lookup = 'found' | 'missing' | 'unmatched' | 'failed';
|
|
1949
2024
|
|
|
2025
|
+
/** The manifest path every compiler plan emits with birth ownership. */
|
|
2026
|
+
export declare const MANIFEST_PATH = "package.json";
|
|
2027
|
+
|
|
2028
|
+
/** The dependency sections read from an existing package manifest. */
|
|
2029
|
+
export declare interface ManifestDependencySet {
|
|
2030
|
+
readonly runtime: readonly Dependency[];
|
|
2031
|
+
readonly development: readonly Dependency[];
|
|
2032
|
+
readonly peer: readonly Dependency[];
|
|
2033
|
+
}
|
|
2034
|
+
|
|
1950
2035
|
/**
|
|
1951
|
-
*
|
|
2036
|
+
* The manifest regions a writing operation may change.
|
|
2037
|
+
*
|
|
2038
|
+
* @remarks
|
|
2039
|
+
* Each region is written in place, so every byte outside the named ranges
|
|
2040
|
+
* survives. `pins` names the declared ranges and `scripts` the declared script
|
|
2041
|
+
* values; a region given nothing to write leaves its section untouched.
|
|
2042
|
+
*/
|
|
2043
|
+
export declare interface ManifestRegionSet {
|
|
2044
|
+
readonly pins: DependencyPinSet;
|
|
2045
|
+
readonly scripts: readonly ManifestScript[];
|
|
2046
|
+
}
|
|
2047
|
+
|
|
2048
|
+
/**
|
|
2049
|
+
* One manifest script a region-writing operation may replace.
|
|
2050
|
+
*
|
|
2051
|
+
* @remarks
|
|
2052
|
+
* `command` is the value the write lands. `accepted` is the closed set of
|
|
2053
|
+
* values the write is willing to overwrite, so a script holding anything else
|
|
2054
|
+
* is a chain its author customized and the write retains rather than takes it.
|
|
2055
|
+
* Retaining one differing script does not block another named script from being
|
|
2056
|
+
* appended or upgraded. An absent script is always writable and needs no entry
|
|
2057
|
+
* in `accepted`.
|
|
2058
|
+
*/
|
|
2059
|
+
export declare interface ManifestScript {
|
|
2060
|
+
readonly name: string;
|
|
2061
|
+
readonly command: string;
|
|
2062
|
+
readonly accepted: readonly string[];
|
|
2063
|
+
}
|
|
2064
|
+
|
|
2065
|
+
/**
|
|
2066
|
+
* Project a package manifest's text to the `@orkestrel/*` packages each dependency section declares.
|
|
1952
2067
|
*
|
|
1953
2068
|
* @param manifest - The `package.json` text.
|
|
1954
|
-
* @returns
|
|
1955
|
-
* with the first declaration of a repeated name winning.
|
|
2069
|
+
* @returns The runtime, development, and peer declarations as separate lists.
|
|
1956
2070
|
*
|
|
1957
2071
|
* @remarks
|
|
1958
|
-
*
|
|
1959
|
-
*
|
|
1960
|
-
*
|
|
1961
|
-
* unrelated dependencies are not this package's to report on.
|
|
2072
|
+
* Every other name is skipped rather than refused: a workspace's unrelated
|
|
2073
|
+
* dependencies are not this package's to report on. Keeping the sections
|
|
2074
|
+
* separate prevents a caller from treating a peer as a writable floor.
|
|
1962
2075
|
*
|
|
1963
|
-
* Never throws, and every row it returns satisfies `isDependency` while
|
|
2076
|
+
* Never throws, and every row it returns satisfies `isDependency` while each
|
|
1964
2077
|
* list satisfies `isCollection`, so the result crosses the compiler's own
|
|
1965
2078
|
* boundary without a second cleaning.
|
|
1966
2079
|
*
|
|
@@ -1969,10 +2082,10 @@ export declare class Compiler implements CompilerInterface {
|
|
|
1969
2082
|
* import { manifestToDependencies } from '@orkestrel/scaffold'
|
|
1970
2083
|
*
|
|
1971
2084
|
* manifestToDependencies('{"dependencies":{"@orkestrel/emitter":"^0.0.5","vite":"~8.2.0"}}')
|
|
1972
|
-
* // [{ name: '@orkestrel/emitter', range: '^0.0.5' }]
|
|
2085
|
+
* // { runtime: [{ name: '@orkestrel/emitter', range: '^0.0.5' }], development: [], peer: [] }
|
|
1973
2086
|
* ```
|
|
1974
2087
|
*/
|
|
1975
|
-
export declare function manifestToDependencies(manifest: string):
|
|
2088
|
+
export declare function manifestToDependencies(manifest: string): ManifestDependencySet;
|
|
1976
2089
|
|
|
1977
2090
|
/**
|
|
1978
2091
|
* Project a package manifest's text to its own name.
|
|
@@ -2169,6 +2282,9 @@ export declare class Compiler implements CompilerInterface {
|
|
|
2169
2282
|
*/
|
|
2170
2283
|
export declare const MAX_REGISTRY_BYTES = 33554432;
|
|
2171
2284
|
|
|
2285
|
+
/** Maximum length of one manifest script name or command. */
|
|
2286
|
+
export declare const MAX_SCRIPT_LENGTH = 4096;
|
|
2287
|
+
|
|
2172
2288
|
/** Maximum bytes retained across one whole plan or audit. */
|
|
2173
2289
|
export declare const MAX_TOTAL_ARTIFACT_BYTES = 104857600;
|
|
2174
2290
|
|
|
@@ -2508,7 +2624,9 @@ export declare class Compiler implements CompilerInterface {
|
|
|
2508
2624
|
*
|
|
2509
2625
|
* @remarks
|
|
2510
2626
|
* `hash` is the plan's content identity and is absent until the pin stage
|
|
2511
|
-
* fills it.
|
|
2627
|
+
* fills it. An artifact at `package.json` carries `birth` ownership because the
|
|
2628
|
+
* compiler emits the manifest that way. A plan claiming another ownership at
|
|
2629
|
+
* that path contradicts the compiler and is outside this contract.
|
|
2512
2630
|
*/
|
|
2513
2631
|
export declare interface Plan {
|
|
2514
2632
|
readonly blueprint: Blueprint;
|
|
@@ -2651,38 +2769,87 @@ export declare class Compiler implements CompilerInterface {
|
|
|
2651
2769
|
readonly latest?: never;
|
|
2652
2770
|
};
|
|
2653
2771
|
|
|
2772
|
+
/**
|
|
2773
|
+
* The `prepublishOnly` row that runs the packed-package proof against a real registry.
|
|
2774
|
+
*
|
|
2775
|
+
* @remarks
|
|
2776
|
+
* The proof reads `import.meta.env.MODE`, so without `--mode release` it passes
|
|
2777
|
+
* on an unreachable registry instead of failing. The row therefore has one home
|
|
2778
|
+
* and both the script compiler and the manifest region writer read it from here.
|
|
2779
|
+
*/
|
|
2780
|
+
export declare const RELEASE_PROOF_COMMAND = "npm run test:distribution -- --mode release";
|
|
2781
|
+
|
|
2654
2782
|
/**
|
|
2655
2783
|
* Replace declared dependency ranges in package manifest text.
|
|
2656
2784
|
*
|
|
2657
2785
|
* @param manifest - The manifest text to compile.
|
|
2658
|
-
* @param
|
|
2659
|
-
* @returns The manifest with every matching
|
|
2660
|
-
*
|
|
2786
|
+
* @param pins - The runtime and development names and replacement ranges.
|
|
2787
|
+
* @returns The manifest with every matching writable value replaced, or
|
|
2788
|
+
* `undefined` when a name has no quoted declaration in its named section.
|
|
2661
2789
|
*
|
|
2662
2790
|
* @remarks
|
|
2663
2791
|
* The compiler replaces values in place instead of serializing the manifest,
|
|
2664
2792
|
* so description, keywords, scripts, key order, indentation, and every byte
|
|
2665
|
-
* outside the named ranges survive.
|
|
2666
|
-
*
|
|
2667
|
-
*
|
|
2668
|
-
*
|
|
2793
|
+
* outside the named ranges survive. Runtime pins apply only to `dependencies`,
|
|
2794
|
+
* and development pins apply only to `devDependencies`. The compiler never
|
|
2795
|
+
* reads or writes `peerDependencies` or `peerDependenciesMeta`. An override or
|
|
2796
|
+
* resolution with the same name stays untouched.
|
|
2669
2797
|
*
|
|
2670
2798
|
* @example
|
|
2671
2799
|
* ```ts
|
|
2672
2800
|
* import { replaceManifestRanges } from '@orkestrel/scaffold'
|
|
2673
2801
|
*
|
|
2674
2802
|
* const manifest = '{"devDependencies":{"typescript":"^6"}}\n'
|
|
2675
|
-
* replaceManifestRanges(manifest,
|
|
2803
|
+
* replaceManifestRanges(manifest, {
|
|
2804
|
+
* runtime: [],
|
|
2805
|
+
* development: [{ name: 'typescript', range: '^7' }],
|
|
2806
|
+
* })
|
|
2676
2807
|
* // the manifest with the declared range replaced
|
|
2677
2808
|
* ```
|
|
2678
2809
|
*/
|
|
2679
|
-
export declare function replaceManifestRanges(manifest: string,
|
|
2810
|
+
export declare function replaceManifestRanges(manifest: string, pins: DependencyPinSet): string | undefined;
|
|
2811
|
+
|
|
2812
|
+
/**
|
|
2813
|
+
* Replace named script values in package manifest text.
|
|
2814
|
+
*
|
|
2815
|
+
* @param manifest - The manifest text to compile.
|
|
2816
|
+
* @param scripts - The scripts to write, each with the predecessors it accepts.
|
|
2817
|
+
* @returns The manifest carrying every absent or accepted named script and
|
|
2818
|
+
* retaining every differing string value, or `undefined` when a planned key
|
|
2819
|
+
* holds a non-string value or the text carries no readable `scripts` object to
|
|
2820
|
+
* write into.
|
|
2821
|
+
*
|
|
2822
|
+
* @remarks
|
|
2823
|
+
* The compiler replaces values in place instead of serializing the manifest, so
|
|
2824
|
+
* description, keywords, dependencies, key order, indentation, and every byte
|
|
2825
|
+
* outside the replaced ranges survive. A named script the manifest already
|
|
2826
|
+
* declares is overwritten only when its value is one of its
|
|
2827
|
+
* {@link ManifestScript.accepted} predecessors. The planned value stands. Any
|
|
2828
|
+
* other string is a chain the workspace author customized, so it stays
|
|
2829
|
+
* byte-identical while the other named scripts are written independently. A
|
|
2830
|
+
* named script the manifest does not declare is appended after the last
|
|
2831
|
+
* declared script, copying that section's indentation. A region declaring
|
|
2832
|
+
* nothing takes every named script as its first entries, indented from the line
|
|
2833
|
+
* its own opening brace sits on.
|
|
2834
|
+
*
|
|
2835
|
+
* @example
|
|
2836
|
+
* ```ts
|
|
2837
|
+
* import { replaceManifestScripts } from '@orkestrel/scaffold'
|
|
2838
|
+
*
|
|
2839
|
+
* const manifest = '{\n\t"scripts": {\n\t\t"test": "vitest run"\n\t}\n}\n'
|
|
2840
|
+
* replaceManifestScripts(manifest, [
|
|
2841
|
+
* { name: 'test', command: 'vitest run --no-cache', accepted: ['vitest run'] },
|
|
2842
|
+
* ])
|
|
2843
|
+
* // the manifest with the declared script replaced
|
|
2844
|
+
* ```
|
|
2845
|
+
*/
|
|
2846
|
+
export declare function replaceManifestScripts(manifest: string, scripts: readonly ManifestScript[]): string | undefined;
|
|
2680
2847
|
|
|
2681
2848
|
/**
|
|
2682
2849
|
* Replace dependency ranges in a plan's manifest and recompute its identity.
|
|
2683
2850
|
*
|
|
2684
2851
|
* @param plan - The plan carrying the manifest artifact to compile.
|
|
2685
|
-
* @param
|
|
2852
|
+
* @param pins - The runtime and development names and replacement ranges.
|
|
2686
2853
|
* @returns A plan with replaced manifest ranges and a matching hash, or
|
|
2687
2854
|
* `undefined` when the manifest or its identity cannot be compiled.
|
|
2688
2855
|
*
|
|
@@ -2696,10 +2863,10 @@ export declare class Compiler implements CompilerInterface {
|
|
|
2696
2863
|
* ```ts
|
|
2697
2864
|
* import { replacePlanRanges } from '@orkestrel/scaffold'
|
|
2698
2865
|
*
|
|
2699
|
-
* replacePlanRanges(plan,
|
|
2866
|
+
* replacePlanRanges(plan, pins) // the plan carrying the resolved writable ranges
|
|
2700
2867
|
* ```
|
|
2701
2868
|
*/
|
|
2702
|
-
export declare function replacePlanRanges(plan: Plan,
|
|
2869
|
+
export declare function replacePlanRanges(plan: Plan, pins: DependencyPinSet): Plan | undefined;
|
|
2703
2870
|
|
|
2704
2871
|
/**
|
|
2705
2872
|
* The one error this package throws, carrying the coded reason it was raised.
|