@orkestrel/scaffold 0.0.49 → 0.0.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/dist/bin/main.js +229 -88
- package/dist/bin/main.js.map +1 -1
- package/dist/host/AGENTS.md +2 -2
- package/dist/host/agents/orchestration.md +42 -15
- package/dist/host/claude/agents/orkestrel.md +54 -54
- package/dist/host/claude/rules/architecture.md +6 -6
- package/dist/host/claude/rules/tests.md +10 -4
- package/dist/host/claude/rules/workspace.md +1 -1
- package/dist/host/codex/config.toml +1 -1
- package/dist/host/guides/scaffold.md +353 -117
- package/dist/host/manifest.json +10 -10
- package/dist/host/scripts/codex.sh +0 -0
- package/dist/host/scripts/cursor.sh +0 -0
- package/dist/host/scripts/deps.sh +0 -0
- package/dist/host/scripts/ollama.sh +0 -0
- package/dist/host/tests/config.test.ts +83 -5
- package/dist/src/core/index.cjs +1683 -431
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +222 -55
- package/dist/src/core/index.d.ts +222 -55
- package/dist/src/core/index.js +1678 -432
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +47 -15
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +42 -15
- package/dist/src/server/index.d.ts +42 -15
- package/dist/src/server/index.js +48 -17
- package/dist/src/server/index.js.map +1 -1
- package/package.json +9 -9
package/dist/src/core/index.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.51",
|
|
8
8
|
description: "Scaffold workspaces with five commands: new, audit, repair, catalog, and overwrite.",
|
|
9
9
|
keywords: [
|
|
10
10
|
"audit",
|
|
@@ -89,19 +89,19 @@ var package_default = {
|
|
|
89
89
|
"prepublishOnly": "npm run format:check && npm run lint:check && npm run check && npm run build && npm test && npm run test:distribution -- --mode release"
|
|
90
90
|
},
|
|
91
91
|
dependencies: {
|
|
92
|
-
"@orkestrel/console": "^0.0.
|
|
92
|
+
"@orkestrel/console": "^0.0.10",
|
|
93
93
|
"@orkestrel/contract": "^0.0.13",
|
|
94
|
-
"@orkestrel/emitter": "^0.0.
|
|
95
|
-
"@orkestrel/markdown": "^0.0.
|
|
94
|
+
"@orkestrel/emitter": "^0.0.8",
|
|
95
|
+
"@orkestrel/markdown": "^0.0.10",
|
|
96
96
|
"@orkestrel/process": "^0.0.6",
|
|
97
|
-
"@orkestrel/template": "^0.0.
|
|
97
|
+
"@orkestrel/template": "^0.0.5"
|
|
98
98
|
},
|
|
99
99
|
devDependencies: {
|
|
100
100
|
"@microsoft/api-extractor": "^7.59.0",
|
|
101
|
-
"@orkestrel/guide": "^0.0.
|
|
102
|
-
"@orkestrel/html": "^0.0.
|
|
103
|
-
"@orkestrel/probe": "^0.0.
|
|
104
|
-
"@orkestrel/test": "^0.0.
|
|
101
|
+
"@orkestrel/guide": "^0.0.13",
|
|
102
|
+
"@orkestrel/html": "^0.0.5",
|
|
103
|
+
"@orkestrel/probe": "^0.0.3",
|
|
104
|
+
"@orkestrel/test": "^0.0.11",
|
|
105
105
|
"@types/node": "^26.2.0",
|
|
106
106
|
"@vitest/browser-playwright": "^4.1.11",
|
|
107
107
|
"oxfmt": "^0.64.0",
|
|
@@ -325,10 +325,21 @@ var SERVICE_SCRIPT_PATH = "scripts/service.sh";
|
|
|
325
325
|
var GLOBAL_SETUP_PATH = "tests/setupGlobal.ts";
|
|
326
326
|
/** The guide-parity proof whose presence selects the planned `guides` project. */
|
|
327
327
|
var GUIDES_TEST_PATH = "tests/guides.test.ts";
|
|
328
|
-
/** The packed-package proof
|
|
328
|
+
/** The generated packed-package proof every publishing workspace is planned at. */
|
|
329
329
|
var DISTRIBUTION_TEST_PATH = "tests/distribution.test.ts";
|
|
330
|
+
/**
|
|
331
|
+
* The `prepublishOnly` row that runs the packed-package proof against a real registry.
|
|
332
|
+
*
|
|
333
|
+
* @remarks
|
|
334
|
+
* The proof reads `import.meta.env.MODE`, so without `--mode release` it passes
|
|
335
|
+
* on an unreachable registry instead of failing. The row therefore has one home
|
|
336
|
+
* and both the script compiler and the manifest region writer read it from here.
|
|
337
|
+
*/
|
|
338
|
+
var RELEASE_PROOF_COMMAND = "npm run test:distribution -- --mode release";
|
|
330
339
|
/** The cross-environment composition proof whose presence makes a workspace `integration`. */
|
|
331
340
|
var INTEGRATION_TEST_PATH = "tests/integration.test.ts";
|
|
341
|
+
/** The manifest path every compiler plan emits with birth ownership. */
|
|
342
|
+
var MANIFEST_PATH = "package.json";
|
|
332
343
|
/** The official-tooling drift proof whose presence makes a workspace `conformance`. */
|
|
333
344
|
var CONFORMANCE_TEST_PATH = "tests/conformance.test.ts";
|
|
334
345
|
/** The live-service readiness module whose presence makes a workspace `service`. */
|
|
@@ -403,6 +414,8 @@ var MAX_NAME_LENGTH = 203;
|
|
|
403
414
|
var MAX_DEPENDENCY_NAME_LENGTH = 214;
|
|
404
415
|
/** Maximum length of one declared package range. */
|
|
405
416
|
var MAX_RANGE_LENGTH = 2048;
|
|
417
|
+
/** Maximum length of one manifest script name or command. */
|
|
418
|
+
var MAX_SCRIPT_LENGTH = 4096;
|
|
406
419
|
/** Maximum length of one path, matching the longest a supported filesystem accepts. */
|
|
407
420
|
var MAX_PATH_LENGTH = 32767;
|
|
408
421
|
/** Maximum items accepted in one public collection. */
|
|
@@ -548,7 +561,7 @@ var CONFIG_TEMPLATES = Object.freeze({
|
|
|
548
561
|
}
|
|
549
562
|
`,
|
|
550
563
|
vite: `import type { {{viteTypes}} } from 'vite'
|
|
551
|
-
{{imports}}import { defineConfig
|
|
564
|
+
{{imports}}import { defineConfig } from 'vitest/config'
|
|
552
565
|
import manifest from './package.json' with { type: 'json' }
|
|
553
566
|
import tsconfig from './tsconfig.json' with { type: 'json' }
|
|
554
567
|
{{helpers}}{{browsers}}import { fileURLToPath, URL } from 'node:url'
|
|
@@ -587,160 +600,140 @@ const resolve = {
|
|
|
587
600
|
}),
|
|
588
601
|
factories: Object.freeze({
|
|
589
602
|
src: Object.freeze({
|
|
590
|
-
core: `export const srcCore = (
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
},
|
|
608
|
-
},
|
|
609
|
-
options ?? {},
|
|
610
|
-
)
|
|
603
|
+
core: `export const srcCore = (): UserConfig => ({
|
|
604
|
+
resolve,
|
|
605
|
+
publicDir: false,
|
|
606
|
+
build: {
|
|
607
|
+
emptyOutDir: true,
|
|
608
|
+
sourcemap: true,
|
|
609
|
+
minify: false,
|
|
610
|
+
rolldownOptions: { onLog: enforceBuildLog },
|
|
611
|
+
},
|
|
612
|
+
test: {
|
|
613
|
+
name: { label: 'src:core', color: 'magenta' },
|
|
614
|
+
include: ['tests/src/core/**/*.test.ts'],
|
|
615
|
+
setupFiles: ['./tests/setup.ts'],
|
|
616
|
+
environment: 'node',
|
|
617
|
+
browser: { enabled: false },
|
|
618
|
+
},
|
|
619
|
+
})
|
|
611
620
|
`,
|
|
612
|
-
browser: `export const srcBrowser = (
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
onLog: enforceBuildLog,
|
|
630
|
-
{{external}}
|
|
621
|
+
browser: `export const srcBrowser = (): UserConfig => ({
|
|
622
|
+
resolve,
|
|
623
|
+
publicDir: false,
|
|
624
|
+
plugins: [outputBoundary('dist/src/browser'), environmentBoundary('src/browser')],
|
|
625
|
+
build: {
|
|
626
|
+
emptyOutDir: true,
|
|
627
|
+
sourcemap: true,
|
|
628
|
+
minify: false,
|
|
629
|
+
lib: {
|
|
630
|
+
entry: resolveWorkspacePath('src/browser/index.ts'),
|
|
631
|
+
formats: ['es'],
|
|
632
|
+
fileName: () => 'index.js',
|
|
633
|
+
},
|
|
634
|
+
outDir: 'dist/src/browser',
|
|
635
|
+
rolldownOptions: {
|
|
636
|
+
onLog: enforceBuildLog,
|
|
637
|
+
{{external}}
|
|
631
638
|
{{output}}
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
{{exclude}}
|
|
639
|
+
},
|
|
640
|
+
},
|
|
641
|
+
test: {
|
|
642
|
+
name: { label: 'src:browser', color: 'yellow' },
|
|
643
|
+
include: ['tests/src/browser/**/*.test.ts'],
|
|
644
|
+
{{exclude}} setupFiles: ['./tests/setup.ts', './tests/setupBrowser.ts'],
|
|
638
645
|
{{global}}
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
},
|
|
644
|
-
fileParallelism: false,
|
|
645
|
-
},
|
|
646
|
+
browser: {
|
|
647
|
+
enabled: true,
|
|
648
|
+
provider: playwright(browserOptions),
|
|
649
|
+
instances: [{ browser: 'chromium', headless: true }],
|
|
646
650
|
},
|
|
647
|
-
|
|
648
|
-
|
|
651
|
+
fileParallelism: false,
|
|
652
|
+
},
|
|
653
|
+
})
|
|
649
654
|
`,
|
|
650
|
-
server: `export const srcServer = (
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
platform: 'node',
|
|
670
|
-
{{external}}
|
|
655
|
+
server: `export const srcServer = (): UserConfig => ({
|
|
656
|
+
resolve,
|
|
657
|
+
publicDir: false,
|
|
658
|
+
plugins: [outputBoundary('dist/src/server'), environmentBoundary('src/server')],
|
|
659
|
+
build: {
|
|
660
|
+
emptyOutDir: true,
|
|
661
|
+
sourcemap: true,
|
|
662
|
+
minify: false,
|
|
663
|
+
lib: {
|
|
664
|
+
entry: resolveWorkspacePath('src/server/index.ts'),
|
|
665
|
+
formats: ['es', 'cjs'],
|
|
666
|
+
fileName: (format: string) => (format === 'es' ? 'index.js' : 'index.cjs'),
|
|
667
|
+
},
|
|
668
|
+
outDir: 'dist/src/server',
|
|
669
|
+
target: 'node22',
|
|
670
|
+
rolldownOptions: {
|
|
671
|
+
onLog: enforceBuildLog,
|
|
672
|
+
platform: 'node',
|
|
673
|
+
{{external}}
|
|
671
674
|
{{output}}
|
|
672
|
-
},
|
|
673
|
-
},
|
|
674
|
-
test: {
|
|
675
|
-
name: { label: 'src:server', color: 'red' },
|
|
676
|
-
include: ['tests/src/server/**/*.test.ts'],
|
|
677
|
-
{{exclude}} setupFiles: ['./tests/setup.ts', './tests/setupServer.ts'],
|
|
678
|
-
environment: 'node',
|
|
679
|
-
browser: { enabled: false },
|
|
680
|
-
},
|
|
681
675
|
},
|
|
682
|
-
|
|
683
|
-
|
|
676
|
+
},
|
|
677
|
+
test: {
|
|
678
|
+
name: { label: 'src:server', color: 'red' },
|
|
679
|
+
include: ['tests/src/server/**/*.test.ts'],
|
|
680
|
+
{{exclude}} setupFiles: ['./tests/setup.ts', './tests/setupServer.ts'],
|
|
681
|
+
environment: 'node',
|
|
682
|
+
browser: { enabled: false },
|
|
683
|
+
},
|
|
684
|
+
})
|
|
684
685
|
`,
|
|
685
|
-
bin: `export const srcBin = (
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
formats: ['es'],
|
|
698
|
-
fileName: () => 'main.js',
|
|
699
|
-
},
|
|
700
|
-
outDir: 'dist/bin',
|
|
701
|
-
target: 'node22',
|
|
702
|
-
rolldownOptions: {
|
|
703
|
-
onLog: enforceBuildLog,
|
|
704
|
-
external: (id: string) =>
|
|
705
|
-
id.startsWith('node:') ||
|
|
706
|
-
id.startsWith('@orkestrel/') ||
|
|
707
|
-
id.startsWith('@src/') ||
|
|
708
|
-
peers.some((peer) => id === peer || id.startsWith(peer + '/')),
|
|
709
|
-
},
|
|
710
|
-
},
|
|
711
|
-
test: {
|
|
712
|
-
name: { label: 'src:bin', color: 'yellow' },
|
|
713
|
-
include: ['tests/src/bin/**/*.test.ts'],
|
|
714
|
-
setupFiles: ['./tests/setup.ts', './tests/setupServer.ts'],
|
|
715
|
-
environment: 'node',
|
|
716
|
-
browser: { enabled: false },
|
|
717
|
-
// A bin test drives the real executable over a real temporary repository, so it
|
|
718
|
-
// spends seconds in process startup and filesystem work rather than milliseconds.
|
|
719
|
-
// Vitest's five-second default clears one alone and times out under a full suite.
|
|
720
|
-
testTimeout: 15_000,
|
|
721
|
-
},
|
|
686
|
+
bin: `export const srcBin = (): UserConfig => ({
|
|
687
|
+
resolve,
|
|
688
|
+
publicDir: false,
|
|
689
|
+
plugins: [outputBoundary('dist/bin')],
|
|
690
|
+
build: {
|
|
691
|
+
emptyOutDir: true,
|
|
692
|
+
sourcemap: true,
|
|
693
|
+
minify: false,
|
|
694
|
+
lib: {
|
|
695
|
+
entry: resolveWorkspacePath('${BIN_ENTRY_PATH}'),
|
|
696
|
+
formats: ['es'],
|
|
697
|
+
fileName: () => 'main.js',
|
|
722
698
|
},
|
|
723
|
-
|
|
724
|
-
|
|
699
|
+
outDir: 'dist/bin',
|
|
700
|
+
target: 'node22',
|
|
701
|
+
rolldownOptions: {
|
|
702
|
+
onLog: enforceBuildLog,
|
|
703
|
+
external: (id: string) =>
|
|
704
|
+
id.startsWith('node:') ||
|
|
705
|
+
id.startsWith('@orkestrel/') ||
|
|
706
|
+
id.startsWith('@src/') ||
|
|
707
|
+
peers.some((peer) => id === peer || id.startsWith(peer + '/')),
|
|
708
|
+
},
|
|
709
|
+
},
|
|
710
|
+
test: {
|
|
711
|
+
name: { label: 'src:bin', color: 'yellow' },
|
|
712
|
+
include: ['tests/src/bin/**/*.test.ts'],
|
|
713
|
+
setupFiles: ['./tests/setup.ts', './tests/setupServer.ts'],
|
|
714
|
+
environment: 'node',
|
|
715
|
+
browser: { enabled: false },
|
|
716
|
+
// A bin test drives the real executable over a real temporary repository, so it
|
|
717
|
+
// spends seconds in process startup and filesystem work rather than milliseconds.
|
|
718
|
+
// Vitest's five-second default clears one alone and times out under a full suite.
|
|
719
|
+
testTimeout: 15_000,
|
|
720
|
+
},
|
|
721
|
+
})
|
|
725
722
|
`
|
|
726
723
|
}),
|
|
727
724
|
app: Object.freeze({
|
|
728
|
-
core: `export const appCore = (
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
},
|
|
741
|
-
},
|
|
742
|
-
options ?? {},
|
|
743
|
-
)
|
|
725
|
+
core: `export const appCore = (): UserConfig => ({
|
|
726
|
+
resolve,
|
|
727
|
+
publicDir: false,
|
|
728
|
+
plugins: [environmentBoundary('app/core')],
|
|
729
|
+
test: {
|
|
730
|
+
name: { label: 'app:core', color: 'cyan' },
|
|
731
|
+
include: ['tests/app/core/**/*.test.ts'],
|
|
732
|
+
setupFiles: ['./tests/setup.ts'],
|
|
733
|
+
environment: 'node',
|
|
734
|
+
browser: { enabled: false },
|
|
735
|
+
},
|
|
736
|
+
})
|
|
744
737
|
`,
|
|
745
738
|
browser: `function applicationBrowser(showcase: boolean): UserConfig {
|
|
746
739
|
const output = showcase ? 'dist/showcase' : 'dist/app/browser'
|
|
@@ -776,193 +769,153 @@ export function appBrowser(): UserConfig {
|
|
|
776
769
|
return applicationBrowser(false)
|
|
777
770
|
}
|
|
778
771
|
{{showcaseFactory}}`,
|
|
779
|
-
server: `export const appServer = (
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
formats: ['cjs'],
|
|
790
|
-
fileName: () => 'main.cjs',
|
|
791
|
-
},
|
|
792
|
-
outDir: resolveWorkspacePath('dist/app/server'),
|
|
793
|
-
target: 'node22',
|
|
794
|
-
rolldownOptions: {
|
|
795
|
-
onLog: enforceBuildLog,
|
|
796
|
-
external: (id: string) => id.startsWith('node:'),
|
|
797
|
-
},
|
|
798
|
-
},
|
|
799
|
-
test: {
|
|
800
|
-
name: { label: 'app:server', color: 'green' },
|
|
801
|
-
include: ['tests/app/server/**/*.test.ts'],
|
|
802
|
-
setupFiles: ['./tests/setup.ts', './tests/setupServer.ts'],
|
|
803
|
-
environment: 'node',
|
|
804
|
-
browser: { enabled: false },
|
|
805
|
-
},
|
|
772
|
+
server: `export const appServer = (): UserConfig => ({
|
|
773
|
+
resolve,
|
|
774
|
+
publicDir: false,
|
|
775
|
+
plugins: [outputBoundary('dist/app/server'), environmentBoundary('app/server')],
|
|
776
|
+
build: {
|
|
777
|
+
emptyOutDir: true,
|
|
778
|
+
lib: {
|
|
779
|
+
entry: resolveWorkspacePath('app/server/main.ts'),
|
|
780
|
+
formats: ['cjs'],
|
|
781
|
+
fileName: () => 'main.cjs',
|
|
806
782
|
},
|
|
807
|
-
|
|
808
|
-
|
|
783
|
+
outDir: resolveWorkspacePath('dist/app/server'),
|
|
784
|
+
target: 'node22',
|
|
785
|
+
rolldownOptions: {
|
|
786
|
+
onLog: enforceBuildLog,
|
|
787
|
+
external: (id: string) => id.startsWith('node:'),
|
|
788
|
+
},
|
|
789
|
+
},
|
|
790
|
+
test: {
|
|
791
|
+
name: { label: 'app:server', color: 'green' },
|
|
792
|
+
include: ['tests/app/server/**/*.test.ts'],
|
|
793
|
+
setupFiles: ['./tests/setup.ts', './tests/setupServer.ts'],
|
|
794
|
+
environment: 'node',
|
|
795
|
+
browser: { enabled: false },
|
|
796
|
+
},
|
|
797
|
+
})
|
|
809
798
|
`
|
|
810
799
|
}),
|
|
811
|
-
policy: `export const policy = (
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
},
|
|
822
|
-
},
|
|
823
|
-
options ?? {},
|
|
824
|
-
)
|
|
800
|
+
policy: `export const policy = (): UserConfig => ({
|
|
801
|
+
resolve,
|
|
802
|
+
test: {
|
|
803
|
+
name: { label: 'policy', color: 'white' },
|
|
804
|
+
include: ['tests/policy.test.ts'],
|
|
805
|
+
setupFiles: ['./tests/setup.ts'],
|
|
806
|
+
environment: 'node',
|
|
807
|
+
browser: { enabled: false },
|
|
808
|
+
},
|
|
809
|
+
})
|
|
825
810
|
`,
|
|
826
|
-
config: `export const config = (
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
},
|
|
840
|
-
},
|
|
841
|
-
options ?? {},
|
|
842
|
-
)
|
|
811
|
+
config: `export const config = (): UserConfig => ({
|
|
812
|
+
resolve,
|
|
813
|
+
test: {
|
|
814
|
+
name: { label: 'config', color: 'yellow' },
|
|
815
|
+
include: ['tests/config.test.ts'],
|
|
816
|
+
setupFiles: ['./tests/setup.ts'],
|
|
817
|
+
environment: 'node',
|
|
818
|
+
browser: { enabled: false },
|
|
819
|
+
// A config test validates every target wrapper and runs the real linter twice with
|
|
820
|
+
// 15-second child caps, so this budget clears both caps and reports their diagnostics.
|
|
821
|
+
testTimeout: 45_000,
|
|
822
|
+
},
|
|
823
|
+
})
|
|
843
824
|
`,
|
|
844
|
-
setup: `export const setup = (
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
},
|
|
855
|
-
},
|
|
856
|
-
options ?? {},
|
|
857
|
-
)
|
|
825
|
+
setup: `export const setup = (): UserConfig => ({
|
|
826
|
+
resolve,
|
|
827
|
+
test: {
|
|
828
|
+
name: { label: 'setup', color: 'white' },
|
|
829
|
+
include: ['tests/setup*.test.ts'],
|
|
830
|
+
setupFiles: ['./tests/setup.ts'],
|
|
831
|
+
environment: 'node',
|
|
832
|
+
browser: { enabled: false },
|
|
833
|
+
},
|
|
834
|
+
})
|
|
858
835
|
`,
|
|
859
|
-
guides: `export const guides = (
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
},
|
|
871
|
-
},
|
|
872
|
-
options ?? {},
|
|
873
|
-
)
|
|
836
|
+
guides: `export const guides = (): UserConfig => ({
|
|
837
|
+
resolve,
|
|
838
|
+
test: {
|
|
839
|
+
name: { label: 'guides', color: 'green' },
|
|
840
|
+
include: ['${GUIDES_TEST_PATH}'],
|
|
841
|
+
exclude: ['tests/src/**/*.test.ts', 'tests/app/**/*.test.ts', 'tests/setup.test.ts'],
|
|
842
|
+
setupFiles: ['./tests/setup.ts'],
|
|
843
|
+
environment: 'node',
|
|
844
|
+
browser: { enabled: false },
|
|
845
|
+
},
|
|
846
|
+
})
|
|
874
847
|
`,
|
|
875
848
|
conformance: `// Where this package drifts from the official tooling it stays compatible with.
|
|
876
849
|
// The subject is this package, so the proof is hermetic and stays in \`npm test\`.
|
|
877
|
-
export const conformance = (
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
},
|
|
888
|
-
},
|
|
889
|
-
options ?? {},
|
|
890
|
-
)
|
|
850
|
+
export const conformance = (): UserConfig => ({
|
|
851
|
+
resolve,
|
|
852
|
+
test: {
|
|
853
|
+
name: { label: 'conformance', color: 'magenta' },
|
|
854
|
+
include: ['${CONFORMANCE_TEST_PATH}'],
|
|
855
|
+
setupFiles: ['./tests/setup.ts'],
|
|
856
|
+
environment: 'node',
|
|
857
|
+
browser: { enabled: false },
|
|
858
|
+
},
|
|
859
|
+
})
|
|
891
860
|
`,
|
|
892
861
|
service: `// The live external services this package drives. It starts nothing itself:
|
|
893
862
|
// \`scripts/service.sh\` provisions, \`tests/setupService.ts\` proves readiness, and
|
|
894
863
|
// the project stays out of \`npm test\` because a real service answers it.
|
|
895
|
-
export const service = (
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
},
|
|
909
|
-
},
|
|
910
|
-
options ?? {},
|
|
911
|
-
)
|
|
864
|
+
export const service = (): UserConfig => ({
|
|
865
|
+
resolve,
|
|
866
|
+
test: {
|
|
867
|
+
name: { label: 'service', color: 'red' },
|
|
868
|
+
include: ['${SERVICE_TEST_INCLUDE}'],
|
|
869
|
+
setupFiles: ['./tests/setup.ts', './tests/setupService.ts'],
|
|
870
|
+
environment: 'node',
|
|
871
|
+
browser: { enabled: false },
|
|
872
|
+
testTimeout: 120_000,
|
|
873
|
+
hookTimeout: 120_000,
|
|
874
|
+
fileParallelism: false,
|
|
875
|
+
},
|
|
876
|
+
})
|
|
912
877
|
`,
|
|
913
|
-
distribution: `export const distribution = (
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
},
|
|
926
|
-
},
|
|
927
|
-
options ?? {},
|
|
928
|
-
)
|
|
878
|
+
distribution: `export const distribution = (): UserConfig => ({
|
|
879
|
+
resolve,
|
|
880
|
+
test: {
|
|
881
|
+
name: { label: 'distribution', color: 'cyan' },
|
|
882
|
+
include: ['${DISTRIBUTION_TEST_PATH}'],
|
|
883
|
+
setupFiles: ['./tests/setup.ts'],
|
|
884
|
+
environment: 'node',
|
|
885
|
+
testTimeout: 120_000,
|
|
886
|
+
hookTimeout: 120_000,
|
|
887
|
+
fileParallelism: false,
|
|
888
|
+
},
|
|
889
|
+
})
|
|
929
890
|
`,
|
|
930
891
|
probe: `// A workbench, not a proof. No gate selects this project. Run in test mode by the
|
|
931
892
|
// \`test:probe\` script, it collects \`tmp/probe/**/*.test.ts\`. Run in benchmark mode by the
|
|
932
893
|
// \`test:bench\` script, the same workbench also collects \`tests/**/*.test.ts\` for a \`bench\` block,
|
|
933
894
|
// so a suite may carry a bench beside its ordinary tests without a second project. The mode
|
|
934
895
|
// guard around each \`bench\` call keeps it out of test mode, so it never executes there.
|
|
935
|
-
export const probe = (
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
},
|
|
949
|
-
},
|
|
950
|
-
options ?? {},
|
|
951
|
-
)
|
|
896
|
+
export const probe = (): UserConfig => ({
|
|
897
|
+
resolve,
|
|
898
|
+
test: {
|
|
899
|
+
name: { label: 'probe', color: 'black' },
|
|
900
|
+
include: ['tmp/probe/**/*.test.ts'],
|
|
901
|
+
setupFiles: ['./tests/setup.ts'],
|
|
902
|
+
environment: 'node',
|
|
903
|
+
browser: { enabled: false },
|
|
904
|
+
fileParallelism: false,
|
|
905
|
+
pool: 'threads',
|
|
906
|
+
benchmark: { include: ['tmp/probe/**/*.test.ts', 'tests/**/*.test.ts'] },
|
|
907
|
+
},
|
|
908
|
+
})
|
|
952
909
|
`,
|
|
953
|
-
integration: `export const integration = (
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
},
|
|
963
|
-
},
|
|
964
|
-
options ?? {},
|
|
965
|
-
)
|
|
910
|
+
integration: `export const integration = (): UserConfig => ({
|
|
911
|
+
resolve,
|
|
912
|
+
test: {
|
|
913
|
+
name: { label: 'integration', color: 'blue' },
|
|
914
|
+
include: ['${INTEGRATION_TEST_PATH}'],
|
|
915
|
+
setupFiles: ['./tests/setup.ts'],
|
|
916
|
+
{{global}} environment: 'node',
|
|
917
|
+
},
|
|
918
|
+
})
|
|
966
919
|
`
|
|
967
920
|
}),
|
|
968
921
|
tsconfigs: Object.freeze({
|
|
@@ -1090,13 +1043,13 @@ export const probe = (options?: UserConfig): UserConfig =>
|
|
|
1090
1043
|
}),
|
|
1091
1044
|
vites: Object.freeze({
|
|
1092
1045
|
src: Object.freeze({
|
|
1093
|
-
core: `import { defineConfig } from 'vite'
|
|
1046
|
+
core: `import { defineConfig, mergeConfig } from 'vite'
|
|
1094
1047
|
import dts from 'vite-plugin-dts'
|
|
1095
1048
|
import { environmentBoundary, outputBoundary } from '../helpers.js'
|
|
1096
1049
|
import { peers, srcCore, resolveWorkspacePath } from '../../vite.config.ts'
|
|
1097
1050
|
|
|
1098
1051
|
export default defineConfig(
|
|
1099
|
-
srcCore({
|
|
1052
|
+
mergeConfig(srcCore(), {
|
|
1100
1053
|
publicDir: false,
|
|
1101
1054
|
plugins: [
|
|
1102
1055
|
outputBoundary('dist/src/core'),
|
|
@@ -1118,7 +1071,7 @@ export default defineConfig(
|
|
|
1118
1071
|
lib: {
|
|
1119
1072
|
entry: resolveWorkspacePath('src/core/index.ts'),
|
|
1120
1073
|
formats: ['es', 'cjs'],
|
|
1121
|
-
fileName: (format) => (format === 'es' ? 'index.js' : 'index.cjs'),
|
|
1074
|
+
fileName: (format: string) => (format === 'es' ? 'index.js' : 'index.cjs'),
|
|
1122
1075
|
},
|
|
1123
1076
|
outDir: 'dist/src/core',
|
|
1124
1077
|
rolldownOptions: {
|
|
@@ -1131,17 +1084,17 @@ export default defineConfig(
|
|
|
1131
1084
|
}),
|
|
1132
1085
|
)
|
|
1133
1086
|
`,
|
|
1134
|
-
browser: `import { defineConfig } from 'vite'
|
|
1087
|
+
browser: `import { defineConfig, mergeConfig } from 'vite'
|
|
1135
1088
|
import dts from 'vite-plugin-dts'
|
|
1136
1089
|
import { srcBrowser, resolveWorkspacePath } from '../../vite.config.ts'
|
|
1137
1090
|
|
|
1138
1091
|
// vite-plugin-dts rolls this face into one declaration, and the roll-up reaches
|
|
1139
1092
|
// src/core through a relative source path the tarball does not carry. The path
|
|
1140
1093
|
// keeps each source module's own depth, so a module in a browser subfolder emits
|
|
1141
|
-
// one that leaves dist/src entirely. The rewrite
|
|
1142
|
-
// the package's own published root export, on the final roll-up only.
|
|
1094
|
+
// one that leaves dist/src entirely. The following rewrite externalizes core
|
|
1095
|
+
// through the package's own published root export, on the final roll-up only.
|
|
1143
1096
|
export default defineConfig(
|
|
1144
|
-
srcBrowser({
|
|
1097
|
+
mergeConfig(srcBrowser(), {
|
|
1145
1098
|
plugins: [
|
|
1146
1099
|
dts({
|
|
1147
1100
|
tsconfigPath: resolveWorkspacePath('configs/src/tsconfig.browser.json'),
|
|
@@ -1156,16 +1109,16 @@ export default defineConfig(
|
|
|
1156
1109
|
}),
|
|
1157
1110
|
)
|
|
1158
1111
|
`,
|
|
1159
|
-
server: `import { defineConfig } from 'vite'
|
|
1112
|
+
server: `import { defineConfig, mergeConfig } from 'vite'
|
|
1160
1113
|
import dts from 'vite-plugin-dts'
|
|
1161
1114
|
import { srcServer, resolveWorkspacePath } from '../../vite.config.ts'
|
|
1162
1115
|
|
|
1163
1116
|
// vite-plugin-dts rolls this face into one declaration, and the roll-up reaches
|
|
1164
|
-
// src/core through a relative source path the tarball does not carry. The
|
|
1165
|
-
//
|
|
1166
|
-
// final roll-up only.
|
|
1117
|
+
// src/core through a relative source path the tarball does not carry. The
|
|
1118
|
+
// following rewrite externalizes core through the package's own published root
|
|
1119
|
+
// export, on the final roll-up only.
|
|
1167
1120
|
export default defineConfig(
|
|
1168
|
-
srcServer({
|
|
1121
|
+
mergeConfig(srcServer(), {
|
|
1169
1122
|
plugins: [
|
|
1170
1123
|
dts({
|
|
1171
1124
|
tsconfigPath: resolveWorkspacePath('configs/src/tsconfig.server.json'),
|
|
@@ -1181,7 +1134,7 @@ export default defineConfig(
|
|
|
1181
1134
|
)
|
|
1182
1135
|
`
|
|
1183
1136
|
}),
|
|
1184
|
-
bin: `import { defineConfig } from 'vite'
|
|
1137
|
+
bin: `import { defineConfig, mergeConfig } from 'vite'
|
|
1185
1138
|
import { srcBin } from '../../vite.config.ts'
|
|
1186
1139
|
|
|
1187
1140
|
// The \`scaffold\` executable build — a single ESM lib file, no declarations (an
|
|
@@ -1190,7 +1143,7 @@ import { srcBin } from '../../vite.config.ts'
|
|
|
1190
1143
|
// \`output.paths\` rewriting the externalized \`@src/*\` specifiers to the built sibling
|
|
1191
1144
|
// src environments (relative to \`dist/bin/\`), so the emitted bin resolves at runtime.
|
|
1192
1145
|
export default defineConfig(
|
|
1193
|
-
srcBin({
|
|
1146
|
+
mergeConfig(srcBin(), {
|
|
1194
1147
|
build: {
|
|
1195
1148
|
rolldownOptions: {
|
|
1196
1149
|
output: {
|
|
@@ -1591,6 +1544,957 @@ describe('bin entry', () => {
|
|
|
1591
1544
|
})
|
|
1592
1545
|
})
|
|
1593
1546
|
`,
|
|
1547
|
+
distribution: Object.freeze({
|
|
1548
|
+
proof: `// The artifact a consumer installs, measured rather than described. This workspace
|
|
1549
|
+
// is packed and installed into a throwaway consumer, and every following claim is read
|
|
1550
|
+
// off that installed tree: the exports map it publishes, the declarations it ships,
|
|
1551
|
+
// and the module objects a real runtime hands a consumer. Nothing here names this
|
|
1552
|
+
// package, one of its exports, or how many there are, so the proof stays true as
|
|
1553
|
+
// the published surface moves.
|
|
1554
|
+
{{types}}import type { SpawnSyncReturns } from 'node:child_process'
|
|
1555
|
+
import type { TestContext } from 'vitest'
|
|
1556
|
+
import { spawnSync } from 'node:child_process'
|
|
1557
|
+
import {
|
|
1558
|
+
existsSync,
|
|
1559
|
+
mkdirSync,
|
|
1560
|
+
mkdtempSync,
|
|
1561
|
+
readdirSync,
|
|
1562
|
+
readFileSync,
|
|
1563
|
+
rmSync,
|
|
1564
|
+
statSync,
|
|
1565
|
+
writeFileSync,
|
|
1566
|
+
} from 'node:fs'
|
|
1567
|
+
{{transport}}import { tmpdir } from 'node:os'
|
|
1568
|
+
import { dirname, join, resolve } from 'node:path'
|
|
1569
|
+
import { fileURLToPath } from 'node:url'
|
|
1570
|
+
{{launcher}}import ts from 'typescript'
|
|
1571
|
+
import { afterAll, describe, expect, it } from 'vitest'
|
|
1572
|
+
|
|
1573
|
+
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
|
1574
|
+
const NPM = process.platform === 'win32' ? 'npm.cmd' : 'npm'
|
|
1575
|
+
// Windows needs a shell to launch a \`.cmd\`: Node refuses one directly since the
|
|
1576
|
+
// batch-argument hardening, and \`spawnSync\` returns \`EINVAL\` with a null status
|
|
1577
|
+
// rather than an exit code a caller can read. Every following argument is a literal or
|
|
1578
|
+
// a path this file built, so the shell has nothing to escape.
|
|
1579
|
+
const SHELL = process.platform === 'win32'
|
|
1580
|
+
// \`prepublishOnly\` runs this proof as \`npm run test:distribution -- --mode release\`.
|
|
1581
|
+
// Release is the publish gate, so evidence it cannot obtain fails there and skips
|
|
1582
|
+
// everywhere else: a gate that passes on missing evidence proves nothing.
|
|
1583
|
+
const RELEASE = import.meta.env.MODE === 'release'
|
|
1584
|
+
// The built output directory convention a browser face may publish from. Every
|
|
1585
|
+
// selection reads this prefix off the export target and never off the subpath name. A
|
|
1586
|
+
// workspace whose only published face is the browser one publishes that face at the
|
|
1587
|
+
// root subpath, so a rule keyed on the subpath name drives a browser bundle through
|
|
1588
|
+
// Node and the miss is silent.
|
|
1589
|
+
const BROWSER_OUTPUT = './dist/src/browser/'
|
|
1590
|
+
const ABSENT_SUBPATH = '/no-subpath-is-published-under-this-name'
|
|
1591
|
+
const PING = ['ping', '--fetch-retries=0', '--fetch-timeout=5000', '--loglevel=silent']
|
|
1592
|
+
const ESM_DRIVER = 'drive.mjs'
|
|
1593
|
+
const CJS_DRIVER = 'drive.cjs'
|
|
1594
|
+
const CONSUMER_MANIFEST = \`{ "name": "distribution-consumer", "private": true, "type": "module" }\\n\`
|
|
1595
|
+
const ESM_DRIVER_SOURCE = \`const entry = await import(process.argv[2])
|
|
1596
|
+
process.stdout.write(JSON.stringify(Object.keys(entry).sort()))
|
|
1597
|
+
\`
|
|
1598
|
+
const CJS_DRIVER_SOURCE = \`const entry = require(process.argv[2])
|
|
1599
|
+
process.stdout.write(JSON.stringify(Object.keys(entry).sort()))
|
|
1600
|
+
\`
|
|
1601
|
+
|
|
1602
|
+
// The extensions a JavaScript handler loads as modules. Node loads a native addon
|
|
1603
|
+
// through its addon handler instead, so that extension is named separately.
|
|
1604
|
+
const MODULE_EXTENSIONS = ['.js', '.mjs', '.cjs']
|
|
1605
|
+
const ADDON_EXTENSION = '.node'
|
|
1606
|
+
// The extensions a declaration file carries. A \`require\` condition declares
|
|
1607
|
+
// \`.d.cts\` and an ESM-only one \`.d.mts\`, so the \`.d.ts\` spelling alone does not
|
|
1608
|
+
// name them.
|
|
1609
|
+
const DECLARATION_EXTENSIONS = ['.d.ts', '.d.cts', '.d.mts']
|
|
1610
|
+
type Format = 'module' | 'commonjs'
|
|
1611
|
+
|
|
1612
|
+
// The Node import target is resolved with the conditions that driver supplies. The
|
|
1613
|
+
// CommonJS compile probe is selected from its declaration's format, and its runtime
|
|
1614
|
+
// drive loads the same subpath through Node's require resolver. Vite's production
|
|
1615
|
+
// client build enables its module and browser conditions.
|
|
1616
|
+
const RUNTIME_CONDITIONS = Object.freeze({
|
|
1617
|
+
module: Object.freeze(['node-addons', 'node', 'import', 'module-sync']),
|
|
1618
|
+
commonjs: Object.freeze(['node-addons', 'node', 'require', 'module-sync']),
|
|
1619
|
+
browser: Object.freeze(['module', 'browser', 'production', 'import']),
|
|
1620
|
+
})
|
|
1621
|
+
// TypeScript's Node resolutions add \`node\` to the format condition. Its bundler
|
|
1622
|
+
// resolution does not, so a browser drive compares against the declaration a bundler
|
|
1623
|
+
// consumer reads rather than borrowing the Node declaration.
|
|
1624
|
+
const BUNDLER_CONDITIONS = Object.freeze({
|
|
1625
|
+
module: ['types', 'import'],
|
|
1626
|
+
commonjs: ['types', 'require'],
|
|
1627
|
+
})
|
|
1628
|
+
const DECLARATION_CONDITIONS = Object.freeze({
|
|
1629
|
+
module: ['types', 'node', 'import'],
|
|
1630
|
+
commonjs: ['types', 'node', 'require'],
|
|
1631
|
+
browser: BUNDLER_CONDITIONS.module,
|
|
1632
|
+
})
|
|
1633
|
+
|
|
1634
|
+
interface Resolution {
|
|
1635
|
+
readonly label: string
|
|
1636
|
+
readonly resolution: ts.ModuleResolutionKind
|
|
1637
|
+
readonly module: ts.ModuleKind
|
|
1638
|
+
readonly conditions: Readonly<Record<Format, readonly string[]>>
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
interface TargetResolution {
|
|
1642
|
+
readonly target: string
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
// Each compile driver carries the conditions TypeScript applies for its resolution
|
|
1646
|
+
// and importing format. A \`require\`-only subpath therefore stays in each CommonJS
|
|
1647
|
+
// probe that can resolve it.
|
|
1648
|
+
const RESOLUTIONS: readonly Resolution[] = [
|
|
1649
|
+
{
|
|
1650
|
+
label: 'node16',
|
|
1651
|
+
resolution: ts.ModuleResolutionKind.Node16,
|
|
1652
|
+
module: ts.ModuleKind.Node16,
|
|
1653
|
+
conditions: DECLARATION_CONDITIONS,
|
|
1654
|
+
},
|
|
1655
|
+
{
|
|
1656
|
+
label: 'nodenext',
|
|
1657
|
+
resolution: ts.ModuleResolutionKind.NodeNext,
|
|
1658
|
+
module: ts.ModuleKind.NodeNext,
|
|
1659
|
+
conditions: DECLARATION_CONDITIONS,
|
|
1660
|
+
},
|
|
1661
|
+
{
|
|
1662
|
+
label: 'bundler',
|
|
1663
|
+
resolution: ts.ModuleResolutionKind.Bundler,
|
|
1664
|
+
module: ts.ModuleKind.ESNext,
|
|
1665
|
+
conditions: BUNDLER_CONDITIONS,
|
|
1666
|
+
},
|
|
1667
|
+
]
|
|
1668
|
+
|
|
1669
|
+
const FORMATS: ReadonlyArray<readonly [extension: string, format: Format]> = [
|
|
1670
|
+
['ts', 'module'],
|
|
1671
|
+
['cts', 'commonjs'],
|
|
1672
|
+
]
|
|
1673
|
+
|
|
1674
|
+
// One published subpath, resolved to what this proof can drive: the specifier a
|
|
1675
|
+
// consumer writes, the declarations its consumer formats name, whether its target
|
|
1676
|
+
// is a browser bundle, and whether it answers \`import\` and \`require\` at all.
|
|
1677
|
+
interface Entry {
|
|
1678
|
+
readonly subpath: string
|
|
1679
|
+
readonly specifier: string
|
|
1680
|
+
readonly mapping: unknown
|
|
1681
|
+
readonly declaration: {
|
|
1682
|
+
readonly module: string | undefined
|
|
1683
|
+
readonly commonjs: string | undefined
|
|
1684
|
+
readonly browser: string | undefined
|
|
1685
|
+
}
|
|
1686
|
+
readonly browser: boolean
|
|
1687
|
+
readonly module: boolean
|
|
1688
|
+
readonly commonjs: boolean
|
|
1689
|
+
readonly required: boolean
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
// The installed tree every claim is read from. Every subpath the exports map names
|
|
1693
|
+
// lands in exactly one of \`entries\`, \`undeclared\`, and \`excluded\`, so a subpath this
|
|
1694
|
+
// proof cannot drive is reported rather than dropped.
|
|
1695
|
+
interface Stage {
|
|
1696
|
+
readonly consumer: string
|
|
1697
|
+
readonly installed: string
|
|
1698
|
+
readonly archives: readonly string[]
|
|
1699
|
+
readonly entries: readonly Entry[]
|
|
1700
|
+
readonly subpaths: readonly string[]
|
|
1701
|
+
readonly undeclared: readonly string[]
|
|
1702
|
+
readonly excluded: readonly string[]
|
|
1703
|
+
readonly targets: readonly string[]
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
function isRecord(value: unknown): value is Readonly<Record<string, unknown>> {
|
|
1707
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
function isNames(value: unknown): value is readonly string[] {
|
|
1711
|
+
return Array.isArray(value) && value.every((name) => typeof name === 'string')
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
// A fallback list, which is what Node reads an array in an exports entry as. The
|
|
1715
|
+
// narrowing is what the following walkers need: \`Array.isArray\` widens an \`unknown\`
|
|
1716
|
+
// member to \`any\`, and an entry read that way is not read at all.
|
|
1717
|
+
function isList(value: unknown): value is readonly unknown[] {
|
|
1718
|
+
return Array.isArray(value)
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
// Whether a string is a valid package target. Node rejects a target outside the
|
|
1722
|
+
// package and a target containing a dot, parent, or node_modules segment during
|
|
1723
|
+
// package-target resolution. A later module-resolution failure is not the same
|
|
1724
|
+
// thing: an array falls through the former and keeps the latter.
|
|
1725
|
+
function isPackageTarget(target: string): boolean {
|
|
1726
|
+
if (!target.startsWith('./')) return false
|
|
1727
|
+
for (const segment of target.slice(2).split(/[\\\\/]/u)) {
|
|
1728
|
+
let decoded = segment
|
|
1729
|
+
try {
|
|
1730
|
+
decoded = decodeURIComponent(segment)
|
|
1731
|
+
} catch {}
|
|
1732
|
+
const normalized = decoded.toLowerCase()
|
|
1733
|
+
if (normalized === '.' || normalized === '..' || normalized === 'node_modules') return false
|
|
1734
|
+
}
|
|
1735
|
+
return true
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
function readJson(path: string): unknown {
|
|
1739
|
+
const parsed: unknown = JSON.parse(readFileSync(path, 'utf8'))
|
|
1740
|
+
return parsed
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
function readManifestName(path: string): string {
|
|
1744
|
+
const manifest = readJson(path)
|
|
1745
|
+
if (!isRecord(manifest) || typeof manifest.name !== 'string') {
|
|
1746
|
+
throw new Error(\`The manifest at \${path} declares no package name\`)
|
|
1747
|
+
}
|
|
1748
|
+
return manifest.name
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
function writeFile(path: string, content: string): void {
|
|
1752
|
+
mkdirSync(dirname(path), { recursive: true })
|
|
1753
|
+
writeFileSync(path, content)
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
function readOutput(result: SpawnSyncReturns<string>): string {
|
|
1757
|
+
return \`\${result.stdout ?? ''}\${result.stderr ?? ''}\`.trim()
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
function runNpm(args: readonly string[], cwd: string): SpawnSyncReturns<string> {
|
|
1761
|
+
return spawnSync(NPM, [...args], {
|
|
1762
|
+
cwd,
|
|
1763
|
+
encoding: 'utf8',
|
|
1764
|
+
env: { ...process.env, npm_config_cache: CACHE },
|
|
1765
|
+
shell: SHELL,
|
|
1766
|
+
windowsHide: true,
|
|
1767
|
+
})
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
function runNode(args: readonly string[], cwd: string): SpawnSyncReturns<string> {
|
|
1771
|
+
return spawnSync(process.execPath, [...args], { cwd, encoding: 'utf8', windowsHide: true })
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
// Node's own condition matching, read in declaration order.
|
|
1775
|
+
function resolvePackageTarget(
|
|
1776
|
+
entry: unknown,
|
|
1777
|
+
conditions: readonly string[],
|
|
1778
|
+
): TargetResolution | undefined {
|
|
1779
|
+
if (typeof entry === 'string') return { target: entry }
|
|
1780
|
+
if (isList(entry)) {
|
|
1781
|
+
for (const member of entry) {
|
|
1782
|
+
const resolved = resolvePackageTarget(member, conditions)
|
|
1783
|
+
if (resolved !== undefined && isPackageTarget(resolved.target)) return resolved
|
|
1784
|
+
}
|
|
1785
|
+
return undefined
|
|
1786
|
+
}
|
|
1787
|
+
if (!isRecord(entry)) return undefined
|
|
1788
|
+
for (const [condition, nested] of Object.entries(entry)) {
|
|
1789
|
+
if (condition !== 'default' && !conditions.includes(condition)) continue
|
|
1790
|
+
const resolved = resolvePackageTarget(nested, conditions)
|
|
1791
|
+
if (resolved !== undefined) return resolved
|
|
1792
|
+
}
|
|
1793
|
+
return undefined
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
// A flat entry, a condition-nested entry, and a fallback list all resolve through
|
|
1797
|
+
// one walker. An entry may declare \`types\` beside \`default\` at its top level
|
|
1798
|
+
// rather than inside \`import\`, so a fixed \`entry.import.types\` lookup is not
|
|
1799
|
+
// equivalent to condition resolution.
|
|
1800
|
+
function resolveTarget(entry: unknown, conditions: readonly string[]): string | undefined {
|
|
1801
|
+
return resolvePackageTarget(entry, conditions)?.target
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
// Whether a path is a physical file. TypeScript's file-existence check refuses a
|
|
1805
|
+
// directory at the same spelling and continues to the outer package scope.
|
|
1806
|
+
function matchesFile(path: string): boolean {
|
|
1807
|
+
try {
|
|
1808
|
+
return statSync(path).isFile()
|
|
1809
|
+
} catch {
|
|
1810
|
+
return false
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
// TypeScript resolves a declaration target by accepting an existing declaration
|
|
1815
|
+
// directly or by substituting beside a JavaScript target. A missing target leaves
|
|
1816
|
+
// the containing condition or fallback list unresolved, so the walk continues.
|
|
1817
|
+
function targetToDeclaration(target: string, installed: string): string | undefined {
|
|
1818
|
+
if (!isPackageTarget(target)) return undefined
|
|
1819
|
+
let declaration = target
|
|
1820
|
+
if (target.endsWith('.cjs')) declaration = \`\${target.slice(0, -4)}.d.cts\`
|
|
1821
|
+
else if (target.endsWith('.mjs')) declaration = \`\${target.slice(0, -4)}.d.mts\`
|
|
1822
|
+
else if (target.endsWith('.js')) declaration = \`\${target.slice(0, -3)}.d.ts\`
|
|
1823
|
+
else if (!isDeclaration(target)) return undefined
|
|
1824
|
+
return matchesFile(join(installed, declaration)) ? declaration : undefined
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
// The declaration TypeScript resolves through one importing format's conditions.
|
|
1828
|
+
// Condition objects keep manifest order, and arrays keep fallback order.
|
|
1829
|
+
function resolveDeclaration(
|
|
1830
|
+
entry: unknown,
|
|
1831
|
+
conditions: readonly string[],
|
|
1832
|
+
installed: string,
|
|
1833
|
+
): string | undefined {
|
|
1834
|
+
if (typeof entry === 'string') return targetToDeclaration(entry, installed)
|
|
1835
|
+
if (isList(entry)) {
|
|
1836
|
+
for (const member of entry) {
|
|
1837
|
+
const resolved = resolveDeclaration(member, conditions, installed)
|
|
1838
|
+
if (resolved !== undefined) return resolved
|
|
1839
|
+
}
|
|
1840
|
+
return undefined
|
|
1841
|
+
}
|
|
1842
|
+
if (!isRecord(entry)) return undefined
|
|
1843
|
+
for (const [condition, nested] of Object.entries(entry)) {
|
|
1844
|
+
if (condition !== 'default' && !conditions.includes(condition)) continue
|
|
1845
|
+
const resolved = resolveDeclaration(nested, conditions, installed)
|
|
1846
|
+
if (resolved !== undefined) return resolved
|
|
1847
|
+
}
|
|
1848
|
+
return undefined
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
// The nearest package scope that decides a \`.d.ts\` declaration's module format. A
|
|
1852
|
+
// physical nested manifest starts a scope even when it omits \`type\` or cannot be
|
|
1853
|
+
// parsed. A directory at that spelling is not a manifest, so the walk continues.
|
|
1854
|
+
function readPackageType(installed: string, target: string): unknown {
|
|
1855
|
+
let directory = dirname(join(installed, target))
|
|
1856
|
+
while (true) {
|
|
1857
|
+
const path = join(directory, 'package.json')
|
|
1858
|
+
if (matchesFile(path)) {
|
|
1859
|
+
try {
|
|
1860
|
+
const manifest = readJson(path)
|
|
1861
|
+
return isRecord(manifest) ? manifest.type : undefined
|
|
1862
|
+
} catch {
|
|
1863
|
+
return undefined
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
if (directory === installed) return undefined
|
|
1867
|
+
const parent = dirname(directory)
|
|
1868
|
+
if (parent === directory) return undefined
|
|
1869
|
+
directory = parent
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
function resolvesBrowser(entry: unknown): boolean {
|
|
1874
|
+
const module = resolveTarget(entry, RUNTIME_CONDITIONS.browser)
|
|
1875
|
+
if (module !== undefined && module.startsWith(BROWSER_OUTPUT)) return true
|
|
1876
|
+
if (module === undefined) return false
|
|
1877
|
+
const imported = resolveTarget(entry, RUNTIME_CONDITIONS.module)
|
|
1878
|
+
const required = resolveTarget(entry, RUNTIME_CONDITIONS.commonjs)
|
|
1879
|
+
return module !== imported && module !== required
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
// Whether the target selected by Node's CommonJS conditions is a module require can
|
|
1883
|
+
// load. A JavaScript target takes its own nearest package scope. Native addons and
|
|
1884
|
+
// extensionless targets have their own CommonJS handlers.
|
|
1885
|
+
function resolvesCommonJS(entry: unknown, installed: string): boolean {
|
|
1886
|
+
const target = resolveTarget(entry, RUNTIME_CONDITIONS.commonjs)
|
|
1887
|
+
if (target === undefined) return false
|
|
1888
|
+
const name = target.slice(target.lastIndexOf('/') + 1)
|
|
1889
|
+
if (name.endsWith('.cjs')) return true
|
|
1890
|
+
if (name.endsWith('.mjs')) return false
|
|
1891
|
+
if (name.endsWith('.node')) return true
|
|
1892
|
+
if (!name.includes('.')) return true
|
|
1893
|
+
return name.endsWith('.js') && readPackageType(installed, target) !== 'module'
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1896
|
+
// Whether the declaration selected by a typed CommonJS consumer admits that entry.
|
|
1897
|
+
// A \`.d.cts\` declaration admits and a \`.d.mts\` declaration refuses. A \`.d.ts\`
|
|
1898
|
+
// declaration takes its own nearest package scope.
|
|
1899
|
+
function declaresCommonJS(entry: unknown, installed: string): boolean {
|
|
1900
|
+
const declaration = resolveDeclaration(entry, DECLARATION_CONDITIONS.commonjs, installed)
|
|
1901
|
+
if (declaration === undefined) return false
|
|
1902
|
+
if (declaration.endsWith('.d.cts')) return true
|
|
1903
|
+
if (declaration.endsWith('.d.mts')) return false
|
|
1904
|
+
return declaration.endsWith('.d.ts') && readPackageType(installed, declaration) !== 'module'
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1907
|
+
// Every target an entry names under any condition. A fallback list omits members
|
|
1908
|
+
// Node rejects during package-target validation, because no reader can take them.
|
|
1909
|
+
function collectTargets(entry: unknown): readonly string[] {
|
|
1910
|
+
if (typeof entry === 'string') return [entry]
|
|
1911
|
+
if (isList(entry)) return entry.flatMap(collectTargets).filter(isPackageTarget)
|
|
1912
|
+
if (!isRecord(entry)) return []
|
|
1913
|
+
return Object.values(entry).flatMap((nested) => collectTargets(nested))
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
// Whether a target is a file a runtime loads for its names, which is what a
|
|
1917
|
+
// declaration is owed for. The extension on the target's own file name decides it,
|
|
1918
|
+
// and a name carrying no extension is code: \`require\` reads such a file through its
|
|
1919
|
+
// JavaScript handler, so an extensionless target loads and publishes names. Node
|
|
1920
|
+
// loads \`.node\` through its native-addon handler. Every other extension is an asset
|
|
1921
|
+
// a consumer reads rather than imports — a stylesheet, a WebAssembly binary, the
|
|
1922
|
+
// \`"./package.json"\` manifest pointer, and a declaration alike.
|
|
1923
|
+
// The cost is an extensionless file published for a reader, such as a \`LICENSE\`:
|
|
1924
|
+
// that target reports undeclared until it is given an extension or a declaration.
|
|
1925
|
+
function isModule(target: string): boolean {
|
|
1926
|
+
const name = target.slice(target.lastIndexOf('/') + 1)
|
|
1927
|
+
const dot = name.lastIndexOf('.')
|
|
1928
|
+
if (name.endsWith(ADDON_EXTENSION)) return true
|
|
1929
|
+
return dot === -1 || MODULE_EXTENSIONS.includes(name.slice(dot))
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
// Whether a resolved target is a declaration rather than the JavaScript a
|
|
1933
|
+
// \`default\` branch answers with when the entry declares no \`types\` condition.
|
|
1934
|
+
function isDeclaration(target: string): boolean {
|
|
1935
|
+
return DECLARATION_EXTENSIONS.some((extension) => target.endsWith(extension))
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1938
|
+
// The declarations the Node module, Node CommonJS, and browser drives compare
|
|
1939
|
+
// against. Each field uses the conditions of the TypeScript consumer paired with
|
|
1940
|
+
// that runtime. A JavaScript target resolves through TypeScript's adjacent
|
|
1941
|
+
// declaration substitution rather than standing in for the declaration itself.
|
|
1942
|
+
function readDeclaration(entry: unknown, installed: string): Entry['declaration'] {
|
|
1943
|
+
return {
|
|
1944
|
+
module: resolveDeclaration(entry, DECLARATION_CONDITIONS.module, installed),
|
|
1945
|
+
commonjs: resolveDeclaration(entry, DECLARATION_CONDITIONS.commonjs, installed),
|
|
1946
|
+
browser: resolveDeclaration(entry, DECLARATION_CONDITIONS.browser, installed),
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
// The entries one compile driver can resolve under its own conditions.
|
|
1951
|
+
function selectEntries(entries: readonly Entry[], conditions: readonly string[]): readonly Entry[] {
|
|
1952
|
+
return entries.filter(
|
|
1953
|
+
(entry) =>
|
|
1954
|
+
resolveTarget(entry.mapping, conditions) !== undefined &&
|
|
1955
|
+
(!conditions.includes('require') || entry.commonjs),
|
|
1956
|
+
)
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
// Require-loadable entries that declare CommonJS support but a typed CommonJS
|
|
1960
|
+
// consumer cannot compile against. A default branch resolving under the require
|
|
1961
|
+
// condition set makes no CommonJS claim.
|
|
1962
|
+
function selectUntypable(entries: readonly Entry[], installed: string): readonly Entry[] {
|
|
1963
|
+
return entries.filter(
|
|
1964
|
+
(entry) =>
|
|
1965
|
+
entry.required &&
|
|
1966
|
+
isRecord(entry.mapping) &&
|
|
1967
|
+
Object.hasOwn(entry.mapping, 'require') &&
|
|
1968
|
+
!declaresCommonJS(entry.mapping, installed),
|
|
1969
|
+
)
|
|
1970
|
+
}
|
|
1971
|
+
|
|
1972
|
+
// The value exports a declaration publishes, read through the compiler's checker
|
|
1973
|
+
// over the module symbol rather than off the declaration text. An alias resolves to
|
|
1974
|
+
// what it names, so a re-export counts as the thing it re-exports, and a type-only
|
|
1975
|
+
// symbol is dropped because no runtime publishes one.
|
|
1976
|
+
function readDeclaredExports(declaration: string): readonly string[] {
|
|
1977
|
+
const program = ts.createProgram([declaration], {
|
|
1978
|
+
module: ts.ModuleKind.ESNext,
|
|
1979
|
+
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
1980
|
+
noEmit: true,
|
|
1981
|
+
skipLibCheck: true,
|
|
1982
|
+
target: ts.ScriptTarget.ESNext,
|
|
1983
|
+
})
|
|
1984
|
+
const source = program.getSourceFile(declaration)
|
|
1985
|
+
if (source === undefined) throw new Error(\`The declaration \${declaration} was not read\`)
|
|
1986
|
+
const checker = program.getTypeChecker()
|
|
1987
|
+
const symbol = checker.getSymbolAtLocation(source)
|
|
1988
|
+
if (symbol === undefined) throw new Error(\`\${declaration} declares no module symbol\`)
|
|
1989
|
+
const values: string[] = []
|
|
1990
|
+
for (const exported of checker.getExportsOfModule(symbol)) {
|
|
1991
|
+
const direct = (exported.flags & ts.SymbolFlags.Alias) === 0
|
|
1992
|
+
const resolved = direct ? exported : checker.getAliasedSymbol(exported)
|
|
1993
|
+
if ((resolved.flags & ts.SymbolFlags.Value) !== 0) values.push(exported.getName())
|
|
1994
|
+
}
|
|
1995
|
+
return [...values].sort()
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
// The diagnostics a consumer compiling against the installed declarations reports,
|
|
1999
|
+
// flattened to their messages so a failure names what the consumer could not do.
|
|
2000
|
+
function compileConsumer(
|
|
2001
|
+
entry: string,
|
|
2002
|
+
resolution: ts.ModuleResolutionKind,
|
|
2003
|
+
module: ts.ModuleKind,
|
|
2004
|
+
): readonly string[] {
|
|
2005
|
+
const program = ts.createProgram([entry], {
|
|
2006
|
+
module,
|
|
2007
|
+
moduleResolution: resolution,
|
|
2008
|
+
noEmit: true,
|
|
2009
|
+
skipLibCheck: true,
|
|
2010
|
+
strict: true,
|
|
2011
|
+
target: ts.ScriptTarget.ESNext,
|
|
2012
|
+
})
|
|
2013
|
+
return ts
|
|
2014
|
+
.getPreEmitDiagnostics(program)
|
|
2015
|
+
.map((diagnostic) => ts.flattenDiagnosticMessageText(diagnostic.messageText, ' '))
|
|
2016
|
+
}
|
|
2017
|
+
|
|
2018
|
+
// One consumer module importing every installed entry, written where its own
|
|
2019
|
+
// resolution finds the installed package.
|
|
2020
|
+
function writeConsumerProbe(stage: Stage, path: string, specifiers: readonly string[]): string {
|
|
2021
|
+
const names: string[] = []
|
|
2022
|
+
const bindings: string[] = []
|
|
2023
|
+
for (const [index, specifier] of specifiers.entries()) {
|
|
2024
|
+
const binding = \`entry\${String(index)}\`
|
|
2025
|
+
names.push(binding)
|
|
2026
|
+
bindings.push(\`import * as \${binding} from \${JSON.stringify(specifier)}\`)
|
|
2027
|
+
}
|
|
2028
|
+
const target = join(stage.consumer, path)
|
|
2029
|
+
writeFile(target, \`\${bindings.join('\\n')}\\nexport const surface = [\${names.join(', ')}]\\n\`)
|
|
2030
|
+
return target
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
// The runtime key set a real process reads off one installed entry under one
|
|
2034
|
+
// condition. The driver is a file rather than an \`--eval\` string, so the specifier
|
|
2035
|
+
// travels as an argument and nothing needs escaping.
|
|
2036
|
+
function driveRuntime(stage: Stage, specifier: string, driver: string): readonly string[] {
|
|
2037
|
+
const result = runNode([join(stage.consumer, driver), specifier], stage.consumer)
|
|
2038
|
+
if (result.status !== 0) {
|
|
2039
|
+
throw new Error(\`Loading \${specifier} from the consumer failed: \${readOutput(result)}\`)
|
|
2040
|
+
}
|
|
2041
|
+
const published: unknown = JSON.parse(result.stdout)
|
|
2042
|
+
if (!isNames(published)) throw new Error(\`The driver printed no name list for \${specifier}\`)
|
|
2043
|
+
return published
|
|
2044
|
+
}
|
|
2045
|
+
{{helpers}}
|
|
2046
|
+
// Pack this workspace, install the archive into an isolated consumer, and read the
|
|
2047
|
+
// published surface back off the installed tree. Every later claim reads this
|
|
2048
|
+
// result, so a failure here is raised where it happens rather than once per entry.
|
|
2049
|
+
function buildStage(): Stage {
|
|
2050
|
+
const packed = join(SCRATCH, 'packed')
|
|
2051
|
+
const consumer = join(SCRATCH, 'consumer')
|
|
2052
|
+
mkdirSync(packed, { recursive: true })
|
|
2053
|
+
const pack = runNpm(['pack', '--ignore-scripts', '--pack-destination', packed], ROOT)
|
|
2054
|
+
if (pack.status !== 0) throw new Error(\`npm pack refused this workspace: \${readOutput(pack)}\`)
|
|
2055
|
+
const archives = readdirSync(packed).filter((name) => name.endsWith('.tgz'))
|
|
2056
|
+
const archive = archives[0]
|
|
2057
|
+
if (archives.length !== 1 || archive === undefined) {
|
|
2058
|
+
throw new Error(\`npm pack wrote no single archive: \${archives.join(', ')}\`)
|
|
2059
|
+
}
|
|
2060
|
+
writeFile(join(consumer, 'package.json'), CONSUMER_MANIFEST)
|
|
2061
|
+
writeFile(join(consumer, ESM_DRIVER), ESM_DRIVER_SOURCE)
|
|
2062
|
+
writeFile(join(consumer, CJS_DRIVER), CJS_DRIVER_SOURCE)
|
|
2063
|
+
const install = runNpm(
|
|
2064
|
+
['install', '--ignore-scripts', '--no-audit', '--no-fund', join(packed, archive)],
|
|
2065
|
+
consumer,
|
|
2066
|
+
)
|
|
2067
|
+
if (install.status !== 0) {
|
|
2068
|
+
throw new Error(\`Installing the packed archive failed: \${readOutput(install)}\`)
|
|
2069
|
+
}
|
|
2070
|
+
const name = readManifestName(join(ROOT, 'package.json'))
|
|
2071
|
+
const installed = join(consumer, 'node_modules', ...name.split('/'))
|
|
2072
|
+
const manifest = readJson(join(installed, 'package.json'))
|
|
2073
|
+
if (!isRecord(manifest) || !isRecord(manifest.exports)) {
|
|
2074
|
+
throw new Error('The installed manifest publishes no exports map')
|
|
2075
|
+
}
|
|
2076
|
+
const entries: Entry[] = []
|
|
2077
|
+
const targets: string[] = []
|
|
2078
|
+
const subpaths: string[] = []
|
|
2079
|
+
const undeclared: string[] = []
|
|
2080
|
+
const excluded: string[] = []
|
|
2081
|
+
for (const [subpath, entry] of Object.entries(manifest.exports)) {
|
|
2082
|
+
const files = collectTargets(entry)
|
|
2083
|
+
targets.push(...files)
|
|
2084
|
+
subpaths.push(subpath)
|
|
2085
|
+
const declaration = readDeclaration(entry, installed)
|
|
2086
|
+
// A subpath resolving no declaration is partitioned rather than dropped. It is a
|
|
2087
|
+
// defect when a runtime loads one of its targets for names, because a consumer
|
|
2088
|
+
// importing it compiles against nothing under \`node16\`. It is an excluded
|
|
2089
|
+
// publication otherwise: the \`"./package.json"\` manifest pointer and a stylesheet
|
|
2090
|
+
// are published for a reader rather than an importer.
|
|
2091
|
+
if (
|
|
2092
|
+
declaration.module === undefined &&
|
|
2093
|
+
declaration.commonjs === undefined &&
|
|
2094
|
+
declaration.browser === undefined
|
|
2095
|
+
) {
|
|
2096
|
+
if (files.some(isModule)) undeclared.push(subpath)
|
|
2097
|
+
else excluded.push(subpath)
|
|
2098
|
+
continue
|
|
2099
|
+
}
|
|
2100
|
+
const imported = resolveTarget(entry, RUNTIME_CONDITIONS.module)
|
|
2101
|
+
const requiredTarget = resolveTarget(entry, RUNTIME_CONDITIONS.commonjs)
|
|
2102
|
+
const browserTarget = resolveTarget(entry, RUNTIME_CONDITIONS.browser)
|
|
2103
|
+
const browser = resolvesBrowser(entry)
|
|
2104
|
+
const required = requiredTarget !== undefined && !(browser && requiredTarget === browserTarget)
|
|
2105
|
+
const commonjs = required && resolvesCommonJS(entry, installed)
|
|
2106
|
+
entries.push({
|
|
2107
|
+
subpath,
|
|
2108
|
+
specifier: subpath === '.' ? name : \`\${name}\${subpath.slice(1)}\`,
|
|
2109
|
+
mapping: entry,
|
|
2110
|
+
declaration: {
|
|
2111
|
+
module: declaration.module === undefined ? undefined : join(installed, declaration.module),
|
|
2112
|
+
commonjs:
|
|
2113
|
+
declaration.commonjs === undefined ? undefined : join(installed, declaration.commonjs),
|
|
2114
|
+
browser:
|
|
2115
|
+
declaration.browser === undefined ? undefined : join(installed, declaration.browser),
|
|
2116
|
+
},
|
|
2117
|
+
browser,
|
|
2118
|
+
module: imported !== undefined && !(browser && imported === browserTarget),
|
|
2119
|
+
commonjs,
|
|
2120
|
+
required,
|
|
2121
|
+
})
|
|
2122
|
+
}
|
|
2123
|
+
return { consumer, installed, archives, entries, subpaths, undeclared, excluded, targets }
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2126
|
+
const SCRATCH = mkdtempSync(join(tmpdir(), 'distribution-'))
|
|
2127
|
+
const CACHE = join(SCRATCH, 'cache')
|
|
2128
|
+
mkdirSync(CACHE, { recursive: true })
|
|
2129
|
+
// The scratch tree holds the npm cache, the packed archive, and the installed
|
|
2130
|
+
// consumer, so its removal is registered before the first thing that can throw.
|
|
2131
|
+
afterAll(() => {
|
|
2132
|
+
rmSync(SCRATCH, { force: true, recursive: true })
|
|
2133
|
+
})
|
|
2134
|
+
|
|
2135
|
+
// Installing the packed archive resolves its own runtime dependencies, so an
|
|
2136
|
+
// unreachable registry leaves nothing to measure. Under release that is the gate
|
|
2137
|
+
// failing; anywhere else the suite skips and names the mechanism it wanted.
|
|
2138
|
+
//
|
|
2139
|
+
// A module that throws while loading never reaches the \`afterAll\` it registered,
|
|
2140
|
+
// so every throw here removes the scratch tree on its way out.
|
|
2141
|
+
function openStage(): Stage | undefined {
|
|
2142
|
+
try {
|
|
2143
|
+
if (runNpm(PING, ROOT).status !== 0) {
|
|
2144
|
+
if (!RELEASE) return undefined
|
|
2145
|
+
throw new Error(
|
|
2146
|
+
'The release gate requires a reachable npm registry, and npm ping did not answer',
|
|
2147
|
+
)
|
|
2148
|
+
}
|
|
2149
|
+
return buildStage()
|
|
2150
|
+
} catch (error) {
|
|
2151
|
+
rmSync(SCRATCH, { force: true, recursive: true })
|
|
2152
|
+
throw error
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
|
|
2156
|
+
const STAGE = openStage()
|
|
2157
|
+
const STAGED = STAGE !== undefined
|
|
2158
|
+
|
|
2159
|
+
describe('distribution classifiers', () => {
|
|
2160
|
+
it('classifies synthetic export mappings without a registry stage', () => {
|
|
2161
|
+
const root = join(SCRATCH, 'classifiers')
|
|
2162
|
+
writeFile(
|
|
2163
|
+
join(root, 'package.json'),
|
|
2164
|
+
JSON.stringify({
|
|
2165
|
+
type: 'commonjs',
|
|
2166
|
+
exports: {
|
|
2167
|
+
condition: { browser: './b.js', default: './n.js' },
|
|
2168
|
+
convention: { default: './dist/src/browser/index.js' },
|
|
2169
|
+
universal: { default: './shared.js' },
|
|
2170
|
+
'import-shared': {
|
|
2171
|
+
browser: './shared.mjs',
|
|
2172
|
+
import: './shared.mjs',
|
|
2173
|
+
default: './node.js',
|
|
2174
|
+
},
|
|
2175
|
+
'require-shared': {
|
|
2176
|
+
browser: './shared.cjs',
|
|
2177
|
+
require: './shared.cjs',
|
|
2178
|
+
default: './node.js',
|
|
2179
|
+
},
|
|
2180
|
+
node: { node: './node.js', default: './node.js' },
|
|
2181
|
+
silent: { 'module-sync': './x.cjs', import: './x.mjs' },
|
|
2182
|
+
module: { require: './x.mjs' },
|
|
2183
|
+
'nested-module': { require: './module/x.js' },
|
|
2184
|
+
'nested-commonjs': { require: './commonjs/x.js' },
|
|
2185
|
+
esm: { import: './x.mjs' },
|
|
2186
|
+
},
|
|
2187
|
+
}),
|
|
2188
|
+
)
|
|
2189
|
+
writeFile(join(root, 'module/package.json'), '{ "type": "module" }\\n')
|
|
2190
|
+
writeFile(join(root, 'commonjs/package.json'), '{ "type": "commonjs" }\\n')
|
|
2191
|
+
const manifest = readJson(join(root, 'package.json'))
|
|
2192
|
+
if (!isRecord(manifest) || !isRecord(manifest.exports)) {
|
|
2193
|
+
throw new Error('The classifier fixture declares no exports map')
|
|
2194
|
+
}
|
|
2195
|
+
const mappings = manifest.exports
|
|
2196
|
+
expect({
|
|
2197
|
+
condition: resolvesBrowser(mappings.condition),
|
|
2198
|
+
convention: resolvesBrowser(mappings.convention),
|
|
2199
|
+
universal: resolvesBrowser(mappings.universal),
|
|
2200
|
+
import: resolvesBrowser(mappings['import-shared']),
|
|
2201
|
+
require: resolvesBrowser(mappings['require-shared']),
|
|
2202
|
+
node: resolvesBrowser(mappings.node),
|
|
2203
|
+
}).toStrictEqual({
|
|
2204
|
+
condition: true,
|
|
2205
|
+
convention: true,
|
|
2206
|
+
universal: false,
|
|
2207
|
+
import: false,
|
|
2208
|
+
require: false,
|
|
2209
|
+
node: false,
|
|
2210
|
+
})
|
|
2211
|
+
expect({
|
|
2212
|
+
silent: resolvesCommonJS(mappings.silent, root),
|
|
2213
|
+
module: resolvesCommonJS(mappings.module, root),
|
|
2214
|
+
nestedModule: resolvesCommonJS(mappings['nested-module'], root),
|
|
2215
|
+
nestedCommonJS: resolvesCommonJS(mappings['nested-commonjs'], root),
|
|
2216
|
+
esm: resolvesCommonJS(mappings.esm, root),
|
|
2217
|
+
}).toStrictEqual({
|
|
2218
|
+
silent: true,
|
|
2219
|
+
module: false,
|
|
2220
|
+
nestedModule: false,
|
|
2221
|
+
nestedCommonJS: true,
|
|
2222
|
+
esm: false,
|
|
2223
|
+
})
|
|
2224
|
+
})
|
|
2225
|
+
})
|
|
2226
|
+
|
|
2227
|
+
// The staged consumer, or a skip naming what the run could not reach. \`it.skipIf\`
|
|
2228
|
+
// carries no reason, so the gate sits here where the test context can state one.
|
|
2229
|
+
function requireStage(context: TestContext): Stage {
|
|
2230
|
+
if (!STAGED) {
|
|
2231
|
+
return context.skip('\`npm ping\` did not answer, so nothing was packed or installed')
|
|
2232
|
+
}
|
|
2233
|
+
return STAGE
|
|
2234
|
+
}
|
|
2235
|
+
|
|
2236
|
+
describe('installed package consumer', () => {
|
|
2237
|
+
it('packs one archive and installs it in isolation [requires the registry]', (context) => {
|
|
2238
|
+
const stage = requireStage(context)
|
|
2239
|
+
expect(stage.archives).toHaveLength(1)
|
|
2240
|
+
expect(existsSync(join(stage.installed, 'package.json'))).toBe(true)
|
|
2241
|
+
expect(stage.entries.length).toBeGreaterThan(0)
|
|
2242
|
+
})
|
|
2243
|
+
|
|
2244
|
+
it('ships every relative target its exports map names [requires the registry]', (context) => {
|
|
2245
|
+
const stage = requireStage(context)
|
|
2246
|
+
const relative = stage.targets.filter((target) => target.startsWith('./'))
|
|
2247
|
+
expect(relative).not.toStrictEqual([])
|
|
2248
|
+
expect(relative.filter((target) => !existsSync(join(stage.installed, target)))).toStrictEqual(
|
|
2249
|
+
[],
|
|
2250
|
+
)
|
|
2251
|
+
})
|
|
2252
|
+
|
|
2253
|
+
// Every published subpath is driven, excluded by name, or reported here. A dropped
|
|
2254
|
+
// one leaves no trace: no runtime test, no declaration comparison, and no place in
|
|
2255
|
+
// the resolution compile, so the run reports success for a subpath it never
|
|
2256
|
+
// measured.
|
|
2257
|
+
it('declares types for every module it publishes [requires the registry]', (context) => {
|
|
2258
|
+
const stage = requireStage(context)
|
|
2259
|
+
const partitioned = [
|
|
2260
|
+
...stage.entries.map((entry) => entry.subpath),
|
|
2261
|
+
...stage.undeclared,
|
|
2262
|
+
...stage.excluded,
|
|
2263
|
+
]
|
|
2264
|
+
expect(stage.undeclared).toStrictEqual([])
|
|
2265
|
+
expect(partitioned.sort()).toStrictEqual([...stage.subpaths].sort())
|
|
2266
|
+
// A driven subpath answers a runtime condition. One resolving a declaration and
|
|
2267
|
+
// no Node or browser target compiles for a consumer and throws when that consumer
|
|
2268
|
+
// loads it. Each later drive retires itself for that entry, so this assertion names
|
|
2269
|
+
// the subpath rather than counting it as driven.
|
|
2270
|
+
const unreachable = stage.entries.filter(
|
|
2271
|
+
(entry) => !entry.module && !entry.required && !entry.browser,
|
|
2272
|
+
)
|
|
2273
|
+
expect(unreachable.map((entry) => entry.subpath)).toStrictEqual([])
|
|
2274
|
+
const untypable = selectUntypable(stage.entries, stage.installed)
|
|
2275
|
+
expect(untypable.map((entry) => entry.subpath)).toStrictEqual([])
|
|
2276
|
+
})
|
|
2277
|
+
|
|
2278
|
+
it('refuses a subpath its exports map does not name [requires the registry]', (context) => {
|
|
2279
|
+
const stage = requireStage(context)
|
|
2280
|
+
const name = readManifestName(join(stage.installed, 'package.json'))
|
|
2281
|
+
const driver = join(stage.consumer, ESM_DRIVER)
|
|
2282
|
+
const result = runNode([driver, \`\${name}\${ABSENT_SUBPATH}\`], stage.consumer)
|
|
2283
|
+
expect(result.status).not.toBe(0)
|
|
2284
|
+
expect(readOutput(result)).toContain('ERR_PACKAGE_PATH_NOT_EXPORTED')
|
|
2285
|
+
})
|
|
2286
|
+
|
|
2287
|
+
// The absent subpath is the firing control: a resolution that reports nothing
|
|
2288
|
+
// for every published entry has not been shown to resolve anything at all. Each
|
|
2289
|
+
// module format carries its own control, because a format that resolves nothing
|
|
2290
|
+
// is silent for the same reason a resolution that resolves nothing is.
|
|
2291
|
+
it('compiles a consumer under every module resolution [requires the registry]', (context) => {
|
|
2292
|
+
const stage = requireStage(context)
|
|
2293
|
+
const name = readManifestName(join(stage.installed, 'package.json'))
|
|
2294
|
+
const reported: string[] = []
|
|
2295
|
+
const silent: string[] = []
|
|
2296
|
+
for (const driver of RESOLUTIONS) {
|
|
2297
|
+
for (const [extension, format] of FORMATS) {
|
|
2298
|
+
const written = selectEntries(stage.entries, driver.conditions[format])
|
|
2299
|
+
if (written.length === 0) continue
|
|
2300
|
+
const specifiers = written.map((entry) => entry.specifier)
|
|
2301
|
+
const probe = writeConsumerProbe(stage, \`probe.\${driver.label}.\${extension}\`, specifiers)
|
|
2302
|
+
for (const message of compileConsumer(probe, driver.resolution, driver.module)) {
|
|
2303
|
+
reported.push(\`\${driver.label}.\${extension}: \${message}\`)
|
|
2304
|
+
}
|
|
2305
|
+
const absent = [\`\${name}\${ABSENT_SUBPATH}\`]
|
|
2306
|
+
const control = writeConsumerProbe(stage, \`control.\${driver.label}.\${extension}\`, absent)
|
|
2307
|
+
if (compileConsumer(control, driver.resolution, driver.module).length === 0) {
|
|
2308
|
+
silent.push(\`\${driver.label}.\${extension}\`)
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
expect(reported).toStrictEqual([])
|
|
2313
|
+
expect(silent).toStrictEqual([])
|
|
2314
|
+
})
|
|
2315
|
+
{{guard}}})
|
|
2316
|
+
|
|
2317
|
+
for (const entry of STAGE?.entries ?? []) {
|
|
2318
|
+
describe(\`installed entry \${entry.subpath}\`, () => {
|
|
2319
|
+
it.runIf(entry.module)(
|
|
2320
|
+
'publishes what it declares to a Node import, and no more',
|
|
2321
|
+
(context) => {
|
|
2322
|
+
const declaration = entry.declaration.module
|
|
2323
|
+
if (declaration === undefined) {
|
|
2324
|
+
throw new Error(\`\${entry.subpath} publishes no import declaration\`)
|
|
2325
|
+
}
|
|
2326
|
+
const published = driveRuntime(requireStage(context), entry.specifier, ESM_DRIVER)
|
|
2327
|
+
expect(published).toStrictEqual(readDeclaredExports(declaration))
|
|
2328
|
+
},
|
|
2329
|
+
)
|
|
2330
|
+
|
|
2331
|
+
it.runIf(entry.required)(
|
|
2332
|
+
'publishes what it declares to a Node require, and no more',
|
|
2333
|
+
(context) => {
|
|
2334
|
+
const declaration = entry.declaration.commonjs
|
|
2335
|
+
if (declaration === undefined) {
|
|
2336
|
+
throw new Error(\`\${entry.subpath} publishes no require declaration\`)
|
|
2337
|
+
}
|
|
2338
|
+
const published = driveRuntime(requireStage(context), entry.specifier, CJS_DRIVER)
|
|
2339
|
+
expect(published).toStrictEqual(readDeclaredExports(declaration))
|
|
2340
|
+
},
|
|
2341
|
+
)
|
|
2342
|
+
{{drive}} })
|
|
2343
|
+
}
|
|
2344
|
+
`,
|
|
2345
|
+
transport: `import { createServer } from 'node:http'
|
|
2346
|
+
`,
|
|
2347
|
+
types: `import type { PlaywrightProviderOptions } from '@vitest/browser-playwright'
|
|
2348
|
+
import type { Browser } from 'playwright'
|
|
2349
|
+
`,
|
|
2350
|
+
launcher: `import { chromium } from 'playwright'
|
|
2351
|
+
import { build } from 'vite'
|
|
2352
|
+
import { resolveBrowser, resolvePinnedBrowser } from '../configs/browsers.js'
|
|
2353
|
+
`,
|
|
2354
|
+
helpers: `
|
|
2355
|
+
const BROWSER_PAGE = \`<!doctype html>
|
|
2356
|
+
<html lang="en">
|
|
2357
|
+
<head>
|
|
2358
|
+
<meta charset="UTF-8" />
|
|
2359
|
+
<title>Distribution</title>
|
|
2360
|
+
</head>
|
|
2361
|
+
<body>
|
|
2362
|
+
<script type="module" src="./main.js"><\/script>
|
|
2363
|
+
</body>
|
|
2364
|
+
</html>
|
|
2365
|
+
\`
|
|
2366
|
+
|
|
2367
|
+
function readContentType(path: string): string {
|
|
2368
|
+
if (path.endsWith('.html')) return 'text/html'
|
|
2369
|
+
if (path.endsWith('.js')) return 'text/javascript'
|
|
2370
|
+
if (path.endsWith('.css')) return 'text/css'
|
|
2371
|
+
if (path.endsWith('.json') || path.endsWith('.map')) return 'application/json'
|
|
2372
|
+
return 'application/octet-stream'
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2375
|
+
// \`resolveBrowser\` answers with provider options and never reports absence: its
|
|
2376
|
+
// last resort is a channel nothing verified. So the launch is attempted and its
|
|
2377
|
+
// rejection classified, rather than probed for and ruled on.
|
|
2378
|
+
function describeBrowser(options: PlaywrightProviderOptions): string {
|
|
2379
|
+
const endpoint = options.connectOptions?.wsEndpoint
|
|
2380
|
+
if (endpoint !== undefined) return \`the browser server at \${endpoint}\`
|
|
2381
|
+
const executable = options.launchOptions?.executablePath
|
|
2382
|
+
if (executable !== undefined) return \`the executable at \${executable}\`
|
|
2383
|
+
const channel = options.launchOptions?.channel
|
|
2384
|
+
if (channel !== undefined) return \`the \${channel} channel\`
|
|
2385
|
+
return 'the Chromium Playwright installed for itself'
|
|
2386
|
+
}
|
|
2387
|
+
|
|
2388
|
+
async function launchBrowser(options: PlaywrightProviderOptions): Promise<Browser> {
|
|
2389
|
+
const endpoint = options.connectOptions?.wsEndpoint
|
|
2390
|
+
if (endpoint !== undefined) return chromium.connect(endpoint)
|
|
2391
|
+
return chromium.launch({ ...options.launchOptions, headless: true })
|
|
2392
|
+
}
|
|
2393
|
+
|
|
2394
|
+
// A consumer of one installed browser entry, bundled by the Vite toolchain this
|
|
2395
|
+
// workspace already declares. Nothing is stubbed: the bundle resolves the installed
|
|
2396
|
+
// package and its whole transitive graph as an application consuming it would.
|
|
2397
|
+
async function bundleEntry(stage: Stage, entry: Entry): Promise<string> {
|
|
2398
|
+
const page = join(stage.consumer, 'pages', entry.subpath.replaceAll(/[^\\w]+/gu, '-'))
|
|
2399
|
+
const specifier = JSON.stringify(entry.specifier)
|
|
2400
|
+
writeFile(join(page, 'index.html'), BROWSER_PAGE)
|
|
2401
|
+
writeFile(
|
|
2402
|
+
join(page, 'main.js'),
|
|
2403
|
+
\`import * as entry from \${specifier}\\nglobalThis.subject = Object.keys(entry).sort()\\n\`,
|
|
2404
|
+
)
|
|
2405
|
+
await build({
|
|
2406
|
+
base: './',
|
|
2407
|
+
build: { emptyOutDir: true, outDir: 'bundle' },
|
|
2408
|
+
configFile: false,
|
|
2409
|
+
logLevel: 'error',
|
|
2410
|
+
root: page,
|
|
2411
|
+
})
|
|
2412
|
+
return join(page, 'bundle')
|
|
2413
|
+
}
|
|
2414
|
+
|
|
2415
|
+
// The key set the bundled module publishes in a real browser, read off the page
|
|
2416
|
+
// once it has loaded over a loopback server. A module that never evaluated
|
|
2417
|
+
// publishes nothing, and a page error is raised rather than compared away.
|
|
2418
|
+
async function readBrowserExports(browser: Browser, bundle: string): Promise<readonly string[]> {
|
|
2419
|
+
const server = createServer((request, response) => {
|
|
2420
|
+
const asked = request.url === undefined || request.url === '/' ? '/index.html' : request.url
|
|
2421
|
+
const path = join(bundle, decodeURIComponent(asked))
|
|
2422
|
+
if (!path.startsWith(bundle) || !existsSync(path)) {
|
|
2423
|
+
response.writeHead(404)
|
|
2424
|
+
response.end()
|
|
2425
|
+
return
|
|
2426
|
+
}
|
|
2427
|
+
response.writeHead(200, { 'content-type': readContentType(path) })
|
|
2428
|
+
response.end(readFileSync(path))
|
|
2429
|
+
})
|
|
2430
|
+
try {
|
|
2431
|
+
await new Promise<void>((settle) => {
|
|
2432
|
+
server.listen(0, '127.0.0.1', settle)
|
|
2433
|
+
})
|
|
2434
|
+
const address = server.address()
|
|
2435
|
+
if (address === null || typeof address === 'string') {
|
|
2436
|
+
throw new Error('The bundle server bound no port')
|
|
2437
|
+
}
|
|
2438
|
+
const page = await browser.newPage()
|
|
2439
|
+
const failures: string[] = []
|
|
2440
|
+
page.on('pageerror', (error) => failures.push(String(error)))
|
|
2441
|
+
await page.goto(\`http://127.0.0.1:\${String(address.port)}/\`, { waitUntil: 'load' })
|
|
2442
|
+
const published: unknown = await page.evaluate('globalThis.subject')
|
|
2443
|
+
if (failures.length > 0) throw new Error(\`The bundle raised \${failures.join(' | ')}\`)
|
|
2444
|
+
if (!isNames(published)) throw new Error('The bundled module published no name list')
|
|
2445
|
+
return published
|
|
2446
|
+
} finally {
|
|
2447
|
+
server.close()
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2450
|
+
`,
|
|
2451
|
+
drive: `
|
|
2452
|
+
it.runIf(entry.browser)(
|
|
2453
|
+
'publishes what it declares to a real browser, and no more [requires a browser]',
|
|
2454
|
+
async (context) => {
|
|
2455
|
+
const stage = requireStage(context)
|
|
2456
|
+
const declaration = entry.declaration.browser
|
|
2457
|
+
if (declaration === undefined) {
|
|
2458
|
+
throw new Error(\`\${entry.subpath} publishes no browser declaration\`)
|
|
2459
|
+
}
|
|
2460
|
+
const options = resolveBrowser(resolvePinnedBrowser(), process.platform, process.env)
|
|
2461
|
+
const browser = await launchBrowser(options).catch((error: unknown) => {
|
|
2462
|
+
const cause = \`\${describeBrowser(options)} was rejected: \${String(error)}\`
|
|
2463
|
+
if (RELEASE) throw new Error(\`The release gate requires a browser, and \${cause}\`)
|
|
2464
|
+
return context.skip(\`No browser launched. \${cause}\`)
|
|
2465
|
+
})
|
|
2466
|
+
try {
|
|
2467
|
+
const bundle = await bundleEntry(stage, entry)
|
|
2468
|
+
expect(await readBrowserExports(browser, bundle)).toStrictEqual(
|
|
2469
|
+
readDeclaredExports(declaration),
|
|
2470
|
+
)
|
|
2471
|
+
} finally {
|
|
2472
|
+
await browser.close()
|
|
2473
|
+
}
|
|
2474
|
+
},
|
|
2475
|
+
)
|
|
2476
|
+
`,
|
|
2477
|
+
guard: `
|
|
2478
|
+
// This proof drives a Node import and a Node require and carries no browser
|
|
2479
|
+
// branch: the workspace published no browser face when it was written, and the
|
|
2480
|
+
// browser drive measures the packed artifact, so only a published face is owed
|
|
2481
|
+
// one. A private browser application does not select this branch. It declares the
|
|
2482
|
+
// browser launcher and its Vitest browser provider and gets the generated browser
|
|
2483
|
+
// configuration module beside it, but installed browser tooling does not stand for
|
|
2484
|
+
// a published browser face. \`vite\` selects nothing either, though the branch
|
|
2485
|
+
// imports it: scaffold puts \`vite\` in every workspace's base development
|
|
2486
|
+
// dependencies, whatever that workspace publishes. The later Node
|
|
2487
|
+
// \`it.runIf\` predicates retire each matching Node drive for a face published
|
|
2488
|
+
// later, which leaves nothing measuring it. So it reddens here and names the
|
|
2489
|
+
// subpath a browser branch is owed for. A workspace that gains one deletes this
|
|
2490
|
+
// file and runs the \`repair\` verb, which writes the variant carrying that branch.
|
|
2491
|
+
it('publishes no browser face this proof cannot drive [requires the registry]', (context) => {
|
|
2492
|
+
const stage = requireStage(context)
|
|
2493
|
+
const faces = stage.entries.filter((entry) => entry.browser)
|
|
2494
|
+
expect(faces.map((entry) => entry.subpath)).toStrictEqual([])
|
|
2495
|
+
})
|
|
2496
|
+
`
|
|
2497
|
+
}),
|
|
1594
2498
|
integration: `{{imports}}import { describe, expect, it } from 'vitest'
|
|
1595
2499
|
|
|
1596
2500
|
describe('workspace integration', () => {
|
|
@@ -1870,6 +2774,37 @@ var isDependency = (0, _orkestrel_contract.recordOf)({
|
|
|
1870
2774
|
optional: _orkestrel_contract.isBoolean
|
|
1871
2775
|
}, ["optional"]);
|
|
1872
2776
|
/**
|
|
2777
|
+
* Narrow a value to a {@link ManifestScript}.
|
|
2778
|
+
*
|
|
2779
|
+
* @remarks
|
|
2780
|
+
* Structural and bounded, exactly as {@link isDependency} is: a script name
|
|
2781
|
+
* and a script command are free text a manifest may carry, and which values a
|
|
2782
|
+
* region writer is willing to overwrite is the caller's decision rather than
|
|
2783
|
+
* this guard's.
|
|
2784
|
+
*
|
|
2785
|
+
* @example
|
|
2786
|
+
* ```ts
|
|
2787
|
+
* import { isManifestScript } from '@orkestrel/scaffold'
|
|
2788
|
+
*
|
|
2789
|
+
* isManifestScript({ name: 'test', command: 'vitest run', accepted: [] }) // true
|
|
2790
|
+
* isManifestScript({ name: 'test', command: 'vitest run' }) // false
|
|
2791
|
+
* ```
|
|
2792
|
+
*/
|
|
2793
|
+
var isManifestScript = (0, _orkestrel_contract.recordOf)({
|
|
2794
|
+
name: (0, _orkestrel_contract.stringOf)({
|
|
2795
|
+
min: 1,
|
|
2796
|
+
max: MAX_SCRIPT_LENGTH
|
|
2797
|
+
}),
|
|
2798
|
+
command: (0, _orkestrel_contract.stringOf)({
|
|
2799
|
+
min: 1,
|
|
2800
|
+
max: MAX_SCRIPT_LENGTH
|
|
2801
|
+
}),
|
|
2802
|
+
accepted: (0, _orkestrel_contract.andOf)(isCollection, (0, _orkestrel_contract.arrayOf)((0, _orkestrel_contract.stringOf)({
|
|
2803
|
+
min: 1,
|
|
2804
|
+
max: MAX_SCRIPT_LENGTH
|
|
2805
|
+
})))
|
|
2806
|
+
});
|
|
2807
|
+
/**
|
|
1873
2808
|
* Narrow a value to an {@link Override}.
|
|
1874
2809
|
*
|
|
1875
2810
|
* @remarks
|
|
@@ -1921,7 +2856,6 @@ var isBlueprint = (0, _orkestrel_contract.recordOf)({
|
|
|
1921
2856
|
bin: _orkestrel_contract.isBoolean,
|
|
1922
2857
|
setup: _orkestrel_contract.isBoolean,
|
|
1923
2858
|
guides: _orkestrel_contract.isBoolean,
|
|
1924
|
-
distribution: _orkestrel_contract.isBoolean,
|
|
1925
2859
|
integration: _orkestrel_contract.isBoolean,
|
|
1926
2860
|
conformance: _orkestrel_contract.isBoolean,
|
|
1927
2861
|
service: _orkestrel_contract.isBoolean,
|
|
@@ -1975,14 +2909,16 @@ var isArtifact = (0, _orkestrel_contract.unionOf)((0, _orkestrel_contract.record
|
|
|
1975
2909
|
* @remarks
|
|
1976
2910
|
* A plan reaches the writer, and the writer has no question channel, so this
|
|
1977
2911
|
* carries the whole law of the value: every artifact path, every claimed byte,
|
|
1978
|
-
* and the blueprint it was compiled from.
|
|
2912
|
+
* and the blueprint it was compiled from. An artifact at {@link MANIFEST_PATH}
|
|
2913
|
+
* must carry `birth` ownership. A plan claiming `content` or `presence` there
|
|
2914
|
+
* is refused because the compiler emits the manifest only as birth-owned.
|
|
1979
2915
|
*/
|
|
1980
|
-
var isPlan = (0, _orkestrel_contract.recordOf)({
|
|
2916
|
+
var isPlan = (0, _orkestrel_contract.andOf)((0, _orkestrel_contract.recordOf)({
|
|
1981
2917
|
blueprint: isBlueprint,
|
|
1982
2918
|
groups: isGroups,
|
|
1983
2919
|
artifacts: (0, _orkestrel_contract.andOf)(isCollection, (0, _orkestrel_contract.arrayOf)(isArtifact)),
|
|
1984
2920
|
hash: isHex
|
|
1985
|
-
}, ["hash"]);
|
|
2921
|
+
}, ["hash"]), (plan) => plan.artifacts.every((artifact) => artifact.path !== "package.json" || artifact.ownership === "birth"));
|
|
1986
2922
|
/**
|
|
1987
2923
|
* Narrow a value to a {@link Question}.
|
|
1988
2924
|
*
|
|
@@ -3026,19 +3962,17 @@ function manifestToName(manifest) {
|
|
|
3026
3962
|
return name;
|
|
3027
3963
|
}
|
|
3028
3964
|
/**
|
|
3029
|
-
* Project a package manifest's text to the `@orkestrel/*` packages
|
|
3965
|
+
* Project a package manifest's text to the `@orkestrel/*` packages each dependency section declares.
|
|
3030
3966
|
*
|
|
3031
3967
|
* @param manifest - The `package.json` text.
|
|
3032
|
-
* @returns
|
|
3033
|
-
* with the first declaration of a repeated name winning.
|
|
3968
|
+
* @returns The runtime, development, and peer declarations as separate lists.
|
|
3034
3969
|
*
|
|
3035
3970
|
* @remarks
|
|
3036
|
-
*
|
|
3037
|
-
*
|
|
3038
|
-
*
|
|
3039
|
-
* unrelated dependencies are not this package's to report on.
|
|
3971
|
+
* Every other name is skipped rather than refused: a workspace's unrelated
|
|
3972
|
+
* dependencies are not this package's to report on. Keeping the sections
|
|
3973
|
+
* separate prevents a caller from treating a peer as a writable floor.
|
|
3040
3974
|
*
|
|
3041
|
-
* Never throws, and every row it returns satisfies `isDependency` while
|
|
3975
|
+
* Never throws, and every row it returns satisfies `isDependency` while each
|
|
3042
3976
|
* list satisfies `isCollection`, so the result crosses the compiler's own
|
|
3043
3977
|
* boundary without a second cleaning.
|
|
3044
3978
|
*
|
|
@@ -3047,34 +3981,55 @@ function manifestToName(manifest) {
|
|
|
3047
3981
|
* import { manifestToDependencies } from '@orkestrel/scaffold'
|
|
3048
3982
|
*
|
|
3049
3983
|
* manifestToDependencies('{"dependencies":{"@orkestrel/emitter":"^0.0.5","vite":"~8.2.0"}}')
|
|
3050
|
-
* // [{ name: '@orkestrel/emitter', range: '^0.0.5' }]
|
|
3984
|
+
* // { runtime: [{ name: '@orkestrel/emitter', range: '^0.0.5' }], development: [], peer: [] }
|
|
3051
3985
|
* ```
|
|
3052
3986
|
*/
|
|
3053
3987
|
function manifestToDependencies(manifest) {
|
|
3054
|
-
if (computeBytes(manifest) > 1048576) return
|
|
3988
|
+
if (computeBytes(manifest) > 1048576) return {
|
|
3989
|
+
runtime: [],
|
|
3990
|
+
development: [],
|
|
3991
|
+
peer: []
|
|
3992
|
+
};
|
|
3055
3993
|
const parsed = (0, _orkestrel_contract.parseJSON)(manifest);
|
|
3056
|
-
if (!(0, _orkestrel_contract.isRecord)(parsed)) return
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
]
|
|
3064
|
-
|
|
3994
|
+
if (!(0, _orkestrel_contract.isRecord)(parsed)) return {
|
|
3995
|
+
runtime: [],
|
|
3996
|
+
development: [],
|
|
3997
|
+
peer: []
|
|
3998
|
+
};
|
|
3999
|
+
const runtime = [];
|
|
4000
|
+
const development = [];
|
|
4001
|
+
const peer = [];
|
|
4002
|
+
const sections = [
|
|
4003
|
+
{
|
|
4004
|
+
name: "dependencies",
|
|
4005
|
+
dependencies: runtime
|
|
4006
|
+
},
|
|
4007
|
+
{
|
|
4008
|
+
name: "devDependencies",
|
|
4009
|
+
dependencies: development
|
|
4010
|
+
},
|
|
4011
|
+
{
|
|
4012
|
+
name: "peerDependencies",
|
|
4013
|
+
dependencies: peer
|
|
4014
|
+
}
|
|
4015
|
+
];
|
|
4016
|
+
for (const section of sections) {
|
|
4017
|
+
const entries = parsed[section.name];
|
|
3065
4018
|
if (!(0, _orkestrel_contract.isRecord)(entries)) continue;
|
|
3066
4019
|
for (const [name, range] of Object.entries(entries)) {
|
|
3067
|
-
if (seen.has(name)) continue;
|
|
3068
4020
|
if (!DEPENDENCY_NAME_PATTERN.test(name) || name.length > 214) continue;
|
|
3069
4021
|
if (!(0, _orkestrel_contract.isString)(range) || range.length === 0 || range.length > 2048) continue;
|
|
3070
|
-
|
|
3071
|
-
dependencies.push({
|
|
4022
|
+
section.dependencies.push({
|
|
3072
4023
|
name,
|
|
3073
4024
|
range
|
|
3074
4025
|
});
|
|
3075
4026
|
}
|
|
3076
4027
|
}
|
|
3077
|
-
return
|
|
4028
|
+
return {
|
|
4029
|
+
runtime: (0, _orkestrel_contract.limitEntries)(runtime, MAX_COLLECTION_ITEMS),
|
|
4030
|
+
development: (0, _orkestrel_contract.limitEntries)(development, MAX_COLLECTION_ITEMS),
|
|
4031
|
+
peer: (0, _orkestrel_contract.limitEntries)(peer, MAX_COLLECTION_ITEMS)
|
|
4032
|
+
};
|
|
3078
4033
|
}
|
|
3079
4034
|
//#endregion
|
|
3080
4035
|
//#region src/core/compilers.ts
|
|
@@ -3280,13 +4235,15 @@ function blueprintToDevDependencies(blueprint) {
|
|
|
3280
4235
|
* proofs every workspace can pass before it has a public API, and one build per
|
|
3281
4236
|
* target that actually builds.
|
|
3282
4237
|
*
|
|
3283
|
-
* A publishing workspace isolates distribution and live-service proofs from
|
|
3284
|
-
* `test` and runs them from `prepublishOnly` instead.
|
|
3285
|
-
*
|
|
3286
|
-
*
|
|
3287
|
-
*
|
|
3288
|
-
*
|
|
3289
|
-
*
|
|
4238
|
+
* A publishing workspace isolates its distribution and live-service proofs from
|
|
4239
|
+
* `test` and runs them from `prepublishOnly` instead. Publishing is what selects
|
|
4240
|
+
* the distribution proof: what that proof measures is the packed tarball, so a
|
|
4241
|
+
* workspace that packs no published source has nothing for it to read. A private
|
|
4242
|
+
* workspace therefore omits it and runs a live-service proof from `test`.
|
|
4243
|
+
* Integration and conformance stay in `test` because they neither pack nor
|
|
4244
|
+
* install the workspace and drive no external service. A conformance run may
|
|
4245
|
+
* start a server, but it starts its own and reaches it over loopback, so the run
|
|
4246
|
+
* stays hermetic.
|
|
3290
4247
|
*
|
|
3291
4248
|
* The configuration paths interpolated here are the same ones `SRC_MATRIX` and
|
|
3292
4249
|
* `APP_MATRIX` list as each environment's configuration files, so a rename in
|
|
@@ -3303,7 +4260,6 @@ function blueprintToDevDependencies(blueprint) {
|
|
|
3303
4260
|
*/
|
|
3304
4261
|
function blueprintToScripts(blueprint) {
|
|
3305
4262
|
const publishes = blueprint.src.length > 0;
|
|
3306
|
-
const distributes = blueprint.distribution && publishes;
|
|
3307
4263
|
const integrates = blueprint.integration;
|
|
3308
4264
|
const compiles = publishes || blueprint.bin;
|
|
3309
4265
|
const runtime = blueprint.app.filter((environment) => environment !== "core");
|
|
@@ -3361,7 +4317,7 @@ function blueprintToScripts(blueprint) {
|
|
|
3361
4317
|
if (blueprint.conformance) scripts["test:conformance"] = `${vitest} --project conformance`;
|
|
3362
4318
|
scripts["test:probe"] = "vitest run --config vite.config.ts --no-cache --reporter=verbose --project probe";
|
|
3363
4319
|
scripts["test:bench"] = "vitest bench --config vite.config.ts --no-cache --project probe";
|
|
3364
|
-
if (
|
|
4320
|
+
if (publishes) scripts["test:distribution"] = `${vitest} --project distribution`;
|
|
3365
4321
|
if (integrates) scripts["test:integration"] = `${vitest} --project integration`;
|
|
3366
4322
|
if (blueprint.service) scripts["test:service"] = `${vitest} --project service`;
|
|
3367
4323
|
scripts.build = [
|
|
@@ -3394,16 +4350,73 @@ function blueprintToScripts(blueprint) {
|
|
|
3394
4350
|
scripts["serve:build"] = "npm run build:app:server && npm run serve";
|
|
3395
4351
|
}
|
|
3396
4352
|
if (publishes) {
|
|
3397
|
-
scripts.prepack =
|
|
4353
|
+
scripts.prepack = "npm run build";
|
|
3398
4354
|
scripts.prepublishOnly = [
|
|
3399
4355
|
"npm run format:check && npm run lint:check && npm run check && npm run build && npm test",
|
|
3400
|
-
|
|
4356
|
+
RELEASE_PROOF_COMMAND,
|
|
3401
4357
|
...blueprint.service ? ["npm run test:service"] : []
|
|
3402
4358
|
].join(" && ");
|
|
3403
4359
|
}
|
|
3404
4360
|
return scripts;
|
|
3405
4361
|
}
|
|
3406
4362
|
/**
|
|
4363
|
+
* Project a blueprint into the manifest scripts a region write may replace.
|
|
4364
|
+
*
|
|
4365
|
+
* @param blueprint - The workspace specification.
|
|
4366
|
+
* @returns One entry per writable script.
|
|
4367
|
+
*
|
|
4368
|
+
* @remarks
|
|
4369
|
+
* Every direct `test:<project>` script is writable, together with the probe and
|
|
4370
|
+
* benchmark workbench scripts. Publishing adds the pack and publication
|
|
4371
|
+
* lifecycle scripts. Aggregate test scripts and maintainer-owned gate chains
|
|
4372
|
+
* stay outside the region.
|
|
4373
|
+
*
|
|
4374
|
+
* `accepted` carries each generated predecessor the region can replace. The
|
|
4375
|
+
* pack hook accepts the build chain emitted before it delegated to `build`. The
|
|
4376
|
+
* publication hook accepts the same gate chain without
|
|
4377
|
+
* {@link RELEASE_PROOF_COMMAND}. The value being written is always writable, so
|
|
4378
|
+
* it is not repeated there. Any other value is a script the workspace author
|
|
4379
|
+
* customized, and {@link replaceManifestScripts} retains it while writing the
|
|
4380
|
+
* other named scripts independently.
|
|
4381
|
+
*
|
|
4382
|
+
* @example
|
|
4383
|
+
* ```ts
|
|
4384
|
+
* import { blueprintToWritableScripts, createBlueprint } from '@orkestrel/scaffold'
|
|
4385
|
+
*
|
|
4386
|
+
* const blueprint = createBlueprint('router', { src: ['core'] })
|
|
4387
|
+
*
|
|
4388
|
+
* blueprintToWritableScripts(blueprint)[0]?.name // 'test:src:core'
|
|
4389
|
+
* ```
|
|
4390
|
+
*/
|
|
4391
|
+
function blueprintToWritableScripts(blueprint) {
|
|
4392
|
+
const scripts = blueprintToScripts(blueprint);
|
|
4393
|
+
const writable = [];
|
|
4394
|
+
for (const [name, command] of Object.entries(scripts)) {
|
|
4395
|
+
if (!name.startsWith("test:") || name === "test:src" || name === "test:app") continue;
|
|
4396
|
+
writable.push({
|
|
4397
|
+
name,
|
|
4398
|
+
command,
|
|
4399
|
+
accepted: []
|
|
4400
|
+
});
|
|
4401
|
+
}
|
|
4402
|
+
const prepack = scripts.prepack;
|
|
4403
|
+
if (prepack !== void 0) {
|
|
4404
|
+
const predecessor = scripts.build;
|
|
4405
|
+
writable.push({
|
|
4406
|
+
name: "prepack",
|
|
4407
|
+
command: prepack,
|
|
4408
|
+
accepted: predecessor === void 0 ? [] : [predecessor]
|
|
4409
|
+
});
|
|
4410
|
+
}
|
|
4411
|
+
const prepublish = scripts.prepublishOnly;
|
|
4412
|
+
if (prepublish !== void 0) writable.push({
|
|
4413
|
+
name: "prepublishOnly",
|
|
4414
|
+
command: prepublish,
|
|
4415
|
+
accepted: [prepublish.replace(` && ${RELEASE_PROOF_COMMAND}`, "")]
|
|
4416
|
+
});
|
|
4417
|
+
return writable;
|
|
4418
|
+
}
|
|
4419
|
+
/**
|
|
3407
4420
|
* Compile a blueprint into its `package.json` content.
|
|
3408
4421
|
*
|
|
3409
4422
|
* @param blueprint - The workspace specification.
|
|
@@ -3570,7 +4583,6 @@ function blueprintToRootTsconfig(blueprint) {
|
|
|
3570
4583
|
function blueprintToRootVite(blueprint) {
|
|
3571
4584
|
const machinery = blueprintToMachinery(blueprint);
|
|
3572
4585
|
const publishes = blueprint.src.length > 0;
|
|
3573
|
-
const distributes = blueprint.distribution && publishes;
|
|
3574
4586
|
const imports = [];
|
|
3575
4587
|
if (machinery.browser) imports.push("import { playwright } from '@vitest/browser-playwright'");
|
|
3576
4588
|
if (machinery.vue) imports.push("import vue from '@vitejs/plugin-vue'");
|
|
@@ -3585,14 +4597,14 @@ function blueprintToRootVite(blueprint) {
|
|
|
3585
4597
|
const core = blueprint.src.includes("core");
|
|
3586
4598
|
factories.push((0, _orkestrel_template.fillTemplate)(CONFIG_TEMPLATES.factories.src.browser, {
|
|
3587
4599
|
external: core ? `external: (id: string) =>
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
output: core ? "
|
|
3594
|
-
exclude: core ? "
|
|
3595
|
-
global: blueprint.global ? "
|
|
4600
|
+
id === '@src/core' ||
|
|
4601
|
+
id.startsWith('@orkestrel/') ||
|
|
4602
|
+
peers.some((peer) => id === peer || id.startsWith(peer + '/')),` : `external: (id: string) =>
|
|
4603
|
+
id.startsWith('@orkestrel/') ||
|
|
4604
|
+
peers.some((peer) => id === peer || id.startsWith(peer + '/')),`,
|
|
4605
|
+
output: core ? " output: { paths: { '@src/core': '../core/index.js' } }," : " output: {},",
|
|
4606
|
+
exclude: core ? " exclude: ['tests/src/core/**/*.test.ts'],\n" : "",
|
|
4607
|
+
global: blueprint.global ? " globalSetup: ['./tests/setupGlobal.ts'],\n" : ""
|
|
3596
4608
|
}));
|
|
3597
4609
|
projects.push("srcBrowser");
|
|
3598
4610
|
}
|
|
@@ -3600,26 +4612,26 @@ function blueprintToRootVite(blueprint) {
|
|
|
3600
4612
|
const core = blueprint.src.includes("core");
|
|
3601
4613
|
factories.push((0, _orkestrel_template.fillTemplate)(CONFIG_TEMPLATES.factories.src.server, {
|
|
3602
4614
|
external: core ? `external: (id: string) =>
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
output: core ? `\t\t\
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
exclude: core ? "
|
|
4615
|
+
id === '@src/core' ||
|
|
4616
|
+
id.startsWith('node:') ||
|
|
4617
|
+
id.startsWith('@orkestrel/') ||
|
|
4618
|
+
peers.some((peer) => id === peer || id.startsWith(peer + '/')),` : `external: (id: string) =>
|
|
4619
|
+
id.startsWith('node:') ||
|
|
4620
|
+
id.startsWith('@orkestrel/') ||
|
|
4621
|
+
peers.some((peer) => id === peer || id.startsWith(peer + '/')),`,
|
|
4622
|
+
output: core ? `\t\t\toutput: [
|
|
4623
|
+
{
|
|
4624
|
+
format: 'es',
|
|
4625
|
+
entryFileNames: 'index.js',
|
|
4626
|
+
paths: { '@src/core': '../core/index.js' },
|
|
4627
|
+
},
|
|
4628
|
+
{
|
|
4629
|
+
format: 'cjs',
|
|
4630
|
+
entryFileNames: 'index.cjs',
|
|
4631
|
+
paths: { '@src/core': '../core/index.cjs' },
|
|
4632
|
+
},
|
|
4633
|
+
],` : " output: {},",
|
|
4634
|
+
exclude: core ? " exclude: ['tests/src/core/**/*.test.ts'],\n" : ""
|
|
3623
4635
|
}));
|
|
3624
4636
|
projects.push("srcServer");
|
|
3625
4637
|
}
|
|
@@ -3683,7 +4695,7 @@ export function appShowcase(): UserConfig {
|
|
|
3683
4695
|
showcaseBuild,
|
|
3684
4696
|
showcaseFactory
|
|
3685
4697
|
}));
|
|
3686
|
-
projects.push("appBrowser
|
|
4698
|
+
projects.push("appBrowser");
|
|
3687
4699
|
}
|
|
3688
4700
|
if (blueprint.app.includes("server")) {
|
|
3689
4701
|
factories.push(CONFIG_TEMPLATES.factories.app.server);
|
|
@@ -3709,12 +4721,12 @@ export function appShowcase(): UserConfig {
|
|
|
3709
4721
|
factories.push(CONFIG_TEMPLATES.factories.service);
|
|
3710
4722
|
projects.push("service");
|
|
3711
4723
|
}
|
|
3712
|
-
if (
|
|
4724
|
+
if (publishes) {
|
|
3713
4725
|
factories.push(CONFIG_TEMPLATES.factories.distribution);
|
|
3714
4726
|
projects.push("distribution");
|
|
3715
4727
|
}
|
|
3716
4728
|
if (blueprint.integration) {
|
|
3717
|
-
factories.push((0, _orkestrel_template.fillTemplate)(CONFIG_TEMPLATES.factories.integration, { global: blueprint.global ? "
|
|
4729
|
+
factories.push((0, _orkestrel_template.fillTemplate)(CONFIG_TEMPLATES.factories.integration, { global: blueprint.global ? " globalSetup: ['./tests/setupGlobal.ts'],\n" : "" }));
|
|
3718
4730
|
projects.push("integration");
|
|
3719
4731
|
}
|
|
3720
4732
|
factories.push(CONFIG_TEMPLATES.factories.probe);
|
|
@@ -3943,6 +4955,13 @@ function blueprintToSourceArtifacts(blueprint) {
|
|
|
3943
4955
|
* readiness setup alone, because the root configuration names that module by
|
|
3944
4956
|
* path.
|
|
3945
4957
|
*
|
|
4958
|
+
* The distribution proof is emitted, and the same test separates it from those
|
|
4959
|
+
* two: its subject is the packed tarball rather than anything only the package
|
|
4960
|
+
* knows, so every assertion derives from the installed tree at run time and
|
|
4961
|
+
* nothing has to be named. It follows the published source it packs, and it is
|
|
4962
|
+
* the one artifact here claimed by presence: a target lacking it reports as
|
|
4963
|
+
* drift, and a package that replaced it with a better proof keeps that proof.
|
|
4964
|
+
*
|
|
3946
4965
|
* @example
|
|
3947
4966
|
* ```ts
|
|
3948
4967
|
* import { blueprintToTestArtifacts, createBlueprint } from '@orkestrel/scaffold'
|
|
@@ -4024,6 +5043,24 @@ function blueprintToTestArtifacts(blueprint) {
|
|
|
4024
5043
|
label: serializeTypeScriptString(`app ${environment} entry`)
|
|
4025
5044
|
})
|
|
4026
5045
|
});
|
|
5046
|
+
if (blueprint.src.length > 0) {
|
|
5047
|
+
const browser = blueprint.src.includes("browser");
|
|
5048
|
+
const distribution = ARTIFACT_TEMPLATES.tests.distribution;
|
|
5049
|
+
artifacts.push({
|
|
5050
|
+
path: DISTRIBUTION_TEST_PATH,
|
|
5051
|
+
group: "tests",
|
|
5052
|
+
ownership: "presence",
|
|
5053
|
+
origin: "template",
|
|
5054
|
+
content: (0, _orkestrel_template.fillTemplate)(distribution.proof, {
|
|
5055
|
+
types: browser ? distribution.types : "",
|
|
5056
|
+
transport: browser ? distribution.transport : "",
|
|
5057
|
+
launcher: browser ? distribution.launcher : "",
|
|
5058
|
+
helpers: browser ? distribution.helpers : "",
|
|
5059
|
+
drive: browser ? distribution.drive : "",
|
|
5060
|
+
guard: browser ? "" : distribution.guard
|
|
5061
|
+
})
|
|
5062
|
+
});
|
|
5063
|
+
}
|
|
4027
5064
|
if (blueprint.integration) {
|
|
4028
5065
|
const imports = [];
|
|
4029
5066
|
const entries = [];
|
|
@@ -4210,34 +5247,33 @@ function applyOverrides(artifacts, overrides) {
|
|
|
4210
5247
|
* Replace declared dependency ranges in package manifest text.
|
|
4211
5248
|
*
|
|
4212
5249
|
* @param manifest - The manifest text to compile.
|
|
4213
|
-
* @param
|
|
4214
|
-
* @returns The manifest with every matching
|
|
4215
|
-
*
|
|
5250
|
+
* @param pins - The runtime and development names and replacement ranges.
|
|
5251
|
+
* @returns The manifest with every matching writable value replaced, or
|
|
5252
|
+
* `undefined` when a name has no quoted declaration in its named section.
|
|
4216
5253
|
*
|
|
4217
5254
|
* @remarks
|
|
4218
5255
|
* The compiler replaces values in place instead of serializing the manifest,
|
|
4219
5256
|
* so description, keywords, scripts, key order, indentation, and every byte
|
|
4220
|
-
* outside the named ranges survive.
|
|
4221
|
-
*
|
|
4222
|
-
*
|
|
4223
|
-
*
|
|
5257
|
+
* outside the named ranges survive. Runtime pins apply only to `dependencies`,
|
|
5258
|
+
* and development pins apply only to `devDependencies`. The compiler never
|
|
5259
|
+
* reads or writes `peerDependencies` or `peerDependenciesMeta`. An override or
|
|
5260
|
+
* resolution with the same name stays untouched.
|
|
4224
5261
|
*
|
|
4225
5262
|
* @example
|
|
4226
5263
|
* ```ts
|
|
4227
5264
|
* import { replaceManifestRanges } from '@orkestrel/scaffold'
|
|
4228
5265
|
*
|
|
4229
5266
|
* const manifest = '{"devDependencies":{"typescript":"^6"}}\n'
|
|
4230
|
-
* replaceManifestRanges(manifest,
|
|
5267
|
+
* replaceManifestRanges(manifest, {
|
|
5268
|
+
* runtime: [],
|
|
5269
|
+
* development: [{ name: 'typescript', range: '^7' }],
|
|
5270
|
+
* })
|
|
4231
5271
|
* // the manifest with the declared range replaced
|
|
4232
5272
|
* ```
|
|
4233
5273
|
*/
|
|
4234
|
-
function replaceManifestRanges(manifest,
|
|
4235
|
-
if (
|
|
4236
|
-
const sectionNames = /* @__PURE__ */ new Set([
|
|
4237
|
-
"dependencies",
|
|
4238
|
-
"devDependencies",
|
|
4239
|
-
"peerDependencies"
|
|
4240
|
-
]);
|
|
5274
|
+
function replaceManifestRanges(manifest, pins) {
|
|
5275
|
+
if (pins.runtime.length === 0 && pins.development.length === 0) return manifest;
|
|
5276
|
+
const sectionNames = /* @__PURE__ */ new Set(["dependencies", "devDependencies"]);
|
|
4241
5277
|
const sections = [];
|
|
4242
5278
|
let depth = 0;
|
|
4243
5279
|
let cursor = 0;
|
|
@@ -4297,6 +5333,7 @@ function replaceManifestRanges(manifest, dependencies) {
|
|
|
4297
5333
|
sectionDepth -= 1;
|
|
4298
5334
|
if (sectionDepth === 0) {
|
|
4299
5335
|
sections.push({
|
|
5336
|
+
name: key,
|
|
4300
5337
|
start: value,
|
|
4301
5338
|
end: sectionCursor + 1
|
|
4302
5339
|
});
|
|
@@ -4310,10 +5347,13 @@ function replaceManifestRanges(manifest, dependencies) {
|
|
|
4310
5347
|
}
|
|
4311
5348
|
cursor = end;
|
|
4312
5349
|
}
|
|
4313
|
-
const
|
|
4314
|
-
const
|
|
5350
|
+
const runtime = /* @__PURE__ */ new Set();
|
|
5351
|
+
const development = /* @__PURE__ */ new Set();
|
|
4315
5352
|
let compiled = manifest;
|
|
4316
5353
|
for (const bounds of sections.reverse()) {
|
|
5354
|
+
const dependencies = bounds.name === "dependencies" ? pins.runtime : pins.development;
|
|
5355
|
+
const replacements = new Map(dependencies.map(({ name, range }) => [name, range]));
|
|
5356
|
+
const declared = bounds.name === "dependencies" ? runtime : development;
|
|
4317
5357
|
let section = compiled.slice(bounds.start, bounds.end);
|
|
4318
5358
|
let sectionDepth = 0;
|
|
4319
5359
|
let sectionCursor = 0;
|
|
@@ -4380,13 +5420,225 @@ function replaceManifestRanges(manifest, dependencies) {
|
|
|
4380
5420
|
}
|
|
4381
5421
|
compiled = compiled.slice(0, bounds.start) + section + compiled.slice(bounds.end);
|
|
4382
5422
|
}
|
|
4383
|
-
|
|
5423
|
+
const declaredRuntime = pins.runtime.every(({ name }) => runtime.has(name));
|
|
5424
|
+
const declaredDevelopment = pins.development.every(({ name }) => development.has(name));
|
|
5425
|
+
return declaredRuntime && declaredDevelopment ? compiled : void 0;
|
|
5426
|
+
}
|
|
5427
|
+
/**
|
|
5428
|
+
* Replace named script values in package manifest text.
|
|
5429
|
+
*
|
|
5430
|
+
* @param manifest - The manifest text to compile.
|
|
5431
|
+
* @param scripts - The scripts to write, each with the predecessors it accepts.
|
|
5432
|
+
* @returns The manifest carrying every absent or accepted named script and
|
|
5433
|
+
* retaining every differing string value, or `undefined` when a planned key
|
|
5434
|
+
* holds a non-string value or the text carries no readable `scripts` object to
|
|
5435
|
+
* write into.
|
|
5436
|
+
*
|
|
5437
|
+
* @remarks
|
|
5438
|
+
* The compiler replaces values in place instead of serializing the manifest, so
|
|
5439
|
+
* description, keywords, dependencies, key order, indentation, and every byte
|
|
5440
|
+
* outside the replaced ranges survive. A named script the manifest already
|
|
5441
|
+
* declares is overwritten only when its value is one of its
|
|
5442
|
+
* {@link ManifestScript.accepted} predecessors. The planned value stands. Any
|
|
5443
|
+
* other string is a chain the workspace author customized, so it stays
|
|
5444
|
+
* byte-identical while the other named scripts are written independently. A
|
|
5445
|
+
* named script the manifest does not declare is appended after the last
|
|
5446
|
+
* declared script, copying that section's indentation. A region declaring
|
|
5447
|
+
* nothing takes every named script as its first entries, indented from the line
|
|
5448
|
+
* its own opening brace sits on.
|
|
5449
|
+
*
|
|
5450
|
+
* @example
|
|
5451
|
+
* ```ts
|
|
5452
|
+
* import { replaceManifestScripts } from '@orkestrel/scaffold'
|
|
5453
|
+
*
|
|
5454
|
+
* const manifest = '{\n\t"scripts": {\n\t\t"test": "vitest run"\n\t}\n}\n'
|
|
5455
|
+
* replaceManifestScripts(manifest, [
|
|
5456
|
+
* { name: 'test', command: 'vitest run --no-cache', accepted: ['vitest run'] },
|
|
5457
|
+
* ])
|
|
5458
|
+
* // the manifest with the declared script replaced
|
|
5459
|
+
* ```
|
|
5460
|
+
*/
|
|
5461
|
+
function replaceManifestScripts(manifest, scripts) {
|
|
5462
|
+
if (scripts.length === 0) return manifest;
|
|
5463
|
+
const parsed = (0, _orkestrel_contract.parseJSON)(manifest);
|
|
5464
|
+
if (!(0, _orkestrel_contract.isRecord)(parsed) || !(0, _orkestrel_contract.isRecord)(parsed.scripts)) return void 0;
|
|
5465
|
+
const declared = parsed.scripts;
|
|
5466
|
+
for (const script of scripts) {
|
|
5467
|
+
if (!Object.hasOwn(declared, script.name)) continue;
|
|
5468
|
+
const value = declared[script.name];
|
|
5469
|
+
if (!(0, _orkestrel_contract.isString)(value)) return void 0;
|
|
5470
|
+
}
|
|
5471
|
+
let depth = 0;
|
|
5472
|
+
let cursor = 0;
|
|
5473
|
+
let start = -1;
|
|
5474
|
+
let end = -1;
|
|
5475
|
+
while (cursor < manifest.length && end < 0) {
|
|
5476
|
+
const character = manifest.charAt(cursor);
|
|
5477
|
+
if (character === "{") {
|
|
5478
|
+
depth += 1;
|
|
5479
|
+
cursor += 1;
|
|
5480
|
+
continue;
|
|
5481
|
+
}
|
|
5482
|
+
if (character === "}") {
|
|
5483
|
+
depth -= 1;
|
|
5484
|
+
cursor += 1;
|
|
5485
|
+
continue;
|
|
5486
|
+
}
|
|
5487
|
+
if (character !== "\"") {
|
|
5488
|
+
cursor += 1;
|
|
5489
|
+
continue;
|
|
5490
|
+
}
|
|
5491
|
+
const keyStart = cursor;
|
|
5492
|
+
cursor += 1;
|
|
5493
|
+
while (cursor < manifest.length) {
|
|
5494
|
+
if (manifest.charAt(cursor) === "\\") {
|
|
5495
|
+
cursor += 2;
|
|
5496
|
+
continue;
|
|
5497
|
+
}
|
|
5498
|
+
if (manifest.charAt(cursor) === "\"") break;
|
|
5499
|
+
cursor += 1;
|
|
5500
|
+
}
|
|
5501
|
+
if (cursor >= manifest.length) return void 0;
|
|
5502
|
+
const keyEnd = cursor + 1;
|
|
5503
|
+
cursor = keyEnd;
|
|
5504
|
+
if (depth !== 1) continue;
|
|
5505
|
+
if ((0, _orkestrel_contract.parseJSON)(manifest.slice(keyStart, keyEnd)) !== "scripts") continue;
|
|
5506
|
+
let opening = keyEnd;
|
|
5507
|
+
while (opening < manifest.length && /\s/u.test(manifest.charAt(opening))) opening += 1;
|
|
5508
|
+
if (manifest.charAt(opening) !== ":") continue;
|
|
5509
|
+
opening += 1;
|
|
5510
|
+
while (opening < manifest.length && /\s/u.test(manifest.charAt(opening))) opening += 1;
|
|
5511
|
+
if (manifest.charAt(opening) !== "{") continue;
|
|
5512
|
+
start = opening;
|
|
5513
|
+
let nested = 0;
|
|
5514
|
+
let scan = opening;
|
|
5515
|
+
while (scan < manifest.length) {
|
|
5516
|
+
const inner = manifest.charAt(scan);
|
|
5517
|
+
if (inner === "\"") {
|
|
5518
|
+
scan += 1;
|
|
5519
|
+
while (scan < manifest.length) {
|
|
5520
|
+
if (manifest.charAt(scan) === "\\") {
|
|
5521
|
+
scan += 2;
|
|
5522
|
+
continue;
|
|
5523
|
+
}
|
|
5524
|
+
if (manifest.charAt(scan) === "\"") break;
|
|
5525
|
+
scan += 1;
|
|
5526
|
+
}
|
|
5527
|
+
if (scan >= manifest.length) return void 0;
|
|
5528
|
+
} else if (inner === "{") nested += 1;
|
|
5529
|
+
else if (inner === "}") {
|
|
5530
|
+
nested -= 1;
|
|
5531
|
+
if (nested === 0) {
|
|
5532
|
+
end = scan + 1;
|
|
5533
|
+
break;
|
|
5534
|
+
}
|
|
5535
|
+
}
|
|
5536
|
+
scan += 1;
|
|
5537
|
+
}
|
|
5538
|
+
}
|
|
5539
|
+
if (start < 0 || end < 0) return void 0;
|
|
5540
|
+
const edits = [];
|
|
5541
|
+
const written = /* @__PURE__ */ new Set();
|
|
5542
|
+
let first = -1;
|
|
5543
|
+
let nested = 0;
|
|
5544
|
+
let scan = start;
|
|
5545
|
+
while (scan < end) {
|
|
5546
|
+
const character = manifest.charAt(scan);
|
|
5547
|
+
if (character === "{") {
|
|
5548
|
+
nested += 1;
|
|
5549
|
+
scan += 1;
|
|
5550
|
+
continue;
|
|
5551
|
+
}
|
|
5552
|
+
if (character === "}") {
|
|
5553
|
+
nested -= 1;
|
|
5554
|
+
scan += 1;
|
|
5555
|
+
continue;
|
|
5556
|
+
}
|
|
5557
|
+
if (character !== "\"") {
|
|
5558
|
+
scan += 1;
|
|
5559
|
+
continue;
|
|
5560
|
+
}
|
|
5561
|
+
const keyStart = scan;
|
|
5562
|
+
scan += 1;
|
|
5563
|
+
while (scan < end) {
|
|
5564
|
+
if (manifest.charAt(scan) === "\\") {
|
|
5565
|
+
scan += 2;
|
|
5566
|
+
continue;
|
|
5567
|
+
}
|
|
5568
|
+
if (manifest.charAt(scan) === "\"") break;
|
|
5569
|
+
scan += 1;
|
|
5570
|
+
}
|
|
5571
|
+
if (scan >= end) return void 0;
|
|
5572
|
+
const keyEnd = scan + 1;
|
|
5573
|
+
scan = keyEnd;
|
|
5574
|
+
if (nested !== 1) continue;
|
|
5575
|
+
let valueStart = keyEnd;
|
|
5576
|
+
while (valueStart < end && /\s/u.test(manifest.charAt(valueStart))) valueStart += 1;
|
|
5577
|
+
if (manifest.charAt(valueStart) !== ":") continue;
|
|
5578
|
+
valueStart += 1;
|
|
5579
|
+
while (valueStart < end && /\s/u.test(manifest.charAt(valueStart))) valueStart += 1;
|
|
5580
|
+
if (first < 0) first = keyStart;
|
|
5581
|
+
if (manifest.charAt(valueStart) !== "\"") continue;
|
|
5582
|
+
let valueEnd = valueStart + 1;
|
|
5583
|
+
while (valueEnd < end) {
|
|
5584
|
+
if (manifest.charAt(valueEnd) === "\\") {
|
|
5585
|
+
valueEnd += 2;
|
|
5586
|
+
continue;
|
|
5587
|
+
}
|
|
5588
|
+
if (manifest.charAt(valueEnd) === "\"") break;
|
|
5589
|
+
valueEnd += 1;
|
|
5590
|
+
}
|
|
5591
|
+
if (valueEnd >= end) return void 0;
|
|
5592
|
+
scan = valueEnd + 1;
|
|
5593
|
+
const key = (0, _orkestrel_contract.parseJSON)(manifest.slice(keyStart, keyEnd));
|
|
5594
|
+
const script = scripts.find((entry) => entry.name === key);
|
|
5595
|
+
if (script === void 0) continue;
|
|
5596
|
+
written.add(script.name);
|
|
5597
|
+
const value = declared[script.name];
|
|
5598
|
+
if (value === script.command || !(0, _orkestrel_contract.isString)(value) || !script.accepted.includes(value)) continue;
|
|
5599
|
+
edits.push({
|
|
5600
|
+
start: valueStart,
|
|
5601
|
+
end: valueEnd + 1,
|
|
5602
|
+
text: JSON.stringify(script.command)
|
|
5603
|
+
});
|
|
5604
|
+
}
|
|
5605
|
+
const missing = scripts.filter((script) => !written.has(script.name));
|
|
5606
|
+
if (missing.length > 0) {
|
|
5607
|
+
if (first < 0) {
|
|
5608
|
+
const opening = manifest.lastIndexOf("\n", start);
|
|
5609
|
+
let column = opening + 1;
|
|
5610
|
+
while (column < start && /\s/u.test(manifest.charAt(column))) column += 1;
|
|
5611
|
+
const level = opening < 0 ? "" : manifest.slice(opening + 1, column);
|
|
5612
|
+
const separator = opening < 0 ? "" : `\n${level}${level}`;
|
|
5613
|
+
const closing = opening < 0 ? "" : `\n${level}`;
|
|
5614
|
+
const entries = missing.map((script) => `${separator}${JSON.stringify(script.name)}: ${JSON.stringify(script.command)}`);
|
|
5615
|
+
edits.push({
|
|
5616
|
+
start: start + 1,
|
|
5617
|
+
end: end - 1,
|
|
5618
|
+
text: `${entries.join(",")}${closing}`
|
|
5619
|
+
});
|
|
5620
|
+
} else {
|
|
5621
|
+
let anchor = end - 1;
|
|
5622
|
+
while (anchor > start && /\s/u.test(manifest.charAt(anchor - 1))) anchor -= 1;
|
|
5623
|
+
const newline = manifest.lastIndexOf("\n", first);
|
|
5624
|
+
const indent = newline < 0 ? "" : manifest.slice(newline + 1, first);
|
|
5625
|
+
const separator = newline < 0 || /\S/u.test(indent) ? "" : `\n${indent}`;
|
|
5626
|
+
edits.push({
|
|
5627
|
+
start: anchor,
|
|
5628
|
+
end: anchor,
|
|
5629
|
+
text: missing.map((script) => `,${separator}${JSON.stringify(script.name)}: ${JSON.stringify(script.command)}`).join("")
|
|
5630
|
+
});
|
|
5631
|
+
}
|
|
5632
|
+
}
|
|
5633
|
+
let compiled = manifest;
|
|
5634
|
+
for (const edit of edits.sort((left, right) => right.start - left.start)) compiled = compiled.slice(0, edit.start) + edit.text + compiled.slice(edit.end);
|
|
5635
|
+
return compiled;
|
|
4384
5636
|
}
|
|
4385
5637
|
/**
|
|
4386
5638
|
* Replace dependency ranges in a plan's manifest and recompute its identity.
|
|
4387
5639
|
*
|
|
4388
5640
|
* @param plan - The plan carrying the manifest artifact to compile.
|
|
4389
|
-
* @param
|
|
5641
|
+
* @param pins - The runtime and development names and replacement ranges.
|
|
4390
5642
|
* @returns A plan with replaced manifest ranges and a matching hash, or
|
|
4391
5643
|
* `undefined` when the manifest or its identity cannot be compiled.
|
|
4392
5644
|
*
|
|
@@ -4400,15 +5652,15 @@ function replaceManifestRanges(manifest, dependencies) {
|
|
|
4400
5652
|
* ```ts
|
|
4401
5653
|
* import { replacePlanRanges } from '@orkestrel/scaffold'
|
|
4402
5654
|
*
|
|
4403
|
-
* replacePlanRanges(plan,
|
|
5655
|
+
* replacePlanRanges(plan, pins) // the plan carrying the resolved writable ranges
|
|
4404
5656
|
* ```
|
|
4405
5657
|
*/
|
|
4406
|
-
function replacePlanRanges(plan,
|
|
5658
|
+
function replacePlanRanges(plan, pins) {
|
|
4407
5659
|
let replaced = false;
|
|
4408
5660
|
let refused = false;
|
|
4409
5661
|
const artifacts = plan.artifacts.map((artifact) => {
|
|
4410
5662
|
if (artifact.path !== "package.json" || artifact.origin === "host") return artifact;
|
|
4411
|
-
const content = replaceManifestRanges(artifact.content,
|
|
5663
|
+
const content = replaceManifestRanges(artifact.content, pins);
|
|
4412
5664
|
if (content === void 0) {
|
|
4413
5665
|
refused = true;
|
|
4414
5666
|
return artifact;
|
|
@@ -4735,11 +5987,6 @@ function blueprintToQuestions(blueprint) {
|
|
|
4735
5987
|
});
|
|
4736
5988
|
vendors.add(vendor);
|
|
4737
5989
|
}
|
|
4738
|
-
if (blueprint.distribution && blueprint.src.length === 0) questions.push({
|
|
4739
|
-
field: "distribution",
|
|
4740
|
-
message: "distribution packs the published source, and this workspace declares none, so it emits nothing.",
|
|
4741
|
-
blocking: false
|
|
4742
|
-
});
|
|
4743
5990
|
if (blueprint.showcase && !blueprint.app.includes("browser")) questions.push({
|
|
4744
5991
|
field: "showcase",
|
|
4745
5992
|
message: "showcase projects a browser app, and this workspace declares none, so it emits nothing.",
|
|
@@ -4948,7 +6195,6 @@ function createBlueprint(name, input) {
|
|
|
4948
6195
|
bin: input?.bin ?? false,
|
|
4949
6196
|
setup: input?.setup ?? false,
|
|
4950
6197
|
guides: input?.guides ?? false,
|
|
4951
|
-
distribution: input?.distribution ?? false,
|
|
4952
6198
|
integration: input?.integration ?? false,
|
|
4953
6199
|
conformance: input?.conformance ?? false,
|
|
4954
6200
|
service: input?.service ?? false,
|
|
@@ -5293,6 +6539,7 @@ exports.HOST_INVENTORY_PATH = HOST_INVENTORY_PATH;
|
|
|
5293
6539
|
exports.HOST_PATHS = HOST_PATHS;
|
|
5294
6540
|
exports.INTEGRATION_TEST_PATH = INTEGRATION_TEST_PATH;
|
|
5295
6541
|
exports.INVALID_PATH_CHARACTER_PATTERN = INVALID_PATH_CHARACTER_PATTERN;
|
|
6542
|
+
exports.MANIFEST_PATH = MANIFEST_PATH;
|
|
5296
6543
|
exports.MAX_ARTIFACT_BYTES = MAX_ARTIFACT_BYTES;
|
|
5297
6544
|
exports.MAX_ARTIFACT_HEX_LENGTH = MAX_ARTIFACT_HEX_LENGTH;
|
|
5298
6545
|
exports.MAX_AUDIT_FINDINGS = MAX_AUDIT_FINDINGS;
|
|
@@ -5303,6 +6550,7 @@ exports.MAX_NAME_LENGTH = MAX_NAME_LENGTH;
|
|
|
5303
6550
|
exports.MAX_PATH_LENGTH = MAX_PATH_LENGTH;
|
|
5304
6551
|
exports.MAX_RANGE_LENGTH = MAX_RANGE_LENGTH;
|
|
5305
6552
|
exports.MAX_REGISTRY_BYTES = MAX_REGISTRY_BYTES;
|
|
6553
|
+
exports.MAX_SCRIPT_LENGTH = MAX_SCRIPT_LENGTH;
|
|
5306
6554
|
exports.MAX_TOTAL_ARTIFACT_BYTES = MAX_TOTAL_ARTIFACT_BYTES;
|
|
5307
6555
|
exports.MAX_TOTAL_REGISTRY_BYTES = MAX_TOTAL_REGISTRY_BYTES;
|
|
5308
6556
|
exports.MINIMUM_NODE_VERSION = MINIMUM_NODE_VERSION;
|
|
@@ -5311,6 +6559,7 @@ exports.ORCHESTRATION_PATH_NAMES = ORCHESTRATION_PATH_NAMES;
|
|
|
5311
6559
|
exports.ORCHESTRATION_PATH_PREFIXES = ORCHESTRATION_PATH_PREFIXES;
|
|
5312
6560
|
exports.ORKESTREL_RANGE_PATTERN = ORKESTREL_RANGE_PATTERN;
|
|
5313
6561
|
exports.PRINT_WIDTH = PRINT_WIDTH;
|
|
6562
|
+
exports.RELEASE_PROOF_COMMAND = RELEASE_PROOF_COMMAND;
|
|
5314
6563
|
exports.SERVICE_SCRIPT_PATH = SERVICE_SCRIPT_PATH;
|
|
5315
6564
|
exports.SERVICE_SETUP_PATH = SERVICE_SETUP_PATH;
|
|
5316
6565
|
exports.SERVICE_TEST_INCLUDE = SERVICE_TEST_INCLUDE;
|
|
@@ -5339,6 +6588,7 @@ exports.blueprintToRootVite = blueprintToRootVite;
|
|
|
5339
6588
|
exports.blueprintToScripts = blueprintToScripts;
|
|
5340
6589
|
exports.blueprintToSourceArtifacts = blueprintToSourceArtifacts;
|
|
5341
6590
|
exports.blueprintToTestArtifacts = blueprintToTestArtifacts;
|
|
6591
|
+
exports.blueprintToWritableScripts = blueprintToWritableScripts;
|
|
5342
6592
|
exports.bytesToHex = bytesToHex;
|
|
5343
6593
|
exports.catalogToLayers = catalogToLayers;
|
|
5344
6594
|
exports.cloneValue = cloneValue;
|
|
@@ -5368,6 +6618,7 @@ exports.isFinding = isFinding;
|
|
|
5368
6618
|
exports.isGroup = isGroup;
|
|
5369
6619
|
exports.isGroups = isGroups;
|
|
5370
6620
|
exports.isHex = isHex;
|
|
6621
|
+
exports.isManifestScript = isManifestScript;
|
|
5371
6622
|
exports.isMirror = isMirror;
|
|
5372
6623
|
exports.isOverride = isOverride;
|
|
5373
6624
|
exports.isPath = isPath;
|
|
@@ -5395,6 +6646,7 @@ exports.planToFindings = planToFindings;
|
|
|
5395
6646
|
exports.planToHash = planToHash;
|
|
5396
6647
|
exports.planToSummary = planToSummary;
|
|
5397
6648
|
exports.replaceManifestRanges = replaceManifestRanges;
|
|
6649
|
+
exports.replaceManifestScripts = replaceManifestScripts;
|
|
5398
6650
|
exports.replacePlanRanges = replacePlanRanges;
|
|
5399
6651
|
exports.selectGroups = selectGroups;
|
|
5400
6652
|
exports.selectHostPaths = selectHostPaths;
|