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