@orkestrel/scaffold 0.0.103 → 0.0.104
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/dist/agents/skills/orkestrel-dispatch/scripts/helpers.js +84 -1
- package/dist/agents/skills/orkestrel-dispatch/scripts/launch.js +21 -6
- package/dist/agents/skills/orkestrel-harden/scripts/titles.js +338 -0
- package/dist/agents/skills/orkestrel-publish/scripts/swap.js +3 -39
- package/dist/agents/skills/orkestrel-publish/scripts/wave.js +433 -47
- package/dist/agents/skills/orkestrel-publish/scripts/window.js +2 -1
- package/dist/bin/main.js +16 -9
- package/dist/bin/main.js.map +1 -1
- package/dist/host/agents/orchestration.md +3 -3
- package/dist/host/agents/skills/enterprise-bootstrap/references/components.md +2 -2
- package/dist/host/agents/skills/enterprise-bootstrap/references/inspection.md +1 -1
- package/dist/host/agents/skills/orkestrel-build/SKILL.md +2 -2
- package/dist/host/agents/skills/orkestrel-debrief/SKILL.md +2 -2
- package/dist/host/agents/skills/orkestrel-debrief/references/field-testing.md +1 -1
- package/dist/host/agents/skills/orkestrel-debrief/references/instruction-audit.md +3 -3
- package/dist/host/agents/skills/orkestrel-dispatch/SKILL.md +10 -10
- package/dist/host/agents/skills/orkestrel-dispatch/references/launch.md +1 -0
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/helpers.ts +100 -1
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/launch.ts +27 -6
- package/dist/host/agents/skills/orkestrel-falsify/SKILL.md +4 -4
- package/dist/host/agents/skills/orkestrel-falsify/agents/openai.yaml +1 -1
- package/dist/host/agents/skills/orkestrel-falsify/references/brief.md +8 -7
- package/dist/host/agents/skills/orkestrel-falsify/references/reconcile.md +6 -6
- package/dist/host/agents/skills/orkestrel-harden/SKILL.md +6 -1
- package/dist/host/agents/skills/orkestrel-harden/references/bindings.md +1700 -0
- package/dist/host/agents/skills/orkestrel-harden/references/contract.md +2 -2
- package/dist/host/agents/skills/orkestrel-harden/references/equivalence.md +48 -0
- package/dist/host/agents/skills/orkestrel-harden/references/hardening.md +4 -2
- package/dist/host/agents/skills/orkestrel-harden/scripts/titles.ts +425 -0
- package/dist/host/agents/skills/orkestrel-journey/SKILL.md +2 -2
- package/dist/host/agents/skills/orkestrel-journey/references/captures.md +9 -8
- package/dist/host/agents/skills/orkestrel-polish/SKILL.md +3 -3
- package/dist/host/agents/skills/orkestrel-publish/SKILL.md +2 -2
- package/dist/host/agents/skills/orkestrel-publish/references/release.md +1 -0
- package/dist/host/agents/skills/orkestrel-publish/references/wave.md +56 -13
- package/dist/host/agents/skills/orkestrel-publish/references/window.md +6 -6
- package/dist/host/agents/skills/orkestrel-publish/scripts/swap.ts +5 -49
- package/dist/host/agents/skills/orkestrel-publish/scripts/wave.ts +646 -49
- package/dist/host/agents/skills/orkestrel-publish/scripts/window.ts +2 -1
- package/dist/host/agents/transports/codex.md +1 -1
- package/dist/host/claude/agents/orkestrel.md +54 -53
- package/dist/host/claude/agents/reviewer.md +4 -4
- package/dist/host/claude/rules/documentation.md +2 -1
- package/dist/host/claude/rules/patterns.md +1 -1
- package/dist/host/claude/rules/quality.md +2 -2
- package/dist/host/claude/rules/styles.md +1 -1
- package/dist/host/claude/rules/tests.md +34 -8
- package/dist/host/claude/rules/typescript.md +29 -0
- package/dist/host/claude/rules/workspace.md +32 -22
- package/dist/host/claude/rules/writing.md +34 -26
- package/dist/host/claude/settings.json +2 -0
- package/dist/host/claude/skills/orkestrel-debrief/SKILL.md +1 -1
- package/dist/host/claude/skills/orkestrel-falsify/SKILL.md +1 -1
- package/dist/host/codex/agents/analyst.toml +3 -3
- package/dist/host/codex/agents/reviewer.toml +4 -4
- package/dist/host/configs/policy.ts +13 -2
- package/dist/host/dotfiles/editorconfig +0 -1
- package/dist/host/dotfiles/gitattributes +0 -1
- package/dist/host/dotfiles/gitignore +0 -1
- package/dist/host/dotfiles/oxlintrc.json +24 -0
- package/dist/host/guides/agent.md +1638 -598
- package/dist/host/guides/budget.md +89 -29
- package/dist/host/guides/form.md +477 -91
- package/dist/host/guides/mcp.md +14 -2
- package/dist/host/guides/ollama.md +97 -73
- package/dist/host/guides/probe.md +1 -1
- package/dist/host/guides/scaffold.md +158 -101
- package/dist/host/guides/study.md +2635 -0
- package/dist/host/guides/table.md +225 -15
- package/dist/host/guides/terminal.md +22 -20
- package/dist/host/guides/test.md +689 -255
- package/dist/host/manifest.json +87 -63
- package/dist/host/tests/config.test.ts +136 -82
- package/dist/host/tests/policy.test.ts +39 -1
- package/dist/host/tests/setupPolicy.ts +86 -28
- package/dist/src/core/index.cjs +1387 -611
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +35 -20
- package/dist/src/core/index.d.ts +35 -20
- package/dist/src/core/index.js +1385 -612
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +12 -12
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +12 -12
- package/dist/src/server/index.d.ts +12 -12
- package/dist/src/server/index.js +12 -12
- package/dist/src/server/index.js.map +1 -1
- package/package.json +4 -4
package/dist/src/core/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { fillTemplate } from "@orkestrel/template";
|
|
|
3
3
|
import { Emitter } from "@orkestrel/emitter";
|
|
4
4
|
var package_default = {
|
|
5
5
|
name: "@orkestrel/scaffold",
|
|
6
|
-
version: "0.0.
|
|
6
|
+
version: "0.0.104",
|
|
7
7
|
description: "Scaffold workspaces with commands: new, audit, repair, catalog, and overwrite.",
|
|
8
8
|
keywords: [
|
|
9
9
|
"audit",
|
|
@@ -103,11 +103,11 @@ var package_default = {
|
|
|
103
103
|
},
|
|
104
104
|
devDependencies: {
|
|
105
105
|
"@microsoft/api-extractor": "^7.59.4",
|
|
106
|
-
"@orkestrel/browser": "^0.0.
|
|
106
|
+
"@orkestrel/browser": "^0.0.29",
|
|
107
107
|
"@orkestrel/guide": "^0.0.24",
|
|
108
108
|
"@orkestrel/html": "^0.0.12",
|
|
109
|
-
"@orkestrel/probe": "^0.0.
|
|
110
|
-
"@orkestrel/test": "^0.0.
|
|
109
|
+
"@orkestrel/probe": "^0.0.24",
|
|
110
|
+
"@orkestrel/test": "^0.0.28",
|
|
111
111
|
"@types/node": "^26.6.4",
|
|
112
112
|
"@vitest/browser-playwright": "^4.1.11",
|
|
113
113
|
"oxfmt": "^0.72.0",
|
|
@@ -249,6 +249,8 @@ var APP_MATRIX = Object.freeze({
|
|
|
249
249
|
var BIN_CONFIGS = Object.freeze(["configs/src/vite.bin.config.ts", "configs/src/tsconfig.bin.json"]);
|
|
250
250
|
/** Names the executable entry whose presence makes a workspace `bin`. */
|
|
251
251
|
var BIN_ENTRY_PATH = "src/bin/main.ts";
|
|
252
|
+
/** Names the built executable a bin workspace's manifest command runs. */
|
|
253
|
+
var BIN_OUTPUT_PATH = "dist/bin/main.js";
|
|
252
254
|
/**
|
|
253
255
|
* Lists the paths a target receives from the vendored data root, frozen.
|
|
254
256
|
*
|
|
@@ -477,6 +479,8 @@ var GLOBAL_SETUP_PATH = "tests/setupGlobal.ts";
|
|
|
477
479
|
var GUIDES_TEST_PATH = "tests/guides.test.ts";
|
|
478
480
|
/** Names the generated packed-package proof every publishing workspace is planned at. */
|
|
479
481
|
var DISTRIBUTION_TEST_PATH = "tests/distribution.test.ts";
|
|
482
|
+
/** Names the content-owned harness imported by the generated distribution proof. */
|
|
483
|
+
var DISTRIBUTION_SETUP_PATH = "tests/setupDistribution.ts";
|
|
480
484
|
/**
|
|
481
485
|
* Names the `prepublishOnly` row that runs the packed-package proof against a real registry.
|
|
482
486
|
*
|
|
@@ -495,6 +499,8 @@ var INTEGRATION_TEST_PATH = "tests/integration.test.ts";
|
|
|
495
499
|
var MANIFEST_PATH = "package.json";
|
|
496
500
|
/** Names the official-tooling drift proof whose presence makes a workspace `conformance`. */
|
|
497
501
|
var CONFORMANCE_TEST_PATH = "tests/conformance.test.ts";
|
|
502
|
+
/** Names the real-tree proof whose presence makes a workspace `tree`. */
|
|
503
|
+
var TREE_TEST_PATH = "tests/tree.test.ts";
|
|
498
504
|
/** Names the live-service readiness module whose presence makes a workspace `service`. */
|
|
499
505
|
var SERVICE_SETUP_PATH = "tests/setupService.ts";
|
|
500
506
|
/**
|
|
@@ -526,8 +532,8 @@ var NAME_PATTERN = /^[a-z][a-z0-9-]*$/;
|
|
|
526
532
|
* @remarks
|
|
527
533
|
* A dependency name reaches a path, because a workspace's guide mirror is
|
|
528
534
|
* derived from it. Fixing the scope and forbidding everything but the bare name
|
|
529
|
-
* after it is what stops a hand-built name from
|
|
530
|
-
* mirror belongs in.
|
|
535
|
+
* after it is what stops a hand-built name from resolving outside the directory
|
|
536
|
+
* the mirror belongs in.
|
|
531
537
|
*/
|
|
532
538
|
var DEPENDENCY_NAME_PATTERN = /^@orkestrel\/[a-z][a-z0-9-]*$/;
|
|
533
539
|
/**
|
|
@@ -941,7 +947,13 @@ function isNamedPlugin(plugin: PluginOption): plugin is { name: string } {
|
|
|
941
947
|
name: { label: name, color: 'magenta' },
|
|
942
948
|
root: resolveWorkspacePath('.'),
|
|
943
949
|
isolate: false,
|
|
944
|
-
setupFiles: [
|
|
950
|
+
setupFiles: [
|
|
951
|
+
'./tests/setup.ts',
|
|
952
|
+
'./tests/setupBrowser.ts',
|
|
953
|
+
...(existsSync(resolveWorkspacePath('tests/setupStyles.ts'))
|
|
954
|
+
? ['./tests/setupStyles.ts']
|
|
955
|
+
: []),
|
|
956
|
+
],
|
|
945
957
|
browser: {
|
|
946
958
|
enabled: true,
|
|
947
959
|
provider: playwright(browserOptions),
|
|
@@ -1461,6 +1473,24 @@ export function skills(override?: UserConfig): UserConfig {
|
|
|
1461
1473
|
}
|
|
1462
1474
|
return mergeOverride(project, override)
|
|
1463
1475
|
}
|
|
1476
|
+
`,
|
|
1477
|
+
tree: `// The laws this package states about its own real tree.
|
|
1478
|
+
// Standalone Node proof; reads the tree without a build, install, or service.
|
|
1479
|
+
export function tree(override?: UserConfig): UserConfig {
|
|
1480
|
+
const project: UserConfig = {
|
|
1481
|
+
resolve,
|
|
1482
|
+
test: {
|
|
1483
|
+
name: { label: 'tree', color: 'magenta' },
|
|
1484
|
+
include: ['${TREE_TEST_PATH}'],
|
|
1485
|
+
setupFiles: ['./tests/setup.ts', './tests/setupServer.ts'],
|
|
1486
|
+
pool: 'threads',
|
|
1487
|
+
isolate: false,
|
|
1488
|
+
environment: 'node',
|
|
1489
|
+
browser: { enabled: false },
|
|
1490
|
+
},
|
|
1491
|
+
}
|
|
1492
|
+
return mergeOverride(project, override)
|
|
1493
|
+
}
|
|
1464
1494
|
`,
|
|
1465
1495
|
conformance: `// Where this package drifts from the official tooling it stays compatible with.
|
|
1466
1496
|
// The subject is this package, so the proof is hermetic and stays in \`npm test\`.
|
|
@@ -2225,11 +2255,11 @@ export function resolveSystemBrowser(
|
|
|
2225
2255
|
* verified system channel, then the platform default channel. An operator override outranks
|
|
2226
2256
|
* discovery and is returned exactly as given: none of those environment values is checked
|
|
2227
2257
|
* against the filesystem, because verifying an override would defeat the override. The pinned
|
|
2228
|
-
* managed revision outranks anything found on the host because it is deterministic.
|
|
2229
|
-
*
|
|
2230
|
-
* discovered system channel is verified before it is named. The platform default is
|
|
2231
|
-
* as well and exists only as a last resort: Windows takes \`msedge\`, which ships with the
|
|
2232
|
-
* never collides with a foreground Chrome.
|
|
2258
|
+
* managed revision outranks anything found on the host because it is deterministic. Every resolved
|
|
2259
|
+
* managed path names the full Chromium executable so headless launches retain browser permissions.
|
|
2260
|
+
* Only a discovered system channel is verified before it is named. The platform default is
|
|
2261
|
+
* unverified as well and exists only as a last resort: Windows takes \`msedge\`, which ships with the
|
|
2262
|
+
* OS and never collides with a foreground Chrome.
|
|
2233
2263
|
*
|
|
2234
2264
|
* Set \`PLAYWRIGHT_SCROLLBARS=classic\` only for runs that measure scrollbar compensation, never
|
|
2235
2265
|
* for a standing gate. Every Chromium launch in that run omits Playwright's \`--hide-scrollbars\`
|
|
@@ -2276,7 +2306,6 @@ export function resolveBrowser(
|
|
|
2276
2306
|
}
|
|
2277
2307
|
const managed = pinned === undefined ? undefined : resolveManagedBrowser(pinned)
|
|
2278
2308
|
if (managed !== undefined) {
|
|
2279
|
-
if (managed === pinned) return scrollbars === 'classic' ? { launchOptions: launch } : {}
|
|
2280
2309
|
return { launchOptions: { executablePath: managed, ...launch } }
|
|
2281
2310
|
}
|
|
2282
2311
|
const bundled = resolveBundledBrowser(platform, root)
|
|
@@ -2450,8 +2479,8 @@ declare module 'vitest' {
|
|
|
2450
2479
|
}
|
|
2451
2480
|
`,
|
|
2452
2481
|
sheet: `import sheet from '{{root}}dist/src/{{name}}/index.css?raw'
|
|
2482
|
+
import { adoptSheet, readLayerNames } from '@orkestrel/test/browser'
|
|
2453
2483
|
import { describe, expect, it } from 'vitest'
|
|
2454
|
-
import { adoptSheet, readLayerNames } from '{{setup}}'
|
|
2455
2484
|
|
|
2456
2485
|
describe('{{name}} entry', () => {
|
|
2457
2486
|
it('opens with the declared cascade order', () => {
|
|
@@ -2475,120 +2504,8 @@ describe('{{name}} entry', () => {
|
|
|
2475
2504
|
})
|
|
2476
2505
|
`,
|
|
2477
2506
|
styles: Object.freeze({
|
|
2478
|
-
module:
|
|
2479
|
-
|
|
2480
|
-
readonly sheet: CSSStyleSheet
|
|
2481
|
-
release(): void
|
|
2482
|
-
}
|
|
2483
|
-
|
|
2484
|
-
/**
|
|
2485
|
-
* Adopts CSS text into the document with an idempotent release operation.
|
|
2486
|
-
* @param text - The compiled sheet text.
|
|
2487
|
-
* @returns The adopted sheet and its release operation.
|
|
2488
|
-
* @example
|
|
2489
|
-
* \`\`\`ts
|
|
2490
|
-
* const adopted = adoptSheet('.sample { color: red }')
|
|
2491
|
-
* adopted.release()
|
|
2492
|
-
* \`\`\`
|
|
2493
|
-
*/
|
|
2494
|
-
export function adoptSheet(text: string): SheetAdoption {
|
|
2495
|
-
const sheet = new CSSStyleSheet()
|
|
2496
|
-
sheet.replaceSync(text)
|
|
2497
|
-
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet]
|
|
2498
|
-
return {
|
|
2499
|
-
sheet,
|
|
2500
|
-
release: () => {
|
|
2501
|
-
document.adoptedStyleSheets = document.adoptedStyleSheets.filter(
|
|
2502
|
-
(candidate) => candidate !== sheet,
|
|
2503
|
-
)
|
|
2504
|
-
},
|
|
2505
|
-
}
|
|
2506
|
-
}
|
|
2507
|
-
|
|
2508
|
-
/**
|
|
2509
|
-
* Reads layer statements and blocks in declaration order, including repeated names.
|
|
2510
|
-
* @param source - The parsed sheet, or a grouping rule inside it.
|
|
2511
|
-
* @param layer - The qualified name of the layer that encloses \`source\`, when one does.
|
|
2512
|
-
* @returns Qualified layer names, with an empty name for an anonymous layer.
|
|
2513
|
-
* @example
|
|
2514
|
-
* \`\`\`ts
|
|
2515
|
-
* readLayerNames(new CSSStyleSheet()) // []
|
|
2516
|
-
* \`\`\`
|
|
2517
|
-
*/
|
|
2518
|
-
export function readLayerNames(
|
|
2519
|
-
source: CSSStyleSheet | CSSGroupingRule,
|
|
2520
|
-
layer?: string,
|
|
2521
|
-
): readonly string[] {
|
|
2522
|
-
return Array.from(source.cssRules).flatMap((rule) => [
|
|
2523
|
-
...(rule instanceof CSSLayerStatementRule
|
|
2524
|
-
? rule.nameList
|
|
2525
|
-
: rule instanceof CSSLayerBlockRule
|
|
2526
|
-
? [rule.name]
|
|
2527
|
-
: []
|
|
2528
|
-
).map((name) => (layer === undefined ? name : layer + '.' + name)),
|
|
2529
|
-
...(rule instanceof CSSGroupingRule
|
|
2530
|
-
? readLayerNames(
|
|
2531
|
-
rule,
|
|
2532
|
-
rule instanceof CSSLayerBlockRule
|
|
2533
|
-
? layer === undefined
|
|
2534
|
-
? rule.name
|
|
2535
|
-
: layer + '.' + rule.name
|
|
2536
|
-
: layer,
|
|
2537
|
-
)
|
|
2538
|
-
: []),
|
|
2539
|
-
])
|
|
2540
|
-
}
|
|
2541
|
-
`,
|
|
2542
|
-
proof: `import type { SheetAdoption } from './setupStyles.js'
|
|
2543
|
-
import { describe, expect, it } from 'vitest'
|
|
2544
|
-
import { adoptSheet, readLayerNames } from './setupStyles.js'
|
|
2545
|
-
|
|
2546
|
-
describe('CSSOM instruments', () => {
|
|
2547
|
-
it('adopts and releases only its own sheet, including repeated release', () => {
|
|
2548
|
-
const previous = [...document.adoptedStyleSheets]
|
|
2549
|
-
const first: SheetAdoption = adoptSheet('.sample { color: red }')
|
|
2550
|
-
try {
|
|
2551
|
-
const second = adoptSheet('.other { display: block }')
|
|
2552
|
-
try {
|
|
2553
|
-
expect(document.adoptedStyleSheets).toEqual([...previous, first.sheet, second.sheet])
|
|
2554
|
-
expect(first.sheet.cssRules.length).toBe(1)
|
|
2555
|
-
first.release()
|
|
2556
|
-
first.release()
|
|
2557
|
-
expect(document.adoptedStyleSheets).toEqual([...previous, second.sheet])
|
|
2558
|
-
} finally {
|
|
2559
|
-
second.release()
|
|
2560
|
-
}
|
|
2561
|
-
} finally {
|
|
2562
|
-
first.release()
|
|
2563
|
-
}
|
|
2564
|
-
expect(document.adoptedStyleSheets).toEqual(previous)
|
|
2565
|
-
})
|
|
2566
|
-
|
|
2567
|
-
it('reads statements, blocks, nested conditions, and anonymous layers in order', () => {
|
|
2568
|
-
const adopted = adoptSheet(
|
|
2569
|
-
'@layer first, second; @layer first { @media all { @layer nested {} } } @layer {}',
|
|
2570
|
-
)
|
|
2571
|
-
try {
|
|
2572
|
-
expect(readLayerNames(adopted.sheet)).toEqual([
|
|
2573
|
-
'first',
|
|
2574
|
-
'second',
|
|
2575
|
-
'first',
|
|
2576
|
-
'first.nested',
|
|
2577
|
-
'',
|
|
2578
|
-
])
|
|
2579
|
-
} finally {
|
|
2580
|
-
adopted.release()
|
|
2581
|
-
}
|
|
2582
|
-
})
|
|
2583
|
-
|
|
2584
|
-
it('returns no layer names for an empty sheet or an unlayered rule', () => {
|
|
2585
|
-
const sheet = new CSSStyleSheet()
|
|
2586
|
-
expect(readLayerNames(sheet)).toEqual([])
|
|
2587
|
-
sheet.replaceSync('.sample { color: red }')
|
|
2588
|
-
expect(readLayerNames(sheet)).toEqual([])
|
|
2589
|
-
})
|
|
2590
|
-
})
|
|
2591
|
-
`
|
|
2507
|
+
module: "",
|
|
2508
|
+
proof: ""
|
|
2592
2509
|
}),
|
|
2593
2510
|
setup: "",
|
|
2594
2511
|
global: Object.freeze({
|
|
@@ -2624,13 +2541,7 @@ describe('bin entry', () => {
|
|
|
2624
2541
|
})
|
|
2625
2542
|
`,
|
|
2626
2543
|
distribution: Object.freeze({
|
|
2627
|
-
|
|
2628
|
-
// is packed and installed into a throwaway consumer, and every following claim is read
|
|
2629
|
-
// off that installed tree: the exports map it publishes, the declarations it ships,
|
|
2630
|
-
// and the module objects a real runtime hands a consumer. Nothing here names this
|
|
2631
|
-
// package, one of its exports, or how many there are, so the proof stays true as
|
|
2632
|
-
// the published surface moves.
|
|
2633
|
-
{{types}}import type { SpawnSyncReturns } from 'node:child_process'
|
|
2544
|
+
module: `{{types}}import type { SpawnSyncReturns } from 'node:child_process'
|
|
2634
2545
|
import type { TestContext } from 'vitest'
|
|
2635
2546
|
import { spawnSync } from 'node:child_process'
|
|
2636
2547
|
import {
|
|
@@ -2645,67 +2556,126 @@ import {
|
|
|
2645
2556
|
} from 'node:fs'
|
|
2646
2557
|
{{transport}}import { createRequire } from 'node:module'
|
|
2647
2558
|
import { tmpdir } from 'node:os'
|
|
2648
|
-
import { dirname, join, resolve } from 'node:path'
|
|
2649
|
-
import { fileURLToPath } from 'node:url'
|
|
2650
|
-
|
|
2559
|
+
import { dirname, join, relative as relativePath, resolve, sep } from 'node:path'
|
|
2560
|
+
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
2561
|
+
import { isDeepStrictEqual } from 'node:util'
|
|
2562
|
+
import { parseSync } from 'vite'
|
|
2563
|
+
{{launcher}}
|
|
2564
|
+
/** Defines the lifecycle and installed consumer read by a distribution proof. */
|
|
2565
|
+
export interface DistributionSuiteInterface {
|
|
2566
|
+
readonly scratch: string
|
|
2567
|
+
start(): void
|
|
2568
|
+
read(context: TestContext): DistributionStage
|
|
2569
|
+
destroy(): void
|
|
2570
|
+
}
|
|
2651
2571
|
|
|
2652
|
-
|
|
2653
|
-
|
|
2572
|
+
/** Holds the observed and required values for one distribution assertion. */
|
|
2573
|
+
export interface DistributionObservation {
|
|
2574
|
+
readonly actual: unknown
|
|
2575
|
+
readonly expected: unknown
|
|
2576
|
+
}
|
|
2577
|
+
|
|
2578
|
+
/** Defines one independently registered distribution observation. */
|
|
2579
|
+
export interface DistributionCase {
|
|
2580
|
+
readonly label: string
|
|
2581
|
+
readonly observe: (
|
|
2582
|
+
context: TestContext,
|
|
2583
|
+
) => DistributionObservation | Promise<DistributionObservation>
|
|
2584
|
+
}
|
|
2585
|
+
|
|
2586
|
+
/** Holds the installed export map's classified publications. */
|
|
2587
|
+
export interface DistributionClassification {
|
|
2588
|
+
readonly entries: readonly DistributionEntry[]
|
|
2589
|
+
readonly subpaths: readonly string[]
|
|
2590
|
+
readonly undeclared: readonly string[]
|
|
2591
|
+
readonly excluded: readonly string[]
|
|
2592
|
+
readonly targets: readonly string[]
|
|
2593
|
+
}
|
|
2594
|
+
|
|
2595
|
+
/** Names the workspace whose packed artifact the proof consumes. */
|
|
2596
|
+
export const DISTRIBUTION_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
|
2597
|
+
/** Names the npm executable on the running host. */
|
|
2598
|
+
export const DISTRIBUTION_NPM = process.platform === 'win32' ? 'npm.cmd' : 'npm'
|
|
2654
2599
|
// The compiler this workspace installs, run as a command rather than called in
|
|
2655
2600
|
// process: the command and its plain-text diagnostics are the same across the
|
|
2656
2601
|
// compiler majors this toolchain supports, and its in-process API is not. It is
|
|
2657
2602
|
// resolved from the workspace under proof, so a consumer of the packed artifact is
|
|
2658
2603
|
// checked by the same compiler that workspace's own \`check\` script runs.
|
|
2659
|
-
|
|
2604
|
+
/** Names the compiler installed by the workspace under proof. */
|
|
2605
|
+
export const DISTRIBUTION_COMPILER = createRequire(join(DISTRIBUTION_ROOT, 'package.json')).resolve(
|
|
2606
|
+
'typescript/bin/tsc',
|
|
2607
|
+
)
|
|
2660
2608
|
// Windows needs a shell to launch a \`.cmd\`: Node refuses one directly since the
|
|
2661
2609
|
// batch-argument hardening, and \`spawnSync\` returns \`EINVAL\` with a null status
|
|
2662
2610
|
// rather than an exit code a caller can read. Every following argument is a literal or
|
|
2663
2611
|
// a path this file built, so the shell has nothing to escape.
|
|
2664
|
-
|
|
2612
|
+
/** Selects the Windows command-shell route required by the npm shim. */
|
|
2613
|
+
export const DISTRIBUTION_SHELL = process.platform === 'win32'
|
|
2665
2614
|
// \`prepublishOnly\` runs this proof as \`npm run test:distribution -- --mode release\`,
|
|
2666
2615
|
// and the root configuration's project factories carry that mode into this project.
|
|
2667
2616
|
// Release is the publish gate, so evidence it cannot obtain fails there and skips
|
|
2668
2617
|
// everywhere else: a gate that passes on missing evidence proves nothing.
|
|
2669
|
-
|
|
2618
|
+
/** Indicates whether missing prerequisites must fail the publish gate. */
|
|
2619
|
+
export const DISTRIBUTION_RELEASE = import.meta.env?.MODE === 'release'
|
|
2670
2620
|
// The built output directory convention a browser face may publish from. Every
|
|
2671
2621
|
// selection reads this prefix off the export target and never off the subpath name. A
|
|
2672
2622
|
// workspace whose only published face is the browser one publishes that face at the
|
|
2673
2623
|
// root subpath, so a rule keyed on the subpath name drives a browser bundle through
|
|
2674
2624
|
// Node and the miss is silent.
|
|
2675
|
-
|
|
2676
|
-
const
|
|
2625
|
+
/** Names the conventional output prefix for a browser publication. */
|
|
2626
|
+
export const DISTRIBUTION_BROWSER_OUTPUT = './dist/src/browser/'
|
|
2627
|
+
/** Names the unpublished subpath used as a resolution control. */
|
|
2628
|
+
export const DISTRIBUTION_ABSENT_SUBPATH = '/no-subpath-is-published-under-this-name'
|
|
2677
2629
|
// A compiler diagnostic that says where it is: the path relative to the directory
|
|
2678
2630
|
// the compiler ran in, the 1-based line and column, the code, and the message. The
|
|
2679
2631
|
// diagnostics are the verdict rather than the exit code, which differs between the
|
|
2680
2632
|
// compiler majors this toolchain supports, so a reported line matching nothing here
|
|
2681
2633
|
// came from something other than a check of a consumer module.
|
|
2682
|
-
|
|
2683
|
-
const
|
|
2684
|
-
|
|
2685
|
-
const
|
|
2686
|
-
|
|
2687
|
-
|
|
2634
|
+
/** Matches the location prefix of a consumer compiler diagnostic. */
|
|
2635
|
+
export const DISTRIBUTION_DIAGNOSTIC_PATTERN = /^(.+?)\\(\\d+,\\d+\\): error TS\\d+: /u
|
|
2636
|
+
/** Lists the bounded registry-readiness command arguments. */
|
|
2637
|
+
export const DISTRIBUTION_PING = Object.freeze([
|
|
2638
|
+
'ping',
|
|
2639
|
+
'--fetch-retries=0',
|
|
2640
|
+
'--fetch-timeout=5000',
|
|
2641
|
+
'--loglevel=silent',
|
|
2642
|
+
])
|
|
2643
|
+
/** Names the consumer entry that imports an installed module. */
|
|
2644
|
+
export const DISTRIBUTION_ESM_DRIVER = 'drive.mjs'
|
|
2645
|
+
/** Names the consumer entry that requires an installed module. */
|
|
2646
|
+
export const DISTRIBUTION_CJS_DRIVER = 'drive.cjs'
|
|
2647
|
+
/** Defines the isolated consumer package manifest. */
|
|
2648
|
+
export const DISTRIBUTION_CONSUMER_MANIFEST =
|
|
2649
|
+
'{ "name": "distribution-consumer", "private": true, "type": "module" }\\n'
|
|
2650
|
+
/** Reads the names exported by an installed ESM entry. */
|
|
2651
|
+
export const DISTRIBUTION_ESM_DRIVER_SOURCE = \`const entry = await import(process.argv[2])
|
|
2688
2652
|
process.stdout.write(JSON.stringify(Object.keys(entry).sort()))
|
|
2689
2653
|
\`
|
|
2690
|
-
|
|
2654
|
+
/** Reads the names exported by an installed CommonJS entry. */
|
|
2655
|
+
export const DISTRIBUTION_CJS_DRIVER_SOURCE = \`const entry = require(process.argv[2])
|
|
2691
2656
|
process.stdout.write(JSON.stringify(Object.keys(entry).sort()))
|
|
2692
2657
|
\`
|
|
2693
2658
|
|
|
2694
2659
|
// The extensions a JavaScript handler loads as modules. Node loads a native addon
|
|
2695
2660
|
// through its addon handler instead, so that extension is named separately.
|
|
2696
|
-
|
|
2697
|
-
const
|
|
2661
|
+
/** Lists extensions loaded as JavaScript modules. */
|
|
2662
|
+
export const DISTRIBUTION_MODULE_EXTENSIONS = Object.freeze(['.js', '.mjs', '.cjs'])
|
|
2663
|
+
/** Names the extension loaded by the native-addon handler. */
|
|
2664
|
+
export const DISTRIBUTION_ADDON_EXTENSION = '.node'
|
|
2698
2665
|
// The extensions a declaration file carries. A \`require\` condition declares
|
|
2699
2666
|
// \`.d.cts\` and an ESM-only one \`.d.mts\`, so the \`.d.ts\` spelling alone does not
|
|
2700
2667
|
// name them.
|
|
2701
|
-
|
|
2702
|
-
|
|
2668
|
+
/** Lists the supported TypeScript declaration extensions. */
|
|
2669
|
+
export const DISTRIBUTION_DECLARATION_EXTENSIONS = Object.freeze(['.d.ts', '.d.cts', '.d.mts'])
|
|
2670
|
+
/** Names the module format of a typed consumer. */
|
|
2671
|
+
export type DistributionFormat = 'module' | 'commonjs'
|
|
2703
2672
|
|
|
2704
2673
|
// The Node import target is resolved with the conditions that driver supplies. The
|
|
2705
2674
|
// CommonJS compile probe is selected from its declaration's format, and its runtime
|
|
2706
2675
|
// drive loads the same subpath through Node's require resolver. Vite's production
|
|
2707
2676
|
// client build enables its module and browser conditions.
|
|
2708
|
-
|
|
2677
|
+
/** Defines the conditions each runtime uses to resolve an entry. */
|
|
2678
|
+
export const DISTRIBUTION_RUNTIME_CONDITIONS = Object.freeze({
|
|
2709
2679
|
module: Object.freeze(['node-addons', 'node', 'import', 'module-sync']),
|
|
2710
2680
|
commonjs: Object.freeze(['node-addons', 'node', 'require', 'module-sync']),
|
|
2711
2681
|
browser: Object.freeze(['module', 'browser', 'production', 'import']),
|
|
@@ -2713,24 +2683,28 @@ const RUNTIME_CONDITIONS = Object.freeze({
|
|
|
2713
2683
|
// TypeScript's Node resolutions add \`node\` to the format condition. Its bundler
|
|
2714
2684
|
// resolution does not, so a browser drive compares against the declaration a bundler
|
|
2715
2685
|
// consumer reads rather than borrowing the Node declaration.
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2686
|
+
/** Defines the declaration conditions used by bundler consumers. */
|
|
2687
|
+
export const DISTRIBUTION_BUNDLER_CONDITIONS = Object.freeze({
|
|
2688
|
+
module: Object.freeze(['types', 'import']),
|
|
2689
|
+
commonjs: Object.freeze(['types', 'require']),
|
|
2719
2690
|
})
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2691
|
+
/** Defines the declaration conditions paired with each runtime. */
|
|
2692
|
+
export const DISTRIBUTION_DECLARATION_CONDITIONS = Object.freeze({
|
|
2693
|
+
module: Object.freeze(['types', 'node', 'import']),
|
|
2694
|
+
commonjs: Object.freeze(['types', 'node', 'require']),
|
|
2695
|
+
browser: DISTRIBUTION_BUNDLER_CONDITIONS.module,
|
|
2724
2696
|
})
|
|
2725
2697
|
|
|
2726
|
-
|
|
2698
|
+
/** Describes the compiler options and conditions for one consumer resolution. */
|
|
2699
|
+
export interface DistributionResolution {
|
|
2727
2700
|
readonly label: string
|
|
2728
2701
|
readonly resolution: string
|
|
2729
2702
|
readonly module: string
|
|
2730
|
-
readonly conditions: Readonly<Record<
|
|
2703
|
+
readonly conditions: Readonly<Record<DistributionFormat, readonly string[]>>
|
|
2731
2704
|
}
|
|
2732
2705
|
|
|
2733
|
-
|
|
2706
|
+
/** Holds the target selected by package condition resolution. */
|
|
2707
|
+
export interface DistributionTargetResult {
|
|
2734
2708
|
readonly target: string
|
|
2735
2709
|
}
|
|
2736
2710
|
|
|
@@ -2739,44 +2713,53 @@ interface TargetResolution {
|
|
|
2739
2713
|
// \`require\`-only subpath therefore stays in each CommonJS probe that can resolve it.
|
|
2740
2714
|
// The option values are the spellings the project file takes, so nothing here needs
|
|
2741
2715
|
// the compiler's own API to name them.
|
|
2742
|
-
|
|
2716
|
+
/** Lists the compiler resolutions driven against installed declarations. */
|
|
2717
|
+
export const DISTRIBUTION_RESOLUTIONS: readonly DistributionResolution[] = Object.freeze([
|
|
2743
2718
|
{
|
|
2744
2719
|
label: 'node16',
|
|
2745
2720
|
resolution: 'node16',
|
|
2746
2721
|
module: 'node16',
|
|
2747
|
-
conditions:
|
|
2722
|
+
conditions: DISTRIBUTION_DECLARATION_CONDITIONS,
|
|
2748
2723
|
},
|
|
2749
2724
|
{
|
|
2750
2725
|
label: 'nodenext',
|
|
2751
2726
|
resolution: 'nodenext',
|
|
2752
2727
|
module: 'nodenext',
|
|
2753
|
-
conditions:
|
|
2728
|
+
conditions: DISTRIBUTION_DECLARATION_CONDITIONS,
|
|
2754
2729
|
},
|
|
2755
2730
|
{
|
|
2756
2731
|
label: 'bundler',
|
|
2757
2732
|
resolution: 'bundler',
|
|
2758
2733
|
module: 'esnext',
|
|
2759
|
-
conditions:
|
|
2734
|
+
conditions: DISTRIBUTION_BUNDLER_CONDITIONS,
|
|
2760
2735
|
},
|
|
2761
|
-
]
|
|
2736
|
+
])
|
|
2762
2737
|
|
|
2763
2738
|
// The driver a bundled consumer reads declarations under. A browser application
|
|
2764
2739
|
// compiles through a bundler, so the browser drive answers under this one alone,
|
|
2765
2740
|
// and naming it here is what keeps that selection tied to the driver it selects.
|
|
2766
|
-
|
|
2767
|
-
|
|
2741
|
+
/** Selects the compiler resolution used by a browser consumer. */
|
|
2742
|
+
export const DISTRIBUTION_BROWSER_RESOLUTION = DISTRIBUTION_RESOLUTIONS.find(
|
|
2743
|
+
(candidate) => candidate.label === 'bundler',
|
|
2744
|
+
)
|
|
2745
|
+
if (DISTRIBUTION_BROWSER_RESOLUTION === undefined)
|
|
2746
|
+
throw new Error("DISTRIBUTION_RESOLUTIONS carries no 'bundler' row")
|
|
2768
2747
|
|
|
2769
|
-
|
|
2748
|
+
/** Pairs consumer source extensions with their module formats. */
|
|
2749
|
+
export const DISTRIBUTION_FORMATS: ReadonlyArray<
|
|
2750
|
+
readonly [extension: string, format: DistributionFormat]
|
|
2751
|
+
> = Object.freeze([
|
|
2770
2752
|
['ts', 'module'],
|
|
2771
2753
|
['cts', 'commonjs'],
|
|
2772
|
-
]
|
|
2754
|
+
])
|
|
2773
2755
|
|
|
2774
2756
|
// One published subpath, resolved to what this proof can drive: the specifier a
|
|
2775
2757
|
// consumer writes, whether the declarations its consumer formats resolve at all,
|
|
2776
2758
|
// whether the exports map answers the browser condition with a target of its own,
|
|
2777
2759
|
// whether it answers \`import\` and \`require\` at all, and whether the target that
|
|
2778
2760
|
// \`require\` answers with is one that a CommonJS consumer loads.
|
|
2779
|
-
|
|
2761
|
+
/** Describes one published subpath and its runtime and declaration capabilities. */
|
|
2762
|
+
export interface DistributionEntry {
|
|
2780
2763
|
readonly subpath: string
|
|
2781
2764
|
readonly specifier: string
|
|
2782
2765
|
readonly mapping: unknown
|
|
@@ -2794,29 +2777,48 @@ interface Entry {
|
|
|
2794
2777
|
// The installed tree every claim is read from. Every subpath the exports map names
|
|
2795
2778
|
// lands in exactly one of \`entries\`, \`undeclared\`, and \`excluded\`, so a subpath this
|
|
2796
2779
|
// proof cannot drive is reported rather than dropped.
|
|
2797
|
-
|
|
2780
|
+
/** Holds the installed consumer and the partition of its published subpaths. */
|
|
2781
|
+
export interface DistributionStage {
|
|
2798
2782
|
readonly consumer: string
|
|
2799
2783
|
readonly installed: string
|
|
2800
2784
|
readonly archives: readonly string[]
|
|
2801
|
-
readonly entries: readonly
|
|
2785
|
+
readonly entries: readonly DistributionEntry[]
|
|
2802
2786
|
readonly subpaths: readonly string[]
|
|
2803
2787
|
readonly undeclared: readonly string[]
|
|
2804
2788
|
readonly excluded: readonly string[]
|
|
2805
2789
|
readonly targets: readonly string[]
|
|
2806
2790
|
}
|
|
2807
2791
|
|
|
2808
|
-
|
|
2792
|
+
/**
|
|
2793
|
+
* Checks whether a value is a non-null object other than an array.
|
|
2794
|
+
*
|
|
2795
|
+
* @param value - The value to inspect.
|
|
2796
|
+
* @returns True if the value is a record; false otherwise.
|
|
2797
|
+
*/
|
|
2798
|
+
export function isDistributionRecord(value: unknown): value is Readonly<Record<string, unknown>> {
|
|
2809
2799
|
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
2810
2800
|
}
|
|
2811
2801
|
|
|
2812
|
-
|
|
2802
|
+
/**
|
|
2803
|
+
* Checks whether a value is an array of strings.
|
|
2804
|
+
*
|
|
2805
|
+
* @param value - The value to inspect.
|
|
2806
|
+
* @returns True if every array member is a string; false otherwise.
|
|
2807
|
+
*/
|
|
2808
|
+
export function isDistributionNames(value: unknown): value is readonly string[] {
|
|
2813
2809
|
return Array.isArray(value) && value.every((name) => typeof name === 'string')
|
|
2814
2810
|
}
|
|
2815
2811
|
|
|
2816
2812
|
// A fallback list, which is what Node reads an array in an exports entry as. The
|
|
2817
2813
|
// narrowing is what the following walkers need: \`Array.isArray\` widens an \`unknown\`
|
|
2818
2814
|
// member to \`any\`, and an entry read that way is not read at all.
|
|
2819
|
-
|
|
2815
|
+
/**
|
|
2816
|
+
* Checks whether a value is a fallback array.
|
|
2817
|
+
*
|
|
2818
|
+
* @param value - The value to inspect.
|
|
2819
|
+
* @returns True if the value is an array; false otherwise.
|
|
2820
|
+
*/
|
|
2821
|
+
export function isDistributionList(value: unknown): value is readonly unknown[] {
|
|
2820
2822
|
return Array.isArray(value)
|
|
2821
2823
|
}
|
|
2822
2824
|
|
|
@@ -2824,7 +2826,13 @@ function isList(value: unknown): value is readonly unknown[] {
|
|
|
2824
2826
|
// package and a target containing a dot, parent, or node_modules segment during
|
|
2825
2827
|
// package-target resolution. A later module-resolution failure is not the same
|
|
2826
2828
|
// thing: an array falls through the former and keeps the latter.
|
|
2827
|
-
|
|
2829
|
+
/**
|
|
2830
|
+
* Checks whether a relative package target contains only admitted segments.
|
|
2831
|
+
*
|
|
2832
|
+
* @param target - The package-relative target.
|
|
2833
|
+
* @returns True if the target passes package-target validation; false otherwise.
|
|
2834
|
+
*/
|
|
2835
|
+
export function matchesDistributionPackageTarget(target: string): boolean {
|
|
2828
2836
|
if (!target.startsWith('./')) return false
|
|
2829
2837
|
const refused = ['.', '..', 'node_modules']
|
|
2830
2838
|
return target
|
|
@@ -2839,59 +2847,113 @@ function isPackageTarget(target: string): boolean {
|
|
|
2839
2847
|
})
|
|
2840
2848
|
}
|
|
2841
2849
|
|
|
2842
|
-
|
|
2850
|
+
/**
|
|
2851
|
+
* Reads and parses a JSON file.
|
|
2852
|
+
*
|
|
2853
|
+
* @param path - The file to read.
|
|
2854
|
+
* @returns The parsed value. Throws when reading or parsing fails.
|
|
2855
|
+
*/
|
|
2856
|
+
export function readDistributionJSON(path: string): unknown {
|
|
2843
2857
|
const parsed: unknown = JSON.parse(readFileSync(path, 'utf8'))
|
|
2844
2858
|
return parsed
|
|
2845
2859
|
}
|
|
2846
2860
|
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2861
|
+
/**
|
|
2862
|
+
* Reads the package name from a manifest.
|
|
2863
|
+
*
|
|
2864
|
+
* @param path - The manifest to read.
|
|
2865
|
+
* @returns The package name. Throws when the manifest has no string name.
|
|
2866
|
+
*/
|
|
2867
|
+
export function readDistributionManifestName(path: string): string {
|
|
2868
|
+
const manifest = readDistributionJSON(path)
|
|
2869
|
+
if (!isDistributionRecord(manifest) || typeof manifest.name !== 'string') {
|
|
2850
2870
|
throw new Error(\`The manifest at \${path} declares no package name\`)
|
|
2851
2871
|
}
|
|
2852
2872
|
return manifest.name
|
|
2853
2873
|
}
|
|
2854
2874
|
|
|
2855
|
-
|
|
2875
|
+
/**
|
|
2876
|
+
* Writes a consumer file after creating its parent directory.
|
|
2877
|
+
*
|
|
2878
|
+
* @param path - The file to write.
|
|
2879
|
+
* @param content - The text to write.
|
|
2880
|
+
*/
|
|
2881
|
+
export function writeDistributionFile(path: string, content: string): void {
|
|
2856
2882
|
mkdirSync(dirname(path), { recursive: true })
|
|
2857
2883
|
writeFileSync(path, content)
|
|
2858
2884
|
}
|
|
2859
2885
|
|
|
2860
|
-
|
|
2886
|
+
/**
|
|
2887
|
+
* Combines the output streams of a completed command.
|
|
2888
|
+
*
|
|
2889
|
+
* @param result - The completed command.
|
|
2890
|
+
* @returns The trimmed diagnostic text.
|
|
2891
|
+
*/
|
|
2892
|
+
export function readDistributionOutput(result: SpawnSyncReturns<string>): string {
|
|
2861
2893
|
return \`\${result.stdout ?? ''}\${result.stderr ?? ''}\`.trim()
|
|
2862
2894
|
}
|
|
2863
2895
|
|
|
2864
|
-
|
|
2865
|
-
|
|
2896
|
+
/**
|
|
2897
|
+
* Runs npm with a cache inside the owned scratch directory.
|
|
2898
|
+
*
|
|
2899
|
+
* @param args - The npm arguments.
|
|
2900
|
+
* @param cwd - The working directory.
|
|
2901
|
+
* @param scratch - The owned scratch root.
|
|
2902
|
+
* @returns The command status and captured streams.
|
|
2903
|
+
*/
|
|
2904
|
+
export function runDistributionNpm(
|
|
2905
|
+
args: readonly string[],
|
|
2906
|
+
cwd: string,
|
|
2907
|
+
scratch: string,
|
|
2908
|
+
): SpawnSyncReturns<string> {
|
|
2909
|
+
return spawnSync(DISTRIBUTION_NPM, [...args], {
|
|
2866
2910
|
cwd,
|
|
2867
2911
|
encoding: 'utf8',
|
|
2868
2912
|
env: { ...process.env, npm_config_cache: join(scratch, 'cache') },
|
|
2869
|
-
shell:
|
|
2913
|
+
shell: DISTRIBUTION_SHELL,
|
|
2870
2914
|
windowsHide: true,
|
|
2871
2915
|
})
|
|
2872
2916
|
}
|
|
2873
2917
|
|
|
2874
|
-
|
|
2918
|
+
/**
|
|
2919
|
+
* Runs a JavaScript entry with the running Node executable.
|
|
2920
|
+
*
|
|
2921
|
+
* @param args - The Node arguments.
|
|
2922
|
+
* @param cwd - The working directory.
|
|
2923
|
+
* @returns The command status and captured streams.
|
|
2924
|
+
*/
|
|
2925
|
+
export function runDistributionNode(
|
|
2926
|
+
args: readonly string[],
|
|
2927
|
+
cwd: string,
|
|
2928
|
+
): SpawnSyncReturns<string> {
|
|
2875
2929
|
return spawnSync(process.execPath, [...args], { cwd, encoding: 'utf8', windowsHide: true })
|
|
2876
2930
|
}
|
|
2877
2931
|
|
|
2878
2932
|
// Node's own condition matching, read in declaration order.
|
|
2879
|
-
|
|
2933
|
+
/**
|
|
2934
|
+
* Resolves a package target through conditions and fallback arrays.
|
|
2935
|
+
*
|
|
2936
|
+
* @param entry - The exports-map entry.
|
|
2937
|
+
* @param conditions - The active resolution conditions.
|
|
2938
|
+
* @returns The selected target, or undefined when no branch resolves.
|
|
2939
|
+
*/
|
|
2940
|
+
export function resolveDistributionPackageTarget(
|
|
2880
2941
|
entry: unknown,
|
|
2881
2942
|
conditions: readonly string[],
|
|
2882
|
-
):
|
|
2943
|
+
): DistributionTargetResult | undefined {
|
|
2883
2944
|
if (typeof entry === 'string') return { target: entry }
|
|
2884
|
-
if (
|
|
2945
|
+
if (isDistributionList(entry)) {
|
|
2885
2946
|
for (const member of entry) {
|
|
2886
|
-
const resolved =
|
|
2887
|
-
if (resolved !== undefined &&
|
|
2947
|
+
const resolved = resolveDistributionPackageTarget(member, conditions)
|
|
2948
|
+
if (resolved !== undefined && matchesDistributionPackageTarget(resolved.target))
|
|
2949
|
+
return resolved
|
|
2888
2950
|
}
|
|
2889
2951
|
return undefined
|
|
2890
2952
|
}
|
|
2891
|
-
if (!
|
|
2953
|
+
if (!isDistributionRecord(entry)) return undefined
|
|
2892
2954
|
for (const [condition, nested] of Object.entries(entry)) {
|
|
2893
2955
|
if (condition !== 'default' && !conditions.includes(condition)) continue
|
|
2894
|
-
const resolved =
|
|
2956
|
+
const resolved = resolveDistributionPackageTarget(nested, conditions)
|
|
2895
2957
|
if (resolved !== undefined) return resolved
|
|
2896
2958
|
}
|
|
2897
2959
|
return undefined
|
|
@@ -2901,13 +2963,29 @@ function resolvePackageTarget(
|
|
|
2901
2963
|
// one walker. An entry may declare \`types\` beside \`default\` at its top level
|
|
2902
2964
|
// rather than inside \`import\`, so a fixed \`entry.import.types\` lookup is not
|
|
2903
2965
|
// equivalent to condition resolution.
|
|
2904
|
-
|
|
2905
|
-
|
|
2966
|
+
/**
|
|
2967
|
+
* Resolves the path selected by an exports-map entry.
|
|
2968
|
+
*
|
|
2969
|
+
* @param entry - The exports-map entry.
|
|
2970
|
+
* @param conditions - The active resolution conditions.
|
|
2971
|
+
* @returns The selected path, or undefined when no branch resolves.
|
|
2972
|
+
*/
|
|
2973
|
+
export function resolveDistributionTarget(
|
|
2974
|
+
entry: unknown,
|
|
2975
|
+
conditions: readonly string[],
|
|
2976
|
+
): string | undefined {
|
|
2977
|
+
return resolveDistributionPackageTarget(entry, conditions)?.target
|
|
2906
2978
|
}
|
|
2907
2979
|
|
|
2908
2980
|
// Whether a path is a physical file. TypeScript's file-existence check refuses a
|
|
2909
2981
|
// directory at the same spelling and continues to the outer package scope.
|
|
2910
|
-
|
|
2982
|
+
/**
|
|
2983
|
+
* Checks whether a path names a physical file.
|
|
2984
|
+
*
|
|
2985
|
+
* @param path - The filesystem path.
|
|
2986
|
+
* @returns True if the path names a file; false otherwise.
|
|
2987
|
+
*/
|
|
2988
|
+
export function matchesDistributionFile(path: string): boolean {
|
|
2911
2989
|
try {
|
|
2912
2990
|
return statSync(path).isFile()
|
|
2913
2991
|
} catch {
|
|
@@ -2918,8 +2996,18 @@ function matchesFile(path: string): boolean {
|
|
|
2918
2996
|
// TypeScript resolves a declaration target by accepting an existing declaration
|
|
2919
2997
|
// directly or by substituting beside a JavaScript target. A missing target leaves
|
|
2920
2998
|
// the containing condition or fallback list unresolved, so the walk continues.
|
|
2921
|
-
|
|
2922
|
-
|
|
2999
|
+
/**
|
|
3000
|
+
* Resolves an existing declaration beside a package target.
|
|
3001
|
+
*
|
|
3002
|
+
* @param target - The package-relative target.
|
|
3003
|
+
* @param installed - The installed package root.
|
|
3004
|
+
* @returns The declaration target, or undefined when no declaration exists.
|
|
3005
|
+
*/
|
|
3006
|
+
export function targetToDistributionDeclaration(
|
|
3007
|
+
target: string,
|
|
3008
|
+
installed: string,
|
|
3009
|
+
): string | undefined {
|
|
3010
|
+
if (!matchesDistributionPackageTarget(target)) return undefined
|
|
2923
3011
|
const declaration = target.endsWith('.cjs')
|
|
2924
3012
|
? \`\${target.slice(0, -4)}.d.cts\`
|
|
2925
3013
|
: target.endsWith('.mjs')
|
|
@@ -2927,29 +3015,37 @@ function targetToDeclaration(target: string, installed: string): string | undefi
|
|
|
2927
3015
|
: target.endsWith('.js')
|
|
2928
3016
|
? \`\${target.slice(0, -3)}.d.ts\`
|
|
2929
3017
|
: target
|
|
2930
|
-
if (declaration === target && !
|
|
2931
|
-
return
|
|
3018
|
+
if (declaration === target && !matchesDistributionDeclaration(target)) return undefined
|
|
3019
|
+
return matchesDistributionFile(join(installed, declaration)) ? declaration : undefined
|
|
2932
3020
|
}
|
|
2933
3021
|
|
|
2934
3022
|
// The declaration TypeScript resolves through one importing format's conditions.
|
|
2935
3023
|
// Condition objects keep manifest order, and arrays keep fallback order.
|
|
2936
|
-
|
|
3024
|
+
/**
|
|
3025
|
+
* Resolves an existing declaration through consumer conditions.
|
|
3026
|
+
*
|
|
3027
|
+
* @param entry - The exports-map entry.
|
|
3028
|
+
* @param conditions - The active declaration conditions.
|
|
3029
|
+
* @param installed - The installed package root.
|
|
3030
|
+
* @returns The declaration target, or undefined when no branch resolves.
|
|
3031
|
+
*/
|
|
3032
|
+
export function resolveDistributionDeclaration(
|
|
2937
3033
|
entry: unknown,
|
|
2938
3034
|
conditions: readonly string[],
|
|
2939
3035
|
installed: string,
|
|
2940
3036
|
): string | undefined {
|
|
2941
|
-
if (typeof entry === 'string') return
|
|
2942
|
-
if (
|
|
3037
|
+
if (typeof entry === 'string') return targetToDistributionDeclaration(entry, installed)
|
|
3038
|
+
if (isDistributionList(entry)) {
|
|
2943
3039
|
for (const member of entry) {
|
|
2944
|
-
const resolved =
|
|
3040
|
+
const resolved = resolveDistributionDeclaration(member, conditions, installed)
|
|
2945
3041
|
if (resolved !== undefined) return resolved
|
|
2946
3042
|
}
|
|
2947
3043
|
return undefined
|
|
2948
3044
|
}
|
|
2949
|
-
if (!
|
|
3045
|
+
if (!isDistributionRecord(entry)) return undefined
|
|
2950
3046
|
for (const [condition, nested] of Object.entries(entry)) {
|
|
2951
3047
|
if (condition !== 'default' && !conditions.includes(condition)) continue
|
|
2952
|
-
const resolved =
|
|
3048
|
+
const resolved = resolveDistributionDeclaration(nested, conditions, installed)
|
|
2953
3049
|
if (resolved !== undefined) return resolved
|
|
2954
3050
|
}
|
|
2955
3051
|
return undefined
|
|
@@ -2958,7 +3054,14 @@ function resolveDeclaration(
|
|
|
2958
3054
|
// The nearest package scope that decides a \`.d.ts\` declaration's module format. A
|
|
2959
3055
|
// physical nested manifest starts a scope even when it omits \`type\` or cannot be
|
|
2960
3056
|
// parsed. A directory at that spelling is not a manifest, so the walk continues.
|
|
2961
|
-
|
|
3057
|
+
/**
|
|
3058
|
+
* Reads the nearest package scope for a target inside an installed package.
|
|
3059
|
+
*
|
|
3060
|
+
* @param installed - The installed package root.
|
|
3061
|
+
* @param target - The package-relative target.
|
|
3062
|
+
* @returns The manifest type, or undefined when the nearest scope omits it or cannot be read.
|
|
3063
|
+
*/
|
|
3064
|
+
export function readDistributionPackageType(installed: string, target: string): unknown {
|
|
2962
3065
|
// One \`dirname\` per path segment reaches the root from any path. The walk ends at the
|
|
2963
3066
|
// installed package, so no scope above it is consulted.
|
|
2964
3067
|
const start = dirname(join(installed, target))
|
|
@@ -2968,57 +3071,91 @@ function readPackageType(installed: string, target: string): unknown {
|
|
|
2968
3071
|
const end = chain.indexOf(installed)
|
|
2969
3072
|
const scope = chain
|
|
2970
3073
|
.slice(0, end === -1 ? chain.length : end + 1)
|
|
2971
|
-
.find((directory) =>
|
|
3074
|
+
.find((directory) => matchesDistributionFile(join(directory, 'package.json')))
|
|
2972
3075
|
if (scope === undefined) return undefined
|
|
2973
3076
|
try {
|
|
2974
|
-
const manifest =
|
|
2975
|
-
return
|
|
3077
|
+
const manifest = readDistributionJSON(join(scope, 'package.json'))
|
|
3078
|
+
return isDistributionRecord(manifest) ? manifest.type : undefined
|
|
2976
3079
|
} catch {
|
|
2977
3080
|
return undefined
|
|
2978
3081
|
}
|
|
2979
3082
|
}
|
|
2980
3083
|
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
3084
|
+
/**
|
|
3085
|
+
* Checks whether an entry publishes a distinct or conventional browser target.
|
|
3086
|
+
*
|
|
3087
|
+
* @param entry - The exports-map entry.
|
|
3088
|
+
* @returns True if the entry requires a browser drive; false otherwise.
|
|
3089
|
+
*/
|
|
3090
|
+
export function resolvesDistributionBrowser(entry: unknown): boolean {
|
|
3091
|
+
const module = resolveDistributionTarget(entry, DISTRIBUTION_RUNTIME_CONDITIONS.browser)
|
|
3092
|
+
if (module !== undefined && module.startsWith(DISTRIBUTION_BROWSER_OUTPUT)) return true
|
|
2984
3093
|
if (module === undefined) return false
|
|
2985
|
-
const imported =
|
|
2986
|
-
const required =
|
|
3094
|
+
const imported = resolveDistributionTarget(entry, DISTRIBUTION_RUNTIME_CONDITIONS.module)
|
|
3095
|
+
const required = resolveDistributionTarget(entry, DISTRIBUTION_RUNTIME_CONDITIONS.commonjs)
|
|
2987
3096
|
return module !== imported && module !== required
|
|
2988
3097
|
}
|
|
2989
3098
|
|
|
2990
3099
|
// Whether the target selected by Node's CommonJS conditions is a module require can
|
|
2991
3100
|
// load. A JavaScript target takes its own nearest package scope. Native addons and
|
|
2992
3101
|
// extensionless targets have their own CommonJS handlers.
|
|
2993
|
-
|
|
2994
|
-
|
|
3102
|
+
/**
|
|
3103
|
+
* Checks whether Node require can load an entry selected by its conditions.
|
|
3104
|
+
*
|
|
3105
|
+
* @param entry - The exports-map entry.
|
|
3106
|
+
* @param installed - The installed package root.
|
|
3107
|
+
* @returns True if the target has a CommonJS-compatible format; false otherwise.
|
|
3108
|
+
*/
|
|
3109
|
+
export function resolvesDistributionCommonJS(entry: unknown, installed: string): boolean {
|
|
3110
|
+
const target = resolveDistributionTarget(entry, DISTRIBUTION_RUNTIME_CONDITIONS.commonjs)
|
|
2995
3111
|
if (target === undefined) return false
|
|
2996
3112
|
const name = target.slice(target.lastIndexOf('/') + 1)
|
|
2997
3113
|
if (name.endsWith('.cjs')) return true
|
|
2998
3114
|
if (name.endsWith('.mjs')) return false
|
|
2999
3115
|
if (name.endsWith('.node')) return true
|
|
3000
3116
|
if (!name.includes('.')) return true
|
|
3001
|
-
return name.endsWith('.js') &&
|
|
3117
|
+
return name.endsWith('.js') && readDistributionPackageType(installed, target) !== 'module'
|
|
3002
3118
|
}
|
|
3003
3119
|
|
|
3004
3120
|
// Whether the declaration selected by a typed CommonJS consumer admits that entry.
|
|
3005
3121
|
// A \`.d.cts\` declaration admits and a \`.d.mts\` declaration refuses. A \`.d.ts\`
|
|
3006
3122
|
// declaration takes its own nearest package scope.
|
|
3007
|
-
|
|
3008
|
-
|
|
3123
|
+
/**
|
|
3124
|
+
* Checks whether an entry resolves a CommonJS-compatible declaration.
|
|
3125
|
+
*
|
|
3126
|
+
* @param entry - The exports-map entry.
|
|
3127
|
+
* @param installed - The installed package root.
|
|
3128
|
+
* @returns True if the selected declaration admits CommonJS consumption; false otherwise.
|
|
3129
|
+
*/
|
|
3130
|
+
export function declaresDistributionCommonJS(entry: unknown, installed: string): boolean {
|
|
3131
|
+
const declaration = resolveDistributionDeclaration(
|
|
3132
|
+
entry,
|
|
3133
|
+
DISTRIBUTION_DECLARATION_CONDITIONS.commonjs,
|
|
3134
|
+
installed,
|
|
3135
|
+
)
|
|
3009
3136
|
if (declaration === undefined) return false
|
|
3010
3137
|
if (declaration.endsWith('.d.cts')) return true
|
|
3011
3138
|
if (declaration.endsWith('.d.mts')) return false
|
|
3012
|
-
return
|
|
3139
|
+
return (
|
|
3140
|
+
declaration.endsWith('.d.ts') &&
|
|
3141
|
+
readDistributionPackageType(installed, declaration) !== 'module'
|
|
3142
|
+
)
|
|
3013
3143
|
}
|
|
3014
3144
|
|
|
3015
3145
|
// Every target an entry names under any condition. A fallback list omits members
|
|
3016
3146
|
// Node rejects during package-target validation, because no reader can take them.
|
|
3017
|
-
|
|
3147
|
+
/**
|
|
3148
|
+
* Collects the targets an entry publishes under every condition.
|
|
3149
|
+
*
|
|
3150
|
+
* @param entry - The exports-map entry.
|
|
3151
|
+
* @returns The targets, excluding invalid members of fallback arrays.
|
|
3152
|
+
*/
|
|
3153
|
+
export function collectDistributionTargets(entry: unknown): readonly string[] {
|
|
3018
3154
|
if (typeof entry === 'string') return [entry]
|
|
3019
|
-
if (
|
|
3020
|
-
|
|
3021
|
-
|
|
3155
|
+
if (isDistributionList(entry))
|
|
3156
|
+
return entry.flatMap(collectDistributionTargets).filter(matchesDistributionPackageTarget)
|
|
3157
|
+
if (!isDistributionRecord(entry)) return []
|
|
3158
|
+
return Object.values(entry).flatMap((nested) => collectDistributionTargets(nested))
|
|
3022
3159
|
}
|
|
3023
3160
|
|
|
3024
3161
|
// Whether a target is a file a runtime loads for its names, which is what a
|
|
@@ -3030,24 +3167,43 @@ function collectTargets(entry: unknown): readonly string[] {
|
|
|
3030
3167
|
// \`"./package.json"\` manifest pointer, and a declaration alike.
|
|
3031
3168
|
// The cost is an extensionless file published for a reader, such as a \`LICENSE\`:
|
|
3032
3169
|
// that target reports undeclared until it is given an extension or a declaration.
|
|
3033
|
-
|
|
3170
|
+
/**
|
|
3171
|
+
* Checks whether a target names a runtime module rather than an asset.
|
|
3172
|
+
*
|
|
3173
|
+
* @param target - The package-relative target.
|
|
3174
|
+
* @returns True if the target has a module extension or no extension; false otherwise.
|
|
3175
|
+
*/
|
|
3176
|
+
export function matchesDistributionModule(target: string): boolean {
|
|
3034
3177
|
const name = target.slice(target.lastIndexOf('/') + 1)
|
|
3035
3178
|
const dot = name.lastIndexOf('.')
|
|
3036
|
-
if (name.endsWith(
|
|
3037
|
-
return dot === -1 ||
|
|
3179
|
+
if (name.endsWith(DISTRIBUTION_ADDON_EXTENSION)) return true
|
|
3180
|
+
return dot === -1 || DISTRIBUTION_MODULE_EXTENSIONS.includes(name.slice(dot))
|
|
3038
3181
|
}
|
|
3039
3182
|
|
|
3040
3183
|
// Whether a resolved target is a declaration rather than the JavaScript a
|
|
3041
3184
|
// \`default\` branch answers with when the entry declares no \`types\` condition.
|
|
3042
|
-
|
|
3043
|
-
|
|
3185
|
+
/**
|
|
3186
|
+
* Checks whether a target names a TypeScript declaration.
|
|
3187
|
+
*
|
|
3188
|
+
* @param target - The package-relative target.
|
|
3189
|
+
* @returns True if the target has a declaration extension; false otherwise.
|
|
3190
|
+
*/
|
|
3191
|
+
export function matchesDistributionDeclaration(target: string): boolean {
|
|
3192
|
+
return DISTRIBUTION_DECLARATION_EXTENSIONS.some((extension) => target.endsWith(extension))
|
|
3044
3193
|
}
|
|
3045
3194
|
|
|
3046
3195
|
// The declarations the Node module, Node CommonJS, and browser drives compare
|
|
3047
3196
|
// against. Each field uses the conditions of the TypeScript consumer paired with
|
|
3048
3197
|
// that runtime. A JavaScript target resolves through TypeScript's adjacent
|
|
3049
3198
|
// declaration substitution rather than standing in for the declaration itself.
|
|
3050
|
-
|
|
3199
|
+
/**
|
|
3200
|
+
* Reads the declarations paired with Node and browser consumers.
|
|
3201
|
+
*
|
|
3202
|
+
* @param entry - The exports-map entry.
|
|
3203
|
+
* @param installed - The installed package root.
|
|
3204
|
+
* @returns The declaration selected by each consumer format.
|
|
3205
|
+
*/
|
|
3206
|
+
export function readDistributionDeclaration(
|
|
3051
3207
|
entry: unknown,
|
|
3052
3208
|
installed: string,
|
|
3053
3209
|
): {
|
|
@@ -3056,17 +3212,39 @@ function readDeclaration(
|
|
|
3056
3212
|
readonly browser: string | undefined
|
|
3057
3213
|
} {
|
|
3058
3214
|
return {
|
|
3059
|
-
module:
|
|
3060
|
-
|
|
3061
|
-
|
|
3215
|
+
module: resolveDistributionDeclaration(
|
|
3216
|
+
entry,
|
|
3217
|
+
DISTRIBUTION_DECLARATION_CONDITIONS.module,
|
|
3218
|
+
installed,
|
|
3219
|
+
),
|
|
3220
|
+
commonjs: resolveDistributionDeclaration(
|
|
3221
|
+
entry,
|
|
3222
|
+
DISTRIBUTION_DECLARATION_CONDITIONS.commonjs,
|
|
3223
|
+
installed,
|
|
3224
|
+
),
|
|
3225
|
+
browser: resolveDistributionDeclaration(
|
|
3226
|
+
entry,
|
|
3227
|
+
DISTRIBUTION_DECLARATION_CONDITIONS.browser,
|
|
3228
|
+
installed,
|
|
3229
|
+
),
|
|
3062
3230
|
}
|
|
3063
3231
|
}
|
|
3064
3232
|
|
|
3065
3233
|
// The entries one compile driver can resolve under its own conditions.
|
|
3066
|
-
|
|
3234
|
+
/**
|
|
3235
|
+
* Filters entries to those a consumer can resolve.
|
|
3236
|
+
*
|
|
3237
|
+
* @param entries - The installed entries.
|
|
3238
|
+
* @param conditions - The active consumer conditions.
|
|
3239
|
+
* @returns The matching entries in their original order.
|
|
3240
|
+
*/
|
|
3241
|
+
export function filterDistributionEntries(
|
|
3242
|
+
entries: readonly DistributionEntry[],
|
|
3243
|
+
conditions: readonly string[],
|
|
3244
|
+
): readonly DistributionEntry[] {
|
|
3067
3245
|
return entries.filter(
|
|
3068
3246
|
(entry) =>
|
|
3069
|
-
|
|
3247
|
+
resolveDistributionTarget(entry.mapping, conditions) !== undefined &&
|
|
3070
3248
|
(!conditions.includes('require') || entry.loadable),
|
|
3071
3249
|
)
|
|
3072
3250
|
}
|
|
@@ -3074,22 +3252,42 @@ function selectEntries(entries: readonly Entry[], conditions: readonly string[])
|
|
|
3074
3252
|
// The compile drivers whose conditions reach one entry under one importing format.
|
|
3075
3253
|
// A driver that resolves no target for that entry compiles nothing, so a consumer
|
|
3076
3254
|
// written under it would report a resolution failure this package never made.
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3255
|
+
/**
|
|
3256
|
+
* Filters compiler resolutions to those that can read an entry.
|
|
3257
|
+
*
|
|
3258
|
+
* @param entry - The installed entry.
|
|
3259
|
+
* @param format - The consumer module format.
|
|
3260
|
+
* @returns The matching compiler resolutions.
|
|
3261
|
+
*/
|
|
3262
|
+
export function filterDistributionResolutions(
|
|
3263
|
+
entry: DistributionEntry,
|
|
3264
|
+
format: DistributionFormat,
|
|
3265
|
+
): readonly DistributionResolution[] {
|
|
3266
|
+
return DISTRIBUTION_RESOLUTIONS.filter(
|
|
3267
|
+
(driver) => filterDistributionEntries([entry], driver.conditions[format]).length > 0,
|
|
3080
3268
|
)
|
|
3081
3269
|
}
|
|
3082
3270
|
|
|
3083
3271
|
// Requirable entries that declare CommonJS support but a typed CommonJS consumer
|
|
3084
3272
|
// cannot compile against. A default branch resolving under the require condition
|
|
3085
3273
|
// set makes no CommonJS claim.
|
|
3086
|
-
|
|
3274
|
+
/**
|
|
3275
|
+
* Filters entries whose require target lacks a compatible declaration.
|
|
3276
|
+
*
|
|
3277
|
+
* @param entries - The installed entries.
|
|
3278
|
+
* @param installed - The installed package root.
|
|
3279
|
+
* @returns The entries a CommonJS consumer cannot typecheck.
|
|
3280
|
+
*/
|
|
3281
|
+
export function filterDistributionUntypable(
|
|
3282
|
+
entries: readonly DistributionEntry[],
|
|
3283
|
+
installed: string,
|
|
3284
|
+
): readonly DistributionEntry[] {
|
|
3087
3285
|
return entries.filter(
|
|
3088
3286
|
(entry) =>
|
|
3089
3287
|
entry.requirable &&
|
|
3090
|
-
|
|
3288
|
+
isDistributionRecord(entry.mapping) &&
|
|
3091
3289
|
Object.hasOwn(entry.mapping, 'require') &&
|
|
3092
|
-
!
|
|
3290
|
+
!declaresDistributionCommonJS(entry.mapping, installed),
|
|
3093
3291
|
)
|
|
3094
3292
|
}
|
|
3095
3293
|
|
|
@@ -3097,20 +3295,30 @@ function selectUntypable(entries: readonly Entry[], installed: string): readonly
|
|
|
3097
3295
|
// installed entry that consumer imports, the file extension fixing its importing
|
|
3098
3296
|
// format, the names a real runtime published off it, and the driver whose scratch
|
|
3099
3297
|
// project compiles it.
|
|
3100
|
-
|
|
3101
|
-
|
|
3298
|
+
/** Describes the runtime names and declaration resolution compared for one entry. */
|
|
3299
|
+
export interface DistributionSurface {
|
|
3300
|
+
readonly entry: DistributionEntry
|
|
3102
3301
|
readonly extension: string
|
|
3103
3302
|
readonly published: readonly string[]
|
|
3104
|
-
readonly driver:
|
|
3303
|
+
readonly driver: DistributionResolution
|
|
3105
3304
|
}
|
|
3106
3305
|
|
|
3107
3306
|
// A scratch project over named consumer modules, written beside them so their own
|
|
3108
3307
|
// resolution reaches the installed package. Nothing is emitted and no ambient types
|
|
3109
3308
|
// are pulled in, so what the check reads is the installed declarations alone.
|
|
3110
|
-
|
|
3111
|
-
|
|
3309
|
+
/**
|
|
3310
|
+
* Writes a compiler project for a consumer probe.
|
|
3311
|
+
*
|
|
3312
|
+
* @param stage - The installed consumer.
|
|
3313
|
+
* @param name - The project filename suffix.
|
|
3314
|
+
* @param driver - The compiler resolution.
|
|
3315
|
+
* @param files - The consumer source files.
|
|
3316
|
+
* @returns The absolute project path.
|
|
3317
|
+
*/
|
|
3318
|
+
export function writeDistributionProject(
|
|
3319
|
+
stage: DistributionStage,
|
|
3112
3320
|
name: string,
|
|
3113
|
-
driver:
|
|
3321
|
+
driver: DistributionResolution,
|
|
3114
3322
|
files: readonly string[],
|
|
3115
3323
|
): string {
|
|
3116
3324
|
const path = join(stage.consumer, \`tsconfig.\${name}.json\`)
|
|
@@ -3126,7 +3334,7 @@ function writeProject(
|
|
|
3126
3334
|
},
|
|
3127
3335
|
files: [...files],
|
|
3128
3336
|
}
|
|
3129
|
-
|
|
3337
|
+
writeDistributionFile(path, \`\${JSON.stringify(project, undefined, '\\t')}\\n\`)
|
|
3130
3338
|
return path
|
|
3131
3339
|
}
|
|
3132
3340
|
|
|
@@ -3137,8 +3345,21 @@ function writeProject(
|
|
|
3137
3345
|
// naming the scratch project rather than a consumer module, and anything at all on
|
|
3138
3346
|
// the error stream are faults of this proof rather than of the package under proof,
|
|
3139
3347
|
// so each is raised where it happens instead of counted against the package.
|
|
3140
|
-
|
|
3141
|
-
|
|
3348
|
+
/**
|
|
3349
|
+
* Checks a consumer project with the installed TypeScript compiler.
|
|
3350
|
+
*
|
|
3351
|
+
* @param stage - The installed consumer.
|
|
3352
|
+
* @param project - The compiler project path.
|
|
3353
|
+
* @returns The located compiler diagnostics. Throws on a tool or project failure.
|
|
3354
|
+
*/
|
|
3355
|
+
export function checkDistributionProject(
|
|
3356
|
+
stage: DistributionStage,
|
|
3357
|
+
project: string,
|
|
3358
|
+
): readonly string[] {
|
|
3359
|
+
const result = runDistributionNode(
|
|
3360
|
+
[DISTRIBUTION_COMPILER, '--noEmit', '--pretty', 'false', '-p', project],
|
|
3361
|
+
stage.consumer,
|
|
3362
|
+
)
|
|
3142
3363
|
const refused = \`\${result.stderr ?? ''}\`.trim()
|
|
3143
3364
|
if (refused.length > 0) {
|
|
3144
3365
|
throw new Error(\`The consumer compiler wrote \${refused} to its error stream\`)
|
|
@@ -3150,7 +3371,7 @@ function checkProject(stage: Stage, project: string): readonly string[] {
|
|
|
3150
3371
|
const heads = lines.flatMap((line, index) => (index === 0 || !/^\\s/u.test(line) ? [index] : []))
|
|
3151
3372
|
const reported = heads.map((head, position) => {
|
|
3152
3373
|
const line = lines[head] ?? ''
|
|
3153
|
-
const located =
|
|
3374
|
+
const located = DISTRIBUTION_DIAGNOSTIC_PATTERN.exec(line)?.[1]
|
|
3154
3375
|
if (located === undefined) {
|
|
3155
3376
|
throw new Error(\`The consumer compiler reported \${line}, which names no location\`)
|
|
3156
3377
|
}
|
|
@@ -3163,7 +3384,7 @@ function checkProject(stage: Stage, project: string): readonly string[] {
|
|
|
3163
3384
|
].join(' ')
|
|
3164
3385
|
})
|
|
3165
3386
|
if (reported.length === 0 && result.status !== 0) {
|
|
3166
|
-
throw new Error(\`The consumer compiler refused the project: \${
|
|
3387
|
+
throw new Error(\`The consumer compiler refused the project: \${readDistributionOutput(result)}\`)
|
|
3167
3388
|
}
|
|
3168
3389
|
return reported
|
|
3169
3390
|
}
|
|
@@ -3178,26 +3399,47 @@ function checkProject(stage: Stage, project: string): readonly string[] {
|
|
|
3178
3399
|
// as a type alone, land on \`surfaced\`: a variable widens into the type
|
|
3179
3400
|
// \`declared\` annotates, and only \`surfaced\` reads the literal's own keys back.
|
|
3180
3401
|
// Each names the member it is about, so the failure says which export moved.
|
|
3181
|
-
|
|
3402
|
+
/**
|
|
3403
|
+
* Checks runtime export names against their installed declarations.
|
|
3404
|
+
*
|
|
3405
|
+
* @param stage - The installed consumer.
|
|
3406
|
+
* @param surface - The publication and declaration pair.
|
|
3407
|
+
* @returns The diagnostics labelled by compiler resolution.
|
|
3408
|
+
*/
|
|
3409
|
+
export function checkDistributionSurface(
|
|
3410
|
+
stage: DistributionStage,
|
|
3411
|
+
surface: DistributionSurface,
|
|
3412
|
+
): readonly string[] {
|
|
3182
3413
|
const slug = surface.entry.subpath.replaceAll(/[^\\w]+/gu, '-')
|
|
3183
3414
|
const name = \`surface.\${surface.driver.label}\${slug}.\${surface.extension}\`
|
|
3184
3415
|
const module = \`\${name}\`
|
|
3185
3416
|
const keys = surface.published.map((key) => \`\${JSON.stringify(key)}: true\`).join(', ')
|
|
3186
|
-
|
|
3417
|
+
writeDistributionFile(
|
|
3187
3418
|
join(stage.consumer, module),
|
|
3188
3419
|
\`import * as entry from \${JSON.stringify(surface.entry.specifier)}
|
|
3189
|
-
const published = {\${keys.length === 0 ? '' : \` \${keys} \`}} as const
|
|
3190
|
-
const declared: Record<keyof typeof entry, true> = published
|
|
3191
|
-
const surfaced: Record<keyof typeof published, true> = declared
|
|
3420
|
+
export const published = {\${keys.length === 0 ? '' : \` \${keys} \`}} as const
|
|
3421
|
+
export const declared: Record<keyof typeof entry, true> = published
|
|
3422
|
+
export const surfaced: Record<keyof typeof published, true> = declared
|
|
3192
3423
|
\`,
|
|
3193
3424
|
)
|
|
3194
|
-
const project =
|
|
3195
|
-
return
|
|
3425
|
+
const project = writeDistributionProject(stage, name, surface.driver, [\`./\${module}\`])
|
|
3426
|
+
return checkDistributionProject(stage, project).map((line) => \`\${surface.driver.label}: \${line}\`)
|
|
3196
3427
|
}
|
|
3197
3428
|
|
|
3198
3429
|
// One consumer module importing every installed entry, written where its own
|
|
3199
3430
|
// resolution finds the installed package.
|
|
3200
|
-
|
|
3431
|
+
/**
|
|
3432
|
+
* Writes a consumer importing the requested public specifiers.
|
|
3433
|
+
*
|
|
3434
|
+
* @param stage - The installed consumer.
|
|
3435
|
+
* @param path - The consumer-relative source path.
|
|
3436
|
+
* @param specifiers - The public imports to resolve.
|
|
3437
|
+
*/
|
|
3438
|
+
export function writeDistributionConsumerProbe(
|
|
3439
|
+
stage: DistributionStage,
|
|
3440
|
+
path: string,
|
|
3441
|
+
specifiers: readonly string[],
|
|
3442
|
+
): void {
|
|
3201
3443
|
const modules = specifiers.map((specifier, index) => ({
|
|
3202
3444
|
binding: \`entry\${String(index)}\`,
|
|
3203
3445
|
specifier,
|
|
@@ -3207,112 +3449,94 @@ function writeConsumerProbe(stage: Stage, path: string, specifiers: readonly str
|
|
|
3207
3449
|
)
|
|
3208
3450
|
const names = modules.map(({ binding }) => binding)
|
|
3209
3451
|
const source = \`\${bindings.join('\\n')}\\nexport const surface = [\${names.join(', ')}]\\n\`
|
|
3210
|
-
|
|
3452
|
+
writeDistributionFile(join(stage.consumer, path), source)
|
|
3211
3453
|
}
|
|
3212
3454
|
|
|
3213
3455
|
// The runtime key set a real process reads off one installed entry under one
|
|
3214
3456
|
// condition. The driver is a file rather than an \`--eval\` string, so the specifier
|
|
3215
3457
|
// travels as an argument and nothing needs escaping.
|
|
3216
|
-
|
|
3217
|
-
|
|
3458
|
+
/**
|
|
3459
|
+
* Reads the names a real Node process loads from an installed entry.
|
|
3460
|
+
*
|
|
3461
|
+
* @param stage - The installed consumer.
|
|
3462
|
+
* @param specifier - The public package specifier.
|
|
3463
|
+
* @param driver - The consumer driver filename.
|
|
3464
|
+
* @returns The exported names. Throws when loading or decoding fails.
|
|
3465
|
+
*/
|
|
3466
|
+
export function driveDistributionRuntime(
|
|
3467
|
+
stage: DistributionStage,
|
|
3468
|
+
specifier: string,
|
|
3469
|
+
driver: string,
|
|
3470
|
+
): readonly string[] {
|
|
3471
|
+
const result = runDistributionNode([join(stage.consumer, driver), specifier], stage.consumer)
|
|
3218
3472
|
if (result.status !== 0) {
|
|
3219
|
-
throw new Error(
|
|
3473
|
+
throw new Error(
|
|
3474
|
+
\`Loading \${specifier} from the consumer failed: \${readDistributionOutput(result)}\`,
|
|
3475
|
+
)
|
|
3220
3476
|
}
|
|
3221
3477
|
const published: unknown = JSON.parse(result.stdout)
|
|
3222
|
-
if (!
|
|
3478
|
+
if (!isDistributionNames(published))
|
|
3479
|
+
throw new Error(\`The driver printed no name list for \${specifier}\`)
|
|
3223
3480
|
return published
|
|
3224
3481
|
}
|
|
3225
3482
|
{{helpers}}
|
|
3226
3483
|
// Pack this workspace, install the archive into an isolated consumer, and read the
|
|
3227
3484
|
// published surface back off the installed tree. Every later claim reads this
|
|
3228
3485
|
// result, so a failure here is raised where it happens rather than once per entry.
|
|
3229
|
-
|
|
3486
|
+
/**
|
|
3487
|
+
* Packs the workspace and installs its archive into an isolated consumer.
|
|
3488
|
+
*
|
|
3489
|
+
* @param scratch - The owned scratch root.
|
|
3490
|
+
* @returns The installed consumer and classified exports.
|
|
3491
|
+
* @throws Thrown when packing or installation fails.
|
|
3492
|
+
*/
|
|
3493
|
+
export function buildDistributionStage(scratch: string): DistributionStage {
|
|
3230
3494
|
const packed = join(scratch, 'packed')
|
|
3231
3495
|
const consumer = join(scratch, 'consumer')
|
|
3232
3496
|
mkdirSync(packed, { recursive: true })
|
|
3233
|
-
const pack =
|
|
3234
|
-
|
|
3497
|
+
const pack = runDistributionNpm(
|
|
3498
|
+
['pack', '--ignore-scripts', '--pack-destination', packed],
|
|
3499
|
+
DISTRIBUTION_ROOT,
|
|
3500
|
+
scratch,
|
|
3501
|
+
)
|
|
3502
|
+
if (pack.status !== 0)
|
|
3503
|
+
throw new Error(\`npm pack refused this workspace: \${readDistributionOutput(pack)}\`)
|
|
3235
3504
|
const archives = readdirSync(packed).filter((name) => name.endsWith('.tgz'))
|
|
3236
3505
|
const archive = archives[0]
|
|
3237
3506
|
if (archives.length !== 1 || archive === undefined) {
|
|
3238
3507
|
throw new Error(\`npm pack wrote no single archive: \${archives.join(', ')}\`)
|
|
3239
3508
|
}
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
const install =
|
|
3509
|
+
writeDistributionFile(join(consumer, 'package.json'), DISTRIBUTION_CONSUMER_MANIFEST)
|
|
3510
|
+
writeDistributionFile(join(consumer, DISTRIBUTION_ESM_DRIVER), DISTRIBUTION_ESM_DRIVER_SOURCE)
|
|
3511
|
+
writeDistributionFile(join(consumer, DISTRIBUTION_CJS_DRIVER), DISTRIBUTION_CJS_DRIVER_SOURCE)
|
|
3512
|
+
const install = runDistributionNpm(
|
|
3244
3513
|
['install', '--ignore-scripts', '--no-audit', '--no-fund', join(packed, archive)],
|
|
3245
3514
|
consumer,
|
|
3246
3515
|
scratch,
|
|
3247
3516
|
)
|
|
3248
3517
|
if (install.status !== 0) {
|
|
3249
|
-
throw new Error(\`Installing the packed archive failed: \${
|
|
3518
|
+
throw new Error(\`Installing the packed archive failed: \${readDistributionOutput(install)}\`)
|
|
3250
3519
|
}
|
|
3251
|
-
const name =
|
|
3520
|
+
const name = readDistributionManifestName(join(DISTRIBUTION_ROOT, 'package.json'))
|
|
3252
3521
|
const installed = join(consumer, 'node_modules', ...name.split('/'))
|
|
3253
|
-
const manifest =
|
|
3254
|
-
if (!
|
|
3522
|
+
const manifest = readDistributionJSON(join(installed, 'package.json'))
|
|
3523
|
+
if (!isDistributionRecord(manifest) || !isDistributionRecord(manifest.exports)) {
|
|
3255
3524
|
throw new Error('The installed manifest publishes no exports map')
|
|
3256
3525
|
}
|
|
3257
|
-
|
|
3258
|
-
const files = collectTargets(mapping)
|
|
3259
|
-
const declaration = readDeclaration(mapping, installed)
|
|
3260
|
-
// A subpath resolving no declaration is partitioned rather than dropped. It is a
|
|
3261
|
-
// defect when a runtime loads one of its targets for names, because a consumer
|
|
3262
|
-
// importing it compiles against nothing under \`node16\`. It is an excluded
|
|
3263
|
-
// publication otherwise: the \`"./package.json"\` manifest pointer and a stylesheet
|
|
3264
|
-
// are published for a reader rather than an importer.
|
|
3265
|
-
if (
|
|
3266
|
-
declaration.module === undefined &&
|
|
3267
|
-
declaration.commonjs === undefined &&
|
|
3268
|
-
declaration.browser === undefined
|
|
3269
|
-
) {
|
|
3270
|
-
return { subpath, files, entry: undefined }
|
|
3271
|
-
}
|
|
3272
|
-
const importTarget = resolveTarget(mapping, RUNTIME_CONDITIONS.module)
|
|
3273
|
-
const requireTarget = resolveTarget(mapping, RUNTIME_CONDITIONS.commonjs)
|
|
3274
|
-
const browserTarget = resolveTarget(mapping, RUNTIME_CONDITIONS.browser)
|
|
3275
|
-
const browsable = resolvesBrowser(mapping)
|
|
3276
|
-
const shadowed = browsable && requireTarget === browserTarget
|
|
3277
|
-
const requirable = requireTarget !== undefined && !shadowed
|
|
3278
|
-
const loadable = requirable && resolvesCommonJS(mapping, installed)
|
|
3279
|
-
const entry: Entry = {
|
|
3280
|
-
subpath,
|
|
3281
|
-
specifier: subpath === '.' ? name : \`\${name}\${subpath.slice(1)}\`,
|
|
3282
|
-
mapping,
|
|
3283
|
-
declaration: {
|
|
3284
|
-
importable: declaration.module !== undefined,
|
|
3285
|
-
requirable: declaration.commonjs !== undefined,
|
|
3286
|
-
browsable: declaration.browser !== undefined,
|
|
3287
|
-
},
|
|
3288
|
-
browsable,
|
|
3289
|
-
importable: importTarget !== undefined && !(browsable && importTarget === browserTarget),
|
|
3290
|
-
requirable,
|
|
3291
|
-
loadable,
|
|
3292
|
-
}
|
|
3293
|
-
return { subpath, files, entry }
|
|
3294
|
-
})
|
|
3295
|
-
const entries = readings.flatMap(({ entry }) => (entry === undefined ? [] : [entry]))
|
|
3296
|
-
const targets = readings.flatMap(({ files }) => files)
|
|
3297
|
-
const subpaths = readings.map(({ subpath }) => subpath)
|
|
3298
|
-
const undeclared = readings
|
|
3299
|
-
.filter(({ entry, files }) => entry === undefined && files.some(isModule))
|
|
3300
|
-
.map(({ subpath }) => subpath)
|
|
3301
|
-
const excluded = readings
|
|
3302
|
-
.filter(({ entry, files }) => entry === undefined && !files.some(isModule))
|
|
3303
|
-
.map(({ subpath }) => subpath)
|
|
3304
|
-
return { consumer, installed, archives, entries, subpaths, undeclared, excluded, targets }
|
|
3526
|
+
return { consumer, installed, archives, ...classifyDistributionStage(manifest, installed, name) }
|
|
3305
3527
|
}
|
|
3306
3528
|
|
|
3307
3529
|
// One file's run: the scratch directory and the staged consumer, which is absent when the
|
|
3308
3530
|
// registry could not be reached.
|
|
3309
|
-
|
|
3531
|
+
/** Binds an owned scratch directory to its optional installed consumer. */
|
|
3532
|
+
export interface DistributionRun {
|
|
3310
3533
|
readonly scratch: string
|
|
3311
|
-
readonly stage:
|
|
3534
|
+
readonly stage: DistributionStage | undefined
|
|
3312
3535
|
}
|
|
3313
3536
|
|
|
3314
3537
|
// The run the start hook binds once for this file and the stop hook releases.
|
|
3315
|
-
|
|
3538
|
+
/** Owns the staged consumer shared by one distribution proof. */
|
|
3539
|
+
export class DistributionSuite implements DistributionSuiteInterface {
|
|
3316
3540
|
#run: DistributionRun | undefined
|
|
3317
3541
|
|
|
3318
3542
|
get scratch(): string {
|
|
@@ -3322,8 +3546,12 @@ class DistributionSuite {
|
|
|
3322
3546
|
return this.#run.scratch
|
|
3323
3547
|
}
|
|
3324
3548
|
|
|
3325
|
-
|
|
3326
|
-
|
|
3549
|
+
read(context: TestContext): DistributionStage {
|
|
3550
|
+
const stage = this.#run?.stage
|
|
3551
|
+
if (stage === undefined) {
|
|
3552
|
+
return context.skip('\`npm ping\` did not answer, so nothing was packed or installed')
|
|
3553
|
+
}
|
|
3554
|
+
return stage
|
|
3327
3555
|
}
|
|
3328
3556
|
|
|
3329
3557
|
start(): void {
|
|
@@ -3331,7 +3559,7 @@ class DistributionSuite {
|
|
|
3331
3559
|
throw new Error('The distribution setup has already allocated its scratch directory')
|
|
3332
3560
|
}
|
|
3333
3561
|
const scratch = mkdtempSync(join(tmpdir(), 'distribution-'))
|
|
3334
|
-
this.#run = { scratch, stage:
|
|
3562
|
+
this.#run = { scratch, stage: openDistributionStage(scratch) }
|
|
3335
3563
|
}
|
|
3336
3564
|
|
|
3337
3565
|
destroy(): void {
|
|
@@ -3342,25 +3570,26 @@ class DistributionSuite {
|
|
|
3342
3570
|
}
|
|
3343
3571
|
}
|
|
3344
3572
|
|
|
3345
|
-
const DISTRIBUTION_SUITE = new DistributionSuite()
|
|
3346
|
-
|
|
3347
|
-
beforeAll(() => DISTRIBUTION_SUITE.start())
|
|
3348
|
-
|
|
3349
|
-
afterAll(() => DISTRIBUTION_SUITE.destroy())
|
|
3350
|
-
|
|
3351
3573
|
// Installing the packed archive resolves its own runtime dependencies, so an
|
|
3352
3574
|
// unreachable registry leaves nothing to measure. Under release that is the gate
|
|
3353
3575
|
// failing; anywhere else the suite skips and names the mechanism it wanted.
|
|
3354
|
-
|
|
3576
|
+
/**
|
|
3577
|
+
* Opens an installed consumer after checking registry readiness.
|
|
3578
|
+
*
|
|
3579
|
+
* @param scratch - The owned scratch root.
|
|
3580
|
+
* @returns The installed consumer, or undefined when an ordinary run cannot reach the registry.
|
|
3581
|
+
* @throws Thrown in release mode or when staging fails, after removing scratch.
|
|
3582
|
+
*/
|
|
3583
|
+
export function openDistributionStage(scratch: string): DistributionStage | undefined {
|
|
3355
3584
|
try {
|
|
3356
3585
|
mkdirSync(join(scratch, 'cache'), { recursive: true })
|
|
3357
|
-
if (
|
|
3358
|
-
if (!
|
|
3586
|
+
if (runDistributionNpm(DISTRIBUTION_PING, DISTRIBUTION_ROOT, scratch).status !== 0) {
|
|
3587
|
+
if (!DISTRIBUTION_RELEASE) return undefined
|
|
3359
3588
|
throw new Error(
|
|
3360
3589
|
'The release gate requires a reachable npm registry, and npm ping did not answer',
|
|
3361
3590
|
)
|
|
3362
3591
|
}
|
|
3363
|
-
return
|
|
3592
|
+
return buildDistributionStage(scratch)
|
|
3364
3593
|
} catch (error) {
|
|
3365
3594
|
rmSync(scratch, {
|
|
3366
3595
|
force: true,
|
|
@@ -3372,200 +3601,690 @@ function openStage(scratch: string): Stage | undefined {
|
|
|
3372
3601
|
}
|
|
3373
3602
|
}
|
|
3374
3603
|
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3604
|
+
/**
|
|
3605
|
+
* Partitions an installed exports map into driven, undeclared, and excluded subpaths.
|
|
3606
|
+
*
|
|
3607
|
+
* @param manifest - The installed package manifest.
|
|
3608
|
+
* @param installed - The installed package root.
|
|
3609
|
+
* @param name - The published package name.
|
|
3610
|
+
* @returns The classified entries and complete target population.
|
|
3611
|
+
* @throws Thrown when the exports map is absent.
|
|
3612
|
+
*/
|
|
3613
|
+
export function classifyDistributionStage(
|
|
3614
|
+
manifest: Readonly<Record<string, unknown>>,
|
|
3615
|
+
installed: string,
|
|
3616
|
+
name: string,
|
|
3617
|
+
): DistributionClassification {
|
|
3618
|
+
if (!isDistributionRecord(manifest.exports))
|
|
3619
|
+
throw new Error('The installed manifest publishes no exports map')
|
|
3620
|
+
const readings = Object.entries(manifest.exports).map(([subpath, mapping]) => {
|
|
3621
|
+
const files = collectDistributionTargets(mapping)
|
|
3622
|
+
const declaration = readDistributionDeclaration(mapping, installed)
|
|
3623
|
+
// A subpath resolving no declaration is partitioned rather than dropped. It is a
|
|
3624
|
+
// defect when a runtime loads one of its targets for names, because a consumer
|
|
3625
|
+
// importing it compiles against nothing under \`node16\`. It is an excluded
|
|
3626
|
+
// publication otherwise: the \`"./package.json"\` manifest pointer and a stylesheet
|
|
3627
|
+
// are published for a reader rather than an importer.
|
|
3628
|
+
if (
|
|
3629
|
+
declaration.module === undefined &&
|
|
3630
|
+
declaration.commonjs === undefined &&
|
|
3631
|
+
declaration.browser === undefined
|
|
3632
|
+
) {
|
|
3633
|
+
return { subpath, files, entry: undefined }
|
|
3634
|
+
}
|
|
3635
|
+
const importTarget = resolveDistributionTarget(mapping, DISTRIBUTION_RUNTIME_CONDITIONS.module)
|
|
3636
|
+
const requireTarget = resolveDistributionTarget(
|
|
3637
|
+
mapping,
|
|
3638
|
+
DISTRIBUTION_RUNTIME_CONDITIONS.commonjs,
|
|
3404
3639
|
)
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3640
|
+
const browserTarget = resolveDistributionTarget(
|
|
3641
|
+
mapping,
|
|
3642
|
+
DISTRIBUTION_RUNTIME_CONDITIONS.browser,
|
|
3643
|
+
)
|
|
3644
|
+
const browsable = resolvesDistributionBrowser(mapping)
|
|
3645
|
+
const shadowed = browsable && requireTarget === browserTarget
|
|
3646
|
+
const requirable = requireTarget !== undefined && !shadowed
|
|
3647
|
+
const loadable = requirable && resolvesDistributionCommonJS(mapping, installed)
|
|
3648
|
+
const entry: DistributionEntry = {
|
|
3649
|
+
subpath,
|
|
3650
|
+
specifier: subpath === '.' ? name : \`\${name}\${subpath.slice(1)}\`,
|
|
3651
|
+
mapping,
|
|
3652
|
+
declaration: {
|
|
3653
|
+
importable: declaration.module !== undefined,
|
|
3654
|
+
requirable: declaration.commonjs !== undefined,
|
|
3655
|
+
browsable: declaration.browser !== undefined,
|
|
3656
|
+
},
|
|
3657
|
+
browsable,
|
|
3658
|
+
importable: importTarget !== undefined && !(browsable && importTarget === browserTarget),
|
|
3659
|
+
requirable,
|
|
3660
|
+
loadable,
|
|
3410
3661
|
}
|
|
3411
|
-
|
|
3412
|
-
expect({
|
|
3413
|
-
condition: resolvesBrowser(mappings.condition),
|
|
3414
|
-
convention: resolvesBrowser(mappings.convention),
|
|
3415
|
-
universal: resolvesBrowser(mappings.universal),
|
|
3416
|
-
import: resolvesBrowser(mappings['import-shared']),
|
|
3417
|
-
require: resolvesBrowser(mappings['require-shared']),
|
|
3418
|
-
node: resolvesBrowser(mappings.node),
|
|
3419
|
-
}).toStrictEqual({
|
|
3420
|
-
condition: true,
|
|
3421
|
-
convention: true,
|
|
3422
|
-
universal: false,
|
|
3423
|
-
import: false,
|
|
3424
|
-
require: false,
|
|
3425
|
-
node: false,
|
|
3426
|
-
})
|
|
3427
|
-
expect({
|
|
3428
|
-
silent: resolvesCommonJS(mappings.silent, root),
|
|
3429
|
-
module: resolvesCommonJS(mappings.module, root),
|
|
3430
|
-
nestedModule: resolvesCommonJS(mappings['nested-module'], root),
|
|
3431
|
-
nestedCommonJS: resolvesCommonJS(mappings['nested-commonjs'], root),
|
|
3432
|
-
esm: resolvesCommonJS(mappings.esm, root),
|
|
3433
|
-
}).toStrictEqual({
|
|
3434
|
-
silent: true,
|
|
3435
|
-
module: false,
|
|
3436
|
-
nestedModule: false,
|
|
3437
|
-
nestedCommonJS: true,
|
|
3438
|
-
esm: false,
|
|
3439
|
-
})
|
|
3662
|
+
return { subpath, files, entry }
|
|
3440
3663
|
})
|
|
3441
|
-
})
|
|
3664
|
+
const entries = readings.flatMap(({ entry }) => (entry === undefined ? [] : [entry]))
|
|
3665
|
+
const targets = readings.flatMap(({ files }) => files)
|
|
3666
|
+
const subpaths = readings.map(({ subpath }) => subpath)
|
|
3667
|
+
const undeclared = readings
|
|
3668
|
+
.filter(({ entry, files }) => entry === undefined && files.some(matchesDistributionModule))
|
|
3669
|
+
.map(({ subpath }) => subpath)
|
|
3670
|
+
const excluded = readings
|
|
3671
|
+
.filter(({ entry, files }) => entry === undefined && !files.some(matchesDistributionModule))
|
|
3672
|
+
.map(({ subpath }) => subpath)
|
|
3673
|
+
return { entries, subpaths, undeclared, excluded, targets }
|
|
3674
|
+
}
|
|
3442
3675
|
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
}
|
|
3450
|
-
return staged
|
|
3676
|
+
/**
|
|
3677
|
+
* Creates the lifecycle boundary used by generated distribution proofs.
|
|
3678
|
+
* @returns The suite interface backed by an owned run.
|
|
3679
|
+
*/
|
|
3680
|
+
export function createDistributionSuite(): DistributionSuiteInterface {
|
|
3681
|
+
return new DistributionSuite()
|
|
3451
3682
|
}
|
|
3452
3683
|
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3684
|
+
/**
|
|
3685
|
+
* Collects physical files beneath an installed output directory.
|
|
3686
|
+
* @param directory - The output directory to walk.
|
|
3687
|
+
* @returns The installed files, or an empty list when the directory is absent.
|
|
3688
|
+
*/
|
|
3689
|
+
export function collectDistributionFiles(directory: string): readonly string[] {
|
|
3690
|
+
if (!existsSync(directory)) return []
|
|
3691
|
+
return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
|
|
3692
|
+
const path = join(directory, entry.name)
|
|
3693
|
+
return entry.isDirectory() ? collectDistributionFiles(path) : entry.isFile() ? [path] : []
|
|
3459
3694
|
})
|
|
3695
|
+
}
|
|
3460
3696
|
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3697
|
+
/**
|
|
3698
|
+
* Reads the source environment owning an installed output path.
|
|
3699
|
+
* @param installed - The installed package root.
|
|
3700
|
+
* @param path - The output file.
|
|
3701
|
+
* @returns The source environment, or undefined outside a JavaScript face.
|
|
3702
|
+
*/
|
|
3703
|
+
export function readDistributionFace(installed: string, path: string): string | undefined {
|
|
3704
|
+
const [output, axis, face] = relativePath(installed, path).split(sep)
|
|
3705
|
+
return output === 'dist'
|
|
3706
|
+
? axis === 'src'
|
|
3707
|
+
? face
|
|
3708
|
+
: axis === 'bin'
|
|
3709
|
+
? 'bin'
|
|
3710
|
+
: undefined
|
|
3711
|
+
: undefined
|
|
3712
|
+
}
|
|
3713
|
+
|
|
3714
|
+
/**
|
|
3715
|
+
* Scans installed JavaScript maps for sources copied from another environment.
|
|
3716
|
+
* @param installed - The installed package root.
|
|
3717
|
+
* @returns The copied sources and unreadable maps.
|
|
3718
|
+
* @throws Thrown when a code-bearing output has no readable map of its own.
|
|
3719
|
+
*/
|
|
3720
|
+
export function scanDistributionSourceMaps(installed: string): readonly string[] {
|
|
3721
|
+
const files = collectDistributionFiles(join(installed, 'dist')).filter(
|
|
3722
|
+
(path) => readDistributionFace(installed, path) !== undefined,
|
|
3723
|
+
)
|
|
3724
|
+
const outputs = files
|
|
3725
|
+
.filter((path) => /\\.(?:js|cjs|mjs)$/u.test(path))
|
|
3726
|
+
.map((path) => {
|
|
3727
|
+
const parsed = parseSync(path, readFileSync(path, 'utf8'))
|
|
3728
|
+
const code = parsed.program.body.some(
|
|
3729
|
+
(statement) =>
|
|
3730
|
+
statement.type !== 'EmptyStatement' &&
|
|
3731
|
+
!(
|
|
3732
|
+
statement.type === 'ExportNamedDeclaration' &&
|
|
3733
|
+
statement.declaration === null &&
|
|
3734
|
+
statement.specifiers.length === 0 &&
|
|
3735
|
+
statement.source === null
|
|
3736
|
+
),
|
|
3737
|
+
)
|
|
3738
|
+
const reference = parsed.comments
|
|
3739
|
+
.flatMap((comment) => {
|
|
3740
|
+
const match = /^[@#]\\s*sourceMappingURL=(\\S+)\\s*$/u.exec(comment.value)
|
|
3741
|
+
return match?.[1] === undefined ? [] : [match[1]]
|
|
3742
|
+
})
|
|
3743
|
+
.at(-1)
|
|
3744
|
+
try {
|
|
3745
|
+
const inline = reference?.startsWith('data:') === true
|
|
3746
|
+
const target = inline
|
|
3747
|
+
? path
|
|
3748
|
+
: reference === undefined
|
|
3749
|
+
? \`\${path}.map\`
|
|
3750
|
+
: fileURLToPath(new URL(reference.replaceAll('\\\\', '/'), pathToFileURL(path)))
|
|
3751
|
+
return {
|
|
3752
|
+
path: target,
|
|
3753
|
+
face: readDistributionFace(installed, path),
|
|
3754
|
+
code,
|
|
3755
|
+
reference: inline ? reference : undefined,
|
|
3756
|
+
}
|
|
3757
|
+
} catch {
|
|
3758
|
+
throw new Error(\`Unreadable source maps for \${path}\`)
|
|
3759
|
+
}
|
|
3760
|
+
})
|
|
3761
|
+
const maps = [
|
|
3762
|
+
...outputs.filter(
|
|
3763
|
+
({ path, code, reference }) => code || reference !== undefined || existsSync(path),
|
|
3764
|
+
),
|
|
3765
|
+
...files
|
|
3766
|
+
.filter(
|
|
3767
|
+
(path) =>
|
|
3768
|
+
/\\.(?:js|cjs|mjs)\\.map$/u.test(path) && !outputs.some((output) => output.path === path),
|
|
3769
|
+
)
|
|
3770
|
+
.map((path) => ({
|
|
3771
|
+
path,
|
|
3772
|
+
face: readDistributionFace(installed, path),
|
|
3773
|
+
code: false,
|
|
3774
|
+
reference: undefined,
|
|
3775
|
+
})),
|
|
3776
|
+
]
|
|
3777
|
+
if (maps.length === 0)
|
|
3778
|
+
console.info('No built JavaScript face holds code, so no source map is required')
|
|
3779
|
+
return maps.flatMap(({ path, face, code, reference }) => {
|
|
3780
|
+
const label = relativePath(installed, path).split(sep).join('/')
|
|
3781
|
+
try {
|
|
3782
|
+
const payload =
|
|
3783
|
+
reference === undefined
|
|
3784
|
+
? undefined
|
|
3785
|
+
: /^data:application\\/json(?:;charset=[^;,]+)?(;base64)?,(.*)$/iu.exec(reference)
|
|
3786
|
+
const map: unknown =
|
|
3787
|
+
reference === undefined
|
|
3788
|
+
? readDistributionJSON(path)
|
|
3789
|
+
: payload?.[2] === undefined
|
|
3790
|
+
? undefined
|
|
3791
|
+
: JSON.parse(
|
|
3792
|
+
payload[1] === undefined
|
|
3793
|
+
? decodeURIComponent(payload[2])
|
|
3794
|
+
: Buffer.from(payload[2], 'base64').toString('utf8'),
|
|
3795
|
+
)
|
|
3796
|
+
if (
|
|
3797
|
+
!isDistributionRecord(map) ||
|
|
3798
|
+
!isDistributionNames(map.sources) ||
|
|
3799
|
+
(map.sourceRoot !== undefined && typeof map.sourceRoot !== 'string')
|
|
3800
|
+
) {
|
|
3801
|
+
throw new Error('Unreadable source map')
|
|
3802
|
+
}
|
|
3803
|
+
const root = typeof map.sourceRoot === 'string' ? map.sourceRoot : ''
|
|
3804
|
+
const directory = root.startsWith('file:')
|
|
3805
|
+
? fileURLToPath(new URL(root))
|
|
3806
|
+
: resolve(dirname(path), root.replaceAll('\\\\', '/'))
|
|
3807
|
+
const sources = map.sources.map((source) =>
|
|
3808
|
+
relativePath(
|
|
3809
|
+
resolve(installed),
|
|
3810
|
+
source.startsWith('file:')
|
|
3811
|
+
? fileURLToPath(new URL(source))
|
|
3812
|
+
: resolve(directory, source.replaceAll('\\\\', '/')),
|
|
3813
|
+
)
|
|
3814
|
+
.split(sep)
|
|
3815
|
+
.join('/'),
|
|
3816
|
+
)
|
|
3817
|
+
const copied = sources.filter(
|
|
3818
|
+
(source) => source.startsWith('src/') && !source.startsWith(\`src/\${face}/\`),
|
|
3819
|
+
)
|
|
3820
|
+
return [
|
|
3821
|
+
...copied.map((source) => \`\${label}: \${source}\`),
|
|
3822
|
+
...(sources.some((source) => source.startsWith(\`src/\${face}/\`))
|
|
3823
|
+
? []
|
|
3824
|
+
: [\`\${label}: unreadable source map (no src/\${face}/ source)\`]),
|
|
3825
|
+
]
|
|
3826
|
+
} catch {
|
|
3827
|
+
if (code) throw new Error(\`Unreadable source maps for \${path}\`)
|
|
3828
|
+
return [\`\${label}: unreadable source map\`]
|
|
3829
|
+
}
|
|
3468
3830
|
})
|
|
3831
|
+
}
|
|
3469
3832
|
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3833
|
+
/**
|
|
3834
|
+
* Reads top-level names declared by a roll-up, including unexported declarations.
|
|
3835
|
+
* @param path - The installed declaration file.
|
|
3836
|
+
* @returns The declared names. Refuses syntax errors and unreadable binding patterns.
|
|
3837
|
+
*/
|
|
3838
|
+
export function readDistributionNames(path: string): readonly string[] {
|
|
3839
|
+
const source = parseSync(path, readFileSync(path, 'utf8'))
|
|
3840
|
+
if (source.errors.length > 0) throw new Error(\`Unreadable declarations at \${path}\`)
|
|
3841
|
+
return source.program.body.flatMap((statement) => {
|
|
3842
|
+
const declaration =
|
|
3843
|
+
statement.type === 'ExportNamedDeclaration' || statement.type === 'ExportDefaultDeclaration'
|
|
3844
|
+
? statement.declaration
|
|
3845
|
+
: statement
|
|
3846
|
+
if (declaration === null) return []
|
|
3847
|
+
if (declaration.type === 'VariableDeclaration') {
|
|
3848
|
+
return declaration.declarations.map((binding) => {
|
|
3849
|
+
if (binding.id.type !== 'Identifier')
|
|
3850
|
+
throw new Error(\`Unreadable declaration binding at \${path}\`)
|
|
3851
|
+
return binding.id.name
|
|
3852
|
+
})
|
|
3853
|
+
}
|
|
3854
|
+
if (
|
|
3855
|
+
declaration.type === 'ClassDeclaration' ||
|
|
3856
|
+
declaration.type === 'FunctionDeclaration' ||
|
|
3857
|
+
declaration.type === 'TSDeclareFunction' ||
|
|
3858
|
+
declaration.type === 'TSInterfaceDeclaration' ||
|
|
3859
|
+
declaration.type === 'TSTypeAliasDeclaration' ||
|
|
3860
|
+
declaration.type === 'TSEnumDeclaration' ||
|
|
3861
|
+
declaration.type === 'TSModuleDeclaration'
|
|
3862
|
+
) {
|
|
3863
|
+
return declaration.id?.type === 'Identifier' ? [declaration.id.name] : []
|
|
3864
|
+
}
|
|
3865
|
+
return []
|
|
3493
3866
|
})
|
|
3867
|
+
}
|
|
3494
3868
|
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3869
|
+
/**
|
|
3870
|
+
* Checks whether a source entry exports names, following local star exports.
|
|
3871
|
+
* @param path - The source entry to inspect.
|
|
3872
|
+
* @param ancestors - The entries already visited on this export path.
|
|
3873
|
+
* @returns True if the entry exports a name; false otherwise.
|
|
3874
|
+
*/
|
|
3875
|
+
export function matchesDistributionSourceExports(
|
|
3876
|
+
path: string,
|
|
3877
|
+
ancestors: readonly string[] = [],
|
|
3878
|
+
): boolean {
|
|
3879
|
+
if (ancestors.includes(path)) return false
|
|
3880
|
+
const source = parseSync(path, readFileSync(path, 'utf8'))
|
|
3881
|
+
if (source.errors.length > 0) throw new Error(\`Unreadable source exports at \${path}\`)
|
|
3882
|
+
return source.program.body.some((statement) => {
|
|
3883
|
+
if (statement.type === 'ExportDefaultDeclaration') return ancestors.length === 0
|
|
3884
|
+
if (statement.type === 'ExportNamedDeclaration')
|
|
3885
|
+
return (
|
|
3886
|
+
statement.declaration !== null ||
|
|
3887
|
+
statement.specifiers.some((specifier) => {
|
|
3888
|
+
const name =
|
|
3889
|
+
specifier.exported.type === 'Identifier'
|
|
3890
|
+
? specifier.exported.name
|
|
3891
|
+
: specifier.exported.value
|
|
3892
|
+
return ancestors.length === 0 || name !== 'default'
|
|
3893
|
+
})
|
|
3894
|
+
)
|
|
3895
|
+
if (statement.type !== 'ExportAllDeclaration') return false
|
|
3896
|
+
if (statement.exported !== null || !statement.source.value.startsWith('.')) return true
|
|
3897
|
+
const target = resolve(dirname(path), statement.source.value.replace(/\\.js$/u, '.ts'))
|
|
3898
|
+
return matchesDistributionSourceExports(target, [...ancestors, path])
|
|
3502
3899
|
})
|
|
3900
|
+
}
|
|
3503
3901
|
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
}),
|
|
3526
|
-
)
|
|
3527
|
-
expect(readings.flatMap(({ messages }) => messages)).toStrictEqual([])
|
|
3528
|
-
expect(readings.flatMap(({ silent }) => silent)).toStrictEqual([])
|
|
3902
|
+
/**
|
|
3903
|
+
* Scans installed declaration faces for names declared by multiple environments.
|
|
3904
|
+
* @param installed - The installed package root.
|
|
3905
|
+
* @param source - The workspace holding the source entries being published.
|
|
3906
|
+
* @returns The repeated names with their declaring faces.
|
|
3907
|
+
* @throws Thrown when a source entry exports names but its roll-up declares none.
|
|
3908
|
+
*/
|
|
3909
|
+
export function scanDistributionDeclarations(
|
|
3910
|
+
installed: string,
|
|
3911
|
+
source: string = DISTRIBUTION_ROOT,
|
|
3912
|
+
): readonly string[] {
|
|
3913
|
+
const declarations = collectDistributionFiles(join(installed, 'dist'))
|
|
3914
|
+
.filter(matchesDistributionDeclaration)
|
|
3915
|
+
.map((path) => ({
|
|
3916
|
+
path,
|
|
3917
|
+
face: readDistributionFace(installed, path),
|
|
3918
|
+
names: readDistributionNames(path),
|
|
3919
|
+
}))
|
|
3920
|
+
const entries = collectDistributionFiles(join(source, 'src')).filter((path) => {
|
|
3921
|
+
const parts = relativePath(source, path).split(sep)
|
|
3922
|
+
return parts.length === 3 && parts[2] === 'index.ts'
|
|
3529
3923
|
})
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
if (entries.length === 0) return context.skip('No installed entry supports Node import')
|
|
3537
|
-
for (const entry of entries) {
|
|
3538
|
-
if (!entry.declaration.importable) {
|
|
3539
|
-
throw new Error(\`\${entry.subpath} publishes no import declaration\`)
|
|
3540
|
-
}
|
|
3541
|
-
const published = driveRuntime(stage, entry.specifier, ESM_DRIVER)
|
|
3542
|
-
const drivers = selectDrivers(entry, 'module')
|
|
3543
|
-
expect(drivers).not.toStrictEqual([])
|
|
3544
|
-
const reported = drivers.flatMap((driver) =>
|
|
3545
|
-
checkSurface(stage, { entry, extension: 'ts', published, driver }),
|
|
3924
|
+
const published = entries.filter((path) => matchesDistributionSourceExports(path))
|
|
3925
|
+
for (const path of published) {
|
|
3926
|
+
const face = relativePath(source, path).split(sep)[1]
|
|
3927
|
+
if (!declarations.some((entry) => entry.face === face && entry.names.length > 0))
|
|
3928
|
+
throw new Error(
|
|
3929
|
+
\`The \${face} source entry exports names but its declaration roll-up declares none\`,
|
|
3546
3930
|
)
|
|
3547
|
-
|
|
3548
|
-
|
|
3931
|
+
}
|
|
3932
|
+
if (published.length === 0)
|
|
3933
|
+
console.info('No source entry exports names, so no declaration names are required')
|
|
3934
|
+
const names = [...new Set(declarations.flatMap((entry) => entry.names))].sort()
|
|
3935
|
+
return names.flatMap((name) => {
|
|
3936
|
+
const faces = [
|
|
3937
|
+
...new Set(
|
|
3938
|
+
declarations
|
|
3939
|
+
.filter((entry) => entry.names.includes(name))
|
|
3940
|
+
.map((entry) => entry.face)
|
|
3941
|
+
.filter((face) => face !== undefined),
|
|
3942
|
+
),
|
|
3943
|
+
].sort()
|
|
3944
|
+
return faces.length > 1 ? [\`\${name}: \${faces.join(', ')}\`] : []
|
|
3549
3945
|
})
|
|
3946
|
+
}
|
|
3550
3947
|
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3948
|
+
/**
|
|
3949
|
+
* Checks the installed manifest against npm's normalization in an owned scratch copy.
|
|
3950
|
+
* @param installed - The installed package root, read only.
|
|
3951
|
+
* @param scratch - The suite's owned scratch root and npm cache parent.
|
|
3952
|
+
* @returns The top-level keys changed by npm. Refuses an unsuccessful npm command.
|
|
3953
|
+
*/
|
|
3954
|
+
export function checkDistributionManifest(installed: string, scratch: string): readonly string[] {
|
|
3955
|
+
const directory = mkdtempSync(join(scratch, 'manifest-'))
|
|
3956
|
+
try {
|
|
3957
|
+
const path = join(directory, 'package.json')
|
|
3958
|
+
writeFileSync(path, readFileSync(join(installed, 'package.json')))
|
|
3959
|
+
const before = readDistributionJSON(path)
|
|
3960
|
+
const result = runDistributionNpm(['pkg', 'fix'], directory, scratch)
|
|
3961
|
+
if (result.status !== 0)
|
|
3962
|
+
throw new Error(\`npm pkg fix failed: \${readDistributionOutput(result)}\`)
|
|
3963
|
+
const after = readDistributionJSON(path)
|
|
3964
|
+
if (!isDistributionRecord(before) || !isDistributionRecord(after))
|
|
3965
|
+
throw new Error('Unreadable manifest')
|
|
3966
|
+
return [...new Set([...Object.keys(before), ...Object.keys(after)])]
|
|
3967
|
+
.filter((key) => !isDeepStrictEqual(before[key], after[key]))
|
|
3968
|
+
.sort()
|
|
3969
|
+
} finally {
|
|
3970
|
+
rmSync(directory, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 })
|
|
3971
|
+
}
|
|
3972
|
+
}
|
|
3973
|
+
|
|
3974
|
+
/**
|
|
3975
|
+
* Builds independently registered observations over the suite's installed consumer.
|
|
3976
|
+
* @param suite - The lifecycle shared by the proof's hooks and observations.
|
|
3977
|
+
* @returns The case rows whose observations each require one assertion.
|
|
3978
|
+
*/
|
|
3979
|
+
export function buildDistributionCases(
|
|
3980
|
+
suite: DistributionSuiteInterface,
|
|
3981
|
+
): readonly DistributionCase[] {
|
|
3982
|
+
return [
|
|
3983
|
+
{
|
|
3984
|
+
label: 'classifies synthetic export mappings without a registry stage',
|
|
3985
|
+
observe: () => {
|
|
3986
|
+
const root = join(suite.scratch, 'classifiers')
|
|
3987
|
+
writeDistributionFile(
|
|
3988
|
+
join(root, 'package.json'),
|
|
3989
|
+
JSON.stringify({
|
|
3990
|
+
type: 'commonjs',
|
|
3991
|
+
exports: {
|
|
3992
|
+
condition: { browser: './b.js', default: './n.js' },
|
|
3993
|
+
convention: { default: './dist/src/browser/index.js' },
|
|
3994
|
+
universal: { default: './shared.js' },
|
|
3995
|
+
'import-shared': {
|
|
3996
|
+
browser: './shared.mjs',
|
|
3997
|
+
import: './shared.mjs',
|
|
3998
|
+
default: './node.js',
|
|
3999
|
+
},
|
|
4000
|
+
'require-shared': {
|
|
4001
|
+
browser: './shared.cjs',
|
|
4002
|
+
require: './shared.cjs',
|
|
4003
|
+
default: './node.js',
|
|
4004
|
+
},
|
|
4005
|
+
node: { node: './node.js', default: './node.js' },
|
|
4006
|
+
silent: { 'module-sync': './x.cjs', import: './x.mjs' },
|
|
4007
|
+
module: { require: './x.mjs' },
|
|
4008
|
+
'nested-module': { require: './module/x.js' },
|
|
4009
|
+
'nested-commonjs': { require: './commonjs/x.js' },
|
|
4010
|
+
esm: { import: './x.mjs' },
|
|
4011
|
+
},
|
|
4012
|
+
}),
|
|
4013
|
+
)
|
|
4014
|
+
writeDistributionFile(join(root, 'module/package.json'), '{ "type": "module" }\\n')
|
|
4015
|
+
writeDistributionFile(join(root, 'commonjs/package.json'), '{ "type": "commonjs" }\\n')
|
|
4016
|
+
const manifest = readDistributionJSON(join(root, 'package.json'))
|
|
4017
|
+
if (!isDistributionRecord(manifest) || !isDistributionRecord(manifest.exports)) {
|
|
4018
|
+
throw new Error('The classifier fixture declares no exports map')
|
|
4019
|
+
}
|
|
4020
|
+
const mappings = manifest.exports
|
|
4021
|
+
|
|
4022
|
+
return {
|
|
4023
|
+
actual: {
|
|
4024
|
+
browser: {
|
|
4025
|
+
condition: resolvesDistributionBrowser(mappings.condition),
|
|
4026
|
+
convention: resolvesDistributionBrowser(mappings.convention),
|
|
4027
|
+
universal: resolvesDistributionBrowser(mappings.universal),
|
|
4028
|
+
import: resolvesDistributionBrowser(mappings['import-shared']),
|
|
4029
|
+
require: resolvesDistributionBrowser(mappings['require-shared']),
|
|
4030
|
+
node: resolvesDistributionBrowser(mappings.node),
|
|
4031
|
+
},
|
|
4032
|
+
commonjs: {
|
|
4033
|
+
silent: resolvesDistributionCommonJS(mappings.silent, root),
|
|
4034
|
+
module: resolvesDistributionCommonJS(mappings.module, root),
|
|
4035
|
+
nestedModule: resolvesDistributionCommonJS(mappings['nested-module'], root),
|
|
4036
|
+
nestedCommonJS: resolvesDistributionCommonJS(mappings['nested-commonjs'], root),
|
|
4037
|
+
esm: resolvesDistributionCommonJS(mappings.esm, root),
|
|
4038
|
+
},
|
|
4039
|
+
},
|
|
4040
|
+
expected: {
|
|
4041
|
+
browser: {
|
|
4042
|
+
condition: true,
|
|
4043
|
+
convention: true,
|
|
4044
|
+
universal: false,
|
|
4045
|
+
import: false,
|
|
4046
|
+
require: false,
|
|
4047
|
+
node: false,
|
|
4048
|
+
},
|
|
4049
|
+
commonjs: {
|
|
4050
|
+
silent: true,
|
|
4051
|
+
module: false,
|
|
4052
|
+
nestedModule: false,
|
|
4053
|
+
nestedCommonJS: true,
|
|
4054
|
+
esm: false,
|
|
4055
|
+
},
|
|
4056
|
+
},
|
|
4057
|
+
}
|
|
4058
|
+
},
|
|
4059
|
+
},
|
|
4060
|
+
{
|
|
4061
|
+
label: 'keeps installed JavaScript sources in their own environment [requires the registry]',
|
|
4062
|
+
observe: (context) => {
|
|
4063
|
+
const stage = suite.read(context)
|
|
4064
|
+
return { actual: scanDistributionSourceMaps(stage.installed), expected: [] }
|
|
4065
|
+
},
|
|
4066
|
+
},
|
|
4067
|
+
{
|
|
4068
|
+
label: 'declares each installed name in one environment [requires the registry]',
|
|
4069
|
+
observe: (context) => {
|
|
4070
|
+
const stage = suite.read(context)
|
|
4071
|
+
const faces = new Set(
|
|
4072
|
+
collectDistributionFiles(join(stage.installed, 'dist'))
|
|
4073
|
+
.filter(matchesDistributionDeclaration)
|
|
4074
|
+
.map((path) => readDistributionFace(stage.installed, path))
|
|
4075
|
+
.filter((face) => face !== undefined),
|
|
4076
|
+
)
|
|
4077
|
+
const findings = scanDistributionDeclarations(stage.installed)
|
|
4078
|
+
if (faces.size <= 1)
|
|
4079
|
+
console.info(
|
|
4080
|
+
'Fewer than two declaration faces are installed, so no cross-face copy exists',
|
|
4081
|
+
)
|
|
4082
|
+
return { actual: findings, expected: [] }
|
|
4083
|
+
},
|
|
4084
|
+
},
|
|
4085
|
+
{
|
|
4086
|
+
label: 'keeps the installed manifest fixed under npm pkg fix [requires the registry]',
|
|
4087
|
+
observe: (context) => ({
|
|
4088
|
+
actual: checkDistributionManifest(suite.read(context).installed, suite.scratch),
|
|
4089
|
+
expected: [],
|
|
4090
|
+
}),
|
|
4091
|
+
},
|
|
4092
|
+
{
|
|
4093
|
+
label: 'packs one archive and installs it in isolation [requires the registry]',
|
|
4094
|
+
observe: (context) => {
|
|
4095
|
+
const stage = suite.read(context)
|
|
4096
|
+
return {
|
|
4097
|
+
actual: {
|
|
4098
|
+
archives: stage.archives.length,
|
|
4099
|
+
installed: existsSync(join(stage.installed, 'package.json')),
|
|
4100
|
+
entries: stage.entries.length > 0,
|
|
4101
|
+
},
|
|
4102
|
+
expected: { archives: 1, installed: true, entries: true },
|
|
4103
|
+
}
|
|
4104
|
+
},
|
|
4105
|
+
},
|
|
4106
|
+
{
|
|
4107
|
+
label: 'ships every relative target its exports map names [requires the registry]',
|
|
4108
|
+
observe: (context) => {
|
|
4109
|
+
const stage = suite.read(context)
|
|
4110
|
+
const relative = stage.targets.filter((target) => target.startsWith('./'))
|
|
4111
|
+
return {
|
|
4112
|
+
actual: {
|
|
4113
|
+
populated: relative.length > 0,
|
|
4114
|
+
missing: relative.filter((target) => !existsSync(join(stage.installed, target))),
|
|
4115
|
+
},
|
|
4116
|
+
expected: { populated: true, missing: [] },
|
|
4117
|
+
}
|
|
4118
|
+
},
|
|
4119
|
+
},
|
|
4120
|
+
{
|
|
4121
|
+
label: 'declares types for every module it publishes [requires the registry]',
|
|
4122
|
+
observe: (context) => {
|
|
4123
|
+
const stage = suite.read(context)
|
|
4124
|
+
const partitioned = [
|
|
4125
|
+
...stage.entries.map((entry) => entry.subpath),
|
|
4126
|
+
...stage.undeclared,
|
|
4127
|
+
...stage.excluded,
|
|
4128
|
+
]
|
|
4129
|
+
return {
|
|
4130
|
+
actual: {
|
|
4131
|
+
undeclared: stage.undeclared,
|
|
4132
|
+
partitioned: partitioned.sort(),
|
|
4133
|
+
unreachable: stage.entries
|
|
4134
|
+
.filter((entry) => !entry.importable && !entry.requirable && !entry.browsable)
|
|
4135
|
+
.map((entry) => entry.subpath),
|
|
4136
|
+
untypable: filterDistributionUntypable(stage.entries, stage.installed).map(
|
|
4137
|
+
(entry) => entry.subpath,
|
|
4138
|
+
),
|
|
4139
|
+
},
|
|
4140
|
+
expected: {
|
|
4141
|
+
undeclared: [],
|
|
4142
|
+
partitioned: [...stage.subpaths].sort(),
|
|
4143
|
+
unreachable: [],
|
|
4144
|
+
untypable: [],
|
|
4145
|
+
},
|
|
4146
|
+
}
|
|
4147
|
+
},
|
|
4148
|
+
},
|
|
4149
|
+
{
|
|
4150
|
+
label: 'refuses a subpath its exports map does not name [requires the registry]',
|
|
4151
|
+
observe: (context) => {
|
|
4152
|
+
const stage = suite.read(context)
|
|
4153
|
+
const name = readDistributionManifestName(join(stage.installed, 'package.json'))
|
|
4154
|
+
const result = runDistributionNode(
|
|
4155
|
+
[join(stage.consumer, DISTRIBUTION_ESM_DRIVER), \`\${name}\${DISTRIBUTION_ABSENT_SUBPATH}\`],
|
|
4156
|
+
stage.consumer,
|
|
4157
|
+
)
|
|
4158
|
+
return {
|
|
4159
|
+
actual: {
|
|
4160
|
+
refused: result.status !== 0,
|
|
4161
|
+
coded: readDistributionOutput(result).includes('ERR_PACKAGE_PATH_NOT_EXPORTED'),
|
|
4162
|
+
},
|
|
4163
|
+
expected: { refused: true, coded: true },
|
|
4164
|
+
}
|
|
4165
|
+
},
|
|
4166
|
+
},
|
|
4167
|
+
{
|
|
4168
|
+
label: 'compiles a consumer under every module resolution [requires the registry]',
|
|
4169
|
+
observe: (context) => {
|
|
4170
|
+
const stage = suite.read(context)
|
|
4171
|
+
const name = readDistributionManifestName(join(stage.installed, 'package.json'))
|
|
4172
|
+
const readings = DISTRIBUTION_RESOLUTIONS.flatMap((driver) =>
|
|
4173
|
+
DISTRIBUTION_FORMATS.flatMap(([extension, format]) => {
|
|
4174
|
+
const written = filterDistributionEntries(stage.entries, driver.conditions[format])
|
|
4175
|
+
if (written.length === 0) return []
|
|
4176
|
+
const label = \`\${driver.label}.\${extension}\`
|
|
4177
|
+
const probe = \`probe.\${label}\`
|
|
4178
|
+
const specifiers = written.map((entry) => entry.specifier)
|
|
4179
|
+
writeDistributionConsumerProbe(stage, probe, specifiers)
|
|
4180
|
+
const project = writeDistributionProject(stage, probe, driver, [\`./\${probe}\`])
|
|
4181
|
+
const messages = checkDistributionProject(stage, project).map(
|
|
4182
|
+
(message) => \`\${label}: \${message}\`,
|
|
4183
|
+
)
|
|
4184
|
+
const control = \`control.\${label}\`
|
|
4185
|
+
writeDistributionConsumerProbe(stage, control, [
|
|
4186
|
+
\`\${name}\${DISTRIBUTION_ABSENT_SUBPATH}\`,
|
|
4187
|
+
])
|
|
4188
|
+
const refused = writeDistributionProject(stage, control, driver, [\`./\${control}\`])
|
|
4189
|
+
return [
|
|
4190
|
+
{
|
|
4191
|
+
messages,
|
|
4192
|
+
silent: checkDistributionProject(stage, refused).length === 0 ? [label] : [],
|
|
4193
|
+
},
|
|
4194
|
+
]
|
|
4195
|
+
}),
|
|
4196
|
+
)
|
|
4197
|
+
return {
|
|
4198
|
+
actual: {
|
|
4199
|
+
messages: readings.flatMap(({ messages }) => messages),
|
|
4200
|
+
silent: readings.flatMap(({ silent }) => silent),
|
|
4201
|
+
},
|
|
4202
|
+
expected: { messages: [], silent: [] },
|
|
4203
|
+
}
|
|
4204
|
+
},
|
|
4205
|
+
},
|
|
4206
|
+
{
|
|
4207
|
+
label: 'publishes what it declares to a Node import, and no more',
|
|
4208
|
+
observe: (context) => {
|
|
4209
|
+
const stage = suite.read(context)
|
|
4210
|
+
const entries = stage.entries.filter((entry) => entry.importable)
|
|
4211
|
+
if (entries.length === 0) return context.skip('No installed entry supports Node import')
|
|
4212
|
+
const readings = entries.map((entry) => {
|
|
4213
|
+
if (!entry.declaration.importable)
|
|
4214
|
+
throw new Error(\`\${entry.subpath} publishes no import declaration\`)
|
|
4215
|
+
const published = driveDistributionRuntime(
|
|
4216
|
+
stage,
|
|
4217
|
+
entry.specifier,
|
|
4218
|
+
DISTRIBUTION_ESM_DRIVER,
|
|
4219
|
+
)
|
|
4220
|
+
const drivers = filterDistributionResolutions(entry, 'module')
|
|
4221
|
+
return {
|
|
4222
|
+
entry: entry.subpath,
|
|
4223
|
+
driven: drivers.length > 0,
|
|
4224
|
+
reported: drivers.flatMap((driver) =>
|
|
4225
|
+
checkDistributionSurface(stage, { entry, extension: 'ts', published, driver }),
|
|
4226
|
+
),
|
|
4227
|
+
}
|
|
4228
|
+
})
|
|
4229
|
+
return {
|
|
4230
|
+
actual: readings,
|
|
4231
|
+
expected: entries.map((entry) => ({ entry: entry.subpath, driven: true, reported: [] })),
|
|
4232
|
+
}
|
|
4233
|
+
},
|
|
4234
|
+
},
|
|
4235
|
+
{
|
|
4236
|
+
label: 'publishes what it declares to a Node require, and no more',
|
|
4237
|
+
observe: (context) => {
|
|
4238
|
+
const stage = suite.read(context)
|
|
4239
|
+
const entries = stage.entries.filter((entry) => entry.requirable)
|
|
4240
|
+
if (entries.length === 0) return context.skip('No installed entry supports Node require')
|
|
4241
|
+
const readings = entries.map((entry) => {
|
|
4242
|
+
if (!entry.declaration.requirable)
|
|
4243
|
+
throw new Error(\`\${entry.subpath} publishes no require declaration\`)
|
|
4244
|
+
const published = driveDistributionRuntime(
|
|
4245
|
+
stage,
|
|
4246
|
+
entry.specifier,
|
|
4247
|
+
DISTRIBUTION_CJS_DRIVER,
|
|
4248
|
+
)
|
|
4249
|
+
const drivers = filterDistributionResolutions(entry, 'commonjs')
|
|
4250
|
+
return {
|
|
4251
|
+
entry: entry.subpath,
|
|
4252
|
+
driven: drivers.length > 0,
|
|
4253
|
+
reported: drivers.flatMap((driver) =>
|
|
4254
|
+
checkDistributionSurface(stage, { entry, extension: 'cts', published, driver }),
|
|
4255
|
+
),
|
|
4256
|
+
}
|
|
4257
|
+
})
|
|
4258
|
+
return {
|
|
4259
|
+
actual: readings,
|
|
4260
|
+
expected: entries.map((entry) => ({ entry: entry.subpath, driven: true, reported: [] })),
|
|
4261
|
+
}
|
|
4262
|
+
},
|
|
4263
|
+
},
|
|
4264
|
+
]
|
|
4265
|
+
}
|
|
4266
|
+
`,
|
|
4267
|
+
proof: `import {
|
|
4268
|
+
// These exports remain available to proofs emitted by earlier scaffold releases.
|
|
4269
|
+
buildDistributionCases,
|
|
4270
|
+
{{import}}createDistributionSuite,
|
|
4271
|
+
} from './setupDistribution.js'
|
|
4272
|
+
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
|
|
4273
|
+
|
|
4274
|
+
const DISTRIBUTION_SUITE = createDistributionSuite()
|
|
4275
|
+
|
|
4276
|
+
beforeAll(() => DISTRIBUTION_SUITE.start())
|
|
4277
|
+
|
|
4278
|
+
afterAll(() => DISTRIBUTION_SUITE.destroy())
|
|
4279
|
+
|
|
4280
|
+
describe('installed package consumer', () => {
|
|
4281
|
+
for (const row of buildDistributionCases(DISTRIBUTION_SUITE)) {
|
|
4282
|
+
it(\`\${row.label}\`, async (context) => {
|
|
4283
|
+
const observation = await row.observe(context)
|
|
4284
|
+
expect(observation.actual).toStrictEqual(observation.expected)
|
|
4285
|
+
})
|
|
4286
|
+
}
|
|
4287
|
+
{{guard}}{{drive}}})
|
|
3569
4288
|
`,
|
|
3570
4289
|
transport: `import { createServer } from 'node:http'
|
|
3571
4290
|
`,
|
|
@@ -3576,8 +4295,9 @@ import type { Browser } from 'playwright'
|
|
|
3576
4295
|
import { build } from 'vite'
|
|
3577
4296
|
import { resolveBrowser, resolvePinnedBrowser } from '../configs/browsers.js'
|
|
3578
4297
|
`,
|
|
3579
|
-
|
|
3580
|
-
|
|
4298
|
+
import: "checkDistributionBrowser,\n ",
|
|
4299
|
+
helpers: `/** Defines the page that loads a bundled browser consumer. */
|
|
4300
|
+
export const DISTRIBUTION_BROWSER_PAGE = \`<!doctype html>
|
|
3581
4301
|
<html lang="en">
|
|
3582
4302
|
<head>
|
|
3583
4303
|
<meta charset="UTF-8" />
|
|
@@ -3589,7 +4309,13 @@ const BROWSER_PAGE = \`<!doctype html>
|
|
|
3589
4309
|
</html>
|
|
3590
4310
|
\`
|
|
3591
4311
|
|
|
3592
|
-
|
|
4312
|
+
/**
|
|
4313
|
+
* Infers the response media type from a bundle filename.
|
|
4314
|
+
*
|
|
4315
|
+
* @param path - The requested bundle path.
|
|
4316
|
+
* @returns The media type, defaulting to application/octet-stream.
|
|
4317
|
+
*/
|
|
4318
|
+
export function inferDistributionContentType(path: string): string {
|
|
3593
4319
|
if (path.endsWith('.html')) return 'text/html'
|
|
3594
4320
|
if (path.endsWith('.js')) return 'text/javascript'
|
|
3595
4321
|
if (path.endsWith('.css')) return 'text/css'
|
|
@@ -3600,7 +4326,13 @@ function readContentType(path: string): string {
|
|
|
3600
4326
|
// \`resolveBrowser\` answers with provider options and never reports absence: its
|
|
3601
4327
|
// last resort is a channel nothing verified. So the launch is attempted and its
|
|
3602
4328
|
// rejection classified, rather than probed for and ruled on.
|
|
3603
|
-
|
|
4329
|
+
/**
|
|
4330
|
+
* Renders the browser endpoint selected by provider options.
|
|
4331
|
+
*
|
|
4332
|
+
* @param options - The browser provider options.
|
|
4333
|
+
* @returns The endpoint, executable, channel, or bundled-browser description.
|
|
4334
|
+
*/
|
|
4335
|
+
export function renderDistributionBrowser(options: PlaywrightProviderOptions): string {
|
|
3604
4336
|
const endpoint = options.connectOptions?.wsEndpoint
|
|
3605
4337
|
if (endpoint !== undefined) return \`the browser server at \${endpoint}\`
|
|
3606
4338
|
const executable = options.launchOptions?.executablePath
|
|
@@ -3610,7 +4342,15 @@ function describeBrowser(options: PlaywrightProviderOptions): string {
|
|
|
3610
4342
|
return 'the Chromium Playwright installed for itself'
|
|
3611
4343
|
}
|
|
3612
4344
|
|
|
3613
|
-
|
|
4345
|
+
/**
|
|
4346
|
+
* Launches or connects to the selected Chromium browser.
|
|
4347
|
+
*
|
|
4348
|
+
* @param options - The browser provider options.
|
|
4349
|
+
* @returns The real browser. Rejects when the selected browser is unavailable.
|
|
4350
|
+
*/
|
|
4351
|
+
export async function launchDistributionBrowser(
|
|
4352
|
+
options: PlaywrightProviderOptions,
|
|
4353
|
+
): Promise<Browser> {
|
|
3614
4354
|
const endpoint = options.connectOptions?.wsEndpoint
|
|
3615
4355
|
if (endpoint !== undefined) return chromium.connect(endpoint)
|
|
3616
4356
|
return chromium.launch({ ...options.launchOptions, headless: true })
|
|
@@ -3619,11 +4359,21 @@ async function launchBrowser(options: PlaywrightProviderOptions): Promise<Browse
|
|
|
3619
4359
|
// A consumer of one installed browser entry, bundled by the Vite toolchain this
|
|
3620
4360
|
// workspace already declares. Nothing is stubbed: the bundle resolves the installed
|
|
3621
4361
|
// package and its whole transitive graph as an application consuming it would.
|
|
3622
|
-
|
|
4362
|
+
/**
|
|
4363
|
+
* Bundles a browser consumer against an installed public entry.
|
|
4364
|
+
*
|
|
4365
|
+
* @param stage - The installed consumer.
|
|
4366
|
+
* @param entry - The browser publication.
|
|
4367
|
+
* @returns The bundle directory. Rejects when Vite cannot build the consumer.
|
|
4368
|
+
*/
|
|
4369
|
+
export async function bundleDistributionEntry(
|
|
4370
|
+
stage: DistributionStage,
|
|
4371
|
+
entry: DistributionEntry,
|
|
4372
|
+
): Promise<string> {
|
|
3623
4373
|
const page = join(stage.consumer, 'pages', entry.subpath.replaceAll(/[^\\w]+/gu, '-'))
|
|
3624
4374
|
const specifier = JSON.stringify(entry.specifier)
|
|
3625
|
-
|
|
3626
|
-
|
|
4375
|
+
writeDistributionFile(join(page, 'index.html'), DISTRIBUTION_BROWSER_PAGE)
|
|
4376
|
+
writeDistributionFile(
|
|
3627
4377
|
join(page, 'main.js'),
|
|
3628
4378
|
\`import * as entry from \${specifier}\\nglobalThis.subject = Object.keys(entry).sort()\\n\`,
|
|
3629
4379
|
)
|
|
@@ -3640,7 +4390,17 @@ async function bundleEntry(stage: Stage, entry: Entry): Promise<string> {
|
|
|
3640
4390
|
// The key set the bundled module publishes in a real browser, read off the page
|
|
3641
4391
|
// once it has loaded over a loopback server. A module that never evaluated
|
|
3642
4392
|
// publishes nothing, and a page error is raised rather than compared away.
|
|
3643
|
-
|
|
4393
|
+
/**
|
|
4394
|
+
* Reads the names a real browser publishes from a served bundle.
|
|
4395
|
+
*
|
|
4396
|
+
* @param browser - The browser to drive.
|
|
4397
|
+
* @param bundle - The built consumer directory.
|
|
4398
|
+
* @returns The exported names. Rejects on a page error or an invalid name list.
|
|
4399
|
+
*/
|
|
4400
|
+
export async function readDistributionBrowserExports(
|
|
4401
|
+
browser: Browser,
|
|
4402
|
+
bundle: string,
|
|
4403
|
+
): Promise<readonly string[]> {
|
|
3644
4404
|
const server = createServer((request, response) => {
|
|
3645
4405
|
const asked = request.url === undefined || request.url === '/' ? '/index.html' : request.url
|
|
3646
4406
|
const path = join(bundle, decodeURIComponent(asked))
|
|
@@ -3649,7 +4409,7 @@ async function readBrowserExports(browser: Browser, bundle: string): Promise<rea
|
|
|
3649
4409
|
response.end()
|
|
3650
4410
|
return
|
|
3651
4411
|
}
|
|
3652
|
-
response.writeHead(200, { 'content-type':
|
|
4412
|
+
response.writeHead(200, { 'content-type': inferDistributionContentType(path) })
|
|
3653
4413
|
response.end(readFileSync(path))
|
|
3654
4414
|
})
|
|
3655
4415
|
try {
|
|
@@ -3666,63 +4426,65 @@ async function readBrowserExports(browser: Browser, bundle: string): Promise<rea
|
|
|
3666
4426
|
await page.goto(\`http://127.0.0.1:\${String(address.port)}/\`, { waitUntil: 'load' })
|
|
3667
4427
|
const published: unknown = await page.evaluate('globalThis.subject')
|
|
3668
4428
|
if (failures.length > 0) throw new Error(\`The bundle raised \${failures.join(' | ')}\`)
|
|
3669
|
-
if (!
|
|
4429
|
+
if (!isDistributionNames(published))
|
|
4430
|
+
throw new Error('The bundled module published no name list')
|
|
3670
4431
|
return published
|
|
3671
4432
|
} finally {
|
|
3672
4433
|
server.close()
|
|
3673
4434
|
}
|
|
3674
4435
|
}
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
4436
|
+
/**
|
|
4437
|
+
* Checks each browser publication against its installed declarations in a real browser.
|
|
4438
|
+
* @param stage - The installed consumer to drive.
|
|
4439
|
+
* @param context - The proof context that reports unavailable browser prerequisites.
|
|
4440
|
+
* @returns The compiler diagnostics each browser publication reports.
|
|
4441
|
+
*/
|
|
4442
|
+
export async function checkDistributionBrowser(
|
|
4443
|
+
stage: DistributionStage,
|
|
4444
|
+
context: TestContext,
|
|
4445
|
+
): Promise<readonly string[]> {
|
|
4446
|
+
const driver = DISTRIBUTION_BROWSER_RESOLUTION
|
|
4447
|
+
if (driver === undefined) throw new Error("DISTRIBUTION_RESOLUTIONS carries no 'bundler' row")
|
|
4448
|
+
const entries = stage.entries.filter((entry) => entry.browsable)
|
|
4449
|
+
if (entries.length === 0) return context.skip('No installed entry supports a browser')
|
|
4450
|
+
const readings: Array<readonly string[]> = []
|
|
4451
|
+
for (const entry of entries) {
|
|
4452
|
+
if (!entry.declaration.browsable) {
|
|
4453
|
+
throw new Error(\`\${entry.subpath} publishes no browser declaration\`)
|
|
4454
|
+
}
|
|
4455
|
+
const options = resolveBrowser(resolvePinnedBrowser(), process.platform, process.env)
|
|
4456
|
+
const browser = await launchDistributionBrowser(options).catch((error: unknown) => {
|
|
4457
|
+
const cause = \`\${renderDistributionBrowser(options)} was rejected: \${String(error)}\`
|
|
4458
|
+
if (DISTRIBUTION_RELEASE) throw new Error(\`The release gate requires a browser, and \${cause}\`)
|
|
4459
|
+
return context.skip(\`No browser launched. \${cause}\`)
|
|
4460
|
+
})
|
|
4461
|
+
try {
|
|
4462
|
+
const bundle = await bundleDistributionEntry(stage, entry)
|
|
4463
|
+
const published = await readDistributionBrowserExports(browser, bundle)
|
|
4464
|
+
// A browser consumer reads the installed declarations through a bundler, so
|
|
4465
|
+
// that is the one driver this face answers under.
|
|
4466
|
+
const reported = checkDistributionSurface(stage, {
|
|
4467
|
+
entry,
|
|
4468
|
+
extension: 'ts',
|
|
4469
|
+
published,
|
|
4470
|
+
driver,
|
|
3690
4471
|
})
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
// A browser consumer reads the installed declarations through a bundler, so
|
|
3695
|
-
// that is the one driver this face answers under.
|
|
3696
|
-
const reported = checkSurface(stage, {
|
|
3697
|
-
entry,
|
|
3698
|
-
extension: 'ts',
|
|
3699
|
-
published,
|
|
3700
|
-
driver: BROWSER_DRIVER,
|
|
3701
|
-
})
|
|
3702
|
-
expect(reported).toStrictEqual([])
|
|
3703
|
-
} finally {
|
|
3704
|
-
await browser.close()
|
|
3705
|
-
}
|
|
4472
|
+
readings.push(reported)
|
|
4473
|
+
} finally {
|
|
4474
|
+
await browser.close()
|
|
3706
4475
|
}
|
|
4476
|
+
}
|
|
4477
|
+
return readings.flat()
|
|
4478
|
+
}
|
|
4479
|
+
`,
|
|
4480
|
+
drive: ` it('publishes what it declares to a real browser [requires a browser]', async (context) => {
|
|
4481
|
+
expect(await checkDistributionBrowser(DISTRIBUTION_SUITE.read(context), context)).toStrictEqual(
|
|
4482
|
+
[],
|
|
4483
|
+
)
|
|
3707
4484
|
})
|
|
3708
4485
|
`,
|
|
3709
|
-
guard: `
|
|
3710
|
-
|
|
3711
|
-
// branch: the workspace published no browser face when it was written, and the
|
|
3712
|
-
// browser drive measures the packed artifact, so only a published face is owed
|
|
3713
|
-
// one. A private browser application does not select this branch. It declares the
|
|
3714
|
-
// browser launcher and its Vitest browser provider and gets the generated browser
|
|
3715
|
-
// configuration module beside it, but installed browser tooling does not stand for
|
|
3716
|
-
// a published browser face. \`vite\` selects nothing either, though the branch
|
|
3717
|
-
// imports it: scaffold puts \`vite\` in every workspace's base development
|
|
3718
|
-
// dependencies, whatever that workspace publishes. The later Node
|
|
3719
|
-
// entry filters retire each matching Node drive for a face published
|
|
3720
|
-
// later, which leaves nothing measuring it. So it reddens here and names the
|
|
3721
|
-
// subpath a browser branch is owed for. A workspace that gains one deletes this
|
|
3722
|
-
// file and runs the \`repair\` verb, which writes the variant carrying that branch.
|
|
3723
|
-
it('publishes no browser face this proof cannot drive [requires the registry]', (context) => {
|
|
3724
|
-
const stage = requireStage(context)
|
|
3725
|
-
const faces = stage.entries.filter((entry) => entry.browsable)
|
|
4486
|
+
guard: ` it('publishes no browser face this proof cannot drive [requires the registry]', (context) => {
|
|
4487
|
+
const faces = DISTRIBUTION_SUITE.read(context).entries.filter((entry) => entry.browsable)
|
|
3726
4488
|
expect(faces.map((entry) => entry.subpath)).toStrictEqual([])
|
|
3727
4489
|
})
|
|
3728
4490
|
`
|
|
@@ -3972,7 +4734,7 @@ var isContent = stringOf({ max: MAX_ARTIFACT_BYTES });
|
|
|
3972
4734
|
*
|
|
3973
4735
|
* @remarks
|
|
3974
4736
|
* Compose this ahead of an element guard so the item count is settled before
|
|
3975
|
-
* anything walks the items. A
|
|
4737
|
+
* anything walks the items. A throwing `length` accessor answers `false` here
|
|
3976
4738
|
* rather than escaping as a thrown error.
|
|
3977
4739
|
*
|
|
3978
4740
|
* @example
|
|
@@ -4223,6 +4985,7 @@ var isBlueprint = recordOf({
|
|
|
4223
4985
|
guides: isBoolean,
|
|
4224
4986
|
integration: isBoolean,
|
|
4225
4987
|
conformance: isBoolean,
|
|
4988
|
+
tree: isBoolean,
|
|
4226
4989
|
service: isBoolean,
|
|
4227
4990
|
vendors: andOf(isCollection, arrayOf(isString)),
|
|
4228
4991
|
studies: isBoolean,
|
|
@@ -4404,7 +5167,7 @@ var isCatalogEntry = unionOf(recordOf({
|
|
|
4404
5167
|
* whose every value is exact lowercase hexadecimal bytes; false otherwise.
|
|
4405
5168
|
*
|
|
4406
5169
|
* @remarks
|
|
4407
|
-
* Read through the shared total key lens, so
|
|
5170
|
+
* Read through the shared total key lens, so an `ownKeys` trap that throws and a
|
|
4408
5171
|
* throwing accessor both answer `false` rather than escaping. There is no
|
|
4409
5172
|
* dictionary combinator upstream to compose this from: the key law and the
|
|
4410
5173
|
* value law are both this package's own.
|
|
@@ -6125,10 +6888,11 @@ function blueprintToScripts(blueprint) {
|
|
|
6125
6888
|
"npm run test:policy",
|
|
6126
6889
|
"npm run test:config",
|
|
6127
6890
|
...blueprint.setup.includes("node") || blueprint.global ? ["npm run test:setup"] : [],
|
|
6128
|
-
...blueprint.setup.includes("browser")
|
|
6891
|
+
...blueprint.setup.includes("browser") ? ["npm run test:setup:browser"] : [],
|
|
6129
6892
|
...blueprint.skills ? ["npm run test:skills"] : [],
|
|
6130
6893
|
...blueprint.guides ? ["npm run test:guides"] : [],
|
|
6131
6894
|
...blueprint.conformance ? ["npm run test:conformance"] : [],
|
|
6895
|
+
...blueprint.tree ? ["npm run test:tree"] : [],
|
|
6132
6896
|
...integrates ? ["npm run test:integration"] : [],
|
|
6133
6897
|
...!publishes && blueprint.studies ? ["npm run test:studies"] : [],
|
|
6134
6898
|
...!publishes && blueprint.service ? ["npm run test:service"] : []
|
|
@@ -6157,7 +6921,7 @@ function blueprintToScripts(blueprint) {
|
|
|
6157
6921
|
"test:policy": `${vitest} --project policy`,
|
|
6158
6922
|
"test:config": `${vitest} --project config`,
|
|
6159
6923
|
...blueprint.setup.includes("node") || blueprint.global ? { "test:setup": `${vitest} --project setup` } : {},
|
|
6160
|
-
...blueprint.setup.includes("browser")
|
|
6924
|
+
...blueprint.setup.includes("browser") ? { "test:setup:browser": `${vitest} --project setup:browser` } : {},
|
|
6161
6925
|
...blueprint.journey && blueprint.app.includes("browser") ? {
|
|
6162
6926
|
"test:journey": journey,
|
|
6163
6927
|
...Object.fromEntries(application.map((name) => [`test:journey:${name}`, `${journey} --mode ${name}`]))
|
|
@@ -6165,6 +6929,7 @@ function blueprintToScripts(blueprint) {
|
|
|
6165
6929
|
...blueprint.skills ? { "test:skills": `${vitest} --project skills` } : {},
|
|
6166
6930
|
...blueprint.guides ? { "test:guides": `node --experimental-strip-types ${GUIDES_TEST_PATH}` } : {},
|
|
6167
6931
|
...blueprint.conformance ? { "test:conformance": `${vitest} --project conformance` } : {},
|
|
6932
|
+
...blueprint.tree ? { "test:tree": `${vitest} --project tree` } : {},
|
|
6168
6933
|
"test:probe": "vitest run --config vite.config.ts --no-cache --reporter=verbose --project probe",
|
|
6169
6934
|
"test:bench": "vitest bench --config vite.config.ts --no-cache --project probe",
|
|
6170
6935
|
...publishes ? { "test:distribution": `${vitest} --project distribution` } : {},
|
|
@@ -6350,7 +7115,7 @@ function blueprintToManifest(blueprint) {
|
|
|
6350
7115
|
type: "git",
|
|
6351
7116
|
url: `git+https://github.com/orkestrel/${blueprint.name}.git`
|
|
6352
7117
|
} } : {},
|
|
6353
|
-
...blueprint.bin ? { bin: { [blueprint.name]:
|
|
7118
|
+
...blueprint.bin ? { bin: { [blueprint.name]: BIN_OUTPUT_PATH } } : {},
|
|
6354
7119
|
files: [
|
|
6355
7120
|
blueprint.bin || publishes ? "dist/src" : "dist/app",
|
|
6356
7121
|
...blueprint.bin ? ["dist/bin"] : [],
|
|
@@ -6361,7 +7126,7 @@ function blueprintToManifest(blueprint) {
|
|
|
6361
7126
|
],
|
|
6362
7127
|
type: "module",
|
|
6363
7128
|
...publishes ? {
|
|
6364
|
-
sideEffects: targets.length > 0 || blueprint.bin ? [...targets.length > 0 ? ["**/*.css", "**/*.scss"] : [], ...blueprint.bin ? [`./${BIN_ENTRY_PATH}`,
|
|
7129
|
+
sideEffects: targets.length > 0 || blueprint.bin ? [...targets.length > 0 ? ["**/*.css", "**/*.scss"] : [], ...blueprint.bin ? [`./${BIN_ENTRY_PATH}`, `./${BIN_OUTPUT_PATH}`] : []] : false,
|
|
6365
7130
|
...blueprint.src.length > 0 ? entry : {},
|
|
6366
7131
|
exports: blueprintToExports(blueprint),
|
|
6367
7132
|
publishConfig: { access: "public" }
|
|
@@ -6512,10 +7277,11 @@ function blueprintToProjects(blueprint) {
|
|
|
6512
7277
|
"policy",
|
|
6513
7278
|
"config",
|
|
6514
7279
|
...blueprint.setup.includes("node") || blueprint.global ? ["setup"] : [],
|
|
6515
|
-
...blueprint.setup.includes("browser")
|
|
7280
|
+
...blueprint.setup.includes("browser") ? ["setup:browser"] : [],
|
|
6516
7281
|
...blueprint.guides ? ["guides"] : [],
|
|
6517
7282
|
...blueprint.skills ? ["skills"] : [],
|
|
6518
7283
|
...blueprint.conformance ? ["conformance"] : [],
|
|
7284
|
+
...blueprint.tree ? ["tree"] : [],
|
|
6519
7285
|
...blueprint.service ? ["service"] : [],
|
|
6520
7286
|
...blueprint.studies ? ["studies"] : [],
|
|
6521
7287
|
...blueprint.src.length > 0 || styled ? ["distribution"] : [],
|
|
@@ -6581,13 +7347,10 @@ function blueprintToRootVite(blueprint) {
|
|
|
6581
7347
|
const styled = sheets.length > 0 || blueprint.themes;
|
|
6582
7348
|
const global = blueprint.global ? " globalSetup: ['./tests/setupGlobal.ts'],\n" : "";
|
|
6583
7349
|
const imports = [
|
|
7350
|
+
...styled || machinery.showcase ? [`import { ${[...styled ? ["existsSync"] : [], ...machinery.showcase ? ["renameSync"] : []].join(", ")} } from 'node:fs'`] : [],
|
|
6584
7351
|
...machinery.browser ? ["import { playwright } from '@vitest/browser-playwright'"] : [],
|
|
6585
7352
|
...machinery.frameworks.includes("vue") ? ["import vue from '@vitejs/plugin-vue'"] : [],
|
|
6586
|
-
...machinery.showcase ? [
|
|
6587
|
-
"import { viteSingleFile } from 'vite-plugin-singlefile'",
|
|
6588
|
-
"import { renameSync } from 'node:fs'",
|
|
6589
|
-
"import { resolve as resolvePath } from 'node:path'"
|
|
6590
|
-
] : []
|
|
7353
|
+
...machinery.showcase ? ["import { viteSingleFile } from 'vite-plugin-singlefile'", "import { resolve as resolvePath } from 'node:path'"] : []
|
|
6591
7354
|
];
|
|
6592
7355
|
const external = "external: resolveSourceExternal,";
|
|
6593
7356
|
const factories = [
|
|
@@ -6650,6 +7413,7 @@ function blueprintToRootVite(blueprint) {
|
|
|
6650
7413
|
...planned.has("guides") ? [CONFIG_TEMPLATES.factories.guides] : [],
|
|
6651
7414
|
...planned.has("skills") ? [CONFIG_TEMPLATES.factories.skills] : [],
|
|
6652
7415
|
...planned.has("conformance") ? [CONFIG_TEMPLATES.factories.conformance] : [],
|
|
7416
|
+
...planned.has("tree") ? [CONFIG_TEMPLATES.factories.tree] : [],
|
|
6653
7417
|
...planned.has("service") ? [CONFIG_TEMPLATES.factories.service] : [],
|
|
6654
7418
|
...planned.has("studies") ? [CONFIG_TEMPLATES.factories.studies] : [],
|
|
6655
7419
|
...planned.has("distribution") ? [CONFIG_TEMPLATES.factories.distribution] : [],
|
|
@@ -6674,6 +7438,7 @@ function blueprintToRootVite(blueprint) {
|
|
|
6674
7438
|
...planned.has("guides") ? ["guides"] : [],
|
|
6675
7439
|
...planned.has("skills") ? ["skills"] : [],
|
|
6676
7440
|
...planned.has("conformance") ? ["conformance"] : [],
|
|
7441
|
+
...planned.has("tree") ? ["tree"] : [],
|
|
6677
7442
|
...planned.has("service") ? ["service"] : [],
|
|
6678
7443
|
...planned.has("studies") ? ["studies"] : [],
|
|
6679
7444
|
...planned.has("distribution") ? ["distribution"] : [],
|
|
@@ -7039,12 +7804,12 @@ function blueprintToSourceArtifacts(blueprint) {
|
|
|
7039
7804
|
* path. `studies` emits nothing: the root configuration names no study harness,
|
|
7040
7805
|
* so `tests/setupStudy.ts` is the package's own module.
|
|
7041
7806
|
*
|
|
7042
|
-
* The distribution
|
|
7043
|
-
*
|
|
7044
|
-
* knows
|
|
7045
|
-
* nothing has to be named.
|
|
7046
|
-
*
|
|
7047
|
-
*
|
|
7807
|
+
* The content-owned distribution harness `tests/setupDistribution.ts` and the presence-owned
|
|
7808
|
+
* proof `tests/distribution.test.ts` are emitted because the proof measures the packed tarball
|
|
7809
|
+
* rather than anything only the package knows. Every assertion derives from the installed tree
|
|
7810
|
+
* at run time, so nothing has to be named. The proof follows the published source it packs and
|
|
7811
|
+
* alone is claimed by presence: a target lacking the proof reports as drift, and a package that
|
|
7812
|
+
* replaced the proof with a better one keeps that proof.
|
|
7048
7813
|
*
|
|
7049
7814
|
* @example
|
|
7050
7815
|
* ```ts
|
|
@@ -7090,8 +7855,7 @@ function blueprintToTestArtifacts(blueprint) {
|
|
|
7090
7855
|
origin: "template",
|
|
7091
7856
|
content: fillTemplate(ARTIFACT_TEMPLATES.tests.sheet, {
|
|
7092
7857
|
name,
|
|
7093
|
-
root: `${tests}
|
|
7094
|
-
setup: `${tests}setupStyles.js`
|
|
7858
|
+
root: `${tests}../`
|
|
7095
7859
|
})
|
|
7096
7860
|
};
|
|
7097
7861
|
});
|
|
@@ -7103,7 +7867,7 @@ function blueprintToTestArtifacts(blueprint) {
|
|
|
7103
7867
|
environment: "browser",
|
|
7104
7868
|
content: blueprint.journey ? ARTIFACT_TEMPLATES.tests.browser : ARTIFACT_TEMPLATES.tests.setup
|
|
7105
7869
|
}] : [];
|
|
7106
|
-
const servers = blueprint.src.includes("server") || blueprint.app.includes("server") || blueprint.bin || blueprint.conformance ? [{
|
|
7870
|
+
const servers = blueprint.src.includes("server") || blueprint.app.includes("server") || blueprint.bin || blueprint.conformance || blueprint.tree ? [{
|
|
7107
7871
|
path: "tests/setupServer.ts",
|
|
7108
7872
|
group: "tests",
|
|
7109
7873
|
ownership: "birth",
|
|
@@ -7178,20 +7942,28 @@ function blueprintToTestArtifacts(blueprint) {
|
|
|
7178
7942
|
})
|
|
7179
7943
|
})).toArray();
|
|
7180
7944
|
const distribution = ARTIFACT_TEMPLATES.tests.distribution;
|
|
7181
|
-
const distributions = blueprint.src.length > 0 || blueprintToSheets(blueprint).length > 0 || blueprint.themes ? [blueprint.src.includes("browser")].
|
|
7182
|
-
path:
|
|
7945
|
+
const distributions = blueprint.src.length > 0 || blueprintToSheets(blueprint).length > 0 || blueprint.themes ? [blueprint.src.includes("browser")].flatMap((browser) => [{
|
|
7946
|
+
path: DISTRIBUTION_SETUP_PATH,
|
|
7183
7947
|
group: "tests",
|
|
7184
|
-
ownership: "
|
|
7948
|
+
ownership: "content",
|
|
7185
7949
|
origin: "template",
|
|
7186
|
-
content: fillTemplate(distribution.
|
|
7950
|
+
content: fillTemplate(distribution.module, {
|
|
7187
7951
|
types: browser ? distribution.types : "",
|
|
7188
7952
|
transport: browser ? distribution.transport : "",
|
|
7189
7953
|
launcher: browser ? distribution.launcher : "",
|
|
7190
|
-
helpers: browser ? distribution.helpers : ""
|
|
7954
|
+
helpers: browser ? distribution.helpers : ""
|
|
7955
|
+
})
|
|
7956
|
+
}, {
|
|
7957
|
+
path: DISTRIBUTION_TEST_PATH,
|
|
7958
|
+
group: "tests",
|
|
7959
|
+
ownership: "presence",
|
|
7960
|
+
origin: "template",
|
|
7961
|
+
content: fillTemplate(distribution.proof, {
|
|
7962
|
+
import: browser ? distribution.import : "",
|
|
7191
7963
|
drive: browser ? distribution.drive : "",
|
|
7192
7964
|
guard: browser ? "" : distribution.guard
|
|
7193
7965
|
})
|
|
7194
|
-
})
|
|
7966
|
+
}]) : [];
|
|
7195
7967
|
const integrations = blueprint.integration ? [[...Iterator.from(blueprint.src).map((environment) => ({
|
|
7196
7968
|
entry: environment === "core" ? "srcCore" : environment === "browser" ? "srcBrowser" : "srcServer",
|
|
7197
7969
|
specifier: `@src/${environment}`
|
|
@@ -7226,7 +7998,7 @@ function blueprintToTestArtifacts(blueprint) {
|
|
|
7226
7998
|
content: ARTIFACT_TEMPLATES.tests.setup
|
|
7227
7999
|
},
|
|
7228
8000
|
...faces,
|
|
7229
|
-
...styles,
|
|
8001
|
+
...styles.filter(({ content }) => content.length > 0),
|
|
7230
8002
|
...sheetTests,
|
|
7231
8003
|
...browsers,
|
|
7232
8004
|
...servers,
|
|
@@ -8384,13 +9156,14 @@ function createBlueprint(name, input) {
|
|
|
8384
9156
|
peers: input?.peers ?? [],
|
|
8385
9157
|
extras: input?.extras ?? [],
|
|
8386
9158
|
version: input?.version ?? "0.0.1",
|
|
8387
|
-
engines: input?.engines ??
|
|
9159
|
+
engines: input?.engines ?? ">=22.18.0",
|
|
8388
9160
|
overrides: input?.overrides ?? [],
|
|
8389
9161
|
bin: input?.bin ?? false,
|
|
8390
9162
|
setup: input?.setup ?? [],
|
|
8391
9163
|
guides: input?.guides ?? false,
|
|
8392
9164
|
integration: input?.integration ?? false,
|
|
8393
9165
|
conformance: input?.conformance ?? false,
|
|
9166
|
+
tree: input?.tree ?? false,
|
|
8394
9167
|
service: input?.service ?? false,
|
|
8395
9168
|
vendors: input?.vendors ?? [],
|
|
8396
9169
|
studies: input?.studies ?? false,
|
|
@@ -8704,6 +9477,6 @@ var Compiler = class {
|
|
|
8704
9477
|
}
|
|
8705
9478
|
};
|
|
8706
9479
|
//#endregion
|
|
8707
|
-
export { APP_BROWSER_DEV_DEPENDENCIES, APP_DEV_DEPENDENCIES, APP_MATRIX, APP_SERVER_DEV_DEPENDENCIES, ARTIFACT_TEMPLATES, AXES, BASE_DEV_DEPENDENCIES, BIN_CONFIGS, BIN_ENTRY_PATH, BROWSE_DEV_DEPENDENCIES, BROWSE_UPSTREAM, CANON_PATHS, CATALOG_AGENT_PATH, CATALOG_CLOSING_MARKER, CATALOG_OPENING_MARKER, CONFIG_TEMPLATES, CONFORMANCE_TEST_PATH, CONTROL_CHARACTER_PATTERN, Compiler, DECLARATION_DEV_DEPENDENCIES, DEFAULT_ENGINES, DEFAULT_VERSION, DEPENDENCY_NAME_PATTERN, DISTRIBUTION_TEST_PATH, ENGINES_PATTERN, ENVIRONMENTS, EXECUTABLE_PATHS, EXTRA_RANGE_PATTERN, FLOOR_RANGE_PATTERN, FOREIGN_NAME_PATTERN, FRAMEWORKS, FRAMEWORK_MATRIX, GLOBAL_SETUP_PATH, GROUPS, GUIDES_TEST_PATH, HEX_PATTERN, HOST_INVENTORY_PATH, HOST_PATHS, INTEGRATION_TEST_PATH, INVALID_PATH_CHARACTER_PATTERN, JOURNEY_CONFIG_PATH, MANIFEST_PATH, MAX_ARTIFACT_BYTES, MAX_ARTIFACT_HEX_LENGTH, MAX_AUDIT_FINDINGS, MAX_COLLECTION_ITEMS, MAX_DEPENDENCY_NAME_LENGTH, MAX_MANIFEST_BYTES, MAX_NAME_LENGTH, MAX_PATH_LENGTH, MAX_RANGE_LENGTH, MAX_REGISTRY_BYTES, MAX_SCRIPT_LENGTH, MAX_TOTAL_ARTIFACT_BYTES, MAX_TOTAL_REGISTRY_BYTES, MINIMUM_NODE_VERSION, MINIMUM_NPM_VERSION, NAME_PATTERN, ORCHESTRATION_PATH_NAMES, ORCHESTRATION_PATH_PREFIXES, ORKESTREL_RANGE_PATTERN, PRINT_WIDTH, REFERENCE_PATHS, RELEASE_PROOF_COMMAND, RESERVED_SHEET_NAMES, SEED_GUIDE_PATHS, SERVICE_SCRIPT_PATH, SERVICE_SETUP_PATH, SERVICE_TEST_INCLUDE, SHEET_ENTRY_NAME, SHOWCASE_CONFIG_PATH, SHOWCASE_DEV_DEPENDENCIES, SHOWCASE_OVERRIDES, SHOWCASE_PAGES_PATH, SKILLS_CONFIG_PATH, SOURCE_BROWSER_DEV_DEPENDENCIES, SRC_MATRIX, STUDIES_TEST_INCLUDE, STYLES_DEV_DEPENDENCIES, STYLES_ENTRY_PATH, SURFACES, ScaffoldError, TAB_WIDTH, TARGET_SKILL_NAMES, THEMES_BARREL_PATH, THEMES_ENTRY_PATH, VERSION_PATTERN, WORKSPACE_DEV_ENGINES, WORKSPACE_OWNED_PATHS, applyOverrides, artifactToFinding, artifactToHex, artifactsToQuestions, blueprintToConfigArtifacts, blueprintToDevDependencies, blueprintToDocumentArtifacts, blueprintToExports, blueprintToFaces, blueprintToGuideArtifacts, blueprintToHostArtifacts, blueprintToMachinery, blueprintToManifest, blueprintToManifestOverrides, blueprintToOptimizedDependencies, blueprintToOrchestrationArtifacts, blueprintToProjects, blueprintToQuestions, blueprintToRootTsconfig, blueprintToRootVite, blueprintToScripts, blueprintToSheets, blueprintToSourceArtifacts, blueprintToTestArtifacts, blueprintToWritableScripts, bytesToHex, catalogToLayers, cloneValue, compareVersions, computeBytes, computeHash, computeTotal, contentToHex, createBlueprint, dependenciesToQuestions, extractRangeMajor, extractVersion, inferDrift, inferGroup, insertManifestDependencies, insertManifestSection, isArtifact, isAudit, isBlueprint, isBrowserExtension, isCanonPath, isCatalogEntry, isCollection, isCompilerHooks, isCompilerOptions, isContent, isDeferredPath, isDependency, isDependencyName, isEnvironment, isExtension, isFinding, isFloorPath, isGroup, isGroups, isHex, isManifestScript, isMirror, isOverride, isPath, isPlan, isQuestion, isRetainedPath, isScaffoldError, isSheetName, isSnapshot, isStylesExtension, isSurface, iterateAnswers, manifestToDependencies, manifestToName, matchesDriftReachability, matchesEngines, matchesOrchestrationPath, matchesPrintWidth, matchesRange, nameToGuide, overridesToQuestions, parseBlueprint, parseCompilerOptions, parseExtension, parseGroups, parseSnapshot, pathToCondition, planToFindings, planToHash, planToSummary, renderSkillPointer, replaceManifestOverride, replaceManifestOverrides, replaceManifestRanges, replaceManifestScripts, replacePlanRanges, scanManifestTokens, selectGroups, selectHostPaths, serializeTypeScriptString, srcToEntry, srcToExports, srcToRoot };
|
|
9480
|
+
export { APP_BROWSER_DEV_DEPENDENCIES, APP_DEV_DEPENDENCIES, APP_MATRIX, APP_SERVER_DEV_DEPENDENCIES, ARTIFACT_TEMPLATES, AXES, BASE_DEV_DEPENDENCIES, BIN_CONFIGS, BIN_ENTRY_PATH, BIN_OUTPUT_PATH, BROWSE_DEV_DEPENDENCIES, BROWSE_UPSTREAM, CANON_PATHS, CATALOG_AGENT_PATH, CATALOG_CLOSING_MARKER, CATALOG_OPENING_MARKER, CONFIG_TEMPLATES, CONFORMANCE_TEST_PATH, CONTROL_CHARACTER_PATTERN, Compiler, DECLARATION_DEV_DEPENDENCIES, DEFAULT_ENGINES, DEFAULT_VERSION, DEPENDENCY_NAME_PATTERN, DISTRIBUTION_SETUP_PATH, DISTRIBUTION_TEST_PATH, ENGINES_PATTERN, ENVIRONMENTS, EXECUTABLE_PATHS, EXTRA_RANGE_PATTERN, FLOOR_RANGE_PATTERN, FOREIGN_NAME_PATTERN, FRAMEWORKS, FRAMEWORK_MATRIX, GLOBAL_SETUP_PATH, GROUPS, GUIDES_TEST_PATH, HEX_PATTERN, HOST_INVENTORY_PATH, HOST_PATHS, INTEGRATION_TEST_PATH, INVALID_PATH_CHARACTER_PATTERN, JOURNEY_CONFIG_PATH, MANIFEST_PATH, MAX_ARTIFACT_BYTES, MAX_ARTIFACT_HEX_LENGTH, MAX_AUDIT_FINDINGS, MAX_COLLECTION_ITEMS, MAX_DEPENDENCY_NAME_LENGTH, MAX_MANIFEST_BYTES, MAX_NAME_LENGTH, MAX_PATH_LENGTH, MAX_RANGE_LENGTH, MAX_REGISTRY_BYTES, MAX_SCRIPT_LENGTH, MAX_TOTAL_ARTIFACT_BYTES, MAX_TOTAL_REGISTRY_BYTES, MINIMUM_NODE_VERSION, MINIMUM_NPM_VERSION, NAME_PATTERN, ORCHESTRATION_PATH_NAMES, ORCHESTRATION_PATH_PREFIXES, ORKESTREL_RANGE_PATTERN, PRINT_WIDTH, REFERENCE_PATHS, RELEASE_PROOF_COMMAND, RESERVED_SHEET_NAMES, SEED_GUIDE_PATHS, SERVICE_SCRIPT_PATH, SERVICE_SETUP_PATH, SERVICE_TEST_INCLUDE, SHEET_ENTRY_NAME, SHOWCASE_CONFIG_PATH, SHOWCASE_DEV_DEPENDENCIES, SHOWCASE_OVERRIDES, SHOWCASE_PAGES_PATH, SKILLS_CONFIG_PATH, SOURCE_BROWSER_DEV_DEPENDENCIES, SRC_MATRIX, STUDIES_TEST_INCLUDE, STYLES_DEV_DEPENDENCIES, STYLES_ENTRY_PATH, SURFACES, ScaffoldError, TAB_WIDTH, TARGET_SKILL_NAMES, THEMES_BARREL_PATH, THEMES_ENTRY_PATH, TREE_TEST_PATH, VERSION_PATTERN, WORKSPACE_DEV_ENGINES, WORKSPACE_OWNED_PATHS, applyOverrides, artifactToFinding, artifactToHex, artifactsToQuestions, blueprintToConfigArtifacts, blueprintToDevDependencies, blueprintToDocumentArtifacts, blueprintToExports, blueprintToFaces, blueprintToGuideArtifacts, blueprintToHostArtifacts, blueprintToMachinery, blueprintToManifest, blueprintToManifestOverrides, blueprintToOptimizedDependencies, blueprintToOrchestrationArtifacts, blueprintToProjects, blueprintToQuestions, blueprintToRootTsconfig, blueprintToRootVite, blueprintToScripts, blueprintToSheets, blueprintToSourceArtifacts, blueprintToTestArtifacts, blueprintToWritableScripts, bytesToHex, catalogToLayers, cloneValue, compareVersions, computeBytes, computeHash, computeTotal, contentToHex, createBlueprint, dependenciesToQuestions, extractRangeMajor, extractVersion, inferDrift, inferGroup, insertManifestDependencies, insertManifestSection, isArtifact, isAudit, isBlueprint, isBrowserExtension, isCanonPath, isCatalogEntry, isCollection, isCompilerHooks, isCompilerOptions, isContent, isDeferredPath, isDependency, isDependencyName, isEnvironment, isExtension, isFinding, isFloorPath, isGroup, isGroups, isHex, isManifestScript, isMirror, isOverride, isPath, isPlan, isQuestion, isRetainedPath, isScaffoldError, isSheetName, isSnapshot, isStylesExtension, isSurface, iterateAnswers, manifestToDependencies, manifestToName, matchesDriftReachability, matchesEngines, matchesOrchestrationPath, matchesPrintWidth, matchesRange, nameToGuide, overridesToQuestions, parseBlueprint, parseCompilerOptions, parseExtension, parseGroups, parseSnapshot, pathToCondition, planToFindings, planToHash, planToSummary, renderSkillPointer, replaceManifestOverride, replaceManifestOverrides, replaceManifestRanges, replaceManifestScripts, replacePlanRanges, scanManifestTokens, selectGroups, selectHostPaths, serializeTypeScriptString, srcToEntry, srcToExports, srcToRoot };
|
|
8708
9481
|
|
|
8709
9482
|
//# sourceMappingURL=index.js.map
|