@orkestrel/scaffold 0.0.49 → 0.0.50
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 +184 -72
- package/dist/bin/main.js.map +1 -1
- package/dist/host/agents/orchestration.md +28 -3
- package/dist/host/claude/agents/orkestrel.md +44 -44
- package/dist/host/claude/rules/tests.md +6 -2
- package/dist/host/guides/scaffold.md +328 -96
- package/dist/host/manifest.json +6 -6
- 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 +26 -5
- package/dist/src/core/index.cjs +1285 -80
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +200 -36
- package/dist/src/core/index.d.ts +200 -36
- package/dist/src/core/index.js +1280 -81
- 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 a browser face is published from. Every selection here\n// 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\n// Whether the declaration selected by a typed CommonJS consumer admits that entry\n// to its compile probe. A `.d.cts` declaration admits and a `.d.mts`\n// declaration refuses. A `.d.ts` declaration takes its own nearest package scope.\n// Runtime target format remains the runtime drive's separate question.\nfunction resolvesCommonJS(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[]): 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!entry.commonjs,\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 commonjs = resolvesCommonJS(entry, installed)\n\t\tconst required = resolveTarget(entry, RUNTIME_CONDITIONS.commonjs) !== undefined\n\t\tconst module = resolveTarget(entry, RUNTIME_CONDITIONS.browser)\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: module !== undefined && module.startsWith(BROWSER_OUTPUT),\n\t\t\tmodule: imported !== undefined,\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\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)\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 && !entry.browser)(\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.browser && 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,35 @@ 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, or none when the workspace publishes
|
|
627
|
+
* nothing and therefore declares neither script.
|
|
628
|
+
*
|
|
629
|
+
* @remarks
|
|
630
|
+
* Publishing is what selects these scripts, so a private workspace answers an
|
|
631
|
+
* empty region and the write leaves its manifest alone.
|
|
632
|
+
*
|
|
633
|
+
* `accepted` carries the predecessor a target can hold before this package
|
|
634
|
+
* generated the packed-package proof: the same gate chain without
|
|
635
|
+
* {@link RELEASE_PROOF_COMMAND}. The value being written is always writable, so
|
|
636
|
+
* it is not repeated there. Any other value is a chain the workspace author
|
|
637
|
+
* customized, and {@link replaceManifestScripts} refuses the whole region
|
|
638
|
+
* rather than taking it.
|
|
639
|
+
*
|
|
640
|
+
* @example
|
|
641
|
+
* ```ts
|
|
642
|
+
* import { blueprintToWritableScripts, createBlueprint } from '@orkestrel/scaffold'
|
|
643
|
+
*
|
|
644
|
+
* const blueprint = createBlueprint('router', { src: ['core'] })
|
|
645
|
+
*
|
|
646
|
+
* blueprintToWritableScripts(blueprint)[0]?.name // 'test:distribution'
|
|
647
|
+
* ```
|
|
648
|
+
*/
|
|
649
|
+
export declare function blueprintToWritableScripts(blueprint: Blueprint): readonly ManifestScript[];
|
|
650
|
+
|
|
605
651
|
/** One module format a published library environment builds. */
|
|
606
652
|
export declare type BuildFormat = 'es' | 'cjs';
|
|
607
653
|
|
|
@@ -1050,7 +1096,7 @@ export declare class Compiler implements CompilerInterface {
|
|
|
1050
1096
|
}>;
|
|
1051
1097
|
app: Readonly<{
|
|
1052
1098
|
core: "export const appCore = (options?: UserConfig): UserConfig =>\n\tmergeConfig(\n\t\t{\n\t\t\tresolve,\n\t\t\tpublicDir: false,\n\t\t\tplugins: [environmentBoundary('app/core')],\n\t\t\ttest: {\n\t\t\t\tname: { label: 'app:core', color: 'cyan' },\n\t\t\t\tinclude: ['tests/app/core/**/*.test.ts'],\n\t\t\t\tsetupFiles: ['./tests/setup.ts'],\n\t\t\t\tenvironment: 'node',\n\t\t\t\tbrowser: { enabled: false },\n\t\t\t},\n\t\t},\n\t\toptions ?? {},\n\t)\n";
|
|
1053
|
-
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}}";
|
|
1099
|
+
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(options?: UserConfig): UserConfig {\n\treturn mergeConfig(applicationBrowser(false), options ?? {})\n}\n{{showcaseFactory}}";
|
|
1054
1100
|
server: "export const appServer = (options?: UserConfig): UserConfig =>\n\tmergeConfig(\n\t\t{\n\t\t\tresolve,\n\t\t\tpublicDir: false,\n\t\t\tplugins: [outputBoundary('dist/app/server'), environmentBoundary('app/server')],\n\t\t\tbuild: {\n\t\t\t\temptyOutDir: true,\n\t\t\t\tlib: {\n\t\t\t\t\tentry: resolveWorkspacePath('app/server/main.ts'),\n\t\t\t\t\tformats: ['cjs'],\n\t\t\t\t\tfileName: () => 'main.cjs',\n\t\t\t\t},\n\t\t\t\toutDir: resolveWorkspacePath('dist/app/server'),\n\t\t\t\ttarget: 'node22',\n\t\t\t\trolldownOptions: {\n\t\t\t\t\tonLog: enforceBuildLog,\n\t\t\t\t\texternal: (id: string) => id.startsWith('node:'),\n\t\t\t\t},\n\t\t\t},\n\t\t\ttest: {\n\t\t\t\tname: { label: 'app:server', color: 'green' },\n\t\t\t\tinclude: ['tests/app/server/**/*.test.ts'],\n\t\t\t\tsetupFiles: ['./tests/setup.ts', './tests/setupServer.ts'],\n\t\t\t\tenvironment: 'node',\n\t\t\t\tbrowser: { enabled: false },\n\t\t\t},\n\t\t},\n\t\toptions ?? {},\n\t)\n";
|
|
1055
1101
|
}>;
|
|
1056
1102
|
policy: "export const policy = (options?: UserConfig): UserConfig =>\n\tmergeConfig(\n\t\t{\n\t\t\tresolve,\n\t\t\ttest: {\n\t\t\t\tname: { label: 'policy', color: 'white' },\n\t\t\t\tinclude: ['tests/policy.test.ts'],\n\t\t\t\tsetupFiles: ['./tests/setup.ts'],\n\t\t\t\tenvironment: 'node',\n\t\t\t\tbrowser: { enabled: false },\n\t\t\t},\n\t\t},\n\t\toptions ?? {},\n\t)\n";
|
|
@@ -1235,7 +1281,13 @@ export declare class Compiler implements CompilerInterface {
|
|
|
1235
1281
|
*/
|
|
1236
1282
|
export declare const DEPENDENCY_NAME_PATTERN: RegExp;
|
|
1237
1283
|
|
|
1238
|
-
/** The
|
|
1284
|
+
/** The dependency sections a range-writing operation may change. */
|
|
1285
|
+
export declare interface DependencyPinSet {
|
|
1286
|
+
readonly runtime: readonly Dependency[];
|
|
1287
|
+
readonly development: readonly Dependency[];
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
/** The generated packed-package proof every publishing workspace is planned at. */
|
|
1239
1291
|
export declare const DISTRIBUTION_TEST_PATH = "tests/distribution.test.ts";
|
|
1240
1292
|
|
|
1241
1293
|
/**
|
|
@@ -1820,6 +1872,25 @@ export declare class Compiler implements CompilerInterface {
|
|
|
1820
1872
|
*/
|
|
1821
1873
|
export declare const isHex: Guard<string>;
|
|
1822
1874
|
|
|
1875
|
+
/**
|
|
1876
|
+
* Narrow a value to a {@link ManifestScript}.
|
|
1877
|
+
*
|
|
1878
|
+
* @remarks
|
|
1879
|
+
* Structural and bounded, exactly as {@link isDependency} is: a script name
|
|
1880
|
+
* and a script command are free text a manifest may carry, and which values a
|
|
1881
|
+
* region writer is willing to overwrite is the caller's decision rather than
|
|
1882
|
+
* this guard's.
|
|
1883
|
+
*
|
|
1884
|
+
* @example
|
|
1885
|
+
* ```ts
|
|
1886
|
+
* import { isManifestScript } from '@orkestrel/scaffold'
|
|
1887
|
+
*
|
|
1888
|
+
* isManifestScript({ name: 'test', command: 'vitest run', accepted: [] }) // true
|
|
1889
|
+
* isManifestScript({ name: 'test', command: 'vitest run' }) // false
|
|
1890
|
+
* ```
|
|
1891
|
+
*/
|
|
1892
|
+
export declare const isManifestScript: Guard<ManifestScript>;
|
|
1893
|
+
|
|
1823
1894
|
/**
|
|
1824
1895
|
* Narrow a value to a {@link Mirror}.
|
|
1825
1896
|
*
|
|
@@ -1878,7 +1949,9 @@ export declare class Compiler implements CompilerInterface {
|
|
|
1878
1949
|
* @remarks
|
|
1879
1950
|
* A plan reaches the writer, and the writer has no question channel, so this
|
|
1880
1951
|
* carries the whole law of the value: every artifact path, every claimed byte,
|
|
1881
|
-
* and the blueprint it was compiled from.
|
|
1952
|
+
* and the blueprint it was compiled from. An artifact at {@link MANIFEST_PATH}
|
|
1953
|
+
* must carry `birth` ownership. A plan claiming `content` or `presence` there
|
|
1954
|
+
* is refused because the compiler emits the manifest only as birth-owned.
|
|
1882
1955
|
*/
|
|
1883
1956
|
export declare const isPlan: Guard<Plan>;
|
|
1884
1957
|
|
|
@@ -1947,20 +2020,56 @@ export declare class Compiler implements CompilerInterface {
|
|
|
1947
2020
|
*/
|
|
1948
2021
|
export declare type Lookup = 'found' | 'missing' | 'unmatched' | 'failed';
|
|
1949
2022
|
|
|
2023
|
+
/** The manifest path every compiler plan emits with birth ownership. */
|
|
2024
|
+
export declare const MANIFEST_PATH = "package.json";
|
|
2025
|
+
|
|
2026
|
+
/** The dependency sections read from an existing package manifest. */
|
|
2027
|
+
export declare interface ManifestDependencySet {
|
|
2028
|
+
readonly runtime: readonly Dependency[];
|
|
2029
|
+
readonly development: readonly Dependency[];
|
|
2030
|
+
readonly peer: readonly Dependency[];
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
/**
|
|
2034
|
+
* The manifest regions a writing operation may change.
|
|
2035
|
+
*
|
|
2036
|
+
* @remarks
|
|
2037
|
+
* Each region is written in place, so every byte outside the named ranges
|
|
2038
|
+
* survives. `pins` names the declared ranges and `scripts` the declared script
|
|
2039
|
+
* values; a region given nothing to write leaves its section untouched.
|
|
2040
|
+
*/
|
|
2041
|
+
export declare interface ManifestRegionSet {
|
|
2042
|
+
readonly pins: DependencyPinSet;
|
|
2043
|
+
readonly scripts: readonly ManifestScript[];
|
|
2044
|
+
}
|
|
2045
|
+
|
|
1950
2046
|
/**
|
|
1951
|
-
*
|
|
2047
|
+
* One manifest script a region-writing operation may replace.
|
|
2048
|
+
*
|
|
2049
|
+
* @remarks
|
|
2050
|
+
* `command` is the value the write lands. `accepted` is the closed set of
|
|
2051
|
+
* values the write is willing to overwrite, so a script holding anything else
|
|
2052
|
+
* is a chain its author customized and the write refuses rather than takes it.
|
|
2053
|
+
* An absent script is always writable and needs no entry in `accepted`.
|
|
2054
|
+
*/
|
|
2055
|
+
export declare interface ManifestScript {
|
|
2056
|
+
readonly name: string;
|
|
2057
|
+
readonly command: string;
|
|
2058
|
+
readonly accepted: readonly string[];
|
|
2059
|
+
}
|
|
2060
|
+
|
|
2061
|
+
/**
|
|
2062
|
+
* Project a package manifest's text to the `@orkestrel/*` packages each dependency section declares.
|
|
1952
2063
|
*
|
|
1953
2064
|
* @param manifest - The `package.json` text.
|
|
1954
|
-
* @returns
|
|
1955
|
-
* with the first declaration of a repeated name winning.
|
|
2065
|
+
* @returns The runtime, development, and peer declarations as separate lists.
|
|
1956
2066
|
*
|
|
1957
2067
|
* @remarks
|
|
1958
|
-
*
|
|
1959
|
-
*
|
|
1960
|
-
*
|
|
1961
|
-
* unrelated dependencies are not this package's to report on.
|
|
2068
|
+
* Every other name is skipped rather than refused: a workspace's unrelated
|
|
2069
|
+
* dependencies are not this package's to report on. Keeping the sections
|
|
2070
|
+
* separate prevents a caller from treating a peer as a writable floor.
|
|
1962
2071
|
*
|
|
1963
|
-
* Never throws, and every row it returns satisfies `isDependency` while
|
|
2072
|
+
* Never throws, and every row it returns satisfies `isDependency` while each
|
|
1964
2073
|
* list satisfies `isCollection`, so the result crosses the compiler's own
|
|
1965
2074
|
* boundary without a second cleaning.
|
|
1966
2075
|
*
|
|
@@ -1969,10 +2078,10 @@ export declare class Compiler implements CompilerInterface {
|
|
|
1969
2078
|
* import { manifestToDependencies } from '@orkestrel/scaffold'
|
|
1970
2079
|
*
|
|
1971
2080
|
* manifestToDependencies('{"dependencies":{"@orkestrel/emitter":"^0.0.5","vite":"~8.2.0"}}')
|
|
1972
|
-
* // [{ name: '@orkestrel/emitter', range: '^0.0.5' }]
|
|
2081
|
+
* // { runtime: [{ name: '@orkestrel/emitter', range: '^0.0.5' }], development: [], peer: [] }
|
|
1973
2082
|
* ```
|
|
1974
2083
|
*/
|
|
1975
|
-
export declare function manifestToDependencies(manifest: string):
|
|
2084
|
+
export declare function manifestToDependencies(manifest: string): ManifestDependencySet;
|
|
1976
2085
|
|
|
1977
2086
|
/**
|
|
1978
2087
|
* Project a package manifest's text to its own name.
|
|
@@ -2169,6 +2278,9 @@ export declare class Compiler implements CompilerInterface {
|
|
|
2169
2278
|
*/
|
|
2170
2279
|
export declare const MAX_REGISTRY_BYTES = 33554432;
|
|
2171
2280
|
|
|
2281
|
+
/** Maximum length of one manifest script name or command. */
|
|
2282
|
+
export declare const MAX_SCRIPT_LENGTH = 4096;
|
|
2283
|
+
|
|
2172
2284
|
/** Maximum bytes retained across one whole plan or audit. */
|
|
2173
2285
|
export declare const MAX_TOTAL_ARTIFACT_BYTES = 104857600;
|
|
2174
2286
|
|
|
@@ -2508,7 +2620,9 @@ export declare class Compiler implements CompilerInterface {
|
|
|
2508
2620
|
*
|
|
2509
2621
|
* @remarks
|
|
2510
2622
|
* `hash` is the plan's content identity and is absent until the pin stage
|
|
2511
|
-
* fills it.
|
|
2623
|
+
* fills it. An artifact at `package.json` carries `birth` ownership because the
|
|
2624
|
+
* compiler emits the manifest that way. A plan claiming another ownership at
|
|
2625
|
+
* that path contradicts the compiler and is outside this contract.
|
|
2512
2626
|
*/
|
|
2513
2627
|
export declare interface Plan {
|
|
2514
2628
|
readonly blueprint: Blueprint;
|
|
@@ -2651,38 +2765,88 @@ export declare class Compiler implements CompilerInterface {
|
|
|
2651
2765
|
readonly latest?: never;
|
|
2652
2766
|
};
|
|
2653
2767
|
|
|
2768
|
+
/**
|
|
2769
|
+
* The `prepublishOnly` row that runs the packed-package proof against a real registry.
|
|
2770
|
+
*
|
|
2771
|
+
* @remarks
|
|
2772
|
+
* The proof reads `import.meta.env.MODE`, so without `--mode release` it passes
|
|
2773
|
+
* on an unreachable registry instead of failing. The row therefore has one home
|
|
2774
|
+
* and both the script compiler and the manifest region writer read it from here.
|
|
2775
|
+
*/
|
|
2776
|
+
export declare const RELEASE_PROOF_COMMAND = "npm run test:distribution -- --mode release";
|
|
2777
|
+
|
|
2654
2778
|
/**
|
|
2655
2779
|
* Replace declared dependency ranges in package manifest text.
|
|
2656
2780
|
*
|
|
2657
2781
|
* @param manifest - The manifest text to compile.
|
|
2658
|
-
* @param
|
|
2659
|
-
* @returns The manifest with every matching
|
|
2660
|
-
*
|
|
2782
|
+
* @param pins - The runtime and development names and replacement ranges.
|
|
2783
|
+
* @returns The manifest with every matching writable value replaced, or
|
|
2784
|
+
* `undefined` when a name has no quoted declaration in its named section.
|
|
2661
2785
|
*
|
|
2662
2786
|
* @remarks
|
|
2663
2787
|
* The compiler replaces values in place instead of serializing the manifest,
|
|
2664
2788
|
* so description, keywords, scripts, key order, indentation, and every byte
|
|
2665
|
-
* outside the named ranges survive.
|
|
2666
|
-
*
|
|
2667
|
-
*
|
|
2668
|
-
*
|
|
2789
|
+
* outside the named ranges survive. Runtime pins apply only to `dependencies`,
|
|
2790
|
+
* and development pins apply only to `devDependencies`. The compiler never
|
|
2791
|
+
* reads or writes `peerDependencies` or `peerDependenciesMeta`. An override or
|
|
2792
|
+
* resolution with the same name stays untouched.
|
|
2669
2793
|
*
|
|
2670
2794
|
* @example
|
|
2671
2795
|
* ```ts
|
|
2672
2796
|
* import { replaceManifestRanges } from '@orkestrel/scaffold'
|
|
2673
2797
|
*
|
|
2674
2798
|
* const manifest = '{"devDependencies":{"typescript":"^6"}}\n'
|
|
2675
|
-
* replaceManifestRanges(manifest,
|
|
2799
|
+
* replaceManifestRanges(manifest, {
|
|
2800
|
+
* runtime: [],
|
|
2801
|
+
* development: [{ name: 'typescript', range: '^7' }],
|
|
2802
|
+
* })
|
|
2676
2803
|
* // the manifest with the declared range replaced
|
|
2677
2804
|
* ```
|
|
2678
2805
|
*/
|
|
2679
|
-
export declare function replaceManifestRanges(manifest: string,
|
|
2806
|
+
export declare function replaceManifestRanges(manifest: string, pins: DependencyPinSet): string | undefined;
|
|
2807
|
+
|
|
2808
|
+
/**
|
|
2809
|
+
* Replace named script values in package manifest text.
|
|
2810
|
+
*
|
|
2811
|
+
* @param manifest - The manifest text to compile.
|
|
2812
|
+
* @param scripts - The scripts to write, each with the predecessors it accepts.
|
|
2813
|
+
* @returns The manifest carrying every named script, or `undefined` when a
|
|
2814
|
+
* named script holds a value outside what it accepts, or when the text carries
|
|
2815
|
+
* no readable `scripts` object to write into.
|
|
2816
|
+
*
|
|
2817
|
+
* @remarks
|
|
2818
|
+
* The compiler replaces values in place instead of serializing the manifest, so
|
|
2819
|
+
* description, keywords, dependencies, key order, indentation, and every byte
|
|
2820
|
+
* outside the replaced ranges survive. A named script the manifest already
|
|
2821
|
+
* declares is overwritten only when its value is the one being written or one
|
|
2822
|
+
* of its {@link ManifestScript.accepted} predecessors; anything else is a chain
|
|
2823
|
+
* the workspace author customized, and the whole region is refused without a
|
|
2824
|
+
* byte moving. A named script the manifest does not declare is appended after
|
|
2825
|
+
* the last declared script, copying that section's indentation. A region
|
|
2826
|
+
* declaring nothing takes every named script as its first entries, indented
|
|
2827
|
+
* from the line its own opening brace sits on.
|
|
2828
|
+
*
|
|
2829
|
+
* The refusal is whole rather than per script, so a manifest never ends up
|
|
2830
|
+
* holding one written script beside one refused one.
|
|
2831
|
+
*
|
|
2832
|
+
* @example
|
|
2833
|
+
* ```ts
|
|
2834
|
+
* import { replaceManifestScripts } from '@orkestrel/scaffold'
|
|
2835
|
+
*
|
|
2836
|
+
* const manifest = '{\n\t"scripts": {\n\t\t"test": "vitest run"\n\t}\n}\n'
|
|
2837
|
+
* replaceManifestScripts(manifest, [
|
|
2838
|
+
* { name: 'test', command: 'vitest run --no-cache', accepted: ['vitest run'] },
|
|
2839
|
+
* ])
|
|
2840
|
+
* // the manifest with the declared script replaced
|
|
2841
|
+
* ```
|
|
2842
|
+
*/
|
|
2843
|
+
export declare function replaceManifestScripts(manifest: string, scripts: readonly ManifestScript[]): string | undefined;
|
|
2680
2844
|
|
|
2681
2845
|
/**
|
|
2682
2846
|
* Replace dependency ranges in a plan's manifest and recompute its identity.
|
|
2683
2847
|
*
|
|
2684
2848
|
* @param plan - The plan carrying the manifest artifact to compile.
|
|
2685
|
-
* @param
|
|
2849
|
+
* @param pins - The runtime and development names and replacement ranges.
|
|
2686
2850
|
* @returns A plan with replaced manifest ranges and a matching hash, or
|
|
2687
2851
|
* `undefined` when the manifest or its identity cannot be compiled.
|
|
2688
2852
|
*
|
|
@@ -2696,10 +2860,10 @@ export declare class Compiler implements CompilerInterface {
|
|
|
2696
2860
|
* ```ts
|
|
2697
2861
|
* import { replacePlanRanges } from '@orkestrel/scaffold'
|
|
2698
2862
|
*
|
|
2699
|
-
* replacePlanRanges(plan,
|
|
2863
|
+
* replacePlanRanges(plan, pins) // the plan carrying the resolved writable ranges
|
|
2700
2864
|
* ```
|
|
2701
2865
|
*/
|
|
2702
|
-
export declare function replacePlanRanges(plan: Plan,
|
|
2866
|
+
export declare function replacePlanRanges(plan: Plan, pins: DependencyPinSet): Plan | undefined;
|
|
2703
2867
|
|
|
2704
2868
|
/**
|
|
2705
2869
|
* The one error this package throws, carrying the coded reason it was raised.
|