@orkestrel/scaffold 0.0.22 → 0.0.24

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.
Files changed (39) hide show
  1. package/README.md +84 -99
  2. package/dist/bin/main.js +1094 -0
  3. package/dist/bin/main.js.map +1 -0
  4. package/dist/host/CLAUDE.md +3 -1
  5. package/dist/host/agents/orchestration.md +61 -4
  6. package/dist/host/agents/skills/orkestrel-align-packages/SKILL.md +1 -1
  7. package/dist/host/agents/skills/orkestrel-falsify/SKILL.md +7 -5
  8. package/dist/host/agents/skills/orkestrel-harden-package/SKILL.md +1 -1
  9. package/dist/host/agents/skills/orkestrel-harden-package/references/contract.md +1 -1
  10. package/dist/host/claude/agents/orkestrel.md +9 -9
  11. package/dist/host/claude/rules/architecture.md +45 -3
  12. package/dist/host/claude/rules/quality.md +4 -0
  13. package/dist/host/claude/rules/tests.md +57 -1
  14. package/dist/host/claude/rules/workspace.md +50 -17
  15. package/dist/host/codex/agents/orkestrel.toml +1 -1
  16. package/dist/host/configs/helpers.ts +762 -0
  17. package/dist/host/dotfiles/oxlintrc.json +2 -1
  18. package/dist/host/guides/scaffold.md +862 -0
  19. package/dist/host/manifest.json +40 -33
  20. package/dist/host/tests/config.test.ts +544 -0
  21. package/dist/host/tests/policy.test.ts +46 -0
  22. package/dist/host/tests/setupPolicy.ts +557 -602
  23. package/dist/src/core/index.cjs +3569 -10510
  24. package/dist/src/core/index.cjs.map +1 -1
  25. package/dist/src/core/index.d.cts +2361 -2789
  26. package/dist/src/core/index.d.ts +2361 -2789
  27. package/dist/src/core/index.js +3513 -10374
  28. package/dist/src/core/index.js.map +1 -1
  29. package/dist/src/server/index.cjs +2855 -3765
  30. package/dist/src/server/index.cjs.map +1 -1
  31. package/dist/src/server/index.d.cts +1920 -1335
  32. package/dist/src/server/index.d.ts +1920 -1335
  33. package/dist/src/server/index.js +2812 -3680
  34. package/dist/src/server/index.js.map +1 -1
  35. package/package.json +16 -23
  36. package/dist/bin/scaffold.js +0 -1896
  37. package/dist/bin/scaffold.js.map +0 -1
  38. package/dist/host/guides/src/scaffold.md +0 -2886
  39. /package/dist/host/guides/{src/guide.md → guide.md} +0 -0
@@ -1,5 +1,20 @@
1
1
  {
2
2
  "entries": [
3
+ {
4
+ "storage": "AGENTS.md",
5
+ "destination": "AGENTS.md",
6
+ "executable": false
7
+ },
8
+ {
9
+ "storage": "CLAUDE.md",
10
+ "destination": "CLAUDE.md",
11
+ "executable": false
12
+ },
13
+ {
14
+ "storage": "LICENSE",
15
+ "destination": "LICENSE",
16
+ "executable": false
17
+ },
3
18
  {
4
19
  "storage": "agents/orchestration.md",
5
20
  "destination": ".agents/orchestration.md",
@@ -380,6 +395,11 @@
380
395
  "destination": ".codex/config.toml",
381
396
  "executable": false
382
397
  },
398
+ {
399
+ "storage": "configs/helpers.ts",
400
+ "destination": "configs/helpers.ts",
401
+ "executable": false
402
+ },
383
403
  {
384
404
  "storage": "cursor/mcp.json",
385
405
  "destination": ".cursor/mcp.json",
@@ -431,49 +451,44 @@
431
451
  "executable": false
432
452
  },
433
453
  {
434
- "storage": "AGENTS.md",
435
- "destination": "AGENTS.md",
454
+ "storage": "guides/guide.md",
455
+ "destination": "guides/guide.md",
436
456
  "executable": false
437
457
  },
438
458
  {
439
- "storage": "CLAUDE.md",
440
- "destination": "CLAUDE.md",
441
- "executable": false
442
- },
443
- {
444
- "storage": "LICENSE",
445
- "destination": "LICENSE",
446
- "executable": false
447
- },
448
- {
449
- "storage": "guides/src/guide.md",
450
- "destination": "guides/src/guide.md",
451
- "executable": false
452
- },
453
- {
454
- "storage": "guides/src/scaffold.md",
455
- "destination": "guides/src/scaffold.md",
459
+ "storage": "guides/scaffold.md",
460
+ "destination": "guides/scaffold.md",
456
461
  "executable": false
457
462
  },
458
463
  {
459
464
  "storage": "scripts/codex.sh",
460
465
  "destination": "scripts/codex.sh",
461
- "executable": true
466
+ "executable": false
462
467
  },
463
468
  {
464
469
  "storage": "scripts/cursor.sh",
465
470
  "destination": "scripts/cursor.sh",
466
- "executable": true
471
+ "executable": false
467
472
  },
468
473
  {
469
474
  "storage": "scripts/deps.sh",
470
475
  "destination": "scripts/deps.sh",
471
- "executable": true
476
+ "executable": false
472
477
  },
473
478
  {
474
479
  "storage": "scripts/ollama.sh",
475
480
  "destination": "scripts/ollama.sh",
476
- "executable": true
481
+ "executable": false
482
+ },
483
+ {
484
+ "storage": "tests/config.test.ts",
485
+ "destination": "tests/config.test.ts",
486
+ "executable": false
487
+ },
488
+ {
489
+ "storage": "tests/policy.test.ts",
490
+ "destination": "tests/policy.test.ts",
491
+ "executable": false
477
492
  },
478
493
  {
479
494
  "storage": "tests/setupPolicy.ts",
@@ -482,7 +497,6 @@
482
497
  }
483
498
  ],
484
499
  "roots": [
485
- ".agents",
486
500
  ".agents/skills",
487
501
  ".agents/skills/enterprise-bootstrap",
488
502
  ".agents/skills/enterprise-bootstrap/agents",
@@ -504,7 +518,6 @@
504
518
  ".agents/skills/orkestrel-polish-surface",
505
519
  ".agents/skills/orkestrel-polish-surface/agents",
506
520
  ".agents/skills/orkestrel-polish-surface/references",
507
- ".claude",
508
521
  ".claude/agents",
509
522
  ".claude/rules",
510
523
  ".claude/skills",
@@ -515,14 +528,8 @@
515
528
  ".claude/skills/orkestrel-falsify",
516
529
  ".claude/skills/orkestrel-harden-package",
517
530
  ".claude/skills/orkestrel-polish-surface",
518
- ".codex",
519
531
  ".codex/agents",
520
- ".cursor",
521
- ".cursor/rules",
522
- "guides",
523
- "guides/src",
524
- "scripts",
525
- "tests"
532
+ ".cursor/rules"
526
533
  ],
527
- "digest": "e4351919e4d34e3696ee3ce77e2cce555c20acc58a29649cd9f3c489f3d0bf2b"
534
+ "digest": "b852817ada9fa3b30ce2ee47c7bf786e46dfbc1c5531e4d208b0bf8edcc2482c"
528
535
  }
@@ -0,0 +1,544 @@
1
+ // P1: Every checked population must exist and be non-empty; absence fails instead of passing vacuously.
2
+ // P2: Required items are checked strictly; extra items are ignored before their shape is read.
3
+
4
+ import {
5
+ existsSync,
6
+ globSync,
7
+ mkdtempSync,
8
+ mkdirSync,
9
+ readFileSync,
10
+ realpathSync,
11
+ rmSync,
12
+ writeFileSync,
13
+ } from 'node:fs'
14
+ import { tmpdir } from 'node:os'
15
+ import { dirname, join, resolve } from 'node:path'
16
+ import { fileURLToPath, pathToFileURL } from 'node:url'
17
+ import { build, loadConfigFromFile } from 'vite'
18
+ import * as configHelpers from '../configs/helpers.js'
19
+ import configuration, { resolveWorkspacePath } from '../vite.config.js'
20
+ import tsconfig from '../tsconfig.json' with { type: 'json' }
21
+ import { describe, expect, it } from 'vitest'
22
+
23
+ const root = resolve(dirname(fileURLToPath(import.meta.url)), '..')
24
+
25
+ describe('root configuration', () => {
26
+ it('resolves every declared alias to its real entry', () => {
27
+ const aliases = configuration.resolve?.alias
28
+ if (typeof aliases !== 'object' || aliases === null || Array.isArray(aliases)) {
29
+ throw new Error('The root configuration carries no alias record')
30
+ }
31
+ const required = new Map<string, string>()
32
+ for (const axis of ['src', 'app']) {
33
+ for (const environment of ['core', 'browser', 'server']) {
34
+ const path = `${axis}/${environment}/index.ts`
35
+ if (existsSync(resolve(root, path))) required.set(`@${axis}/${environment}`, path)
36
+ }
37
+ }
38
+ if (required.size === 0) throw new Error('The workspace selects no alias target')
39
+ const declared = new Map(Object.entries(tsconfig.compilerOptions.paths))
40
+ const absent = new Map<string, readonly string[]>()
41
+ expect(() => {
42
+ for (const key of required.keys()) {
43
+ if (!absent.has(key)) throw new Error('The alias population carries no required entry')
44
+ }
45
+ }).toThrowError('The alias population carries no required entry')
46
+ for (const [key, expected] of required) {
47
+ const values = declared.get(key)
48
+ if (values === undefined) throw new Error(`${key} is not declared`)
49
+ const [path] = values
50
+ if (path === undefined) throw new Error(`${key} carries no target`)
51
+ const target = resolveWorkspacePath(path)
52
+ expect(existsSync(target)).toBe(true)
53
+ expect(target).toBe(resolve(root, expected))
54
+ expect(Object.getOwnPropertyDescriptor(aliases, key)?.value).toBe(target)
55
+ }
56
+ })
57
+
58
+ it('registers every workspace project with its fixed include and setup files', () => {
59
+ const expected = new Map<
60
+ string,
61
+ { readonly include: string; readonly setup: readonly string[] }
62
+ >()
63
+ if (existsSync(resolve(root, 'src/core'))) {
64
+ expected.set('src:core', {
65
+ include: 'tests/src/core/**/*.test.ts',
66
+ setup: ['./tests/setup.ts'],
67
+ })
68
+ }
69
+ if (existsSync(resolve(root, 'src/browser'))) {
70
+ expected.set('src:browser', {
71
+ include: 'tests/src/browser/**/*.test.ts',
72
+ setup: ['./tests/setup.ts', './tests/setupBrowser.ts'],
73
+ })
74
+ }
75
+ if (existsSync(resolve(root, 'src/server'))) {
76
+ expected.set('src:server', {
77
+ include: 'tests/src/server/**/*.test.ts',
78
+ setup: ['./tests/setup.ts', './tests/setupServer.ts'],
79
+ })
80
+ }
81
+ if (existsSync(resolve(root, 'src/bin'))) {
82
+ expected.set('src:bin', {
83
+ include: 'tests/src/bin/**/*.test.ts',
84
+ setup: ['./tests/setup.ts', './tests/setupServer.ts'],
85
+ })
86
+ }
87
+ if (existsSync(resolve(root, 'app/core'))) {
88
+ expected.set('app:core', {
89
+ include: 'tests/app/core/**/*.test.ts',
90
+ setup: ['./tests/setup.ts'],
91
+ })
92
+ }
93
+ if (existsSync(resolve(root, 'app/browser'))) {
94
+ expected.set('app:browser', {
95
+ include: 'tests/app/browser/**/*.test.ts',
96
+ setup: ['./tests/setup.ts', './tests/setupBrowser.ts'],
97
+ })
98
+ }
99
+ if (existsSync(resolve(root, 'app/server'))) {
100
+ expected.set('app:server', {
101
+ include: 'tests/app/server/**/*.test.ts',
102
+ setup: ['./tests/setup.ts', './tests/setupServer.ts'],
103
+ })
104
+ }
105
+ for (const label of ['policy', 'config', 'guides', 'integration']) {
106
+ if (!existsSync(resolve(root, `tests/${label}.test.ts`))) continue
107
+ expected.set(label, {
108
+ include: `tests/${label}.test.ts`,
109
+ setup: ['./tests/setup.ts'],
110
+ })
111
+ }
112
+ expected.set('probe', { include: 'tmp/probe/**/*.test.ts', setup: ['./tests/setup.ts'] })
113
+
114
+ const projects = configuration.test?.projects
115
+ if (!Array.isArray(projects)) throw new Error('The root configuration carries no projects')
116
+ let extraLoaded = false
117
+ const control = Object.defineProperty(
118
+ () => {
119
+ extraLoaded = true
120
+ return {
121
+ test: {
122
+ name: { label: 'control' },
123
+ include: ['tests/control.test.ts', 'tests/control.integration.test.ts'],
124
+ setupFiles: ['./tests/setup.ts'],
125
+ },
126
+ }
127
+ },
128
+ 'name',
129
+ { value: 'control' },
130
+ )
131
+ const controlled = projects.concat(control)
132
+ const configured = new Map<
133
+ string,
134
+ { readonly include: string; readonly setup: readonly string[] }
135
+ >()
136
+ for (const [requiredLabel] of expected) {
137
+ const factoryName = requiredLabel.replace(/:([a-z])/gu, (_match, letter: string) =>
138
+ letter.toUpperCase(),
139
+ )
140
+ const factory = controlled.find(
141
+ (candidate) => typeof candidate === 'function' && candidate.name === factoryName,
142
+ )
143
+ if (typeof factory !== 'function') {
144
+ throw new Error(`${requiredLabel} has no project factory`)
145
+ }
146
+ const project: unknown = Reflect.apply(factory, undefined, [])
147
+ if (typeof project !== 'object' || project === null) {
148
+ throw new Error('A project factory returned no configuration')
149
+ }
150
+ const test: unknown = Object.getOwnPropertyDescriptor(project, 'test')?.value
151
+ if (typeof test !== 'object' || test === null) {
152
+ throw new Error('A project configuration carries no test block')
153
+ }
154
+ const name: unknown = Object.getOwnPropertyDescriptor(test, 'name')?.value
155
+ const include: unknown = Object.getOwnPropertyDescriptor(test, 'include')?.value
156
+ const exclude: unknown = Object.getOwnPropertyDescriptor(test, 'exclude')?.value
157
+ const setup: unknown = Object.getOwnPropertyDescriptor(test, 'setupFiles')?.value
158
+ const label =
159
+ typeof name === 'object' && name !== null
160
+ ? Object.getOwnPropertyDescriptor(name, 'label')?.value
161
+ : undefined
162
+ if (
163
+ typeof label !== 'string' ||
164
+ !Array.isArray(include) ||
165
+ typeof include[0] !== 'string' ||
166
+ !Array.isArray(setup) ||
167
+ !setup.every((path) => typeof path === 'string')
168
+ ) {
169
+ throw new Error('A project does not expose one include and its setup files')
170
+ }
171
+ const effective = include.filter(
172
+ (path) => typeof path === 'string' && (!Array.isArray(exclude) || !exclude.includes(path)),
173
+ )
174
+ if (effective.length !== 1 || typeof effective[0] !== 'string') {
175
+ throw new Error(`${label} does not resolve to one effective include`)
176
+ }
177
+ configured.set(label, { include: effective[0], setup: [...new Set(setup)] })
178
+ }
179
+
180
+ // Required projects come from present source and test paths. Each factory is selected by name
181
+ // before its result is read. Extra factories are ignored without validating their result shape.
182
+ expect(extraLoaded).toBe(false)
183
+ for (const [label, project] of expected) expect(configured.get(label)).toStrictEqual(project)
184
+
185
+ const missing = new Map(configured)
186
+ expect(missing.delete('probe')).toBe(true)
187
+ expect(() => {
188
+ for (const [label, project] of expected) expect(missing.get(label)).toStrictEqual(project)
189
+ }).toThrowError(/strictly equal/u)
190
+
191
+ const misconfigured = new Map(configured)
192
+ const probe = misconfigured.get('probe')
193
+ if (probe === undefined) throw new Error('The configured projects carry no probe control')
194
+ misconfigured.set('probe', { ...probe, setup: ['./tests/setupServer.ts'] })
195
+ expect(() => {
196
+ for (const [label, project] of expected)
197
+ expect(misconfigured.get(label)).toStrictEqual(project)
198
+ }).toThrowError(/strictly equal/u)
199
+ })
200
+
201
+ it('requires and validates every selected target wrapper', async () => {
202
+ const required: string[] = []
203
+ for (const axis of ['src', 'app']) {
204
+ for (const environment of ['core', 'browser', 'server']) {
205
+ if (!existsSync(resolve(root, axis, environment))) continue
206
+ required.push(`configs/${axis}/tsconfig.${environment}.json`)
207
+ if (axis === 'src' || environment !== 'core') {
208
+ required.push(`configs/${axis}/vite.${environment}.config.ts`)
209
+ }
210
+ }
211
+ }
212
+ if (existsSync(resolve(root, 'src/bin'))) {
213
+ required.push('configs/src/tsconfig.bin.json', 'configs/src/vite.bin.config.ts')
214
+ }
215
+ if (existsSync(resolve(root, 'configs/app/vite.showcase.config.ts'))) {
216
+ required.push('configs/app/vite.showcase.config.ts')
217
+ }
218
+ if (required[0] === undefined) {
219
+ throw new Error('The workspace selects no configuration target')
220
+ }
221
+ const found = globSync(
222
+ [
223
+ 'configs/src/vite.*.config.ts',
224
+ 'configs/app/vite.*.config.ts',
225
+ 'configs/src/tsconfig.*.json',
226
+ 'configs/app/tsconfig.*.json',
227
+ ],
228
+ { cwd: root },
229
+ ).map((path) => path.replaceAll('\\', '/'))
230
+ const extra = 'configs/app/vite.core.config.ts'
231
+ const controlled = found.concat(extra)
232
+
233
+ // Required wrappers come from selected src/app targets. Only that set is loaded and validated.
234
+ // Extra wrappers remain in the found population but are ignored before their content is read.
235
+ expect(controlled).toContain(extra)
236
+ expect(required).not.toContain(extra)
237
+ for (const wrapper of required) {
238
+ expect(controlled).toContain(wrapper)
239
+ const viteMatch =
240
+ /^configs\/(src|app)\/vite\.(core|browser|server|bin|showcase)\.config\.ts$/u.exec(wrapper)
241
+ if (viteMatch !== null) {
242
+ const [, axis, environment] = viteMatch
243
+ if (axis === undefined || environment === undefined) {
244
+ throw new Error(`${wrapper} carries no target`)
245
+ }
246
+ const loaded = await loadConfigFromFile(
247
+ { command: 'build', mode: 'test', isSsrBuild: false, isPreview: false },
248
+ resolve(root, wrapper),
249
+ root,
250
+ 'silent',
251
+ )
252
+ if (loaded === null) throw new Error(`${wrapper} did not load`)
253
+ const output = loaded.config.build?.outDir
254
+ if (output === undefined) throw new Error(`${wrapper} carries no output`)
255
+ const expected =
256
+ environment === 'bin'
257
+ ? 'dist/bin'
258
+ : environment === 'showcase'
259
+ ? 'dist/showcase'
260
+ : `dist/${axis}/${environment}`
261
+ if (resolve(root, output) !== resolve(root, expected)) {
262
+ throw new Error(`${wrapper} resolves to the wrong output`)
263
+ }
264
+ continue
265
+ }
266
+
267
+ const tsconfigMatch = /^configs\/(src|app)\/tsconfig\.(core|browser|server|bin)\.json$/u.exec(
268
+ wrapper,
269
+ )
270
+ if (tsconfigMatch === null) {
271
+ throw new Error(`${wrapper} is not a required target wrapper`)
272
+ }
273
+ const [, axis, environment] = tsconfigMatch
274
+ if (axis === undefined || environment === undefined) {
275
+ throw new Error(`${wrapper} carries no TypeScript scope`)
276
+ }
277
+ const parsed: unknown = JSON.parse(readFileSync(resolve(root, wrapper), 'utf8'))
278
+ if (typeof parsed !== 'object' || parsed === null) {
279
+ throw new Error(`${wrapper} is not a TypeScript configuration record`)
280
+ }
281
+ const compilerOptions: unknown = Object.getOwnPropertyDescriptor(
282
+ parsed,
283
+ 'compilerOptions',
284
+ )?.value
285
+ if (typeof compilerOptions !== 'object' || compilerOptions === null) {
286
+ throw new Error(`${wrapper} carries no compiler options`)
287
+ }
288
+ const lib: unknown = Object.getOwnPropertyDescriptor(compilerOptions, 'lib')?.value
289
+ const types: unknown = Object.getOwnPropertyDescriptor(compilerOptions, 'types')?.value
290
+ const expectedLib =
291
+ environment === 'core'
292
+ ? ['ESNext', 'WebWorker']
293
+ : environment === 'browser'
294
+ ? ['ESNext', 'DOM', 'DOM.Iterable']
295
+ : ['ESNext']
296
+ const expectedTypes =
297
+ environment === 'core'
298
+ ? []
299
+ : environment === 'browser'
300
+ ? axis === 'app'
301
+ ? ['vite/client', 'vue']
302
+ : ['vite/client']
303
+ : ['node']
304
+ expect(lib).toStrictEqual(expectedLib)
305
+ expect(types).toStrictEqual(expectedTypes)
306
+ }
307
+
308
+ const controlRequired = [
309
+ 'configs/app/vite.browser.config.ts',
310
+ 'configs/app/vite.server.config.ts',
311
+ ]
312
+ const controlFound = ['configs/app/vite.server.config.ts']
313
+ expect(() => {
314
+ for (const wrapper of controlRequired) expect(controlFound).toContain(wrapper)
315
+ }).toThrowError(/vite\.browser/u)
316
+ expect(() =>
317
+ expect(resolve(root, 'dist/actual')).toBe(resolve(root, 'dist/control')),
318
+ ).toThrowError(/expected/u)
319
+ })
320
+
321
+ it('registers proof scripts in the correct gate', () => {
322
+ const manifest: unknown = JSON.parse(readFileSync(resolve(root, 'package.json'), 'utf8'))
323
+ if (typeof manifest !== 'object' || manifest === null) {
324
+ throw new Error('The package manifest is not a record')
325
+ }
326
+ const scripts: unknown = Object.getOwnPropertyDescriptor(manifest, 'scripts')?.value
327
+ if (typeof scripts !== 'object' || scripts === null) {
328
+ throw new Error('The package manifest carries no scripts')
329
+ }
330
+ const test = Object.getOwnPropertyDescriptor(scripts, 'test')?.value
331
+ const config = Object.getOwnPropertyDescriptor(scripts, 'test:config')?.value
332
+ const integration = Object.getOwnPropertyDescriptor(scripts, 'test:integration')?.value
333
+ const publish = Object.getOwnPropertyDescriptor(scripts, 'prepublishOnly')?.value
334
+ const hasIntegration = existsSync(resolve(root, 'tests/integration.test.ts'))
335
+ expect(config).toBe(
336
+ 'vitest run --config vite.config.ts --no-cache --reporter=dot --project config',
337
+ )
338
+ expect(typeof test === 'string' && test.includes('npm run test:config')).toBe(true)
339
+ expect(integration).toBe(
340
+ hasIntegration
341
+ ? 'vitest run --config vite.config.ts --no-cache --reporter=dot --project integration'
342
+ : undefined,
343
+ )
344
+ expect(typeof test === 'string' && test.includes('test:integration')).toBe(false)
345
+ expect(typeof publish === 'string' && publish.includes('npm run test:integration')).toBe(
346
+ hasIntegration,
347
+ )
348
+ })
349
+ })
350
+
351
+ describe('configuration helpers', () => {
352
+ it('exposes every helper this proof requires', () => {
353
+ const required = [
354
+ 'ENVIRONMENT_MODULE_BYTES',
355
+ 'PACKAGE_MANIFEST_BYTES',
356
+ 'WORKSPACE_ROOT',
357
+ 'containedPath',
358
+ 'decodeAssetSource',
359
+ 'enforceOutputPath',
360
+ 'environmentAssetSources',
361
+ 'environmentBoundary',
362
+ 'environmentPathError',
363
+ 'environmentSourceError',
364
+ 'fileSystemPath',
365
+ 'hasAsciiUrlControl',
366
+ 'isBoundaryExemptModule',
367
+ 'isOutsideWorkspacePath',
368
+ 'isPackageBoundary',
369
+ 'isStylesheetPath',
370
+ 'isWorkspaceBoundaryModule',
371
+ 'outputBoundary',
372
+ 'packageManifestName',
373
+ 'packageNameOf',
374
+ 'packageRootForResolved',
375
+ 'packageRootOf',
376
+ 'physicalPath',
377
+ 'readBoundedFile',
378
+ 'sourceFallback',
379
+ 'trustedPackageRootFor',
380
+ 'workspacePath',
381
+ ]
382
+ const found = Object.keys(configHelpers)
383
+ for (const name of required) expect(found).toContain(name)
384
+ })
385
+
386
+ it('resolves contained workspace paths and refuses a real outside sibling', () => {
387
+ const outside = mkdtempSync(join(tmpdir(), 'orkestrel-config-outside-'))
388
+ try {
389
+ const importer = resolve(root, 'tests/config.test.ts')
390
+ expect(configHelpers.WORKSPACE_ROOT).toBe(realpathSync.native(root))
391
+ expect(configHelpers.fileSystemPath(`/@fs/${root}`)).toBe(root)
392
+ expect(configHelpers.physicalPath(`${root}?control=true`)).toBe(realpathSync.native(root))
393
+ expect(configHelpers.sourceFallback(importer, './setup.ts')).toBe(
394
+ resolve(root, 'tests/setup.ts'),
395
+ )
396
+ expect(configHelpers.sourceFallback(importer, pathToFileURL(importer).href)).toBe(importer)
397
+ expect(configHelpers.workspacePath(importer)).toBe('tests/config.test.ts')
398
+ expect(configHelpers.containedPath(root, importer)).toBe(true)
399
+ expect(configHelpers.containedPath(root, outside)).toBe(false)
400
+ expect(configHelpers.workspacePath(outside)).toBeUndefined()
401
+ expect(configHelpers.isOutsideWorkspacePath(outside)).toBe(true)
402
+ expect(configHelpers.isOutsideWorkspacePath('src/core/index.ts')).toBe(false)
403
+ } finally {
404
+ rmSync(outside, { recursive: true, force: true })
405
+ }
406
+ })
407
+
408
+ it('reads bounded files and resolves package roots from real manifests', () => {
409
+ const workspace = mkdtempSync(join(tmpdir(), 'orkestrel-config-package-'))
410
+ try {
411
+ const packageRoot = resolve(workspace, 'node_modules/@sample/package')
412
+ const source = resolve(packageRoot, 'src/index.ts')
413
+ mkdirSync(dirname(source), { recursive: true })
414
+ writeFileSync(resolve(packageRoot, 'package.json'), '{"name":"@sample/package"}', 'utf8')
415
+ writeFileSync(source, 'export {}\n', 'utf8')
416
+ const under = resolve(workspace, 'under.txt')
417
+ const at = resolve(workspace, 'at.txt')
418
+ const over = resolve(workspace, 'over.txt')
419
+ writeFileSync(under, 'abc', 'utf8')
420
+ writeFileSync(at, 'abcd', 'utf8')
421
+ writeFileSync(over, 'abcde', 'utf8')
422
+
423
+ expect(configHelpers.PACKAGE_MANIFEST_BYTES).toBe(1_048_576)
424
+ expect(configHelpers.ENVIRONMENT_MODULE_BYTES).toBe(8_388_608)
425
+ expect(configHelpers.readBoundedFile(under, 4)).toBe('abc')
426
+ expect(configHelpers.readBoundedFile(at, 4)).toBe('abcd')
427
+ expect(configHelpers.readBoundedFile(over, 4)).toBeUndefined()
428
+ expect(configHelpers.packageNameOf('@sample/package/subpath')).toBe('@sample/package')
429
+ expect(configHelpers.packageNameOf('vite/client')).toBe('vite')
430
+ expect(configHelpers.packageNameOf('node:fs')).toBeUndefined()
431
+ expect(configHelpers.packageManifestName(packageRoot)).toBe('@sample/package')
432
+ expect(configHelpers.isPackageBoundary(packageRoot)).toBe(true)
433
+ expect(configHelpers.packageRootOf('@sample/package', source)).toBe(
434
+ realpathSync.native(packageRoot),
435
+ )
436
+ expect(configHelpers.packageRootForResolved(source)).toBe(realpathSync.native(packageRoot))
437
+ expect(
438
+ configHelpers.trustedPackageRootFor(source, new Set([realpathSync.native(packageRoot)])),
439
+ ).toBe(realpathSync.native(packageRoot))
440
+ } finally {
441
+ rmSync(workspace, { recursive: true, force: true })
442
+ }
443
+ })
444
+
445
+ it('classifies module boundaries and extracts static asset sources', async () => {
446
+ const workspace = mkdtempSync(join(tmpdir(), 'orkestrel-config-assets-'))
447
+ try {
448
+ const source = resolve(workspace, 'entry.ts')
449
+ const code =
450
+ "const module = import('./module.js')\nconst asset = new URL('./asset%20name.png', import.meta.url)\nvoid module\nvoid asset\n"
451
+ writeFileSync(source, code, 'utf8')
452
+
453
+ expect(configHelpers.hasAsciiUrlControl('clean')).toBe(false)
454
+ expect(configHelpers.hasAsciiUrlControl('line\u0000break')).toBe(true)
455
+ expect(configHelpers.isBoundaryExemptModule('virtual:control')).toBe(true)
456
+ expect(configHelpers.isBoundaryExemptModule(resolve(root, 'src/core/index.ts'))).toBe(false)
457
+ expect(configHelpers.isWorkspaceBoundaryModule(resolve(root, 'src/core/index.ts'))).toBe(true)
458
+ expect(configHelpers.isWorkspaceBoundaryModule(source)).toBe(false)
459
+ expect(configHelpers.isStylesheetPath('src/styles/index.scss?direct')).toBe(true)
460
+ expect(configHelpers.isStylesheetPath('src/core/index.ts')).toBe(false)
461
+ expect(configHelpers.decodeAssetSource('./asset%20name.png')).toBe('./asset name.png')
462
+ expect(configHelpers.decodeAssetSource('%')).toBeUndefined()
463
+ await expect(
464
+ configHelpers.environmentAssetSources(readFileSync(source, 'utf8'), source),
465
+ ).resolves.toStrictEqual(['./module.js', './asset name.png'])
466
+ await expect(
467
+ configHelpers.environmentAssetSources(readFileSync(source, 'utf8'), source, true),
468
+ ).resolves.toStrictEqual([])
469
+ } finally {
470
+ rmSync(workspace, { recursive: true, force: true })
471
+ }
472
+ })
473
+
474
+ it('reports environment and output boundary errors with legal controls', () => {
475
+ expect(configHelpers.environmentPathError('src/browser', 'src/server/index.ts')).toBe(
476
+ 'Browser modules cannot depend on Node or server-only modules',
477
+ )
478
+ expect(configHelpers.environmentPathError('src/browser', 'src/core/index.ts')).toBeUndefined()
479
+ expect(configHelpers.environmentSourceError('src/browser', '@src/server')).toBe(
480
+ 'Browser modules cannot depend on Node or server-only modules',
481
+ )
482
+ expect(configHelpers.environmentSourceError('src/browser', '@src/core')).toBeUndefined()
483
+ const expected = resolve(root, 'dist/config-control')
484
+ expect(() => configHelpers.enforceOutputPath(expected, expected)).not.toThrow()
485
+ expect(() =>
486
+ configHelpers.enforceOutputPath(resolve(root, 'dist/outside-control'), expected),
487
+ ).toThrowError('Build output must use its exact configured workspace directory')
488
+ const outside = resolve(dirname(root), 'outside-config-control')
489
+ expect(() => configHelpers.enforceOutputPath(outside, outside)).toThrowError(
490
+ 'Build output must remain inside the workspace',
491
+ )
492
+ })
493
+
494
+ it('drives both plugins through their real Vite hooks', async () => {
495
+ const environments: ReadonlyArray<
496
+ 'src/core' | 'src/browser' | 'src/server' | 'app/core' | 'app/browser' | 'app/server'
497
+ > = ['src/core', 'src/browser', 'src/server', 'app/core', 'app/browser', 'app/server']
498
+ const owner = environments.find((environment) => existsSync(resolve(root, environment)))
499
+ if (owner === undefined) throw new Error('The workspace carries no environment plugin target')
500
+ const workspace = mkdtempSync(join(resolve(root, owner), 'config-build-'))
501
+ try {
502
+ const source = resolve(workspace, 'index.ts')
503
+ writeFileSync(source, 'export const control = true\n', 'utf8')
504
+ await expect(
505
+ build({
506
+ root,
507
+ configFile: false,
508
+ logLevel: 'silent',
509
+ publicDir: false,
510
+ plugins: [
511
+ configHelpers.outputBoundary('dist/config-control'),
512
+ configHelpers.environmentBoundary(owner),
513
+ ],
514
+ build: {
515
+ write: false,
516
+ outDir: 'dist/config-control',
517
+ lib: {
518
+ entry: source,
519
+ formats: ['es'],
520
+ fileName: () => 'index.js',
521
+ },
522
+ rolldownOptions: { external: [/^node:/u, /^@orkestrel\//u] },
523
+ },
524
+ }),
525
+ ).resolves.toBeDefined()
526
+
527
+ const boundary = configHelpers.environmentBoundary('src/browser')
528
+ const hook = boundary.resolveId
529
+ if (typeof hook !== 'function') {
530
+ throw new Error('The environment boundary has no resolve hook')
531
+ }
532
+ const context = {
533
+ error: expect.unreachable,
534
+ resolve: Promise.resolve.bind(Promise, { id: source }),
535
+ }
536
+ await expect(Reflect.apply(hook, context, ['@src/server', source])).rejects.toThrowError(
537
+ 'Browser modules cannot depend on Node or server-only modules',
538
+ )
539
+ await expect(Reflect.apply(hook, context, ['@src/core', source])).resolves.toBeNull()
540
+ } finally {
541
+ rmSync(workspace, { recursive: true, force: true })
542
+ }
543
+ })
544
+ })