@orkestrel/scaffold 0.0.6 → 0.0.8

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.
@@ -76,6 +76,8 @@ var SRC_MATRIX = Object.freeze({
76
76
  formats: Object.freeze(["es", "cjs"])
77
77
  })
78
78
  });
79
+ /** The computed configuration files required by a workspace-owned executable. */
80
+ var BIN_CONFIGS = Object.freeze(["configs/src/vite.bin.config.ts", "configs/src/tsconfig.bin.json"]);
79
81
  /**
80
82
  * The per-environment application matrix: thin config artifacts, Vitest project
81
83
  * label, and executable entry where the environment produces a runtime bundle.
@@ -105,7 +107,7 @@ var APP_MATRIX = Object.freeze({
105
107
  * `scripts/codex.sh` / `scripts/ollama.sh`), the repository coding-law policy module,
106
108
  * the line's seven byte-identical root dotfiles, and the two guides-grouped
107
109
  * mirror candidates: the line-wide dev-tooling guide
108
- * (`guides/src/guide.md`) and the scaffold engine's own self-guide
110
+ * (`guides/src/guide.md`) and the scaffold bin's own self-guide
109
111
  * (`guides/src/scaffold.md`). `stageHost` vendors both; each plan carries the
110
112
  * subset selected by `selectHostPaths`, omitting the target blueprint's own
111
113
  * guide.
@@ -138,6 +140,8 @@ var HOST_PATHS = Object.freeze([
138
140
  "guides/src/guide.md",
139
141
  "guides/src/scaffold.md"
140
142
  ]);
143
+ /** The consumer-owned live-service provisioner expected only by service workspaces. */
144
+ var SERVICE_SCRIPT_PATH = "scripts/service.sh";
141
145
  /** The package-name RegExp — lowercase alphanumeric-with-hyphens, letter-first. */
142
146
  var NAME_PATTERN = /^[a-z][a-z0-9-]*$/;
143
147
  /** Maximum bare workspace name length beneath the generated `@orkestrel/` scope. */
@@ -220,11 +224,11 @@ var DEFAULT_VERSION = "0.0.1";
220
224
  /** The `engines.node` range the `blueprint` builder fills. */
221
225
  var DEFAULT_ENGINES = `>=${MINIMUM_NODE_VERSION}`;
222
226
  /** The devDependency range generated packages pin `@orkestrel/scaffold` at. */
223
- var SCAFFOLD_RANGE = "^0.0.6";
227
+ var SCAFFOLD_RANGE = "^0.0.8";
224
228
  /** Tooling versions shared by scaffold and every generated workspace. */
225
229
  var BASE_DEV_DEPENDENCIES = Object.freeze({
226
230
  "@microsoft/api-extractor": "^7.58.12",
227
- "@orkestrel/guide": "^0.0.5",
231
+ "@orkestrel/guide": "^0.0.7",
228
232
  "@orkestrel/scaffold": SCAFFOLD_RANGE,
229
233
  "@types/node": "^26.1.2",
230
234
  oxfmt: "^0.61.0",
@@ -419,9 +423,9 @@ function member(name, category, summary, environment = "core") {
419
423
  * @remarks
420
424
  * `version` / `engines` default `DEFAULT_VERSION` / `DEFAULT_ENGINES`,
421
425
  * `src` defaults `['core']`, and `app` / `keywords` / `dependencies` /
422
- * `peers` / `extras` / `overrides` default `[]`. `description` is OMITTED
423
- * entirely when absent, so the result round-trips the exact-record
424
- * `Blueprint` guard.
426
+ * `peers` / `extras` / `overrides` default `[]`, and `bin` / `integration` /
427
+ * `service` default `false`. `description` is OMITTED entirely when absent, so
428
+ * the result round-trips the exact-record `Blueprint` guard.
425
429
  * @returns A complete `Blueprint`.
426
430
  *
427
431
  * @example
@@ -443,7 +447,9 @@ function blueprint(name, options) {
443
447
  version: options?.version ?? "0.0.1",
444
448
  engines: options?.engines ?? DEFAULT_ENGINES,
445
449
  overrides: options?.overrides ?? [],
446
- engine: options?.engine ?? false
450
+ bin: options?.bin ?? false,
451
+ integration: options?.integration ?? false,
452
+ service: options?.service ?? false
447
453
  };
448
454
  return options?.description === void 0 ? base : {
449
455
  ...base,
@@ -1794,7 +1800,9 @@ function blueprintShape() {
1794
1800
  max: MAX_RANGE_LENGTH
1795
1801
  }),
1796
1802
  overrides: arrayShape(overrideShape(), { max: MAX_COLLECTION_ITEMS }),
1797
- engine: booleanShape()
1803
+ bin: booleanShape(),
1804
+ integration: booleanShape(),
1805
+ service: booleanShape()
1798
1806
  });
1799
1807
  }
1800
1808
  /**
@@ -5522,24 +5530,28 @@ function compareCodeUnit(a, b) {
5522
5530
  return a < b ? -1 : a > b ? 1 : 0;
5523
5531
  }
5524
5532
  /**
5525
- * The host-neutral devDependency baseline every generated workspace needs.
5526
- * Browser providers are added only by browser selections or the scaffold
5527
- * engine's generated-browser consumer proof. A package's `extras` (code-unit
5528
- * sorted) merge in on top, the extras' declared range winning on a name
5529
- * collision with the baseline.
5533
+ * The complete development dependency set one blueprint emits. The shared
5534
+ * baseline is extended by package extras, dev-installed peers, selected
5535
+ * browser environments, and the bin axis's browser test provider.
5530
5536
  *
5531
- * @param extras - The blueprint's package-specific `extras` `Dependency[]`.
5537
+ * @param spec - The blueprint whose development dependencies are required.
5532
5538
  * @returns The merged `devDependencies` record.
5533
5539
  *
5534
5540
  * @example
5535
5541
  * ```ts
5536
- * devDependenciesFor([])['typescript'] // '^6.0.3'
5542
+ * devDependenciesFor(blueprint('router'))['typescript'] // '^6.0.3'
5537
5543
  * ```
5538
5544
  */
5539
- function devDependenciesFor(extras) {
5540
- const baseline = { ...BASE_DEV_DEPENDENCIES };
5541
- for (const extra of [...extras].sort((a, b) => compareCodeUnit(a.name, b.name))) baseline[extra.name] = extra.range;
5542
- return baseline;
5545
+ function devDependenciesFor(spec) {
5546
+ const dependencies = { ...BASE_DEV_DEPENDENCIES };
5547
+ for (const extra of [...spec.extras].sort((a, b) => compareCodeUnit(a.name, b.name))) dependencies[extra.name] = extra.range;
5548
+ for (const peer of [...spec.peers].sort((a, b) => compareCodeUnit(a.name, b.name))) dependencies[peer.name] = peer.range;
5549
+ return {
5550
+ ...dependencies,
5551
+ ...spec.src.includes("browser") ? SOURCE_BROWSER_DEV_DEPENDENCIES : {},
5552
+ ...spec.app.includes("browser") ? APP_BROWSER_DEV_DEPENDENCIES : {},
5553
+ ...spec.bin ? { "@vitest/browser-playwright": SOURCE_BROWSER_DEV_DEPENDENCIES["@vitest/browser-playwright"] } : {}
5554
+ };
5543
5555
  }
5544
5556
  /**
5545
5557
  * Compute the `package.json` artifact's `content`, applying the manifest and
@@ -5564,24 +5576,22 @@ function packageManifest(spec) {
5564
5576
  for (const peer of [...spec.peers].sort((a, b) => compareCodeUnit(a.name, b.name))) peerDependencies[peer.name] = peer.range;
5565
5577
  const peerDependenciesMeta = {};
5566
5578
  for (const peer of spec.peers) if (peer.optional === true) peerDependenciesMeta[peer.name] = { optional: true };
5567
- const peerDevDependencies = {};
5568
- for (const peer of [...spec.peers].sort((a, b) => compareCodeUnit(a.name, b.name))) peerDevDependencies[peer.name] = peer.range;
5569
5579
  const scripts = {
5570
5580
  clean: "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
5571
5581
  copy: "node -e \"const fs=require('node:fs'),p=require('node:path'),a=process.argv[1],b=process.argv[2];fs.mkdirSync(p.dirname(b),{recursive:true});fs.cpSync(a,b,{force:true});console.log('Copied: '+a+' to '+b)\"",
5572
- scaffold: spec.engine ? "node ./dist/bin/scaffold.js" : "scaffold",
5582
+ scaffold: spec.bin ? "node ./dist/bin/scaffold.js" : "scaffold",
5573
5583
  lint: "oxlint --config .oxlintrc.json --fix --deny-warnings .",
5574
5584
  check: [
5575
5585
  "tsc --noEmit --project tsconfig.json",
5576
- ...hasSource || spec.engine ? ["npm run check:src"] : [],
5586
+ ...hasSource || spec.bin ? ["npm run check:src"] : [],
5577
5587
  ...spec.app.length > 0 ? ["npm run check:app"] : []
5578
5588
  ].join(" && ")
5579
5589
  };
5580
- if (hasSource || spec.engine) {
5581
- scripts["check:src"] = spec.src.map((environment) => `npm run check:src:${environment}`).join(" && ") + (spec.engine ? `${spec.src.length > 0 ? " && " : ""}npm run check:src:bin` : "");
5590
+ if (hasSource || spec.bin) {
5591
+ scripts["check:src"] = spec.src.map((environment) => `npm run check:src:${environment}`).join(" && ") + (spec.bin ? `${spec.src.length > 0 ? " && " : ""}npm run check:src:bin` : "");
5582
5592
  for (const environment of spec.src) scripts[`check:src:${environment}`] = `tsc --noEmit -p configs/src/tsconfig.${environment}.json`;
5583
5593
  }
5584
- if (spec.engine) scripts["check:src:bin"] = "tsc --noEmit -p configs/src/tsconfig.bin.json";
5594
+ if (spec.bin) scripts["check:src:bin"] = "tsc --noEmit -p configs/src/tsconfig.bin.json";
5585
5595
  if (spec.app.length > 0) {
5586
5596
  scripts["check:app"] = spec.app.map((environment) => `npm run check:app:${environment}`).join(" && ");
5587
5597
  for (const environment of spec.app) scripts[`check:app:${environment}`] = environment === "browser" ? "vue-tsc --noEmit -p configs/app/tsconfig.browser.json" : `tsc --noEmit -p configs/app/tsconfig.${environment}.json`;
@@ -5590,17 +5600,19 @@ function packageManifest(spec) {
5590
5600
  scripts["format:check"] = "oxfmt --config .oxfmtrc.json --check .";
5591
5601
  scripts["lint:check"] = "oxlint --config .oxlintrc.json --deny-warnings .";
5592
5602
  scripts.test = [
5593
- ...hasSource || spec.engine ? ["npm run test:src"] : [],
5603
+ ...hasSource || spec.bin ? ["npm run test:src"] : [],
5594
5604
  ...spec.app.length > 0 ? ["npm run test:app"] : [],
5595
5605
  "npm run test:policy",
5596
5606
  "npm run test:guides"
5597
5607
  ].join(" && ");
5598
- if (hasSource || spec.engine) {
5599
- scripts["test:src"] = "vitest run --config vite.config.ts --no-cache --reporter=dot " + spec.src.map((environment) => `--project src:${environment}`).join(" ") + (spec.engine ? `${spec.src.length > 0 ? " " : ""}--project src:bin` : "");
5608
+ if (hasSource || spec.bin) {
5609
+ scripts["test:src"] = "vitest run --config vite.config.ts --no-cache --reporter=dot " + spec.src.map((environment) => `--project src:${environment}`).join(" ") + (spec.bin ? `${spec.src.length > 0 ? " " : ""}--project src:bin` : "");
5600
5610
  for (const environment of spec.src) scripts[`test:src:${environment}`] = `vitest run --config vite.config.ts --no-cache --reporter=dot --project src:${environment}`;
5601
5611
  }
5602
- if (spec.engine) scripts["test:src:bin"] = "vitest run --config vite.config.ts --no-cache --reporter=dot --project src:bin";
5603
- if (spec.engine) scripts["test:integration"] = "vitest run --config vite.config.ts --no-cache --reporter=dot --project integration";
5612
+ if (spec.bin) scripts["test:src:bin"] = "vitest run --config vite.config.ts --no-cache --reporter=dot --project src:bin";
5613
+ if (spec.integration) scripts["test:integration"] = "vitest run --config vite.config.ts --no-cache --reporter=dot --project integration";
5614
+ if (spec.bin && spec.integration) scripts["test:equivalence"] = "node -e \"const c=require('node:child_process'),n=process.platform==='win32'?'npm.cmd':'npm',r=c.spawnSync(n,['run','test:integration'],{stdio:'inherit',env:{...process.env,SCAFFOLD_BOUNDARY_EQUIVALENCE:'1'}});process.exit(r.status??1)\"";
5615
+ if (spec.service) scripts["test:service"] = "vitest run --config vite.config.ts --no-cache --reporter=dot --project service";
5604
5616
  if (spec.app.length > 0) {
5605
5617
  scripts["test:app"] = "vitest run --config vite.config.ts --no-cache --reporter=dot " + spec.app.map((environment) => `--project ${APP_MATRIX[environment].project}`).join(" ");
5606
5618
  for (const environment of spec.app) scripts[`test:app:${environment}`] = `vitest run --config vite.config.ts --no-cache --reporter=dot --project ${APP_MATRIX[environment].project}`;
@@ -5609,12 +5621,12 @@ function packageManifest(spec) {
5609
5621
  scripts["test:guides"] = "vitest run --config vite.config.ts --reporter=dot --project guides";
5610
5622
  scripts.build = [
5611
5623
  "npm run clean",
5612
- ...hasSource || spec.engine ? ["npm run build:src"] : [],
5624
+ ...hasSource || spec.bin ? ["npm run build:src"] : [],
5613
5625
  ...spec.app.length > 0 ? ["npm run build:app"] : [],
5614
- ...spec.engine ? ["npm run build:host"] : []
5626
+ ...spec.bin ? ["npm run build:host"] : []
5615
5627
  ].join(" && ");
5616
- if (hasSource || spec.engine) {
5617
- scripts["build:src"] = spec.src.map((environment) => `npm run build:src:${environment}`).join(" && ") + (spec.engine ? `${spec.src.length > 0 ? " && " : ""}npm run build:src:bin` : "");
5628
+ if (hasSource || spec.bin) {
5629
+ scripts["build:src"] = spec.src.map((environment) => `npm run build:src:${environment}`).join(" && ") + (spec.bin ? `${spec.src.length > 0 ? " && " : ""}npm run build:src:bin` : "");
5618
5630
  for (const environment of spec.src) scripts[`build:src:${environment}`] = environment === "browser" ? `vite build --config configs/src/vite.${environment}.config.ts` : `vite build --config configs/src/vite.${environment}.config.ts && npm run copy dist/src/${environment}/index.d.ts dist/src/${environment}/index.d.cts`;
5619
5631
  }
5620
5632
  if (spec.app.length > 0) {
@@ -5627,18 +5639,12 @@ function packageManifest(spec) {
5627
5639
  scripts["serve:build"] = "npm run build:app:server && npm run serve";
5628
5640
  }
5629
5641
  }
5630
- if (spec.engine) {
5642
+ if (spec.bin) {
5631
5643
  scripts["build:src:bin"] = "vite build --config configs/src/vite.bin.config.ts";
5632
5644
  scripts["build:host"] = "node -e \"import('./dist/src/server/index.js').then((m)=>{const n=m.stageHost(process.cwd(),'dist/host').length;console.log('build-host: staged '+n+' file(s) into dist/host')})\"";
5633
5645
  }
5634
- scripts.prepublishOnly = "npm run format:check && npm run lint:check && npm run check && npm run build && npm test" + (spec.engine ? " && npm run test:integration" : "");
5635
- const devDependencies = {
5636
- ...devDependenciesFor(spec.extras),
5637
- ...peerDevDependencies,
5638
- ...spec.src.includes("browser") ? SOURCE_BROWSER_DEV_DEPENDENCIES : {},
5639
- ...spec.app.includes("browser") ? APP_BROWSER_DEV_DEPENDENCIES : {},
5640
- ...spec.engine ? SOURCE_BROWSER_DEV_DEPENDENCIES : {}
5641
- };
5646
+ scripts.prepublishOnly = "npm run format:check && npm run lint:check && npm run check && npm run build && npm test" + (spec.integration ? " && npm run test:integration" : "");
5647
+ const devDependencies = devDependenciesFor(spec);
5642
5648
  const manifest = {
5643
5649
  name: hasSource ? `@orkestrel/${spec.name}` : spec.name,
5644
5650
  version: spec.version,
@@ -5652,15 +5658,15 @@ function packageManifest(spec) {
5652
5658
  type: "git",
5653
5659
  url: `git+https://github.com/orkestrel/${spec.name}.git`
5654
5660
  },
5655
- ...spec.engine ? { bin: { scaffold: "./dist/bin/scaffold.js" } } : {},
5656
- files: spec.engine ? [
5661
+ ...spec.bin ? { bin: { scaffold: "./dist/bin/scaffold.js" } } : {},
5662
+ files: spec.bin ? [
5657
5663
  "dist/src",
5658
5664
  "dist/bin",
5659
5665
  "dist/host",
5660
5666
  "README.md"
5661
5667
  ] : hasSource ? ["dist/src", "README.md"] : ["dist/app", "README.md"],
5662
5668
  type: "module",
5663
- ...hasSource ? { sideEffects: spec.engine ? ["./src/bin/scaffold.ts", "./dist/bin/scaffold.js"] : false } : {},
5669
+ ...hasSource ? { sideEffects: spec.bin ? ["./src/bin/scaffold.ts", "./dist/bin/scaffold.js"] : false } : {},
5664
5670
  ...entry === void 0 ? {} : {
5665
5671
  main: entry.main,
5666
5672
  module: entry.module,
@@ -5670,7 +5676,7 @@ function packageManifest(spec) {
5670
5676
  },
5671
5677
  scripts,
5672
5678
  dependencies,
5673
- devDependencies: Object.fromEntries(Object.entries(devDependencies).filter(([depName]) => !spec.engine || depName !== "@orkestrel/scaffold").filter(([depName]) => hasSource || depName !== "@microsoft/api-extractor" && depName !== "vite-plugin-dts").filter(([depName]) => hasSource || spec.app.includes("browser") || depName !== "@vitest/browser-playwright").sort(([a], [b]) => compareCodeUnit(a, b))),
5679
+ devDependencies: Object.fromEntries(Object.entries(devDependencies).filter(([depName]) => !spec.bin || depName !== "@orkestrel/scaffold").filter(([depName]) => hasSource || depName !== "@microsoft/api-extractor" && depName !== "vite-plugin-dts").filter(([depName]) => hasSource || spec.app.includes("browser") || depName !== "@vitest/browser-playwright").sort(([a], [b]) => compareCodeUnit(a, b))),
5674
5680
  ...Object.keys(peerDependencies).length > 0 ? { peerDependencies } : {},
5675
5681
  ...Object.keys(peerDependenciesMeta).length > 0 ? { peerDependenciesMeta } : {},
5676
5682
  engines: { node: spec.engines }
@@ -5743,7 +5749,7 @@ function rootTsconfig(src, app = []) {
5743
5749
  *
5744
5750
  * @param src - The declared published `Environment[]`.
5745
5751
  * @param app - The declared application `Environment[]`, defaulting to none.
5746
- * @param engine - Whether the workspace also builds its own executable.
5752
+ * @param bin - Whether the workspace also builds its own executable.
5747
5753
  * @returns The machinery set the generated header renders.
5748
5754
  *
5749
5755
  * @example
@@ -5752,8 +5758,8 @@ function rootTsconfig(src, app = []) {
5752
5758
  * viteMachinery([], ['core']) // { browser: false, vue: false, output: false }
5753
5759
  * ```
5754
5760
  */
5755
- function viteMachinery(src, app = [], engine = false) {
5756
- const unbuilt = src.length === 0 && app.length > 0 && !engine && app.every((environment) => environment === "core");
5761
+ function viteMachinery(src, app = [], bin = false) {
5762
+ const unbuilt = src.length === 0 && app.length > 0 && !bin && app.every((environment) => environment === "core");
5757
5763
  return {
5758
5764
  browser: src.includes("browser") || app.includes("browser"),
5759
5765
  vue: app.includes("browser"),
@@ -5761,6 +5767,96 @@ function viteMachinery(src, app = [], engine = false) {
5761
5767
  };
5762
5768
  }
5763
5769
  /**
5770
+ * Derive the one ordered Vitest project registration list shared by every
5771
+ * generated root configuration shape.
5772
+ *
5773
+ * @param src - The declared published environments.
5774
+ * @param app - The declared application environments.
5775
+ * @param axes - Optional executable, integration, and service project axes.
5776
+ * @returns Source projects, application projects, proof projects, then optional axis projects.
5777
+ *
5778
+ * @example
5779
+ * ```ts
5780
+ * viteProjectRegistrations(['core'], [], { integration: true })
5781
+ * // [{ project: 'srcCore' }, { project: 'policy' }, { project: 'guides' }, { project: 'integration' }]
5782
+ * ```
5783
+ */
5784
+ function viteProjectRegistrations(src, app = [], axes = {}) {
5785
+ const registrations = [];
5786
+ for (const environment of ENVIRONMENTS) {
5787
+ if (!src.includes(environment)) continue;
5788
+ if (environment === "core") registrations.push({ project: "srcCore" });
5789
+ if (environment === "browser") registrations.push({
5790
+ project: "srcBrowser",
5791
+ browser: SRC_MATRIX.browser.project
5792
+ });
5793
+ if (environment === "server") registrations.push({ project: "srcServer" });
5794
+ }
5795
+ for (const environment of ENVIRONMENTS) {
5796
+ if (!app.includes(environment)) continue;
5797
+ if (environment === "core") registrations.push({ project: "appCore" });
5798
+ if (environment === "browser") registrations.push({
5799
+ project: "appBrowser",
5800
+ browser: APP_MATRIX.browser.project
5801
+ });
5802
+ if (environment === "server") registrations.push({ project: "appServer" });
5803
+ }
5804
+ registrations.push({ project: "policy" }, { project: "guides" });
5805
+ if (axes.bin === true) registrations.push({ project: "srcBin" });
5806
+ if (axes.integration === true) registrations.push({ project: "integration" });
5807
+ if (axes.service === true) registrations.push({ project: "service" });
5808
+ return registrations;
5809
+ }
5810
+ /**
5811
+ * Render the one ordered proof and structural-axis project definition block.
5812
+ *
5813
+ * @param axes - Optional executable, integration, and service project axes.
5814
+ * @returns Policy, guides, then selected axis project definitions, separated by one blank line.
5815
+ *
5816
+ * @example
5817
+ * ```ts
5818
+ * viteProjectDefinitions({ bin: true }).includes('export const srcBin =') // true
5819
+ * ```
5820
+ */
5821
+ function viteProjectDefinitions(axes = {}) {
5822
+ const definitions = [policyViteProject(), guidesViteProject()];
5823
+ if (axes.bin === true) definitions.push(binViteProject());
5824
+ if (axes.integration === true) definitions.push(integrationViteProject(axes));
5825
+ if (axes.service === true) definitions.push(serviceViteProject());
5826
+ return definitions.join("\n");
5827
+ }
5828
+ /**
5829
+ * Render the root Vitest project registration, preserving browser ownership
5830
+ * supplied by the caller.
5831
+ *
5832
+ * @param registrations - Ordered project factory identifiers with optional browser labels.
5833
+ * @param browser - Whether the generated configuration carries browser machinery.
5834
+ * @returns The rendered root `test` property.
5835
+ *
5836
+ * @example
5837
+ * ```ts
5838
+ * renderViteTest([{ project: 'srcCore' }], false)
5839
+ * // '\ttest: {\n\t\tprojects: [srcCore],\n\t},'
5840
+ * ```
5841
+ */
5842
+ function renderViteTest(registrations, browser) {
5843
+ const projects = registrations.map((registration) => registration.project);
5844
+ const inlineProjects = ` projects: [${projects.join(", ")}],`;
5845
+ const renderedProjects = computeColumnWidth(inlineProjects) <= 100 ? inlineProjects : ` projects: [
5846
+ ${projects.map((project) => ` ${project},`).join("\n")}
5847
+ ],`;
5848
+ if (!browser) return ` test: {
5849
+ ${renderedProjects}
5850
+ },`;
5851
+ return ` test: gateBrowserProjects(
5852
+ [
5853
+ ${registrations.map((registration) => registration.browser === void 0 ? `{ project: ${registration.project} }` : `{ project: ${registration.project}, browser: ${serializeTypeScriptString(registration.browser)} }`).map((registration) => ` ${registration},`).join("\n")}
5854
+ ],
5855
+ hasChromium,
5856
+ process.argv,
5857
+ ),`;
5858
+ }
5859
+ /**
5764
5860
  * The rendered import / `resolve` header block every `rootViteConfig` shape
5765
5861
  * prefixes — the environment boundary and every guarantee it enforces ship
5766
5862
  * unconditionally; `machinery` selects only the host-specific pipelines layered
@@ -5798,6 +5894,7 @@ import { parse as parseVue } from 'vue/compiler-sfc'
5798
5894
  transform: {
5799
5895
  order: 'pre',
5800
5896
  async handler(code, id) {
5897
+ if (!isWorkspaceBoundaryModule(id)) return null
5801
5898
  const restored = /[?&]html-proxy(?:[=&]|$)/.test(id) ? restoreIgnoredHtml(code) : code
5802
5899
  const target = workspacePath(id)
5803
5900
  const physicalImporter = physicalPath(id)
@@ -5957,6 +6054,7 @@ import { parse as parseVue } from 'vue/compiler-sfc'
5957
6054
  transform: {
5958
6055
  order: 'pre',
5959
6056
  async handler(code, id) {
6057
+ if (!isWorkspaceBoundaryModule(id)) return null
5960
6058
  const target = workspacePath(id)
5961
6059
  const physicalImporter = physicalPath(id)
5962
6060
  const importerPackageRoot = trustedPackageRootFor(physicalImporter, trustedPackageRoots)
@@ -6039,6 +6137,7 @@ import { parse as parseVue } from 'vue/compiler-sfc'
6039
6137
  transform: {
6040
6138
  order: 'pre',
6041
6139
  async handler(code, id) {
6140
+ if (!isWorkspaceBoundaryModule(id)) return null
6042
6141
  const target = workspacePath(id)
6043
6142
  const physicalImporter = physicalPath(id)
6044
6143
  const importerPackageRoot = trustedPackageRootFor(physicalImporter, trustedPackageRoots)
@@ -6102,15 +6201,21 @@ import { parse as parseVue } from 'vue/compiler-sfc'
6102
6201
  const environmentBoundary = `
6103
6202
  ${CONST_KEYWORD} WORKSPACE_ROOT = realpathSync.native(dirname(fileURLToPath(import.meta.url)))${needsVue ? `\n${EXPORT_KEYWORD} ${CONST_KEYWORD} IMPORT_META_ENV_PREFIX = 'import.meta.env.'` : ""}
6104
6203
 
6204
+ ${EXPORT_KEYWORD} function fileSystemPath(pathname: string): string {
6205
+ if (!pathname.startsWith('/@fs/')) return pathname
6206
+ const candidate = pathname.slice('/@fs/'.length)
6207
+ // Vite URL normalization can collapse the leading slash of a POSIX absolute path.
6208
+ return candidate.startsWith('/') || /^[A-Za-z]:[\\\\/]/.test(candidate)
6209
+ ? candidate
6210
+ : \`/\${candidate}\`
6211
+ }
6212
+
6105
6213
  ${EXPORT_KEYWORD} function physicalPath(path: string): string {
6106
6214
  const [pathWithoutQuery] = path.split('?')
6107
- const candidate = pathWithoutQuery?.startsWith('/@fs/')
6108
- ? pathWithoutQuery.slice('/@fs/'.length)
6109
- : pathWithoutQuery
6110
- const physicalCandidate =
6111
- candidate !== undefined && /^file:/i.test(candidate) ? fileURLToPath(candidate) : candidate
6215
+ const candidate = fileSystemPath(pathWithoutQuery ?? path)
6216
+ const physicalCandidate = /^file:/i.test(candidate) ? fileURLToPath(candidate) : candidate
6112
6217
  const absoluteCandidate =
6113
- physicalCandidate === undefined || physicalCandidate.length === 0
6218
+ physicalCandidate.length === 0
6114
6219
  ? WORKSPACE_ROOT
6115
6220
  : isAbsolute(physicalCandidate)
6116
6221
  ? physicalCandidate
@@ -6130,13 +6235,68 @@ ${EXPORT_KEYWORD} function workspacePath(path: string): string | undefined {
6130
6235
  return relativePath
6131
6236
  }
6132
6237
 
6238
+ ${EXPORT_KEYWORD} function isBoundaryExemptModule(id: string): boolean {
6239
+ const normalizedId = id.replaceAll('\\\\', '/')
6240
+ const [path] = normalizedId.split(/[?#]/)
6241
+ if (
6242
+ path === undefined ||
6243
+ normalizedId.startsWith('\\0') ||
6244
+ normalizedId.includes('virtual:') ||
6245
+ normalizedId === '@vite/client' ||
6246
+ normalizedId === '@vite/env' ||
6247
+ normalizedId.startsWith('/@id/') ||
6248
+ normalizedId.startsWith('/@vite/') ||
6249
+ normalizedId.startsWith('/__vite') ||
6250
+ normalizedId.startsWith('/__vitest') ||
6251
+ normalizedId.startsWith('@vitest/browser') ||
6252
+ normalizedId.includes('/@vitest/browser/')
6253
+ ) {
6254
+ return true
6255
+ }
6256
+ let physicalId: string | undefined
6257
+ try {
6258
+ physicalId = physicalPath(id).replaceAll('\\\\', '/')
6259
+ } catch {
6260
+ physicalId = undefined
6261
+ }
6262
+ for (const candidate of physicalId === undefined ? [path] : [path, physicalId]) {
6263
+ if (candidate.split('/').some((segment) => segment.toLowerCase() === 'node_modules')) {
6264
+ return true
6265
+ }
6266
+ }
6267
+ return false
6268
+ }
6269
+
6270
+ ${EXPORT_KEYWORD} function isWorkspaceBoundaryModule(id: string): boolean {
6271
+ if (isBoundaryExemptModule(id)) return false
6272
+ const normalizedId = id.replaceAll('\\\\', '/')
6273
+ const [path] = normalizedId.split(/[?#]/)
6274
+ if (path === undefined) return false
6275
+ let candidate = fileSystemPath(path)
6276
+ try {
6277
+ if (/^file:/i.test(candidate)) candidate = fileURLToPath(candidate)
6278
+ } catch {
6279
+ return false
6280
+ }
6281
+ const rootRelative = /^\\/(?:app|src)\\/(?:core|browser|server)\\//.test(candidate)
6282
+ const absoluteCandidate = rootRelative
6283
+ ? resolvePath(WORKSPACE_ROOT, candidate.slice(1))
6284
+ : isAbsolute(candidate)
6285
+ ? candidate
6286
+ : resolvePath(WORKSPACE_ROOT, candidate)
6287
+ const relativeId = relative(WORKSPACE_ROOT, absoluteCandidate).replaceAll('\\\\', '/')
6288
+ return (
6289
+ relativeId !== '..' &&
6290
+ !relativeId.startsWith('../') &&
6291
+ !isAbsolute(relativeId) &&
6292
+ /^(?:app|src)\\/(?:core|browser|server)\\//.test(relativeId)
6293
+ )
6294
+ }
6295
+
6133
6296
  ${EXPORT_KEYWORD} function isOutsideWorkspacePath(path: string): boolean {
6134
6297
  const [pathWithoutQuery] = path.split('?')
6135
6298
  if (pathWithoutQuery === undefined) return false
6136
- const candidate = pathWithoutQuery.startsWith('/@fs/')
6137
- ? pathWithoutQuery.slice('/@fs/'.length)
6138
- : pathWithoutQuery
6139
- return isAbsolute(candidate)
6299
+ return isAbsolute(fileSystemPath(pathWithoutQuery))
6140
6300
  }
6141
6301
 
6142
6302
  ${EXPORT_KEYWORD} function containedPath(root: string, target: string): boolean {
@@ -6186,7 +6346,7 @@ ${EXPORT_KEYWORD} function browserServerPath(
6186
6346
  try {
6187
6347
  const decoded = decodeURIComponent(pathname)
6188
6348
  if (decoded.startsWith('/@fs/')) {
6189
- const candidate = decoded.slice('/@fs/'.length)
6349
+ const candidate = fileSystemPath(decoded)
6190
6350
  if (candidate.length === 0) return null
6191
6351
  return physicalPath(candidate)
6192
6352
  }
@@ -6205,8 +6365,13 @@ ${EXPORT_KEYWORD} function browserServerPath(
6205
6365
  return undefined
6206
6366
  }
6207
6367
  if (/^\\/@(?:app|src)\\//.test(decoded)) return null
6368
+ // Vite owns the app root request, while Vitest owns its in-memory tester root request.
6369
+ if (decoded === '/') return undefined
6208
6370
  if (!decoded.startsWith('/')) return null
6209
- return physicalPath(resolvePath(root, decoded.slice(1)))
6371
+ const candidate = physicalPath(resolvePath(root, decoded.slice(1)))
6372
+ if (existsSync(candidate) || !existsSync(decoded)) return candidate
6373
+ // An absolute module URL denotes itself; browserServerRoots still bounds it.
6374
+ return physicalPath(decoded)
6210
6375
  } catch {
6211
6376
  return null
6212
6377
  }
@@ -6707,6 +6872,12 @@ ${EXPORT_KEYWORD} function maskIgnoredHtml(environmentKeys: ReadonlySet<string>,
6707
6872
  )
6708
6873
  }
6709
6874
 
6875
+ ${EXPORT_KEYWORD} function isBrowserHtmlEntry(filename: string): boolean {
6876
+ return (
6877
+ physicalPath(filename) === physicalPath(resolvePath(WORKSPACE_ROOT, 'app/browser/index.html'))
6878
+ )
6879
+ }
6880
+
6710
6881
  ${EXPORT_KEYWORD} function prepareHtml(): Plugin {
6711
6882
  const environmentKeys = new Set<string>()
6712
6883
  return {
@@ -6722,7 +6893,10 @@ ${EXPORT_KEYWORD} function prepareHtml(): Plugin {
6722
6893
  },
6723
6894
  transformIndexHtml: {
6724
6895
  order: 'pre',
6725
- handler: maskIgnoredHtml.bind(undefined, environmentKeys),
6896
+ handler(html, context) {
6897
+ if (!isBrowserHtmlEntry(context.filename)) return undefined
6898
+ return maskIgnoredHtml(environmentKeys, html)
6899
+ },
6726
6900
  },
6727
6901
  }
6728
6902
  }
@@ -6731,7 +6905,10 @@ ${EXPORT_KEYWORD} function restoreHtml(): Plugin {
6731
6905
  return {
6732
6906
  name: 'orkestrel-html-boundary-restore',
6733
6907
  enforce: 'pre',
6734
- transformIndexHtml: restoreIgnoredHtml,
6908
+ transformIndexHtml(html, context) {
6909
+ if (!isBrowserHtmlEntry(context.filename)) return undefined
6910
+ return restoreIgnoredHtml(html)
6911
+ },
6735
6912
  }
6736
6913
  }
6737
6914
 
@@ -6741,7 +6918,8 @@ ${EXPORT_KEYWORD} function finalizeHtml(): Plugin {
6741
6918
  enforce: 'post',
6742
6919
  transformIndexHtml: {
6743
6920
  order: 'post',
6744
- handler(html) {
6921
+ handler(html, context) {
6922
+ if (!isBrowserHtmlEntry(context.filename)) return undefined
6745
6923
  if (!html.includes(HTML_SECURITY_META)) {
6746
6924
  throw new Error(
6747
6925
  '[orkestrel-environment-boundary] Browser HTML must retain its security policy',
@@ -6768,6 +6946,7 @@ ${EXPORT_KEYWORD} function finalizeHtml(): Plugin {
6768
6946
  const transformed = await transformWithOxc(code, path)
6769
6947
  const visitor = new Visitor({
6770
6948
  ImportExpression(node) {
6949
+ if (emitted) return
6771
6950
  let value: string | undefined
6772
6951
  if (node.source.type === 'Literal' && typeof node.source.value === 'string') {
6773
6952
  value = node.source.value
@@ -6866,8 +7045,8 @@ ${needsVue ? ` configureServer(server) {
6866
7045
  })
6867
7046
  },
6868
7047
  ` : ""} async resolveId(source, importer) {
6869
- if (importer === undefined) return null
6870
- if (source.startsWith('\\0')) return null
7048
+ if (importer === undefined || !isWorkspaceBoundaryModule(importer)) return null
7049
+ if (isBoundaryExemptModule(source)) return null
6871
7050
  const normalizedSource = source.replaceAll('\\\\', '/')
6872
7051
  const sourceError = environmentSourceError(owner, normalizedSource)
6873
7052
  if (sourceError !== undefined) this.error(sourceError)
@@ -6944,6 +7123,7 @@ ${needsVue ? ` configureServer(server) {
6944
7123
  return null
6945
7124
  },
6946
7125
  async load(id) {
7126
+ if (!isWorkspaceBoundaryModule(id)) return null
6947
7127
  const physicalImporter = physicalPath(id)
6948
7128
  const trustedPackageRoot = trustedPackageRootFor(physicalImporter, trustedPackageRoots)
6949
7129
  const inferredPackageRoot =
@@ -7001,6 +7181,7 @@ ${needsVue ? ` configureServer(server) {
7001
7181
  const physical = physicalPath(
7002
7182
  isAbsolute(original) ? original : resolvePath(environmentRoot, original),
7003
7183
  )
7184
+ if (isBoundaryExemptModule(original) || isBoundaryExemptModule(physical)) continue
7004
7185
  const target = workspacePath(physical)
7005
7186
  if (target === undefined) {
7006
7187
  if (trustedPackageRootFor(physical, trustedPackageRoots) === undefined) {
@@ -7016,6 +7197,7 @@ ${needsVue ? ` configureServer(server) {
7016
7197
  buildEnd(error) {
7017
7198
  if (error !== undefined) return
7018
7199
  for (const id of this.getModuleIds()) {
7200
+ if (!isWorkspaceBoundaryModule(id)) continue
7019
7201
  const target = workspacePath(id)
7020
7202
  if (target === undefined) {
7021
7203
  if (
@@ -7072,7 +7254,58 @@ ${CONST_KEYWORD} resolve = {
7072
7254
  }, {}),
7073
7255
  }
7074
7256
 
7075
- ${needsBrowser ? `${EXPORT_KEYWORD} ${CONST_KEYWORD} ENVIRONMENT_CSS = Object.freeze({
7257
+ ${needsBrowser ? `${EXPORT_KEYWORD} function gateBrowserProjects(
7258
+ registrations: readonly {
7259
+ readonly project: () => UserConfig
7260
+ readonly browser?: string
7261
+ }[],
7262
+ available: boolean,
7263
+ argv: readonly string[],
7264
+ ): NonNullable<UserConfig['test']> {
7265
+ const projects: UserConfig[] = []
7266
+ const gated: string[] = []
7267
+ for (const registration of registrations) {
7268
+ if (registration.browser !== undefined && !available) {
7269
+ gated.push(registration.browser)
7270
+ projects.push({
7271
+ resolve,
7272
+ test: {
7273
+ name: { label: registration.browser, color: 'yellow' },
7274
+ include: [],
7275
+ environment: 'node',
7276
+ browser: { enabled: false },
7277
+ },
7278
+ })
7279
+ continue
7280
+ }
7281
+ projects.push(registration.project())
7282
+ }
7283
+ if (gated.length === 0) return { projects }
7284
+ console.warn(\`browser projects skipped: Chromium absent (\${gated.join(', ')})\`)
7285
+ const filters: string[] = []
7286
+ let readable = true
7287
+ for (let index = 0; index < argv.length; index += 1) {
7288
+ const argument = argv[index]
7289
+ if (argument === '--project') {
7290
+ const filter = argv[index + 1]
7291
+ if (filter === undefined) {
7292
+ readable = false
7293
+ continue
7294
+ }
7295
+ filters.push(filter)
7296
+ index += 1
7297
+ continue
7298
+ }
7299
+ if (argument?.startsWith('--project=') === true) {
7300
+ filters.push(argument.slice('--project='.length))
7301
+ }
7302
+ }
7303
+ return readable && filters.length > 0 && filters.every((filter) => gated.includes(filter))
7304
+ ? { passWithNoTests: true, projects }
7305
+ : { projects }
7306
+ }
7307
+
7308
+ ${EXPORT_KEYWORD} ${CONST_KEYWORD} ENVIRONMENT_CSS = Object.freeze({
7076
7309
  transformer: 'lightningcss',
7077
7310
  lightningcss: {
7078
7311
  visitor: () => {
@@ -7103,11 +7336,28 @@ ${needsBrowser ? `${EXPORT_KEYWORD} ${CONST_KEYWORD} ENVIRONMENT_CSS = Object.fr
7103
7336
  },
7104
7337
  },
7105
7338
  } satisfies CSSOptions)
7339
+
7340
+ /** Prevent the Vitest browser mid-run "optimized dependencies changed, reloading" stall. */
7341
+ ${EXPORT_KEYWORD} ${CONST_KEYWORD} BROWSER_TEST_DEPENDENCIES = Object.freeze([
7342
+ '@vitest/browser/client',
7343
+ 'vitest/browser',
7344
+ 'vitest/internal/browser',
7345
+ 'vitest',
7346
+ ])
7106
7347
  ` : ""}${EXPORT_KEYWORD} ${CONST_KEYWORD} PACKAGE_MANIFEST_BYTES = 1_048_576
7107
7348
  ${EXPORT_KEYWORD} ${CONST_KEYWORD} ENVIRONMENT_MODULE_BYTES = 8_388_608
7108
7349
  ${environmentBoundary}`;
7109
7350
  }
7110
- /** Build the dedicated Node-only repository policy Vitest project. */
7351
+ /**
7352
+ * Build the dedicated standalone Node-only repository-policy Vitest project.
7353
+ *
7354
+ * @returns The emitted `policy` project definition.
7355
+ *
7356
+ * @example
7357
+ * ```ts
7358
+ * policyViteProject().includes("label: 'policy'") // true
7359
+ * ```
7360
+ */
7111
7361
  function policyViteProject() {
7112
7362
  return `${EXPORT_KEYWORD} const policy = (config?: UserConfig): UserConfig =>
7113
7363
  mergeConfig(
@@ -7126,12 +7376,150 @@ function policyViteProject() {
7126
7376
  `;
7127
7377
  }
7128
7378
  /**
7379
+ * Build the standalone Node-only guide-parity Vitest project.
7380
+ *
7381
+ * @returns The emitted `guides` project definition.
7382
+ *
7383
+ * @example
7384
+ * ```ts
7385
+ * guidesViteProject().includes("label: 'guides'") // true
7386
+ * ```
7387
+ */
7388
+ function guidesViteProject() {
7389
+ return `${EXPORT_KEYWORD} const guides = (config?: UserConfig): UserConfig =>
7390
+ mergeConfig(
7391
+ {
7392
+ resolve,
7393
+ test: {
7394
+ name: { label: 'guides', color: 'green' },
7395
+ include: ['tests/guides/**/*.test.ts'],
7396
+ exclude: ['tests/src/**/*.test.ts', 'tests/app/**/*.test.ts', 'tests/setup.test.ts'],
7397
+ setupFiles: ['./tests/setup.ts'],
7398
+ environment: 'node',
7399
+ browser: { enabled: false },
7400
+ },
7401
+ },
7402
+ config ?? {},
7403
+ )
7404
+ `;
7405
+ }
7406
+ /**
7407
+ * Build the executable's dedicated Node-only build and test project.
7408
+ *
7409
+ * @returns The emitted `srcBin` project definition.
7410
+ *
7411
+ * @example
7412
+ * ```ts
7413
+ * binViteProject().includes("label: 'src:bin'") // true
7414
+ * ```
7415
+ */
7416
+ function binViteProject() {
7417
+ return `${EXPORT_KEYWORD} const srcBin = (config?: UserConfig): UserConfig =>
7418
+ mergeConfig(
7419
+ {
7420
+ resolve,
7421
+ publicDir: false,
7422
+ plugins: [outputBoundary('dist/bin')],
7423
+ build: {
7424
+ emptyOutDir: true,
7425
+ sourcemap: true,
7426
+ minify: false,
7427
+ lib: {
7428
+ entry: resolveWorkspacePath('src/bin/scaffold.ts'),
7429
+ formats: ['es'],
7430
+ fileName: () => 'scaffold.js',
7431
+ },
7432
+ outDir: 'dist/bin',
7433
+ target: 'node22',
7434
+ rolldownOptions: { external: [/^node:/, /^@orkestrel\\//, /^@src\\//] },
7435
+ },
7436
+ test: {
7437
+ name: { label: 'src:bin', color: 'yellow' },
7438
+ include: ['tests/src/bin/**/*.test.ts'],
7439
+ setupFiles: ['./tests/setup.ts', './tests/setupServer.ts'],
7440
+ environment: 'node',
7441
+ browser: { enabled: false },
7442
+ },
7443
+ },
7444
+ config ?? {},
7445
+ )
7446
+ `;
7447
+ }
7448
+ /**
7449
+ * Build the standalone Node-only installed-consumer integration proof project.
7450
+ *
7451
+ * @param axes - Optional executable and integration axes controlling the shared registry setup.
7452
+ * @returns The emitted `integration` project definition.
7453
+ *
7454
+ * @example
7455
+ * ```ts
7456
+ * integrationViteProject({ bin: true, integration: true }).includes(
7457
+ * "globalSetup: ['./tests/setupIntegration.ts']",
7458
+ * ) // true
7459
+ * ```
7460
+ */
7461
+ function integrationViteProject(axes = {}) {
7462
+ return `${EXPORT_KEYWORD} const integration = (config?: UserConfig): UserConfig =>
7463
+ mergeConfig(
7464
+ {
7465
+ resolve,
7466
+ test: {
7467
+ name: { label: 'integration', color: 'blue' },
7468
+ include: ['tests/integration/**/*.test.ts'],
7469
+ setupFiles: ['./tests/setup.ts'],
7470
+ ${axes.bin === true && axes.integration === true ? ` // Wire the template registry for the generated-consumer proof.
7471
+ globalSetup: ['./tests/setupIntegration.ts'],
7472
+ ` : ""} environment: 'node',
7473
+ browser: { enabled: false },
7474
+ testTimeout: 120_000,
7475
+ hookTimeout: 120_000,
7476
+ fileParallelism: false,
7477
+ },
7478
+ },
7479
+ config ?? {},
7480
+ )
7481
+ `;
7482
+ }
7483
+ /**
7484
+ * Build the standalone Node-only live-service proof project.
7485
+ *
7486
+ * @returns The emitted `service` project definition.
7487
+ *
7488
+ * @example
7489
+ * ```ts
7490
+ * serviceViteProject().includes("label: 'service'") // true
7491
+ * ```
7492
+ */
7493
+ function serviceViteProject() {
7494
+ return `${EXPORT_KEYWORD} const service = (config?: UserConfig): UserConfig =>
7495
+ mergeConfig(
7496
+ {
7497
+ resolve,
7498
+ test: {
7499
+ name: { label: 'service', color: 'red' },
7500
+ include: ['tests/service/**/*.test.ts'],
7501
+ setupFiles: ['./tests/setup.ts', './tests/setupService.ts'],
7502
+ environment: 'node',
7503
+ browser: { enabled: false },
7504
+ testTimeout: 120_000,
7505
+ hookTimeout: 120_000,
7506
+ fileParallelism: false,
7507
+ },
7508
+ },
7509
+ config ?? {},
7510
+ )
7511
+ `;
7512
+ }
7513
+ /**
7129
7514
  * The single non-`core` environment's factory IS the base (Shape 3 of
7130
7515
  * `rootViteConfig`) — the environment's own `viteHeader` (Playwright only when
7131
7516
  * `environment === 'browser'`, per the live sqlite/indexeddb exemplars) prefixes
7132
- * the environment-specific `srcBrowser` / `srcServer` + `guides` projects export.
7517
+ * the environment-specific `srcBrowser` / `srcServer` project, followed by the
7518
+ * standalone policy and guides proof projects and any selected structural-axis
7519
+ * projects.
7133
7520
  *
7134
7521
  * @param environment - The sole declared non-`core` environment.
7522
+ * @param axes - Optional executable, integration, and service project axes.
7135
7523
  * @returns The root `vite.config.ts` file content for a single non-`core` environment, newline-terminated.
7136
7524
  *
7137
7525
  * @example
@@ -7139,11 +7527,12 @@ function policyViteProject() {
7139
7527
  * singleSrcViteConfig('server').includes('srcServer') // true
7140
7528
  * ```
7141
7529
  */
7142
- function singleSrcViteConfig(environment) {
7530
+ function singleSrcViteConfig(environment, axes = {}) {
7143
7531
  const machinery = viteMachinery([environment]);
7144
7532
  const header = viteHeader(machinery);
7533
+ const renderedTest = renderViteTest(viteProjectRegistrations([environment], [], axes), machinery.browser);
7534
+ const definitions = viteProjectDefinitions(axes);
7145
7535
  if (environment === "browser") return `${header}
7146
- if (!hasChromium) console.warn('browser projects skipped: Chromium absent (${SRC_MATRIX.browser.project})')
7147
7536
  ${EXPORT_KEYWORD} const srcBrowser = (config?: UserConfig): UserConfig =>
7148
7537
  mergeConfig(
7149
7538
  {
@@ -7168,11 +7557,22 @@ ${EXPORT_KEYWORD} const srcBrowser = (config?: UserConfig): UserConfig =>
7168
7557
  },
7169
7558
  test: {
7170
7559
  name: { label: 'src:browser', color: 'yellow' },
7171
- include: hasChromium ? ['tests/src/browser/**/*.test.ts'] : [],
7172
- passWithNoTests: !hasChromium,
7560
+ include: ['tests/src/browser/**/*.test.ts'],
7173
7561
  setupFiles: ['./tests/setup.ts', './tests/setupBrowser.ts'],
7562
+ ...(config?.test?.browser?.enabled === false
7563
+ ? {}
7564
+ : {
7565
+ deps: {
7566
+ optimizer: {
7567
+ client: {
7568
+ enabled: true,
7569
+ include: [...BROWSER_TEST_DEPENDENCIES],
7570
+ },
7571
+ },
7572
+ },
7573
+ }),
7174
7574
  browser: {
7175
- enabled: hasChromium,
7575
+ enabled: true,
7176
7576
  provider: playwright(),
7177
7577
  instances: [{ browser: 'chromium', headless: true }],
7178
7578
  },
@@ -7182,28 +7582,10 @@ ${EXPORT_KEYWORD} const srcBrowser = (config?: UserConfig): UserConfig =>
7182
7582
  config ?? {},
7183
7583
  )
7184
7584
 
7185
- ${policyViteProject()}
7186
- ${EXPORT_KEYWORD} const guides = (config?: UserConfig): UserConfig =>
7187
- srcBrowser(
7188
- mergeConfig(
7189
- {
7190
- test: {
7191
- name: { label: 'guides', color: 'green' },
7192
- include: ['tests/guides/**/*.test.ts'],
7193
- exclude: ['tests/src/**/*.test.ts', 'tests/setup.test.ts'],
7194
- environment: 'node',
7195
- browser: { enabled: false },
7196
- },
7197
- },
7198
- config ?? {},
7199
- ),
7200
- )
7201
-
7585
+ ${definitions}
7202
7586
  export default defineConfig({
7203
7587
  resolve,
7204
- test: {
7205
- projects: [...(hasChromium ? [srcBrowser] : []), policy, guides],
7206
- },
7588
+ ${renderedTest}
7207
7589
  })
7208
7590
  `;
7209
7591
  return `${header}
@@ -7239,33 +7621,18 @@ ${EXPORT_KEYWORD} const srcServer = (config?: UserConfig): UserConfig =>
7239
7621
  config ?? {},
7240
7622
  )
7241
7623
 
7242
- ${policyViteProject()}
7243
- ${EXPORT_KEYWORD} const guides = (config?: UserConfig): UserConfig =>
7244
- srcServer(
7245
- mergeConfig(
7246
- {
7247
- test: {
7248
- name: { label: 'guides', color: 'green' },
7249
- include: ['tests/guides/**/*.test.ts'],
7250
- exclude: ['tests/src/**/*.test.ts', 'tests/setup.test.ts'],
7251
- },
7252
- },
7253
- config ?? {},
7254
- ),
7255
- )
7256
-
7624
+ ${definitions}
7257
7625
  export default defineConfig({
7258
7626
  resolve,
7259
- test: {
7260
- projects: [srcServer, policy, guides],
7261
- },
7627
+ ${renderedTest}
7262
7628
  })
7263
7629
  `;
7264
7630
  }
7265
7631
  /**
7266
7632
  * The root `vite.config.ts` — three grounded shapes, chosen by a blueprint's
7267
7633
  * `src`:
7268
- * 1. `core`-only — `srcCore` + `guides`, no Playwright at all (the live
7634
+ * 1. `core`-only — `srcCore` + standalone policy/guides proof projects, no
7635
+ * Playwright at all (the live
7269
7636
  * timeout exemplar: no browser project exists anywhere in the file).
7270
7637
  * 2. Multi-environment (2+ src, always including `core` per the live
7271
7638
  * middleware/router exemplars) — `srcCore` is the shared base;
@@ -7280,10 +7647,9 @@ export default defineConfig({
7280
7647
  * environment is `browser` (it must run its own tests in a real browser).
7281
7648
  *
7282
7649
  * @param src - The declared `Environment[]`.
7283
- * @param engine - Structural: `true` appends the `srcBin` project (an
7284
- * executable build target, never a barrel) after the other declared
7285
- * projects the self-hosting tax, grounded against this very repo's own
7286
- * checked-in `vite.config.ts`.
7650
+ * @param axes - Optional structural project axes. `bin` appends the standalone
7651
+ * executable build-and-test project; `integration` and `service` append their
7652
+ * standalone proof projects.
7287
7653
  * @returns The root `vite.config.ts` file content, newline-terminated.
7288
7654
  *
7289
7655
  * @example
@@ -7291,14 +7657,14 @@ export default defineConfig({
7291
7657
  * rootViteConfig(['core']).includes('srcCore') // true
7292
7658
  * ```
7293
7659
  */
7294
- function rootViteConfig(src, engine = false) {
7660
+ function rootViteConfig(src, axes = {}) {
7295
7661
  const hasCore = src.includes("core");
7296
- const nonCore = src.filter((environment) => environment !== "core");
7662
+ const nonCore = ENVIRONMENTS.filter((environment) => environment !== "core" && src.includes(environment));
7297
7663
  const machinery = viteMachinery(src);
7298
7664
  const header = viteHeader(machinery);
7299
7665
  if (!hasCore) {
7300
7666
  const [onlyEnvironment] = nonCore;
7301
- if (onlyEnvironment === "browser" || onlyEnvironment === "server") return singleSrcViteConfig(onlyEnvironment);
7667
+ if (onlyEnvironment === "browser" || onlyEnvironment === "server") return singleSrcViteConfig(onlyEnvironment, axes);
7302
7668
  }
7303
7669
  const browserBlock = `
7304
7670
  ${EXPORT_KEYWORD} const srcBrowser = (config?: UserConfig): UserConfig =>
@@ -7323,12 +7689,23 @@ ${EXPORT_KEYWORD} const srcBrowser = (config?: UserConfig): UserConfig =>
7323
7689
  },
7324
7690
  test: {
7325
7691
  name: { label: 'src:browser', color: 'yellow' },
7326
- include: hasChromium ? ['tests/src/browser/**/*.test.ts'] : [],
7327
- passWithNoTests: !hasChromium,
7692
+ include: ['tests/src/browser/**/*.test.ts'],
7328
7693
  exclude: ['tests/src/core/**/*.test.ts'],
7329
7694
  setupFiles: ['./tests/setup.ts', './tests/setupBrowser.ts'],
7695
+ ...(config?.test?.browser?.enabled === false
7696
+ ? {}
7697
+ : {
7698
+ deps: {
7699
+ optimizer: {
7700
+ client: {
7701
+ enabled: true,
7702
+ include: [...BROWSER_TEST_DEPENDENCIES],
7703
+ },
7704
+ },
7705
+ },
7706
+ }),
7330
7707
  browser: {
7331
- enabled: hasChromium,
7708
+ enabled: true,
7332
7709
  provider: playwright(),
7333
7710
  instances: [{ browser: 'chromium', headless: true }],
7334
7711
  },
@@ -7381,65 +7758,10 @@ ${EXPORT_KEYWORD} const srcServer = (config?: UserConfig): UserConfig =>
7381
7758
  ),
7382
7759
  )
7383
7760
  `;
7384
- const binBlock = engine ? `
7385
- ${EXPORT_KEYWORD} const srcBin = (config?: UserConfig): UserConfig =>
7386
- srcCore(
7387
- mergeConfig(
7388
- {
7389
- publicDir: false,
7390
- plugins: [outputBoundary('dist/bin')],
7391
- build: {
7392
- lib: {
7393
- entry: resolveWorkspacePath('src/bin/scaffold.ts'),
7394
- formats: ['es'],
7395
- fileName: () => 'scaffold.js',
7396
- },
7397
- outDir: 'dist/bin',
7398
- target: 'node22',
7399
- rolldownOptions: {
7400
- external: [/^node:/, /^@orkestrel\\//, /^@src\\//],
7401
- },
7402
- },
7403
- test: {
7404
- name: { label: 'src:bin', color: 'yellow' },
7405
- include: ['tests/src/bin/**/*.test.ts'],
7406
- exclude: ['tests/src/core/**/*.test.ts', 'tests/src/server/**/*.test.ts'],
7407
- setupFiles: ['./tests/setup.ts', './tests/setupServer.ts'],
7408
- },
7409
- },
7410
- config ?? {},
7411
- ),
7412
- )
7413
-
7414
- ${EXPORT_KEYWORD} const integration = (config?: UserConfig): UserConfig =>
7415
- srcBin(
7416
- mergeConfig(
7417
- {
7418
- test: {
7419
- name: { label: 'integration', color: 'blue' },
7420
- include: ['tests/integration/**/*.test.ts'],
7421
- exclude: ['tests/src/**/*.test.ts', 'tests/guides/**/*.test.ts'],
7422
- },
7423
- },
7424
- config ?? {},
7425
- ),
7426
- )
7427
- ` : "";
7428
- const blocks = nonCore.map((environment) => environment === "browser" ? browserBlock : serverBlock).join("") + binBlock;
7429
- const projectNames = [
7430
- ...hasCore ? ["srcCore"] : [],
7431
- ...nonCore.map((environment) => environment === "browser" ? "...(hasChromium ? [srcBrowser] : [])" : `src${pascalCase(environment)}`),
7432
- "policy",
7433
- "guides",
7434
- ...engine ? ["srcBin"] : [],
7435
- ...engine ? ["integration"] : []
7436
- ];
7437
- const inlineProjects = ` projects: [${projectNames.join(", ")}],`;
7438
- const renderedProjects = computeColumnWidth(inlineProjects) <= 100 ? inlineProjects : ` projects: [
7439
- ${projectNames.map((project) => ` ${project},`).join("\n")}
7440
- ],`;
7761
+ const blocks = nonCore.map((environment) => environment === "browser" ? browserBlock : serverBlock).join("");
7762
+ const renderedTest = renderViteTest(viteProjectRegistrations(src, [], axes), machinery.browser);
7441
7763
  return `${header}
7442
- ${machinery.browser ? `if (!hasChromium) console.warn('browser projects skipped: Chromium absent (${SRC_MATRIX.browser.project})')\n` : ""}${EXPORT_KEYWORD} const srcCore = (config?: UserConfig): UserConfig =>
7764
+ ${EXPORT_KEYWORD} const srcCore = (config?: UserConfig): UserConfig =>
7443
7765
  mergeConfig(
7444
7766
  {
7445
7767
  resolve,
@@ -7459,27 +7781,11 @@ ${machinery.browser ? `if (!hasChromium) console.warn('browser projects skipped:
7459
7781
  },
7460
7782
  config ?? {},
7461
7783
  )
7462
-
7463
- ${policyViteProject()}
7464
- ${EXPORT_KEYWORD} const guides = (config?: UserConfig): UserConfig =>
7465
- srcCore(
7466
- mergeConfig(
7467
- {
7468
- test: {
7469
- name: { label: 'guides', color: 'green' },
7470
- include: ['tests/guides/**/*.test.ts'],
7471
- exclude: ['tests/src/**/*.test.ts', 'tests/setup.test.ts'],
7472
- },
7473
- },
7474
- config ?? {},
7475
- ),
7476
- )
7477
7784
  ${blocks}
7785
+ ${viteProjectDefinitions(axes)}
7478
7786
  export default defineConfig({
7479
7787
  resolve,
7480
- test: {
7481
- ${renderedProjects}
7482
- },
7788
+ ${renderedTest}
7483
7789
  })
7484
7790
  `;
7485
7791
  }
@@ -7489,7 +7795,7 @@ ${renderedProjects}
7489
7795
  *
7490
7796
  * @param src - Published src environments.
7491
7797
  * @param app - Private app environments.
7492
- * @param engine - Whether the workspace also builds scaffold's executable.
7798
+ * @param axes - Optional executable, integration, and service project axes.
7493
7799
  * @returns The root `vite.config.ts` content.
7494
7800
  *
7495
7801
  * @example
@@ -7497,15 +7803,12 @@ ${renderedProjects}
7497
7803
  * applicationViteConfig([], ['core', 'server']).includes('appServer') // true
7498
7804
  * ```
7499
7805
  */
7500
- function applicationViteConfig(src, app, engine = false) {
7806
+ function applicationViteConfig(src, app, axes = {}) {
7501
7807
  const hasSourceCore = src.includes("core");
7502
- const machinery = viteMachinery(src, app, engine);
7808
+ const machinery = viteMachinery(src, app, axes.bin === true);
7503
7809
  const header = viteHeader(machinery);
7504
- const projects = [];
7505
7810
  const blocks = [];
7506
- if (src.includes("core")) {
7507
- projects.push("srcCore");
7508
- blocks.push(`
7811
+ if (src.includes("core")) blocks.push(`
7509
7812
  ${EXPORT_KEYWORD} const srcCore = (config?: UserConfig): UserConfig =>
7510
7813
  mergeConfig(
7511
7814
  {
@@ -7524,9 +7827,7 @@ ${EXPORT_KEYWORD} const srcCore = (config?: UserConfig): UserConfig =>
7524
7827
  config ?? {},
7525
7828
  )
7526
7829
  `);
7527
- }
7528
7830
  if (src.includes("browser")) {
7529
- projects.push("...(hasChromium ? [srcBrowser] : [])");
7530
7831
  const coreOutput = hasSourceCore ? `
7531
7832
  output: { paths: { '@src/core': '../core/index.js' } },` : "";
7532
7833
  const coreExternal = hasSourceCore ? `id === '@src/core' || ` : "";
@@ -7555,11 +7856,22 @@ ${EXPORT_KEYWORD} const srcBrowser = (config?: UserConfig): UserConfig =>
7555
7856
  },
7556
7857
  test: {
7557
7858
  name: { label: 'src:browser', color: 'yellow' },
7558
- include: hasChromium ? ['tests/src/browser/**/*.test.ts'] : [],
7559
- passWithNoTests: !hasChromium,
7859
+ include: ['tests/src/browser/**/*.test.ts'],
7560
7860
  ${hasSourceCore ? "exclude: ['tests/src/core/**/*.test.ts'],\n " : ""}setupFiles: ['./tests/setup.ts', './tests/setupBrowser.ts'],
7861
+ ...(config?.test?.browser?.enabled === false
7862
+ ? {}
7863
+ : {
7864
+ deps: {
7865
+ optimizer: {
7866
+ client: {
7867
+ enabled: true,
7868
+ include: [...BROWSER_TEST_DEPENDENCIES],
7869
+ },
7870
+ },
7871
+ },
7872
+ }),
7561
7873
  browser: {
7562
- enabled: hasChromium,
7874
+ enabled: true,
7563
7875
  provider: playwright(),
7564
7876
  instances: [{ browser: 'chromium', headless: true }],
7565
7877
  },
@@ -7571,7 +7883,6 @@ ${EXPORT_KEYWORD} const srcBrowser = (config?: UserConfig): UserConfig =>
7571
7883
  `);
7572
7884
  }
7573
7885
  if (src.includes("server")) {
7574
- projects.push("srcServer");
7575
7886
  const coreOutput = hasSourceCore ? `
7576
7887
  output: [
7577
7888
  {
@@ -7621,9 +7932,7 @@ ${EXPORT_KEYWORD} const srcServer = (config?: UserConfig): UserConfig =>
7621
7932
  )
7622
7933
  `);
7623
7934
  }
7624
- if (app.includes("core")) {
7625
- projects.push("appCore");
7626
- blocks.push(`
7935
+ if (app.includes("core")) blocks.push(`
7627
7936
  ${EXPORT_KEYWORD} const appCore = (config?: UserConfig): UserConfig =>
7628
7937
  mergeConfig(
7629
7938
  {
@@ -7641,10 +7950,7 @@ ${EXPORT_KEYWORD} const appCore = (config?: UserConfig): UserConfig =>
7641
7950
  config ?? {},
7642
7951
  )
7643
7952
  `);
7644
- }
7645
- if (app.includes("browser")) {
7646
- projects.push("...(hasChromium ? [appBrowser()] : [])");
7647
- blocks.push(`
7953
+ if (app.includes("browser")) blocks.push(`
7648
7954
  ${EXPORT_KEYWORD} function appBrowser(...config: readonly never[]): UserConfig {
7649
7955
  if (config.length > 0) {
7650
7956
  throw new Error(
@@ -7663,7 +7969,6 @@ ${EXPORT_KEYWORD} function appBrowser(...config: readonly never[]): UserConfig {
7663
7969
  prepareHtml(),
7664
7970
  finalizeHtml(),
7665
7971
  ],
7666
- optimizeDeps: { include: ['vue'] },
7667
7972
  root: resolveWorkspacePath('app/browser'),
7668
7973
  publicDir: false,
7669
7974
  server: {
@@ -7684,11 +7989,18 @@ ${EXPORT_KEYWORD} function appBrowser(...config: readonly never[]): UserConfig {
7684
7989
  name: { label: '${APP_MATRIX.browser.project}', color: 'blue' },
7685
7990
  root: resolveWorkspacePath('.'),
7686
7991
  dir: resolveWorkspacePath('.'),
7687
- include: hasChromium ? ['tests/app/browser/**/*.test.ts'] : [],
7688
- passWithNoTests: !hasChromium,
7992
+ include: ['tests/app/browser/**/*.test.ts'],
7689
7993
  setupFiles: ['./tests/setup.ts', './tests/setupBrowser.ts'],
7994
+ deps: {
7995
+ optimizer: {
7996
+ client: {
7997
+ enabled: true,
7998
+ include: ['vue', ...BROWSER_TEST_DEPENDENCIES],
7999
+ },
8000
+ },
8001
+ },
7690
8002
  browser: {
7691
- enabled: hasChromium,
8003
+ enabled: true,
7692
8004
  provider: playwright(),
7693
8005
  instances: [{ browser: 'chromium', headless: true }],
7694
8006
  },
@@ -7697,10 +8009,7 @@ ${EXPORT_KEYWORD} function appBrowser(...config: readonly never[]): UserConfig {
7697
8009
  }
7698
8010
  }
7699
8011
  `);
7700
- }
7701
- if (app.includes("server")) {
7702
- projects.push("appServer");
7703
- blocks.push(`
8012
+ if (app.includes("server")) blocks.push(`
7704
8013
  ${EXPORT_KEYWORD} const appServer = (config?: UserConfig): UserConfig =>
7705
8014
  mergeConfig(
7706
8015
  {
@@ -7731,89 +8040,13 @@ ${EXPORT_KEYWORD} const appServer = (config?: UserConfig): UserConfig =>
7731
8040
  config ?? {},
7732
8041
  )
7733
8042
  `);
7734
- }
7735
- if (engine) {
7736
- projects.push("srcBin", "integration");
7737
- blocks.push(`
7738
- ${EXPORT_KEYWORD} const srcBin = (config?: UserConfig): UserConfig =>
7739
- mergeConfig(
7740
- {
7741
- resolve,
7742
- publicDir: false,
7743
- plugins: [outputBoundary('dist/bin')],
7744
- build: {
7745
- lib: {
7746
- entry: resolveWorkspacePath('src/bin/scaffold.ts'),
7747
- formats: ['es'],
7748
- fileName: () => 'scaffold.js',
7749
- },
7750
- outDir: 'dist/bin',
7751
- target: 'node22',
7752
- rolldownOptions: { external: [/^node:/, /^@orkestrel\\//, /^@src\\//] },
7753
- },
7754
- test: {
7755
- name: { label: 'src:bin', color: 'yellow' },
7756
- include: ['tests/src/bin/**/*.test.ts'],
7757
- setupFiles: ['./tests/setup.ts', './tests/setupServer.ts'],
7758
- environment: 'node',
7759
- browser: { enabled: false },
7760
- },
7761
- },
7762
- config ?? {},
7763
- )
7764
-
7765
- ${EXPORT_KEYWORD} const integration = (config?: UserConfig): UserConfig =>
7766
- srcBin(
7767
- mergeConfig(
7768
- {
7769
- test: {
7770
- name: { label: 'integration', color: 'blue' },
7771
- include: ['tests/integration/**/*.test.ts'],
7772
- exclude: ['tests/src/**/*.test.ts', 'tests/app/**/*.test.ts', 'tests/guides/**/*.test.ts'],
7773
- },
7774
- },
7775
- config ?? {},
7776
- ),
7777
- )
7778
- `);
7779
- }
7780
- const projectNames = [
7781
- ...projects,
7782
- "policy",
7783
- "guides"
7784
- ];
7785
- const inlineProjects = ` projects: [${projectNames.join(", ")}],`;
7786
- const renderedProjects = computeColumnWidth(inlineProjects) <= 100 ? inlineProjects : ` projects: [
7787
- ${projectNames.map((project) => ` ${project},`).join("\n")}
7788
- ],`;
7789
- const browserProjects = [...src.includes("browser") ? [SRC_MATRIX.browser.project] : [], ...app.includes("browser") ? [APP_MATRIX.browser.project] : []];
7790
- const browserNotice = serializeTypeScriptString(`browser projects skipped: Chromium absent (${browserProjects.join(", ")})`);
7791
- const inlineBrowserNotice = `if (!hasChromium) console.warn(${browserNotice})`;
7792
- const renderedBrowserNotice = browserProjects.length === 0 ? void 0 : computeColumnWidth(inlineBrowserNotice) <= 100 ? inlineBrowserNotice : `if (!hasChromium)
7793
- console.warn(${browserNotice})`;
7794
- return `${header}
7795
- ${renderedBrowserNotice === void 0 ? "" : `${renderedBrowserNotice}\n`}${policyViteProject()}
7796
- ${EXPORT_KEYWORD} const guides = (config?: UserConfig): UserConfig =>
7797
- mergeConfig(
7798
- {
7799
- resolve,
7800
- test: {
7801
- name: { label: 'guides', color: 'green' },
7802
- include: ['tests/guides/**/*.test.ts'],
7803
- exclude: ['tests/src/**/*.test.ts', 'tests/app/**/*.test.ts', 'tests/setup.test.ts'],
7804
- setupFiles: ['./tests/setup.ts'],
7805
- environment: 'node',
7806
- browser: { enabled: false },
7807
- },
7808
- },
7809
- config ?? {},
7810
- )
7811
- ${blocks.join("")}
8043
+ const renderedTest = renderViteTest(viteProjectRegistrations(src, app, axes), machinery.browser);
8044
+ const definitions = viteProjectDefinitions(axes);
8045
+ return `${header}${blocks.join("")}
8046
+ ${definitions}
7812
8047
  export default defineConfig({
7813
8048
  resolve,
7814
- test: {
7815
- ${renderedProjects}
7816
- },
8049
+ ${renderedTest}
7817
8050
  })
7818
8051
  `;
7819
8052
  }
@@ -7964,6 +8197,67 @@ export default defineConfig(
7964
8197
  `;
7965
8198
  }
7966
8199
  /**
8200
+ * Build the executable's declaration-only `configs/src/tsconfig.bin.json`.
8201
+ *
8202
+ * @returns The executable `tsconfig` file content, newline-terminated.
8203
+ *
8204
+ * @example
8205
+ * ```ts
8206
+ * binTsconfig().includes('"outDir": "../../dist/bin"') // true
8207
+ * ```
8208
+ */
8209
+ function binTsconfig() {
8210
+ return formatJson({
8211
+ extends: "../../tsconfig.json",
8212
+ compilerOptions: {
8213
+ lib: ["ESNext"],
8214
+ types: ["node"],
8215
+ noEmit: false,
8216
+ declaration: true,
8217
+ emitDeclarationOnly: true,
8218
+ rootDir: "../../src",
8219
+ outDir: "../../dist/bin"
8220
+ },
8221
+ include: TYPESCRIPT_EXTENSIONS.map((extension) => `../../src/bin/**/*.${extension}`)
8222
+ });
8223
+ }
8224
+ /**
8225
+ * Build the executable's `configs/src/vite.bin.config.ts` wrapper.
8226
+ *
8227
+ * @returns The executable Vite configuration content, newline-terminated.
8228
+ *
8229
+ * @example
8230
+ * ```ts
8231
+ * binViteConfig().includes("banner: '#!/usr/bin/env node'") // true
8232
+ * ```
8233
+ */
8234
+ function binViteConfig() {
8235
+ return `import { defineConfig } from 'vite'
8236
+ import { srcBin } from '../../vite.config'
8237
+
8238
+ // The \`scaffold\` executable build — a single ESM lib file, no declarations (an
8239
+ // executable ships no types), with the \`#!/usr/bin/env node\` shebang re-emitted via
8240
+ // \`output.banner\` (rolldown strips shebangs from source during bundling), and
8241
+ // \`output.paths\` rewriting the externalized \`@src/*\` specifiers to the built sibling
8242
+ // src environments (relative to \`dist/bin/\`), so the emitted bin resolves at runtime.
8243
+ export default defineConfig(
8244
+ srcBin({
8245
+ build: {
8246
+ rolldownOptions: {
8247
+ output: {
8248
+ banner: '#!/usr/bin/env node',
8249
+ paths: {
8250
+ '@src/core': '../src/core/index.js',
8251
+ '@src/server': '../src/server/index.js',
8252
+ },
8253
+ },
8254
+ },
8255
+ },
8256
+ }),
8257
+ )
8258
+ `;
8259
+ }
8260
+ /**
7967
8261
  * Build one `configs/app/tsconfig.<environment>.json` check-only configuration.
7968
8262
  *
7969
8263
  * @param environment - The application environment.
@@ -8012,12 +8306,25 @@ export default defineConfig(${anchor}())
8012
8306
  `;
8013
8307
  }
8014
8308
  /**
8015
- * Build selection-aware GitHub CI without external service dependencies.
8309
+ * Build selection-aware GitHub CI, provisioning the declared foreign service before its proof.
8016
8310
  *
8017
8311
  * @param spec - The workspace blueprint.
8018
8312
  * @returns The complete `.github/workflows/ci.yml` content.
8019
8313
  */
8020
8314
  function ciWorkflow(spec) {
8315
+ const browser = spec.bin || spec.src.includes("browser") || spec.app.includes("browser") ? `
8316
+ - name: Install Playwright browsers
8317
+ run: npx --no-install playwright install --with-deps chromium
8318
+ ` : "";
8319
+ const tail = [];
8320
+ if (spec.integration) tail.push(` - name: Run live consumer integration
8321
+ run: npm run test:integration`);
8322
+ if (spec.service) {
8323
+ tail.push(` - name: Provision live service
8324
+ run: bash ${SERVICE_SCRIPT_PATH}`);
8325
+ tail.push(` - name: Run live service tests
8326
+ run: npm run test:service`);
8327
+ }
8021
8328
  return `name: ci.yml
8022
8329
 
8023
8330
  on:
@@ -8049,10 +8356,7 @@ jobs:
8049
8356
 
8050
8357
  - name: Install dependencies
8051
8358
  run: npm ci --ignore-scripts
8052
- ${spec.engine || spec.src.includes("browser") || spec.app.includes("browser") ? `
8053
- - name: Install Playwright browsers
8054
- run: npx --no-install playwright install --with-deps chromium
8055
- ` : ""}
8359
+ ${browser}
8056
8360
  - name: Check formatting
8057
8361
  run: npm run format:check
8058
8362
 
@@ -8066,11 +8370,7 @@ ${spec.engine || spec.src.includes("browser") || spec.app.includes("browser") ?
8066
8370
  run: npm run build
8067
8371
 
8068
8372
  - name: Run tests
8069
- run: npm test${spec.engine ? `
8070
-
8071
- - name: Run live consumer integration
8072
- run: npm run test:integration
8073
- ` : ""}
8373
+ run: npm test${tail.length === 0 ? "" : `\n\n${tail.join("\n\n")}`}
8074
8374
  `;
8075
8375
  }
8076
8376
  /**
@@ -8088,7 +8388,7 @@ ${spec.engine || spec.src.includes("browser") || spec.app.includes("browser") ?
8088
8388
  * ```
8089
8389
  */
8090
8390
  function configArtifacts(spec) {
8091
- const machinery = viteMachinery(spec.src, spec.app, spec.engine);
8391
+ const machinery = viteMachinery(spec.src, spec.app, spec.bin);
8092
8392
  const artifacts = [{
8093
8393
  path: "tsconfig.json",
8094
8394
  group: "configs",
@@ -8098,7 +8398,7 @@ function configArtifacts(spec) {
8098
8398
  path: "vite.config.ts",
8099
8399
  group: "configs",
8100
8400
  origin: "computed",
8101
- content: spec.app.length > 0 ? applicationViteConfig(spec.src, spec.app, spec.engine) : rootViteConfig(spec.src, spec.engine)
8401
+ content: spec.app.length > 0 ? applicationViteConfig(spec.src, spec.app, spec) : rootViteConfig(spec.src, spec)
8102
8402
  }];
8103
8403
  for (const environment of spec.src) {
8104
8404
  const row = SRC_MATRIX[environment];
@@ -8114,6 +8414,12 @@ function configArtifacts(spec) {
8114
8414
  });
8115
8415
  }
8116
8416
  }
8417
+ if (spec.bin) for (const path of BIN_CONFIGS) artifacts.push({
8418
+ path,
8419
+ group: "configs",
8420
+ origin: "computed",
8421
+ content: path.endsWith(".json") ? binTsconfig() : binViteConfig()
8422
+ });
8117
8423
  for (const environment of spec.app) {
8118
8424
  const row = APP_MATRIX[environment];
8119
8425
  for (const path of row.configs) {
@@ -9259,6 +9565,6 @@ function createBlueprint(data) {
9259
9565
  return candidate;
9260
9566
  }
9261
9567
  //#endregion
9262
- export { APP_BROWSER_DEV_DEPENDENCIES, APP_MATRIX, BASE_DEV_DEPENDENCIES, CATEGORIES, CHECKOUT_ACTION_SHA, COMPILER_ID, COMPILE_STAGES, CONST_KEYWORD, CONTROL_CHARACTER_PATTERN, Compiler, DEFAULT_ENGINES, DEFAULT_VERSION, DEPENDENCY_NAME_PATTERN, ENGINES_PATTERN, ENVIRONMENTS, EXPORT_KEYWORD, EXTRA_NAME_PATTERN, EXTRA_RANGE_PATTERN, FRESHNESS, FUNCTION_KEYWORD, GROUPS, HEX_PATTERN, HOST_PATHS, IMPORT_KEYWORD, INVALID_PATH_CHARACTER_PATTERN, JSON_PRINT_WIDTH, JSON_TAB_WIDTH, MAX_ARTIFACT_BYTES, MAX_ARTIFACT_HEX_LENGTH, MAX_COLLECTION_ITEMS, MAX_DATA_GRAPH_KEYS, MAX_DATA_GRAPH_NODES, MAX_DEPENDENCY_NAME_LENGTH, MAX_MANIFEST_BYTES, MAX_NAME_LENGTH, MAX_PATH_LENGTH, MAX_RANGE_LENGTH, MAX_SERIALIZED_INPUT_BYTES, MAX_TOTAL_ARTIFACT_BYTES, MINIMUM_NODE_VERSION, NAME_PATTERN, ORIGINS, ORKESTREL_RANGE_PATTERN, PlanManager, SCAFFOLD_RANGE, SETUP_NODE_ACTION_SHA, SOURCE_BROWSER_DEV_DEPENDENCIES, SRC_MATRIX, SYNC_BASELINE_PATTERN, ScaffoldError, TEMPLATES, TYPESCRIPT_EXTENSIONS, VERSION_PATTERN, alignTable, appTsconfig, appViteConfig, applicationArtifacts, applicationViteConfig, applyOverrides, artifactShape, auditToReview, blueprint, blueprintShape, blueprintToMembers, blueprintToPlan, bytesToHex, catalogNames, catalogToBlock, ciWorkflow, compareCodeUnit, computeColumnWidth, computeHash, configArtifacts, contentByteLength, contentCodePoint, contentToBytes, contentToHex, coreTsconfig, coreViteConfig, createBlueprint, createCompiler, createPlanManager, delimiterCell, dependency, dependencyShape, devDependenciesFor, diffPlan, dualCondition, entryFields, escapeHtmlText, exportsMap, fillArtifact, findFileConflict, findPathConflict, formatJson, guideArtifacts, guideMemberTable, guideMethods, guideTests, guideUsage, hasBlueprintEnvironment, hasOnlyDataProperties, hasValidArtifactBytes, hasValidArtifactHex, hasValidAuditBytes, hasValidBlueprintBytes, hasValidOverrideBytes, hasValidPlanBytes, hasValidPlanHex, hasValidSnapshotBytes, hasValidSyncReportBytes, hostGroup, inferGroup, isArtifact, isBehind, isBlueprint, isCompilerEventHooks, isDenseDataArray, isDependency, isEmitterErrorHandler, isMember, isOverride, isPlan, isPlanManagerEventHooks, isScaffoldError, isSyncReport, isWorkspaceName, manifestToDependencies, manifestToName, member, memberShape, override, overrideShape, ownDataValue, packageManifest, padCell, paritySpecifiers, parseBlueprint, parseBoundedJSON, parseCompilerOptions, parsePlan, parsePlanIds, parsePlanManagerOptions, parseSyncReport, pascalCase, pinPlan, planPayload, planShape, planToReview, planToSummary, policyViteProject, rangeToFreshness, renderArray, renderObject, renderValue, rootTsconfig, rootViteConfig, selectHostPaths, serializeTypeScriptString, singleSrcViteConfig, snapshotOf, snapshotPlan, sourceArtifacts, splitTableRow, srcTsconfig, srcVariant, srcViteConfig, stableStringify, syncReportShape, syncToReview, testArtifacts, validateBlueprint, validateDependencyArray, validatePlan, viteHeader, viteMachinery };
9568
+ export { APP_BROWSER_DEV_DEPENDENCIES, APP_MATRIX, BASE_DEV_DEPENDENCIES, BIN_CONFIGS, CATEGORIES, CHECKOUT_ACTION_SHA, COMPILER_ID, COMPILE_STAGES, CONST_KEYWORD, CONTROL_CHARACTER_PATTERN, Compiler, DEFAULT_ENGINES, DEFAULT_VERSION, DEPENDENCY_NAME_PATTERN, ENGINES_PATTERN, ENVIRONMENTS, EXPORT_KEYWORD, EXTRA_NAME_PATTERN, EXTRA_RANGE_PATTERN, FRESHNESS, FUNCTION_KEYWORD, GROUPS, HEX_PATTERN, HOST_PATHS, IMPORT_KEYWORD, INVALID_PATH_CHARACTER_PATTERN, JSON_PRINT_WIDTH, JSON_TAB_WIDTH, MAX_ARTIFACT_BYTES, MAX_ARTIFACT_HEX_LENGTH, MAX_COLLECTION_ITEMS, MAX_DATA_GRAPH_KEYS, MAX_DATA_GRAPH_NODES, MAX_DEPENDENCY_NAME_LENGTH, MAX_MANIFEST_BYTES, MAX_NAME_LENGTH, MAX_PATH_LENGTH, MAX_RANGE_LENGTH, MAX_SERIALIZED_INPUT_BYTES, MAX_TOTAL_ARTIFACT_BYTES, MINIMUM_NODE_VERSION, NAME_PATTERN, ORIGINS, ORKESTREL_RANGE_PATTERN, PlanManager, SCAFFOLD_RANGE, SERVICE_SCRIPT_PATH, SETUP_NODE_ACTION_SHA, SOURCE_BROWSER_DEV_DEPENDENCIES, SRC_MATRIX, SYNC_BASELINE_PATTERN, ScaffoldError, TEMPLATES, TYPESCRIPT_EXTENSIONS, VERSION_PATTERN, alignTable, appTsconfig, appViteConfig, applicationArtifacts, applicationViteConfig, applyOverrides, artifactShape, auditToReview, binTsconfig, binViteConfig, binViteProject, blueprint, blueprintShape, blueprintToMembers, blueprintToPlan, bytesToHex, catalogNames, catalogToBlock, ciWorkflow, compareCodeUnit, computeColumnWidth, computeHash, configArtifacts, contentByteLength, contentCodePoint, contentToBytes, contentToHex, coreTsconfig, coreViteConfig, createBlueprint, createCompiler, createPlanManager, delimiterCell, dependency, dependencyShape, devDependenciesFor, diffPlan, dualCondition, entryFields, escapeHtmlText, exportsMap, fillArtifact, findFileConflict, findPathConflict, formatJson, guideArtifacts, guideMemberTable, guideMethods, guideTests, guideUsage, guidesViteProject, hasBlueprintEnvironment, hasOnlyDataProperties, hasValidArtifactBytes, hasValidArtifactHex, hasValidAuditBytes, hasValidBlueprintBytes, hasValidOverrideBytes, hasValidPlanBytes, hasValidPlanHex, hasValidSnapshotBytes, hasValidSyncReportBytes, hostGroup, inferGroup, integrationViteProject, isArtifact, isBehind, isBlueprint, isCompilerEventHooks, isDenseDataArray, isDependency, isEmitterErrorHandler, isMember, isOverride, isPlan, isPlanManagerEventHooks, isScaffoldError, isSyncReport, isWorkspaceName, manifestToDependencies, manifestToName, member, memberShape, override, overrideShape, ownDataValue, packageManifest, padCell, paritySpecifiers, parseBlueprint, parseBoundedJSON, parseCompilerOptions, parsePlan, parsePlanIds, parsePlanManagerOptions, parseSyncReport, pascalCase, pinPlan, planPayload, planShape, planToReview, planToSummary, policyViteProject, rangeToFreshness, renderArray, renderObject, renderValue, renderViteTest, rootTsconfig, rootViteConfig, selectHostPaths, serializeTypeScriptString, serviceViteProject, singleSrcViteConfig, snapshotOf, snapshotPlan, sourceArtifacts, splitTableRow, srcTsconfig, srcVariant, srcViteConfig, stableStringify, syncReportShape, syncToReview, testArtifacts, validateBlueprint, validateDependencyArray, validatePlan, viteHeader, viteMachinery, viteProjectDefinitions, viteProjectRegistrations };
9263
9569
 
9264
9570
  //# sourceMappingURL=index.js.map