@orkestrel/scaffold 0.0.5 → 0.0.7

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.
@@ -104,9 +104,12 @@ var APP_MATRIX = Object.freeze({
104
104
  * The root docs (`AGENTS.md` / `CLAUDE.md`), `LICENSE`, `.agents`, `.claude`, `.codex`,
105
105
  * the four SessionStart hook scripts (`scripts/deps.sh` / `scripts/cursor.sh` /
106
106
  * `scripts/codex.sh` / `scripts/ollama.sh`), the repository coding-law policy module,
107
- * the line's seven byte-identical root dotfiles, and the two guides-grouped mirrors every repo carries: the line-wide
108
- * dev-tooling guide (`guides/src/guide.md`) and the
109
- * scaffold engine's own self-guide (`guides/src/scaffold.md`).
107
+ * the line's seven byte-identical root dotfiles, and the two guides-grouped
108
+ * mirror candidates: the line-wide dev-tooling guide
109
+ * (`guides/src/guide.md`) and the scaffold engine's own self-guide
110
+ * (`guides/src/scaffold.md`). `stageHost` vendors both; each plan carries the
111
+ * subset selected by `selectHostPaths`, omitting the target blueprint's own
112
+ * guide.
110
113
  */
111
114
  var HOST_PATHS = Object.freeze([
112
115
  "AGENTS.md",
@@ -218,15 +221,15 @@ var DEFAULT_VERSION = "0.0.1";
218
221
  /** The `engines.node` range the `blueprint` builder fills. */
219
222
  var DEFAULT_ENGINES = `>=${MINIMUM_NODE_VERSION}`;
220
223
  /** The devDependency range generated packages pin `@orkestrel/scaffold` at. */
221
- var SCAFFOLD_RANGE = "^0.0.5";
224
+ var SCAFFOLD_RANGE = "^0.0.7";
222
225
  /** Tooling versions shared by scaffold and every generated workspace. */
223
226
  var BASE_DEV_DEPENDENCIES = Object.freeze({
224
227
  "@microsoft/api-extractor": "^7.58.12",
225
228
  "@orkestrel/guide": "^0.0.5",
226
229
  "@orkestrel/scaffold": SCAFFOLD_RANGE,
227
- "@types/node": "^26.1.1",
228
- oxfmt: "^0.60.0",
229
- oxlint: "^1.75.0",
230
+ "@types/node": "^26.1.2",
231
+ oxfmt: "^0.61.0",
232
+ oxlint: "^1.76.0",
230
233
  typescript: "^6.0.3",
231
234
  vite: "^8.1.5",
232
235
  "vite-plugin-dts": "^5.0.3",
@@ -1140,6 +1143,22 @@ function snapshotOf(current) {
1140
1143
  return Object.fromEntries(entries);
1141
1144
  }
1142
1145
  /**
1146
+ * Select host paths without the guide owned by the target blueprint.
1147
+ *
1148
+ * @param paths - Host artifact paths in deterministic input order.
1149
+ * @param name - The target blueprint's unscoped package name.
1150
+ * @returns Every host path except `guides/src/<name>.md`, in input order.
1151
+ *
1152
+ * @example
1153
+ * ```ts
1154
+ * selectHostPaths(['guides/src/guide.md', 'LICENSE'], 'guide') // ['LICENSE']
1155
+ * ```
1156
+ */
1157
+ function selectHostPaths(paths, name) {
1158
+ const guide = `guides/src/${name}.md`;
1159
+ return paths.filter((path) => path !== guide);
1160
+ }
1161
+ /**
1143
1162
  * Find the first exact or portable case-insensitive path collision.
1144
1163
  *
1145
1164
  * @param paths - Portable paths in deterministic input order.
@@ -1442,6 +1461,29 @@ function manifestToDependencies(manifestText) {
1442
1461
  return dependencies;
1443
1462
  }
1444
1463
  /**
1464
+ * Project a `package.json` text to its own string `name`.
1465
+ *
1466
+ * @param manifest - The `package.json` file content.
1467
+ * @returns The own string `name`, or `undefined` when the manifest exceeds its
1468
+ * byte ceiling, is malformed, has a non-object root, or has no own string
1469
+ * `name`.
1470
+ *
1471
+ * @example
1472
+ * ```ts
1473
+ * import { manifestToName } from '@orkestrel/scaffold'
1474
+ *
1475
+ * manifestToName('{"name":"@orkestrel/router"}') // '@orkestrel/router'
1476
+ * manifestToName('{}') // undefined
1477
+ * ```
1478
+ */
1479
+ function manifestToName(manifest) {
1480
+ if (manifest.length > 1048576 || contentByteLength(manifest) > 1048576) return;
1481
+ const parsed = (0, _orkestrel_contract.parseJSON)(manifest);
1482
+ if (!(0, _orkestrel_contract.isRecord)(parsed)) return void 0;
1483
+ const name = ownDataValue(parsed, "name");
1484
+ return typeof name === "string" ? name : void 0;
1485
+ }
1486
+ /**
1445
1487
  * Compare a declared range to the registry latest.
1446
1488
  *
1447
1489
  * @param range - The declared semver range.
@@ -2146,6 +2188,7 @@ var isPlan = (0, _orkestrel_contract.andOf)((0, _orkestrel_contract.andOf)((0, _
2146
2188
  function validatePlan(plan) {
2147
2189
  const blueprintValidation = validateBlueprint(plan.blueprint);
2148
2190
  const questions = [...blueprintValidation.questions];
2191
+ const warnings = [...blueprintValidation.warnings];
2149
2192
  if (!hasValidPlanBytes(plan)) questions.push({
2150
2193
  field: "artifacts",
2151
2194
  text: "Plan artifact content exceeds the retained byte limits",
@@ -2170,16 +2213,20 @@ function validatePlan(plan) {
2170
2213
  });
2171
2214
  continue;
2172
2215
  }
2173
- if (artifact.path === "package.json") questions.push({
2174
- field: "overrides",
2175
- text: "Override path \"package.json\" targets the blueprint-owned publication boundary",
2176
- blocking: true
2177
- });
2216
+ if (artifact.path === "package.json") {
2217
+ questions.push({
2218
+ field: "overrides",
2219
+ text: "Override path \"package.json\" targets the blueprint-owned publication boundary",
2220
+ blocking: true
2221
+ });
2222
+ continue;
2223
+ }
2224
+ warnings.push(`Override path "${item.path}" replaces its planned artifact content`);
2178
2225
  }
2179
2226
  return {
2180
2227
  valid: questions.length === 0,
2181
2228
  questions,
2182
- warnings: blueprintValidation.warnings
2229
+ warnings
2183
2230
  };
2184
2231
  }
2185
2232
  /** Determine whether every guide body fits the public UTF-8 artifact byte limit. */
@@ -5555,6 +5602,7 @@ function packageManifest(spec) {
5555
5602
  }
5556
5603
  if (spec.engine) scripts["test:src:bin"] = "vitest run --config vite.config.ts --no-cache --reporter=dot --project src:bin";
5557
5604
  if (spec.engine) scripts["test:integration"] = "vitest run --config vite.config.ts --no-cache --reporter=dot --project integration";
5605
+ if (spec.engine) 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)\"";
5558
5606
  if (spec.app.length > 0) {
5559
5607
  scripts["test:app"] = "vitest run --config vite.config.ts --no-cache --reporter=dot " + spec.app.map((environment) => `--project ${APP_MATRIX[environment].project}`).join(" ");
5560
5608
  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}`;
@@ -5715,6 +5763,37 @@ function viteMachinery(src, app = [], engine = false) {
5715
5763
  };
5716
5764
  }
5717
5765
  /**
5766
+ * Render the root Vitest project registration, preserving browser ownership
5767
+ * supplied by the caller.
5768
+ *
5769
+ * @param registrations - Ordered project factory identifiers with optional browser labels.
5770
+ * @param browser - Whether the generated configuration carries browser machinery.
5771
+ * @returns The rendered root `test` property.
5772
+ *
5773
+ * @example
5774
+ * ```ts
5775
+ * renderViteTest([{ project: 'srcCore' }], false)
5776
+ * // '\ttest: {\n\t\tprojects: [srcCore],\n\t},'
5777
+ * ```
5778
+ */
5779
+ function renderViteTest(registrations, browser) {
5780
+ const projects = registrations.map((registration) => registration.project);
5781
+ const inlineProjects = ` projects: [${projects.join(", ")}],`;
5782
+ const renderedProjects = computeColumnWidth(inlineProjects) <= 100 ? inlineProjects : ` projects: [
5783
+ ${projects.map((project) => ` ${project},`).join("\n")}
5784
+ ],`;
5785
+ if (!browser) return ` test: {
5786
+ ${renderedProjects}
5787
+ },`;
5788
+ return ` test: gateBrowserProjects(
5789
+ [
5790
+ ${registrations.map((registration) => registration.browser === void 0 ? `{ project: ${registration.project} }` : `{ project: ${registration.project}, browser: ${serializeTypeScriptString(registration.browser)} }`).map((registration) => ` ${registration},`).join("\n")}
5791
+ ],
5792
+ hasChromium,
5793
+ process.argv,
5794
+ ),`;
5795
+ }
5796
+ /**
5718
5797
  * The rendered import / `resolve` header block every `rootViteConfig` shape
5719
5798
  * prefixes — the environment boundary and every guarantee it enforces ship
5720
5799
  * unconditionally; `machinery` selects only the host-specific pipelines layered
@@ -5752,6 +5831,7 @@ import { parse as parseVue } from 'vue/compiler-sfc'
5752
5831
  transform: {
5753
5832
  order: 'pre',
5754
5833
  async handler(code, id) {
5834
+ if (!isWorkspaceBoundaryModule(id)) return null
5755
5835
  const restored = /[?&]html-proxy(?:[=&]|$)/.test(id) ? restoreIgnoredHtml(code) : code
5756
5836
  const target = workspacePath(id)
5757
5837
  const physicalImporter = physicalPath(id)
@@ -5911,6 +5991,7 @@ import { parse as parseVue } from 'vue/compiler-sfc'
5911
5991
  transform: {
5912
5992
  order: 'pre',
5913
5993
  async handler(code, id) {
5994
+ if (!isWorkspaceBoundaryModule(id)) return null
5914
5995
  const target = workspacePath(id)
5915
5996
  const physicalImporter = physicalPath(id)
5916
5997
  const importerPackageRoot = trustedPackageRootFor(physicalImporter, trustedPackageRoots)
@@ -5993,6 +6074,7 @@ import { parse as parseVue } from 'vue/compiler-sfc'
5993
6074
  transform: {
5994
6075
  order: 'pre',
5995
6076
  async handler(code, id) {
6077
+ if (!isWorkspaceBoundaryModule(id)) return null
5996
6078
  const target = workspacePath(id)
5997
6079
  const physicalImporter = physicalPath(id)
5998
6080
  const importerPackageRoot = trustedPackageRootFor(physicalImporter, trustedPackageRoots)
@@ -6084,6 +6166,64 @@ ${EXPORT_KEYWORD} function workspacePath(path: string): string | undefined {
6084
6166
  return relativePath
6085
6167
  }
6086
6168
 
6169
+ ${EXPORT_KEYWORD} function isBoundaryExemptModule(id: string): boolean {
6170
+ const normalizedId = id.replaceAll('\\\\', '/')
6171
+ const [path] = normalizedId.split(/[?#]/)
6172
+ if (
6173
+ path === undefined ||
6174
+ normalizedId.startsWith('\\0') ||
6175
+ normalizedId.includes('virtual:') ||
6176
+ normalizedId === '@vite/client' ||
6177
+ normalizedId === '@vite/env' ||
6178
+ normalizedId.startsWith('/@id/') ||
6179
+ normalizedId.startsWith('/@vite/') ||
6180
+ normalizedId.startsWith('/__vite') ||
6181
+ normalizedId.startsWith('/__vitest') ||
6182
+ normalizedId.startsWith('@vitest/browser') ||
6183
+ normalizedId.includes('/@vitest/browser/')
6184
+ ) {
6185
+ return true
6186
+ }
6187
+ let physicalId: string | undefined
6188
+ try {
6189
+ physicalId = physicalPath(id).replaceAll('\\\\', '/')
6190
+ } catch {
6191
+ physicalId = undefined
6192
+ }
6193
+ for (const candidate of physicalId === undefined ? [path] : [path, physicalId]) {
6194
+ if (candidate.split('/').some((segment) => segment.toLowerCase() === 'node_modules')) {
6195
+ return true
6196
+ }
6197
+ }
6198
+ return false
6199
+ }
6200
+
6201
+ ${EXPORT_KEYWORD} function isWorkspaceBoundaryModule(id: string): boolean {
6202
+ if (isBoundaryExemptModule(id)) return false
6203
+ const normalizedId = id.replaceAll('\\\\', '/')
6204
+ const [path] = normalizedId.split(/[?#]/)
6205
+ if (path === undefined) return false
6206
+ let candidate = path.startsWith('/@fs/') ? path.slice('/@fs/'.length) : path
6207
+ try {
6208
+ if (/^file:/i.test(candidate)) candidate = fileURLToPath(candidate)
6209
+ } catch {
6210
+ return false
6211
+ }
6212
+ const rootRelative = /^\\/(?:app|src)\\/(?:core|browser|server)\\//.test(candidate)
6213
+ const absoluteCandidate = rootRelative
6214
+ ? resolvePath(WORKSPACE_ROOT, candidate.slice(1))
6215
+ : isAbsolute(candidate)
6216
+ ? candidate
6217
+ : resolvePath(WORKSPACE_ROOT, candidate)
6218
+ const relativeId = relative(WORKSPACE_ROOT, absoluteCandidate).replaceAll('\\\\', '/')
6219
+ return (
6220
+ relativeId !== '..' &&
6221
+ !relativeId.startsWith('../') &&
6222
+ !isAbsolute(relativeId) &&
6223
+ /^(?:app|src)\\/(?:core|browser|server)\\//.test(relativeId)
6224
+ )
6225
+ }
6226
+
6087
6227
  ${EXPORT_KEYWORD} function isOutsideWorkspacePath(path: string): boolean {
6088
6228
  const [pathWithoutQuery] = path.split('?')
6089
6229
  if (pathWithoutQuery === undefined) return false
@@ -6722,6 +6862,7 @@ ${EXPORT_KEYWORD} function finalizeHtml(): Plugin {
6722
6862
  const transformed = await transformWithOxc(code, path)
6723
6863
  const visitor = new Visitor({
6724
6864
  ImportExpression(node) {
6865
+ if (emitted) return
6725
6866
  let value: string | undefined
6726
6867
  if (node.source.type === 'Literal' && typeof node.source.value === 'string') {
6727
6868
  value = node.source.value
@@ -6820,8 +6961,8 @@ ${needsVue ? ` configureServer(server) {
6820
6961
  })
6821
6962
  },
6822
6963
  ` : ""} async resolveId(source, importer) {
6823
- if (importer === undefined) return null
6824
- if (source.startsWith('\\0')) return null
6964
+ if (importer === undefined || !isWorkspaceBoundaryModule(importer)) return null
6965
+ if (isBoundaryExemptModule(source)) return null
6825
6966
  const normalizedSource = source.replaceAll('\\\\', '/')
6826
6967
  const sourceError = environmentSourceError(owner, normalizedSource)
6827
6968
  if (sourceError !== undefined) this.error(sourceError)
@@ -6898,6 +7039,7 @@ ${needsVue ? ` configureServer(server) {
6898
7039
  return null
6899
7040
  },
6900
7041
  async load(id) {
7042
+ if (!isWorkspaceBoundaryModule(id)) return null
6901
7043
  const physicalImporter = physicalPath(id)
6902
7044
  const trustedPackageRoot = trustedPackageRootFor(physicalImporter, trustedPackageRoots)
6903
7045
  const inferredPackageRoot =
@@ -6955,6 +7097,7 @@ ${needsVue ? ` configureServer(server) {
6955
7097
  const physical = physicalPath(
6956
7098
  isAbsolute(original) ? original : resolvePath(environmentRoot, original),
6957
7099
  )
7100
+ if (isBoundaryExemptModule(original) || isBoundaryExemptModule(physical)) continue
6958
7101
  const target = workspacePath(physical)
6959
7102
  if (target === undefined) {
6960
7103
  if (trustedPackageRootFor(physical, trustedPackageRoots) === undefined) {
@@ -6970,6 +7113,7 @@ ${needsVue ? ` configureServer(server) {
6970
7113
  buildEnd(error) {
6971
7114
  if (error !== undefined) return
6972
7115
  for (const id of this.getModuleIds()) {
7116
+ if (!isWorkspaceBoundaryModule(id)) continue
6973
7117
  const target = workspacePath(id)
6974
7118
  if (target === undefined) {
6975
7119
  if (
@@ -7026,7 +7170,58 @@ ${CONST_KEYWORD} resolve = {
7026
7170
  }, {}),
7027
7171
  }
7028
7172
 
7029
- ${needsBrowser ? `${EXPORT_KEYWORD} ${CONST_KEYWORD} ENVIRONMENT_CSS = Object.freeze({
7173
+ ${needsBrowser ? `${EXPORT_KEYWORD} function gateBrowserProjects(
7174
+ registrations: readonly {
7175
+ readonly project: () => UserConfig
7176
+ readonly browser?: string
7177
+ }[],
7178
+ available: boolean,
7179
+ argv: readonly string[],
7180
+ ): NonNullable<UserConfig['test']> {
7181
+ const projects: UserConfig[] = []
7182
+ const gated: string[] = []
7183
+ for (const registration of registrations) {
7184
+ if (registration.browser !== undefined && !available) {
7185
+ gated.push(registration.browser)
7186
+ projects.push({
7187
+ resolve,
7188
+ test: {
7189
+ name: { label: registration.browser, color: 'yellow' },
7190
+ include: [],
7191
+ environment: 'node',
7192
+ browser: { enabled: false },
7193
+ },
7194
+ })
7195
+ continue
7196
+ }
7197
+ projects.push(registration.project())
7198
+ }
7199
+ if (gated.length === 0) return { projects }
7200
+ console.warn(\`browser projects skipped: Chromium absent (\${gated.join(', ')})\`)
7201
+ const filters: string[] = []
7202
+ let readable = true
7203
+ for (let index = 0; index < argv.length; index += 1) {
7204
+ const argument = argv[index]
7205
+ if (argument === '--project') {
7206
+ const filter = argv[index + 1]
7207
+ if (filter === undefined) {
7208
+ readable = false
7209
+ continue
7210
+ }
7211
+ filters.push(filter)
7212
+ index += 1
7213
+ continue
7214
+ }
7215
+ if (argument?.startsWith('--project=') === true) {
7216
+ filters.push(argument.slice('--project='.length))
7217
+ }
7218
+ }
7219
+ return readable && filters.length > 0 && filters.every((filter) => gated.includes(filter))
7220
+ ? { passWithNoTests: true, projects }
7221
+ : { projects }
7222
+ }
7223
+
7224
+ ${EXPORT_KEYWORD} ${CONST_KEYWORD} ENVIRONMENT_CSS = Object.freeze({
7030
7225
  transformer: 'lightningcss',
7031
7226
  lightningcss: {
7032
7227
  visitor: () => {
@@ -7121,11 +7316,10 @@ ${EXPORT_KEYWORD} const srcBrowser = (config?: UserConfig): UserConfig =>
7121
7316
  },
7122
7317
  test: {
7123
7318
  name: { label: 'src:browser', color: 'yellow' },
7124
- include: hasChromium ? ['tests/src/browser/**/*.test.ts'] : [],
7125
- passWithNoTests: !hasChromium,
7319
+ include: ['tests/src/browser/**/*.test.ts'],
7126
7320
  setupFiles: ['./tests/setup.ts', './tests/setupBrowser.ts'],
7127
7321
  browser: {
7128
- enabled: hasChromium,
7322
+ enabled: true,
7129
7323
  provider: playwright(),
7130
7324
  instances: [{ browser: 'chromium', headless: true }],
7131
7325
  },
@@ -7154,9 +7348,15 @@ ${EXPORT_KEYWORD} const guides = (config?: UserConfig): UserConfig =>
7154
7348
 
7155
7349
  export default defineConfig({
7156
7350
  resolve,
7157
- test: {
7158
- projects: [srcBrowser, policy, guides],
7159
- },
7351
+ test: gateBrowserProjects(
7352
+ [
7353
+ { project: srcBrowser, browser: 'src:browser' },
7354
+ { project: policy },
7355
+ { project: guides },
7356
+ ],
7357
+ hasChromium,
7358
+ process.argv,
7359
+ ),
7160
7360
  })
7161
7361
  `;
7162
7362
  return `${header}
@@ -7276,12 +7476,11 @@ ${EXPORT_KEYWORD} const srcBrowser = (config?: UserConfig): UserConfig =>
7276
7476
  },
7277
7477
  test: {
7278
7478
  name: { label: 'src:browser', color: 'yellow' },
7279
- include: hasChromium ? ['tests/src/browser/**/*.test.ts'] : [],
7280
- passWithNoTests: !hasChromium,
7479
+ include: ['tests/src/browser/**/*.test.ts'],
7281
7480
  exclude: ['tests/src/core/**/*.test.ts'],
7282
7481
  setupFiles: ['./tests/setup.ts', './tests/setupBrowser.ts'],
7283
7482
  browser: {
7284
- enabled: hasChromium,
7483
+ enabled: true,
7285
7484
  provider: playwright(),
7286
7485
  instances: [{ browser: 'chromium', headless: true }],
7287
7486
  },
@@ -7379,14 +7578,15 @@ ${EXPORT_KEYWORD} const integration = (config?: UserConfig): UserConfig =>
7379
7578
  )
7380
7579
  ` : "";
7381
7580
  const blocks = nonCore.map((environment) => environment === "browser" ? browserBlock : serverBlock).join("") + binBlock;
7382
- const projectNames = [
7383
- ...hasCore ? ["srcCore"] : [],
7384
- ...nonCore.map((environment) => `src${pascalCase(environment)}`),
7385
- "policy",
7386
- "guides",
7387
- ...engine ? ["srcBin"] : [],
7388
- ...engine ? ["integration"] : []
7389
- ];
7581
+ const registrations = [];
7582
+ if (hasCore) registrations.push({ project: "srcCore" });
7583
+ for (const environment of nonCore) registrations.push(environment === "browser" ? {
7584
+ project: "srcBrowser",
7585
+ browser: SRC_MATRIX.browser.project
7586
+ } : { project: "srcServer" });
7587
+ registrations.push({ project: "policy" }, { project: "guides" });
7588
+ if (engine) registrations.push({ project: "srcBin" }, { project: "integration" });
7589
+ const renderedTest = renderViteTest(registrations, machinery.browser);
7390
7590
  return `${header}
7391
7591
  ${EXPORT_KEYWORD} const srcCore = (config?: UserConfig): UserConfig =>
7392
7592
  mergeConfig(
@@ -7426,9 +7626,7 @@ ${EXPORT_KEYWORD} const guides = (config?: UserConfig): UserConfig =>
7426
7626
  ${blocks}
7427
7627
  export default defineConfig({
7428
7628
  resolve,
7429
- test: {
7430
- projects: [${projectNames.join(", ")}],
7431
- },
7629
+ ${renderedTest}
7432
7630
  })
7433
7631
  `;
7434
7632
  }
@@ -7450,10 +7648,10 @@ function applicationViteConfig(src, app, engine = false) {
7450
7648
  const hasSourceCore = src.includes("core");
7451
7649
  const machinery = viteMachinery(src, app, engine);
7452
7650
  const header = viteHeader(machinery);
7453
- const projects = [];
7651
+ const registrations = [];
7454
7652
  const blocks = [];
7455
7653
  if (src.includes("core")) {
7456
- projects.push("srcCore");
7654
+ registrations.push({ project: "srcCore" });
7457
7655
  blocks.push(`
7458
7656
  ${EXPORT_KEYWORD} const srcCore = (config?: UserConfig): UserConfig =>
7459
7657
  mergeConfig(
@@ -7475,7 +7673,10 @@ ${EXPORT_KEYWORD} const srcCore = (config?: UserConfig): UserConfig =>
7475
7673
  `);
7476
7674
  }
7477
7675
  if (src.includes("browser")) {
7478
- projects.push("srcBrowser");
7676
+ registrations.push({
7677
+ project: "srcBrowser",
7678
+ browser: SRC_MATRIX.browser.project
7679
+ });
7479
7680
  const coreOutput = hasSourceCore ? `
7480
7681
  output: { paths: { '@src/core': '../core/index.js' } },` : "";
7481
7682
  const coreExternal = hasSourceCore ? `id === '@src/core' || ` : "";
@@ -7504,11 +7705,10 @@ ${EXPORT_KEYWORD} const srcBrowser = (config?: UserConfig): UserConfig =>
7504
7705
  },
7505
7706
  test: {
7506
7707
  name: { label: 'src:browser', color: 'yellow' },
7507
- include: hasChromium ? ['tests/src/browser/**/*.test.ts'] : [],
7508
- passWithNoTests: !hasChromium,
7708
+ include: ['tests/src/browser/**/*.test.ts'],
7509
7709
  ${hasSourceCore ? "exclude: ['tests/src/core/**/*.test.ts'],\n " : ""}setupFiles: ['./tests/setup.ts', './tests/setupBrowser.ts'],
7510
7710
  browser: {
7511
- enabled: hasChromium,
7711
+ enabled: true,
7512
7712
  provider: playwright(),
7513
7713
  instances: [{ browser: 'chromium', headless: true }],
7514
7714
  },
@@ -7520,7 +7720,7 @@ ${EXPORT_KEYWORD} const srcBrowser = (config?: UserConfig): UserConfig =>
7520
7720
  `);
7521
7721
  }
7522
7722
  if (src.includes("server")) {
7523
- projects.push("srcServer");
7723
+ registrations.push({ project: "srcServer" });
7524
7724
  const coreOutput = hasSourceCore ? `
7525
7725
  output: [
7526
7726
  {
@@ -7571,7 +7771,7 @@ ${EXPORT_KEYWORD} const srcServer = (config?: UserConfig): UserConfig =>
7571
7771
  `);
7572
7772
  }
7573
7773
  if (app.includes("core")) {
7574
- projects.push("appCore");
7774
+ registrations.push({ project: "appCore" });
7575
7775
  blocks.push(`
7576
7776
  ${EXPORT_KEYWORD} const appCore = (config?: UserConfig): UserConfig =>
7577
7777
  mergeConfig(
@@ -7592,7 +7792,10 @@ ${EXPORT_KEYWORD} const appCore = (config?: UserConfig): UserConfig =>
7592
7792
  `);
7593
7793
  }
7594
7794
  if (app.includes("browser")) {
7595
- projects.push("appBrowser()");
7795
+ registrations.push({
7796
+ project: "appBrowser",
7797
+ browser: APP_MATRIX.browser.project
7798
+ });
7596
7799
  blocks.push(`
7597
7800
  ${EXPORT_KEYWORD} function appBrowser(...config: readonly never[]): UserConfig {
7598
7801
  if (config.length > 0) {
@@ -7633,11 +7836,10 @@ ${EXPORT_KEYWORD} function appBrowser(...config: readonly never[]): UserConfig {
7633
7836
  name: { label: '${APP_MATRIX.browser.project}', color: 'blue' },
7634
7837
  root: resolveWorkspacePath('.'),
7635
7838
  dir: resolveWorkspacePath('.'),
7636
- include: hasChromium ? ['tests/app/browser/**/*.test.ts'] : [],
7637
- passWithNoTests: !hasChromium,
7839
+ include: ['tests/app/browser/**/*.test.ts'],
7638
7840
  setupFiles: ['./tests/setup.ts', './tests/setupBrowser.ts'],
7639
7841
  browser: {
7640
- enabled: hasChromium,
7842
+ enabled: true,
7641
7843
  provider: playwright(),
7642
7844
  instances: [{ browser: 'chromium', headless: true }],
7643
7845
  },
@@ -7648,7 +7850,7 @@ ${EXPORT_KEYWORD} function appBrowser(...config: readonly never[]): UserConfig {
7648
7850
  `);
7649
7851
  }
7650
7852
  if (app.includes("server")) {
7651
- projects.push("appServer");
7853
+ registrations.push({ project: "appServer" });
7652
7854
  blocks.push(`
7653
7855
  ${EXPORT_KEYWORD} const appServer = (config?: UserConfig): UserConfig =>
7654
7856
  mergeConfig(
@@ -7682,7 +7884,7 @@ ${EXPORT_KEYWORD} const appServer = (config?: UserConfig): UserConfig =>
7682
7884
  `);
7683
7885
  }
7684
7886
  if (engine) {
7685
- projects.push("srcBin", "integration");
7887
+ registrations.push({ project: "srcBin" }, { project: "integration" });
7686
7888
  blocks.push(`
7687
7889
  ${EXPORT_KEYWORD} const srcBin = (config?: UserConfig): UserConfig =>
7688
7890
  mergeConfig(
@@ -7726,6 +7928,8 @@ ${EXPORT_KEYWORD} const integration = (config?: UserConfig): UserConfig =>
7726
7928
  )
7727
7929
  `);
7728
7930
  }
7931
+ registrations.push({ project: "policy" }, { project: "guides" });
7932
+ const renderedTest = renderViteTest(registrations, machinery.browser);
7729
7933
  return `${header}
7730
7934
  ${policyViteProject()}
7731
7935
  ${EXPORT_KEYWORD} const guides = (config?: UserConfig): UserConfig =>
@@ -7746,18 +7950,12 @@ ${EXPORT_KEYWORD} const guides = (config?: UserConfig): UserConfig =>
7746
7950
  ${blocks.join("")}
7747
7951
  export default defineConfig({
7748
7952
  resolve,
7749
- test: {
7750
- projects: [${[
7751
- ...projects,
7752
- "policy",
7753
- "guides"
7754
- ].join(", ")}],
7755
- },
7953
+ ${renderedTest}
7756
7954
  })
7757
7955
  `;
7758
7956
  }
7759
7957
  /**
7760
- * `configs/src/tsconfig.core.json` — unchanged core shape.
7958
+ * `configs/src/tsconfig.core.json` — host-neutral core with web interop declarations.
7761
7959
  *
7762
7960
  * @returns The core environment `tsconfig` file content, newline-terminated.
7763
7961
  *
@@ -7770,7 +7968,7 @@ function coreTsconfig() {
7770
7968
  return formatJson({
7771
7969
  extends: "../../tsconfig.json",
7772
7970
  compilerOptions: {
7773
- lib: ["ESNext"],
7971
+ lib: ["ESNext", "WebWorker"],
7774
7972
  types: [],
7775
7973
  noEmit: false,
7776
7974
  declaration: true,
@@ -7925,7 +8123,7 @@ function appTsconfig(environment, hasCore) {
7925
8123
  "ESNext",
7926
8124
  "DOM",
7927
8125
  "DOM.Iterable"
7928
- ] : ["ESNext"],
8126
+ ] : environment === "core" ? ["ESNext", "WebWorker"] : ["ESNext"],
7929
8127
  types: environment === "browser" ? ["vite/client", "vue"] : environment === "server" ? ["node"] : []
7930
8128
  },
7931
8129
  include
@@ -8527,12 +8725,15 @@ function guideTests(spec, pascal) {
8527
8725
  }
8528
8726
  /**
8529
8727
  * Draft the `guides` group's artifacts — the package's own filled guide stub,
8530
- * the guides index, and any vendored dependency guide mirrors.
8728
+ * the guides index, and vendored dependency guide mirrors whose paths are
8729
+ * neither the package's own guide nor already carried by the selected host
8730
+ * set.
8531
8731
  *
8532
8732
  * @param spec - The `Blueprint` to derive guide artifacts from.
8533
8733
  * @param pascal - The package's PascalCase entity name.
8534
8734
  * @param members - The blueprint's derived `Member[]`.
8535
- * @returns The `guides` group's `Artifact[]`.
8735
+ * @returns The `guides` group's `Artifact[]`, with one contributor per guide
8736
+ * path.
8536
8737
  *
8537
8738
  * @example
8538
8739
  * ```ts
@@ -8585,14 +8786,16 @@ function guideArtifacts(spec, pascal, members) {
8585
8786
  ]]),
8586
8787
  directory: alignTable(["Directory", "Guide"], sourceDirectories.map((directory) => [directory, `[\`${spec.name}.md\`](src/${spec.name}.md)`]))
8587
8788
  })];
8789
+ const guidePath = `guides/src/${spec.name}.md`;
8588
8790
  for (const dep of spec.dependencies) {
8589
8791
  if (!vendoredGuides.includes(dep.name)) continue;
8590
- const short = dep.name.replace("@orkestrel/", "");
8792
+ const path = `guides/src/${dep.name.replace("@orkestrel/", "")}.md`;
8793
+ if (HOST_PATHS.includes(path) || path === guidePath) continue;
8591
8794
  artifacts.push({
8592
- path: `guides/src/${short}.md`,
8795
+ path,
8593
8796
  group: "guides",
8594
8797
  origin: "host",
8595
- source: `guides/src/${short}.md`
8798
+ source: path
8596
8799
  });
8597
8800
  }
8598
8801
  return artifacts;
@@ -8628,7 +8831,7 @@ function applyOverrides(artifacts, overrides) {
8628
8831
  /**
8629
8832
  * The full pure compilation: draft a blueprint's artifacts — the manifest and
8630
8833
  * exports combination rules over the per-environment `SRC_MATRIX` rows, plus
8631
- * `HOST_PATHS` and `overrides` — then pin.
8834
+ * the `selectHostPaths` selection of `HOST_PATHS` and `overrides` — then pin.
8632
8835
  *
8633
8836
  * @param blueprint - The `Blueprint` to compile.
8634
8837
  * @param groups - An optional `Group[]` selection (default: all groups).
@@ -8672,7 +8875,7 @@ npm install @orkestrel/${blueprint.name}
8672
8875
  origin: "computed",
8673
8876
  content: ciWorkflow(blueprint)
8674
8877
  });
8675
- for (const path of HOST_PATHS) {
8878
+ for (const path of selectHostPaths(HOST_PATHS, blueprint.name)) {
8676
8879
  const group = hostGroup(path);
8677
8880
  if (!selected.includes(group)) continue;
8678
8881
  artifacts.push({
@@ -8795,7 +8998,10 @@ var Compiler = class Compiler {
8795
8998
  this.#emitter.emit("audit", result);
8796
8999
  return result;
8797
9000
  }
8798
- const result = diffPlan(scaffolding.plan, current);
9001
+ const result = {
9002
+ ...diffPlan(scaffolding.plan, current),
9003
+ questions: scaffolding.questions
9004
+ };
8799
9005
  this.#emitter.emit("audit", result);
8800
9006
  return result;
8801
9007
  }
@@ -8859,7 +9065,16 @@ var Compiler = class Compiler {
8859
9065
  const validation = validatePlan(draft);
8860
9066
  const dependencyQuestions = this.#dependencyQuestions(blueprint);
8861
9067
  const blocking = [...validation.questions];
8862
- const questions = [...blocking, ...dependencyQuestions];
9068
+ const warningQuestions = validation.warnings.map((text) => ({
9069
+ field: "overrides",
9070
+ text,
9071
+ blocking: false
9072
+ }));
9073
+ const questions = [
9074
+ ...blocking,
9075
+ ...warningQuestions,
9076
+ ...dependencyQuestions
9077
+ ];
8863
9078
  stages.push({
8864
9079
  stage: "gate",
8865
9080
  input: draft,
@@ -8952,9 +9167,11 @@ var Compiler = class Compiler {
8952
9167
  }
8953
9168
  #pointerArtifacts(blueprint) {
8954
9169
  const artifacts = [];
9170
+ const guidePath = `guides/src/${blueprint.name}.md`;
8955
9171
  for (const item of blueprint.dependencies) {
8956
9172
  if (Compiler.#vendored.includes(item.name)) continue;
8957
9173
  const path = `guides/src/${item.name.replace("@orkestrel/", "")}.md`;
9174
+ if (path === guidePath || HOST_PATHS.includes(path)) continue;
8958
9175
  artifacts.push({
8959
9176
  path,
8960
9177
  group: "guides",
@@ -9307,6 +9524,7 @@ exports.isScaffoldError = isScaffoldError;
9307
9524
  exports.isSyncReport = isSyncReport;
9308
9525
  exports.isWorkspaceName = isWorkspaceName;
9309
9526
  exports.manifestToDependencies = manifestToDependencies;
9527
+ exports.manifestToName = manifestToName;
9310
9528
  exports.member = member;
9311
9529
  exports.memberShape = memberShape;
9312
9530
  exports.override = override;
@@ -9333,8 +9551,10 @@ exports.rangeToFreshness = rangeToFreshness;
9333
9551
  exports.renderArray = renderArray;
9334
9552
  exports.renderObject = renderObject;
9335
9553
  exports.renderValue = renderValue;
9554
+ exports.renderViteTest = renderViteTest;
9336
9555
  exports.rootTsconfig = rootTsconfig;
9337
9556
  exports.rootViteConfig = rootViteConfig;
9557
+ exports.selectHostPaths = selectHostPaths;
9338
9558
  exports.serializeTypeScriptString = serializeTypeScriptString;
9339
9559
  exports.singleSrcViteConfig = singleSrcViteConfig;
9340
9560
  exports.snapshotOf = snapshotOf;