@orkestrel/scaffold 0.0.100 → 0.0.102

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.
Files changed (60) hide show
  1. package/dist/agents/skills/orkestrel-dispatch/scripts/brief.js +6 -14
  2. package/dist/agents/skills/orkestrel-dispatch/scripts/cite.js +41 -28
  3. package/dist/agents/skills/orkestrel-dispatch/scripts/helpers.js +5 -12
  4. package/dist/agents/skills/orkestrel-dispatch/scripts/launch.js +25 -13
  5. package/dist/agents/skills/orkestrel-dispatch/scripts/login.js +18 -25
  6. package/dist/agents/skills/orkestrel-dispatch/scripts/result.js +11 -9
  7. package/dist/agents/skills/orkestrel-harden/scripts/discovery.js +233 -232
  8. package/dist/agents/skills/orkestrel-publish/scripts/compare.js +95 -89
  9. package/dist/agents/skills/orkestrel-publish/scripts/pins.js +5 -7
  10. package/dist/agents/skills/orkestrel-publish/scripts/swap.js +1273 -0
  11. package/dist/agents/skills/orkestrel-publish/scripts/wave.js +99 -89
  12. package/dist/agents/skills/orkestrel-publish/scripts/window.js +54 -52
  13. package/dist/agents/skills/orkestrel-scout/scripts/map.js +160 -161
  14. package/dist/bin/main.js +483 -444
  15. package/dist/bin/main.js.map +1 -1
  16. package/dist/host/AGENTS.md +1 -0
  17. package/dist/host/agents/skills/orkestrel-dispatch/SKILL.md +10 -10
  18. package/dist/host/agents/skills/orkestrel-dispatch/scripts/brief.ts +8 -11
  19. package/dist/host/agents/skills/orkestrel-dispatch/scripts/cite.ts +58 -28
  20. package/dist/host/agents/skills/orkestrel-dispatch/scripts/helpers.ts +5 -10
  21. package/dist/host/agents/skills/orkestrel-dispatch/scripts/launch.ts +31 -12
  22. package/dist/host/agents/skills/orkestrel-dispatch/scripts/login.ts +28 -23
  23. package/dist/host/agents/skills/orkestrel-dispatch/scripts/result.ts +12 -10
  24. package/dist/host/agents/skills/orkestrel-harden/scripts/discovery.ts +318 -237
  25. package/dist/host/agents/skills/orkestrel-journey/references/statechart.md +2 -5
  26. package/dist/host/agents/skills/orkestrel-publish/SKILL.md +7 -6
  27. package/dist/host/agents/skills/orkestrel-publish/references/release.md +12 -10
  28. package/dist/host/agents/skills/orkestrel-publish/references/wave.md +12 -5
  29. package/dist/host/agents/skills/orkestrel-publish/scripts/compare.ts +124 -82
  30. package/dist/host/agents/skills/orkestrel-publish/scripts/pins.ts +5 -7
  31. package/dist/host/agents/skills/orkestrel-publish/scripts/swap.ts +1655 -0
  32. package/dist/host/agents/skills/orkestrel-publish/scripts/wave.ts +128 -97
  33. package/dist/host/agents/skills/orkestrel-publish/scripts/window.ts +70 -55
  34. package/dist/host/agents/skills/orkestrel-scout/scripts/map.ts +215 -155
  35. package/dist/host/claude/rules/architecture.md +2 -2
  36. package/dist/host/claude/rules/tests.md +8 -2
  37. package/dist/host/claude/rules/typescript.md +130 -0
  38. package/dist/host/claude/rules/workspace.md +36 -12
  39. package/dist/host/configs/helpers.ts +214 -133
  40. package/dist/host/configs/policy.ts +110 -105
  41. package/dist/host/dotfiles/gitignore +1 -0
  42. package/dist/host/dotfiles/oxlintrc.json +10 -0
  43. package/dist/host/guides/scaffold.md +244 -170
  44. package/dist/host/manifest.json +37 -31
  45. package/dist/host/tests/config.test.ts +1305 -619
  46. package/dist/host/tests/policy.test.ts +19 -0
  47. package/dist/host/tests/setupPolicy.ts +1710 -995
  48. package/dist/src/core/index.cjs +1595 -1686
  49. package/dist/src/core/index.cjs.map +1 -1
  50. package/dist/src/core/index.d.cts +202 -18
  51. package/dist/src/core/index.d.ts +202 -18
  52. package/dist/src/core/index.js +1590 -1687
  53. package/dist/src/core/index.js.map +1 -1
  54. package/dist/src/server/index.cjs +448 -392
  55. package/dist/src/server/index.cjs.map +1 -1
  56. package/dist/src/server/index.d.cts +1182 -1052
  57. package/dist/src/server/index.d.ts +1182 -1052
  58. package/dist/src/server/index.js +447 -395
  59. package/dist/src/server/index.js.map +1 -1
  60. package/package.json +2 -2
@@ -3,7 +3,7 @@ import { fillTemplate } from "@orkestrel/template";
3
3
  import { Emitter } from "@orkestrel/emitter";
4
4
  var package_default = {
5
5
  name: "@orkestrel/scaffold",
6
- version: "0.0.100",
6
+ version: "0.0.102",
7
7
  description: "Scaffold workspaces with commands: new, audit, repair, catalog, and overwrite.",
8
8
  keywords: [
9
9
  "audit",
@@ -107,7 +107,7 @@ var package_default = {
107
107
  "@orkestrel/guide": "^0.0.24",
108
108
  "@orkestrel/html": "^0.0.12",
109
109
  "@orkestrel/probe": "^0.0.23",
110
- "@orkestrel/test": "^0.0.25",
110
+ "@orkestrel/test": "^0.0.26",
111
111
  "@types/node": "^26.6.4",
112
112
  "@vitest/browser-playwright": "^4.1.11",
113
113
  "oxfmt": "^0.72.0",
@@ -502,6 +502,16 @@ var SERVICE_SETUP_PATH = "tests/setupService.ts";
502
502
  * one proof.
503
503
  */
504
504
  var SERVICE_TEST_INCLUDE = "tests/service/**/*.test.ts";
505
+ /**
506
+ * Names the include the study project covers, where an exact-case `*.test.ts` file beneath
507
+ * `tests/studies`, outside any `node_modules` or `.git` directory, makes a workspace `studies`.
508
+ *
509
+ * @remarks
510
+ * The selector reads the include's population less Vitest's default exclude: an exact-case file
511
+ * beneath `tests/studies`, nested or direct, whose name ends in `.test.ts`, outside any
512
+ * `node_modules` or `.git` directory, which Vitest's default exclude never collects.
513
+ */
514
+ var STUDIES_TEST_INCLUDE = "tests/studies/**/*.test.ts";
505
515
  /** Names the Vite wrapper whose presence makes a workspace `showcase`. */
506
516
  var SHOWCASE_CONFIG_PATH = "configs/app/vite.showcase.config.ts";
507
517
  /** Names the Vite wrapper whose presence makes a workspace `journey`. */
@@ -804,11 +814,13 @@ export const peers: readonly string[] =
804
814
  peerDependencies === undefined ? [] : Object.keys(peerDependencies)
805
815
 
806
816
  const resolve = {
807
- alias: Object.entries(tsconfig.compilerOptions.paths).reduce((aliases, [key, values]) => {
808
- const [path] = values
809
- if (path === undefined) throw new Error('tsconfig path alias ' + key + ' has no target')
810
- return Object.assign(aliases, { [key]: resolveWorkspacePath(path) })
811
- }, {}),
817
+ alias: Object.fromEntries(
818
+ Object.entries(tsconfig.compilerOptions.paths).map(([key, values]) => {
819
+ const [path] = values
820
+ if (path === undefined) throw new Error('tsconfig path alias ' + key + ' has no target')
821
+ return [key, resolveWorkspacePath(path)]
822
+ }),
823
+ ),
812
824
  }
813
825
 
814
826
  // Merges a caller's override onto the configuration a factory declares, so a
@@ -837,53 +849,67 @@ const resolve = {
837
849
  // merges it, so an override's arrays elsewhere concatenate with the base's rather than
838
850
  // replacing them.
839
851
  export function mergeOverride(base: UserConfig, override?: UserConfig): UserConfig {
840
- if (override !== undefined && 'command' in override && 'mode' in override) {
841
- if (typeof override.mode !== 'string') {
842
- throw new Error('The project invocation carries no string mode')
843
- }
844
- override = { mode: override.mode }
845
- }
846
- const merged: UserConfig = override === undefined ? { ...base } : mergeConfig(base, override)
852
+ if (
853
+ override !== undefined &&
854
+ 'command' in override &&
855
+ 'mode' in override &&
856
+ typeof override.mode !== 'string'
857
+ ) {
858
+ throw new Error('The project invocation carries no string mode')
859
+ }
860
+ const effective: UserConfig | undefined =
861
+ override !== undefined && 'command' in override && typeof override.mode === 'string'
862
+ ? { mode: override.mode }
863
+ : override
864
+ const merged: UserConfig = effective === undefined ? { ...base } : mergeConfig(base, effective)
847
865
  const name = merged.test?.name
848
866
  const label = typeof name === 'string' ? name : name?.label
849
867
  const browser = merged.test?.browser
850
- if (label !== undefined && browser?.instances !== undefined) {
851
- merged.test = {
852
- ...merged.test,
853
- browser: {
854
- ...browser,
855
- instances: browser.instances.map((instance) => ({
856
- ...instance,
857
- name: instance.name ?? \`\${label} (\${instance.browser})\`,
858
- })),
859
- },
860
- }
861
- }
862
- if (merged.plugins === undefined || override === undefined) return merged
863
- const candidates = override.plugins ?? []
864
- const taken = new Set<number>()
865
- const selected: PluginOption[] = []
866
- for (const plugin of base.plugins ?? []) {
867
- if (!isNamedPlugin(plugin)) {
868
- selected.push(plugin)
869
- continue
870
- }
871
- const index = candidates.findIndex(
872
- (candidate, position) =>
873
- !taken.has(position) && isNamedPlugin(candidate) && candidate.name === plugin.name,
874
- )
875
- const replacement = candidates[index]
876
- if (replacement === undefined) {
877
- selected.push(plugin)
878
- } else {
879
- selected.push(replacement)
880
- taken.add(index)
881
- }
882
- }
883
- for (const [index, plugin] of candidates.entries()) {
884
- if (!taken.has(index)) selected.push(plugin)
868
+ const named: UserConfig =
869
+ label !== undefined && browser?.instances !== undefined
870
+ ? {
871
+ ...merged,
872
+ test: {
873
+ ...merged.test,
874
+ browser: {
875
+ ...browser,
876
+ instances: browser.instances.map((instance) => ({
877
+ ...instance,
878
+ name: instance.name ?? \`\${label} (\${instance.browser})\`,
879
+ })),
880
+ },
881
+ },
882
+ }
883
+ : merged
884
+ if (named.plugins === undefined || effective === undefined) return named
885
+ const candidates = [...(effective.plugins ?? [])]
886
+ const bases = [...(base.plugins ?? [])]
887
+ // A base plugin takes the first candidate of its own name that no earlier base plugin took, and
888
+ // candidates of other names never compete for it, so its pick is the candidate whose rank among
889
+ // its name equals the number of earlier base plugins sharing that name.
890
+ const chosen = bases.map((plugin, position) =>
891
+ isNamedPlugin(plugin)
892
+ ? candidates
893
+ .flatMap((candidate, index) =>
894
+ isNamedPlugin(candidate) && candidate.name === plugin.name ? [index] : [],
895
+ )
896
+ .at(
897
+ bases
898
+ .slice(0, position)
899
+ .filter((earlier) => isNamedPlugin(earlier) && earlier.name === plugin.name).length,
900
+ )
901
+ : undefined,
902
+ )
903
+ return {
904
+ ...named,
905
+ plugins: [
906
+ ...bases.map((plugin, position) => {
907
+ const index = chosen[position]
908
+ return index === undefined ? plugin : (candidates[index] ?? plugin)
909
+ }),
910
+ ...candidates.filter((_, index) => !chosen.includes(index)),
911
+ ],
885
912
  }
886
- return { ...merged, plugins: selected }
887
913
  }
888
914
 
889
915
  function isNamedPlugin(plugin: PluginOption): plugin is { name: string } {
@@ -919,6 +945,7 @@ function isNamedPlugin(plugin: PluginOption): plugin is { name: string } {
919
945
  browser: {
920
946
  enabled: true,
921
947
  provider: playwright(browserOptions),
948
+ commands,
922
949
  instances: [{ browser: 'chromium', headless: true }],
923
950
  },
924
951
  fileParallelism: false,
@@ -979,6 +1006,7 @@ export function srcVue(override?: UserConfig): UserConfig {
979
1006
  browser: {
980
1007
  enabled: true,
981
1008
  provider: playwright(browserOptions),
1009
+ commands,
982
1010
  instances: [{ browser: 'chromium', headless: true }],
983
1011
  },
984
1012
  fileParallelism: false,
@@ -1022,6 +1050,7 @@ export function srcBrowser(override?: UserConfig): UserConfig {
1022
1050
  browser: {
1023
1051
  enabled: true,
1024
1052
  provider: playwright(browserOptions),
1053
+ commands,
1025
1054
  instances: [{ browser: 'chromium', headless: true }],
1026
1055
  },
1027
1056
  fileParallelism: false,
@@ -1157,6 +1186,7 @@ export function srcBin(override?: UserConfig): UserConfig {
1157
1186
  browser: {
1158
1187
  enabled: true,
1159
1188
  provider: playwright(browserOptions),
1189
+ commands,
1160
1190
  instances: [{ browser: 'chromium', headless: true }],
1161
1191
  },
1162
1192
  fileParallelism: false,
@@ -1390,6 +1420,7 @@ export function appServer(override?: UserConfig): UserConfig {
1390
1420
  {{global}} browser: {
1391
1421
  enabled: true,
1392
1422
  provider: playwright(browserOptions),
1423
+ commands,
1393
1424
  instances: [{ browser: 'chromium', headless: true }],
1394
1425
  },
1395
1426
  },
@@ -1469,6 +1500,26 @@ export function service(override?: UserConfig): UserConfig {
1469
1500
  }
1470
1501
  return mergeOverride(project, override)
1471
1502
  }
1503
+ `,
1504
+ studies: `// Authored study proofs run installed packages through bounded subprocesses and protocol fixtures,
1505
+ // so this project is isolated in its own gate script, test:studies.
1506
+ export function studies(override?: UserConfig): UserConfig {
1507
+ const project: UserConfig = {
1508
+ resolve,
1509
+ test: {
1510
+ name: { label: 'studies', color: 'red' },
1511
+ include: ['${STUDIES_TEST_INCLUDE}'],
1512
+ setupFiles: ['./tests/setup.ts'],
1513
+ environment: 'node',
1514
+ browser: { enabled: false },
1515
+ testTimeout: 120_000,
1516
+ hookTimeout: 120_000,
1517
+ fileParallelism: false,
1518
+ sequence: { groupOrder: 1 },
1519
+ },
1520
+ }
1521
+ return mergeOverride(project, override)
1522
+ }
1472
1523
  `,
1473
1524
  distribution: `export function distribution(override?: UserConfig): UserConfig {
1474
1525
  const project: UserConfig = {
@@ -2058,34 +2109,33 @@ export function resolvePinnedBrowser(): string | undefined {
2058
2109
  */
2059
2110
  export function resolveManagedBrowser(pinned: string): string | undefined {
2060
2111
  if (isBrowserExecutable(pinned)) return pinned
2061
- let revisionRoot = dirname(pinned)
2062
- for (;;) {
2063
- if (CHROMIUM_ENTRY_PATTERN.test(basename(revisionRoot))) break
2064
- const parent = dirname(revisionRoot)
2065
- if (parent === revisionRoot) return undefined
2066
- revisionRoot = parent
2067
- }
2112
+ // One \`dirname\` per path segment reaches the root from any path, and the nearest
2113
+ // ancestor named \`chromium-<revision>\` is the revision directory this build sits in.
2114
+ const directory = dirname(pinned)
2115
+ const ancestors = directory
2116
+ .split(/[\\\\/]/u)
2117
+ .reduce<string[]>((chain) => [...chain, dirname(chain.at(-1) ?? directory)], [directory])
2118
+ const revisionRoot = ancestors.find((ancestor) => CHROMIUM_ENTRY_PATTERN.test(basename(ancestor)))
2119
+ if (revisionRoot === undefined) return undefined
2068
2120
  const browsersRoot = dirname(revisionRoot)
2069
2121
  for (const alias of ['chromium', 'chromium.exe']) {
2070
2122
  const candidate = resolvePath(browsersRoot, alias)
2071
2123
  if (isBrowserExecutable(candidate)) return candidate
2072
2124
  }
2073
- let entries: readonly string[]
2074
2125
  try {
2075
- entries = readdirSync(browsersRoot)
2126
+ const revisions = readdirSync(browsersRoot)
2127
+ .filter((entry) => CHROMIUM_ENTRY_PATTERN.test(entry))
2128
+ .sort(compareRevisions)
2129
+ for (const revision of revisions) {
2130
+ for (const layout of CHROMIUM_LAYOUTS) {
2131
+ const candidate = resolvePath(browsersRoot, revision, layout)
2132
+ if (isBrowserExecutable(candidate)) return candidate
2133
+ }
2134
+ }
2135
+ return undefined
2076
2136
  } catch {
2077
2137
  return undefined
2078
2138
  }
2079
- const revisions = entries
2080
- .filter((entry) => CHROMIUM_ENTRY_PATTERN.test(entry))
2081
- .sort(compareRevisions)
2082
- for (const revision of revisions) {
2083
- for (const layout of CHROMIUM_LAYOUTS) {
2084
- const candidate = resolvePath(browsersRoot, revision, layout)
2085
- if (isBrowserExecutable(candidate)) return candidate
2086
- }
2087
- }
2088
- return undefined
2089
2139
  }
2090
2140
 
2091
2141
  /**
@@ -2103,16 +2153,14 @@ export function resolveManagedBrowser(pinned: string): string | undefined {
2103
2153
  export function resolveBundledBrowser(platform: NodeJS.Platform, root: string): string | undefined {
2104
2154
  if (platform !== 'linux') return undefined
2105
2155
  for (const layout of BUNDLED_CHROMIUM_LAYOUTS) {
2106
- let matches: readonly string[]
2107
2156
  try {
2108
- matches = globSync(layout, { cwd: root })
2157
+ for (const match of globSync(layout, { cwd: root }).sort(compareRevisions)) {
2158
+ const candidate = resolvePath(root, match)
2159
+ if (isBrowserExecutable(candidate)) return candidate
2160
+ }
2109
2161
  } catch {
2110
2162
  return undefined
2111
2163
  }
2112
- for (const match of [...matches].sort(compareRevisions)) {
2113
- const candidate = resolvePath(root, match)
2114
- if (isBrowserExecutable(candidate)) return candidate
2115
- }
2116
2164
  }
2117
2165
  return undefined
2118
2166
  }
@@ -2134,21 +2182,22 @@ export function resolveSystemBrowser(
2134
2182
  environment: NodeJS.ProcessEnv,
2135
2183
  ): string | undefined {
2136
2184
  if (platform !== 'linux' && platform !== 'darwin' && platform !== 'win32') return undefined
2137
- const roots = new Set<string>()
2138
- if (platform === 'win32') {
2139
- for (const root of [
2140
- environment.LOCALAPPDATA,
2141
- environment.PROGRAMFILES,
2142
- environment['PROGRAMFILES(X86)'],
2143
- ]) {
2144
- if (root !== undefined && root.length > 0) roots.add(root)
2145
- }
2146
- const homeDrive = environment.HOMEDRIVE
2147
- if (homeDrive !== undefined && homeDrive.length > 0) {
2148
- roots.add(join(homeDrive, 'Program Files'))
2149
- roots.add(join(homeDrive, 'Program Files (x86)'))
2150
- }
2151
- }
2185
+ const roots = new Set(
2186
+ platform === 'win32'
2187
+ ? [
2188
+ ...[
2189
+ environment.LOCALAPPDATA,
2190
+ environment.PROGRAMFILES,
2191
+ environment['PROGRAMFILES(X86)'],
2192
+ ].flatMap((root) => (root !== undefined && root.length > 0 ? [root] : [])),
2193
+ ...[environment.HOMEDRIVE].flatMap((homeDrive) =>
2194
+ homeDrive !== undefined && homeDrive.length > 0
2195
+ ? [join(homeDrive, 'Program Files'), join(homeDrive, 'Program Files (x86)')]
2196
+ : [],
2197
+ ),
2198
+ ]
2199
+ : [],
2200
+ )
2152
2201
  for (const browser of SYSTEM_BROWSER_CHANNELS) {
2153
2202
  if (platform === 'win32') {
2154
2203
  for (const root of roots) {
@@ -2237,6 +2286,10 @@ export function resolveBrowser(
2237
2286
  launchOptions: { channel: resolveSystemBrowser(platform, environment) ?? fallback, ...launch },
2238
2287
  }
2239
2288
  }
2289
+ `,
2290
+ commands: `import type { BrowserConfigOptions } from 'vitest/node'
2291
+
2292
+ export const commands: Readonly<NonNullable<BrowserConfigOptions['commands']>> = Object.freeze({})
2240
2293
  `
2241
2294
  });
2242
2295
  /**
@@ -2324,10 +2377,10 @@ const CAPTURE = inject('capture')
2324
2377
 
2325
2378
  describe('{{application}} arrival journey', () => {
2326
2379
  it('proves the declared arrival families', async () => {
2327
- const families = new Set(['Journey', 'Refusal', 'Matrix', ...(CAPTURE ? ['Capture'] : [])])
2328
- const proven = new Set<string>()
2380
+ const families = ['Journey', 'Refusal', 'Matrix', ...(CAPTURE ? ['Capture'] : [])]
2381
+ const proven: string[] = []
2329
2382
  const states = ['{{application}}-arrival']
2330
- const placed = new Set<string>()
2383
+ const placed: string[] = []
2331
2384
  const portfolio = createPortfolio({
2332
2385
  states,
2333
2386
  variants: VARIANTS,
@@ -2344,30 +2397,30 @@ describe('{{application}} arrival journey', () => {
2344
2397
  })
2345
2398
  .element()
2346
2399
  expect(isRendered(heading)).toBe(true)
2347
- proven.add('Journey')
2400
+ proven.push('Journey')
2348
2401
  await portfolio.place('{{application}}-arrival')
2349
- placed.add('{{application}}-arrival')
2402
+ placed.push('{{application}}-arrival')
2350
2403
 
2351
2404
  for (const role of ACCESSIBLE_ROLES) expect(page.getByRole(role).elements()).toEqual([])
2352
2405
  expect(readRefusal('Continue')).toBe(
2353
2406
  'No interactive element has the accessible name "Continue"',
2354
2407
  )
2355
- proven.add('Refusal')
2408
+ proven.push('Refusal')
2356
2409
 
2357
- const measured = new Set<string>()
2410
+ const measured: string[] = []
2358
2411
  for (const variant of VARIANTS) {
2359
2412
  await page.viewport(variant.width, variant.height)
2360
2413
  expect(readStyle(heading, 'display')).toBe('block')
2361
2414
  expect(window.innerWidth).toBe(variant.width)
2362
- measured.add(variant.name)
2415
+ measured.push(variant.name)
2363
2416
  }
2364
- expect(measured).toEqual(new Set(VARIANTS.map(({ name }) => name)))
2365
- proven.add('Matrix')
2417
+ expect(measured).toEqual(VARIANTS.map(({ name }) => name))
2418
+ proven.push('Matrix')
2366
2419
 
2367
2420
  const files = expandCaptures(states, VARIANTS)
2368
2421
  expect(portfolio.files).toEqual(files)
2369
2422
  expect(new Set(files).size).toBe(states.length * VARIANTS.length)
2370
- expect(placed).toEqual(new Set(states))
2423
+ expect(placed).toEqual(states)
2371
2424
  const expected = CAPTURE
2372
2425
  ? expandCaptures(
2373
2426
  states,
@@ -2377,7 +2430,7 @@ describe('{{application}} arrival journey', () => {
2377
2430
  expect(portfolio.paths.map((path) => path.replaceAll('\\\\', '/').split('/').at(-1))).toEqual(
2378
2431
  expected,
2379
2432
  )
2380
- if (CAPTURE) proven.add('Capture')
2433
+ if (CAPTURE) proven.push('Capture')
2381
2434
  expect(proven).toEqual(families)
2382
2435
  } finally {
2383
2436
  {{cleanup}} const variant = VARIANTS.find(({ name }) => name === VARIANT)
@@ -2422,13 +2475,7 @@ describe('{{name}} entry', () => {
2422
2475
  })
2423
2476
  `,
2424
2477
  styles: Object.freeze({
2425
- module: `/** Carries a CSSOM rule and its enclosing layer. */
2426
- export interface SheetEntry {
2427
- readonly rule: CSSRule
2428
- readonly layer: string | undefined
2429
- }
2430
-
2431
- /** Describes an adopted stylesheet and its idempotent release operation. */
2478
+ module: `/** Describes an adopted stylesheet and its idempotent release operation. */
2432
2479
  export interface SheetAdoption {
2433
2480
  readonly sheet: CSSStyleSheet
2434
2481
  release(): void
@@ -2460,42 +2507,36 @@ export function adoptSheet(text: string): SheetAdoption {
2460
2507
 
2461
2508
  /**
2462
2509
  * Reads layer statements and blocks in declaration order, including repeated names.
2463
- * @param sheet - The parsed sheet.
2510
+ * @param source - The parsed sheet, or a grouping rule inside it.
2511
+ * @param layer - The qualified name of the layer that encloses \`source\`, when one does.
2464
2512
  * @returns Qualified layer names, with an empty name for an anonymous layer.
2465
2513
  * @example
2466
2514
  * \`\`\`ts
2467
2515
  * readLayerNames(new CSSStyleSheet()) // []
2468
2516
  * \`\`\`
2469
2517
  */
2470
- export function readLayerNames(sheet: CSSStyleSheet): readonly string[] {
2471
- const pending: SheetEntry[] = Array.from(sheet.cssRules, (rule) => ({
2472
- rule,
2473
- layer: undefined,
2474
- })).reverse()
2475
- const names: string[] = []
2476
- while (pending.length > 0) {
2477
- const entry = pending.pop()
2478
- if (entry === undefined) break
2479
- const { rule, layer } = entry
2480
- const declared =
2481
- rule instanceof CSSLayerStatementRule
2482
- ? rule.nameList
2483
- : rule instanceof CSSLayerBlockRule
2484
- ? [rule.name]
2485
- : []
2486
- for (const name of declared) names.push(layer === undefined ? name : layer + '.' + name)
2487
- if (!(rule instanceof CSSGroupingRule)) continue
2488
- const parent =
2489
- rule instanceof CSSLayerBlockRule
2490
- ? layer === undefined
2491
- ? rule.name
2492
- : layer + '.' + rule.name
2493
- : layer
2494
- for (const child of Array.from(rule.cssRules).reverse()) {
2495
- pending.push({ rule: child, layer: parent })
2496
- }
2497
- }
2498
- return names
2518
+ export function readLayerNames(
2519
+ source: CSSStyleSheet | CSSGroupingRule,
2520
+ layer?: string,
2521
+ ): readonly string[] {
2522
+ return Array.from(source.cssRules).flatMap((rule) => [
2523
+ ...(rule instanceof CSSLayerStatementRule
2524
+ ? rule.nameList
2525
+ : rule instanceof CSSLayerBlockRule
2526
+ ? [rule.name]
2527
+ : []
2528
+ ).map((name) => (layer === undefined ? name : layer + '.' + name)),
2529
+ ...(rule instanceof CSSGroupingRule
2530
+ ? readLayerNames(
2531
+ rule,
2532
+ rule instanceof CSSLayerBlockRule
2533
+ ? layer === undefined
2534
+ ? rule.name
2535
+ : layer + '.' + rule.name
2536
+ : layer,
2537
+ )
2538
+ : []),
2539
+ ])
2499
2540
  }
2500
2541
  `,
2501
2542
  proof: `import type { SheetAdoption } from './setupStyles.js'
@@ -2785,15 +2826,17 @@ function isList(value: unknown): value is readonly unknown[] {
2785
2826
  // thing: an array falls through the former and keeps the latter.
2786
2827
  function isPackageTarget(target: string): boolean {
2787
2828
  if (!target.startsWith('./')) return false
2788
- for (const segment of target.slice(2).split(/[\\\\/]/u)) {
2789
- let decoded = segment
2790
- try {
2791
- decoded = decodeURIComponent(segment)
2792
- } catch {}
2793
- const normalized = decoded.toLowerCase()
2794
- if (normalized === '.' || normalized === '..' || normalized === 'node_modules') return false
2795
- }
2796
- return true
2829
+ const refused = ['.', '..', 'node_modules']
2830
+ return target
2831
+ .slice(2)
2832
+ .split(/[\\\\/]/u)
2833
+ .every((segment) => {
2834
+ try {
2835
+ return !refused.includes(decodeURIComponent(segment).toLowerCase())
2836
+ } catch {
2837
+ return !refused.includes(segment.toLowerCase())
2838
+ }
2839
+ })
2797
2840
  }
2798
2841
 
2799
2842
  function readJson(path: string): unknown {
@@ -2818,11 +2861,11 @@ function readOutput(result: SpawnSyncReturns<string>): string {
2818
2861
  return \`\${result.stdout ?? ''}\${result.stderr ?? ''}\`.trim()
2819
2862
  }
2820
2863
 
2821
- function runNpm(args: readonly string[], cwd: string): SpawnSyncReturns<string> {
2864
+ function runNpm(args: readonly string[], cwd: string, scratch: string): SpawnSyncReturns<string> {
2822
2865
  return spawnSync(NPM, [...args], {
2823
2866
  cwd,
2824
2867
  encoding: 'utf8',
2825
- env: { ...process.env, npm_config_cache: join(readScratch(), 'cache') },
2868
+ env: { ...process.env, npm_config_cache: join(scratch, 'cache') },
2826
2869
  shell: SHELL,
2827
2870
  windowsHide: true,
2828
2871
  })
@@ -2877,11 +2920,14 @@ function matchesFile(path: string): boolean {
2877
2920
  // the containing condition or fallback list unresolved, so the walk continues.
2878
2921
  function targetToDeclaration(target: string, installed: string): string | undefined {
2879
2922
  if (!isPackageTarget(target)) return undefined
2880
- let declaration = target
2881
- if (target.endsWith('.cjs')) declaration = \`\${target.slice(0, -4)}.d.cts\`
2882
- else if (target.endsWith('.mjs')) declaration = \`\${target.slice(0, -4)}.d.mts\`
2883
- else if (target.endsWith('.js')) declaration = \`\${target.slice(0, -3)}.d.ts\`
2884
- else if (!isDeclaration(target)) return undefined
2923
+ const declaration = target.endsWith('.cjs')
2924
+ ? \`\${target.slice(0, -4)}.d.cts\`
2925
+ : target.endsWith('.mjs')
2926
+ ? \`\${target.slice(0, -4)}.d.mts\`
2927
+ : target.endsWith('.js')
2928
+ ? \`\${target.slice(0, -3)}.d.ts\`
2929
+ : target
2930
+ if (declaration === target && !isDeclaration(target)) return undefined
2885
2931
  return matchesFile(join(installed, declaration)) ? declaration : undefined
2886
2932
  }
2887
2933
 
@@ -2913,21 +2959,22 @@ function resolveDeclaration(
2913
2959
  // physical nested manifest starts a scope even when it omits \`type\` or cannot be
2914
2960
  // parsed. A directory at that spelling is not a manifest, so the walk continues.
2915
2961
  function readPackageType(installed: string, target: string): unknown {
2916
- let directory = dirname(join(installed, target))
2917
- while (true) {
2918
- const path = join(directory, 'package.json')
2919
- if (matchesFile(path)) {
2920
- try {
2921
- const manifest = readJson(path)
2922
- return isRecord(manifest) ? manifest.type : undefined
2923
- } catch {
2924
- return undefined
2925
- }
2926
- }
2927
- if (directory === installed) return undefined
2928
- const parent = dirname(directory)
2929
- if (parent === directory) return undefined
2930
- directory = parent
2962
+ // One \`dirname\` per path segment reaches the root from any path. The walk ends at the
2963
+ // installed package, so no scope above it is consulted.
2964
+ const start = dirname(join(installed, target))
2965
+ const chain = start
2966
+ .split(/[\\\\/]/u)
2967
+ .reduce<string[]>((climbed) => [...climbed, dirname(climbed.at(-1) ?? start)], [start])
2968
+ const end = chain.indexOf(installed)
2969
+ const scope = chain
2970
+ .slice(0, end === -1 ? chain.length : end + 1)
2971
+ .find((directory) => matchesFile(join(directory, 'package.json')))
2972
+ if (scope === undefined) return undefined
2973
+ try {
2974
+ const manifest = readJson(join(scope, 'package.json'))
2975
+ return isRecord(manifest) ? manifest.type : undefined
2976
+ } catch {
2977
+ return undefined
2931
2978
  }
2932
2979
  }
2933
2980
 
@@ -3096,16 +3143,13 @@ function checkProject(stage: Stage, project: string): readonly string[] {
3096
3143
  if (refused.length > 0) {
3097
3144
  throw new Error(\`The consumer compiler wrote \${refused} to its error stream\`)
3098
3145
  }
3099
- const reported: string[] = []
3100
- for (const line of \`\${result.stdout ?? ''}\`.split(/\\r\\n|\\n/u)) {
3101
- if (line.trim().length === 0) continue
3102
- const last = reported.at(-1)
3103
- // An elaborated diagnostic prints its detail on indented lines under its own
3104
- // first line, so each of those joins the diagnostic it elaborates.
3105
- if (/^\\s/u.test(line) && last !== undefined) {
3106
- reported[reported.length - 1] = \`\${last} \${line.trim()}\`
3107
- continue
3108
- }
3146
+ const lines = \`\${result.stdout ?? ''}\`.split(/\\r\\n|\\n/u).filter((line) => line.trim().length > 0)
3147
+ // An elaborated diagnostic prints its detail on indented lines under its own
3148
+ // first line, so each of those joins the diagnostic it elaborates. The first line
3149
+ // has no diagnostic above it to join, so it opens one whatever its indentation.
3150
+ const heads = lines.flatMap((line, index) => (index === 0 || !/^\\s/u.test(line) ? [index] : []))
3151
+ const reported = heads.map((head, position) => {
3152
+ const line = lines[head] ?? ''
3109
3153
  const located = DIAGNOSTIC_PATTERN.exec(line)?.[1]
3110
3154
  if (located === undefined) {
3111
3155
  throw new Error(\`The consumer compiler reported \${line}, which names no location\`)
@@ -3113,8 +3157,11 @@ function checkProject(stage: Stage, project: string): readonly string[] {
3113
3157
  if (resolve(stage.consumer, located) === project) {
3114
3158
  throw new Error(\`The scratch project is itself at fault: \${line}\`)
3115
3159
  }
3116
- reported.push(line)
3117
- }
3160
+ return [
3161
+ line,
3162
+ ...lines.slice(head + 1, heads[position + 1] ?? lines.length).map((detail) => detail.trim()),
3163
+ ].join(' ')
3164
+ })
3118
3165
  if (reported.length === 0 && result.status !== 0) {
3119
3166
  throw new Error(\`The consumer compiler refused the project: \${readOutput(result)}\`)
3120
3167
  }
@@ -3151,13 +3198,14 @@ const surfaced: Record<keyof typeof published, true> = declared
3151
3198
  // One consumer module importing every installed entry, written where its own
3152
3199
  // resolution finds the installed package.
3153
3200
  function writeConsumerProbe(stage: Stage, path: string, specifiers: readonly string[]): void {
3154
- const names: string[] = []
3155
- const bindings: string[] = []
3156
- for (const [index, specifier] of specifiers.entries()) {
3157
- const binding = \`entry\${String(index)}\`
3158
- names.push(binding)
3159
- bindings.push(\`import * as \${binding} from \${JSON.stringify(specifier)}\`)
3160
- }
3201
+ const modules = specifiers.map((specifier, index) => ({
3202
+ binding: \`entry\${String(index)}\`,
3203
+ specifier,
3204
+ }))
3205
+ const bindings = modules.map(
3206
+ ({ binding, specifier }) => \`import * as \${binding} from \${JSON.stringify(specifier)}\`,
3207
+ )
3208
+ const names = modules.map(({ binding }) => binding)
3161
3209
  const source = \`\${bindings.join('\\n')}\\nexport const surface = [\${names.join(', ')}]\\n\`
3162
3210
  writeFile(join(stage.consumer, path), source)
3163
3211
  }
@@ -3178,11 +3226,11 @@ function driveRuntime(stage: Stage, specifier: string, driver: string): readonly
3178
3226
  // Pack this workspace, install the archive into an isolated consumer, and read the
3179
3227
  // published surface back off the installed tree. Every later claim reads this
3180
3228
  // result, so a failure here is raised where it happens rather than once per entry.
3181
- function buildStage(): Stage {
3182
- const packed = join(readScratch(), 'packed')
3183
- const consumer = join(readScratch(), 'consumer')
3229
+ function buildStage(scratch: string): Stage {
3230
+ const packed = join(scratch, 'packed')
3231
+ const consumer = join(scratch, 'consumer')
3184
3232
  mkdirSync(packed, { recursive: true })
3185
- const pack = runNpm(['pack', '--ignore-scripts', '--pack-destination', packed], ROOT)
3233
+ const pack = runNpm(['pack', '--ignore-scripts', '--pack-destination', packed], ROOT, scratch)
3186
3234
  if (pack.status !== 0) throw new Error(\`npm pack refused this workspace: \${readOutput(pack)}\`)
3187
3235
  const archives = readdirSync(packed).filter((name) => name.endsWith('.tgz'))
3188
3236
  const archive = archives[0]
@@ -3195,6 +3243,7 @@ function buildStage(): Stage {
3195
3243
  const install = runNpm(
3196
3244
  ['install', '--ignore-scripts', '--no-audit', '--no-fund', join(packed, archive)],
3197
3245
  consumer,
3246
+ scratch,
3198
3247
  )
3199
3248
  if (install.status !== 0) {
3200
3249
  throw new Error(\`Installing the packed archive failed: \${readOutput(install)}\`)
@@ -3205,16 +3254,9 @@ function buildStage(): Stage {
3205
3254
  if (!isRecord(manifest) || !isRecord(manifest.exports)) {
3206
3255
  throw new Error('The installed manifest publishes no exports map')
3207
3256
  }
3208
- const entries: Entry[] = []
3209
- const targets: string[] = []
3210
- const subpaths: string[] = []
3211
- const undeclared: string[] = []
3212
- const excluded: string[] = []
3213
- for (const [subpath, entry] of Object.entries(manifest.exports)) {
3214
- const files = collectTargets(entry)
3215
- targets.push(...files)
3216
- subpaths.push(subpath)
3217
- const declaration = readDeclaration(entry, installed)
3257
+ const readings = Object.entries(manifest.exports).map(([subpath, mapping]) => {
3258
+ const files = collectTargets(mapping)
3259
+ const declaration = readDeclaration(mapping, installed)
3218
3260
  // A subpath resolving no declaration is partitioned rather than dropped. It is a
3219
3261
  // defect when a runtime loads one of its targets for names, because a consumer
3220
3262
  // importing it compiles against nothing under \`node16\`. It is an excluded
@@ -3225,21 +3267,19 @@ function buildStage(): Stage {
3225
3267
  declaration.commonjs === undefined &&
3226
3268
  declaration.browser === undefined
3227
3269
  ) {
3228
- if (files.some(isModule)) undeclared.push(subpath)
3229
- else excluded.push(subpath)
3230
- continue
3270
+ return { subpath, files, entry: undefined }
3231
3271
  }
3232
- const importTarget = resolveTarget(entry, RUNTIME_CONDITIONS.module)
3233
- const requireTarget = resolveTarget(entry, RUNTIME_CONDITIONS.commonjs)
3234
- const browserTarget = resolveTarget(entry, RUNTIME_CONDITIONS.browser)
3235
- const browsable = resolvesBrowser(entry)
3272
+ const importTarget = resolveTarget(mapping, RUNTIME_CONDITIONS.module)
3273
+ const requireTarget = resolveTarget(mapping, RUNTIME_CONDITIONS.commonjs)
3274
+ const browserTarget = resolveTarget(mapping, RUNTIME_CONDITIONS.browser)
3275
+ const browsable = resolvesBrowser(mapping)
3236
3276
  const shadowed = browsable && requireTarget === browserTarget
3237
3277
  const requirable = requireTarget !== undefined && !shadowed
3238
- const loadable = requirable && resolvesCommonJS(entry, installed)
3239
- entries.push({
3278
+ const loadable = requirable && resolvesCommonJS(mapping, installed)
3279
+ const entry: Entry = {
3240
3280
  subpath,
3241
3281
  specifier: subpath === '.' ? name : \`\${name}\${subpath.slice(1)}\`,
3242
- mapping: entry,
3282
+ mapping,
3243
3283
  declaration: {
3244
3284
  importable: declaration.module !== undefined,
3245
3285
  requirable: declaration.commonjs !== undefined,
@@ -3249,54 +3289,92 @@ function buildStage(): Stage {
3249
3289
  importable: importTarget !== undefined && !(browsable && importTarget === browserTarget),
3250
3290
  requirable,
3251
3291
  loadable,
3252
- })
3253
- }
3292
+ }
3293
+ return { subpath, files, entry }
3294
+ })
3295
+ const entries = readings.flatMap(({ entry }) => (entry === undefined ? [] : [entry]))
3296
+ const targets = readings.flatMap(({ files }) => files)
3297
+ const subpaths = readings.map(({ subpath }) => subpath)
3298
+ const undeclared = readings
3299
+ .filter(({ entry, files }) => entry === undefined && files.some(isModule))
3300
+ .map(({ subpath }) => subpath)
3301
+ const excluded = readings
3302
+ .filter(({ entry, files }) => entry === undefined && !files.some(isModule))
3303
+ .map(({ subpath }) => subpath)
3254
3304
  return { consumer, installed, archives, entries, subpaths, undeclared, excluded, targets }
3255
3305
  }
3256
3306
 
3257
- let scratch: string | undefined
3258
- let staged: Stage | undefined
3307
+ // One file's run: the scratch directory and the staged consumer, which is absent when the
3308
+ // registry could not be reached.
3309
+ interface DistributionRun {
3310
+ readonly scratch: string
3311
+ readonly stage: Stage | undefined
3312
+ }
3313
+
3314
+ // The run the start hook binds once for this file and the stop hook releases.
3315
+ class DistributionSuite {
3316
+ #run: DistributionRun | undefined
3317
+
3318
+ get scratch(): string {
3319
+ if (this.#run === undefined) {
3320
+ throw new Error('The distribution setup has not allocated its scratch directory')
3321
+ }
3322
+ return this.#run.scratch
3323
+ }
3259
3324
 
3260
- function readScratch(): string {
3261
- if (scratch === undefined) {
3262
- throw new Error('The distribution setup has not allocated its scratch directory')
3325
+ get stage(): Stage | undefined {
3326
+ return this.#run?.stage
3263
3327
  }
3264
- return scratch
3265
- }
3266
3328
 
3267
- beforeAll(() => {
3268
- scratch = mkdtempSync(join(tmpdir(), 'distribution-'))
3269
- mkdirSync(join(scratch, 'cache'), { recursive: true })
3270
- staged = openStage()
3271
- })
3329
+ start(): void {
3330
+ if (this.#run !== undefined) {
3331
+ throw new Error('The distribution setup has already allocated its scratch directory')
3332
+ }
3333
+ const scratch = mkdtempSync(join(tmpdir(), 'distribution-'))
3334
+ this.#run = { scratch, stage: openStage(scratch) }
3335
+ }
3272
3336
 
3273
- afterAll(() => {
3274
- if (scratch !== undefined) {
3275
- rmSync(scratch, { force: true, recursive: true, maxRetries: 3, retryDelay: 100 })
3337
+ destroy(): void {
3338
+ if (this.#run !== undefined) {
3339
+ rmSync(this.#run.scratch, { force: true, recursive: true, maxRetries: 3, retryDelay: 100 })
3340
+ }
3341
+ this.#run = undefined
3276
3342
  }
3277
- })
3343
+ }
3344
+
3345
+ const DISTRIBUTION_SUITE = new DistributionSuite()
3346
+
3347
+ beforeAll(() => DISTRIBUTION_SUITE.start())
3348
+
3349
+ afterAll(() => DISTRIBUTION_SUITE.destroy())
3278
3350
 
3279
3351
  // Installing the packed archive resolves its own runtime dependencies, so an
3280
3352
  // unreachable registry leaves nothing to measure. Under release that is the gate
3281
3353
  // failing; anywhere else the suite skips and names the mechanism it wanted.
3282
- function openStage(): Stage | undefined {
3354
+ function openStage(scratch: string): Stage | undefined {
3283
3355
  try {
3284
- if (runNpm(PING, ROOT).status !== 0) {
3356
+ mkdirSync(join(scratch, 'cache'), { recursive: true })
3357
+ if (runNpm(PING, ROOT, scratch).status !== 0) {
3285
3358
  if (!RELEASE) return undefined
3286
3359
  throw new Error(
3287
3360
  'The release gate requires a reachable npm registry, and npm ping did not answer',
3288
3361
  )
3289
3362
  }
3290
- return buildStage()
3363
+ return buildStage(scratch)
3291
3364
  } catch (error) {
3292
- rmSync(readScratch(), { force: true, recursive: true, maxRetries: 3, retryDelay: 100 })
3365
+ rmSync(scratch, {
3366
+ force: true,
3367
+ recursive: true,
3368
+ maxRetries: 3,
3369
+ retryDelay: 100,
3370
+ })
3293
3371
  throw error
3294
3372
  }
3295
3373
  }
3296
3374
 
3297
3375
  describe('distribution classifiers', () => {
3298
3376
  it('classifies synthetic export mappings without a registry stage', () => {
3299
- const root = join(readScratch(), 'classifiers')
3377
+ const root = join(DISTRIBUTION_SUITE.scratch, 'classifiers')
3300
3378
  writeFile(
3301
3379
  join(root, 'package.json'),
3302
3380
  JSON.stringify({
@@ -3365,6 +3443,7 @@ describe('distribution classifiers', () => {
3365
3443
  // The staged consumer, or a skip naming what the run could not reach. \`it.skipIf\`
3366
3444
  // carries no reason, so the gate sits here where the test context can state one.
3367
3445
  function requireStage(context: TestContext): Stage {
3446
+ const staged = DISTRIBUTION_SUITE.stage
3368
3447
  if (staged === undefined) {
3369
3448
  return context.skip('\`npm ping\` did not answer, so nothing was packed or installed')
3370
3449
  }
@@ -3429,28 +3508,24 @@ describe('installed package consumer', () => {
3429
3508
  it('compiles a consumer under every module resolution [requires the registry]', (context) => {
3430
3509
  const stage = requireStage(context)
3431
3510
  const name = readManifestName(join(stage.installed, 'package.json'))
3432
- const reported: string[] = []
3433
- const silent: string[] = []
3434
- for (const driver of RESOLUTIONS) {
3435
- for (const [extension, format] of FORMATS) {
3511
+ const readings = RESOLUTIONS.flatMap((driver) =>
3512
+ FORMATS.flatMap(([extension, format]) => {
3436
3513
  const written = selectEntries(stage.entries, driver.conditions[format])
3437
- if (written.length === 0) continue
3514
+ if (written.length === 0) return []
3438
3515
  const label = \`\${driver.label}.\${extension}\`
3439
3516
  const probe = \`probe.\${label}\`
3440
3517
  const specifiers = written.map((entry) => entry.specifier)
3441
3518
  writeConsumerProbe(stage, probe, specifiers)
3442
3519
  const project = writeProject(stage, probe, driver, [\`./\${probe}\`])
3443
- for (const message of checkProject(stage, project)) {
3444
- reported.push(\`\${label}: \${message}\`)
3445
- }
3520
+ const messages = checkProject(stage, project).map((message) => \`\${label}: \${message}\`)
3446
3521
  const control = \`control.\${label}\`
3447
3522
  writeConsumerProbe(stage, control, [\`\${name}\${ABSENT_SUBPATH}\`])
3448
3523
  const refused = writeProject(stage, control, driver, [\`./\${control}\`])
3449
- if (checkProject(stage, refused).length === 0) silent.push(label)
3450
- }
3451
- }
3452
- expect(reported).toStrictEqual([])
3453
- expect(silent).toStrictEqual([])
3524
+ return [{ messages, silent: checkProject(stage, refused).length === 0 ? [label] : [] }]
3525
+ }),
3526
+ )
3527
+ expect(readings.flatMap(({ messages }) => messages)).toStrictEqual([])
3528
+ expect(readings.flatMap(({ silent }) => silent)).toStrictEqual([])
3454
3529
  })
3455
3530
  {{guard}}})
3456
3531
 
@@ -4150,6 +4225,7 @@ var isBlueprint = recordOf({
4150
4225
  conformance: isBoolean,
4151
4226
  service: isBoolean,
4152
4227
  vendors: andOf(isCollection, arrayOf(isString)),
4228
+ studies: isBoolean,
4153
4229
  global: isBoolean,
4154
4230
  showcase: isBoolean,
4155
4231
  journey: isBoolean,
@@ -4566,9 +4642,8 @@ function parseCompilerOptions(value) {
4566
4642
  * ```
4567
4643
  */
4568
4644
  function bytesToHex(bytes) {
4569
- let hex = "";
4570
- for (const byte of bytes) hex += byte.toString(16).padStart(2, "0");
4571
- return hex;
4645
+ const digits = Array.from({ length: 256 }, (_, value) => value.toString(16).padStart(2, "0"));
4646
+ return Iterator.from(bytes).map((byte) => digits[byte] ?? "").toArray().join("");
4572
4647
  }
4573
4648
  /**
4574
4649
  * Encodes text as the exact lowercase hexadecimal form of its UTF-8 bytes.
@@ -4613,12 +4688,68 @@ function contentToHex(content) {
4613
4688
  * ```
4614
4689
  */
4615
4690
  function computeBytes(content) {
4616
- let bytes = 0;
4617
- for (const character of content) {
4691
+ return content[Symbol.iterator]().reduce((bytes, character) => {
4618
4692
  const code = character.codePointAt(0) ?? 0;
4619
- bytes += code <= 127 ? 1 : code <= 2047 ? 2 : code <= 65535 ? 3 : 4;
4693
+ return bytes + (code <= 127 ? 1 : code <= 2047 ? 2 : code <= 65535 ? 3 : 4);
4694
+ }, 0);
4695
+ }
4696
+ /**
4697
+ * Computes the sum of a list of numbers.
4698
+ *
4699
+ * @param values - The numbers to add.
4700
+ * @returns The sum, and `0` for no numbers.
4701
+ *
4702
+ * @remarks
4703
+ * The one place a running tally is derived from the amounts that make it up, so a
4704
+ * byte budget is the allowance less what was spent rather than a counter that is
4705
+ * reassigned as reads land.
4706
+ *
4707
+ * @example
4708
+ * ```ts
4709
+ * import { computeTotal } from '@orkestrel/scaffold'
4710
+ *
4711
+ * computeTotal([65536, 65536, 12]) // 131084
4712
+ * computeTotal([]) // 0
4713
+ * ```
4714
+ */
4715
+ function computeTotal(values) {
4716
+ return values.reduce((total, value) => total + value, 0);
4717
+ }
4718
+ /**
4719
+ * Yields the answer to each value of a source and ends after the first refused answer.
4720
+ *
4721
+ * @param source - The values to answer, requested one at a time as the answers are read.
4722
+ * @param answer - Answers one value, or returns `undefined` to refuse it.
4723
+ * @returns A generator of the answers in source order, whose last answer is the first refusal when
4724
+ * the source holds one.
4725
+ * @throws Thrown when `answer` throws for a value, with that error, or when closing `source` after
4726
+ * a refusal throws, with the close's error.
4727
+ *
4728
+ * @remarks
4729
+ * The body is a `for…of` loop, so the source's iterator is requested once, when the first answer
4730
+ * is read, and stepped only as the answers are read: a length the source reports never bounds the
4731
+ * read, a hole reads as an `undefined` value, and an infinite source is read as far as the first
4732
+ * refusal. The iterator is closed after a refusal with a normal completion, so a raising close
4733
+ * surfaces, and is closed when `answer` throws with a throw completion, so the error from `answer`
4734
+ * wins. An iterator that ends is left alone. A caller that stops at a refusal and keeps what came
4735
+ * before it reads the answers into a list and checks that none is `undefined`.
4736
+ *
4737
+ * @example
4738
+ * ```ts
4739
+ * import { iterateAnswers } from '@orkestrel/scaffold'
4740
+ *
4741
+ * const halve = (value: number): number | undefined => (value % 2 === 0 ? value / 2 : undefined)
4742
+ *
4743
+ * Array.from(iterateAnswers([2, 4, 5, 6], halve)).length // 3
4744
+ * Array.from(iterateAnswers([2, 4], halve)) // [1, 2]
4745
+ * ```
4746
+ */
4747
+ function* iterateAnswers(source, answer) {
4748
+ for (const value of source) {
4749
+ const answered = answer(value);
4750
+ yield answered;
4751
+ if (answered === void 0) return;
4620
4752
  }
4621
- return bytes;
4622
4753
  }
4623
4754
  /**
4624
4755
  * Computes the deterministic content identity of text.
@@ -4645,9 +4776,10 @@ function computeBytes(content) {
4645
4776
  * ```
4646
4777
  */
4647
4778
  function computeHash(text) {
4648
- let hash = 14695981039346656037n;
4649
- for (let index = 0; index < text.length; index += 1) hash = BigInt.asUintN(64, (hash ^ BigInt(text.charCodeAt(index))) * 1099511628211n);
4650
- return hash.toString(16).padStart(16, "0");
4779
+ return text[Symbol.iterator]().reduce((digest, character) => {
4780
+ const first = BigInt.asUintN(64, (digest ^ BigInt(character.charCodeAt(0))) * 1099511628211n);
4781
+ return character.length === 1 ? first : BigInt.asUintN(64, (first ^ BigInt(character.charCodeAt(1))) * 1099511628211n);
4782
+ }, 14695981039346656037n).toString(16).padStart(16, "0");
4651
4783
  }
4652
4784
  /**
4653
4785
  * Tests whether a path instructs or wires an agent rather than the toolchain.
@@ -4833,28 +4965,23 @@ function inferGroup(path) {
4833
4965
  * ```
4834
4966
  */
4835
4967
  function serializeTypeScriptString(value) {
4836
- let serialized = "'";
4837
- for (let index = 0; index < value.length; index += 1) {
4838
- const code = value.charCodeAt(index);
4839
- const character = value.charAt(index);
4840
- if (character === "'") serialized += "\\'";
4841
- else if (character === "\\") serialized += "\\\\";
4842
- else if (character === "\b") serialized += "\\b";
4843
- else if (character === "\f") serialized += "\\f";
4844
- else if (character === "\n") serialized += "\\n";
4845
- else if (character === "\r") serialized += "\\r";
4846
- else if (character === " ") serialized += "\\t";
4847
- else if (code === 8232) serialized += "\\u2028";
4848
- else if (code === 8233) serialized += "\\u2029";
4849
- else if (code < 32 || code >= 55296 && code <= 57343) {
4850
- const next = value.charCodeAt(index + 1);
4851
- if (code >= 55296 && code <= 56319 && next >= 56320 && next <= 57343) {
4852
- serialized += `${character}${value.charAt(index + 1)}`;
4853
- index += 1;
4854
- } else serialized += `\\u${code.toString(16).padStart(4, "0")}`;
4855
- } else serialized += character;
4856
- }
4857
- return `${serialized}'`;
4968
+ const escapes = {
4969
+ "'": "\\'",
4970
+ "\\": "\\\\",
4971
+ "\b": "\\b",
4972
+ "\f": "\\f",
4973
+ "\n": "\\n",
4974
+ "\r": "\\r",
4975
+ " ": "\\t",
4976
+ "\u2028": "\\u2028",
4977
+ "\u2029": "\\u2029"
4978
+ };
4979
+ return `'${Array.from(value, (character) => {
4980
+ const escape = escapes[character];
4981
+ if (escape !== void 0) return escape;
4982
+ const code = character.charCodeAt(0);
4983
+ return character.length === 1 && (code < 32 || code >= 55296 && code <= 57343) ? `\\u${code.toString(16).padStart(4, "0")}` : character;
4984
+ }).join("")}'`;
4858
4985
  }
4859
4986
  /**
4860
4987
  * Derives the guide mirror path a package name answers for.
@@ -5186,21 +5313,18 @@ function matchesDriftReachability(ownership, finding) {
5186
5313
  */
5187
5314
  function catalogToLayers(entries) {
5188
5315
  const published = new Set(entries.filter((entry) => entry.lookup === "found").map((entry) => entry.name));
5189
- const pending = /* @__PURE__ */ new Map();
5190
- for (const entry of entries) {
5191
- if (entry.lookup !== "found") continue;
5192
- const edges = [...entry.dependencies, ...entry.peers].map((dependency) => dependency.name);
5193
- pending.set(entry.name, new Set(edges.filter((name) => published.has(name))));
5194
- }
5195
- const layers = [];
5196
- while (pending.size > 0) {
5197
- const ready = [...pending].filter(([, edges]) => edges.size === 0).map(([name]) => name);
5198
- if (ready.length === 0) break;
5199
- for (const name of ready) pending.delete(name);
5200
- for (const edges of pending.values()) for (const name of ready) edges.delete(name);
5201
- layers.push(ready.sort());
5202
- }
5203
- return layers;
5316
+ const found = Iterator.from(entries).flatMap((entry) => entry.lookup === "found" ? [[entry.name, [...entry.dependencies, ...entry.peers].map((dependency) => dependency.name).filter((name) => published.has(name))]] : []).toArray();
5317
+ const dependencies = new Map(found);
5318
+ return found.reduce((state) => {
5319
+ const ready = [...dependencies].filter(([name, edges]) => !state.placed.has(name) && edges.every((edge) => state.placed.has(edge))).map(([name]) => name).sort();
5320
+ return ready.length === 0 ? state : {
5321
+ layers: [...state.layers, ready],
5322
+ placed: /* @__PURE__ */ new Set([...state.placed, ...ready])
5323
+ };
5324
+ }, {
5325
+ layers: [],
5326
+ placed: /* @__PURE__ */ new Set()
5327
+ }).layers;
5204
5328
  }
5205
5329
  /**
5206
5330
  * Projects a plan into its tally by artifact origin.
@@ -5223,12 +5347,9 @@ function catalogToLayers(entries) {
5223
5347
  * ```
5224
5348
  */
5225
5349
  function planToSummary(plan) {
5226
- let host = 0;
5227
- let template = 0;
5228
- let computed = 0;
5229
- for (const artifact of plan.artifacts) if (artifact.origin === "host") host += 1;
5230
- else if (artifact.origin === "template") template += 1;
5231
- else computed += 1;
5350
+ const origins = Iterator.from(plan.artifacts).map((artifact) => artifact.origin).toArray();
5351
+ const host = origins.filter((origin) => origin === "host").length;
5352
+ const template = origins.filter((origin) => origin === "template").length;
5232
5353
  return {
5233
5354
  name: plan.blueprint.name,
5234
5355
  src: plan.blueprint.src,
@@ -5236,7 +5357,7 @@ function planToSummary(plan) {
5236
5357
  groups: plan.groups,
5237
5358
  host,
5238
5359
  template,
5239
- computed
5360
+ computed: origins.length - host - template
5240
5361
  };
5241
5362
  }
5242
5363
  /**
@@ -5324,8 +5445,8 @@ function compareVersions(left, right) {
5324
5445
  if (second !== void 0) return -1;
5325
5446
  return compareValues(left, right);
5326
5447
  }
5327
- for (let index = 0; index < first.length; index += 1) {
5328
- const order = compareValues(first[index] ?? 0, second[index] ?? 0);
5448
+ for (const [index, component] of first.entries()) {
5449
+ const order = compareValues(component, second[index] ?? 0);
5329
5450
  if (order !== 0) return order;
5330
5451
  }
5331
5452
  return 0;
@@ -5476,35 +5597,17 @@ function manifestToDependencies(manifest) {
5476
5597
  development: [],
5477
5598
  peer: []
5478
5599
  };
5479
- const runtime = [];
5480
- const development = [];
5481
- const peer = [];
5482
- const sections = [
5483
- {
5484
- name: "dependencies",
5485
- dependencies: runtime
5486
- },
5487
- {
5488
- name: "devDependencies",
5489
- dependencies: development
5490
- },
5491
- {
5492
- name: "peerDependencies",
5493
- dependencies: peer
5494
- }
5495
- ];
5496
- for (const section of sections) {
5497
- const entries = parsed[section.name];
5498
- if (!isRecord(entries)) continue;
5499
- for (const [name, range] of Object.entries(entries)) {
5500
- if (!DEPENDENCY_NAME_PATTERN.test(name) || name.length > 214) continue;
5501
- if (!isString(range) || range.length === 0 || range.length > 2048) continue;
5502
- section.dependencies.push({
5503
- name,
5504
- range
5505
- });
5506
- }
5507
- }
5600
+ const [runtime = [], development = [], peer = []] = [
5601
+ "dependencies",
5602
+ "devDependencies",
5603
+ "peerDependencies"
5604
+ ].map((section) => {
5605
+ const entries = parsed[section];
5606
+ return isRecord(entries) ? Object.entries(entries).flatMap(([name, range]) => !DEPENDENCY_NAME_PATTERN.test(name) || name.length > 214 || !isString(range) || range.length === 0 || range.length > 2048 ? [] : [{
5607
+ name,
5608
+ range
5609
+ }]) : [];
5610
+ });
5508
5611
  return {
5509
5612
  runtime: limitEntries(runtime, MAX_COLLECTION_ITEMS),
5510
5613
  development: limitEntries(development, MAX_COLLECTION_ITEMS),
@@ -5541,6 +5644,50 @@ function renderSkillPointer(canonical, name) {
5541
5644
  if (closing === -1) return void 0;
5542
5645
  return `${lines.slice(0, closing + 1).join("\n")}\n${fillTemplate(ARTIFACT_TEMPLATES.orchestration.skill, { name })}`;
5543
5646
  }
5647
+ /**
5648
+ * Scans manifest text for its strings and object braces, in order.
5649
+ *
5650
+ * @param text - The manifest text to scan.
5651
+ * @returns One token per quoted string and per `{` or `}` outside a string, or
5652
+ * `undefined` when a string is never closed.
5653
+ *
5654
+ * @remarks
5655
+ * Native JSON validation owns syntax; this scan only locates the byte ranges an
5656
+ * in-place writer edits. A string is opaque, so a brace inside one is never read,
5657
+ * and a backslash escapes the character after it. Depth counts objects alone and
5658
+ * ignores arrays, exactly as the range writers count them, so a key nested inside
5659
+ * another object never reads as a top-level one. A string that a colon follows
5660
+ * carries `value`, the offset of the character after that colon and the
5661
+ * whitespace beside it.
5662
+ *
5663
+ * @example
5664
+ * ```ts
5665
+ * import { scanManifestTokens } from '@orkestrel/scaffold'
5666
+ *
5667
+ * scanManifestTokens('{"a": "b"}')?.length // 4
5668
+ * scanManifestTokens('{"a') // undefined
5669
+ * ```
5670
+ */
5671
+ function scanManifestTokens(text) {
5672
+ const tokens = [];
5673
+ for (const match of text.matchAll(/(?<string>"(?:\\[\s\S]|[^"\\])*")(?<colon>\s*:\s*)?|[{}]|"/gu)) {
5674
+ if (match[0] === "\"") return void 0;
5675
+ const literal = match.groups?.string;
5676
+ const depth = (tokens.at(-1)?.depth ?? 0) + (match[0] === "{" ? 1 : match[0] === "}" ? -1 : 0);
5677
+ tokens.push(literal === void 0 ? {
5678
+ text: match[0],
5679
+ index: match.index,
5680
+ depth,
5681
+ value: void 0
5682
+ } : {
5683
+ text: literal,
5684
+ index: match.index,
5685
+ depth,
5686
+ value: match.groups?.colon === void 0 ? void 0 : match.index + match[0].length
5687
+ });
5688
+ }
5689
+ return tokens;
5690
+ }
5544
5691
  //#endregion
5545
5692
  //#region src/core/compilers.ts
5546
5693
  /**
@@ -5591,18 +5738,18 @@ function blueprintToSheets(blueprint) {
5591
5738
  * ```
5592
5739
  */
5593
5740
  function blueprintToExports(blueprint) {
5594
- const exports = { ...srcToExports(blueprint.src) };
5595
- for (const extension of blueprintToFaces(blueprint)) {
5596
- if (extension.surface !== "browser" || !extension.axes.includes("src")) continue;
5597
- exports[`./${extension.name}`] = pathToCondition(`./dist/src/${extension.name}/index`, ["es"]);
5598
- if (blueprint.src.includes("browser")) exports["./browser"] = pathToCondition("./dist/src/browser/index", ["es"]);
5599
- }
5600
- for (const name of [...blueprintToSheets(blueprint), ...blueprint.themes ? ["styles/themes"] : []]) {
5601
- exports[`./${name}`] = `./dist/src/${name}/index.css`;
5602
- exports[`./${name}/scss`] = `./src/${name}/index.scss`;
5603
- }
5604
- if (Object.keys(exports).length > 0) exports["./package.json"] = "./package.json";
5605
- return exports;
5741
+ const exported = {
5742
+ ...srcToExports(blueprint.src),
5743
+ ...Object.fromEntries(blueprintToFaces(blueprint).filter((extension) => extension.axes.includes("src")).flatMap((extension) => {
5744
+ const face = [`./${extension.name}`, pathToCondition(`./dist/src/${extension.name}/index`, ["es"])];
5745
+ return blueprint.src.includes("browser") ? [face, ["./browser", pathToCondition("./dist/src/browser/index", ["es"])]] : [face];
5746
+ })),
5747
+ ...Object.fromEntries([...blueprintToSheets(blueprint), ...blueprint.themes ? ["styles/themes"] : []].flatMap((name) => [[`./${name}`, `./dist/src/${name}/index.css`], [`./${name}/scss`, `./src/${name}/index.scss`]]))
5748
+ };
5749
+ return Object.keys(exported).length > 0 ? {
5750
+ ...exported,
5751
+ "./package.json": "./package.json"
5752
+ } : exported;
5606
5753
  }
5607
5754
  /**
5608
5755
  * Builds one `exports` condition block for a built environment.
@@ -5702,17 +5849,14 @@ function srcToEntry(src) {
5702
5849
  */
5703
5850
  function srcToExports(src) {
5704
5851
  if (src.length === 0) return {};
5705
- const map = {};
5706
5852
  const root = srcToRoot(src);
5707
5853
  const environment = root ?? "core";
5708
- map["."] = pathToCondition(`./dist/src/${environment}/index`, SRC_MATRIX[environment].formats);
5709
- if (root === void 0) for (const declared of ENVIRONMENTS) {
5710
- if (declared === "core" || !src.includes(declared)) continue;
5711
- const row = SRC_MATRIX[declared];
5712
- map[row.path] = pathToCondition(`./dist/src/${declared}/index`, row.formats);
5713
- }
5714
- map["./package.json"] = "./package.json";
5715
- return map;
5854
+ const entries = [
5855
+ [".", pathToCondition(`./dist/src/${environment}/index`, SRC_MATRIX[environment].formats)],
5856
+ ...root === void 0 ? ENVIRONMENTS.filter((declared) => declared !== "core" && src.includes(declared)).map((declared) => [SRC_MATRIX[declared].path, pathToCondition(`./dist/src/${declared}/index`, SRC_MATRIX[declared].formats)]) : [],
5857
+ ["./package.json", "./package.json"]
5858
+ ];
5859
+ return Object.fromEntries(entries);
5716
5860
  }
5717
5861
  /**
5718
5862
  * Projects a blueprint into the development dependencies its manifest declares.
@@ -5761,7 +5905,7 @@ function blueprintToDevDependencies(blueprint) {
5761
5905
  const sheets = blueprintToSheets(blueprint).length > 0 || blueprint.themes;
5762
5906
  const rendered = blueprint.src.includes("browser") || blueprint.app.includes("browser") || sheets;
5763
5907
  const own = blueprint.src.length > 0 || sheets ? `@orkestrel/${blueprint.name}` : blueprint.name;
5764
- const merged = {
5908
+ const prior = {
5765
5909
  ...BASE_DEV_DEPENDENCIES,
5766
5910
  ...rendered && !BROWSE_UPSTREAM.includes(own) ? BROWSE_DEV_DEPENDENCIES : {},
5767
5911
  ...sheets ? STYLES_DEV_DEPENDENCIES : {},
@@ -5769,12 +5913,16 @@ function blueprintToDevDependencies(blueprint) {
5769
5913
  ...blueprint.src.includes("browser") || blueprint.setup.includes("browser") || sheets ? SOURCE_BROWSER_DEV_DEPENDENCIES : {},
5770
5914
  ...blueprint.app.length > 0 ? APP_DEV_DEPENDENCIES : {},
5771
5915
  ...blueprint.app.includes("browser") ? APP_BROWSER_DEV_DEPENDENCIES : {},
5772
- ...blueprint.app.includes("server") ? APP_SERVER_DEV_DEPENDENCIES : {}
5916
+ ...blueprint.app.includes("server") ? APP_SERVER_DEV_DEPENDENCIES : {},
5917
+ ...Object.fromEntries(blueprintToMachinery(blueprint).frameworks.flatMap((framework) => Object.entries(FRAMEWORK_MATRIX[framework].dependencies))),
5918
+ ...Object.fromEntries(Iterator.from(blueprint.extras).map((extra) => [extra.name, extra.range]).toArray()),
5919
+ ...blueprint.showcase && blueprint.app.includes("browser") ? SHOWCASE_DEV_DEPENDENCIES : {}
5920
+ };
5921
+ const peers = Iterator.from(blueprint.peers).flatMap((peer) => Object.hasOwn(prior, peer.name) ? [] : [[peer.name, peer.range]]).toArray();
5922
+ const merged = {
5923
+ ...prior,
5924
+ ...Object.fromEntries(peers.filter(([name], index) => peers.findIndex(([other]) => other === name) === index))
5773
5925
  };
5774
- for (const framework of blueprintToMachinery(blueprint).frameworks) Object.assign(merged, FRAMEWORK_MATRIX[framework].dependencies);
5775
- for (const extra of blueprint.extras) merged[extra.name] = extra.range;
5776
- if (blueprint.showcase && blueprint.app.includes("browser")) Object.assign(merged, SHOWCASE_DEV_DEPENDENCIES);
5777
- for (const peer of blueprint.peers) if (!Object.hasOwn(merged, peer.name)) merged[peer.name] = peer.range;
5778
5926
  const runtime = new Set(blueprint.dependencies.map((dependency) => dependency.name));
5779
5927
  return Object.fromEntries(Object.entries(merged).filter(([name]) => name !== own && !runtime.has(name)).sort(([left], [right]) => compareValues(left, right)));
5780
5928
  }
@@ -5811,62 +5959,82 @@ function blueprintToManifestOverrides(blueprint) {
5811
5959
  */
5812
5960
  function replaceManifestOverrides(manifest, overrides) {
5813
5961
  if (!isRecord(parseJSON(manifest))) return void 0;
5814
- let compiled = manifest;
5815
- for (const [selector, children] of Object.entries(overrides)) for (const [child, range] of Object.entries(children)) {
5816
- const path = [
5962
+ return Object.entries(overrides).flatMap(([selector, children]) => Object.entries(children).map(([child, range]) => ({
5963
+ path: [
5817
5964
  "overrides",
5818
5965
  selector,
5819
5966
  child
5820
- ];
5821
- let open = compiled.indexOf("{");
5822
- for (let level = 0; level < path.length; level += 1) {
5823
- const key = path[level];
5824
- let depth = 0;
5825
- let close = -1;
5826
- let start = -1;
5827
- let end = -1;
5828
- let count = 0;
5829
- for (const token of compiled.slice(open).matchAll(/"(?:\\[\s\S]|[^"\\])*"|[{}[\]]/gu)) {
5830
- const position = open + token.index;
5831
- const text = token[0];
5832
- if (text === "{" || text === "[") depth += 1;
5833
- else if (text === "}" || text === "]") {
5834
- depth -= 1;
5835
- if (depth === 1 && start >= 0 && end < 0) end = position + 1;
5836
- if (depth === 0) {
5837
- close = position;
5838
- break;
5839
- }
5840
- } else if (depth === 1) {
5841
- if (position === start) end = position + text.length;
5842
- let colon = position + text.length;
5843
- while (/\s/u.test(compiled.charAt(colon))) colon += 1;
5844
- if (compiled.charAt(colon) !== ":" || parseJSON(text) !== key) continue;
5845
- count += 1;
5846
- start = colon + 1;
5847
- while (/\s/u.test(compiled.charAt(start))) start += 1;
5848
- end = -1;
5849
- }
5850
- }
5851
- if (close < 0 || count > 1) return void 0;
5852
- if (count === 0) {
5853
- let insertion = JSON.stringify(range);
5854
- for (const name of path.slice(level).reverse()) insertion = `{${JSON.stringify(name)}:${insertion}}`;
5855
- let tail = close - 1;
5856
- while (tail > open && /\s/u.test(compiled.charAt(tail))) tail -= 1;
5857
- compiled = compiled.slice(0, tail + 1) + (tail === open ? "" : ",") + insertion.slice(1, -1) + compiled.slice(tail + 1);
5858
- break;
5859
- }
5860
- if (level < path.length - 1) {
5861
- if (compiled.charAt(start) !== "{") return void 0;
5862
- open = start;
5863
- } else {
5864
- if (compiled.charAt(start) !== "\"" || end < 0) return void 0;
5865
- if (parseJSON(compiled.slice(start, end)) !== range) compiled = compiled.slice(0, start) + JSON.stringify(range) + compiled.slice(end);
5866
- }
5967
+ ],
5968
+ range
5969
+ }))).reduce((compiled, leaf) => compiled === void 0 ? void 0 : replaceManifestOverride(compiled, leaf.path, leaf.range), manifest);
5970
+ }
5971
+ /**
5972
+ * Replaces one npm override leaf in manifest text, inserting its absent ancestors.
5973
+ *
5974
+ * @param manifest - The JSON package manifest text, already known to be an object.
5975
+ * @param path - The object keys from the manifest root to the leaf, ending at the leaf's key.
5976
+ * @param range - The string the leaf must hold.
5977
+ * @returns The text carrying the leaf, or `undefined` for a duplicate selected key, a malformed
5978
+ * ancestor, or a non-string leaf.
5979
+ *
5980
+ * @remarks
5981
+ * The one step {@link replaceManifestOverrides} repeats per leaf. Absent ancestors and the leaf are
5982
+ * inserted after the last member of the deepest ancestor the text declares, and a leaf already
5983
+ * holding `range` keeps its original spelling. Every byte outside the edited path survives.
5984
+ *
5985
+ * @example
5986
+ * ```ts
5987
+ * import { replaceManifestOverride } from '@orkestrel/scaffold'
5988
+ *
5989
+ * replaceManifestOverride('{}', ['overrides', 'plugin@1.0.0', 'child'], '2.0.0')
5990
+ * // '{"overrides":{"plugin@1.0.0":{"child":"2.0.0"}}}'
5991
+ * ```
5992
+ */
5993
+ function replaceManifestOverride(manifest, path, range) {
5994
+ const tokens = scanManifestTokens(manifest);
5995
+ if (tokens === void 0) return void 0;
5996
+ return path.reduce((state, key, level) => {
5997
+ if (state.done) return state;
5998
+ const refused = {
5999
+ ...state,
6000
+ done: true,
6001
+ text: void 0
6002
+ };
6003
+ const brace = tokens.find((token) => token.index === state.open && token.text === "{");
6004
+ if (brace === void 0) return refused;
6005
+ const close = tokens.find((token) => token.text === "}" && token.index > state.open && token.depth === brace.depth - 1);
6006
+ if (close === void 0) return refused;
6007
+ const matches = tokens.flatMap((token, at) => token.index > state.open && token.index < close.index && token.depth === brace.depth && token.value !== void 0 && parseJSON(token.text) === key ? [at] : []);
6008
+ if (matches.length > 1) return refused;
6009
+ const at = matches[0];
6010
+ const entry = at === void 0 ? void 0 : tokens[at];
6011
+ if (at === void 0 || entry === void 0) {
6012
+ const insertion = path.slice(level).reduceRight((inner, name) => `{${JSON.stringify(name)}:${inner}}`, JSON.stringify(range));
6013
+ const tail = state.open + manifest.slice(state.open + 1, close.index).trimEnd().length;
6014
+ return {
6015
+ ...state,
6016
+ done: true,
6017
+ text: manifest.slice(0, tail + 1) + (tail === state.open ? "" : ",") + insertion.slice(1, -1) + manifest.slice(tail + 1)
6018
+ };
5867
6019
  }
5868
- }
5869
- return compiled;
6020
+ const start = entry.value;
6021
+ if (start === void 0) return refused;
6022
+ if (level < path.length - 1) return manifest.charAt(start) === "{" ? {
6023
+ ...state,
6024
+ open: start
6025
+ } : refused;
6026
+ const literal = tokens[at + 1];
6027
+ if (manifest.charAt(start) !== "\"" || literal === void 0) return refused;
6028
+ return {
6029
+ ...state,
6030
+ done: true,
6031
+ text: parseJSON(literal.text) === range ? manifest : manifest.slice(0, start) + JSON.stringify(range) + manifest.slice(literal.index + literal.text.length)
6032
+ };
6033
+ }, {
6034
+ done: false,
6035
+ open: manifest.indexOf("{"),
6036
+ text: manifest
6037
+ }).text;
5870
6038
  }
5871
6039
  /**
5872
6040
  * Projects a blueprint into the scripts its manifest declares.
@@ -5881,11 +6049,12 @@ function replaceManifestOverrides(manifest, overrides) {
5881
6049
  * proofs every workspace can pass before it has a public API, and one build per
5882
6050
  * target that actually builds.
5883
6051
  *
5884
- * A publishing workspace isolates its distribution and live-service proofs from
5885
- * `test` and runs them from `prepublishOnly` instead. Publishing is what selects
5886
- * the distribution proof: what that proof measures is the packed tarball, so a
5887
- * workspace that packs no published source has nothing for it to read. A private
5888
- * workspace therefore omits it and runs a live-service proof from `test`.
6052
+ * A publishing workspace isolates its distribution, study, and live-service proofs
6053
+ * from `test` and runs them from `prepublishOnly` instead. Publishing is what
6054
+ * selects the distribution proof: what that proof measures is the packed tarball,
6055
+ * so a workspace that packs no published source has nothing for it to read. A
6056
+ * private workspace therefore omits it and runs a study proof and a live-service
6057
+ * proof from `test`, the study proof first.
5889
6058
  * Integration and conformance stay in `test` because they neither pack nor
5890
6059
  * install the workspace and drive no external service. A conformance run may
5891
6060
  * start a server, but it starts its own and reaches it over loopback, so the run
@@ -5921,7 +6090,10 @@ function blueprintToScripts(blueprint) {
5921
6090
  ...sheets,
5922
6091
  ...blueprint.bin ? ["bin"] : []
5923
6092
  ].map((name) => `npm run check:src:${name}`);
5924
- const scripts = {
6093
+ const journey = `vitest run --config ${JOURNEY_CONFIG_PATH} --no-cache --reporter=dot`;
6094
+ const showcase = `vite --config ${SHOWCASE_CONFIG_PATH}`;
6095
+ const buildShowcase = `vite build --config ${SHOWCASE_CONFIG_PATH}`;
6096
+ return {
5925
6097
  clean: "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
5926
6098
  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)\"",
5927
6099
  format: "oxfmt --config .oxfmtrc.json --write .",
@@ -5933,123 +6105,118 @@ function blueprintToScripts(blueprint) {
5933
6105
  ...checks.length > 0 ? ["npm run check:src"] : [],
5934
6106
  ...blueprint.app.length > 0 ? ["npm run check:app"] : [],
5935
6107
  ...blueprint.skills ? ["npm run check:skills"] : []
5936
- ].join(" && ")
6108
+ ].join(" && "),
6109
+ ...checks.length > 0 ? {
6110
+ "check:src": checks.join(" && "),
6111
+ ...Object.fromEntries(Iterator.from(blueprint.src).map((environment) => [`check:src:${environment}`, `tsc --noEmit -p configs/src/tsconfig.${environment}.json`]).toArray()),
6112
+ ...blueprint.bin ? { "check:src:bin": "tsc --noEmit -p configs/src/tsconfig.bin.json" } : {},
6113
+ ...Object.fromEntries([...sheets, ...source].map((name) => [`check:src:${name}`, `tsc --noEmit -p configs/src/tsconfig.${name}.json`]))
6114
+ } : {},
6115
+ ...blueprint.app.length > 0 ? {
6116
+ "check:app": [...blueprint.app, ...application].map((environment) => `npm run check:app:${environment}`).join(" && "),
6117
+ ...Object.fromEntries(Iterator.from(blueprint.app).map((environment) => [`check:app:${environment}`, `tsc --noEmit -p configs/app/tsconfig.${environment}.json`]).toArray()),
6118
+ ...Object.fromEntries(application.map((name) => [`check:app:${name}`, `${FRAMEWORK_MATRIX[name].checker} --noEmit -p configs/app/tsconfig.${name}.json`]))
6119
+ } : {},
6120
+ ...blueprint.skills ? { "check:skills": `tsc --noEmit -p ${SKILLS_CONFIG_PATH}` } : {},
6121
+ test: [
6122
+ ...compiles ? ["npm run test:src"] : [],
6123
+ ...blueprint.app.length > 0 ? ["npm run test:app"] : [],
6124
+ ...blueprint.journey && blueprint.app.includes("browser") ? ["npm run test:journey", ...application.map((name) => `npm run test:journey:${name}`)] : [],
6125
+ "npm run test:policy",
6126
+ "npm run test:config",
6127
+ ...blueprint.setup.includes("node") || blueprint.global ? ["npm run test:setup"] : [],
6128
+ ...blueprint.setup.includes("browser") || targets.length > 0 ? ["npm run test:setup:browser"] : [],
6129
+ ...blueprint.skills ? ["npm run test:skills"] : [],
6130
+ ...blueprint.guides ? ["npm run test:guides"] : [],
6131
+ ...blueprint.conformance ? ["npm run test:conformance"] : [],
6132
+ ...integrates ? ["npm run test:integration"] : [],
6133
+ ...!publishes && blueprint.studies ? ["npm run test:studies"] : [],
6134
+ ...!publishes && blueprint.service ? ["npm run test:service"] : []
6135
+ ].join(" && "),
6136
+ ...compiles ? {
6137
+ "test:src": [...blueprint.src.length > 0 || blueprint.bin ? [[
6138
+ vitest,
6139
+ ...blueprint.src.map((environment) => `--project ${SRC_MATRIX[environment].project}`),
6140
+ ...source.map((name) => `--project src:${name}`),
6141
+ ...blueprint.bin ? ["--project src:bin"] : []
6142
+ ].join(" ")] : [], ...targets.map((name) => `npm run test:src:${name}`)].join(" && "),
6143
+ ...Object.fromEntries(Iterator.from(blueprint.src).map((environment) => [`test:src:${environment}`, `${vitest} --project ${SRC_MATRIX[environment].project}`]).toArray()),
6144
+ ...blueprint.bin ? { "test:src:bin": `${vitest} --project src:bin` } : {},
6145
+ ...Object.fromEntries(source.map((name) => [`test:src:${name}`, `${vitest} --project src:${name}`])),
6146
+ ...Object.fromEntries(targets.map((name) => [`test:src:${name}`, `npm run build:src:${name} && ${vitest} --project src:${name}`]))
6147
+ } : {},
6148
+ ...blueprint.app.length > 0 ? {
6149
+ "test:app": [
6150
+ vitest,
6151
+ ...blueprint.app.map((environment) => `--project ${APP_MATRIX[environment].project}`),
6152
+ ...application.map((name) => `--project app:${name}`)
6153
+ ].join(" "),
6154
+ ...Object.fromEntries(Iterator.from(blueprint.app).map((environment) => [`test:app:${environment}`, `${vitest} --project ${APP_MATRIX[environment].project}`]).toArray()),
6155
+ ...Object.fromEntries(application.map((name) => [`test:app:${name}`, `${vitest} --project app:${name}`]))
6156
+ } : {},
6157
+ "test:policy": `${vitest} --project policy`,
6158
+ "test:config": `${vitest} --project config`,
6159
+ ...blueprint.setup.includes("node") || blueprint.global ? { "test:setup": `${vitest} --project setup` } : {},
6160
+ ...blueprint.setup.includes("browser") || targets.length > 0 ? { "test:setup:browser": `${vitest} --project setup:browser` } : {},
6161
+ ...blueprint.journey && blueprint.app.includes("browser") ? {
6162
+ "test:journey": journey,
6163
+ ...Object.fromEntries(application.map((name) => [`test:journey:${name}`, `${journey} --mode ${name}`]))
6164
+ } : {},
6165
+ ...blueprint.skills ? { "test:skills": `${vitest} --project skills` } : {},
6166
+ ...blueprint.guides ? { "test:guides": `node --experimental-strip-types ${GUIDES_TEST_PATH}` } : {},
6167
+ ...blueprint.conformance ? { "test:conformance": `${vitest} --project conformance` } : {},
6168
+ "test:probe": "vitest run --config vite.config.ts --no-cache --reporter=verbose --project probe",
6169
+ "test:bench": "vitest bench --config vite.config.ts --no-cache --project probe",
6170
+ ...publishes ? { "test:distribution": `${vitest} --project distribution` } : {},
6171
+ ...integrates ? { "test:integration": `${vitest} --project integration` } : {},
6172
+ ...blueprint.service ? { "test:service": `${vitest} --project service` } : {},
6173
+ ...blueprint.studies ? { "test:studies": `${vitest} --project studies` } : {},
6174
+ build: [
6175
+ "npm run clean",
6176
+ ...compiles ? ["npm run build:src"] : [],
6177
+ ...blueprint.app.length > 0 ? ["npm run build:app"] : []
6178
+ ].join(" && "),
6179
+ ...compiles ? {
6180
+ "build:src": [
6181
+ ...blueprint.src.map((environment) => `npm run build:src:${environment}`),
6182
+ ...source.map((name) => `npm run build:src:${name}`),
6183
+ ...targets.map((name) => `npm run build:src:${name}`),
6184
+ ...blueprint.bin ? ["npm run build:src:bin"] : []
6185
+ ].join(" && "),
6186
+ ...Object.fromEntries(Iterator.from(blueprint.src).map((environment) => [`build:src:${environment}`, SRC_MATRIX[environment].formats.includes("cjs") ? `vite build --config configs/src/vite.${environment}.config.ts && npm run copy dist/src/${environment}/index.d.ts dist/src/${environment}/index.d.cts` : `vite build --config configs/src/vite.${environment}.config.ts`]).toArray()),
6187
+ ...blueprint.bin ? { "build:src:bin": "vite build --config configs/src/vite.bin.config.ts" } : {},
6188
+ ...Object.fromEntries(source.map((name) => [`build:src:${name}`, `vite build --config configs/src/vite.${name}.config.ts`])),
6189
+ ...Object.fromEntries(targets.map((name) => [`build:src:${name}`, [`vite build --config configs/src/vite.${name}.config.ts`, ...name === "styles" && blueprint.themes ? ["vite build --config configs/src/vite.themes.config.ts"] : []].join(" && ")]))
6190
+ } : {},
6191
+ ...blueprint.app.length > 0 ? {
6192
+ "build:app": runtime.length > 0 ? [...runtime, ...application].map((environment) => `npm run build:app:${environment}`).join(" && ") : "npm run check:app:core",
6193
+ ...Object.fromEntries([...runtime, ...application].map((environment) => [`build:app:${environment}`, `vite build --config configs/app/vite.${environment}.config.ts`]))
6194
+ } : {},
6195
+ ...blueprint.app.includes("browser") ? {
6196
+ dev: "vite --config configs/app/vite.browser.config.ts",
6197
+ ...Object.fromEntries(application.map((name) => [`dev:${name}`, `vite --config configs/app/vite.${name}.config.ts`])),
6198
+ ...blueprint.showcase ? {
6199
+ showcase,
6200
+ "build:showcase": buildShowcase,
6201
+ ...Object.fromEntries(application.flatMap((name) => [[`showcase:${name}`, `${showcase} --mode ${name}`], [`build:showcase:${name}`, `${buildShowcase} --mode ${name}`]]))
6202
+ } : {}
6203
+ } : {},
6204
+ ...blueprint.app.includes("server") ? {
6205
+ serve: "node dist/app/server/main.cjs",
6206
+ "serve:build": "npm run build:app:server && npm run serve"
6207
+ } : {},
6208
+ ...publishes ? {
6209
+ prepack: "npm run build",
6210
+ prepublishOnly: [
6211
+ "npm run format:check && npm run lint:check && npm run check && npm run build",
6212
+ ...blueprint.showcase && blueprint.app.includes("browser") ? ["npm run build:showcase", ...application.map((name) => `npm run build:showcase:${name}`)] : [],
6213
+ "npm test",
6214
+ ...blueprint.studies ? ["npm run test:studies"] : [],
6215
+ RELEASE_PROOF_COMMAND,
6216
+ ...blueprint.service ? ["npm run test:service"] : []
6217
+ ].join(" && ")
6218
+ } : {}
5937
6219
  };
5938
- if (checks.length > 0) {
5939
- scripts["check:src"] = checks.join(" && ");
5940
- for (const environment of blueprint.src) scripts[`check:src:${environment}`] = `tsc --noEmit -p configs/src/tsconfig.${environment}.json`;
5941
- if (blueprint.bin) scripts["check:src:bin"] = "tsc --noEmit -p configs/src/tsconfig.bin.json";
5942
- for (const name of [...sheets, ...source]) scripts[`check:src:${name}`] = `tsc --noEmit -p configs/src/tsconfig.${name}.json`;
5943
- }
5944
- if (blueprint.app.length > 0) {
5945
- scripts["check:app"] = [...blueprint.app, ...application].map((environment) => `npm run check:app:${environment}`).join(" && ");
5946
- for (const environment of blueprint.app) scripts[`check:app:${environment}`] = `tsc --noEmit -p configs/app/tsconfig.${environment}.json`;
5947
- for (const name of application) scripts[`check:app:${name}`] = `${FRAMEWORK_MATRIX[name].checker} --noEmit -p configs/app/tsconfig.${name}.json`;
5948
- }
5949
- if (blueprint.skills) scripts["check:skills"] = `tsc --noEmit -p ${SKILLS_CONFIG_PATH}`;
5950
- scripts.test = [
5951
- ...compiles ? ["npm run test:src"] : [],
5952
- ...blueprint.app.length > 0 ? ["npm run test:app"] : [],
5953
- ...blueprint.journey && blueprint.app.includes("browser") ? ["npm run test:journey", ...application.map((name) => `npm run test:journey:${name}`)] : [],
5954
- "npm run test:policy",
5955
- "npm run test:config",
5956
- ...blueprint.setup.includes("node") || blueprint.global ? ["npm run test:setup"] : [],
5957
- ...blueprint.setup.includes("browser") || targets.length > 0 ? ["npm run test:setup:browser"] : [],
5958
- ...blueprint.skills ? ["npm run test:skills"] : [],
5959
- ...blueprint.guides ? ["npm run test:guides"] : [],
5960
- ...blueprint.conformance ? ["npm run test:conformance"] : [],
5961
- ...integrates ? ["npm run test:integration"] : [],
5962
- ...!publishes && blueprint.service ? ["npm run test:service"] : []
5963
- ].join(" && ");
5964
- if (compiles) {
5965
- scripts["test:src"] = [
5966
- vitest,
5967
- ...blueprint.src.map((environment) => `--project ${SRC_MATRIX[environment].project}`),
5968
- ...source.map((name) => `--project src:${name}`),
5969
- ...blueprint.bin ? ["--project src:bin"] : []
5970
- ].join(" ");
5971
- for (const environment of blueprint.src) scripts[`test:src:${environment}`] = `${vitest} --project ${SRC_MATRIX[environment].project}`;
5972
- if (blueprint.bin) scripts["test:src:bin"] = `${vitest} --project src:bin`;
5973
- for (const name of source) scripts[`test:src:${name}`] = `${vitest} --project src:${name}`;
5974
- scripts["test:src"] = [...blueprint.src.length > 0 || blueprint.bin ? [scripts["test:src"]] : [], ...targets.map((name) => `npm run test:src:${name}`)].join(" && ");
5975
- for (const name of targets) scripts[`test:src:${name}`] = `npm run build:src:${name} && ${vitest} --project src:${name}`;
5976
- }
5977
- if (blueprint.app.length > 0) {
5978
- scripts["test:app"] = [
5979
- vitest,
5980
- ...blueprint.app.map((environment) => `--project ${APP_MATRIX[environment].project}`),
5981
- ...application.map((name) => `--project app:${name}`)
5982
- ].join(" ");
5983
- for (const environment of blueprint.app) scripts[`test:app:${environment}`] = `${vitest} --project ${APP_MATRIX[environment].project}`;
5984
- for (const name of application) scripts[`test:app:${name}`] = `${vitest} --project app:${name}`;
5985
- }
5986
- scripts["test:policy"] = `${vitest} --project policy`;
5987
- scripts["test:config"] = `${vitest} --project config`;
5988
- if (blueprint.setup.includes("node") || blueprint.global) scripts["test:setup"] = `${vitest} --project setup`;
5989
- if (blueprint.setup.includes("browser") || targets.length > 0) scripts["test:setup:browser"] = `${vitest} --project setup:browser`;
5990
- if (blueprint.journey && blueprint.app.includes("browser")) {
5991
- scripts["test:journey"] = `vitest run --config ${JOURNEY_CONFIG_PATH} --no-cache --reporter=dot`;
5992
- for (const name of application) scripts[`test:journey:${name}`] = `${scripts["test:journey"]} --mode ${name}`;
5993
- }
5994
- if (blueprint.skills) scripts["test:skills"] = `${vitest} --project skills`;
5995
- if (blueprint.guides) scripts["test:guides"] = `node --experimental-strip-types ${GUIDES_TEST_PATH}`;
5996
- if (blueprint.conformance) scripts["test:conformance"] = `${vitest} --project conformance`;
5997
- scripts["test:probe"] = "vitest run --config vite.config.ts --no-cache --reporter=verbose --project probe";
5998
- scripts["test:bench"] = "vitest bench --config vite.config.ts --no-cache --project probe";
5999
- if (publishes) scripts["test:distribution"] = `${vitest} --project distribution`;
6000
- if (integrates) scripts["test:integration"] = `${vitest} --project integration`;
6001
- if (blueprint.service) scripts["test:service"] = `${vitest} --project service`;
6002
- scripts.build = [
6003
- "npm run clean",
6004
- ...compiles ? ["npm run build:src"] : [],
6005
- ...blueprint.app.length > 0 ? ["npm run build:app"] : []
6006
- ].join(" && ");
6007
- if (compiles) {
6008
- scripts["build:src"] = [
6009
- ...blueprint.src.map((environment) => `npm run build:src:${environment}`),
6010
- ...source.map((name) => `npm run build:src:${name}`),
6011
- ...targets.map((name) => `npm run build:src:${name}`),
6012
- ...blueprint.bin ? ["npm run build:src:bin"] : []
6013
- ].join(" && ");
6014
- for (const environment of blueprint.src) {
6015
- const build = `vite build --config configs/src/vite.${environment}.config.ts`;
6016
- scripts[`build:src:${environment}`] = SRC_MATRIX[environment].formats.includes("cjs") ? `${build} && npm run copy dist/src/${environment}/index.d.ts dist/src/${environment}/index.d.cts` : build;
6017
- }
6018
- if (blueprint.bin) scripts["build:src:bin"] = "vite build --config configs/src/vite.bin.config.ts";
6019
- for (const name of source) scripts[`build:src:${name}`] = `vite build --config configs/src/vite.${name}.config.ts`;
6020
- for (const name of targets) scripts[`build:src:${name}`] = [`vite build --config configs/src/vite.${name}.config.ts`, ...name === "styles" && blueprint.themes ? ["vite build --config configs/src/vite.themes.config.ts"] : []].join(" && ");
6021
- }
6022
- if (blueprint.app.length > 0) {
6023
- scripts["build:app"] = runtime.length > 0 ? [...runtime, ...application].map((environment) => `npm run build:app:${environment}`).join(" && ") : "npm run check:app:core";
6024
- for (const environment of [...runtime, ...application]) scripts[`build:app:${environment}`] = `vite build --config configs/app/vite.${environment}.config.ts`;
6025
- }
6026
- if (blueprint.app.includes("browser")) {
6027
- scripts.dev = "vite --config configs/app/vite.browser.config.ts";
6028
- for (const name of application) scripts[`dev:${name}`] = `vite --config configs/app/vite.${name}.config.ts`;
6029
- if (blueprint.showcase) {
6030
- scripts.showcase = `vite --config ${SHOWCASE_CONFIG_PATH}`;
6031
- scripts["build:showcase"] = `vite build --config ${SHOWCASE_CONFIG_PATH}`;
6032
- for (const name of application) {
6033
- scripts[`showcase:${name}`] = `${scripts.showcase} --mode ${name}`;
6034
- scripts[`build:showcase:${name}`] = `${scripts["build:showcase"]} --mode ${name}`;
6035
- }
6036
- }
6037
- }
6038
- if (blueprint.app.includes("server")) {
6039
- scripts.serve = "node dist/app/server/main.cjs";
6040
- scripts["serve:build"] = "npm run build:app:server && npm run serve";
6041
- }
6042
- if (publishes) {
6043
- scripts.prepack = "npm run build";
6044
- scripts.prepublishOnly = [
6045
- "npm run format:check && npm run lint:check && npm run check && npm run build",
6046
- ...blueprint.showcase && blueprint.app.includes("browser") ? ["npm run build:showcase", ...application.map((name) => `npm run build:showcase:${name}`)] : [],
6047
- "npm test",
6048
- RELEASE_PROOF_COMMAND,
6049
- ...blueprint.service ? ["npm run test:service"] : []
6050
- ].join(" && ");
6051
- }
6052
- return scripts;
6053
6220
  }
6054
6221
  /**
6055
6222
  * Projects a blueprint into the manifest scripts a region write may replace.
@@ -6071,7 +6238,9 @@ function blueprintToScripts(blueprint) {
6071
6238
  * guides entry accepts the generated command that invoked the guides project
6072
6239
  * through Vitest alone. The pack hook accepts the build chain emitted before it
6073
6240
  * delegated to `build`. The publication hook accepts the same gate chain without
6074
- * {@link RELEASE_PROOF_COMMAND}. The value being written is always writable, so
6241
+ * {@link RELEASE_PROOF_COMMAND}. A workspace carrying studies also accepts the
6242
+ * chain without its study step and the chain without both its study step and
6243
+ * the release proof. The value being written is always writable, so
6075
6244
  * it is not repeated there. Any other value is a script the workspace author
6076
6245
  * customized, and {@link replaceManifestScripts} retains it while writing the
6077
6246
  * other named scripts independently.
@@ -6088,35 +6257,38 @@ function blueprintToScripts(blueprint) {
6088
6257
  function blueprintToWritableScripts(blueprint) {
6089
6258
  const scripts = blueprintToScripts(blueprint);
6090
6259
  const sheets = [...blueprintToSheets(blueprint), ...blueprint.themes && !blueprint.styles ? ["themes"] : []];
6091
- const writable = [];
6092
- for (const [name, command] of Object.entries(scripts)) {
6260
+ const body = Object.entries(scripts).flatMap(([name, command]) => {
6093
6261
  const direct = name.startsWith("test:") && name !== "test:src" && name !== "test:app";
6094
6262
  const sheet = sheets.some((face) => name === `build:src:${face}` || name === `check:src:${face}`);
6095
6263
  const framework = blueprintToFaces(blueprint).some((extension) => extension.surface === "browser" && extension.axes.some((axis) => name === `check:${axis}:${extension.name}` || name === `build:${axis}:${extension.name}` || axis === "app" && name === `dev:${extension.name}`));
6096
6264
  const showcase = name === "showcase" || name.startsWith("showcase:") || name === "build:showcase" || name.startsWith("build:showcase:");
6097
- if (!direct && !sheet && !framework && !showcase) continue;
6098
- writable.push({
6265
+ if (!direct && !sheet && !framework && !showcase) return [];
6266
+ return [{
6099
6267
  name,
6100
6268
  command,
6101
6269
  accepted: name === "test:guides" ? ["vitest run --config vite.config.ts --no-cache --reporter=dot --project guides"] : name === "build:src:styles" && blueprint.themes ? ["vite build --config configs/src/vite.styles.config.ts"] : sheets.some((face) => name === `test:src:${face}`) ? [command.slice(command.indexOf(" && ") + 4)] : []
6102
- });
6103
- }
6270
+ }];
6271
+ });
6104
6272
  const prepack = scripts.prepack;
6105
- if (prepack !== void 0) {
6106
- const predecessor = scripts.build;
6107
- writable.push({
6273
+ const prepublish = scripts.prepublishOnly;
6274
+ const unreleased = prepublish?.replace(` && ${RELEASE_PROOF_COMMAND}`, "");
6275
+ return [
6276
+ ...body,
6277
+ ...prepack === void 0 ? [] : [{
6108
6278
  name: "prepack",
6109
6279
  command: prepack,
6110
- accepted: predecessor === void 0 ? [] : [predecessor]
6111
- });
6112
- }
6113
- const prepublish = scripts.prepublishOnly;
6114
- if (prepublish !== void 0) writable.push({
6115
- name: "prepublishOnly",
6116
- command: prepublish,
6117
- accepted: [prepublish.replace(` && ${RELEASE_PROOF_COMMAND}`, "")]
6118
- });
6119
- return writable;
6280
+ accepted: scripts.build === void 0 ? [] : [scripts.build]
6281
+ }],
6282
+ ...prepublish === void 0 || unreleased === void 0 ? [] : [{
6283
+ name: "prepublishOnly",
6284
+ command: prepublish,
6285
+ accepted: blueprint.studies ? [
6286
+ unreleased,
6287
+ prepublish.replace(" && npm run test:studies", ""),
6288
+ unreleased.replace(" && npm run test:studies", "")
6289
+ ] : [unreleased]
6290
+ }]
6291
+ ];
6120
6292
  }
6121
6293
  /**
6122
6294
  * Compiles a blueprint into its `package.json` content.
@@ -6158,14 +6330,10 @@ function blueprintToManifest(blueprint) {
6158
6330
  const targets = [...sheets, ...blueprint.themes ? ["styles/themes"] : []];
6159
6331
  const publishes = blueprint.src.length > 0 || targets.length > 0;
6160
6332
  const name = publishes ? `@orkestrel/${blueprint.name}` : blueprint.name;
6161
- const dependencies = {};
6162
- for (const dependency of [...blueprint.dependencies].sort((left, right) => compareValues(left.name, right.name))) dependencies[dependency.name] = dependency.range;
6163
- const peerDependencies = {};
6164
- const peerDependenciesMeta = {};
6165
- for (const peer of [...blueprint.peers].sort((left, right) => compareValues(left.name, right.name))) {
6166
- peerDependencies[peer.name] = peer.range;
6167
- if (peer.optional === true) peerDependenciesMeta[peer.name] = { optional: true };
6168
- }
6333
+ const dependencies = Object.fromEntries([...blueprint.dependencies].sort((left, right) => compareValues(left.name, right.name)).map((dependency) => [dependency.name, dependency.range]));
6334
+ const peers = [...blueprint.peers].sort((left, right) => compareValues(left.name, right.name));
6335
+ const peerDependencies = Object.fromEntries(peers.map((peer) => [peer.name, peer.range]));
6336
+ const peerDependenciesMeta = Object.fromEntries(peers.filter((peer) => peer.optional === true).map((peer) => [peer.name, { optional: true }]));
6169
6337
  const entry = srcToEntry(blueprint.src);
6170
6338
  const manifest = {
6171
6339
  name,
@@ -6277,23 +6445,37 @@ function blueprintToMachinery(blueprint) {
6277
6445
  * ```
6278
6446
  */
6279
6447
  function blueprintToRootTsconfig(blueprint) {
6280
- const aliases = [];
6281
- for (const extension of blueprintToFaces(blueprint)) {
6282
- if (extension.surface !== "browser") continue;
6283
- for (const axis of extension.axes) aliases.push([`@${axis}/${extension.name}`, `./${axis}/${extension.name}/index.ts`]);
6284
- if (extension.axes.includes("src")) aliases.push([`@orkestrel/${blueprint.name}/${extension.name}`, `./src/${extension.name}/index.ts`]);
6285
- }
6286
- for (const name of blueprintToSheets(blueprint)) aliases.push([`@src/${name}`, `./src/${name}/index.ts`]);
6287
- for (const environment of ENVIRONMENTS) if (blueprint.src.includes(environment)) aliases.push([`@src/${environment}`, `./src/${environment}/index.ts`]);
6288
- for (const environment of ENVIRONMENTS) if (blueprint.app.includes(environment)) aliases.push([`@app/${environment}`, `./app/${environment}/index.ts`]);
6289
6448
  const root = srcToRoot(blueprint.src);
6290
- if (root === void 0) for (const environment of ENVIRONMENTS) {
6291
- if (environment === "core" || !blueprint.src.includes(environment)) continue;
6292
- const subpath = SRC_MATRIX[environment].path.slice(1);
6293
- aliases.push([`@orkestrel/${blueprint.name}${subpath}`, `./src/${environment}/index.ts`]);
6294
- }
6295
- if (blueprint.src.length > 0) aliases.push([`@orkestrel/${blueprint.name}`, `./src/${root ?? "core"}/index.ts`]);
6296
- const paths = aliases.map(([specifier, target], index) => {
6449
+ const aliases = [
6450
+ ...blueprintToFaces(blueprint).flatMap((extension) => [...extension.axes.map((axis) => ({
6451
+ specifier: `@${axis}/${extension.name}`,
6452
+ target: `./${axis}/${extension.name}/index.ts`
6453
+ })), ...extension.axes.includes("src") ? [{
6454
+ specifier: `@orkestrel/${blueprint.name}/${extension.name}`,
6455
+ target: `./src/${extension.name}/index.ts`
6456
+ }] : []]),
6457
+ ...blueprintToSheets(blueprint).map((name) => ({
6458
+ specifier: `@src/${name}`,
6459
+ target: `./src/${name}/index.ts`
6460
+ })),
6461
+ ...ENVIRONMENTS.filter((environment) => blueprint.src.includes(environment)).map((environment) => ({
6462
+ specifier: `@src/${environment}`,
6463
+ target: `./src/${environment}/index.ts`
6464
+ })),
6465
+ ...ENVIRONMENTS.filter((environment) => blueprint.app.includes(environment)).map((environment) => ({
6466
+ specifier: `@app/${environment}`,
6467
+ target: `./app/${environment}/index.ts`
6468
+ })),
6469
+ ...root === void 0 ? ENVIRONMENTS.filter((environment) => environment !== "core" && blueprint.src.includes(environment)).map((environment) => ({
6470
+ specifier: `@orkestrel/${blueprint.name}${SRC_MATRIX[environment].path.slice(1)}`,
6471
+ target: `./src/${environment}/index.ts`
6472
+ })) : [],
6473
+ ...blueprint.src.length > 0 ? [{
6474
+ specifier: `@orkestrel/${blueprint.name}`,
6475
+ target: `./src/${root ?? "core"}/index.ts`
6476
+ }] : []
6477
+ ];
6478
+ const paths = aliases.map(({ specifier, target }, index) => {
6297
6479
  const separator = index === aliases.length - 1 ? "" : ",";
6298
6480
  const line = `\t\t\t"${specifier}": ["${target}"]${separator}`;
6299
6481
  if (matchesPrintWidth(line)) return line;
@@ -6318,28 +6500,28 @@ function blueprintToProjects(blueprint) {
6318
6500
  const sheets = blueprintToSheets(blueprint);
6319
6501
  const styled = sheets.length > 0 || blueprint.themes;
6320
6502
  const faces = blueprintToFaces(blueprint);
6321
- const projects = sheets.map((name) => `src:${name}`);
6322
- if (blueprint.themes && !blueprint.styles) projects.push("src:themes");
6323
- for (const axis of AXES) {
6324
- for (const environment of ENVIRONMENTS) {
6325
- if (axis === "app" && environment === "server") continue;
6326
- if (blueprint[axis].includes(environment)) projects.push(`${axis}:${environment}`);
6327
- }
6328
- for (const face of faces) if (face.surface === "browser" && face.axes.includes(axis)) projects.push(`${axis}:${face.name}`);
6329
- if (axis === "src" && blueprint.bin) projects.push("src:bin");
6330
- if (axis === "app" && blueprint.app.includes("server")) projects.push("app:server");
6331
- }
6332
- projects.push("policy", "config");
6333
- if (blueprint.setup.includes("node") || blueprint.global) projects.push("setup");
6334
- if (blueprint.setup.includes("browser") || styled) projects.push("setup:browser");
6335
- if (blueprint.guides) projects.push("guides");
6336
- if (blueprint.skills) projects.push("skills");
6337
- if (blueprint.conformance) projects.push("conformance");
6338
- if (blueprint.service) projects.push("service");
6339
- if (blueprint.src.length > 0 || styled) projects.push("distribution");
6340
- if (blueprint.integration) projects.push("integration");
6341
- projects.push("probe");
6342
- return projects;
6503
+ return [
6504
+ ...sheets.map((name) => `src:${name}`),
6505
+ ...blueprint.themes && !blueprint.styles ? ["src:themes"] : [],
6506
+ ...AXES.flatMap((axis) => [
6507
+ ...ENVIRONMENTS.filter((environment) => (axis !== "app" || environment !== "server") && blueprint[axis].includes(environment)).map((environment) => `${axis}:${environment}`),
6508
+ ...faces.filter((face) => face.axes.includes(axis)).map((face) => `${axis}:${face.name}`),
6509
+ ...axis === "src" && blueprint.bin ? ["src:bin"] : [],
6510
+ ...axis === "app" && blueprint.app.includes("server") ? ["app:server"] : []
6511
+ ]),
6512
+ "policy",
6513
+ "config",
6514
+ ...blueprint.setup.includes("node") || blueprint.global ? ["setup"] : [],
6515
+ ...blueprint.setup.includes("browser") || styled ? ["setup:browser"] : [],
6516
+ ...blueprint.guides ? ["guides"] : [],
6517
+ ...blueprint.skills ? ["skills"] : [],
6518
+ ...blueprint.conformance ? ["conformance"] : [],
6519
+ ...blueprint.service ? ["service"] : [],
6520
+ ...blueprint.studies ? ["studies"] : [],
6521
+ ...blueprint.src.length > 0 || styled ? ["distribution"] : [],
6522
+ ...blueprint.integration ? ["integration"] : [],
6523
+ "probe"
6524
+ ];
6343
6525
  }
6344
6526
  /**
6345
6527
  * Compiles the browser projects' pre-bundled dependency list for a blueprint.
@@ -6398,18 +6580,18 @@ function blueprintToRootVite(blueprint) {
6398
6580
  const sheets = blueprintToSheets(blueprint);
6399
6581
  const styled = sheets.length > 0 || blueprint.themes;
6400
6582
  const global = blueprint.global ? " globalSetup: ['./tests/setupGlobal.ts'],\n" : "";
6401
- const imports = [];
6402
- if (machinery.browser) imports.push("import { playwright } from '@vitest/browser-playwright'");
6403
- if (machinery.frameworks.includes("vue")) imports.push("import vue from '@vitejs/plugin-vue'");
6404
- if (machinery.showcase) imports.push("import { viteSingleFile } from 'vite-plugin-singlefile'");
6405
- if (machinery.showcase) {
6406
- imports.push("import { renameSync } from 'node:fs'");
6407
- imports.push("import { resolve as resolvePath } from 'node:path'");
6408
- }
6409
- const factories = [];
6410
- const projects = [];
6583
+ const imports = [
6584
+ ...machinery.browser ? ["import { playwright } from '@vitest/browser-playwright'"] : [],
6585
+ ...machinery.frameworks.includes("vue") ? ["import vue from '@vitejs/plugin-vue'"] : [],
6586
+ ...machinery.showcase ? [
6587
+ "import { viteSingleFile } from 'vite-plugin-singlefile'",
6588
+ "import { renameSync } from 'node:fs'",
6589
+ "import { resolve as resolvePath } from 'node:path'"
6590
+ ] : []
6591
+ ];
6411
6592
  const external = "external: resolveSourceExternal,";
6412
- if (blueprint.src.includes("browser") || blueprint.src.includes("server")) factories.push(blueprint.src.includes("core") ? `function resolveSourceExternal(id: string): boolean {
6593
+ const factories = [
6594
+ ...blueprint.src.includes("browser") || blueprint.src.includes("server") ? [blueprint.src.includes("core") ? `function resolveSourceExternal(id: string): boolean {
6413
6595
  return (
6414
6596
  id === '@src/core' ||
6415
6597
  resolveExternal(id, {
@@ -6422,29 +6604,16 @@ function blueprintToRootVite(blueprint) {
6422
6604
  ` : `function resolveSourceExternal(id: string): boolean {
6423
6605
  return resolveExternal(id, { peers, refused: [], siblings: [] })
6424
6606
  }
6425
- `);
6426
- if (styled) {
6427
- factories.push(CONFIG_TEMPLATES.factories.sheet);
6428
- for (const name of sheets) projects.push(`'./configs/src/vite.${name}.config.ts'`);
6429
- if (planned.has("src:themes")) projects.push("'./configs/src/vite.themes.config.ts'");
6430
- }
6431
- if (planned.has("src:core")) {
6432
- factories.push(CONFIG_TEMPLATES.factories.src.core);
6433
- projects.push("srcCore");
6434
- }
6435
- if (planned.has("src:browser")) {
6436
- const core = blueprint.src.includes("core");
6437
- factories.push(fillTemplate(CONFIG_TEMPLATES.factories.src.browser, {
6607
+ `] : [],
6608
+ ...styled ? [CONFIG_TEMPLATES.factories.sheet] : [],
6609
+ ...planned.has("src:core") ? [CONFIG_TEMPLATES.factories.src.core] : [],
6610
+ ...planned.has("src:browser") ? [blueprint.src.includes("core")].map((core) => fillTemplate(CONFIG_TEMPLATES.factories.src.browser, {
6438
6611
  external,
6439
6612
  output: core ? " output: { paths: { '@src/core': '../core/index.js' } }," : " output: {},",
6440
6613
  exclude: core ? " exclude: ['tests/src/core/**/*.test.ts'],\n" : "",
6441
6614
  global
6442
- }));
6443
- projects.push("srcBrowser");
6444
- }
6445
- if (planned.has("src:server")) {
6446
- const core = blueprint.src.includes("core");
6447
- factories.push(fillTemplate(CONFIG_TEMPLATES.factories.src.server, {
6615
+ })) : [],
6616
+ ...planned.has("src:server") ? [blueprint.src.includes("core")].map((core) => fillTemplate(CONFIG_TEMPLATES.factories.src.server, {
6448
6617
  external,
6449
6618
  output: core ? `\t\t\t\toutput: [
6450
6619
  {
@@ -6459,80 +6628,58 @@ function blueprintToRootVite(blueprint) {
6459
6628
  },
6460
6629
  ],` : " output: {},",
6461
6630
  exclude: core ? " exclude: ['tests/src/core/**/*.test.ts'],\n" : ""
6462
- }));
6463
- projects.push("srcServer");
6464
- }
6465
- if (planned.has("src:vue")) {
6466
- factories.push(CONFIG_TEMPLATES.factories.src.vue);
6467
- projects.push("srcVue");
6468
- }
6469
- if (planned.has("src:bin")) {
6470
- factories.push(CONFIG_TEMPLATES.factories.src.bin);
6471
- projects.push("srcBin");
6472
- }
6473
- if (planned.has("app:core")) {
6474
- factories.push(CONFIG_TEMPLATES.factories.app.core);
6475
- projects.push("appCore");
6476
- }
6477
- if (planned.has("app:browser")) {
6478
- const showcaseFactory = machinery.showcase ? CONFIG_TEMPLATES.factories.app.showcase : "";
6479
- factories.push(fillTemplate(CONFIG_TEMPLATES.factories.app.browser, {
6480
- showcaseFactory,
6631
+ })) : [],
6632
+ ...planned.has("src:vue") ? [CONFIG_TEMPLATES.factories.src.vue] : [],
6633
+ ...planned.has("src:bin") ? [CONFIG_TEMPLATES.factories.src.bin] : [],
6634
+ ...planned.has("app:core") ? [CONFIG_TEMPLATES.factories.app.core] : [],
6635
+ ...planned.has("app:browser") ? [fillTemplate(CONFIG_TEMPLATES.factories.app.browser, {
6636
+ showcaseFactory: machinery.showcase ? CONFIG_TEMPLATES.factories.app.showcase : "",
6481
6637
  journeyFactory: blueprint.journey ? CONFIG_TEMPLATES.factories.app.journey : "",
6482
6638
  journeyExclude: blueprint.journey ? " exclude: ['tests/app/browser/integration.test.ts'],\n" : ""
6483
- }));
6484
- projects.push("appBrowser");
6485
- }
6486
- if (planned.has("app:vue")) {
6487
- factories.push(fillTemplate(CONFIG_TEMPLATES.factories.app.vue, { journeyExclude: blueprint.journey ? " exclude: ['tests/app/vue/integration.test.ts'],\n" : " exclude: [],\n" }));
6488
- projects.push("appVue");
6489
- }
6490
- if (planned.has("app:server")) {
6491
- factories.push(CONFIG_TEMPLATES.factories.app.server);
6492
- projects.push("appServer");
6493
- }
6494
- factories.push(CONFIG_TEMPLATES.factories.policy);
6495
- projects.push("policy");
6496
- factories.push(CONFIG_TEMPLATES.factories.config);
6497
- projects.push("config");
6498
- if (planned.has("setup")) {
6499
- factories.push(CONFIG_TEMPLATES.factories.setup);
6500
- projects.push("setup");
6501
- }
6502
- if (planned.has("setup:browser")) {
6503
- factories.push(fillTemplate(CONFIG_TEMPLATES.factories.browser, {
6639
+ })] : [],
6640
+ ...planned.has("app:vue") ? [fillTemplate(CONFIG_TEMPLATES.factories.app.vue, { journeyExclude: blueprint.journey ? " exclude: ['tests/app/vue/integration.test.ts'],\n" : " exclude: [],\n" })] : [],
6641
+ ...planned.has("app:server") ? [CONFIG_TEMPLATES.factories.app.server] : [],
6642
+ CONFIG_TEMPLATES.factories.policy,
6643
+ CONFIG_TEMPLATES.factories.config,
6644
+ ...planned.has("setup") ? [CONFIG_TEMPLATES.factories.setup] : [],
6645
+ ...planned.has("setup:browser") ? [fillTemplate(CONFIG_TEMPLATES.factories.browser, {
6504
6646
  optimization: machinery.frameworks.includes("vue") ? "optimizeDeps: { include: [...optimizeDeps.include, 'vue'] }" : "optimizeDeps",
6505
6647
  plugins: machinery.frameworks.includes("vue") ? " plugins: [vue()],\n" : "",
6506
6648
  global
6507
- }));
6508
- projects.push("setupBrowser");
6509
- }
6510
- if (planned.has("guides")) {
6511
- factories.push(CONFIG_TEMPLATES.factories.guides);
6512
- projects.push("guides");
6513
- }
6514
- if (planned.has("skills")) {
6515
- factories.push(CONFIG_TEMPLATES.factories.skills);
6516
- projects.push("skills");
6517
- }
6518
- if (planned.has("conformance")) {
6519
- factories.push(CONFIG_TEMPLATES.factories.conformance);
6520
- projects.push("conformance");
6521
- }
6522
- if (planned.has("service")) {
6523
- factories.push(CONFIG_TEMPLATES.factories.service);
6524
- projects.push("service");
6525
- }
6526
- if (planned.has("distribution")) {
6527
- factories.push(CONFIG_TEMPLATES.factories.distribution);
6528
- projects.push("distribution");
6529
- }
6530
- if (planned.has("integration")) {
6531
- factories.push(fillTemplate(styled ? CONFIG_TEMPLATES.factories.integration.sheet : CONFIG_TEMPLATES.factories.integration.module, { global }));
6532
- projects.push("integration");
6533
- }
6534
- factories.push(CONFIG_TEMPLATES.factories.probe);
6535
- projects.push("probe");
6649
+ })] : [],
6650
+ ...planned.has("guides") ? [CONFIG_TEMPLATES.factories.guides] : [],
6651
+ ...planned.has("skills") ? [CONFIG_TEMPLATES.factories.skills] : [],
6652
+ ...planned.has("conformance") ? [CONFIG_TEMPLATES.factories.conformance] : [],
6653
+ ...planned.has("service") ? [CONFIG_TEMPLATES.factories.service] : [],
6654
+ ...planned.has("studies") ? [CONFIG_TEMPLATES.factories.studies] : [],
6655
+ ...planned.has("distribution") ? [CONFIG_TEMPLATES.factories.distribution] : [],
6656
+ ...planned.has("integration") ? [fillTemplate(styled ? CONFIG_TEMPLATES.factories.integration.sheet : CONFIG_TEMPLATES.factories.integration.module, { global })] : [],
6657
+ CONFIG_TEMPLATES.factories.probe
6658
+ ];
6659
+ const projects = [
6660
+ ...styled ? [...sheets.map((name) => `'./configs/src/vite.${name}.config.ts'`), ...planned.has("src:themes") ? ["'./configs/src/vite.themes.config.ts'"] : []] : [],
6661
+ ...planned.has("src:core") ? ["srcCore"] : [],
6662
+ ...planned.has("src:browser") ? ["srcBrowser"] : [],
6663
+ ...planned.has("src:server") ? ["srcServer"] : [],
6664
+ ...planned.has("src:vue") ? ["srcVue"] : [],
6665
+ ...planned.has("src:bin") ? ["srcBin"] : [],
6666
+ ...planned.has("app:core") ? ["appCore"] : [],
6667
+ ...planned.has("app:browser") ? ["appBrowser"] : [],
6668
+ ...planned.has("app:vue") ? ["appVue"] : [],
6669
+ ...planned.has("app:server") ? ["appServer"] : [],
6670
+ "policy",
6671
+ "config",
6672
+ ...planned.has("setup") ? ["setup"] : [],
6673
+ ...planned.has("setup:browser") ? ["setupBrowser"] : [],
6674
+ ...planned.has("guides") ? ["guides"] : [],
6675
+ ...planned.has("skills") ? ["skills"] : [],
6676
+ ...planned.has("conformance") ? ["conformance"] : [],
6677
+ ...planned.has("service") ? ["service"] : [],
6678
+ ...planned.has("studies") ? ["studies"] : [],
6679
+ ...planned.has("distribution") ? ["distribution"] : [],
6680
+ ...planned.has("integration") ? ["integration"] : [],
6681
+ "probe"
6682
+ ];
6536
6683
  const joinedProjects = `\t\tprojects: [${projects.join(", ")}],`;
6537
6684
  const projectRows = matchesPrintWidth(joinedProjects) ? joinedProjects : `\t\tprojects: [
6538
6685
  ${projects.map((project) => `\t\t\t${project},`).join("\n")}
@@ -6551,7 +6698,7 @@ ${projects.map((project) => `\t\t\t${project},`).join("\n")}
6551
6698
  journey: blueprint.journey && blueprint.app.includes("browser") ? "import type { JourneyVariant } from '@orkestrel/test'\n" : "",
6552
6699
  imports: imports.length === 0 ? "" : `${imports.join("\n")}\n`,
6553
6700
  helpers: helpers.length === 0 ? "" : matchesPrintWidth(helperImport.trimEnd()) ? helperImport : `import {\n${helpers.map((helper) => `\t${helper},`).join("\n")}\n} from './configs/helpers.js'\n`,
6554
- browsers: machinery.browser ? "import { resolveBrowser, resolvePinnedBrowser } from './configs/browsers.js'\n" : "",
6701
+ browsers: machinery.browser ? "import { resolveBrowser, resolvePinnedBrowser } from './configs/browsers.js'\nimport { commands } from './configs/commands.js'\n" : "",
6555
6702
  options: (machinery.browser ? "const browserOptions = resolveBrowser(resolvePinnedBrowser(), process.platform, process.env)\n\n" + blueprintToOptimizedDependencies(blueprint) : "") + (blueprint.journey && blueprint.app.includes("browser") ? "const capture = process.env.CAPTURE === '1'\n\n" : "") + ((blueprint.journey || blueprint.showcase) && blueprint.app.includes("browser") ? `const applications = {\n\tbrowser: appBrowser,\n${blueprintToFaces(blueprint).filter((extension) => extension.surface === "browser" && extension.axes.includes("app")).map((extension) => `\t${extension.name}: app${extension.name[0]?.toUpperCase()}${extension.name.slice(1)},\n`).join("")}}\n\n` : ""),
6556
6703
  factories: body,
6557
6704
  projects: projectRows
@@ -6573,7 +6720,7 @@ ${projects.map((project) => `\t\t\t${project},`).join("\n")}
6573
6720
  * ```
6574
6721
  */
6575
6722
  function blueprintToConfigArtifacts(blueprint) {
6576
- const artifacts = [{
6723
+ const roots = [{
6577
6724
  path: "tsconfig.json",
6578
6725
  group: "configs",
6579
6726
  ownership: "content",
@@ -6586,172 +6733,167 @@ function blueprintToConfigArtifacts(blueprint) {
6586
6733
  origin: "template",
6587
6734
  content: blueprintToRootVite(blueprint)
6588
6735
  }];
6589
- if (blueprintToMachinery(blueprint).browser) artifacts.push({
6736
+ const browsers = blueprintToMachinery(blueprint).browser ? [{
6590
6737
  path: "configs/browsers.ts",
6591
6738
  group: "configs",
6592
6739
  ownership: "content",
6593
6740
  origin: "template",
6594
6741
  content: CONFIG_TEMPLATES.browsers
6595
- });
6596
- for (const name of [...blueprintToSheets(blueprint), ...blueprint.themes ? ["styles/themes"] : []]) {
6597
- const label = name === "styles/themes" ? "themes" : name;
6598
- artifacts.push({
6599
- path: `configs/src/vite.${label}.config.ts`,
6742
+ }, {
6743
+ path: "configs/commands.ts",
6744
+ group: "configs",
6745
+ ownership: "presence",
6746
+ origin: "template",
6747
+ content: CONFIG_TEMPLATES.commands
6748
+ }] : [];
6749
+ const sheets = [...blueprintToSheets(blueprint), ...blueprint.themes ? ["styles/themes"] : []].flatMap((name) => {
6750
+ const wrapper = {
6751
+ path: `configs/src/vite.${name === "styles/themes" ? "themes" : name}.config.ts`,
6600
6752
  group: "configs",
6601
6753
  ownership: "content",
6602
6754
  origin: "template",
6603
6755
  content: fillTemplate(CONFIG_TEMPLATES.vites.sheet, {
6604
6756
  name,
6605
- label
6757
+ label: name === "styles/themes" ? "themes" : name
6606
6758
  })
6607
- });
6608
- if (name !== "styles/themes") artifacts.push({
6759
+ };
6760
+ return name === "styles/themes" ? [wrapper] : [wrapper, {
6609
6761
  path: `configs/src/tsconfig.${name}.json`,
6610
6762
  group: "configs",
6611
6763
  ownership: "content",
6612
6764
  origin: "template",
6613
6765
  content: fillTemplate(CONFIG_TEMPLATES.tsconfigs.sheet, { name })
6614
- });
6615
- }
6616
- for (const environment of blueprint.src) for (const path of SRC_MATRIX[environment].configs) {
6617
- let content = CONFIG_TEMPLATES.vites.src.core;
6618
- if (path === "configs/src/tsconfig.core.json") content = CONFIG_TEMPLATES.tsconfigs.src.core;
6619
- else if (path === "configs/src/vite.browser.config.ts") content = CONFIG_TEMPLATES.vites.src.browser;
6620
- else if (path === "configs/src/tsconfig.browser.json") content = CONFIG_TEMPLATES.tsconfigs.src.browser;
6621
- else if (path === "configs/src/vite.server.config.ts") content = CONFIG_TEMPLATES.vites.src.server;
6622
- else if (path === "configs/src/tsconfig.server.json") content = CONFIG_TEMPLATES.tsconfigs.src.server;
6623
- artifacts.push({
6624
- path,
6625
- group: "configs",
6626
- ownership: "content",
6627
- origin: "template",
6628
- environment,
6629
- content
6630
- });
6631
- }
6632
- if (blueprint.bin) {
6633
- const paths = blueprint.src.map((environment) => `\t\t\t\t\t\t'@src/${environment}': '../src/${environment}/index.js',`);
6634
- const renderedPaths = paths.length === 0 ? " paths: {}," : `\t\t\t\t\tpaths: {
6766
+ }];
6767
+ });
6768
+ const sources = Iterator.from(blueprint.src).flatMap((environment) => SRC_MATRIX[environment].configs.map((path) => ({
6769
+ path,
6770
+ group: "configs",
6771
+ ownership: "content",
6772
+ origin: "template",
6773
+ environment,
6774
+ content: path === "configs/src/tsconfig.core.json" ? CONFIG_TEMPLATES.tsconfigs.src.core : path === "configs/src/vite.browser.config.ts" ? CONFIG_TEMPLATES.vites.src.browser : path === "configs/src/tsconfig.browser.json" ? CONFIG_TEMPLATES.tsconfigs.src.browser : path === "configs/src/vite.server.config.ts" ? CONFIG_TEMPLATES.vites.src.server : path === "configs/src/tsconfig.server.json" ? CONFIG_TEMPLATES.tsconfigs.src.server : CONFIG_TEMPLATES.vites.src.core
6775
+ }))).toArray();
6776
+ const paths = blueprint.bin ? blueprint.src.map((environment) => `\t\t\t\t\t\t'@src/${environment}': '../src/${environment}/index.js',`) : [];
6777
+ const renderedPaths = paths.length === 0 ? " paths: {}," : `\t\t\t\t\tpaths: {
6635
6778
  ${paths.join("\n")}
6636
6779
  },`;
6637
- for (const path of BIN_CONFIGS) artifacts.push({
6638
- path,
6780
+ const bins = blueprint.bin ? BIN_CONFIGS.map((path) => ({
6781
+ path,
6782
+ group: "configs",
6783
+ ownership: "content",
6784
+ origin: "template",
6785
+ content: path === "configs/src/tsconfig.bin.json" ? CONFIG_TEMPLATES.tsconfigs.bin : fillTemplate(CONFIG_TEMPLATES.vites.bin, { paths: renderedPaths })
6786
+ })) : [];
6787
+ const includeSuffixes = [
6788
+ "cts",
6789
+ "mts",
6790
+ "ts",
6791
+ "tsx"
6792
+ ];
6793
+ const applications = Iterator.from(blueprint.app).flatMap((environment) => APP_MATRIX[environment].configs.map((path) => ({
6794
+ path,
6795
+ group: "configs",
6796
+ ownership: "content",
6797
+ origin: "template",
6798
+ environment,
6799
+ content: path === "configs/app/vite.browser.config.ts" ? CONFIG_TEMPLATES.vites.app.browser : path === "configs/app/tsconfig.browser.json" ? fillTemplate(CONFIG_TEMPLATES.tsconfigs.app.browser, { include: [
6800
+ ...includeSuffixes.map((suffix) => `../../app/browser/**/*.${suffix}`),
6801
+ ...blueprint.app.includes("core") ? includeSuffixes.map((suffix) => `../../app/core/**/*.${suffix}`) : [],
6802
+ ...includeSuffixes.map((suffix) => `../../tests/app/browser/**/*.${suffix}`),
6803
+ "../../tests/setup.ts",
6804
+ "../../tests/setupBrowser.ts"
6805
+ ].map((entry, index, entries) => `\t\t${JSON.stringify(entry)}${index === entries.length - 1 ? "" : ","}`).join("\n") }) : path === "configs/app/vite.server.config.ts" ? CONFIG_TEMPLATES.vites.app.server : path === "configs/app/tsconfig.server.json" ? fillTemplate(CONFIG_TEMPLATES.tsconfigs.app.server, { include: [
6806
+ ...includeSuffixes.map((suffix) => `../../app/server/**/*.${suffix}`),
6807
+ ...blueprint.app.includes("core") ? includeSuffixes.map((suffix) => `../../app/core/**/*.${suffix}`) : [],
6808
+ ...includeSuffixes.map((suffix) => `../../tests/app/server/**/*.${suffix}`),
6809
+ "../../tests/setup.ts",
6810
+ "../../tests/setupServer.ts"
6811
+ ].map((entry, index, entries) => `\t\t${JSON.stringify(entry)}${index === entries.length - 1 ? "" : ","}`).join("\n") }) : CONFIG_TEMPLATES.tsconfigs.app.core
6812
+ }))).toArray();
6813
+ const faces = blueprintToFaces(blueprint).flatMap((extension) => extension.axes.flatMap((axis) => {
6814
+ const include = [
6815
+ ...[
6816
+ "cts",
6817
+ "mts",
6818
+ "ts",
6819
+ "tsx",
6820
+ ...FRAMEWORK_MATRIX[extension.name].suffixes
6821
+ ].map((suffix) => `../../app/${extension.name}/**/*.${suffix}`),
6822
+ ...blueprint.app.includes("core") ? [
6823
+ "cts",
6824
+ "mts",
6825
+ "ts",
6826
+ "tsx"
6827
+ ].map((suffix) => `../../app/core/**/*.${suffix}`) : [],
6828
+ ...[
6829
+ "cts",
6830
+ "mts",
6831
+ "ts",
6832
+ "tsx",
6833
+ ...FRAMEWORK_MATRIX[extension.name].suffixes
6834
+ ].map((suffix) => `../../tests/app/${extension.name}/**/*.${suffix}`),
6835
+ "../../tests/setup.ts",
6836
+ "../../tests/setupBrowser.ts"
6837
+ ];
6838
+ const siblings = blueprint.src.filter((environment) => environment === "core" || environment === "browser").flatMap((environment) => ["ts", "js"].map((suffix) => {
6839
+ const key = `\t[resolveWorkspacePath('src/${environment}/index.${suffix}').replaceAll('\\\\', '/')]:`;
6840
+ const value = `'@orkestrel/${blueprint.name}${environment === "browser" ? "/browser" : ""}',`;
6841
+ return matchesPrintWidth(`${key} ${value}`) ? `${key} ${value}` : `${key}\n\t\t${value}`;
6842
+ })).join("\n");
6843
+ return [{
6844
+ path: `configs/${axis}/vite.${extension.name}.config.ts`,
6639
6845
  group: "configs",
6640
6846
  ownership: "content",
6641
6847
  origin: "template",
6642
- content: path === "configs/src/tsconfig.bin.json" ? CONFIG_TEMPLATES.tsconfigs.bin : fillTemplate(CONFIG_TEMPLATES.vites.bin, { paths: renderedPaths })
6643
- });
6644
- }
6645
- for (const environment of blueprint.app) for (const path of APP_MATRIX[environment].configs) {
6646
- let content = CONFIG_TEMPLATES.tsconfigs.app.core;
6647
- if (path === "configs/app/vite.browser.config.ts") content = CONFIG_TEMPLATES.vites.app.browser;
6648
- else if (path === "configs/app/tsconfig.browser.json") {
6649
- const include = [
6650
- "../../app/browser/**/*.cts",
6651
- "../../app/browser/**/*.mts",
6652
- "../../app/browser/**/*.ts",
6653
- "../../app/browser/**/*.tsx"
6654
- ];
6655
- if (blueprint.app.includes("core")) include.push("../../app/core/**/*.cts", "../../app/core/**/*.mts", "../../app/core/**/*.ts", "../../app/core/**/*.tsx");
6656
- include.push("../../tests/app/browser/**/*.cts", "../../tests/app/browser/**/*.mts", "../../tests/app/browser/**/*.ts", "../../tests/app/browser/**/*.tsx", "../../tests/setup.ts", "../../tests/setupBrowser.ts");
6657
- content = fillTemplate(CONFIG_TEMPLATES.tsconfigs.app.browser, { include: include.map((entry, index) => `\t\t${JSON.stringify(entry)}${index === include.length - 1 ? "" : ","}`).join("\n") });
6658
- } else if (path === "configs/app/vite.server.config.ts") content = CONFIG_TEMPLATES.vites.app.server;
6659
- else if (path === "configs/app/tsconfig.server.json") {
6660
- const include = [
6661
- "../../app/server/**/*.cts",
6662
- "../../app/server/**/*.mts",
6663
- "../../app/server/**/*.ts",
6664
- "../../app/server/**/*.tsx"
6665
- ];
6666
- if (blueprint.app.includes("core")) include.push("../../app/core/**/*.cts", "../../app/core/**/*.mts", "../../app/core/**/*.ts", "../../app/core/**/*.tsx");
6667
- include.push("../../tests/app/server/**/*.cts", "../../tests/app/server/**/*.mts", "../../tests/app/server/**/*.ts", "../../tests/app/server/**/*.tsx", "../../tests/setup.ts", "../../tests/setupServer.ts");
6668
- content = fillTemplate(CONFIG_TEMPLATES.tsconfigs.app.server, { include: include.map((entry, index) => `\t\t${JSON.stringify(entry)}${index === include.length - 1 ? "" : ","}`).join("\n") });
6669
- }
6670
- artifacts.push({
6671
- path,
6848
+ environment: "browser",
6849
+ content: axis === "src" ? fillTemplate(CONFIG_TEMPLATES.vites.src.vue, {
6850
+ siblings,
6851
+ refused: `[${FRAMEWORK_MATRIX[extension.name].refused.map((name) => serializeTypeScriptString(name)).join(", ")}]`
6852
+ }) : CONFIG_TEMPLATES.vites.app.vue
6853
+ }, {
6854
+ path: `configs/${axis}/tsconfig.${extension.name}.json`,
6672
6855
  group: "configs",
6673
6856
  ownership: "content",
6674
6857
  origin: "template",
6675
- environment,
6676
- content
6677
- });
6678
- }
6679
- for (const extension of blueprintToFaces(blueprint)) {
6680
- if (extension.surface !== "browser") continue;
6681
- for (const axis of extension.axes) {
6682
- const include = [
6683
- ...[
6684
- "cts",
6685
- "mts",
6686
- "ts",
6687
- "tsx",
6688
- ...FRAMEWORK_MATRIX[extension.name].suffixes
6689
- ].map((suffix) => `../../app/${extension.name}/**/*.${suffix}`),
6690
- ...blueprint.app.includes("core") ? [
6691
- "cts",
6692
- "mts",
6693
- "ts",
6694
- "tsx"
6695
- ].map((suffix) => `../../app/core/**/*.${suffix}`) : [],
6696
- ...[
6697
- "cts",
6698
- "mts",
6699
- "ts",
6700
- "tsx",
6701
- ...FRAMEWORK_MATRIX[extension.name].suffixes
6702
- ].map((suffix) => `../../tests/app/${extension.name}/**/*.${suffix}`),
6703
- "../../tests/setup.ts",
6704
- "../../tests/setupBrowser.ts"
6705
- ];
6706
- const siblings = blueprint.src.filter((environment) => environment === "core" || environment === "browser").flatMap((environment) => ["ts", "js"].map((suffix) => {
6707
- const key = `\t[resolveWorkspacePath('src/${environment}/index.${suffix}').replaceAll('\\\\', '/')]:`;
6708
- const value = `'@orkestrel/${blueprint.name}${environment === "browser" ? "/browser" : ""}',`;
6709
- return matchesPrintWidth(`${key} ${value}`) ? `${key} ${value}` : `${key}\n\t\t${value}`;
6710
- })).join("\n");
6711
- artifacts.push({
6712
- path: `configs/${axis}/vite.${extension.name}.config.ts`,
6713
- group: "configs",
6714
- ownership: "content",
6715
- origin: "template",
6716
- environment: "browser",
6717
- content: axis === "src" ? fillTemplate(CONFIG_TEMPLATES.vites.src.vue, {
6718
- siblings,
6719
- refused: `[${FRAMEWORK_MATRIX[extension.name].refused.map((name) => serializeTypeScriptString(name)).join(", ")}]`
6720
- }) : CONFIG_TEMPLATES.vites.app.vue
6721
- }, {
6722
- path: `configs/${axis}/tsconfig.${extension.name}.json`,
6723
- group: "configs",
6724
- ownership: "content",
6725
- origin: "template",
6726
- environment: "browser",
6727
- content: axis === "src" ? CONFIG_TEMPLATES.tsconfigs.vue.src : fillTemplate(CONFIG_TEMPLATES.tsconfigs.vue.app, { include: include.map((entry, index) => `\t\t${JSON.stringify(entry)}${index === include.length - 1 ? "" : ","}`).join("\n") })
6728
- });
6729
- }
6730
- }
6731
- if (blueprint.showcase && blueprint.app.includes("browser")) artifacts.push({
6858
+ environment: "browser",
6859
+ content: axis === "src" ? CONFIG_TEMPLATES.tsconfigs.vue.src : fillTemplate(CONFIG_TEMPLATES.tsconfigs.vue.app, { include: include.map((entry, index) => `\t\t${JSON.stringify(entry)}${index === include.length - 1 ? "" : ","}`).join("\n") })
6860
+ }];
6861
+ }));
6862
+ const showcase = blueprint.showcase && blueprint.app.includes("browser") ? [{
6732
6863
  path: SHOWCASE_CONFIG_PATH,
6733
6864
  group: "configs",
6734
6865
  ownership: "content",
6735
6866
  origin: "template",
6736
6867
  environment: "browser",
6737
6868
  content: CONFIG_TEMPLATES.vites.app.showcase
6738
- });
6739
- if (blueprint.journey && blueprint.app.includes("browser")) artifacts.push({
6869
+ }] : [];
6870
+ const journey = blueprint.journey && blueprint.app.includes("browser") ? [{
6740
6871
  path: JOURNEY_CONFIG_PATH,
6741
6872
  group: "configs",
6742
6873
  ownership: "birth",
6743
6874
  origin: "template",
6744
6875
  environment: "browser",
6745
6876
  content: CONFIG_TEMPLATES.vites.app.journey
6746
- });
6747
- if (blueprint.skills) artifacts.push({
6877
+ }] : [];
6878
+ const skills = blueprint.skills ? [{
6748
6879
  path: SKILLS_CONFIG_PATH,
6749
6880
  group: "configs",
6750
6881
  ownership: "content",
6751
6882
  origin: "template",
6752
6883
  content: CONFIG_TEMPLATES.tsconfigs.skills
6753
- });
6754
- return artifacts;
6884
+ }] : [];
6885
+ return [
6886
+ ...roots,
6887
+ ...browsers,
6888
+ ...sheets,
6889
+ ...sources,
6890
+ ...bins,
6891
+ ...applications,
6892
+ ...faces,
6893
+ ...showcase,
6894
+ ...journey,
6895
+ ...skills
6896
+ ];
6755
6897
  }
6756
6898
  /**
6757
6899
  * Compiles every artifact in the `source` group.
@@ -6773,100 +6915,109 @@ ${paths.join("\n")}
6773
6915
  * ```
6774
6916
  */
6775
6917
  function blueprintToSourceArtifacts(blueprint) {
6776
- const artifacts = [];
6777
- if (blueprint.themes && !blueprint.styles) artifacts.push({
6918
+ const tokens = blueprint.themes && !blueprint.styles ? [{
6778
6919
  path: "src/styles/_tokens.scss",
6779
6920
  group: "source",
6780
6921
  ownership: "birth",
6781
6922
  origin: "template",
6782
6923
  content: ARTIFACT_TEMPLATES.source.sheet["_tokens.scss"]
6783
- });
6784
- for (const name of [...blueprintToSheets(blueprint), ...blueprint.themes ? ["styles/themes"] : []]) for (const [path, content] of Object.entries(name === "styles/themes" ? ARTIFACT_TEMPLATES.source.themes : ARTIFACT_TEMPLATES.source.sheet)) artifacts.push({
6924
+ }] : [];
6925
+ const sheets = [...blueprintToSheets(blueprint), ...blueprint.themes ? ["styles/themes"] : []].flatMap((name) => Object.entries(name === "styles/themes" ? ARTIFACT_TEMPLATES.source.themes : ARTIFACT_TEMPLATES.source.sheet).map(([path, content]) => ({
6785
6926
  path: `src/${name}/${path}`,
6786
6927
  group: "source",
6787
6928
  ownership: "birth",
6788
6929
  origin: "template",
6789
6930
  content
6790
- });
6791
- for (const environment of blueprint.src) artifacts.push({
6931
+ })));
6932
+ const sources = Iterator.from(blueprint.src).map((environment) => ({
6792
6933
  path: `src/${environment}/index.ts`,
6793
6934
  group: "source",
6794
6935
  ownership: "birth",
6795
6936
  origin: "template",
6796
6937
  environment,
6797
6938
  content: ARTIFACT_TEMPLATES.source.empty
6798
- });
6799
- for (const environment of blueprint.app) {
6800
- artifacts.push({
6939
+ })).toArray();
6940
+ const applications = Iterator.from(blueprint.app).flatMap((environment) => {
6941
+ const entry = {
6801
6942
  path: `app/${environment}/index.ts`,
6802
6943
  group: "source",
6803
6944
  ownership: "birth",
6804
6945
  origin: "template",
6805
6946
  environment,
6806
6947
  content: ARTIFACT_TEMPLATES.source.empty
6807
- });
6808
- if (environment === "browser") artifacts.push({
6809
- path: "app/browser/main.ts",
6810
- group: "source",
6811
- ownership: "birth",
6812
- origin: "template",
6813
- environment,
6814
- content: fillTemplate(ARTIFACT_TEMPLATES.source.main, { heading: `${matchesPrintWidth(`\t.appendChild(document.createElement('h1')).textContent = ${serializeTypeScriptString(blueprint.name)}`) ? " " : "\n "}${serializeTypeScriptString(blueprint.name)}` })
6815
- }, {
6816
- path: "app/browser/index.html",
6948
+ };
6949
+ if (environment === "browser") return [
6950
+ entry,
6951
+ {
6952
+ path: "app/browser/main.ts",
6953
+ group: "source",
6954
+ ownership: "birth",
6955
+ origin: "template",
6956
+ environment,
6957
+ content: fillTemplate(ARTIFACT_TEMPLATES.source.main, { heading: `${matchesPrintWidth(`\t.appendChild(document.createElement('h1')).textContent = ${serializeTypeScriptString(blueprint.name)}`) ? " " : "\n "}${serializeTypeScriptString(blueprint.name)}` })
6958
+ },
6959
+ {
6960
+ path: "app/browser/index.html",
6961
+ group: "source",
6962
+ ownership: "birth",
6963
+ origin: "template",
6964
+ environment,
6965
+ content: ARTIFACT_TEMPLATES.source.browser
6966
+ }
6967
+ ];
6968
+ return environment === "server" ? [entry, {
6969
+ path: "app/server/main.ts",
6817
6970
  group: "source",
6818
6971
  ownership: "birth",
6819
6972
  origin: "template",
6820
6973
  environment,
6821
- content: ARTIFACT_TEMPLATES.source.browser
6822
- });
6823
- else if (environment === "server") artifacts.push({
6824
- path: "app/server/main.ts",
6974
+ content: ARTIFACT_TEMPLATES.source.empty
6975
+ }] : [entry];
6976
+ }).toArray();
6977
+ const faces = blueprintToFaces(blueprint).flatMap((extension) => extension.axes.flatMap((axis) => {
6978
+ const barrel = {
6979
+ path: `${axis}/${extension.name}/index.ts`,
6825
6980
  group: "source",
6826
6981
  ownership: "birth",
6827
6982
  origin: "template",
6828
- environment,
6829
- content: ARTIFACT_TEMPLATES.source.empty
6830
- });
6831
- }
6832
- for (const extension of blueprintToFaces(blueprint)) {
6833
- if (extension.surface !== "browser") continue;
6834
- for (const axis of extension.axes) {
6835
- artifacts.push({
6836
- path: `${axis}/${extension.name}/index.ts`,
6837
- group: "source",
6838
- ownership: "birth",
6839
- origin: "template",
6840
- environment: "browser",
6841
- content: axis === "app" ? ARTIFACT_TEMPLATES.source.empty : ARTIFACT_TEMPLATES.source.barrel
6842
- });
6843
- if (axis !== "app") continue;
6844
- artifacts.push({
6983
+ environment: "browser",
6984
+ content: axis === "app" ? ARTIFACT_TEMPLATES.source.empty : ARTIFACT_TEMPLATES.source.barrel
6985
+ };
6986
+ return axis === "app" ? [
6987
+ barrel,
6988
+ {
6845
6989
  path: `app/${extension.name}/index.html`,
6846
6990
  group: "source",
6847
6991
  ownership: "birth",
6848
6992
  origin: "template",
6849
6993
  environment: "browser",
6850
6994
  content: ARTIFACT_TEMPLATES.source.browser
6851
- });
6852
- for (const [path, content] of Object.entries(ARTIFACT_TEMPLATES.source.vue)) artifacts.push({
6995
+ },
6996
+ ...Object.entries(ARTIFACT_TEMPLATES.source.vue).map(([path, content]) => ({
6853
6997
  path: `app/${extension.name}/${path}`,
6854
6998
  group: "source",
6855
6999
  ownership: "birth",
6856
7000
  origin: "template",
6857
7001
  environment: "browser",
6858
7002
  content: fillTemplate(content, { heading: matchesPrintWidth(`\t<h1>${blueprint.name}</h1>`) ? blueprint.name : `\n\t\t${blueprint.name}\n\t` })
6859
- });
6860
- }
6861
- }
6862
- if (blueprint.bin) artifacts.push({
7003
+ }))
7004
+ ] : [barrel];
7005
+ }));
7006
+ const bin = blueprint.bin ? [{
6863
7007
  path: BIN_ENTRY_PATH,
6864
7008
  group: "source",
6865
7009
  ownership: "birth",
6866
7010
  origin: "template",
6867
7011
  content: ARTIFACT_TEMPLATES.source.empty
6868
- });
6869
- return artifacts;
7012
+ }] : [];
7013
+ return [
7014
+ ...tokens,
7015
+ ...sheets,
7016
+ ...sources,
7017
+ ...applications,
7018
+ ...faces,
7019
+ ...bin
7020
+ ];
6870
7021
  }
6871
7022
  /**
6872
7023
  * Compiles every artifact in the `tests` group that is not vendored from the host.
@@ -6885,7 +7036,8 @@ function blueprintToSourceArtifacts(blueprint) {
6885
7036
  * is not: it names an official artifact only the package knows, so a generated
6886
7037
  * placeholder would read as a proof while measuring nothing. `service` emits its
6887
7038
  * readiness setup alone, because the root configuration names that module by
6888
- * path.
7039
+ * path. `studies` emits nothing: the root configuration names no study harness,
7040
+ * so `tests/setupStudy.ts` is the package's own module.
6889
7041
  *
6890
7042
  * The distribution proof is emitted, and the same test separates it from those
6891
7043
  * two: its subject is the packed tarball rather than anything only the package
@@ -6904,29 +7056,19 @@ function blueprintToSourceArtifacts(blueprint) {
6904
7056
  * ```
6905
7057
  */
6906
7058
  function blueprintToTestArtifacts(blueprint) {
6907
- const artifacts = [{
6908
- path: "tests/setup.ts",
7059
+ const sheets = [...blueprintToSheets(blueprint), ...blueprint.themes ? ["styles/themes"] : []];
7060
+ const faces = blueprintToFaces(blueprint).flatMap((extension) => extension.axes.map((axis) => ({
7061
+ path: `tests/${axis}/${extension.name}/index.test.ts`,
6909
7062
  group: "tests",
6910
7063
  ownership: "birth",
6911
7064
  origin: "template",
6912
- content: ARTIFACT_TEMPLATES.tests.setup
6913
- }];
6914
- const sheets = [...blueprintToSheets(blueprint), ...blueprint.themes ? ["styles/themes"] : []];
6915
- for (const extension of blueprintToFaces(blueprint)) {
6916
- if (extension.surface !== "browser") continue;
6917
- for (const axis of extension.axes) artifacts.push({
6918
- path: `tests/${axis}/${extension.name}/index.test.ts`,
6919
- group: "tests",
6920
- ownership: "birth",
6921
- origin: "template",
6922
- environment: "browser",
6923
- content: fillTemplate(ARTIFACT_TEMPLATES.tests.entry, {
6924
- specifier: serializeTypeScriptString(`@${axis}/${extension.name}`),
6925
- label: serializeTypeScriptString(`${axis} ${extension.name} entry`)
6926
- })
6927
- });
6928
- }
6929
- if (sheets.length > 0) artifacts.push({
7065
+ environment: "browser",
7066
+ content: fillTemplate(ARTIFACT_TEMPLATES.tests.entry, {
7067
+ specifier: serializeTypeScriptString(`@${axis}/${extension.name}`),
7068
+ label: serializeTypeScriptString(`${axis} ${extension.name} entry`)
7069
+ })
7070
+ })));
7071
+ const styles = sheets.length > 0 ? [{
6930
7072
  path: "tests/setupStyles.ts",
6931
7073
  group: "tests",
6932
7074
  ownership: "birth",
@@ -6938,10 +7080,10 @@ function blueprintToTestArtifacts(blueprint) {
6938
7080
  ownership: "birth",
6939
7081
  origin: "template",
6940
7082
  content: ARTIFACT_TEMPLATES.tests.styles.proof
6941
- });
6942
- for (const name of sheets) {
7083
+ }] : [];
7084
+ const sheetTests = sheets.map((name) => {
6943
7085
  const tests = "../".repeat(name.split("/").length + 1);
6944
- artifacts.push({
7086
+ return {
6945
7087
  path: `tests/src/${name}/index.test.ts`,
6946
7088
  group: "tests",
6947
7089
  ownership: "birth",
@@ -6951,66 +7093,60 @@ function blueprintToTestArtifacts(blueprint) {
6951
7093
  root: `${tests}../`,
6952
7094
  setup: `${tests}setupStyles.js`
6953
7095
  })
6954
- });
6955
- }
6956
- if (blueprintToMachinery(blueprint).browser) artifacts.push({
7096
+ };
7097
+ });
7098
+ const browsers = blueprintToMachinery(blueprint).browser ? [{
6957
7099
  path: "tests/setupBrowser.ts",
6958
7100
  group: "tests",
6959
7101
  ownership: "birth",
6960
7102
  origin: "template",
6961
7103
  environment: "browser",
6962
7104
  content: blueprint.journey ? ARTIFACT_TEMPLATES.tests.browser : ARTIFACT_TEMPLATES.tests.setup
6963
- });
6964
- if (blueprint.src.includes("server") || blueprint.app.includes("server") || blueprint.bin || blueprint.conformance) artifacts.push({
7105
+ }] : [];
7106
+ const servers = blueprint.src.includes("server") || blueprint.app.includes("server") || blueprint.bin || blueprint.conformance ? [{
6965
7107
  path: "tests/setupServer.ts",
6966
7108
  group: "tests",
6967
7109
  ownership: "birth",
6968
7110
  origin: "template",
6969
7111
  environment: "server",
6970
7112
  content: ARTIFACT_TEMPLATES.tests.setup
6971
- });
6972
- if (blueprint.service) artifacts.push({
7113
+ }] : [];
7114
+ const services = blueprint.service ? [{
6973
7115
  path: SERVICE_SETUP_PATH,
6974
7116
  group: "tests",
6975
7117
  ownership: "birth",
6976
7118
  origin: "template",
6977
7119
  content: ARTIFACT_TEMPLATES.tests.setup
6978
- });
6979
- if (blueprint.global) {
6980
- artifacts.push({
6981
- path: GLOBAL_SETUP_PATH,
6982
- group: "tests",
6983
- ownership: "birth",
6984
- origin: "template",
6985
- content: ARTIFACT_TEMPLATES.tests.global.module
6986
- });
6987
- artifacts.push({
6988
- path: "tests/setupGlobal.test.ts",
6989
- group: "tests",
6990
- ownership: "birth",
6991
- origin: "template",
6992
- content: ARTIFACT_TEMPLATES.tests.global.proof
6993
- });
6994
- }
6995
- if (blueprint.journey && blueprint.app.includes("browser")) {
6996
- const applications = ["browser", ...blueprintToFaces(blueprint).flatMap((extension) => extension.surface === "browser" && extension.axes.includes("app") ? [extension.name] : [])];
6997
- for (const application of applications) artifacts.push({
6998
- path: `tests/app/${application}/integration.test.ts`,
6999
- group: "tests",
7000
- ownership: "birth",
7001
- origin: "template",
7002
- environment: "browser",
7003
- content: fillTemplate(ARTIFACT_TEMPLATES.tests.journey, {
7004
- application,
7005
- heading: serializeTypeScriptString(blueprint.name),
7006
- imports: application === "vue" ? "import { createApp } from 'vue'\nimport App from '../../../app/vue/App.vue'\n" : "",
7007
- mount: application === "vue" ? " const container = document.body.appendChild(document.createElement('main'))\n const app = createApp(App)\n" : "",
7008
- arrival: application === "vue" ? " app.mount(container)\n" : " await import('../../../app/browser/main.js')\n",
7009
- cleanup: application === "vue" ? " app.unmount()\n container.remove()\n" : " for (const main of page.getByRole('main').elements()) main.remove()\n"
7010
- })
7011
- });
7012
- }
7013
- for (const environment of blueprint.src) artifacts.push({
7120
+ }] : [];
7121
+ const globals = blueprint.global ? [{
7122
+ path: GLOBAL_SETUP_PATH,
7123
+ group: "tests",
7124
+ ownership: "birth",
7125
+ origin: "template",
7126
+ content: ARTIFACT_TEMPLATES.tests.global.module
7127
+ }, {
7128
+ path: "tests/setupGlobal.test.ts",
7129
+ group: "tests",
7130
+ ownership: "birth",
7131
+ origin: "template",
7132
+ content: ARTIFACT_TEMPLATES.tests.global.proof
7133
+ }] : [];
7134
+ const journeys = blueprint.journey && blueprint.app.includes("browser") ? ["browser", ...blueprintToFaces(blueprint).flatMap((extension) => extension.surface === "browser" && extension.axes.includes("app") ? [extension.name] : [])].map((application) => ({
7135
+ path: `tests/app/${application}/integration.test.ts`,
7136
+ group: "tests",
7137
+ ownership: "birth",
7138
+ origin: "template",
7139
+ environment: "browser",
7140
+ content: fillTemplate(ARTIFACT_TEMPLATES.tests.journey, {
7141
+ application,
7142
+ heading: serializeTypeScriptString(blueprint.name),
7143
+ imports: application === "vue" ? "import { createApp } from 'vue'\nimport App from '../../../app/vue/App.vue'\n" : "",
7144
+ mount: application === "vue" ? " const container = document.body.appendChild(document.createElement('main'))\n const app = createApp(App)\n" : "",
7145
+ arrival: application === "vue" ? " app.mount(container)\n" : " await import('../../../app/browser/main.js')\n",
7146
+ cleanup: application === "vue" ? " app.unmount()\n container.remove()\n" : " for (const main of page.getByRole('main').elements()) main.remove()\n"
7147
+ })
7148
+ })) : [];
7149
+ const sources = Iterator.from(blueprint.src).map((environment) => ({
7014
7150
  path: `tests/src/${environment}/index.test.ts`,
7015
7151
  group: "tests",
7016
7152
  ownership: "birth",
@@ -7020,20 +7156,17 @@ function blueprintToTestArtifacts(blueprint) {
7020
7156
  specifier: serializeTypeScriptString(`@src/${environment}`),
7021
7157
  label: serializeTypeScriptString(`src ${environment} entry`)
7022
7158
  })
7023
- });
7024
- if (blueprint.bin) {
7025
- const specifier = serializeTypeScriptString("../../../src/bin/main.js");
7026
- const joined = `\t\tconst entry = await import(${specifier})`;
7027
- const statement = matchesPrintWidth(joined) ? joined : `\t\tconst entry =\n\t\t\tawait import(${specifier})`;
7028
- artifacts.push({
7029
- path: "tests/src/bin/main.test.ts",
7030
- group: "tests",
7031
- ownership: "birth",
7032
- origin: "template",
7033
- content: fillTemplate(ARTIFACT_TEMPLATES.tests.bin, { import: statement })
7034
- });
7035
- }
7036
- for (const environment of blueprint.app) artifacts.push({
7159
+ })).toArray();
7160
+ const binSpecifier = serializeTypeScriptString("../../../src/bin/main.js");
7161
+ const joinedImport = `\t\tconst entry = await import(${binSpecifier})`;
7162
+ const bins = blueprint.bin ? [{
7163
+ path: "tests/src/bin/main.test.ts",
7164
+ group: "tests",
7165
+ ownership: "birth",
7166
+ origin: "template",
7167
+ content: fillTemplate(ARTIFACT_TEMPLATES.tests.bin, { import: matchesPrintWidth(joinedImport) ? joinedImport : `\t\tconst entry =\n\t\t\tawait import(${binSpecifier})` })
7168
+ }] : [];
7169
+ const applications = Iterator.from(blueprint.app).map((environment) => ({
7037
7170
  path: `tests/app/${environment}/index.test.ts`,
7038
7171
  group: "tests",
7039
7172
  ownership: "birth",
@@ -7043,43 +7176,36 @@ function blueprintToTestArtifacts(blueprint) {
7043
7176
  specifier: serializeTypeScriptString(`@app/${environment}`),
7044
7177
  label: serializeTypeScriptString(`app ${environment} entry`)
7045
7178
  })
7046
- });
7047
- if (blueprint.src.length > 0 || blueprintToSheets(blueprint).length > 0 || blueprint.themes) {
7048
- const browser = blueprint.src.includes("browser");
7049
- const distribution = ARTIFACT_TEMPLATES.tests.distribution;
7050
- artifacts.push({
7051
- path: DISTRIBUTION_TEST_PATH,
7052
- group: "tests",
7053
- ownership: "presence",
7054
- origin: "template",
7055
- content: fillTemplate(distribution.proof, {
7056
- types: browser ? distribution.types : "",
7057
- transport: browser ? distribution.transport : "",
7058
- launcher: browser ? distribution.launcher : "",
7059
- helpers: browser ? distribution.helpers : "",
7060
- drive: browser ? distribution.drive : "",
7061
- guard: browser ? "" : distribution.guard
7062
- })
7063
- });
7064
- }
7065
- if (blueprint.integration) {
7066
- const imports = [];
7067
- const entries = [];
7068
- for (const environment of blueprint.src) {
7069
- const entry = environment === "core" ? "srcCore" : environment === "browser" ? "srcBrowser" : "srcServer";
7070
- imports.push(`import * as ${entry} from '@src/${environment}'`);
7071
- entries.push(entry);
7072
- }
7073
- for (const environment of blueprint.app) {
7074
- const entry = environment === "core" ? "appCore" : environment === "browser" ? "appBrowser" : "appServer";
7075
- imports.push(`import * as ${entry} from '@app/${environment}'`);
7076
- entries.push(entry);
7077
- }
7179
+ })).toArray();
7180
+ const distribution = ARTIFACT_TEMPLATES.tests.distribution;
7181
+ const distributions = blueprint.src.length > 0 || blueprintToSheets(blueprint).length > 0 || blueprint.themes ? [blueprint.src.includes("browser")].map((browser) => ({
7182
+ path: DISTRIBUTION_TEST_PATH,
7183
+ group: "tests",
7184
+ ownership: "presence",
7185
+ origin: "template",
7186
+ content: fillTemplate(distribution.proof, {
7187
+ types: browser ? distribution.types : "",
7188
+ transport: browser ? distribution.transport : "",
7189
+ launcher: browser ? distribution.launcher : "",
7190
+ helpers: browser ? distribution.helpers : "",
7191
+ drive: browser ? distribution.drive : "",
7192
+ guard: browser ? "" : distribution.guard
7193
+ })
7194
+ })) : [];
7195
+ const integrations = blueprint.integration ? [[...Iterator.from(blueprint.src).map((environment) => ({
7196
+ entry: environment === "core" ? "srcCore" : environment === "browser" ? "srcBrowser" : "srcServer",
7197
+ specifier: `@src/${environment}`
7198
+ })).toArray(), ...Iterator.from(blueprint.app).map((environment) => ({
7199
+ entry: environment === "core" ? "appCore" : environment === "browser" ? "appBrowser" : "appServer",
7200
+ specifier: `@app/${environment}`
7201
+ })).toArray()]].map((modules) => {
7202
+ const entries = modules.map(({ entry }) => entry);
7203
+ const imports = modules.map(({ entry, specifier }) => `import * as ${entry} from '${specifier}'`);
7078
7204
  const joined = `expect([${entries.map((entry) => `Object.keys(${entry})`).join(", ")}]).toStrictEqual(`;
7079
7205
  const actual = matchesPrintWidth(`\t\t${joined}[`) ? joined : `expect([\n${entries.map((entry) => `\t\t\tObject.keys(${entry}),`).join("\n")}\n\t\t]).toStrictEqual(`;
7080
7206
  const expectedInline = `[${entries.map(() => "[]").join(", ")}]`;
7081
7207
  const expected = actual !== joined || matchesPrintWidth(`\t\t${joined}${expectedInline})`) ? expectedInline : `[\n${entries.map(() => " [],").join("\n")}\n\t\t]`;
7082
- artifacts.push({
7208
+ return {
7083
7209
  path: INTEGRATION_TEST_PATH,
7084
7210
  group: "tests",
7085
7211
  ownership: "birth",
@@ -7089,9 +7215,30 @@ function blueprintToTestArtifacts(blueprint) {
7089
7215
  actual,
7090
7216
  expected
7091
7217
  })
7092
- });
7093
- }
7094
- return artifacts;
7218
+ };
7219
+ }) : [];
7220
+ return [
7221
+ {
7222
+ path: "tests/setup.ts",
7223
+ group: "tests",
7224
+ ownership: "birth",
7225
+ origin: "template",
7226
+ content: ARTIFACT_TEMPLATES.tests.setup
7227
+ },
7228
+ ...faces,
7229
+ ...styles,
7230
+ ...sheetTests,
7231
+ ...browsers,
7232
+ ...servers,
7233
+ ...services,
7234
+ ...globals,
7235
+ ...journeys,
7236
+ ...sources,
7237
+ ...bins,
7238
+ ...applications,
7239
+ ...distributions,
7240
+ ...integrations
7241
+ ];
7095
7242
  }
7096
7243
  /**
7097
7244
  * Compiles the generated workspace's guide index.
@@ -7109,30 +7256,29 @@ function blueprintToTestArtifacts(blueprint) {
7109
7256
  * ```
7110
7257
  */
7111
7258
  function blueprintToGuideArtifacts(blueprint) {
7112
- const source = [];
7113
- const tests = [];
7114
- const directories = [];
7115
7259
  const guide = `guides/${blueprint.name}.md`;
7116
- for (const environment of [
7117
- ...blueprint.src,
7118
- ...blueprintToFaces(blueprint).filter((extension) => extension.axes.includes("src")).map((extension) => extension.name),
7119
- ...blueprintToSheets(blueprint),
7120
- ...blueprint.themes ? ["styles/themes"] : []
7121
- ]) {
7122
- source.push(` - [\`src/${environment}\`](../src/${environment})`);
7123
- tests.push(` - [\`tests/src/${environment}\`](../tests/src/${environment})`);
7124
- directories.push(`- [\`src/${environment}\`](../src/${environment})\n - Guide: Not created. Create this file when the workspace has a public surface:\n \`${guide}\`\n - Tests: [\`tests/src/${environment}\`](../tests/src/${environment})`);
7125
- }
7126
- if (blueprint.bin) {
7127
- source.push(" - [`src/bin`](../src/bin)");
7128
- tests.push(" - [`tests/src/bin`](../tests/src/bin)");
7129
- directories.push(`- [\`src/bin\`](../src/bin)\n - Guide: Not created. Create this file when the workspace has a public surface:\n \`${guide}\`\n - Tests: [\`tests/src/bin\`](../tests/src/bin)`);
7130
- }
7131
- for (const environment of [...blueprint.app, ...blueprintToFaces(blueprint).filter((extension) => extension.axes.includes("app")).map((extension) => extension.name)]) {
7132
- source.push(` - [\`app/${environment}\`](../app/${environment})`);
7133
- tests.push(` - [\`tests/app/${environment}\`](../tests/app/${environment})`);
7134
- directories.push(`- [\`app/${environment}\`](../app/${environment})\n - Guide: Not created. Create this file when the workspace has a public surface:\n \`${guide}\`\n - Tests: [\`tests/app/${environment}\`](../tests/app/${environment})`);
7135
- }
7260
+ const rows = [
7261
+ ...[
7262
+ ...blueprint.src,
7263
+ ...blueprintToFaces(blueprint).filter((extension) => extension.axes.includes("src")).map((extension) => extension.name),
7264
+ ...blueprintToSheets(blueprint),
7265
+ ...blueprint.themes ? ["styles/themes"] : []
7266
+ ].map((environment) => ({
7267
+ directory: `src/${environment}`,
7268
+ test: `tests/src/${environment}`
7269
+ })),
7270
+ ...blueprint.bin ? [{
7271
+ directory: "src/bin",
7272
+ test: "tests/src/bin"
7273
+ }] : [],
7274
+ ...[...blueprint.app, ...blueprintToFaces(blueprint).filter((extension) => extension.axes.includes("app")).map((extension) => extension.name)].map((environment) => ({
7275
+ directory: `app/${environment}`,
7276
+ test: `tests/app/${environment}`
7277
+ }))
7278
+ ];
7279
+ const source = rows.map(({ directory }) => ` - [\`${directory}\`](../${directory})`);
7280
+ const tests = rows.map(({ test }) => ` - [\`${test}\`](../${test})`);
7281
+ const directories = rows.map(({ directory, test }) => `- [\`${directory}\`](../${directory})\n - Guide: Not created. Create this file when the workspace has a public surface:\n \`${guide}\`\n - Tests: [\`${test}\`](../${test})`);
7136
7282
  return [{
7137
7283
  path: "guides/README.md",
7138
7284
  group: "guides",
@@ -7384,155 +7530,54 @@ function applyOverrides(artifacts, overrides) {
7384
7530
  */
7385
7531
  function replaceManifestRanges(manifest, pins) {
7386
7532
  if (pins.runtime.length === 0 && pins.development.length === 0) return manifest;
7533
+ const tokens = scanManifestTokens(manifest);
7534
+ if (tokens === void 0) return void 0;
7387
7535
  const sectionNames = /* @__PURE__ */ new Set(["dependencies", "devDependencies"]);
7388
- const sections = [];
7389
- let depth = 0;
7390
- let cursor = 0;
7391
- while (cursor < manifest.length) {
7392
- const character = manifest.charAt(cursor);
7393
- if (character === "{") {
7394
- depth += 1;
7395
- cursor += 1;
7396
- continue;
7397
- }
7398
- if (character === "}") {
7399
- depth -= 1;
7400
- cursor += 1;
7401
- continue;
7402
- }
7403
- if (character !== "\"") {
7404
- cursor += 1;
7405
- continue;
7406
- }
7407
- const start = cursor;
7408
- cursor += 1;
7409
- while (cursor < manifest.length) {
7410
- if (manifest.charAt(cursor) === "\\") {
7411
- cursor += 2;
7412
- continue;
7413
- }
7414
- if (manifest.charAt(cursor) === "\"") break;
7415
- cursor += 1;
7416
- }
7417
- if (cursor >= manifest.length) return void 0;
7418
- const end = cursor + 1;
7419
- if (depth === 1) {
7420
- const key = parseJSON(manifest.slice(start, end));
7421
- let value = end;
7422
- while (value < manifest.length && /\s/u.test(manifest.charAt(value))) value += 1;
7423
- if (manifest.charAt(value) === ":") {
7424
- value += 1;
7425
- while (value < manifest.length && /\s/u.test(manifest.charAt(value))) value += 1;
7426
- if (isString(key) && sectionNames.has(key) && manifest.charAt(value) === "{") {
7427
- let sectionDepth = 0;
7428
- let sectionCursor = value;
7429
- while (sectionCursor < manifest.length) {
7430
- const sectionCharacter = manifest.charAt(sectionCursor);
7431
- if (sectionCharacter === "\"") {
7432
- sectionCursor += 1;
7433
- while (sectionCursor < manifest.length) {
7434
- if (manifest.charAt(sectionCursor) === "\\") {
7435
- sectionCursor += 2;
7436
- continue;
7437
- }
7438
- if (manifest.charAt(sectionCursor) === "\"") break;
7439
- sectionCursor += 1;
7440
- }
7441
- if (sectionCursor >= manifest.length) return void 0;
7442
- } else if (sectionCharacter === "{") sectionDepth += 1;
7443
- else if (sectionCharacter === "}") {
7444
- sectionDepth -= 1;
7445
- if (sectionDepth === 0) {
7446
- sections.push({
7447
- name: key,
7448
- start: value,
7449
- end: sectionCursor + 1
7450
- });
7451
- break;
7452
- }
7453
- }
7454
- sectionCursor += 1;
7536
+ const readings = tokens.flatMap((token, at) => {
7537
+ if (token.depth !== 1 || token.value === void 0) return [];
7538
+ if (manifest.charAt(token.value) !== "{") return [];
7539
+ const key = parseJSON(token.text);
7540
+ if (!isString(key) || !sectionNames.has(key)) return [];
7541
+ const close = tokens.slice(at + 1).find((next) => next.text === "}" && next.depth === 1);
7542
+ return close === void 0 ? [] : [{
7543
+ name: key,
7544
+ start: token.value,
7545
+ end: close.index + 1
7546
+ }];
7547
+ }).map((bounds) => {
7548
+ const replacements = new Map((bounds.name === "dependencies" ? pins.runtime : pins.development).map(({ name, range }) => [name, range]));
7549
+ return {
7550
+ name: bounds.name,
7551
+ log: tokens.reduce((log, token, at) => {
7552
+ if (log === void 0) return void 0;
7553
+ if (token.index < bounds.start || token.index >= bounds.end) return log;
7554
+ if (log.at(-1)?.edit.start === token.index) return log;
7555
+ if (token.depth !== 2 || token.value === void 0) return log;
7556
+ const key = parseJSON(token.text);
7557
+ if (!isString(key) || manifest.charAt(token.value) !== "\"") return log;
7558
+ const replacement = replacements.get(key);
7559
+ if (replacement === void 0) return log;
7560
+ const literal = tokens[at + 1];
7561
+ if (literal === void 0) return void 0;
7562
+ return [...log, {
7563
+ name: key,
7564
+ edit: {
7565
+ start: literal.index,
7566
+ end: literal.index + literal.text.length,
7567
+ text: JSON.stringify(replacement)
7455
7568
  }
7456
- }
7457
- }
7458
- }
7459
- cursor = end;
7460
- }
7461
- const runtime = /* @__PURE__ */ new Set();
7462
- const development = /* @__PURE__ */ new Set();
7463
- let compiled = manifest;
7464
- for (const bounds of sections.reverse()) {
7465
- const dependencies = bounds.name === "dependencies" ? pins.runtime : pins.development;
7466
- const replacements = new Map(dependencies.map(({ name, range }) => [name, range]));
7467
- const declared = bounds.name === "dependencies" ? runtime : development;
7468
- let section = compiled.slice(bounds.start, bounds.end);
7469
- let sectionDepth = 0;
7470
- let sectionCursor = 0;
7471
- while (sectionCursor < section.length) {
7472
- const character = section.charAt(sectionCursor);
7473
- if (character === "{") {
7474
- sectionDepth += 1;
7475
- sectionCursor += 1;
7476
- continue;
7477
- }
7478
- if (character === "}") {
7479
- sectionDepth -= 1;
7480
- sectionCursor += 1;
7481
- continue;
7482
- }
7483
- if (character !== "\"") {
7484
- sectionCursor += 1;
7485
- continue;
7486
- }
7487
- const keyStart = sectionCursor;
7488
- sectionCursor += 1;
7489
- while (sectionCursor < section.length) {
7490
- if (section.charAt(sectionCursor) === "\\") {
7491
- sectionCursor += 2;
7492
- continue;
7493
- }
7494
- if (section.charAt(sectionCursor) === "\"") break;
7495
- sectionCursor += 1;
7496
- }
7497
- if (sectionCursor >= section.length) return void 0;
7498
- const keyEnd = sectionCursor + 1;
7499
- const key = parseJSON(section.slice(keyStart, keyEnd));
7500
- let valueStart = keyEnd;
7501
- while (valueStart < section.length && /\s/u.test(section.charAt(valueStart))) valueStart += 1;
7502
- if (sectionDepth !== 1 || section.charAt(valueStart) !== ":") {
7503
- sectionCursor = keyEnd;
7504
- continue;
7505
- }
7506
- valueStart += 1;
7507
- while (valueStart < section.length && /\s/u.test(section.charAt(valueStart))) valueStart += 1;
7508
- if (!isString(key) || section.charAt(valueStart) !== "\"") {
7509
- sectionCursor = keyEnd;
7510
- continue;
7511
- }
7512
- const replacement = replacements.get(key);
7513
- if (replacement === void 0) {
7514
- sectionCursor = keyEnd;
7515
- continue;
7516
- }
7517
- let valueEnd = valueStart + 1;
7518
- while (valueEnd < section.length) {
7519
- if (section.charAt(valueEnd) === "\\") {
7520
- valueEnd += 2;
7521
- continue;
7522
- }
7523
- if (section.charAt(valueEnd) === "\"") break;
7524
- valueEnd += 1;
7525
- }
7526
- if (valueEnd >= section.length) return void 0;
7527
- const range = JSON.stringify(replacement);
7528
- section = section.slice(0, valueStart) + range + section.slice(valueEnd + 1);
7529
- declared.add(key);
7530
- sectionCursor = valueStart + range.length;
7531
- }
7532
- compiled = compiled.slice(0, bounds.start) + section + compiled.slice(bounds.end);
7533
- }
7534
- const declaredRuntime = pins.runtime.every(({ name }) => runtime.has(name));
7535
- const declaredDevelopment = pins.development.every(({ name }) => development.has(name));
7569
+ }];
7570
+ }, [])
7571
+ };
7572
+ });
7573
+ const settled = readings.flatMap(({ name, log }) => log === void 0 ? [] : [{
7574
+ name,
7575
+ log
7576
+ }]);
7577
+ if (settled.length < readings.length) return void 0;
7578
+ const compiled = settled.flatMap(({ log }) => log.map(({ edit }) => edit)).sort((left, right) => right.start - left.start).reduce((text, edit) => text.slice(0, edit.start) + edit.text + text.slice(edit.end), manifest);
7579
+ const declaredRuntime = pins.runtime.every(({ name }) => settled.some((section) => section.name === "dependencies" && section.log.some((entry) => entry.name === name)));
7580
+ const declaredDevelopment = pins.development.every(({ name }) => settled.some((section) => section.name === "devDependencies" && section.log.some((entry) => entry.name === name)));
7536
7581
  return declaredRuntime && declaredDevelopment ? compiled : void 0;
7537
7582
  }
7538
7583
  /**
@@ -7573,14 +7618,12 @@ function insertManifestDependencies(manifest, additions) {
7573
7618
  if (requested.length === 0) return manifest;
7574
7619
  const parsed = parseJSON(manifest);
7575
7620
  if (!isRecord(parsed)) return void 0;
7576
- const declared = /* @__PURE__ */ new Set();
7577
- for (const section of ["dependencies", "devDependencies"]) {
7621
+ const declared = new Set(["dependencies", "devDependencies"].flatMap((section) => {
7578
7622
  const entries = parsed[section];
7579
- if (isRecord(entries)) for (const name of Object.keys(entries)) declared.add(name);
7580
- }
7623
+ return isRecord(entries) ? Object.keys(entries) : [];
7624
+ }));
7581
7625
  const names = new Set(requested.map(({ name }) => name));
7582
7626
  if (names.size !== requested.length || [...names].some((name) => declared.has(name))) return;
7583
- let compiled = manifest;
7584
7627
  const sections = [{
7585
7628
  name: "dependencies",
7586
7629
  dependencies: additions.runtime
@@ -7588,131 +7631,113 @@ function insertManifestDependencies(manifest, additions) {
7588
7631
  name: "devDependencies",
7589
7632
  dependencies: additions.development
7590
7633
  }];
7591
- for (const section of sections) {
7592
- if (section.dependencies.length === 0) continue;
7593
- const entries = parsed[section.name];
7594
- if (entries !== void 0 && (!isRecord(entries) || !Object.values(entries).every(isString))) return;
7595
- const ordered = [...section.dependencies].sort((left, right) => compareValues(left.name, right.name));
7596
- const lines = ordered.map(({ name, range }) => `${JSON.stringify(name)}: ${JSON.stringify(range)}`);
7597
- const sibling = section.name === "dependencies" ? "devDependencies" : "dependencies";
7598
- const located = /* @__PURE__ */ new Map();
7599
- let current;
7600
- let head = -1;
7601
- let root = -1;
7602
- let depth = 0;
7603
- let cursor = 0;
7604
- while (cursor < compiled.length && root < 0) {
7605
- const character = compiled.charAt(cursor);
7606
- if (character === "\"") {
7607
- const start = cursor;
7608
- let quote = cursor + 1;
7609
- while (quote < compiled.length && compiled.charAt(quote) !== "\"") quote += compiled.charAt(quote) === "\\" ? 2 : 1;
7610
- if (quote >= compiled.length) return void 0;
7611
- let colon = quote + 1;
7612
- while (/\s/u.test(compiled.charAt(colon))) colon += 1;
7613
- cursor = quote + 1;
7614
- if (depth !== 1 || compiled.charAt(colon) !== ":") continue;
7615
- if (head < 0) head = start;
7616
- const name = parseJSON(compiled.slice(start, quote + 1));
7617
- if (!isString(name) || name !== section.name && name !== sibling) continue;
7618
- if (located.has(name)) continue;
7619
- let brace = colon + 1;
7620
- while (/\s/u.test(compiled.charAt(brace))) brace += 1;
7621
- if (compiled.charAt(brace) !== "{") {
7622
- if (name === section.name) return void 0;
7623
- continue;
7624
- }
7625
- current = {
7626
- name,
7627
- start,
7628
- open: brace
7629
- };
7630
- depth += 1;
7631
- cursor = brace + 1;
7632
- continue;
7633
- }
7634
- if (character === "{") depth += 1;
7635
- else if (character === "}") {
7636
- depth -= 1;
7637
- if (depth === 1 && current !== void 0) {
7638
- located.set(current.name, {
7639
- ...current,
7640
- close: cursor
7641
- });
7642
- current = void 0;
7643
- }
7644
- if (depth === 0) root = cursor;
7645
- }
7646
- cursor += 1;
7647
- }
7648
- const bounds = located.get(section.name);
7649
- if (bounds === void 0) {
7650
- if (entries !== void 0 || root < 0) return void 0;
7651
- const breaking = head < 0 ? -1 : compiled.lastIndexOf("\n", head);
7652
- const margin = breaking < 0 ? "" : compiled.slice(breaking + 1, head);
7653
- const gap = breaking < 0 || /\S/u.test(margin) ? "" : `\n${margin}`;
7654
- const block = `${JSON.stringify(section.name)}: {${lines.map((line) => `${gap === "" ? "" : `${gap}${margin}`}${line}`).join(",")}${gap}}`;
7655
- const neighbor = located.get(sibling);
7656
- let tail = root - 1;
7657
- while (tail > 0 && /\s/u.test(compiled.charAt(tail))) tail -= 1;
7658
- const [position, insertion] = neighbor === void 0 ? [tail + 1, compiled.charAt(tail) === "{" ? block : `,${gap}${block}`] : section.name === "devDependencies" ? [neighbor.close + 1, `,${gap}${block}`] : [neighbor.start, `${block},${gap}`];
7659
- compiled = compiled.slice(0, position) + insertion + compiled.slice(position);
7660
- continue;
7661
- }
7662
- const { open, close } = bounds;
7663
- const declarations = [];
7664
- let key;
7665
- let scan = open + 1;
7666
- while (scan < close) {
7667
- if (compiled.charAt(scan) !== "\"") {
7668
- scan += 1;
7669
- continue;
7670
- }
7671
- let quote = scan + 1;
7672
- while (quote < close && compiled.charAt(quote) !== "\"") quote += compiled.charAt(quote) === "\\" ? 2 : 1;
7673
- if (quote >= close) return void 0;
7674
- if (key === void 0) {
7675
- const name = parseJSON(compiled.slice(scan, quote + 1));
7676
- if (!isString(name)) return void 0;
7677
- key = {
7678
- name,
7679
- start: scan
7680
- };
7681
- } else {
7682
- declarations.push({
7683
- ...key,
7684
- end: quote + 1
7685
- });
7686
- key = void 0;
7687
- }
7688
- scan = quote + 1;
7689
- }
7690
- const first = declarations[0];
7691
- const last = declarations.at(-1);
7692
- const edits = /* @__PURE__ */ new Map();
7693
- if (first === void 0 || last === void 0) {
7694
- const opening = compiled.lastIndexOf("\n", open);
7695
- let column = opening + 1;
7696
- while (column < open && /\s/u.test(compiled.charAt(column))) column += 1;
7697
- const level = opening < 0 ? "" : compiled.slice(opening + 1, column);
7698
- const separator = opening < 0 ? "" : `\n${level}${level}`;
7699
- const ending = opening < 0 ? "" : `\n${level}`;
7700
- compiled = `${compiled.slice(0, open + 1)}${lines.map((line) => `${separator}${line}`).join(",")}${ending}${compiled.slice(close)}`;
7701
- continue;
7702
- }
7703
- const newline = compiled.lastIndexOf("\n", first.start);
7704
- const indent = newline < 0 ? "" : compiled.slice(newline + 1, first.start);
7705
- const separator = newline < 0 || /\S/u.test(indent) ? "" : `\n${indent}`;
7706
- for (const [index, dependency] of ordered.entries()) {
7707
- const line = lines[index] ?? "";
7708
- const anchor = declarations.find((declaration) => compareValues(declaration.name, dependency.name) > 0);
7709
- const position = anchor?.start ?? last.end;
7710
- const text = anchor === void 0 ? `,${separator}${line}` : `${line},${separator}`;
7711
- edits.set(position, `${edits.get(position) ?? ""}${text}`);
7712
- }
7713
- for (const [position, text] of [...edits].sort(([left], [right]) => right - left)) compiled = compiled.slice(0, position) + text + compiled.slice(position);
7714
- }
7715
- return compiled;
7634
+ if (sections.some(({ name, dependencies }) => {
7635
+ const entries = parsed[name];
7636
+ return dependencies.length > 0 && entries !== void 0 && (!isRecord(entries) || !Object.values(entries).every(isString));
7637
+ })) return void 0;
7638
+ return sections.reduce((compiled, section) => compiled === void 0 || section.dependencies.length === 0 ? compiled : insertManifestSection(compiled, section.name, section.dependencies, parsed[section.name] !== void 0), manifest);
7639
+ }
7640
+ /**
7641
+ * Inserts dependencies into one section of package manifest text.
7642
+ *
7643
+ * @param manifest - The manifest text to compile, already known to be a JSON object.
7644
+ * @param section - The section the additions enter: `dependencies` or `devDependencies`.
7645
+ * @param dependencies - The names and ranges to declare; the section declares none of them.
7646
+ * @param present - If `true`, the manifest declares the section as an object; if `false`, it declares none.
7647
+ * @returns The manifest declaring every addition in the section, or `undefined` when the section
7648
+ * cannot be located as an object the text declares.
7649
+ *
7650
+ * @remarks
7651
+ * The one step {@link insertManifestDependencies} repeats per section. Each addition lands before
7652
+ * the first declared key that sorts after it, or after the last declared key when none does, and
7653
+ * copies the indentation of the section's first entry. An empty section takes its entries one
7654
+ * level inside the indentation of its opening line, and an absent section is created as one
7655
+ * top-level key beside its sibling, or last in the manifest object when the sibling is absent too.
7656
+ *
7657
+ * @example
7658
+ * ```ts
7659
+ * import { insertManifestSection } from '@orkestrel/scaffold'
7660
+ *
7661
+ * insertManifestSection('{"dependencies":{}}', 'dependencies', [{ name: 'vite', range: '^8' }], true)
7662
+ * // '{"dependencies":{"vite": "^8"}}'
7663
+ * ```
7664
+ */
7665
+ function insertManifestSection(manifest, section, dependencies, present) {
7666
+ const tokens = scanManifestTokens(manifest);
7667
+ if (tokens === void 0) return void 0;
7668
+ const ordered = [...dependencies].sort((left, right) => compareValues(left.name, right.name));
7669
+ const lines = ordered.map(({ name, range }) => `${JSON.stringify(name)}: ${JSON.stringify(range)}`);
7670
+ const sibling = section === "dependencies" ? "devDependencies" : "dependencies";
7671
+ const occurrences = tokens.flatMap((token, at) => {
7672
+ if (token.depth !== 1 || token.value === void 0) return [];
7673
+ const key = parseJSON(token.text);
7674
+ if (!isString(key) || key !== section && key !== sibling) return [];
7675
+ const object = manifest.charAt(token.value) === "{";
7676
+ const close = object ? tokens.slice(at + 1).find((next) => next.text === "}" && next.depth === 1) : void 0;
7677
+ return [{
7678
+ key,
7679
+ start: token.index,
7680
+ open: token.value,
7681
+ object,
7682
+ close: close?.index
7683
+ }];
7684
+ });
7685
+ const decisive = occurrences.find(({ key, object, close }) => key === section && (!object || close !== void 0));
7686
+ if (decisive !== void 0 && !decisive.object) return void 0;
7687
+ const closed = occurrences.flatMap(({ key, start, open, close }) => close === void 0 ? [] : [{
7688
+ key,
7689
+ start,
7690
+ open,
7691
+ close
7692
+ }]);
7693
+ const bounds = closed.find(({ key }) => key === section);
7694
+ const neighbor = closed.find(({ key }) => key === sibling);
7695
+ const head = tokens.find((token) => token.depth === 1 && token.value !== void 0)?.index;
7696
+ const root = tokens.find((token) => token.text === "}" && token.depth === 0)?.index;
7697
+ if (bounds === void 0) {
7698
+ if (present || root === void 0) return void 0;
7699
+ const breaking = head === void 0 ? -1 : manifest.lastIndexOf("\n", head);
7700
+ const margin = breaking < 0 ? "" : manifest.slice(breaking + 1, head);
7701
+ const gap = breaking < 0 || /\S/u.test(margin) ? "" : `\n${margin}`;
7702
+ const block = `${JSON.stringify(section)}: {${lines.map((line) => `${gap === "" ? "" : `${gap}${margin}`}${line}`).join(",")}${gap}}`;
7703
+ const tail = manifest.slice(1, root).trimEnd().length;
7704
+ const [position, insertion] = neighbor === void 0 ? [tail + 1, manifest.charAt(tail) === "{" ? block : `,${gap}${block}`] : section === "devDependencies" ? [neighbor.close + 1, `,${gap}${block}`] : [neighbor.start, `${block},${gap}`];
7705
+ return manifest.slice(0, position) + insertion + manifest.slice(position);
7706
+ }
7707
+ const { open, close } = bounds;
7708
+ const literals = tokens.filter((token) => token.index > open && token.index < close && token.text.startsWith("\""));
7709
+ const keys = literals.filter((_, index) => index % 2 === 0);
7710
+ if (keys.some((literal) => !isString(parseJSON(literal.text)))) return void 0;
7711
+ const declarations = keys.flatMap((literal, position) => {
7712
+ const name = parseJSON(literal.text);
7713
+ const value = literals[position * 2 + 1];
7714
+ return isString(name) && value !== void 0 ? [{
7715
+ name,
7716
+ start: literal.index,
7717
+ end: value.index + value.text.length
7718
+ }] : [];
7719
+ });
7720
+ const first = declarations[0];
7721
+ const last = declarations.at(-1);
7722
+ if (first === void 0 || last === void 0) {
7723
+ const opening = manifest.lastIndexOf("\n", open);
7724
+ const level = opening < 0 ? "" : /^\s*/u.exec(manifest.slice(opening + 1, open))?.[0] ?? "";
7725
+ const separator = opening < 0 ? "" : `\n${level}${level}`;
7726
+ const ending = opening < 0 ? "" : `\n${level}`;
7727
+ return `${manifest.slice(0, open + 1)}${lines.map((line) => `${separator}${line}`).join(",")}${ending}${manifest.slice(close)}`;
7728
+ }
7729
+ const newline = manifest.lastIndexOf("\n", first.start);
7730
+ const indent = newline < 0 ? "" : manifest.slice(newline + 1, first.start);
7731
+ const separator = newline < 0 || /\S/u.test(indent) ? "" : `\n${indent}`;
7732
+ const placements = ordered.map((dependency, index) => {
7733
+ const line = lines[index] ?? "";
7734
+ const anchor = declarations.find((declaration) => compareValues(declaration.name, dependency.name) > 0);
7735
+ return {
7736
+ position: anchor?.start ?? last.end,
7737
+ text: anchor === void 0 ? `,${separator}${line}` : `${line},${separator}`
7738
+ };
7739
+ });
7740
+ return [...Map.groupBy(placements, ({ position }) => position)].map(([position, group]) => [position, group.map(({ text }) => text).join("")]).sort(([left], [right]) => right - left).reduce((text, [position, insertion]) => text.slice(0, position) + insertion + text.slice(position), manifest);
7716
7741
  }
7717
7742
  /**
7718
7743
  * Replaces named script values in package manifest text.
@@ -7756,191 +7781,86 @@ function replaceManifestScripts(manifest, scripts) {
7756
7781
  const parsed = parseJSON(manifest);
7757
7782
  if (!isRecord(parsed) || !isRecord(parsed.scripts)) return void 0;
7758
7783
  const declared = parsed.scripts;
7759
- for (const script of scripts) {
7760
- if (!Object.hasOwn(declared, script.name)) continue;
7761
- const value = declared[script.name];
7762
- if (!isString(value)) return void 0;
7763
- }
7764
- let depth = 0;
7765
- let cursor = 0;
7766
- let start = -1;
7767
- let end = -1;
7768
- while (cursor < manifest.length && end < 0) {
7769
- const character = manifest.charAt(cursor);
7770
- if (character === "{") {
7771
- depth += 1;
7772
- cursor += 1;
7773
- continue;
7774
- }
7775
- if (character === "}") {
7776
- depth -= 1;
7777
- cursor += 1;
7778
- continue;
7779
- }
7780
- if (character !== "\"") {
7781
- cursor += 1;
7782
- continue;
7783
- }
7784
- const keyStart = cursor;
7785
- cursor += 1;
7786
- while (cursor < manifest.length) {
7787
- if (manifest.charAt(cursor) === "\\") {
7788
- cursor += 2;
7789
- continue;
7790
- }
7791
- if (manifest.charAt(cursor) === "\"") break;
7792
- cursor += 1;
7793
- }
7794
- if (cursor >= manifest.length) return void 0;
7795
- const keyEnd = cursor + 1;
7796
- cursor = keyEnd;
7797
- if (depth !== 1) continue;
7798
- if (parseJSON(manifest.slice(keyStart, keyEnd)) !== "scripts") continue;
7799
- let opening = keyEnd;
7800
- while (opening < manifest.length && /\s/u.test(manifest.charAt(opening))) opening += 1;
7801
- if (manifest.charAt(opening) !== ":") continue;
7802
- opening += 1;
7803
- while (opening < manifest.length && /\s/u.test(manifest.charAt(opening))) opening += 1;
7804
- if (manifest.charAt(opening) !== "{") continue;
7805
- start = opening;
7806
- let nested = 0;
7807
- let scan = opening;
7808
- while (scan < manifest.length) {
7809
- const inner = manifest.charAt(scan);
7810
- if (inner === "\"") {
7811
- scan += 1;
7812
- while (scan < manifest.length) {
7813
- if (manifest.charAt(scan) === "\\") {
7814
- scan += 2;
7815
- continue;
7816
- }
7817
- if (manifest.charAt(scan) === "\"") break;
7818
- scan += 1;
7819
- }
7820
- if (scan >= manifest.length) return void 0;
7821
- } else if (inner === "{") nested += 1;
7822
- else if (inner === "}") {
7823
- nested -= 1;
7824
- if (nested === 0) {
7825
- end = scan + 1;
7826
- break;
7827
- }
7828
- }
7829
- scan += 1;
7830
- }
7831
- }
7832
- if (start < 0 || end < 0) return void 0;
7833
- const edits = [];
7834
- const written = /* @__PURE__ */ new Set();
7835
- const anchors = [];
7836
- let first = -1;
7837
- let nested = 0;
7838
- let scan = start;
7839
- while (scan < end) {
7840
- const character = manifest.charAt(scan);
7841
- if (character === "{") {
7842
- nested += 1;
7843
- scan += 1;
7844
- continue;
7845
- }
7846
- if (character === "}") {
7847
- nested -= 1;
7848
- scan += 1;
7849
- continue;
7850
- }
7851
- if (character !== "\"") {
7852
- scan += 1;
7853
- continue;
7854
- }
7855
- const keyStart = scan;
7856
- scan += 1;
7857
- while (scan < end) {
7858
- if (manifest.charAt(scan) === "\\") {
7859
- scan += 2;
7860
- continue;
7861
- }
7862
- if (manifest.charAt(scan) === "\"") break;
7863
- scan += 1;
7864
- }
7865
- if (scan >= end) return void 0;
7866
- const keyEnd = scan + 1;
7867
- scan = keyEnd;
7868
- if (nested !== 1) continue;
7869
- let valueStart = keyEnd;
7870
- while (valueStart < end && /\s/u.test(manifest.charAt(valueStart))) valueStart += 1;
7871
- if (manifest.charAt(valueStart) !== ":") continue;
7872
- valueStart += 1;
7873
- while (valueStart < end && /\s/u.test(manifest.charAt(valueStart))) valueStart += 1;
7874
- if (first < 0) first = keyStart;
7875
- if (manifest.charAt(valueStart) !== "\"") continue;
7876
- let valueEnd = valueStart + 1;
7877
- while (valueEnd < end) {
7878
- if (manifest.charAt(valueEnd) === "\\") {
7879
- valueEnd += 2;
7880
- continue;
7881
- }
7882
- if (manifest.charAt(valueEnd) === "\"") break;
7883
- valueEnd += 1;
7884
- }
7885
- if (valueEnd >= end) return void 0;
7886
- scan = valueEnd + 1;
7887
- const key = parseJSON(manifest.slice(keyStart, keyEnd));
7888
- if (isString(key)) anchors.push({
7889
- name: key,
7890
- end: valueEnd + 1
7891
- });
7892
- const script = scripts.find((entry) => entry.name === key);
7893
- if (script === void 0) continue;
7894
- written.add(script.name);
7895
- const value = declared[script.name];
7896
- if (value === script.command || !isString(value) || !script.accepted.includes(value)) continue;
7897
- edits.push({
7898
- start: valueStart,
7899
- end: valueEnd + 1,
7784
+ if (Iterator.from(scripts).some((script) => Object.hasOwn(declared, script.name) && !isString(declared[script.name]))) return;
7785
+ const tokens = scanManifestTokens(manifest);
7786
+ if (tokens === void 0) return void 0;
7787
+ const position = tokens.findIndex((token, at) => token.depth === 1 && token.value !== void 0 && parseJSON(token.text) === "scripts" && manifest.charAt(token.value) === "{" && tokens.slice(at + 1).some((next) => next.text === "}" && next.depth === 1));
7788
+ const region = tokens[position];
7789
+ const close = tokens.slice(position + 1).find((token) => token.text === "}" && token.depth === 1);
7790
+ if (region?.value === void 0 || close === void 0) return void 0;
7791
+ const start = region.value;
7792
+ const end = close.index + 1;
7793
+ const entries = tokens.reduce((log, token, at) => {
7794
+ if (log === void 0) return void 0;
7795
+ if (token.index < start || token.index >= end) return log;
7796
+ if (token.depth !== 2 || token.value === void 0) return log;
7797
+ if (manifest.charAt(token.value) !== "\"") return [...log, {
7798
+ token,
7799
+ value: token.value,
7800
+ literal: void 0
7801
+ }];
7802
+ const literal = tokens[at + 1];
7803
+ return literal === void 0 ? void 0 : [...log, {
7804
+ token,
7805
+ value: token.value,
7806
+ literal
7807
+ }];
7808
+ }, []);
7809
+ if (entries === void 0) return void 0;
7810
+ const first = entries[0]?.token.index;
7811
+ const keyed = entries.flatMap(({ token, value, literal }) => literal === void 0 ? [] : [{
7812
+ key: parseJSON(token.text),
7813
+ value,
7814
+ stop: literal.index + literal.text.length
7815
+ }]);
7816
+ const anchors = keyed.flatMap(({ key, stop }) => isString(key) ? [{
7817
+ name: key,
7818
+ end: stop
7819
+ }] : []);
7820
+ const matched = keyed.flatMap((entry) => {
7821
+ const script = scripts.find((candidate) => candidate.name === entry.key);
7822
+ return script === void 0 ? [] : [{
7823
+ script,
7824
+ value: entry.value,
7825
+ stop: entry.stop
7826
+ }];
7827
+ });
7828
+ const written = new Set(matched.map(({ script }) => script.name));
7829
+ const replaced = matched.flatMap(({ script, value, stop }) => {
7830
+ const current = declared[script.name];
7831
+ return current === script.command || !isString(current) || !script.accepted.includes(current) ? [] : [{
7832
+ start: value,
7833
+ end: stop,
7900
7834
  text: JSON.stringify(script.command)
7901
- });
7902
- }
7835
+ }];
7836
+ });
7903
7837
  const missing = scripts.filter((script) => !written.has(script.name));
7904
- if (missing.length > 0) {
7905
- if (first < 0) {
7906
- const opening = manifest.lastIndexOf("\n", start);
7907
- let column = opening + 1;
7908
- while (column < start && /\s/u.test(manifest.charAt(column))) column += 1;
7909
- const level = opening < 0 ? "" : manifest.slice(opening + 1, column);
7910
- const separator = opening < 0 ? "" : `\n${level}${level}`;
7911
- const closing = opening < 0 ? "" : `\n${level}`;
7912
- const entries = missing.map((script) => `${separator}${JSON.stringify(script.name)}: ${JSON.stringify(script.command)}`);
7913
- edits.push({
7914
- start: start + 1,
7915
- end: end - 1,
7916
- text: `${entries.join(",")}${closing}`
7917
- });
7918
- } else {
7919
- let tail = end - 1;
7920
- while (tail > start && /\s/u.test(manifest.charAt(tail - 1))) tail -= 1;
7921
- const newline = manifest.lastIndexOf("\n", first);
7922
- const indent = newline < 0 ? "" : manifest.slice(newline + 1, first);
7923
- const separator = newline < 0 || /\S/u.test(indent) ? "" : `\n${indent}`;
7924
- const placed = /* @__PURE__ */ new Map();
7925
- for (const script of missing) {
7926
- const colon = script.name.indexOf(":");
7927
- const family = colon < 0 ? void 0 : script.name.slice(0, colon + 1);
7928
- const anchor = (family === void 0 ? void 0 : anchors.findLast((entry) => entry.name.startsWith(family)))?.end ?? tail;
7929
- const text = `,${separator}${JSON.stringify(script.name)}: ${JSON.stringify(script.command)}`;
7930
- const queued = placed.get(anchor);
7931
- if (queued === void 0) placed.set(anchor, [text]);
7932
- else queued.push(text);
7933
- }
7934
- for (const [anchor, queued] of placed) edits.push({
7935
- start: anchor,
7936
- end: anchor,
7937
- text: queued.join("")
7938
- });
7939
- }
7940
- }
7941
- let compiled = manifest;
7942
- for (const edit of edits.sort((left, right) => right.start - left.start)) compiled = compiled.slice(0, edit.start) + edit.text + compiled.slice(edit.end);
7943
- return compiled;
7838
+ const opening = manifest.lastIndexOf("\n", start);
7839
+ const level = opening < 0 ? "" : /^\s*/u.exec(manifest.slice(opening + 1, start))?.[0] ?? "";
7840
+ const emptySeparator = opening < 0 ? "" : `\n${level}${level}`;
7841
+ const emptyClosing = opening < 0 ? "" : `\n${level}`;
7842
+ const tail = start + manifest.slice(start, end - 1).trimEnd().length;
7843
+ const newline = first === void 0 ? -1 : manifest.lastIndexOf("\n", first);
7844
+ const indent = first === void 0 || newline < 0 ? "" : manifest.slice(newline + 1, first);
7845
+ const separator = newline < 0 || /\S/u.test(indent) ? "" : `\n${indent}`;
7846
+ const placements = missing.map((script) => {
7847
+ const colon = script.name.indexOf(":");
7848
+ const family = colon < 0 ? void 0 : script.name.slice(0, colon + 1);
7849
+ return {
7850
+ anchor: (family === void 0 ? void 0 : anchors.findLast((entry) => entry.name.startsWith(family)))?.end ?? tail,
7851
+ text: `,${separator}${JSON.stringify(script.name)}: ${JSON.stringify(script.command)}`
7852
+ };
7853
+ });
7854
+ const appended = missing.length === 0 ? [] : first === void 0 ? [{
7855
+ start: start + 1,
7856
+ end: end - 1,
7857
+ text: `${missing.map((script) => `${emptySeparator}${JSON.stringify(script.name)}: ${JSON.stringify(script.command)}`).join(",")}${emptyClosing}`
7858
+ }] : [...Map.groupBy(placements, ({ anchor }) => anchor)].map(([anchor, group]) => ({
7859
+ start: anchor,
7860
+ end: anchor,
7861
+ text: group.map(({ text }) => text).join("")
7862
+ }));
7863
+ return [...replaced, ...appended].sort((left, right) => right.start - left.start).reduce((text, edit) => text.slice(0, edit.start) + edit.text + text.slice(edit.end), manifest);
7944
7864
  }
7945
7865
  /**
7946
7866
  * Replaces dependency ranges in a plan's manifest and recomputes its identity.
@@ -7964,22 +7884,28 @@ function replaceManifestScripts(manifest, scripts) {
7964
7884
  * ```
7965
7885
  */
7966
7886
  function replacePlanRanges(plan, pins) {
7967
- let replaced = false;
7968
- let refused = false;
7969
- const artifacts = plan.artifacts.map((artifact) => {
7970
- if (artifact.path !== "package.json" || artifact.origin === "host") return artifact;
7887
+ const outcomes = plan.artifacts.map((artifact) => {
7888
+ if (artifact.path !== "package.json" || artifact.origin === "host") return {
7889
+ artifact,
7890
+ manifest: false,
7891
+ refused: false
7892
+ };
7971
7893
  const content = replaceManifestRanges(artifact.content, pins);
7972
- if (content === void 0) {
7973
- refused = true;
7974
- return artifact;
7975
- }
7976
- replaced = true;
7977
- return {
7978
- ...artifact,
7979
- content
7894
+ return content === void 0 ? {
7895
+ artifact,
7896
+ manifest: true,
7897
+ refused: true
7898
+ } : {
7899
+ artifact: {
7900
+ ...artifact,
7901
+ content
7902
+ },
7903
+ manifest: true,
7904
+ refused: false
7980
7905
  };
7981
7906
  });
7982
- if (!replaced || refused) return void 0;
7907
+ if (!outcomes.some((outcome) => outcome.manifest) || outcomes.some((outcome) => outcome.refused)) return;
7908
+ const artifacts = outcomes.map((outcome) => outcome.artifact);
7983
7909
  const compiled = {
7984
7910
  ...plan,
7985
7911
  artifacts
@@ -8054,24 +7980,21 @@ function planToHash(plan) {
8054
7980
  * ```
8055
7981
  */
8056
7982
  function planToFindings(plan, current) {
8057
- const findings = [];
8058
- const planned = /* @__PURE__ */ new Set();
8059
- for (const artifact of plan.artifacts) {
8060
- planned.add(artifact.path);
8061
- findings.push(artifactToFinding(artifact, current[artifact.path]));
8062
- }
8063
- for (const [path, observed] of Object.entries(current)) {
8064
- if (planned.has(path)) continue;
7983
+ const listed = Iterator.from(plan.artifacts).map((artifact) => ({
7984
+ path: artifact.path,
7985
+ finding: artifactToFinding(artifact, current[artifact.path])
7986
+ })).toArray();
7987
+ const planned = new Set(listed.map(({ path }) => path));
7988
+ return [...listed.map(({ finding }) => finding), ...Object.entries(current).flatMap(([path, observed]) => {
7989
+ if (planned.has(path)) return [];
8065
7990
  const group = inferGroup(path);
8066
- if (!plan.groups.includes(group)) continue;
8067
- findings.push({
7991
+ return plan.groups.includes(group) ? [{
8068
7992
  path,
8069
7993
  group,
8070
7994
  drift: "foreign",
8071
7995
  observed
8072
- });
8073
- }
8074
- return findings;
7996
+ }] : [];
7997
+ })];
8075
7998
  }
8076
7999
  /**
8077
8000
  * Measures one declared package list against the name and range syntax it accepts.
@@ -8108,27 +8031,30 @@ function planToFindings(plan, current) {
8108
8031
  * ```
8109
8032
  */
8110
8033
  function dependenciesToQuestions(dependencies, field, name, range) {
8111
- const questions = [];
8112
- const seen = /* @__PURE__ */ new Set();
8113
- for (const dependency of dependencies) {
8114
- if (!name.test(dependency.name)) questions.push({
8115
- field,
8116
- message: `${dependency.name} is not a package name ${field} accepts.`,
8117
- blocking: true
8118
- });
8119
- else if (seen.has(dependency.name)) questions.push({
8120
- field,
8121
- message: `${dependency.name} is declared more than once on ${field}.`,
8122
- blocking: true
8123
- });
8124
- seen.add(dependency.name);
8125
- if (!range.test(dependency.range)) questions.push({
8126
- field,
8127
- message: `${dependency.name} declares the range ${dependency.range}, which ${field} does not accept.`,
8128
- blocking: true
8129
- });
8130
- }
8131
- return questions;
8034
+ const listed = Iterator.from(dependencies).map((dependency) => {
8035
+ const declared = dependency.name;
8036
+ const valid = name.test(declared);
8037
+ const pinned = dependency.range;
8038
+ return {
8039
+ declared,
8040
+ valid,
8041
+ pinned,
8042
+ accepted: range.test(pinned)
8043
+ };
8044
+ }).toArray();
8045
+ return listed.flatMap(({ declared, valid, pinned, accepted }, index) => [...!valid ? [{
8046
+ field,
8047
+ message: `${declared} is not a package name ${field} accepts.`,
8048
+ blocking: true
8049
+ }] : listed.findIndex((other) => other.declared === declared) < index ? [{
8050
+ field,
8051
+ message: `${declared} is declared more than once on ${field}.`,
8052
+ blocking: true
8053
+ }] : [], ...!accepted ? [{
8054
+ field,
8055
+ message: `${declared} declares the range ${pinned}, which ${field} does not accept.`,
8056
+ blocking: true
8057
+ }] : []]);
8132
8058
  }
8133
8059
  /**
8134
8060
  * Measures a blueprint against every law its own fields decide.
@@ -8176,131 +8102,125 @@ function dependenciesToQuestions(dependencies, field, name, range) {
8176
8102
  * ```
8177
8103
  */
8178
8104
  function blueprintToQuestions(blueprint) {
8179
- const questions = [];
8180
- if (!NAME_PATTERN.test(blueprint.name)) questions.push({
8181
- field: "name",
8182
- message: `${blueprint.name} is not a lowercase alphanumeric name starting with a letter.`,
8183
- blocking: true
8184
- });
8185
- if (!VERSION_PATTERN.test(blueprint.version)) questions.push({
8186
- field: "version",
8187
- message: `${blueprint.version} is not an exact three-component version.`,
8188
- blocking: true
8189
- });
8190
- if (!matchesEngines(blueprint.engines)) questions.push({
8191
- field: "engines",
8192
- message: `${blueprint.engines} is not a supported minimum-Node floor.`,
8193
- blocking: true
8194
- });
8195
- if (blueprint.src.length === 0 && blueprint.app.length === 0 && blueprintToSheets(blueprint).length === 0 && !blueprint.themes) questions.push({
8196
- field: "src",
8197
- message: "A workspace declares at least one environment on src or app.",
8198
- blocking: true,
8199
- candidates: ENVIRONMENTS
8200
- });
8201
- if (blueprint.src.length > 1 && !blueprint.src.includes("core")) questions.push({
8202
- field: "src",
8203
- message: "Several published environments put core at the package root, so this manifest names a core build the workspace never runs. Declare core on src, or publish one environment.",
8204
- blocking: false,
8205
- candidates: ENVIRONMENTS
8206
- });
8207
- for (const environment of ENVIRONMENTS) {
8208
- if (blueprint.src.filter((declared) => declared === environment).length > 1) questions.push({
8105
+ const own = `@orkestrel/${blueprint.name}`;
8106
+ return [
8107
+ ...!NAME_PATTERN.test(blueprint.name) ? [{
8108
+ field: "name",
8109
+ message: `${blueprint.name} is not a lowercase alphanumeric name starting with a letter.`,
8110
+ blocking: true
8111
+ }] : [],
8112
+ ...!VERSION_PATTERN.test(blueprint.version) ? [{
8113
+ field: "version",
8114
+ message: `${blueprint.version} is not an exact three-component version.`,
8115
+ blocking: true
8116
+ }] : [],
8117
+ ...!matchesEngines(blueprint.engines) ? [{
8118
+ field: "engines",
8119
+ message: `${blueprint.engines} is not a supported minimum-Node floor.`,
8120
+ blocking: true
8121
+ }] : [],
8122
+ ...blueprint.src.length === 0 && blueprint.app.length === 0 && blueprintToSheets(blueprint).length === 0 && !blueprint.themes ? [{
8123
+ field: "src",
8124
+ message: "A workspace declares at least one environment on src or app.",
8125
+ blocking: true,
8126
+ candidates: ENVIRONMENTS
8127
+ }] : [],
8128
+ ...blueprint.src.length > 1 && !blueprint.src.includes("core") ? [{
8129
+ field: "src",
8130
+ message: "Several published environments put core at the package root, so this manifest names a core build the workspace never runs. Declare core on src, or publish one environment.",
8131
+ blocking: false,
8132
+ candidates: ENVIRONMENTS
8133
+ }] : [],
8134
+ ...ENVIRONMENTS.flatMap((environment) => [...blueprint.src.filter((declared) => declared === environment).length > 1 ? [{
8209
8135
  field: "src",
8210
8136
  message: `src declares ${environment} more than once.`,
8211
8137
  blocking: true,
8212
8138
  candidates: ENVIRONMENTS
8213
- });
8214
- if (blueprint.app.filter((declared) => declared === environment).length > 1) questions.push({
8139
+ }] : [], ...blueprint.app.filter((declared) => declared === environment).length > 1 ? [{
8215
8140
  field: "app",
8216
8141
  message: `app declares ${environment} more than once.`,
8217
8142
  blocking: true,
8218
8143
  candidates: ENVIRONMENTS
8219
- });
8220
- }
8221
- const extensions = /* @__PURE__ */ new Set();
8222
- for (const extension of blueprint.extensions) {
8223
- const selection = `${extension.surface}:${extension.name}`;
8224
- if (extensions.has(selection)) questions.push({
8144
+ }] : []]),
8145
+ ...Iterator.from(blueprint.extensions).map((extension) => {
8146
+ const selection = `${extension.surface}:${extension.name}`;
8147
+ return {
8148
+ selection,
8149
+ questions: extension.surface === "browser" ? [...extension.axes.length === 0 ? [{
8150
+ field: "extensions",
8151
+ message: `${selection} occupies no axis.`,
8152
+ blocking: false
8153
+ }] : [], ...Iterator.from(extension.axes).flatMap((axis) => blueprint[axis].includes("browser") ? [] : [{
8154
+ field: "extensions",
8155
+ message: `${selection} occupies ${axis}, whose selection lacks browser.`,
8156
+ blocking: false
8157
+ }]).toArray()] : !blueprint.styles ? [{
8158
+ field: "extensions",
8159
+ message: `${selection} extends a styles surface this workspace does not declare.`,
8160
+ blocking: false
8161
+ }] : []
8162
+ };
8163
+ }).toArray().flatMap(({ selection, questions }, index, selected) => [...selected.findIndex((other) => other.selection === selection) < index ? [{
8225
8164
  field: "extensions",
8226
8165
  message: `${selection} is declared more than once on extensions.`,
8227
8166
  blocking: true
8228
- });
8229
- extensions.add(selection);
8230
- if (extension.surface === "browser") {
8231
- if (extension.axes.length === 0) questions.push({
8232
- field: "extensions",
8233
- message: `${selection} occupies no axis.`,
8234
- blocking: false
8235
- });
8236
- for (const axis of extension.axes) if (!blueprint[axis].includes("browser")) questions.push({
8237
- field: "extensions",
8238
- message: `${selection} occupies ${axis}, whose selection lacks browser.`,
8239
- blocking: false
8240
- });
8241
- } else if (!blueprint.styles) questions.push({
8242
- field: "extensions",
8243
- message: `${selection} extends a styles surface this workspace does not declare.`,
8167
+ }] : [], ...questions]),
8168
+ ...blueprint.integration && blueprint.src.length + blueprint.app.length < 2 ? [{
8169
+ field: "integration",
8170
+ message: "integration drives features across environments, and this workspace declares fewer than two, so its seed composes nothing.",
8244
8171
  blocking: false
8245
- });
8246
- }
8247
- if (blueprint.integration && blueprint.src.length + blueprint.app.length < 2) questions.push({
8248
- field: "integration",
8249
- message: "integration drives features across environments, and this workspace declares fewer than two, so its seed composes nothing.",
8250
- blocking: false
8251
- });
8252
- const vendors = /* @__PURE__ */ new Set();
8253
- for (const vendor of blueprint.vendors) {
8254
- if (!NAME_PATTERN.test(vendor)) questions.push({
8172
+ }] : [],
8173
+ ...Iterator.from(blueprint.vendors).map((vendor) => ({
8174
+ vendor,
8175
+ valid: NAME_PATTERN.test(vendor)
8176
+ })).toArray().flatMap(({ vendor, valid }, index, declared) => !valid ? [{
8255
8177
  field: "vendors",
8256
8178
  message: `${vendor} is not a lowercase alphanumeric vendor name starting with a letter.`,
8257
8179
  blocking: true
8258
- });
8259
- else if (vendors.has(vendor)) questions.push({
8180
+ }] : declared.findIndex((other) => other.vendor === vendor) < index ? [{
8260
8181
  field: "vendors",
8261
8182
  message: `${vendor} is declared more than once on vendors.`,
8262
8183
  blocking: true
8263
- });
8264
- vendors.add(vendor);
8265
- }
8266
- if (blueprint.showcase && !blueprint.app.includes("browser")) questions.push({
8267
- field: "showcase",
8268
- message: "showcase projects a browser app, and this workspace declares none, so it emits nothing.",
8269
- blocking: false
8270
- });
8271
- if (blueprint.journey && !blueprint.app.includes("browser")) questions.push({
8272
- field: "journey",
8273
- message: "journey projects a browser app, and this workspace declares none, so it emits nothing.",
8274
- blocking: false
8275
- });
8276
- questions.push(...dependenciesToQuestions(blueprint.dependencies, "dependencies", DEPENDENCY_NAME_PATTERN, ORKESTREL_RANGE_PATTERN), ...dependenciesToQuestions(blueprint.peers.filter((peer) => peer.name.startsWith("@orkestrel/")), "peers", DEPENDENCY_NAME_PATTERN, ORKESTREL_RANGE_PATTERN), ...dependenciesToQuestions(blueprint.peers.filter((peer) => !peer.name.startsWith("@orkestrel/")), "peers", FOREIGN_NAME_PATTERN, FLOOR_RANGE_PATTERN), ...dependenciesToQuestions(blueprint.extras, "extras", FOREIGN_NAME_PATTERN, EXTRA_RANGE_PATTERN));
8277
- const lists = [
8278
- ["dependencies", blueprint.dependencies],
8279
- ["peers", blueprint.peers],
8280
- ["extras", blueprint.extras]
8281
- ];
8282
- const declared = /* @__PURE__ */ new Map();
8283
- const own = `@orkestrel/${blueprint.name}`;
8284
- for (const [field, list] of lists) for (const dependency of list) {
8285
- if (dependency.name === own || dependency.name === blueprint.name) questions.push({
8286
- field,
8287
- message: `${dependency.name} is the workspace itself.`,
8288
- blocking: true
8289
- });
8290
- const first = declared.get(dependency.name);
8291
- if (first === void 0) declared.set(dependency.name, field);
8292
- else if (first !== field) questions.push({
8184
+ }] : []),
8185
+ ...blueprint.showcase && !blueprint.app.includes("browser") ? [{
8186
+ field: "showcase",
8187
+ message: "showcase projects a browser app, and this workspace declares none, so it emits nothing.",
8188
+ blocking: false
8189
+ }] : [],
8190
+ ...blueprint.journey && !blueprint.app.includes("browser") ? [{
8191
+ field: "journey",
8192
+ message: "journey projects a browser app, and this workspace declares none, so it emits nothing.",
8193
+ blocking: false
8194
+ }] : [],
8195
+ ...dependenciesToQuestions(blueprint.dependencies, "dependencies", DEPENDENCY_NAME_PATTERN, ORKESTREL_RANGE_PATTERN),
8196
+ ...dependenciesToQuestions(blueprint.peers.filter((peer) => peer.name.startsWith("@orkestrel/")), "peers", DEPENDENCY_NAME_PATTERN, ORKESTREL_RANGE_PATTERN),
8197
+ ...dependenciesToQuestions(blueprint.peers.filter((peer) => !peer.name.startsWith("@orkestrel/")), "peers", FOREIGN_NAME_PATTERN, FLOOR_RANGE_PATTERN),
8198
+ ...dependenciesToQuestions(blueprint.extras, "extras", FOREIGN_NAME_PATTERN, EXTRA_RANGE_PATTERN),
8199
+ ...[
8200
+ ["dependencies", blueprint.dependencies],
8201
+ ["peers", blueprint.peers],
8202
+ ["extras", blueprint.extras]
8203
+ ].flatMap(([field, list]) => Iterator.from(list).map((dependency) => ({
8293
8204
  field,
8294
- message: `${dependency.name} is declared on both ${first} and ${field}.`,
8205
+ name: dependency.name
8206
+ })).toArray()).flatMap(({ field, name }, _, declared) => {
8207
+ const first = declared.find((other) => other.name === name)?.field;
8208
+ return [...name === own || name === blueprint.name ? [{
8209
+ field,
8210
+ message: `${name} is the workspace itself.`,
8211
+ blocking: true
8212
+ }] : [], ...first !== void 0 && first !== field ? [{
8213
+ field,
8214
+ message: `${name} is declared on both ${first} and ${field}.`,
8215
+ blocking: true
8216
+ }] : []];
8217
+ }),
8218
+ ...Iterator.from(blueprint.extras).flatMap((extra) => Object.hasOwn(BASE_DEV_DEPENDENCIES, extra.name) ? [{
8219
+ field: "extras",
8220
+ message: `${extra.name} is pinned by the shared toolchain, which every workspace carries at one version.`,
8295
8221
  blocking: true
8296
- });
8297
- }
8298
- for (const extra of blueprint.extras) if (Object.hasOwn(BASE_DEV_DEPENDENCIES, extra.name)) questions.push({
8299
- field: "extras",
8300
- message: `${extra.name} is pinned by the shared toolchain, which every workspace carries at one version.`,
8301
- blocking: true
8302
- });
8303
- return questions;
8222
+ }] : []).toArray()
8223
+ ];
8304
8224
  }
8305
8225
  /**
8306
8226
  * Measures a drafted artifact list against the laws a whole plan decides.
@@ -8329,35 +8249,32 @@ function blueprintToQuestions(blueprint) {
8329
8249
  * ```
8330
8250
  */
8331
8251
  function artifactsToQuestions(artifacts) {
8332
- const questions = [];
8333
- const claimed = /* @__PURE__ */ new Set();
8334
- let total = 0;
8335
- for (const artifact of artifacts) {
8336
- if (claimed.has(artifact.path)) questions.push({
8252
+ const listed = Iterator.from(artifacts).map((artifact) => ({
8253
+ path: artifact.path,
8254
+ bytes: artifact.origin === "host" ? artifact.hex === void 0 ? 0 : artifact.hex.length / 2 : computeBytes(artifact.content)
8255
+ })).toArray();
8256
+ const total = computeTotal(listed.map(({ bytes }) => bytes));
8257
+ return [
8258
+ ...listed.flatMap(({ path, bytes }, index) => [...listed.findIndex((other) => other.path === path) < index ? [{
8337
8259
  field: "artifacts",
8338
- message: `Two artifacts claim ${artifact.path}.`,
8260
+ message: `Two artifacts claim ${path}.`,
8339
8261
  blocking: true
8340
- });
8341
- claimed.add(artifact.path);
8342
- const bytes = artifact.origin === "host" ? artifact.hex === void 0 ? 0 : artifact.hex.length / 2 : computeBytes(artifact.content);
8343
- if (bytes > 5242880) questions.push({
8262
+ }] : [], ...bytes > 5242880 ? [{
8344
8263
  field: "artifacts",
8345
- message: `${artifact.path} carries ${bytes} bytes, above the ${MAX_ARTIFACT_BYTES} one artifact admits.`,
8264
+ message: `${path} carries ${bytes} bytes, above the ${MAX_ARTIFACT_BYTES} one artifact admits.`,
8346
8265
  blocking: true
8347
- });
8348
- total += bytes;
8349
- }
8350
- if (artifacts.length > 1e3) questions.push({
8351
- field: "artifacts",
8352
- message: `A plan of ${artifacts.length} artifacts is above the ${MAX_COLLECTION_ITEMS} one collection admits.`,
8353
- blocking: true
8354
- });
8355
- if (total > 104857600) questions.push({
8356
- field: "artifacts",
8357
- message: `A plan of ${total} bytes is above the ${MAX_TOTAL_ARTIFACT_BYTES} one plan retains.`,
8358
- blocking: true
8359
- });
8360
- return questions;
8266
+ }] : []]),
8267
+ ...artifacts.length > 1e3 ? [{
8268
+ field: "artifacts",
8269
+ message: `A plan of ${artifacts.length} artifacts is above the ${MAX_COLLECTION_ITEMS} one collection admits.`,
8270
+ blocking: true
8271
+ }] : [],
8272
+ ...total > 104857600 ? [{
8273
+ field: "artifacts",
8274
+ message: `A plan of ${total} bytes is above the ${MAX_TOTAL_ARTIFACT_BYTES} one plan retains.`,
8275
+ blocking: true
8276
+ }] : []
8277
+ ];
8361
8278
  }
8362
8279
  /**
8363
8280
  * Measures a blueprint's overrides against the artifacts drafted for it.
@@ -8385,41 +8302,28 @@ function artifactsToQuestions(artifacts) {
8385
8302
  * ```
8386
8303
  */
8387
8304
  function overridesToQuestions(overrides, artifacts) {
8388
- const questions = [];
8389
- const planned = /* @__PURE__ */ new Map();
8390
- for (const artifact of artifacts) if (!planned.has(artifact.path)) planned.set(artifact.path, artifact);
8391
- const seen = /* @__PURE__ */ new Set();
8392
- for (const override of overrides) {
8393
- if (seen.has(override.path)) questions.push({
8305
+ const planned = new Map(Iterator.from(artifacts).map((artifact) => [artifact.path, artifact]).toArray().reverse());
8306
+ const listed = Iterator.from(overrides).map((override) => override.path).toArray();
8307
+ return listed.flatMap((path, index) => {
8308
+ const artifact = planned.get(path);
8309
+ return [...listed.indexOf(path) < index ? [{
8394
8310
  field: "overrides",
8395
- message: `${override.path} is overridden more than once.`,
8311
+ message: `${path} is overridden more than once.`,
8396
8312
  blocking: true
8397
- });
8398
- seen.add(override.path);
8399
- const artifact = planned.get(override.path);
8400
- if (artifact === void 0) {
8401
- questions.push({
8402
- field: "overrides",
8403
- message: `${override.path} is not a path this plan carries.`,
8404
- blocking: true
8405
- });
8406
- continue;
8407
- }
8408
- if (artifact.origin === "host") {
8409
- questions.push({
8410
- field: "overrides",
8411
- message: `${override.path} is byte-copied from the vendored data root.`,
8412
- blocking: true
8413
- });
8414
- continue;
8415
- }
8416
- if (artifact.group === "manifest") questions.push({
8313
+ }] : [], ...artifact === void 0 ? [{
8417
8314
  field: "overrides",
8418
- message: `${override.path} is the manifest, which the blueprint's own fields decide.`,
8315
+ message: `${path} is not a path this plan carries.`,
8419
8316
  blocking: true
8420
- });
8421
- }
8422
- return questions;
8317
+ }] : artifact.origin === "host" ? [{
8318
+ field: "overrides",
8319
+ message: `${path} is byte-copied from the vendored data root.`,
8320
+ blocking: true
8321
+ }] : artifact.group === "manifest" ? [{
8322
+ field: "overrides",
8323
+ message: `${path} is the manifest, which the blueprint's own fields decide.`,
8324
+ blocking: true
8325
+ }] : []];
8326
+ });
8423
8327
  }
8424
8328
  //#endregion
8425
8329
  //#region src/core/factories.ts
@@ -8489,6 +8393,7 @@ function createBlueprint(name, input) {
8489
8393
  conformance: input?.conformance ?? false,
8490
8394
  service: input?.service ?? false,
8491
8395
  vendors: input?.vendors ?? [],
8396
+ studies: input?.studies ?? false,
8492
8397
  global: input?.global ?? false,
8493
8398
  showcase: input?.showcase ?? false,
8494
8399
  journey: input?.journey ?? false,
@@ -8675,9 +8580,7 @@ var Compiler = class {
8675
8580
  #scaffold(blueprint, groups) {
8676
8581
  const stages = [];
8677
8582
  const draft = this.#draft(blueprint);
8678
- const selected = [];
8679
- for (const group of groups) for (const artifact of draft) if (artifact.group === group) selected.push(artifact);
8680
- const artifacts = applyOverrides(selected, blueprint.overrides);
8583
+ const artifacts = applyOverrides(groups.flatMap((group) => draft.filter((artifact) => artifact.group === group)), blueprint.overrides);
8681
8584
  stages.push({
8682
8585
  stage: "draft",
8683
8586
  input: {
@@ -8801,6 +8704,6 @@ var Compiler = class {
8801
8704
  }
8802
8705
  };
8803
8706
  //#endregion
8804
- export { APP_BROWSER_DEV_DEPENDENCIES, APP_DEV_DEPENDENCIES, APP_MATRIX, APP_SERVER_DEV_DEPENDENCIES, ARTIFACT_TEMPLATES, AXES, BASE_DEV_DEPENDENCIES, BIN_CONFIGS, BIN_ENTRY_PATH, BROWSE_DEV_DEPENDENCIES, BROWSE_UPSTREAM, CANON_PATHS, CATALOG_AGENT_PATH, CATALOG_CLOSING_MARKER, CATALOG_OPENING_MARKER, CONFIG_TEMPLATES, CONFORMANCE_TEST_PATH, CONTROL_CHARACTER_PATTERN, Compiler, DECLARATION_DEV_DEPENDENCIES, DEFAULT_ENGINES, DEFAULT_VERSION, DEPENDENCY_NAME_PATTERN, DISTRIBUTION_TEST_PATH, ENGINES_PATTERN, ENVIRONMENTS, EXECUTABLE_PATHS, EXTRA_RANGE_PATTERN, FLOOR_RANGE_PATTERN, FOREIGN_NAME_PATTERN, FRAMEWORKS, FRAMEWORK_MATRIX, GLOBAL_SETUP_PATH, GROUPS, GUIDES_TEST_PATH, HEX_PATTERN, HOST_INVENTORY_PATH, HOST_PATHS, INTEGRATION_TEST_PATH, INVALID_PATH_CHARACTER_PATTERN, JOURNEY_CONFIG_PATH, MANIFEST_PATH, MAX_ARTIFACT_BYTES, MAX_ARTIFACT_HEX_LENGTH, MAX_AUDIT_FINDINGS, MAX_COLLECTION_ITEMS, MAX_DEPENDENCY_NAME_LENGTH, MAX_MANIFEST_BYTES, MAX_NAME_LENGTH, MAX_PATH_LENGTH, MAX_RANGE_LENGTH, MAX_REGISTRY_BYTES, MAX_SCRIPT_LENGTH, MAX_TOTAL_ARTIFACT_BYTES, MAX_TOTAL_REGISTRY_BYTES, MINIMUM_NODE_VERSION, MINIMUM_NPM_VERSION, NAME_PATTERN, ORCHESTRATION_PATH_NAMES, ORCHESTRATION_PATH_PREFIXES, ORKESTREL_RANGE_PATTERN, PRINT_WIDTH, REFERENCE_PATHS, RELEASE_PROOF_COMMAND, RESERVED_SHEET_NAMES, SEED_GUIDE_PATHS, SERVICE_SCRIPT_PATH, SERVICE_SETUP_PATH, SERVICE_TEST_INCLUDE, SHEET_ENTRY_NAME, SHOWCASE_CONFIG_PATH, SHOWCASE_DEV_DEPENDENCIES, SHOWCASE_OVERRIDES, SHOWCASE_PAGES_PATH, SKILLS_CONFIG_PATH, SOURCE_BROWSER_DEV_DEPENDENCIES, SRC_MATRIX, STYLES_DEV_DEPENDENCIES, STYLES_ENTRY_PATH, SURFACES, ScaffoldError, TAB_WIDTH, TARGET_SKILL_NAMES, THEMES_BARREL_PATH, THEMES_ENTRY_PATH, VERSION_PATTERN, WORKSPACE_DEV_ENGINES, WORKSPACE_OWNED_PATHS, applyOverrides, artifactToFinding, artifactToHex, artifactsToQuestions, blueprintToConfigArtifacts, blueprintToDevDependencies, blueprintToDocumentArtifacts, blueprintToExports, blueprintToFaces, blueprintToGuideArtifacts, blueprintToHostArtifacts, blueprintToMachinery, blueprintToManifest, blueprintToManifestOverrides, blueprintToOptimizedDependencies, blueprintToOrchestrationArtifacts, blueprintToProjects, blueprintToQuestions, blueprintToRootTsconfig, blueprintToRootVite, blueprintToScripts, blueprintToSheets, blueprintToSourceArtifacts, blueprintToTestArtifacts, blueprintToWritableScripts, bytesToHex, catalogToLayers, cloneValue, compareVersions, computeBytes, computeHash, contentToHex, createBlueprint, dependenciesToQuestions, extractRangeMajor, extractVersion, inferDrift, inferGroup, insertManifestDependencies, isArtifact, isAudit, isBlueprint, isBrowserExtension, isCanonPath, isCatalogEntry, isCollection, isCompilerHooks, isCompilerOptions, isContent, isDeferredPath, isDependency, isDependencyName, isEnvironment, isExtension, isFinding, isFloorPath, isGroup, isGroups, isHex, isManifestScript, isMirror, isOverride, isPath, isPlan, isQuestion, isRetainedPath, isScaffoldError, isSheetName, isSnapshot, isStylesExtension, isSurface, manifestToDependencies, manifestToName, matchesDriftReachability, matchesEngines, matchesOrchestrationPath, matchesPrintWidth, matchesRange, nameToGuide, overridesToQuestions, parseBlueprint, parseCompilerOptions, parseExtension, parseGroups, parseSnapshot, pathToCondition, planToFindings, planToHash, planToSummary, renderSkillPointer, replaceManifestOverrides, replaceManifestRanges, replaceManifestScripts, replacePlanRanges, selectGroups, selectHostPaths, serializeTypeScriptString, srcToEntry, srcToExports, srcToRoot };
8707
+ export { APP_BROWSER_DEV_DEPENDENCIES, APP_DEV_DEPENDENCIES, APP_MATRIX, APP_SERVER_DEV_DEPENDENCIES, ARTIFACT_TEMPLATES, AXES, BASE_DEV_DEPENDENCIES, BIN_CONFIGS, BIN_ENTRY_PATH, BROWSE_DEV_DEPENDENCIES, BROWSE_UPSTREAM, CANON_PATHS, CATALOG_AGENT_PATH, CATALOG_CLOSING_MARKER, CATALOG_OPENING_MARKER, CONFIG_TEMPLATES, CONFORMANCE_TEST_PATH, CONTROL_CHARACTER_PATTERN, Compiler, DECLARATION_DEV_DEPENDENCIES, DEFAULT_ENGINES, DEFAULT_VERSION, DEPENDENCY_NAME_PATTERN, DISTRIBUTION_TEST_PATH, ENGINES_PATTERN, ENVIRONMENTS, EXECUTABLE_PATHS, EXTRA_RANGE_PATTERN, FLOOR_RANGE_PATTERN, FOREIGN_NAME_PATTERN, FRAMEWORKS, FRAMEWORK_MATRIX, GLOBAL_SETUP_PATH, GROUPS, GUIDES_TEST_PATH, HEX_PATTERN, HOST_INVENTORY_PATH, HOST_PATHS, INTEGRATION_TEST_PATH, INVALID_PATH_CHARACTER_PATTERN, JOURNEY_CONFIG_PATH, MANIFEST_PATH, MAX_ARTIFACT_BYTES, MAX_ARTIFACT_HEX_LENGTH, MAX_AUDIT_FINDINGS, MAX_COLLECTION_ITEMS, MAX_DEPENDENCY_NAME_LENGTH, MAX_MANIFEST_BYTES, MAX_NAME_LENGTH, MAX_PATH_LENGTH, MAX_RANGE_LENGTH, MAX_REGISTRY_BYTES, MAX_SCRIPT_LENGTH, MAX_TOTAL_ARTIFACT_BYTES, MAX_TOTAL_REGISTRY_BYTES, MINIMUM_NODE_VERSION, MINIMUM_NPM_VERSION, NAME_PATTERN, ORCHESTRATION_PATH_NAMES, ORCHESTRATION_PATH_PREFIXES, ORKESTREL_RANGE_PATTERN, PRINT_WIDTH, REFERENCE_PATHS, RELEASE_PROOF_COMMAND, RESERVED_SHEET_NAMES, SEED_GUIDE_PATHS, SERVICE_SCRIPT_PATH, SERVICE_SETUP_PATH, SERVICE_TEST_INCLUDE, SHEET_ENTRY_NAME, SHOWCASE_CONFIG_PATH, SHOWCASE_DEV_DEPENDENCIES, SHOWCASE_OVERRIDES, SHOWCASE_PAGES_PATH, SKILLS_CONFIG_PATH, SOURCE_BROWSER_DEV_DEPENDENCIES, SRC_MATRIX, STUDIES_TEST_INCLUDE, STYLES_DEV_DEPENDENCIES, STYLES_ENTRY_PATH, SURFACES, ScaffoldError, TAB_WIDTH, TARGET_SKILL_NAMES, THEMES_BARREL_PATH, THEMES_ENTRY_PATH, VERSION_PATTERN, WORKSPACE_DEV_ENGINES, WORKSPACE_OWNED_PATHS, applyOverrides, artifactToFinding, artifactToHex, artifactsToQuestions, blueprintToConfigArtifacts, blueprintToDevDependencies, blueprintToDocumentArtifacts, blueprintToExports, blueprintToFaces, blueprintToGuideArtifacts, blueprintToHostArtifacts, blueprintToMachinery, blueprintToManifest, blueprintToManifestOverrides, blueprintToOptimizedDependencies, blueprintToOrchestrationArtifacts, blueprintToProjects, blueprintToQuestions, blueprintToRootTsconfig, blueprintToRootVite, blueprintToScripts, blueprintToSheets, blueprintToSourceArtifacts, blueprintToTestArtifacts, blueprintToWritableScripts, bytesToHex, catalogToLayers, cloneValue, compareVersions, computeBytes, computeHash, computeTotal, contentToHex, createBlueprint, dependenciesToQuestions, extractRangeMajor, extractVersion, inferDrift, inferGroup, insertManifestDependencies, insertManifestSection, isArtifact, isAudit, isBlueprint, isBrowserExtension, isCanonPath, isCatalogEntry, isCollection, isCompilerHooks, isCompilerOptions, isContent, isDeferredPath, isDependency, isDependencyName, isEnvironment, isExtension, isFinding, isFloorPath, isGroup, isGroups, isHex, isManifestScript, isMirror, isOverride, isPath, isPlan, isQuestion, isRetainedPath, isScaffoldError, isSheetName, isSnapshot, isStylesExtension, isSurface, iterateAnswers, manifestToDependencies, manifestToName, matchesDriftReachability, matchesEngines, matchesOrchestrationPath, matchesPrintWidth, matchesRange, nameToGuide, overridesToQuestions, parseBlueprint, parseCompilerOptions, parseExtension, parseGroups, parseSnapshot, pathToCondition, planToFindings, planToHash, planToSummary, renderSkillPointer, replaceManifestOverride, replaceManifestOverrides, replaceManifestRanges, replaceManifestScripts, replacePlanRanges, scanManifestTokens, selectGroups, selectHostPaths, serializeTypeScriptString, srcToEntry, srcToExports, srcToRoot };
8805
8708
 
8806
8709
  //# sourceMappingURL=index.js.map