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