@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.
- package/dist/bin/main.js +144 -23
- package/dist/bin/main.js.map +1 -1
- package/dist/host/agents/orchestration.md +48 -6
- package/dist/host/agents/skills/orkestrel-falsify/SKILL.md +12 -0
- package/dist/host/claude/agents/codex.md +10 -0
- package/dist/host/claude/agents/grok.md +19 -8
- package/dist/host/claude/rules/architecture.md +25 -0
- package/dist/host/claude/rules/documentation.md +2 -1
- package/dist/host/claude/rules/tests.md +58 -21
- package/dist/host/claude/rules/workspace.md +25 -20
- package/dist/host/dotfiles/oxlintrc.json +1 -0
- package/dist/host/guides/scaffold.md +142 -67
- package/dist/host/tests/config.test.ts +45 -17
- package/dist/host/tests/policy.test.ts +254 -2
- package/dist/host/tests/setupPolicy.ts +297 -34
- package/dist/src/core/index.cjs +176 -179
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +90 -33
- package/dist/src/core/index.d.ts +90 -33
- package/dist/src/core/index.js +171 -180
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +13 -1
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.js +14 -2
- package/dist/src/server/index.js.map +1 -1
- package/package.json +6 -5
package/dist/src/core/index.cjs
CHANGED
|
@@ -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
|
|
210
|
+
/** The guide-parity proof whose presence selects the planned `guides` project. */
|
|
201
211
|
var GUIDES_TEST_PATH = "tests/guides.test.ts";
|
|
202
|
-
/** The
|
|
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
|
-
"@
|
|
292
|
-
"@orkestrel/
|
|
293
|
-
"@orkestrel/
|
|
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 {
|
|
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(
|
|
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 {
|
|
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('
|
|
1398
|
-
it('loads
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
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
|
|
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
|
|
3088
|
-
*
|
|
3089
|
-
*
|
|
3090
|
-
*
|
|
3091
|
-
*
|
|
3092
|
-
*
|
|
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
|
|
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.
|
|
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
|
-
...
|
|
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(
|
|
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
|
-
|
|
3476
|
-
|
|
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 (
|
|
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
|
|
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
|
|
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
|
|
3702
|
-
*
|
|
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
|
|
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.
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
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,
|
|
4169
|
-
* manifest names a core build the workspace never runs
|
|
4170
|
-
* whose required axis is absent, which emits nothing
|
|
4171
|
-
*
|
|
4172
|
-
*
|
|
4173
|
-
*
|
|
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
|
|
4214
|
+
if (blueprint.integration && blueprint.src.length + blueprint.app.length < 2) questions.push({
|
|
4231
4215
|
field: "integration",
|
|
4232
|
-
message: "integration
|
|
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;
|