@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
@@ -0,0 +1,762 @@
1
+ import type { Plugin } from 'vite'
2
+ import { parseSync, transformWithOxc, Visitor } from 'vite'
3
+ import { fileURLToPath } from 'node:url'
4
+ import { isBuiltin } from 'node:module'
5
+ import {
6
+ closeSync,
7
+ constants as FS_CONSTANTS,
8
+ existsSync,
9
+ fstatSync,
10
+ lstatSync,
11
+ openSync,
12
+ readSync,
13
+ realpathSync,
14
+ } from 'node:fs'
15
+ import { dirname, isAbsolute, relative, resolve as resolvePath, sep } from 'node:path'
16
+
17
+ export function hasAsciiUrlControl(value: string): boolean {
18
+ for (const character of value) {
19
+ const code = character.codePointAt(0)
20
+ if (code !== undefined && (code <= 0x1f || code === 0x7f)) return true
21
+ }
22
+ return false
23
+ }
24
+
25
+ export const PACKAGE_MANIFEST_BYTES = 1_048_576
26
+ export const ENVIRONMENT_MODULE_BYTES = 8_388_608
27
+
28
+ export const WORKSPACE_ROOT = realpathSync.native(
29
+ resolvePath(dirname(fileURLToPath(import.meta.url)), '..'),
30
+ )
31
+
32
+ export function fileSystemPath(pathname: string): string {
33
+ if (!pathname.startsWith('/@fs/')) return pathname
34
+ const candidate = pathname.slice('/@fs/'.length)
35
+ // Vite URL normalization can collapse the leading slash of a POSIX absolute path.
36
+ return candidate.startsWith('/') || /^[A-Za-z]:[\\/]/.test(candidate)
37
+ ? candidate
38
+ : `/${candidate}`
39
+ }
40
+
41
+ export function physicalPath(path: string): string {
42
+ const [pathWithoutQuery] = path.split('?')
43
+ const candidate = fileSystemPath(pathWithoutQuery ?? path)
44
+ const physicalCandidate = /^file:/i.test(candidate) ? fileURLToPath(candidate) : candidate
45
+ const absoluteCandidate =
46
+ physicalCandidate.length === 0
47
+ ? WORKSPACE_ROOT
48
+ : isAbsolute(physicalCandidate)
49
+ ? physicalCandidate
50
+ : resolvePath(WORKSPACE_ROOT, physicalCandidate)
51
+ return existsSync(absoluteCandidate) ? realpathSync.native(absoluteCandidate) : absoluteCandidate
52
+ }
53
+
54
+ export function sourceFallback(importer: string, source: string): string {
55
+ return /^file:/i.test(source) ? fileURLToPath(source) : resolvePath(dirname(importer), source)
56
+ }
57
+
58
+ export function workspacePath(path: string): string | undefined {
59
+ const relativePath = relative(WORKSPACE_ROOT, physicalPath(path)).replaceAll('\\', '/')
60
+ if (relativePath === '..' || relativePath.startsWith('../') || isAbsolute(relativePath)) {
61
+ return undefined
62
+ }
63
+ return relativePath
64
+ }
65
+
66
+ export function isBoundaryExemptModule(id: string): boolean {
67
+ const normalizedId = id.replaceAll('\\', '/')
68
+ const [path] = normalizedId.split(/[?#]/)
69
+ if (
70
+ path === undefined ||
71
+ normalizedId.startsWith('\0') ||
72
+ normalizedId.includes('virtual:') ||
73
+ normalizedId === '@vite/client' ||
74
+ normalizedId === '@vite/env' ||
75
+ normalizedId.startsWith('/@id/') ||
76
+ normalizedId.startsWith('/@vite/') ||
77
+ normalizedId.startsWith('/__vite') ||
78
+ normalizedId.startsWith('/__vitest') ||
79
+ normalizedId.startsWith('@vitest/browser') ||
80
+ normalizedId.includes('/@vitest/browser/')
81
+ ) {
82
+ return true
83
+ }
84
+ let physicalId: string | undefined
85
+ try {
86
+ physicalId = physicalPath(id).replaceAll('\\', '/')
87
+ } catch {
88
+ physicalId = undefined
89
+ }
90
+ for (const candidate of physicalId === undefined ? [path] : [path, physicalId]) {
91
+ if (candidate.split('/').some((segment) => segment.toLowerCase() === 'node_modules')) {
92
+ return true
93
+ }
94
+ }
95
+ return false
96
+ }
97
+
98
+ export function isWorkspaceBoundaryModule(id: string): boolean {
99
+ if (isBoundaryExemptModule(id)) return false
100
+ const normalizedId = id.replaceAll('\\', '/')
101
+ const [path] = normalizedId.split(/[?#]/)
102
+ if (path === undefined) return false
103
+ let candidate = fileSystemPath(path)
104
+ try {
105
+ if (/^file:/i.test(candidate)) candidate = fileURLToPath(candidate)
106
+ } catch {
107
+ return false
108
+ }
109
+ const rootRelative = /^\/(?:app|src)\/(?:core|browser|server)\//.test(candidate)
110
+ const absoluteCandidate = rootRelative
111
+ ? resolvePath(WORKSPACE_ROOT, candidate.slice(1))
112
+ : isAbsolute(candidate)
113
+ ? candidate
114
+ : resolvePath(WORKSPACE_ROOT, candidate)
115
+ const relativeId = relative(WORKSPACE_ROOT, absoluteCandidate).replaceAll('\\', '/')
116
+ return (
117
+ relativeId !== '..' &&
118
+ !relativeId.startsWith('../') &&
119
+ !isAbsolute(relativeId) &&
120
+ /^(?:app|src)\/(?:core|browser|server)\//.test(relativeId)
121
+ )
122
+ }
123
+
124
+ export function isOutsideWorkspacePath(path: string): boolean {
125
+ const [pathWithoutQuery] = path.split('?')
126
+ if (pathWithoutQuery === undefined) return false
127
+ return isAbsolute(fileSystemPath(pathWithoutQuery))
128
+ }
129
+
130
+ export function containedPath(root: string, target: string): boolean {
131
+ const relativePath = relative(root, target)
132
+ return (
133
+ relativePath === '' ||
134
+ (relativePath !== '..' && !relativePath.startsWith(`..${sep}`) && !isAbsolute(relativePath))
135
+ )
136
+ }
137
+
138
+ export function packageNameOf(source: string): string | undefined {
139
+ const [sourcePath] = source.replaceAll('\\', '/').split(/[?#]/)
140
+ if (
141
+ sourcePath === undefined ||
142
+ sourcePath.length === 0 ||
143
+ sourcePath.startsWith('.') ||
144
+ sourcePath.startsWith('/') ||
145
+ sourcePath.startsWith('#') ||
146
+ sourcePath.startsWith('file:') ||
147
+ /^[A-Za-z]:\//.test(sourcePath) ||
148
+ isBuiltin(sourcePath)
149
+ ) {
150
+ return undefined
151
+ }
152
+ const segments = sourcePath.split('/')
153
+ if (sourcePath.startsWith('@')) {
154
+ const [scope, name] = segments
155
+ return scope === undefined || name === undefined ? undefined : `${scope}/${name}`
156
+ }
157
+ return segments[0]
158
+ }
159
+
160
+ export function readBoundedFile(path: string, limit: number): string | undefined {
161
+ if (!existsSync(path)) return undefined
162
+ try {
163
+ const status = lstatSync(path)
164
+ if (!status.isFile() || status.isSymbolicLink() || status.nlink !== 1 || status.size > limit) {
165
+ return undefined
166
+ }
167
+ const handle = openSync(path, FS_CONSTANTS.O_RDONLY | FS_CONSTANTS.O_NOFOLLOW)
168
+ try {
169
+ const current = fstatSync(handle)
170
+ if (
171
+ !current.isFile() ||
172
+ current.nlink !== 1 ||
173
+ current.dev !== status.dev ||
174
+ current.ino !== status.ino ||
175
+ current.size !== status.size ||
176
+ current.mtimeMs !== status.mtimeMs ||
177
+ current.ctimeMs !== status.ctimeMs
178
+ ) {
179
+ return undefined
180
+ }
181
+ const bytes = Buffer.allocUnsafe(current.size + 1)
182
+ let offset = 0
183
+ for (;;) {
184
+ const count = readSync(handle, bytes, offset, bytes.length - offset, null)
185
+ if (count === 0) break
186
+ offset += count
187
+ if (offset > current.size) return undefined
188
+ }
189
+ const final = fstatSync(handle)
190
+ if (
191
+ !final.isFile() ||
192
+ final.nlink !== 1 ||
193
+ final.dev !== current.dev ||
194
+ final.ino !== current.ino ||
195
+ final.size !== current.size ||
196
+ final.size !== offset ||
197
+ final.mtimeMs !== current.mtimeMs ||
198
+ final.ctimeMs !== current.ctimeMs
199
+ ) {
200
+ return undefined
201
+ }
202
+ return bytes.toString('utf8', 0, offset)
203
+ } finally {
204
+ closeSync(handle)
205
+ }
206
+ } catch {
207
+ return undefined
208
+ }
209
+ }
210
+
211
+ export function packageManifestName(directory: string): string | undefined {
212
+ const content = readBoundedFile(resolvePath(directory, 'package.json'), PACKAGE_MANIFEST_BYTES)
213
+ if (content === undefined) return undefined
214
+ try {
215
+ const manifest: unknown = JSON.parse(content)
216
+ if (typeof manifest !== 'object' || manifest === null) return undefined
217
+ const manifestName = Object.getOwnPropertyDescriptor(manifest, 'name')?.value
218
+ return typeof manifestName === 'string' && packageNameOf(manifestName) === manifestName
219
+ ? manifestName
220
+ : undefined
221
+ } catch {
222
+ return undefined
223
+ }
224
+ }
225
+
226
+ export function isPackageBoundary(directory: string): boolean {
227
+ const segments = directory.replaceAll('\\', '/').split('/')
228
+ let nodeModules = -1
229
+ for (const [index, segment] of segments.entries()) {
230
+ if (segment.toLowerCase() === 'node_modules') nodeModules = index
231
+ }
232
+ if (nodeModules < 0) return false
233
+ const packageSegments = segments.slice(nodeModules + 1)
234
+ return (
235
+ (packageSegments.length === 1 && packageSegments[0]?.startsWith('@') === false) ||
236
+ (packageSegments.length === 2 &&
237
+ packageSegments[0]?.startsWith('@') === true &&
238
+ packageSegments[1]?.length !== 0)
239
+ )
240
+ }
241
+
242
+ export function packageRootOf(packageName: string, resolvedPath: string): string | undefined {
243
+ const physical = physicalPath(resolvedPath)
244
+ let current =
245
+ existsSync(physical) && lstatSync(physical).isDirectory() ? physical : dirname(physical)
246
+ for (;;) {
247
+ const boundary = isPackageBoundary(current)
248
+ const manifest = resolvePath(current, 'package.json')
249
+ if (boundary || existsSync(manifest)) {
250
+ return packageManifestName(current) === packageName ? realpathSync.native(current) : undefined
251
+ }
252
+ const parent = dirname(current)
253
+ if (parent === current) return undefined
254
+ current = parent
255
+ }
256
+ }
257
+
258
+ export function packageRootForResolved(resolvedPath: string): string | undefined {
259
+ const physical = physicalPath(resolvedPath)
260
+ let current =
261
+ existsSync(physical) && lstatSync(physical).isDirectory() ? physical : dirname(physical)
262
+ for (;;) {
263
+ const boundary = isPackageBoundary(current)
264
+ const manifest = resolvePath(current, 'package.json')
265
+ if (boundary || existsSync(manifest)) {
266
+ return packageManifestName(current) === undefined ? undefined : realpathSync.native(current)
267
+ }
268
+ const parent = dirname(current)
269
+ if (parent === current) return undefined
270
+ current = parent
271
+ }
272
+ }
273
+
274
+ export function trustedPackageRootFor(
275
+ target: string,
276
+ trustedPackageRoots: ReadonlySet<string>,
277
+ ): string | undefined {
278
+ const physical = physicalPath(target)
279
+ for (const root of trustedPackageRoots) {
280
+ if (containedPath(root, physical)) return root
281
+ }
282
+ return undefined
283
+ }
284
+
285
+ export function isStylesheetPath(path: string): boolean {
286
+ return /\.(?:css|less|sass|scss|styl|stylus|pcss|postcss|sss)(?:[?#]|$)/.test(path)
287
+ }
288
+
289
+ export function environmentPathError(owner: string, target: string): string | undefined {
290
+ const targetApplication = target.startsWith('app/')
291
+ const targetBrowser = target.startsWith('app/browser/') || target.startsWith('src/browser/')
292
+ const targetServer = target.startsWith('app/server/') || target.startsWith('src/server/')
293
+ const stylesheet = isStylesheetPath(target)
294
+ if (owner.startsWith('src/') && targetApplication) {
295
+ return 'Published modules cannot depend on private application modules'
296
+ }
297
+ if (owner.endsWith('/core') && (stylesheet || targetBrowser || targetServer)) {
298
+ return 'Core modules must remain host-independent'
299
+ }
300
+ if (owner.endsWith('/browser') && targetServer) {
301
+ return 'Browser modules cannot depend on Node or server-only modules'
302
+ }
303
+ if (owner.endsWith('/server') && (stylesheet || targetBrowser)) {
304
+ return 'Server modules cannot depend on Vue or browser-only modules'
305
+ }
306
+ return undefined
307
+ }
308
+
309
+ export function environmentSourceError(owner: string, source: string): string | undefined {
310
+ const normalizedSource = source.replaceAll('\\', '/')
311
+ if (hasAsciiUrlControl(normalizedSource)) {
312
+ return 'Environment module URLs cannot contain ASCII controls'
313
+ }
314
+ const [sourcePath] = normalizedSource.split(/[?#]/)
315
+ const builtin = sourcePath !== undefined && isBuiltin(sourcePath)
316
+ const unsupportedScheme =
317
+ sourcePath !== undefined &&
318
+ /^[A-Za-z][A-Za-z0-9+.-]*:/.test(sourcePath) &&
319
+ !builtin &&
320
+ !/^file:/i.test(sourcePath) &&
321
+ !/^[A-Za-z]:\//.test(sourcePath)
322
+ const browserPackage =
323
+ /^(?:(?:vue|vite)(?:[/?#]|$)|@(?:vue|vitejs)\/|@(?:app|src)\/browser(?:[/?#]|$)|@orkestrel\/[^/]+\/browser(?:[/?#]|$))/.test(
324
+ normalizedSource,
325
+ )
326
+ const serverPackage =
327
+ /^(?:@(?:app|src)\/server(?:[/?#]|$)|@orkestrel\/[^/]+\/server(?:[/?#]|$))/.test(
328
+ normalizedSource,
329
+ )
330
+ const stylesheet = isStylesheetPath(normalizedSource)
331
+ if (unsupportedScheme) return 'Environment modules cannot import non-Node URL schemes'
332
+ if (owner.startsWith('src/') && /^@app(?:[/?#]|$)/.test(normalizedSource)) {
333
+ return 'Published modules cannot depend on private application modules'
334
+ }
335
+ if (owner.endsWith('/core') && (builtin || browserPackage || serverPackage || stylesheet)) {
336
+ return 'Core modules must remain host-independent'
337
+ }
338
+ if (owner.endsWith('/browser') && (builtin || serverPackage)) {
339
+ return 'Browser modules cannot depend on Node or server-only modules'
340
+ }
341
+ if (owner.endsWith('/server') && (browserPackage || stylesheet)) {
342
+ return 'Server modules cannot depend on Vue or browser-only modules'
343
+ }
344
+ return undefined
345
+ }
346
+
347
+ export function enforceOutputPath(configured: string, expected: string): void {
348
+ if (relative(expected, configured) !== '') {
349
+ throw new Error(
350
+ '[orkestrel-output-boundary] Build output must use its exact configured workspace directory',
351
+ )
352
+ }
353
+ const workspaceRelative = relative(WORKSPACE_ROOT, expected)
354
+ if (
355
+ workspaceRelative === '..' ||
356
+ workspaceRelative.startsWith(`..${sep}`) ||
357
+ isAbsolute(workspaceRelative)
358
+ ) {
359
+ throw new Error('[orkestrel-output-boundary] Build output must remain inside the workspace')
360
+ }
361
+ let current = WORKSPACE_ROOT
362
+ for (const segment of workspaceRelative.split(sep)) {
363
+ if (segment.length === 0) continue
364
+ current = resolvePath(current, segment)
365
+ if (!existsSync(current)) continue
366
+ const status = lstatSync(current)
367
+ if (status.isSymbolicLink() || !status.isDirectory()) {
368
+ throw new Error(
369
+ '[orkestrel-output-boundary] Build output and its existing parents must be real directories',
370
+ )
371
+ }
372
+ if (workspacePath(realpathSync.native(current)) === undefined) {
373
+ throw new Error('[orkestrel-output-boundary] Build output must remain inside the workspace')
374
+ }
375
+ }
376
+ }
377
+
378
+ export function outputBoundary(output: string): Plugin {
379
+ const expected = resolvePath(WORKSPACE_ROOT, output)
380
+ let configured = expected
381
+ let build = false
382
+ return {
383
+ name: 'orkestrel-output-boundary',
384
+ enforce: 'pre',
385
+ configResolved(config) {
386
+ if (config.publicDir !== '') {
387
+ throw new Error(
388
+ '[orkestrel-output-boundary] Public directories are disabled; every output must come from the audited graph',
389
+ )
390
+ }
391
+ if (
392
+ output.endsWith('/browser') &&
393
+ config.build.lib === false &&
394
+ config.build.assetsInlineLimit !== 0
395
+ ) {
396
+ throw new Error(
397
+ '[orkestrel-output-boundary] Browser assets must remain external for output auditing',
398
+ )
399
+ }
400
+ const outputOptions = config.build.rolldownOptions.output
401
+ const outputs = Array.isArray(outputOptions) ? outputOptions : [outputOptions]
402
+ for (const options of outputs) {
403
+ if (options?.dir !== undefined || options?.file !== undefined) {
404
+ throw new Error(
405
+ '[orkestrel-output-boundary] Rolldown output directories and files cannot override the configured output',
406
+ )
407
+ }
408
+ }
409
+ build = config.command === 'build'
410
+ configured = resolvePath(config.root, config.build.outDir)
411
+ },
412
+ buildStart() {
413
+ if (build) enforceOutputPath(configured, expected)
414
+ },
415
+ }
416
+ }
417
+
418
+ export function decodeAssetSource(source: string): string | undefined {
419
+ try {
420
+ return decodeURI(source)
421
+ } catch {
422
+ return undefined
423
+ }
424
+ }
425
+
426
+ export async function environmentAssetSources(
427
+ code: string,
428
+ id: string,
429
+ emitted = false,
430
+ ): Promise<readonly string[]> {
431
+ const [path] = id.split('?')
432
+ if (
433
+ path === undefined ||
434
+ (!/\.[cm]?[jt]sx?$/.test(path) && !/[?&]html-proxy(?:[=&]|$)/.test(id))
435
+ ) {
436
+ return []
437
+ }
438
+ const sources: string[] = []
439
+ const transformed = await transformWithOxc(code, path)
440
+ const visitor = new Visitor({
441
+ ImportExpression(node) {
442
+ if (emitted) return
443
+ let value: string | undefined
444
+ if (node.source.type === 'Literal' && typeof node.source.value === 'string') {
445
+ value = node.source.value
446
+ } else if (node.source.type === 'TemplateLiteral' && node.source.expressions.length === 0) {
447
+ value = node.source.quasis.map((quasi) => quasi.value.cooked ?? quasi.value.raw).join('')
448
+ } else {
449
+ throw new Error(
450
+ '[orkestrel-environment-boundary] Dynamic imports must use static string values',
451
+ )
452
+ }
453
+ const decoded = decodeAssetSource(value)
454
+ if (decoded === undefined) {
455
+ throw new Error('[orkestrel-environment-boundary] Module URLs must use valid URI encoding')
456
+ }
457
+ sources.push(decoded)
458
+ },
459
+ NewExpression(node) {
460
+ if (emitted) return
461
+ const [source, base] = node.arguments
462
+ if (
463
+ node.callee.type !== 'Identifier' ||
464
+ node.callee.name !== 'URL' ||
465
+ base?.type !== 'MemberExpression' ||
466
+ base.object.type !== 'MetaProperty' ||
467
+ base.object.meta.name !== 'import' ||
468
+ base.object.property.name !== 'meta' ||
469
+ base.property.type !== 'Identifier' ||
470
+ base.property.name !== 'url'
471
+ ) {
472
+ return
473
+ }
474
+ let value: string | undefined
475
+ if (source?.type === 'Literal' && typeof source.value === 'string') {
476
+ const decoded = decodeAssetSource(source.value)
477
+ if (decoded === undefined) {
478
+ throw new Error('[orkestrel-environment-boundary] Asset URLs must use valid URI encoding')
479
+ }
480
+ value = decoded
481
+ } else if (source?.type === 'TemplateLiteral') {
482
+ const decodedQuasis: string[] = []
483
+ for (const quasi of source.quasis) {
484
+ const decoded = decodeAssetSource(quasi.value.cooked ?? quasi.value.raw)
485
+ if (decoded === undefined) {
486
+ throw new Error(
487
+ '[orkestrel-environment-boundary] Asset URLs must use valid URI encoding',
488
+ )
489
+ }
490
+ decodedQuasis.push(decoded)
491
+ }
492
+ if (source.expressions.length > 0) {
493
+ throw new Error(
494
+ '[orkestrel-environment-boundary] Asset URLs must use static string values',
495
+ )
496
+ }
497
+ value = decodedQuasis.join('__orkestrel__')
498
+ } else {
499
+ throw new Error('[orkestrel-environment-boundary] Asset URLs must use static string values')
500
+ }
501
+ if (value === undefined) return
502
+ if (
503
+ value.startsWith('.') ||
504
+ value.startsWith('/') ||
505
+ /^file:/i.test(value) ||
506
+ /^[A-Za-z]:[\\/]/.test(value)
507
+ ) {
508
+ sources.push(value)
509
+ }
510
+ },
511
+ })
512
+ visitor.visit(parseSync(path, transformed.code).program)
513
+ return sources
514
+ }
515
+
516
+ export function environmentBoundary(
517
+ owner: 'src/core' | 'src/browser' | 'src/server' | 'app/core' | 'app/browser' | 'app/server',
518
+ ): Plugin {
519
+ const trustedPackageRoots = new Set<string>()
520
+ let environmentRoot = WORKSPACE_ROOT
521
+ return {
522
+ name: 'orkestrel-environment-boundary',
523
+ enforce: 'pre',
524
+ configResolved(config) {
525
+ environmentRoot = physicalPath(config.root)
526
+ },
527
+ async resolveId(source, importer) {
528
+ if (importer === undefined || !isWorkspaceBoundaryModule(importer)) return null
529
+ if (isBoundaryExemptModule(source)) return null
530
+ const normalizedSource = source.replaceAll('\\', '/')
531
+ const sourceError = environmentSourceError(owner, normalizedSource)
532
+ if (sourceError !== undefined) this.error(sourceError)
533
+ const importerPath = workspacePath(importer)
534
+ const physicalImporter = physicalPath(importer)
535
+ const importerPackageRoot = trustedPackageRootFor(physicalImporter, trustedPackageRoots)
536
+ if (importerPath === undefined && importerPackageRoot === undefined) return null
537
+ const [layer, environment] = importerPath?.split('/') ?? []
538
+ if (
539
+ importerPackageRoot === undefined &&
540
+ ((layer !== 'app' && layer !== 'src') ||
541
+ (environment !== 'core' && environment !== 'browser' && environment !== 'server'))
542
+ ) {
543
+ return null
544
+ }
545
+ const pathLike =
546
+ normalizedSource.startsWith('.') ||
547
+ normalizedSource.startsWith('/') ||
548
+ /^file:/i.test(normalizedSource) ||
549
+ /^[A-Za-z]:[\\/]/.test(normalizedSource)
550
+ const fallbackSource =
551
+ normalizedSource.startsWith('.') ||
552
+ normalizedSource.startsWith('/') ||
553
+ /^file:/i.test(normalizedSource)
554
+ ? sourceFallback(physicalImporter, normalizedSource)
555
+ : ''
556
+ const resolution = await this.resolve(source, importer, { skipSelf: true })
557
+ const [resolvedId] = resolution?.id.split('?') ?? []
558
+ const physicalResolution = resolvedId === undefined ? undefined : physicalPath(resolvedId)
559
+ if (
560
+ importerPackageRoot !== undefined &&
561
+ (pathLike || normalizedSource.startsWith('#')) &&
562
+ physicalResolution !== undefined &&
563
+ !containedPath(importerPackageRoot, physicalResolution)
564
+ ) {
565
+ if (normalizedSource.startsWith('#')) {
566
+ const mappedPackageRoot =
567
+ workspacePath(physicalResolution) === undefined
568
+ ? packageRootForResolved(physicalResolution)
569
+ : undefined
570
+ if (mappedPackageRoot === undefined) {
571
+ return this.error(
572
+ 'Dependency package imports must resolve inside an exact physical package root',
573
+ )
574
+ }
575
+ trustedPackageRoots.add(mappedPackageRoot)
576
+ } else {
577
+ this.error('Dependency modules cannot import files outside their physical package root')
578
+ }
579
+ }
580
+ if (
581
+ !pathLike &&
582
+ !normalizedSource.startsWith('#') &&
583
+ physicalResolution !== undefined &&
584
+ workspacePath(physicalResolution) === undefined
585
+ ) {
586
+ const packageName = packageNameOf(normalizedSource)
587
+ const packageRoot =
588
+ packageName === undefined ? undefined : packageRootOf(packageName, physicalResolution)
589
+ if (packageRoot === undefined || !containedPath(packageRoot, physicalResolution)) {
590
+ return this.error('Resolved dependencies must remain inside their physical package root')
591
+ }
592
+ trustedPackageRoots.add(packageRoot)
593
+ }
594
+ const resolvedSource = workspacePath(resolution?.id ?? fallbackSource)
595
+ if (pathLike && resolvedSource === undefined && importerPackageRoot === undefined) {
596
+ this.error('Environment modules cannot import files outside the workspace')
597
+ }
598
+ const pathError =
599
+ resolvedSource === undefined
600
+ ? undefined
601
+ : environmentPathError(`${layer}/${environment}`, resolvedSource)
602
+ if (pathError !== undefined) this.error(pathError)
603
+ return null
604
+ },
605
+ async load(id) {
606
+ if (!isWorkspaceBoundaryModule(id)) return null
607
+ const physicalImporter = physicalPath(id)
608
+ const trustedPackageRoot = trustedPackageRootFor(physicalImporter, trustedPackageRoots)
609
+ const inferredPackageRoot =
610
+ trustedPackageRoot === undefined ? packageRootForResolved(physicalImporter) : undefined
611
+ const packageRoot =
612
+ trustedPackageRoot ??
613
+ (inferredPackageRoot !== undefined && isPackageBoundary(inferredPackageRoot)
614
+ ? inferredPackageRoot
615
+ : undefined)
616
+ if (packageRoot === undefined || !/\.[cm]?[jt]sx?$/.test(physicalImporter)) {
617
+ return null
618
+ }
619
+ const code = readBoundedFile(physicalImporter, ENVIRONMENT_MODULE_BYTES)
620
+ if (code === undefined) {
621
+ return this.error('Dependency module source must be a bounded regular file')
622
+ }
623
+ for (const source of await environmentAssetSources(code, id)) {
624
+ const normalizedSource = source.replaceAll('\\', '/')
625
+ const sourceError = environmentSourceError(owner, normalizedSource)
626
+ if (sourceError !== undefined) this.error(sourceError)
627
+ const sourcePathError = environmentPathError(owner, normalizedSource)
628
+ if (sourcePathError !== undefined) this.error(sourcePathError)
629
+ const pathLike =
630
+ normalizedSource.startsWith('.') ||
631
+ normalizedSource.startsWith('/') ||
632
+ /^file:/i.test(normalizedSource) ||
633
+ /^[A-Za-z]:[\\/]/.test(normalizedSource)
634
+ if (
635
+ pathLike &&
636
+ !containedPath(
637
+ packageRoot,
638
+ physicalPath(sourceFallback(physicalImporter, normalizedSource)),
639
+ )
640
+ ) {
641
+ this.error('Dependency modules cannot import files outside their physical package root')
642
+ }
643
+ }
644
+ return null
645
+ },
646
+ async generateBundle(_options, bundle) {
647
+ for (const output of Object.values(bundle)) {
648
+ if (output.type === 'chunk') {
649
+ for (const source of await environmentAssetSources(
650
+ output.code,
651
+ output.fileName.endsWith('.js') ? output.fileName : `${output.fileName}.js`,
652
+ true,
653
+ )) {
654
+ const normalizedSource = source.replaceAll('\\', '/')
655
+ const sourceError = environmentSourceError(owner, normalizedSource)
656
+ if (sourceError !== undefined) this.error(sourceError)
657
+ }
658
+ continue
659
+ }
660
+ for (const original of output.originalFileNames) {
661
+ const physical = physicalPath(
662
+ isAbsolute(original) ? original : resolvePath(environmentRoot, original),
663
+ )
664
+ if (isBoundaryExemptModule(original) || isBoundaryExemptModule(physical)) continue
665
+ const target = workspacePath(physical)
666
+ if (target === undefined) {
667
+ if (trustedPackageRootFor(physical, trustedPackageRoots) === undefined) {
668
+ this.error('Environment modules cannot import files outside the workspace')
669
+ }
670
+ continue
671
+ }
672
+ const pathError = environmentPathError(owner, target)
673
+ if (pathError !== undefined) this.error(pathError)
674
+ }
675
+ }
676
+ },
677
+ buildEnd(error) {
678
+ if (error !== undefined) return
679
+ for (const id of this.getModuleIds()) {
680
+ if (!isWorkspaceBoundaryModule(id)) continue
681
+ const target = workspacePath(id)
682
+ if (target === undefined) {
683
+ if (
684
+ isOutsideWorkspacePath(id) &&
685
+ trustedPackageRootFor(physicalPath(id), trustedPackageRoots) === undefined
686
+ ) {
687
+ this.error('Environment modules cannot import files outside the workspace')
688
+ }
689
+ continue
690
+ }
691
+ const pathError = environmentPathError(owner, target)
692
+ if (pathError !== undefined) this.error(pathError)
693
+ }
694
+ },
695
+ transform: {
696
+ order: 'pre',
697
+ async handler(code, id) {
698
+ if (!isWorkspaceBoundaryModule(id)) return null
699
+ const target = workspacePath(id)
700
+ const physicalImporter = physicalPath(id)
701
+ const importerPackageRoot = trustedPackageRootFor(physicalImporter, trustedPackageRoots)
702
+ if (target === undefined) {
703
+ if (isOutsideWorkspacePath(id) && importerPackageRoot === undefined) {
704
+ this.error('Environment modules cannot import files outside the workspace')
705
+ }
706
+ } else {
707
+ const pathError = environmentPathError(owner, target)
708
+ if (pathError !== undefined) this.error(pathError)
709
+ }
710
+ const environmentModule =
711
+ target !== undefined && /^(?:app|src)\/(?:core|browser|server)\//.test(target)
712
+ if (!environmentModule && importerPackageRoot === undefined) return null
713
+ for (const source of await environmentAssetSources(code, id)) {
714
+ const normalizedSource = source.replaceAll('\\', '/')
715
+ const sourceError = environmentSourceError(owner, normalizedSource)
716
+ if (sourceError !== undefined) this.error(sourceError)
717
+ const [sourcePath] = normalizedSource.split(/[?#]/)
718
+ if (sourcePath !== undefined && isBuiltin(sourcePath)) continue
719
+ const resolution = await this.resolve(normalizedSource, id, { skipSelf: true })
720
+ const fallbackSource = sourceFallback(physicalImporter, normalizedSource)
721
+ const physicalSource = physicalPath(resolution?.id ?? fallbackSource)
722
+ if (importerPackageRoot !== undefined) {
723
+ const pathLike =
724
+ normalizedSource.startsWith('.') ||
725
+ normalizedSource.startsWith('/') ||
726
+ /^file:/i.test(normalizedSource) ||
727
+ /^[A-Za-z]:[\\/]/.test(normalizedSource)
728
+ if (pathLike && !containedPath(importerPackageRoot, physicalSource)) {
729
+ this.error(
730
+ 'Dependency modules cannot import files outside their physical package root',
731
+ )
732
+ }
733
+ if (!pathLike && !containedPath(importerPackageRoot, physicalSource)) {
734
+ const packageName = packageNameOf(normalizedSource)
735
+ const packageRoot = normalizedSource.startsWith('#')
736
+ ? workspacePath(physicalSource) === undefined
737
+ ? packageRootForResolved(physicalSource)
738
+ : undefined
739
+ : packageName === undefined
740
+ ? undefined
741
+ : packageRootOf(packageName, physicalSource)
742
+ if (packageRoot === undefined || !containedPath(packageRoot, physicalSource)) {
743
+ return this.error(
744
+ 'Resolved dependencies must remain inside their physical package root',
745
+ )
746
+ }
747
+ trustedPackageRoots.add(packageRoot)
748
+ }
749
+ continue
750
+ }
751
+ const resolvedSource = workspacePath(physicalSource)
752
+ if (resolvedSource === undefined) {
753
+ return this.error('Environment modules cannot import files outside the workspace')
754
+ }
755
+ const assetError = environmentPathError(owner, resolvedSource)
756
+ if (assetError !== undefined) this.error(assetError)
757
+ }
758
+ return null
759
+ },
760
+ },
761
+ }
762
+ }