@orkestrel/scaffold 0.0.87 → 0.0.89

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.
@@ -3,7 +3,7 @@ import { fillTemplate } from "@orkestrel/template";
3
3
  import { Emitter } from "@orkestrel/emitter";
4
4
  var package_default = {
5
5
  name: "@orkestrel/scaffold",
6
- version: "0.0.87",
6
+ version: "0.0.89",
7
7
  description: "Scaffold workspaces with commands: new, audit, repair, catalog, and overwrite.",
8
8
  keywords: [
9
9
  "audit",
@@ -77,6 +77,7 @@ var package_default = {
77
77
  "test:config": "vitest run --config vite.config.ts --no-cache --reporter=dot --project config",
78
78
  "test:guides": "node --experimental-strip-types tests/guides.test.ts",
79
79
  "test:skills": "vitest run --config vite.config.ts --no-cache --reporter=dot --project skills",
80
+ "test:templates": "vitest run --config vite.config.ts --no-cache --reporter=dot --project templates",
80
81
  "test:distribution": "vitest run --config vite.config.ts --no-cache --reporter=dot --project distribution",
81
82
  "test:probe": "vitest run --config vite.config.ts --no-cache --reporter=verbose --project probe",
82
83
  "test:bench": "vitest bench --config vite.config.ts --no-cache --project probe",
@@ -90,7 +91,7 @@ var package_default = {
90
91
  "build:inventory": "node -e \"import('./dist/src/server/index.js').then((m)=>{const p=process.argv[1]??'host.json',n=m.stageInventory(process.cwd(),p).entries.length;console.log('build-inventory: staged '+n+' file(s) into '+p)})\"",
91
92
  "build:skills": "tsc -p configs/agents/tsconfig.skills.json --noEmit false --allowImportingTsExtensions false --rewriteRelativeImportExtensions --declaration false --sourceMap false --rootDir .agents/skills --outDir dist/agents/skills && npm run copy .agents/templates/brief.md dist/agents/templates/brief.md",
92
93
  "prepack": "npm run build",
93
- "prepublishOnly": "npm run format:check && npm run lint:check && npm run check && npm run build && npm test && npm run test:distribution -- --mode release"
94
+ "prepublishOnly": "npm run format:check && npm run lint:check && npm run check && npm run build && npm test && npm run test:templates && npm run test:distribution -- --mode release"
94
95
  },
95
96
  dependencies: {
96
97
  "@orkestrel/console": "^0.0.15",
@@ -102,7 +103,7 @@ var package_default = {
102
103
  },
103
104
  devDependencies: {
104
105
  "@microsoft/api-extractor": "^7.59.3",
105
- "@orkestrel/browser": "^0.0.20",
106
+ "@orkestrel/browser": "^0.0.21",
106
107
  "@orkestrel/guide": "^0.0.24",
107
108
  "@orkestrel/html": "^0.0.12",
108
109
  "@orkestrel/probe": "^0.0.19",
@@ -828,15 +829,29 @@ const resolve = {
828
829
  // merges it, so an override's arrays elsewhere concatenate with the base's rather than
829
830
  // replacing them.
830
831
  export function mergeOverride(base: UserConfig, override?: UserConfig): UserConfig {
831
- if (override === undefined) return base
832
- if ('command' in override && 'mode' in override) {
832
+ if (override !== undefined && 'command' in override && 'mode' in override) {
833
833
  if (typeof override.mode !== 'string') {
834
834
  throw new Error('The project invocation carries no string mode')
835
835
  }
836
- return { ...base, mode: override.mode }
836
+ override = { mode: override.mode }
837
+ }
838
+ const merged: UserConfig = override === undefined ? { ...base } : mergeConfig(base, override)
839
+ const name = merged.test?.name
840
+ const label = typeof name === 'string' ? name : name?.label
841
+ const browser = merged.test?.browser
842
+ if (label !== undefined && browser?.instances !== undefined) {
843
+ merged.test = {
844
+ ...merged.test,
845
+ browser: {
846
+ ...browser,
847
+ instances: browser.instances.map((instance) => ({
848
+ ...instance,
849
+ name: instance.name ?? \`\${label} (\${instance.browser})\`,
850
+ })),
851
+ },
852
+ }
837
853
  }
838
- const merged: UserConfig = mergeConfig(base, override)
839
- if (merged.plugins === undefined) return merged
854
+ if (merged.plugins === undefined || override === undefined) return merged
840
855
  const candidates = override.plugins ?? []
841
856
  const taken = new Set<number>()
842
857
  const selected: PluginOption[] = []
@@ -899,6 +914,7 @@ function isNamedPlugin(plugin: PluginOption): plugin is { name: string } {
899
914
  instances: [{ browser: 'chromium', headless: true }],
900
915
  },
901
916
  fileParallelism: false,
917
+ sequence: { groupOrder: 1 },
902
918
  },
903
919
  }
904
920
  return mergeOverride(project, override)
@@ -958,6 +974,7 @@ export function srcVue(override?: UserConfig): UserConfig {
958
974
  instances: [{ browser: 'chromium', headless: true }],
959
975
  },
960
976
  fileParallelism: false,
977
+ sequence: { groupOrder: 1 },
961
978
  },
962
979
  }
963
980
  return mergeOverride(project, override)
@@ -1000,6 +1017,7 @@ export function srcBrowser(override?: UserConfig): UserConfig {
1000
1017
  instances: [{ browser: 'chromium', headless: true }],
1001
1018
  },
1002
1019
  fileParallelism: false,
1020
+ sequence: { groupOrder: 1 },
1003
1021
  },
1004
1022
  }
1005
1023
  return mergeOverride(project, override)
@@ -1134,6 +1152,7 @@ export function srcBin(override?: UserConfig): UserConfig {
1134
1152
  instances: [{ browser: 'chromium', headless: true }],
1135
1153
  },
1136
1154
  fileParallelism: false,
1155
+ sequence: { groupOrder: 1 },
1137
1156
  },
1138
1157
  }
1139
1158
  return mergeOverride(project, override)
@@ -1158,6 +1177,12 @@ export function srcBin(override?: UserConfig): UserConfig {
1158
1177
  test: {
1159
1178
  ...browser.test,
1160
1179
  name: { label: 'app:vue', color: 'magenta' },
1180
+ browser: {
1181
+ ...browser.test?.browser,
1182
+ instances: (browser.test?.browser?.instances ?? []).map(
1183
+ ({ name: _name, ...instance }) => instance,
1184
+ ),
1185
+ },
1161
1186
  include: ['tests/app/vue/**/*.test.ts'],
1162
1187
  {{journeyExclude}} },
1163
1188
  }
@@ -1174,7 +1199,7 @@ export function appJourney(
1174
1199
  ): UserConfig {
1175
1200
  const application = resolveApplication(mode, applications)
1176
1201
  const browser = applications[application]()
1177
- return {
1202
+ const project: UserConfig = {
1178
1203
  ...browser,
1179
1204
  test: {
1180
1205
  ...browser.test,
@@ -1186,9 +1211,13 @@ export function appJourney(
1186
1211
  ...browser.test?.browser,
1187
1212
  enabled: true,
1188
1213
  viewport: { width: variant.width, height: variant.height },
1214
+ instances: (browser.test?.browser?.instances ?? []).map(
1215
+ ({ name: _name, ...instance }) => instance,
1216
+ ),
1189
1217
  },
1190
1218
  },
1191
1219
  }
1220
+ return mergeOverride(project)
1192
1221
  }
1193
1222
  `,
1194
1223
  showcase: `
@@ -1427,6 +1456,7 @@ export function service(override?: UserConfig): UserConfig {
1427
1456
  testTimeout: 120_000,
1428
1457
  hookTimeout: 120_000,
1429
1458
  fileParallelism: false,
1459
+ sequence: { groupOrder: 1 },
1430
1460
  },
1431
1461
  }
1432
1462
  return mergeOverride(project, override)
@@ -1443,6 +1473,7 @@ export function service(override?: UserConfig): UserConfig {
1443
1473
  testTimeout: 120_000,
1444
1474
  hookTimeout: 120_000,
1445
1475
  fileParallelism: false,
1476
+ sequence: { groupOrder: 1 },
1446
1477
  },
1447
1478
  }
1448
1479
  return mergeOverride(project, override)
@@ -1463,6 +1494,7 @@ export function probe(override?: UserConfig): UserConfig {
1463
1494
  environment: 'node',
1464
1495
  browser: { enabled: false },
1465
1496
  fileParallelism: false,
1497
+ sequence: { groupOrder: 1 },
1466
1498
  pool: 'threads',
1467
1499
  benchmark: { include: ['tmp/probes/**/*.test.ts', 'tests/**/*.test.ts'] },
1468
1500
  },
@@ -1472,12 +1504,12 @@ export function probe(override?: UserConfig): UserConfig {
1472
1504
  `,
1473
1505
  integration: Object.freeze({
1474
1506
  sheet: `export function integration(override?: UserConfig): UserConfig {
1475
- const project = sheetProject('integration', {
1507
+ const project: UserConfig = {
1476
1508
  test: {
1477
1509
  include: ['tests/integration.test.ts'],
1478
1510
  {{global}} },
1479
- })
1480
- return mergeOverride(project, override)
1511
+ }
1512
+ return sheetProject('integration', mergeOverride(project, override))
1481
1513
  }
1482
1514
  `,
1483
1515
  module: `export function integration(override?: UserConfig): UserConfig {
@@ -2547,7 +2579,7 @@ import {
2547
2579
  import { tmpdir } from 'node:os'
2548
2580
  import { dirname, join, resolve } from 'node:path'
2549
2581
  import { fileURLToPath } from 'node:url'
2550
- {{launcher}}import { afterAll, describe, expect, it } from 'vitest'
2582
+ {{launcher}}import { afterAll, beforeAll, describe, expect, it } from 'vitest'
2551
2583
 
2552
2584
  const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
2553
2585
  const NPM = process.platform === 'win32' ? 'npm.cmd' : 'npm'
@@ -2763,7 +2795,7 @@ function runNpm(args: readonly string[], cwd: string): SpawnSyncReturns<string>
2763
2795
  return spawnSync(NPM, [...args], {
2764
2796
  cwd,
2765
2797
  encoding: 'utf8',
2766
- env: { ...process.env, npm_config_cache: CACHE },
2798
+ env: { ...process.env, npm_config_cache: join(readScratch(), 'cache') },
2767
2799
  shell: SHELL,
2768
2800
  windowsHide: true,
2769
2801
  })
@@ -3120,8 +3152,8 @@ function driveRuntime(stage: Stage, specifier: string, driver: string): readonly
3120
3152
  // published surface back off the installed tree. Every later claim reads this
3121
3153
  // result, so a failure here is raised where it happens rather than once per entry.
3122
3154
  function buildStage(): Stage {
3123
- const packed = join(SCRATCH, 'packed')
3124
- const consumer = join(SCRATCH, 'consumer')
3155
+ const packed = join(readScratch(), 'packed')
3156
+ const consumer = join(readScratch(), 'consumer')
3125
3157
  mkdirSync(packed, { recursive: true })
3126
3158
  const pack = runNpm(['pack', '--ignore-scripts', '--pack-destination', packed], ROOT)
3127
3159
  if (pack.status !== 0) throw new Error(\`npm pack refused this workspace: \${readOutput(pack)}\`)
@@ -3195,21 +3227,31 @@ function buildStage(): Stage {
3195
3227
  return { consumer, installed, archives, entries, subpaths, undeclared, excluded, targets }
3196
3228
  }
3197
3229
 
3198
- const SCRATCH = mkdtempSync(join(tmpdir(), 'distribution-'))
3199
- const CACHE = join(SCRATCH, 'cache')
3200
- mkdirSync(CACHE, { recursive: true })
3201
- // The scratch tree holds the npm cache, the packed archive, and the installed
3202
- // consumer, so its removal is registered before the first thing that can throw.
3230
+ let scratch: string | undefined
3231
+ let staged: Stage | undefined
3232
+
3233
+ function readScratch(): string {
3234
+ if (scratch === undefined) {
3235
+ throw new Error('The distribution setup has not allocated its scratch directory')
3236
+ }
3237
+ return scratch
3238
+ }
3239
+
3240
+ beforeAll(() => {
3241
+ scratch = mkdtempSync(join(tmpdir(), 'distribution-'))
3242
+ mkdirSync(join(scratch, 'cache'), { recursive: true })
3243
+ staged = openStage()
3244
+ })
3245
+
3203
3246
  afterAll(() => {
3204
- rmSync(SCRATCH, { force: true, recursive: true })
3247
+ if (scratch !== undefined) {
3248
+ rmSync(scratch, { force: true, recursive: true, maxRetries: 3, retryDelay: 100 })
3249
+ }
3205
3250
  })
3206
3251
 
3207
3252
  // Installing the packed archive resolves its own runtime dependencies, so an
3208
3253
  // unreachable registry leaves nothing to measure. Under release that is the gate
3209
3254
  // failing; anywhere else the suite skips and names the mechanism it wanted.
3210
- //
3211
- // A module that throws while loading never reaches the \`afterAll\` it registered,
3212
- // so every throw here removes the scratch tree on its way out.
3213
3255
  function openStage(): Stage | undefined {
3214
3256
  try {
3215
3257
  if (runNpm(PING, ROOT).status !== 0) {
@@ -3220,17 +3262,14 @@ function openStage(): Stage | undefined {
3220
3262
  }
3221
3263
  return buildStage()
3222
3264
  } catch (error) {
3223
- rmSync(SCRATCH, { force: true, recursive: true })
3265
+ rmSync(readScratch(), { force: true, recursive: true, maxRetries: 3, retryDelay: 100 })
3224
3266
  throw error
3225
3267
  }
3226
3268
  }
3227
3269
 
3228
- const STAGE = openStage()
3229
- const STAGED = STAGE !== undefined
3230
-
3231
3270
  describe('distribution classifiers', () => {
3232
3271
  it('classifies synthetic export mappings without a registry stage', () => {
3233
- const root = join(SCRATCH, 'classifiers')
3272
+ const root = join(readScratch(), 'classifiers')
3234
3273
  writeFile(
3235
3274
  join(root, 'package.json'),
3236
3275
  JSON.stringify({
@@ -3299,10 +3338,10 @@ describe('distribution classifiers', () => {
3299
3338
  // The staged consumer, or a skip naming what the run could not reach. \`it.skipIf\`
3300
3339
  // carries no reason, so the gate sits here where the test context can state one.
3301
3340
  function requireStage(context: TestContext): Stage {
3302
- if (!STAGED) {
3341
+ if (staged === undefined) {
3303
3342
  return context.skip('\`npm ping\` did not answer, so nothing was packed or installed')
3304
3343
  }
3305
- return STAGE
3344
+ return staged
3306
3345
  }
3307
3346
 
3308
3347
  describe('installed package consumer', () => {
@@ -3388,50 +3427,43 @@ describe('installed package consumer', () => {
3388
3427
  })
3389
3428
  {{guard}}})
3390
3429
 
3391
- for (const entry of STAGE?.entries ?? []) {
3392
- describe(\`installed entry \${entry.subpath}\`, () => {
3393
- it.runIf(entry.importable)(
3394
- 'publishes what it declares to a Node import, and no more',
3395
- (context) => {
3396
- const stage = requireStage(context)
3397
- // The exports-map walk resolved a declaration a typed importer reads, so an
3398
- // entry reaching this drive without one is reported for that rather than for
3399
- // what a consumer of a missing declaration goes on to say.
3400
- if (!entry.declaration.importable) {
3401
- throw new Error(\`\${entry.subpath} publishes no import declaration\`)
3402
- }
3403
- const published = driveRuntime(stage, entry.specifier, ESM_DRIVER)
3404
- const drivers = selectDrivers(entry, 'module')
3405
- expect(drivers).not.toStrictEqual([])
3406
- const reported = drivers.flatMap((driver) =>
3407
- checkSurface(stage, { entry, extension: 'ts', published, driver }),
3408
- )
3409
- expect(reported).toStrictEqual([])
3410
- },
3411
- )
3430
+ describe('installed entries', () => {
3431
+ it('publishes what it declares to a Node import, and no more', (context) => {
3432
+ const stage = requireStage(context)
3433
+ const entries = stage.entries.filter((entry) => entry.importable)
3434
+ if (entries.length === 0) return context.skip('No installed entry supports Node import')
3435
+ for (const entry of entries) {
3436
+ if (!entry.declaration.importable) {
3437
+ throw new Error(\`\${entry.subpath} publishes no import declaration\`)
3438
+ }
3439
+ const published = driveRuntime(stage, entry.specifier, ESM_DRIVER)
3440
+ const drivers = selectDrivers(entry, 'module')
3441
+ expect(drivers).not.toStrictEqual([])
3442
+ const reported = drivers.flatMap((driver) =>
3443
+ checkSurface(stage, { entry, extension: 'ts', published, driver }),
3444
+ )
3445
+ expect(reported).toStrictEqual([])
3446
+ }
3447
+ })
3412
3448
 
3413
- it.runIf(entry.requirable)(
3414
- 'publishes what it declares to a Node require, and no more',
3415
- (context) => {
3416
- const stage = requireStage(context)
3417
- if (!entry.declaration.requirable) {
3418
- throw new Error(\`\${entry.subpath} publishes no require declaration\`)
3419
- }
3420
- const published = driveRuntime(stage, entry.specifier, CJS_DRIVER)
3421
- // A subpath whose \`require\` resolves to a module that no typed CommonJS
3422
- // consumer can compile against carries no declared side to compare here, and
3423
- // whether it may publish one at all is the untypable set's question rather
3424
- // than this drive's. The preceding runtime drive ran either way.
3425
- const drivers = selectDrivers(entry, 'commonjs')
3426
- expect(drivers).not.toStrictEqual([])
3427
- const reported = drivers.flatMap((driver) =>
3428
- checkSurface(stage, { entry, extension: 'cts', published, driver }),
3429
- )
3430
- expect(reported).toStrictEqual([])
3431
- },
3432
- )
3433
- {{drive}} })
3434
- }
3449
+ it('publishes what it declares to a Node require, and no more', (context) => {
3450
+ const stage = requireStage(context)
3451
+ const entries = stage.entries.filter((entry) => entry.requirable)
3452
+ if (entries.length === 0) return context.skip('No installed entry supports Node require')
3453
+ for (const entry of entries) {
3454
+ if (!entry.declaration.requirable) {
3455
+ throw new Error(\`\${entry.subpath} publishes no require declaration\`)
3456
+ }
3457
+ const published = driveRuntime(stage, entry.specifier, CJS_DRIVER)
3458
+ const drivers = selectDrivers(entry, 'commonjs')
3459
+ expect(drivers).not.toStrictEqual([])
3460
+ const reported = drivers.flatMap((driver) =>
3461
+ checkSurface(stage, { entry, extension: 'cts', published, driver }),
3462
+ )
3463
+ expect(reported).toStrictEqual([])
3464
+ }
3465
+ })
3466
+ {{drive}}})
3435
3467
  `,
3436
3468
  transport: `import { createServer } from 'node:http'
3437
3469
  `,
@@ -3540,36 +3572,37 @@ async function readBrowserExports(browser: Browser, bundle: string): Promise<rea
3540
3572
  }
3541
3573
  `,
3542
3574
  drive: `
3543
- it.runIf(entry.browsable)(
3544
- 'publishes what it declares to a real browser, and no more [requires a browser]',
3545
- async (context) => {
3546
- const stage = requireStage(context)
3547
- if (!entry.declaration.browsable) {
3548
- throw new Error(\`\${entry.subpath} publishes no browser declaration\`)
3549
- }
3550
- const options = resolveBrowser(resolvePinnedBrowser(), process.platform, process.env)
3551
- const browser = await launchBrowser(options).catch((error: unknown) => {
3552
- const cause = \`\${describeBrowser(options)} was rejected: \${String(error)}\`
3553
- if (RELEASE) throw new Error(\`The release gate requires a browser, and \${cause}\`)
3554
- return context.skip(\`No browser launched. \${cause}\`)
3575
+ it('publishes what it declares to a real browser [requires a browser]', async (context) => {
3576
+ const stage = requireStage(context)
3577
+ const entries = stage.entries.filter((entry) => entry.browsable)
3578
+ if (entries.length === 0) return context.skip('No installed entry supports a browser')
3579
+ for (const entry of entries) {
3580
+ if (!entry.declaration.browsable) {
3581
+ throw new Error(\`\${entry.subpath} publishes no browser declaration\`)
3582
+ }
3583
+ const options = resolveBrowser(resolvePinnedBrowser(), process.platform, process.env)
3584
+ const browser = await launchBrowser(options).catch((error: unknown) => {
3585
+ const cause = \`\${describeBrowser(options)} was rejected: \${String(error)}\`
3586
+ if (RELEASE) throw new Error(\`The release gate requires a browser, and \${cause}\`)
3587
+ return context.skip(\`No browser launched. \${cause}\`)
3588
+ })
3589
+ try {
3590
+ const bundle = await bundleEntry(stage, entry)
3591
+ const published = await readBrowserExports(browser, bundle)
3592
+ // A browser consumer reads the installed declarations through a bundler, so
3593
+ // that is the one driver this face answers under.
3594
+ const reported = checkSurface(stage, {
3595
+ entry,
3596
+ extension: 'ts',
3597
+ published,
3598
+ driver: BROWSER_DRIVER,
3555
3599
  })
3556
- try {
3557
- const bundle = await bundleEntry(stage, entry)
3558
- const published = await readBrowserExports(browser, bundle)
3559
- // A browser consumer reads the installed declarations through a bundler, so
3560
- // that is the one driver this face answers under.
3561
- const reported = checkSurface(stage, {
3562
- entry,
3563
- extension: 'ts',
3564
- published,
3565
- driver: BROWSER_DRIVER,
3566
- })
3567
- expect(reported).toStrictEqual([])
3568
- } finally {
3569
- await browser.close()
3570
- }
3571
- },
3572
- )
3600
+ expect(reported).toStrictEqual([])
3601
+ } finally {
3602
+ await browser.close()
3603
+ }
3604
+ }
3605
+ })
3573
3606
  `,
3574
3607
  guard: `
3575
3608
  // This proof drives a Node import and a Node require and carries no browser
@@ -3581,7 +3614,7 @@ async function readBrowserExports(browser: Browser, bundle: string): Promise<rea
3581
3614
  // a published browser face. \`vite\` selects nothing either, though the branch
3582
3615
  // imports it: scaffold puts \`vite\` in every workspace's base development
3583
3616
  // dependencies, whatever that workspace publishes. The later Node
3584
- // \`it.runIf\` predicates retire each matching Node drive for a face published
3617
+ // entry filters retire each matching Node drive for a face published
3585
3618
  // later, which leaves nothing measuring it. So it reddens here and names the
3586
3619
  // subpath a browser branch is owed for. A workspace that gains one deletes this
3587
3620
  // file and runs the \`repair\` verb, which writes the variant carrying that branch.