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