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