@orkestrel/scaffold 0.0.88 → 0.0.90

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.88",
7
+ version: "0.0.90",
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.21",
107
+ "@orkestrel/browser": "^0.0.22",
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[] = []
@@ -1163,6 +1178,12 @@ export function srcBin(override?: UserConfig): UserConfig {
1163
1178
  test: {
1164
1179
  ...browser.test,
1165
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
+ },
1166
1187
  include: ['tests/app/vue/**/*.test.ts'],
1167
1188
  {{journeyExclude}} },
1168
1189
  }
@@ -1179,7 +1200,7 @@ export function appJourney(
1179
1200
  ): UserConfig {
1180
1201
  const application = resolveApplication(mode, applications)
1181
1202
  const browser = applications[application]()
1182
- return {
1203
+ const project: UserConfig = {
1183
1204
  ...browser,
1184
1205
  test: {
1185
1206
  ...browser.test,
@@ -1191,9 +1212,13 @@ export function appJourney(
1191
1212
  ...browser.test?.browser,
1192
1213
  enabled: true,
1193
1214
  viewport: { width: variant.width, height: variant.height },
1215
+ instances: (browser.test?.browser?.instances ?? []).map(
1216
+ ({ name: _name, ...instance }) => instance,
1217
+ ),
1194
1218
  },
1195
1219
  },
1196
1220
  }
1221
+ return mergeOverride(project)
1197
1222
  }
1198
1223
  `,
1199
1224
  showcase: `
@@ -1480,12 +1505,12 @@ export function probe(override?: UserConfig): UserConfig {
1480
1505
  `,
1481
1506
  integration: Object.freeze({
1482
1507
  sheet: `export function integration(override?: UserConfig): UserConfig {
1483
- const project = sheetProject('integration', {
1508
+ const project: UserConfig = {
1484
1509
  test: {
1485
1510
  include: ['tests/integration.test.ts'],
1486
1511
  {{global}} },
1487
- })
1488
- return mergeOverride(project, override)
1512
+ }
1513
+ return sheetProject('integration', mergeOverride(project, override))
1489
1514
  }
1490
1515
  `,
1491
1516
  module: `export function integration(override?: UserConfig): UserConfig {
@@ -2555,7 +2580,7 @@ import {
2555
2580
  import { tmpdir } from 'node:os'
2556
2581
  import { dirname, join, resolve } from 'node:path'
2557
2582
  import { fileURLToPath } from 'node:url'
2558
- {{launcher}}import { afterAll, describe, expect, it } from 'vitest'
2583
+ {{launcher}}import { afterAll, beforeAll, describe, expect, it } from 'vitest'
2559
2584
 
2560
2585
  const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
2561
2586
  const NPM = process.platform === 'win32' ? 'npm.cmd' : 'npm'
@@ -2771,7 +2796,7 @@ function runNpm(args: readonly string[], cwd: string): SpawnSyncReturns<string>
2771
2796
  return spawnSync(NPM, [...args], {
2772
2797
  cwd,
2773
2798
  encoding: 'utf8',
2774
- env: { ...process.env, npm_config_cache: CACHE },
2799
+ env: { ...process.env, npm_config_cache: join(readScratch(), 'cache') },
2775
2800
  shell: SHELL,
2776
2801
  windowsHide: true,
2777
2802
  })
@@ -3128,8 +3153,8 @@ function driveRuntime(stage: Stage, specifier: string, driver: string): readonly
3128
3153
  // published surface back off the installed tree. Every later claim reads this
3129
3154
  // result, so a failure here is raised where it happens rather than once per entry.
3130
3155
  function buildStage(): Stage {
3131
- const packed = join(SCRATCH, 'packed')
3132
- const consumer = join(SCRATCH, 'consumer')
3156
+ const packed = join(readScratch(), 'packed')
3157
+ const consumer = join(readScratch(), 'consumer')
3133
3158
  mkdirSync(packed, { recursive: true })
3134
3159
  const pack = runNpm(['pack', '--ignore-scripts', '--pack-destination', packed], ROOT)
3135
3160
  if (pack.status !== 0) throw new Error(\`npm pack refused this workspace: \${readOutput(pack)}\`)
@@ -3203,21 +3228,31 @@ function buildStage(): Stage {
3203
3228
  return { consumer, installed, archives, entries, subpaths, undeclared, excluded, targets }
3204
3229
  }
3205
3230
 
3206
- const SCRATCH = mkdtempSync(join(tmpdir(), 'distribution-'))
3207
- const CACHE = join(SCRATCH, 'cache')
3208
- mkdirSync(CACHE, { recursive: true })
3209
- // The scratch tree holds the npm cache, the packed archive, and the installed
3210
- // 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
+
3211
3247
  afterAll(() => {
3212
- rmSync(SCRATCH, { force: true, recursive: true })
3248
+ if (scratch !== undefined) {
3249
+ rmSync(scratch, { force: true, recursive: true, maxRetries: 3, retryDelay: 100 })
3250
+ }
3213
3251
  })
3214
3252
 
3215
3253
  // Installing the packed archive resolves its own runtime dependencies, so an
3216
3254
  // unreachable registry leaves nothing to measure. Under release that is the gate
3217
3255
  // failing; anywhere else the suite skips and names the mechanism it wanted.
3218
- //
3219
- // A module that throws while loading never reaches the \`afterAll\` it registered,
3220
- // so every throw here removes the scratch tree on its way out.
3221
3256
  function openStage(): Stage | undefined {
3222
3257
  try {
3223
3258
  if (runNpm(PING, ROOT).status !== 0) {
@@ -3228,17 +3263,14 @@ function openStage(): Stage | undefined {
3228
3263
  }
3229
3264
  return buildStage()
3230
3265
  } catch (error) {
3231
- rmSync(SCRATCH, { force: true, recursive: true })
3266
+ rmSync(readScratch(), { force: true, recursive: true, maxRetries: 3, retryDelay: 100 })
3232
3267
  throw error
3233
3268
  }
3234
3269
  }
3235
3270
 
3236
- const STAGE = openStage()
3237
- const STAGED = STAGE !== undefined
3238
-
3239
3271
  describe('distribution classifiers', () => {
3240
3272
  it('classifies synthetic export mappings without a registry stage', () => {
3241
- const root = join(SCRATCH, 'classifiers')
3273
+ const root = join(readScratch(), 'classifiers')
3242
3274
  writeFile(
3243
3275
  join(root, 'package.json'),
3244
3276
  JSON.stringify({
@@ -3307,10 +3339,10 @@ describe('distribution classifiers', () => {
3307
3339
  // The staged consumer, or a skip naming what the run could not reach. \`it.skipIf\`
3308
3340
  // carries no reason, so the gate sits here where the test context can state one.
3309
3341
  function requireStage(context: TestContext): Stage {
3310
- if (!STAGED) {
3342
+ if (staged === undefined) {
3311
3343
  return context.skip('\`npm ping\` did not answer, so nothing was packed or installed')
3312
3344
  }
3313
- return STAGE
3345
+ return staged
3314
3346
  }
3315
3347
 
3316
3348
  describe('installed package consumer', () => {
@@ -3396,50 +3428,43 @@ describe('installed package consumer', () => {
3396
3428
  })
3397
3429
  {{guard}}})
3398
3430
 
3399
- for (const entry of STAGE?.entries ?? []) {
3400
- describe(\`installed entry \${entry.subpath}\`, () => {
3401
- it.runIf(entry.importable)(
3402
- 'publishes what it declares to a Node import, and no more',
3403
- (context) => {
3404
- const stage = requireStage(context)
3405
- // The exports-map walk resolved a declaration a typed importer reads, so an
3406
- // entry reaching this drive without one is reported for that rather than for
3407
- // what a consumer of a missing declaration goes on to say.
3408
- if (!entry.declaration.importable) {
3409
- throw new Error(\`\${entry.subpath} publishes no import declaration\`)
3410
- }
3411
- const published = driveRuntime(stage, entry.specifier, ESM_DRIVER)
3412
- const drivers = selectDrivers(entry, 'module')
3413
- expect(drivers).not.toStrictEqual([])
3414
- const reported = drivers.flatMap((driver) =>
3415
- checkSurface(stage, { entry, extension: 'ts', published, driver }),
3416
- )
3417
- expect(reported).toStrictEqual([])
3418
- },
3419
- )
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
+ })
3420
3449
 
3421
- it.runIf(entry.requirable)(
3422
- 'publishes what it declares to a Node require, and no more',
3423
- (context) => {
3424
- const stage = requireStage(context)
3425
- if (!entry.declaration.requirable) {
3426
- throw new Error(\`\${entry.subpath} publishes no require declaration\`)
3427
- }
3428
- const published = driveRuntime(stage, entry.specifier, CJS_DRIVER)
3429
- // A subpath whose \`require\` resolves to a module that no typed CommonJS
3430
- // consumer can compile against carries no declared side to compare here, and
3431
- // whether it may publish one at all is the untypable set's question rather
3432
- // than this drive's. The preceding runtime drive ran either way.
3433
- const drivers = selectDrivers(entry, 'commonjs')
3434
- expect(drivers).not.toStrictEqual([])
3435
- const reported = drivers.flatMap((driver) =>
3436
- checkSurface(stage, { entry, extension: 'cts', published, driver }),
3437
- )
3438
- expect(reported).toStrictEqual([])
3439
- },
3440
- )
3441
- {{drive}} })
3442
- }
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}}})
3443
3468
  `,
3444
3469
  transport: `import { createServer } from 'node:http'
3445
3470
  `,
@@ -3548,36 +3573,37 @@ async function readBrowserExports(browser: Browser, bundle: string): Promise<rea
3548
3573
  }
3549
3574
  `,
3550
3575
  drive: `
3551
- it.runIf(entry.browsable)(
3552
- 'publishes what it declares to a real browser, and no more [requires a browser]',
3553
- async (context) => {
3554
- const stage = requireStage(context)
3555
- if (!entry.declaration.browsable) {
3556
- throw new Error(\`\${entry.subpath} publishes no browser declaration\`)
3557
- }
3558
- const options = resolveBrowser(resolvePinnedBrowser(), process.platform, process.env)
3559
- const browser = await launchBrowser(options).catch((error: unknown) => {
3560
- const cause = \`\${describeBrowser(options)} was rejected: \${String(error)}\`
3561
- if (RELEASE) throw new Error(\`The release gate requires a browser, and \${cause}\`)
3562
- 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,
3563
3600
  })
3564
- try {
3565
- const bundle = await bundleEntry(stage, entry)
3566
- const published = await readBrowserExports(browser, bundle)
3567
- // A browser consumer reads the installed declarations through a bundler, so
3568
- // that is the one driver this face answers under.
3569
- const reported = checkSurface(stage, {
3570
- entry,
3571
- extension: 'ts',
3572
- published,
3573
- driver: BROWSER_DRIVER,
3574
- })
3575
- expect(reported).toStrictEqual([])
3576
- } finally {
3577
- await browser.close()
3578
- }
3579
- },
3580
- )
3601
+ expect(reported).toStrictEqual([])
3602
+ } finally {
3603
+ await browser.close()
3604
+ }
3605
+ }
3606
+ })
3581
3607
  `,
3582
3608
  guard: `
3583
3609
  // This proof drives a Node import and a Node require and carries no browser
@@ -3589,7 +3615,7 @@ async function readBrowserExports(browser: Browser, bundle: string): Promise<rea
3589
3615
  // a published browser face. \`vite\` selects nothing either, though the branch
3590
3616
  // imports it: scaffold puts \`vite\` in every workspace's base development
3591
3617
  // dependencies, whatever that workspace publishes. The later Node
3592
- // \`it.runIf\` predicates retire each matching Node drive for a face published
3618
+ // entry filters retire each matching Node drive for a face published
3593
3619
  // later, which leaves nothing measuring it. So it reddens here and names the
3594
3620
  // subpath a browser branch is owed for. A workspace that gains one deletes this
3595
3621
  // file and runs the \`repair\` verb, which writes the variant carrying that branch.