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