@orkestrel/scaffold 0.0.30 → 0.0.32

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.
@@ -140,6 +140,16 @@ var HOST_PATHS = Object.freeze([
140
140
  "guides/scaffold.md"
141
141
  ]);
142
142
  /**
143
+ * The vendored paths whose present bytes belong to each workspace, frozen.
144
+ *
145
+ * @remarks
146
+ * These paths are copied into a workspace when absent and are never compared
147
+ * or replaced while present. A workspace therefore stops receiving later
148
+ * canonical updates to them. `.gitignore` takes that trade because its correct
149
+ * rules differ by workspace, so scaffold cannot own its bytes.
150
+ */
151
+ var WORKSPACE_OWNED_PATHS = Object.freeze([".gitignore"]);
152
+ /**
143
153
  * The vendored paths a target receives with its executable bit set, frozen.
144
154
  *
145
155
  * @remarks
@@ -197,9 +207,11 @@ var CATALOG_AGENT_PATH = ".claude/agents/orkestrel.md";
197
207
  var SERVICE_SCRIPT_PATH = "scripts/service.sh";
198
208
  /** The shared Vitest global-setup module whose presence makes a workspace `global`. */
199
209
  var GLOBAL_SETUP_PATH = "tests/setupGlobal.ts";
200
- /** The guide-parity proof whose physical file selects the fixed `guides` project. */
210
+ /** The guide-parity proof whose presence selects the planned `guides` project. */
201
211
  var GUIDES_TEST_PATH = "tests/guides.test.ts";
202
- /** The installed-package proof whose presence makes a workspace `integration`. */
212
+ /** The packed-package proof whose presence makes a workspace `distribution`. */
213
+ var DISTRIBUTION_TEST_PATH = "tests/distribution.test.ts";
214
+ /** The cross-environment composition proof whose presence makes a workspace `integration`. */
203
215
  var INTEGRATION_TEST_PATH = "tests/integration.test.ts";
204
216
  /** The official-tooling drift proof whose presence makes a workspace `conformance`. */
205
217
  var CONFORMANCE_TEST_PATH = "tests/conformance.test.ts";
@@ -270,6 +282,10 @@ var MAX_RANGE_LENGTH = 2048;
270
282
  var MAX_PATH_LENGTH = 32767;
271
283
  /** Maximum items accepted in one public collection. */
272
284
  var MAX_COLLECTION_ITEMS = 1e3;
285
+ /** Columns one emitted line may occupy, matching `printWidth` in `.oxfmtrc.json`. */
286
+ var PRINT_WIDTH = 100;
287
+ /** Columns one tab occupies when the formatter measures a line, matching `tabWidth`. */
288
+ var TAB_WIDTH = 2;
273
289
  /** Maximum findings one audit can produce from a bounded plan and snapshot. */
274
290
  var MAX_AUDIT_FINDINGS = MAX_COLLECTION_ITEMS * 2;
275
291
  /** Maximum bytes accepted for one artifact. */
@@ -288,17 +304,21 @@ var DEFAULT_VERSION = "0.0.1";
288
304
  var DEFAULT_ENGINES = `>=${MINIMUM_NODE_VERSION}`;
289
305
  /** The tooling versions scaffold and every generated workspace share. */
290
306
  var BASE_DEV_DEPENDENCIES = Object.freeze({
291
- "@microsoft/api-extractor": "^7.58.12",
292
- "@orkestrel/guide": "^0.0.10",
293
- "@orkestrel/scaffold": "^0.0.30",
307
+ "@orkestrel/guide": "^0.0.11",
308
+ "@orkestrel/scaffold": "^0.0.32",
309
+ "@orkestrel/test": "^0.0.2",
294
310
  "@types/node": "^26.2.0",
295
311
  oxfmt: "^0.62.0",
296
312
  oxlint: "^1.77.0",
297
313
  typescript: "^6.0.3",
298
314
  vite: "~8.2.0",
299
- "vite-plugin-dts": "^5.0.3",
300
315
  vitest: "^4.1.10"
301
316
  });
317
+ /** The development dependencies that emit declarations for published source or an executable. */
318
+ var DECLARATION_DEV_DEPENDENCIES = Object.freeze({
319
+ "@microsoft/api-extractor": "^7.58.12",
320
+ "vite-plugin-dts": "^5.0.3"
321
+ });
302
322
  /** The development dependencies a published browser `src` environment adds. */
303
323
  var SOURCE_BROWSER_DEV_DEPENDENCIES = Object.freeze({
304
324
  "@vitest/browser-playwright": "^4.1.10",
@@ -386,38 +406,12 @@ var CONFIG_TEMPLATES = Object.freeze({
386
406
  vite: `import type { {{viteTypes}} } from 'vite'
387
407
  {{imports}}import { defineConfig, mergeConfig } from 'vitest/config'
388
408
  import tsconfig from './tsconfig.json' with { type: 'json' }
389
- {{helpers}}{{browsers}}import { lstatSync, readdirSync, realpathSync } from 'node:fs'
390
- import { basename, join, parse, relative, resolve as resolvePath, sep } from 'node:path'
391
- import { fileURLToPath, URL } from 'node:url'
409
+ {{helpers}}{{browsers}}import { fileURLToPath, URL } from 'node:url'
392
410
 
393
411
  {{options}}export function resolveWorkspacePath(relativePath: string): string {
394
412
  return fileURLToPath(new URL(relativePath, import.meta.url))
395
413
  }
396
414
 
397
- // A generated root config must classify its own fixed proof without importing
398
- // package source, so the exact-case check stays self-contained over Node APIs.
399
- function isExactCaseFile(path: string): boolean {
400
- const full = resolvePath(path)
401
- try {
402
- const status = lstatSync(full)
403
- if (!status.isFile() || status.isSymbolicLink() || status.nlink !== 1) return false
404
- const root = parse(full).root
405
- const segments = relative(root, full).split(sep)
406
- let parent = root
407
- for (const segment of segments) {
408
- try {
409
- if (!readdirSync(parent).includes(segment)) return false
410
- } catch {
411
- if (basename(realpathSync.native(join(parent, segment))) !== segment) return false
412
- }
413
- parent = join(parent, segment)
414
- }
415
- return true
416
- } catch {
417
- return false
418
- }
419
- }
420
-
421
415
  const resolve = {
422
416
  alias: Object.entries(tsconfig.compilerOptions.paths).reduce((aliases, [key, values]) => {
423
417
  const [path] = values
@@ -553,6 +547,10 @@ const resolve = {
553
547
  setupFiles: ['./tests/setup.ts', './tests/setupServer.ts'],
554
548
  environment: 'node',
555
549
  browser: { enabled: false },
550
+ // A bin test drives the real executable over a real temporary repository, so it
551
+ // spends seconds in process startup and filesystem work rather than milliseconds.
552
+ // Vitest's five-second default clears one alone and times out under a full suite.
553
+ testTimeout: 15_000,
556
554
  },
557
555
  },
558
556
  options ?? {},
@@ -604,8 +602,7 @@ const resolve = {
604
602
  }
605
603
  }
606
604
 
607
- export function appBrowser(...options: never[]): UserConfig {
608
- if (options.length > 0) throw new Error('Browser configuration overrides are not permitted')
605
+ export function appBrowser(): UserConfig {
609
606
  return applicationBrowser(false)
610
607
  }
611
608
  {{showcaseFactory}}`,
@@ -721,6 +718,23 @@ export const service = (options?: UserConfig): UserConfig =>
721
718
  },
722
719
  options ?? {},
723
720
  )
721
+ `,
722
+ distribution: `export const distribution = (options?: UserConfig): UserConfig =>
723
+ mergeConfig(
724
+ {
725
+ resolve,
726
+ test: {
727
+ name: { label: 'distribution', color: 'cyan' },
728
+ include: ['${DISTRIBUTION_TEST_PATH}'],
729
+ setupFiles: ['./tests/setup.ts'],
730
+ environment: 'node',
731
+ testTimeout: 120_000,
732
+ hookTimeout: 120_000,
733
+ fileParallelism: false,
734
+ },
735
+ },
736
+ options ?? {},
737
+ )
724
738
  `,
725
739
  probe: `// A workbench, not a proof. No gate selects this project.
726
740
  export const probe = (options?: UserConfig): UserConfig =>
@@ -747,9 +761,6 @@ export const probe = (options?: UserConfig): UserConfig =>
747
761
  include: ['${INTEGRATION_TEST_PATH}'],
748
762
  setupFiles: ['./tests/setup.ts'],
749
763
  {{global}} environment: 'node',
750
- testTimeout: 120_000,
751
- hookTimeout: 120_000,
752
- fileParallelism: false,
753
764
  },
754
765
  },
755
766
  options ?? {},
@@ -1377,112 +1388,14 @@ describe('bin entry', () => {
1377
1388
  })
1378
1389
  })
1379
1390
  `,
1380
- integration: `import { spawnSync } from 'node:child_process'
1381
- import { globSync, mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
1382
- import { tmpdir } from 'node:os'
1383
- import { dirname, join, resolve } from 'node:path'
1384
- import { fileURLToPath, pathToFileURL } from 'node:url'
1385
- import { describe, expect, it } from 'vitest'
1386
-
1387
- const root = resolve(dirname(fileURLToPath(import.meta.url)), '..')
1388
- const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm'
1389
- const registry =
1390
- spawnSync(npm, ['ping', '--fetch-retries=0', '--fetch-timeout=1000', '--loglevel=silent'], {
1391
- cwd: root,
1392
- stdio: 'ignore',
1393
- timeout: 5_000,
1394
- windowsHide: true,
1395
- }).status === 0
1391
+ integration: `{{imports}}import { describe, expect, it } from 'vitest'
1396
1392
 
1397
- describe('installed package consumer', () => {
1398
- it('loads the built package from a process outside the workspace', () => {
1399
- const workspace = mkdtempSync(join(tmpdir(), 'orkestrel-integration-driver-'))
1400
- try {
1401
- const manifest: unknown = JSON.parse(readFileSync(resolve(root, 'package.json'), 'utf8'))
1402
- if (typeof manifest !== 'object' || manifest === null) {
1403
- throw new Error('The package manifest is not a record')
1404
- }
1405
- const module: unknown = Object.getOwnPropertyDescriptor(manifest, 'module')?.value
1406
- if (typeof module !== 'string')
1407
- throw new Error('The package manifest carries no module entry')
1408
- const entry = pathToFileURL(resolve(root, module)).href
1409
- const missing = pathToFileURL(resolve(root, 'dist/outside-integration-control.js')).href
1410
- const control = spawnSync(
1411
- process.execPath,
1412
- ['--input-type=module', '--eval', 'await import(' + JSON.stringify(missing) + ')'],
1413
- { cwd: workspace, encoding: 'utf8', windowsHide: true },
1414
- )
1415
- expect(control.status).not.toBe(0)
1416
- const driven = spawnSync(
1417
- process.execPath,
1418
- ['--input-type=module', '--eval', 'await import(' + JSON.stringify(entry) + ')'],
1419
- { cwd: workspace, encoding: 'utf8', windowsHide: true },
1420
- )
1421
- expect(driven.status).toBe(0)
1422
- expect(driven.stderr).toBe('')
1423
- } finally {
1424
- rmSync(workspace, { recursive: true, force: true })
1425
- }
1393
+ describe('workspace integration', () => {
1394
+ it('loads every selected public barrel together as a composition seed', () => {
1395
+ // Replace this empty-barrel seed with one observable flow that passes one
1396
+ // environment's public result into another.
1397
+ {{actual}}{{expected}})
1426
1398
  })
1427
-
1428
- it.skipIf(!registry)(
1429
- 'installs the packed package into an outside consumer [requires a reachable npm registry]',
1430
- () => {
1431
- const workspace = mkdtempSync(join(tmpdir(), 'orkestrel-integration-install-'))
1432
- const packed = join(workspace, 'packed')
1433
- const consumer = join(workspace, 'consumer')
1434
- try {
1435
- mkdirSync(packed, { recursive: true })
1436
- mkdirSync(consumer, { recursive: true })
1437
- const pack = spawnSync(
1438
- npm,
1439
- ['pack', '--json', '--ignore-scripts', '--pack-destination', packed],
1440
- { cwd: root, encoding: 'utf8', windowsHide: true },
1441
- )
1442
- expect(pack.status).toBe(0)
1443
- const archives = globSync('*.tgz', { cwd: packed })
1444
- expect(archives).toHaveLength(1)
1445
- const archive = archives[0]
1446
- if (archive === undefined) throw new Error('The package archive was not written')
1447
- writeFileSync(
1448
- join(consumer, 'package.json'),
1449
- '{"name":"outside-consumer","private":true,"type":"module"}\\n',
1450
- )
1451
- const install = spawnSync(
1452
- npm,
1453
- ['install', '--ignore-scripts', '--no-audit', '--no-fund', join(packed, archive)],
1454
- { cwd: consumer, encoding: 'utf8', windowsHide: true },
1455
- )
1456
- expect(install.status).toBe(0)
1457
- const manifest: unknown = JSON.parse(readFileSync(resolve(root, 'package.json'), 'utf8'))
1458
- if (typeof manifest !== 'object' || manifest === null) {
1459
- throw new Error('The package manifest is not a record')
1460
- }
1461
- const name: unknown = Object.getOwnPropertyDescriptor(manifest, 'name')?.value
1462
- if (typeof name !== 'string') throw new Error('The package manifest carries no name')
1463
- const control = spawnSync(
1464
- process.execPath,
1465
- [
1466
- '--input-type=module',
1467
- '--eval',
1468
- 'await import(' + JSON.stringify(name + '/outside-integration-control') + ')',
1469
- ],
1470
- { cwd: consumer, encoding: 'utf8', windowsHide: true },
1471
- )
1472
- expect(control.status).not.toBe(0)
1473
- const driven = spawnSync(
1474
- process.execPath,
1475
- ['--input-type=module', '--eval', 'await import(' + JSON.stringify(name) + ')'],
1476
- { cwd: consumer, encoding: 'utf8', windowsHide: true },
1477
- )
1478
- expect(driven.status).toBe(0)
1479
- expect(driven.stderr).toBe('')
1480
- } finally {
1481
- rmSync(workspace, { recursive: true, force: true })
1482
- }
1483
- },
1484
- 300_000,
1485
- )
1486
1399
  })
1487
1400
  `
1488
1401
  }),
@@ -1803,6 +1716,8 @@ var isBlueprint = (0, _orkestrel_contract.recordOf)({
1803
1716
  engines: _orkestrel_contract.isString,
1804
1717
  overrides: (0, _orkestrel_contract.andOf)(isCollection, (0, _orkestrel_contract.arrayOf)(isOverride)),
1805
1718
  bin: _orkestrel_contract.isBoolean,
1719
+ guides: _orkestrel_contract.isBoolean,
1720
+ distribution: _orkestrel_contract.isBoolean,
1806
1721
  integration: _orkestrel_contract.isBoolean,
1807
1722
  conformance: _orkestrel_contract.isBoolean,
1808
1723
  service: _orkestrel_contract.isBoolean,
@@ -2398,6 +2313,30 @@ function nameToGuide(name) {
2398
2313
  return `guides/${name.slice(name.lastIndexOf("/") + 1)}.md`;
2399
2314
  }
2400
2315
  /**
2316
+ * Test whether one emitted line fits the vendored formatter width.
2317
+ *
2318
+ * @param line - One emitted line, leading tabs included.
2319
+ * @returns `true` when the expanded line fits.
2320
+ *
2321
+ * @remarks
2322
+ * A generator writes source the formatter then reads back, so a line packed
2323
+ * past the vendored width is rewrapped on the next `format` run and the emitted
2324
+ * bytes stop matching the plan the audit compares against. The generator
2325
+ * therefore measures a candidate line and chooses the shape the formatter would
2326
+ * have chosen. Tabs are expanded first because the formatter counts them as
2327
+ * `TAB_WIDTH` columns rather than as one character.
2328
+ *
2329
+ * @example
2330
+ * ```ts
2331
+ * import { matchesPrintWidth } from '@orkestrel/scaffold'
2332
+ *
2333
+ * matchesPrintWidth('\t\tprojects: [core],') // true
2334
+ * ```
2335
+ */
2336
+ function matchesPrintWidth(line) {
2337
+ return line.replaceAll(" ", " ".repeat(2)).length <= 100;
2338
+ }
2339
+ /**
2401
2340
  * Derive the declaration rewrite a published face's `beforeWriteFile` applies.
2402
2341
  *
2403
2342
  * @param name - The workspace's own bare package name.
@@ -2429,7 +2368,7 @@ function nameToRewrite(name) {
2429
2368
  const specifier = serializeTypeScriptString(`@orkestrel/${name}`);
2430
2369
  const pattern = "/(?:\\.\\.\\/)+core\\/index\\.[jt]s/g";
2431
2370
  const joined = `\t\t\t\t\t\t? content.replaceAll(${pattern}, ${specifier})`;
2432
- if (joined.replaceAll(" ", " ").length <= 100) return joined;
2371
+ if (matchesPrintWidth(joined)) return joined;
2433
2372
  return [
2434
2373
  " ? content.replaceAll(",
2435
2374
  `\t\t\t\t\t\t\t\t${pattern},`,
@@ -3059,6 +2998,7 @@ function srcToExports(src) {
3059
2998
  function blueprintToDevDependencies(blueprint) {
3060
2999
  const merged = {
3061
3000
  ...BASE_DEV_DEPENDENCIES,
3001
+ ...blueprint.src.length > 0 || blueprint.bin ? DECLARATION_DEV_DEPENDENCIES : {},
3062
3002
  ...blueprint.src.includes("browser") ? SOURCE_BROWSER_DEV_DEPENDENCIES : {},
3063
3003
  ...blueprint.app.length > 0 ? APP_DEV_DEPENDENCIES : {},
3064
3004
  ...blueprint.app.includes("browser") ? APP_BROWSER_DEV_DEPENDENCIES : {},
@@ -3084,12 +3024,13 @@ function blueprintToDevDependencies(blueprint) {
3084
3024
  * proofs every workspace can pass before it has a public API, and one build per
3085
3025
  * target that actually builds.
3086
3026
  *
3087
- * A proof leaves `test` when a real service or a real install answers it. The
3088
- * installed-package integration proof and the live-service proof therefore run
3089
- * from `prepublishOnly` instead. The conformance proof stays in `test`, because
3090
- * it measures this package against official tooling and drives nothing external:
3091
- * a conformance run may start a server, but it starts its own and reaches it
3092
- * over loopback, so the run stays hermetic.
3027
+ * A publishing workspace isolates distribution and live-service proofs from
3028
+ * `test` and runs them from `prepublishOnly` instead. A private workspace has
3029
+ * no publish lifecycle, so it omits distribution and runs a live-service proof
3030
+ * from `test`. Integration and conformance stay in `test` because they neither
3031
+ * pack nor install the workspace and drive no external service. A conformance
3032
+ * run may start a server, but it starts its own and reaches it over loopback,
3033
+ * so the run stays hermetic.
3093
3034
  *
3094
3035
  * The configuration paths interpolated here are the same ones `SRC_MATRIX` and
3095
3036
  * `APP_MATRIX` list as each environment's configuration files, so a rename in
@@ -3106,7 +3047,8 @@ function blueprintToDevDependencies(blueprint) {
3106
3047
  */
3107
3048
  function blueprintToScripts(blueprint) {
3108
3049
  const publishes = blueprint.src.length > 0;
3109
- const integrates = publishes && blueprint.integration;
3050
+ const distributes = blueprint.distribution && publishes;
3051
+ const integrates = blueprint.integration;
3110
3052
  const compiles = publishes || blueprint.bin;
3111
3053
  const runtime = blueprint.app.filter((environment) => environment !== "core");
3112
3054
  const vitest = "vitest run --config vite.config.ts --no-cache --reporter=dot";
@@ -3137,7 +3079,10 @@ function blueprintToScripts(blueprint) {
3137
3079
  ...blueprint.app.length > 0 ? ["npm run test:app"] : [],
3138
3080
  "npm run test:policy",
3139
3081
  "npm run test:config",
3140
- ...blueprint.conformance ? ["npm run test:conformance"] : []
3082
+ ...blueprint.guides ? ["npm run test:guides"] : [],
3083
+ ...blueprint.conformance ? ["npm run test:conformance"] : [],
3084
+ ...integrates ? ["npm run test:integration"] : [],
3085
+ ...!publishes && blueprint.service ? ["npm run test:service"] : []
3141
3086
  ].join(" && ");
3142
3087
  if (compiles) {
3143
3088
  scripts["test:src"] = [
@@ -3154,8 +3099,10 @@ function blueprintToScripts(blueprint) {
3154
3099
  }
3155
3100
  scripts["test:policy"] = `${vitest} --project policy`;
3156
3101
  scripts["test:config"] = `${vitest} --project config`;
3102
+ if (blueprint.guides) scripts["test:guides"] = `${vitest} --project guides`;
3157
3103
  if (blueprint.conformance) scripts["test:conformance"] = `${vitest} --project conformance`;
3158
3104
  scripts["test:probe"] = "vitest run --config vite.config.ts --no-cache --reporter=verbose --project probe";
3105
+ if (distributes) scripts["test:distribution"] = `${vitest} --project distribution`;
3159
3106
  if (integrates) scripts["test:integration"] = `${vitest} --project integration`;
3160
3107
  if (blueprint.service) scripts["test:service"] = `${vitest} --project service`;
3161
3108
  scripts.build = [
@@ -3187,9 +3134,9 @@ function blueprintToScripts(blueprint) {
3187
3134
  scripts.serve = "node dist/app/server/main.cjs";
3188
3135
  scripts["serve:build"] = "npm run build:app:server && npm run serve";
3189
3136
  }
3190
- scripts.prepublishOnly = [
3137
+ if (publishes) scripts.prepublishOnly = [
3191
3138
  "npm run format:check && npm run lint:check && npm run check && npm run build && npm test",
3192
- ...integrates ? ["npm run test:integration"] : [],
3139
+ ...distributes ? ["npm run test:distribution -- --mode release"] : [],
3193
3140
  ...blueprint.service ? ["npm run test:service"] : []
3194
3141
  ].join(" && ");
3195
3142
  return scripts;
@@ -3360,6 +3307,8 @@ function blueprintToRootTsconfig(blueprint) {
3360
3307
  */
3361
3308
  function blueprintToRootVite(blueprint) {
3362
3309
  const machinery = blueprintToMachinery(blueprint);
3310
+ const publishes = blueprint.src.length > 0;
3311
+ const distributes = blueprint.distribution && publishes;
3363
3312
  const imports = [];
3364
3313
  if (machinery.browser) imports.push("import { playwright } from '@vitest/browser-playwright'");
3365
3314
  if (machinery.vue) imports.push("import vue from '@vitejs/plugin-vue'");
@@ -3451,8 +3400,7 @@ function blueprintToRootVite(blueprint) {
3451
3400
  : { assetsInlineLimit: 0 }),
3452
3401
  ` : " assetsInlineLimit: 0,\n";
3453
3402
  const showcaseFactory = machinery.showcase ? `
3454
- export function appShowcase(...options: never[]): UserConfig {
3455
- if (options.length > 0) throw new Error('Showcase configuration overrides are not permitted')
3403
+ export function appShowcase(): UserConfig {
3456
3404
  return applicationBrowser(true)
3457
3405
  }
3458
3406
  ` : "";
@@ -3472,8 +3420,10 @@ export function appShowcase(...options: never[]): UserConfig {
3472
3420
  projects.push("policy");
3473
3421
  factories.push(CONFIG_TEMPLATES.factories.config);
3474
3422
  projects.push("config");
3475
- factories.push(CONFIG_TEMPLATES.factories.guides);
3476
- projects.push(`...(isExactCaseFile(resolveWorkspacePath('${GUIDES_TEST_PATH}')) ? [guides] : [])`);
3423
+ if (blueprint.guides) {
3424
+ factories.push(CONFIG_TEMPLATES.factories.guides);
3425
+ projects.push("guides");
3426
+ }
3477
3427
  if (blueprint.conformance) {
3478
3428
  factories.push(CONFIG_TEMPLATES.factories.conformance);
3479
3429
  projects.push("conformance");
@@ -3482,13 +3432,18 @@ export function appShowcase(...options: never[]): UserConfig {
3482
3432
  factories.push(CONFIG_TEMPLATES.factories.service);
3483
3433
  projects.push("service");
3484
3434
  }
3485
- if (blueprint.src.length > 0 && blueprint.integration) {
3435
+ if (distributes) {
3436
+ factories.push(CONFIG_TEMPLATES.factories.distribution);
3437
+ projects.push("distribution");
3438
+ }
3439
+ if (blueprint.integration) {
3486
3440
  factories.push((0, _orkestrel_template.fillTemplate)(CONFIG_TEMPLATES.factories.integration, { global: blueprint.global ? " globalSetup: ['./tests/setupGlobal.ts'],\n" : "" }));
3487
3441
  projects.push("integration");
3488
3442
  }
3489
3443
  factories.push(CONFIG_TEMPLATES.factories.probe);
3490
3444
  projects.push("probe");
3491
- const projectRows = `\t\tprojects: [
3445
+ const joinedProjects = `\t\tprojects: [${projects.join(", ")}],`;
3446
+ const projectRows = matchesPrintWidth(joinedProjects) ? joinedProjects : `\t\tprojects: [
3492
3447
  ${projects.map((project) => `\t\t\t${project},`).join("\n")}
3493
3448
  \t],`;
3494
3449
  const body = `${factories.join("\n")}\n`;
@@ -3692,15 +3647,14 @@ function blueprintToSourceArtifacts(blueprint) {
3692
3647
  * Compile every artifact in the `tests` group that is not vendored from the host.
3693
3648
  *
3694
3649
  * @param blueprint - The workspace specification.
3695
- * @returns Shared setup modules, axis tests, and the optional install proof.
3650
+ * @returns Shared setup modules, axis tests, and the optional integration seed.
3696
3651
  *
3697
3652
  * @remarks
3698
3653
  * `tests/setupPolicy.ts`, `tests/policy.test.ts`, and `tests/config.test.ts` are
3699
3654
  * fleet-invariant host artifacts and therefore do not appear here. A guide
3700
3655
  * proof is not emitted while the workspace intentionally has empty barrels and
3701
- * no package guide. The install proof is meaningful only for a published
3702
- * workspace and therefore follows the `src` axis as well as its structural
3703
- * flag.
3656
+ * no package guide. The integration seed follows its structural flag and loads
3657
+ * every selected environment through its public barrel.
3704
3658
  *
3705
3659
  * The conformance proof is not emitted either, for the reason the guide proof
3706
3660
  * is not: it names an official artifact only the package knows, so a generated
@@ -3768,7 +3722,8 @@ function blueprintToTestArtifacts(blueprint) {
3768
3722
  });
3769
3723
  if (blueprint.bin) {
3770
3724
  const specifier = serializeTypeScriptString("../../../src/bin/main.js");
3771
- const statement = specifier.length <= 68 ? `\t\tconst entry = await import(${specifier})` : `\t\tconst entry =\n\t\t\tawait import(${specifier})`;
3725
+ const joined = `\t\tconst entry = await import(${specifier})`;
3726
+ const statement = matchesPrintWidth(joined) ? joined : `\t\tconst entry =\n\t\t\tawait import(${specifier})`;
3772
3727
  artifacts.push({
3773
3728
  path: "tests/src/bin/main.test.ts",
3774
3729
  group: "tests",
@@ -3788,13 +3743,35 @@ function blueprintToTestArtifacts(blueprint) {
3788
3743
  label: serializeTypeScriptString(`app ${environment} entry`)
3789
3744
  })
3790
3745
  });
3791
- if (blueprint.src.length > 0 && blueprint.integration) artifacts.push({
3792
- path: INTEGRATION_TEST_PATH,
3793
- group: "tests",
3794
- ownership: "birth",
3795
- origin: "template",
3796
- content: ARTIFACT_TEMPLATES.tests.integration
3797
- });
3746
+ if (blueprint.integration) {
3747
+ const imports = [];
3748
+ const entries = [];
3749
+ for (const environment of blueprint.src) {
3750
+ const entry = environment === "core" ? "srcCore" : environment === "browser" ? "srcBrowser" : "srcServer";
3751
+ imports.push(`import * as ${entry} from '@src/${environment}'`);
3752
+ entries.push(entry);
3753
+ }
3754
+ for (const environment of blueprint.app) {
3755
+ const entry = environment === "core" ? "appCore" : environment === "browser" ? "appBrowser" : "appServer";
3756
+ imports.push(`import * as ${entry} from '@app/${environment}'`);
3757
+ entries.push(entry);
3758
+ }
3759
+ const joined = `expect([${entries.map((entry) => `Object.keys(${entry})`).join(", ")}]).toStrictEqual(`;
3760
+ const actual = matchesPrintWidth(`\t\t${joined}[`) ? joined : `expect([\n${entries.map((entry) => `\t\t\tObject.keys(${entry}),`).join("\n")}\n\t\t]).toStrictEqual(`;
3761
+ const expectedInline = `[${entries.map(() => "[]").join(", ")}]`;
3762
+ const expected = actual !== joined || matchesPrintWidth(`\t\t${joined}${expectedInline})`) ? expectedInline : `[\n${entries.map(() => " [],").join("\n")}\n\t\t]`;
3763
+ artifacts.push({
3764
+ path: INTEGRATION_TEST_PATH,
3765
+ group: "tests",
3766
+ ownership: "birth",
3767
+ origin: "template",
3768
+ content: (0, _orkestrel_template.fillTemplate)(ARTIFACT_TEMPLATES.tests.integration, {
3769
+ imports: `${imports.join("\n")}\n`,
3770
+ actual,
3771
+ expected
3772
+ })
3773
+ });
3774
+ }
3798
3775
  return artifacts;
3799
3776
  }
3800
3777
  /**
@@ -4165,12 +4142,19 @@ function dependenciesToQuestions(dependencies, field, name, range) {
4165
4142
  *
4166
4143
  * A question blocks when it describes a workspace this package cannot generate.
4167
4144
  * Three do not, because each describes a workspace it can describe honestly and
4168
- * should not create: a published axis of several environments without core, whose
4169
- * manifest names a core build the workspace never runs, and a structural flag
4170
- * whose required axis is absent, which emits nothing. Blocking those closed the
4171
- * gate for every verb, and the verbs that read an existing workspace need the
4172
- * plan the gate refused. The caller that chooses the shape refuses the advisory;
4173
- * the callers that read one report it.
4145
+ * should not create: a published axis of several environments without core,
4146
+ * whose manifest names a core build the workspace never runs; a showcase flag
4147
+ * whose required browser axis is absent, which emits nothing; and an
4148
+ * integration flag over fewer than two environments, whose seed does emit and
4149
+ * has nothing to compose across. Blocking any of them closed the gate for every
4150
+ * verb. The verbs that read an existing workspace need the plan the gate
4151
+ * refused. The caller that chooses the shape refuses the advisory; the callers
4152
+ * that read one report it.
4153
+ *
4154
+ * The integration advisory counts both axes together, because the proof it
4155
+ * seeds spans environments rather than published ones: an application of two
4156
+ * environments composes, and a package publishing one does not. Reading either
4157
+ * axis alone withdrew the proof from a workspace that does compose.
4174
4158
  *
4175
4159
  * An environment question carries `ENVIRONMENTS` as its candidates, so a caller
4176
4160
  * reads the accepted values from the question instead of from the documentation.
@@ -4227,9 +4211,9 @@ function blueprintToQuestions(blueprint) {
4227
4211
  candidates: ENVIRONMENTS
4228
4212
  });
4229
4213
  }
4230
- if (blueprint.integration && blueprint.src.length === 0) questions.push({
4214
+ if (blueprint.integration && blueprint.src.length + blueprint.app.length < 2) questions.push({
4231
4215
  field: "integration",
4232
- message: "integration projects a published src, and this workspace declares none, so it emits nothing.",
4216
+ message: "integration drives features across environments, and this workspace declares fewer than two, so its seed composes nothing.",
4233
4217
  blocking: false
4234
4218
  });
4235
4219
  const vendors = /* @__PURE__ */ new Set();
@@ -4246,6 +4230,11 @@ function blueprintToQuestions(blueprint) {
4246
4230
  });
4247
4231
  vendors.add(vendor);
4248
4232
  }
4233
+ if (blueprint.distribution && blueprint.src.length === 0) questions.push({
4234
+ field: "distribution",
4235
+ message: "distribution packs the published source, and this workspace declares none, so it emits nothing.",
4236
+ blocking: false
4237
+ });
4249
4238
  if (blueprint.showcase && !blueprint.app.includes("browser")) questions.push({
4250
4239
  field: "showcase",
4251
4240
  message: "showcase projects a browser app, and this workspace declares none, so it emits nothing.",
@@ -4753,6 +4742,8 @@ function createBlueprint(name, input) {
4753
4742
  engines: input?.engines ?? DEFAULT_ENGINES,
4754
4743
  overrides: input?.overrides ?? [],
4755
4744
  bin: input?.bin ?? false,
4745
+ guides: input?.guides ?? false,
4746
+ distribution: input?.distribution ?? false,
4756
4747
  integration: input?.integration ?? false,
4757
4748
  conformance: input?.conformance ?? false,
4758
4749
  service: input?.service ?? false,
@@ -4796,9 +4787,11 @@ exports.CONFIG_TEMPLATES = CONFIG_TEMPLATES;
4796
4787
  exports.CONFORMANCE_TEST_PATH = CONFORMANCE_TEST_PATH;
4797
4788
  exports.CONTROL_CHARACTER_PATTERN = CONTROL_CHARACTER_PATTERN;
4798
4789
  exports.Compiler = Compiler;
4790
+ exports.DECLARATION_DEV_DEPENDENCIES = DECLARATION_DEV_DEPENDENCIES;
4799
4791
  exports.DEFAULT_ENGINES = DEFAULT_ENGINES;
4800
4792
  exports.DEFAULT_VERSION = DEFAULT_VERSION;
4801
4793
  exports.DEPENDENCY_NAME_PATTERN = DEPENDENCY_NAME_PATTERN;
4794
+ exports.DISTRIBUTION_TEST_PATH = DISTRIBUTION_TEST_PATH;
4802
4795
  exports.ENGINES_PATTERN = ENGINES_PATTERN;
4803
4796
  exports.ENVIRONMENTS = ENVIRONMENTS;
4804
4797
  exports.EXECUTABLE_PATHS = EXECUTABLE_PATHS;
@@ -4826,6 +4819,7 @@ exports.NAME_PATTERN = NAME_PATTERN;
4826
4819
  exports.ORCHESTRATION_PATH_NAMES = ORCHESTRATION_PATH_NAMES;
4827
4820
  exports.ORCHESTRATION_PATH_PREFIXES = ORCHESTRATION_PATH_PREFIXES;
4828
4821
  exports.ORKESTREL_RANGE_PATTERN = ORKESTREL_RANGE_PATTERN;
4822
+ exports.PRINT_WIDTH = PRINT_WIDTH;
4829
4823
  exports.SERVICE_SCRIPT_PATH = SERVICE_SCRIPT_PATH;
4830
4824
  exports.SERVICE_SETUP_PATH = SERVICE_SETUP_PATH;
4831
4825
  exports.SERVICE_TEST_INCLUDE = SERVICE_TEST_INCLUDE;
@@ -4834,7 +4828,9 @@ exports.SHOWCASE_DEV_DEPENDENCIES = SHOWCASE_DEV_DEPENDENCIES;
4834
4828
  exports.SOURCE_BROWSER_DEV_DEPENDENCIES = SOURCE_BROWSER_DEV_DEPENDENCIES;
4835
4829
  exports.SRC_MATRIX = SRC_MATRIX;
4836
4830
  exports.ScaffoldError = ScaffoldError;
4831
+ exports.TAB_WIDTH = TAB_WIDTH;
4837
4832
  exports.VERSION_PATTERN = VERSION_PATTERN;
4833
+ exports.WORKSPACE_OWNED_PATHS = WORKSPACE_OWNED_PATHS;
4838
4834
  exports.applyOverrides = applyOverrides;
4839
4835
  exports.artifactToFinding = artifactToFinding;
4840
4836
  exports.artifactToHex = artifactToHex;
@@ -4892,6 +4888,7 @@ exports.manifestToName = manifestToName;
4892
4888
  exports.matchesDriftReachability = matchesDriftReachability;
4893
4889
  exports.matchesEngines = matchesEngines;
4894
4890
  exports.matchesOrchestrationPath = matchesOrchestrationPath;
4891
+ exports.matchesPrintWidth = matchesPrintWidth;
4895
4892
  exports.matchesRange = matchesRange;
4896
4893
  exports.nameToGuide = nameToGuide;
4897
4894
  exports.nameToHostArtifacts = nameToHostArtifacts;