@orkestrel/scaffold 0.0.81 → 0.0.82
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 +167 -13
- package/dist/bin/main.js.map +1 -1
- package/dist/host/AGENTS.md +5 -3
- package/dist/host/agents/skills/orkestrel-harden/references/centralization.md +2 -2
- package/dist/host/agents/skills/orkestrel-journey/SKILL.md +41 -38
- package/dist/host/agents/skills/orkestrel-journey/references/captures.md +3 -3
- package/dist/host/agents/skills/orkestrel-journey/references/decide.md +3 -3
- package/dist/host/agents/skills/orkestrel-journey/references/layer.md +5 -5
- package/dist/host/agents/skills/orkestrel-journey/references/statechart.md +3 -3
- package/dist/host/agents/skills/orkestrel-journey/references/styles.md +9 -9
- package/dist/host/claude/rules/application.md +20 -6
- package/dist/host/claude/rules/architecture.md +2 -2
- package/dist/host/claude/rules/browser.md +9 -0
- package/dist/host/claude/rules/documentation.md +2 -1
- package/dist/host/claude/rules/styles.md +35 -12
- package/dist/host/claude/rules/tests.md +15 -10
- package/dist/host/claude/rules/workspace.md +128 -98
- package/dist/host/claude/skills/orkestrel-journey/SKILL.md +1 -1
- package/dist/host/configs/helpers.ts +157 -9
- package/dist/host/configs/policy.ts +64 -61
- package/dist/host/dotfiles/oxlintrc.json +132 -16
- package/dist/host/dotfiles/prettierignore +1 -1
- package/dist/host/guides/README.md +9 -5
- package/dist/host/guides/scaffold.md +475 -115
- package/dist/host/manifest.json +26 -26
- package/dist/host/tests/config.test.ts +996 -39
- package/dist/host/tests/policy.test.ts +11 -0
- package/dist/host/tests/setupPolicy.ts +396 -22
- package/dist/src/core/index.cjs +1261 -159
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +297 -35
- package/dist/src/core/index.d.ts +297 -35
- package/dist/src/core/index.js +1241 -160
- package/dist/src/core/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
POLICY_SURFACE_HOST,
|
|
29
29
|
POLICY_SURFACE_CATALOG,
|
|
30
30
|
POLICY_CONTROLS,
|
|
31
|
+
SETUP_POLICY_CONTROLS,
|
|
31
32
|
POLICY_MIRROR_PATTERN,
|
|
32
33
|
POLICY_SUPPRESSION_DIRECTIVE,
|
|
33
34
|
POLICY_TERM_FILE,
|
|
@@ -57,6 +58,16 @@ import {
|
|
|
57
58
|
testToPolicyStem,
|
|
58
59
|
} from './setupPolicy.js'
|
|
59
60
|
|
|
61
|
+
describe('root setup mirror controls', () => {
|
|
62
|
+
for (const control of SETUP_POLICY_CONTROLS) {
|
|
63
|
+
it(`${control.label} [membership: ${control.membership}]`, () => {
|
|
64
|
+
expect(
|
|
65
|
+
inspectPolicyControl(control).filter((violation) => violation.rule === 'mirror'),
|
|
66
|
+
).toEqual(control.violations)
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
|
|
60
71
|
describe('surface policy controls', () => {
|
|
61
72
|
it('accepts a quoted relative barrel target containing a space', () => {
|
|
62
73
|
const scratch = createPolicySurfaceFixture()
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Blueprint } from '@orkestrel/scaffold'
|
|
1
2
|
import {
|
|
2
3
|
createGuide,
|
|
3
4
|
extractSourceLines,
|
|
@@ -5,7 +6,10 @@ import {
|
|
|
5
6
|
resolveLink,
|
|
6
7
|
} from '@orkestrel/guide'
|
|
7
8
|
import { BASE_DEV_DEPENDENCIES, HOST_PATHS } from '@orkestrel/scaffold'
|
|
9
|
+
import { spawnSync } from 'node:child_process'
|
|
10
|
+
import { createRequire } from 'node:module'
|
|
8
11
|
import {
|
|
12
|
+
realpathSync,
|
|
9
13
|
existsSync,
|
|
10
14
|
globSync,
|
|
11
15
|
mkdirSync,
|
|
@@ -18,7 +22,7 @@ import {
|
|
|
18
22
|
import { tmpdir } from 'node:os'
|
|
19
23
|
import { basename, dirname, join, matchesGlob, relative as relativePath, resolve } from 'node:path'
|
|
20
24
|
import { fileURLToPath } from 'node:url'
|
|
21
|
-
import { parseSync } from 'vite'
|
|
25
|
+
import { parseAst, parseSync } from 'vite'
|
|
22
26
|
import { stripPolicyCode, textToPolicyHits } from '../configs/policy.js'
|
|
23
27
|
|
|
24
28
|
/** Names a rule the fleet sweep decides from workspace text and paths. */
|
|
@@ -97,7 +101,7 @@ export const POLICY_SURFACE_BARREL_PATTERN =
|
|
|
97
101
|
/^\s*export\s+\*\s+from\s+(?:'(\.\.?\/[^']+\.js)'|"(\.\.?\/[^"]+\.js)")\s*;?\s*$/u
|
|
98
102
|
|
|
99
103
|
/** Names the workspace-relative styles side-effect entry the workspace rule prescribes. */
|
|
100
|
-
export const POLICY_SURFACE_STYLES_ENTRY = 'src/styles/
|
|
104
|
+
export const POLICY_SURFACE_STYLES_ENTRY = 'src/styles/sheet.ts'
|
|
101
105
|
|
|
102
106
|
/** Names the violation reported when the styles side-effect entry does not import `./index.scss` alone. */
|
|
103
107
|
export const POLICY_SURFACE_STYLES_MESSAGE =
|
|
@@ -147,6 +151,8 @@ export function createPolicySurfaceFixture(): PolicyScratchInterface {
|
|
|
147
151
|
try {
|
|
148
152
|
writePolicySurfaceHost(scratch)
|
|
149
153
|
scratch.write('package.json', '{"name":"@orkestrel/sample"}\n')
|
|
154
|
+
scratch.write('tests/setup.ts', '')
|
|
155
|
+
scratch.write('tests/setup.test.ts', "import './setupServer.js'\n")
|
|
150
156
|
return scratch
|
|
151
157
|
} catch (error) {
|
|
152
158
|
scratch.destroy()
|
|
@@ -176,11 +182,14 @@ export interface PolicyScratchInterface {
|
|
|
176
182
|
/**
|
|
177
183
|
* Creates a contained temporary directory owned by one vendored test.
|
|
178
184
|
*
|
|
179
|
-
* @param options - The temporary directory name prefix.
|
|
185
|
+
* @param options - The temporary directory name prefix and optional parent directory.
|
|
180
186
|
* @returns The owned scratch directory.
|
|
181
187
|
*/
|
|
182
|
-
export function createPolicyScratch(options: {
|
|
183
|
-
|
|
188
|
+
export function createPolicyScratch(options: {
|
|
189
|
+
readonly prefix: string
|
|
190
|
+
readonly parent?: string
|
|
191
|
+
}): PolicyScratchInterface {
|
|
192
|
+
const root = mkdtempSync(join(options.parent ?? tmpdir(), options.prefix))
|
|
184
193
|
return {
|
|
185
194
|
path: root,
|
|
186
195
|
write(target, text) {
|
|
@@ -513,7 +522,58 @@ export function inspectPolicyMirrors(root: string): readonly PolicyViolation[] {
|
|
|
513
522
|
...globSync(POLICY_MODULE_GLOB, { cwd: root }).sort().map(normalizePolicyPath),
|
|
514
523
|
...globSync(POLICY_TESTS_MODULE_GLOB, { cwd: root }).sort().map(normalizePolicyPath),
|
|
515
524
|
])
|
|
516
|
-
return inspectPolicyMirrorPaths(tests, modules)
|
|
525
|
+
return [...inspectPolicyMirrorPaths(tests, modules), ...inspectPolicySetup(root)]
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Inspects root setup modules and their proofs in both directions.
|
|
530
|
+
*
|
|
531
|
+
* @param root - The workspace root to inspect.
|
|
532
|
+
* @returns Missing modules and uncovered exporting setup modules.
|
|
533
|
+
*/
|
|
534
|
+
export function inspectPolicySetup(root: string): readonly PolicyViolation[] {
|
|
535
|
+
const paths = new Set(globSync('tests/setup*.ts', { cwd: root }).map(normalizePolicyPath))
|
|
536
|
+
const shared = paths.has('tests/setup.test.ts')
|
|
537
|
+
? readFileSync(join(root, 'tests/setup.test.ts'), 'utf8')
|
|
538
|
+
: ''
|
|
539
|
+
const imports = new Set<string>()
|
|
540
|
+
for (const declaration of parseAst(shared, { lang: 'ts' }).body) {
|
|
541
|
+
if (declaration.type !== 'ImportDeclaration') continue
|
|
542
|
+
const specifier = declaration.source.value
|
|
543
|
+
if (/^\.\/setup[^/]*\.(?:js|ts)$/u.test(specifier))
|
|
544
|
+
imports.add('tests/' + specifier.slice(2).replace(/\.js$/u, '.ts'))
|
|
545
|
+
}
|
|
546
|
+
const violations: PolicyViolation[] = []
|
|
547
|
+
for (const path of [...paths].sort()) {
|
|
548
|
+
const module = path.endsWith('.test.ts') ? path.replace(/\.test\.ts$/u, '.ts') : path
|
|
549
|
+
if (
|
|
550
|
+
HOST_PATHS.some(
|
|
551
|
+
(vendored) =>
|
|
552
|
+
module === normalizePolicyPath(vendored) ||
|
|
553
|
+
module.startsWith(normalizePolicyPath(vendored) + '/'),
|
|
554
|
+
)
|
|
555
|
+
)
|
|
556
|
+
continue
|
|
557
|
+
if (path.endsWith('.test.ts')) {
|
|
558
|
+
if (!paths.has(module))
|
|
559
|
+
violations.push(
|
|
560
|
+
createPolicyViolation('mirror', path, `setup proof requires its module: ${module}`),
|
|
561
|
+
)
|
|
562
|
+
} else if (
|
|
563
|
+
/^export /m.test(readFileSync(join(root, path), 'utf8')) &&
|
|
564
|
+
!paths.has(path.replace(/\.ts$/u, '.test.ts')) &&
|
|
565
|
+
!imports.has(path)
|
|
566
|
+
) {
|
|
567
|
+
violations.push(
|
|
568
|
+
createPolicyViolation(
|
|
569
|
+
'mirror',
|
|
570
|
+
path,
|
|
571
|
+
'exporting setup module requires its sibling proof or an import from tests/setup.test.ts',
|
|
572
|
+
),
|
|
573
|
+
)
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
return violations
|
|
517
577
|
}
|
|
518
578
|
|
|
519
579
|
/**
|
|
@@ -2345,22 +2405,26 @@ export function collectPolicyDeclarations(
|
|
|
2345
2405
|
* @param root - The workspace root to inspect.
|
|
2346
2406
|
* @returns The parsed declarations and incomplete-population violations.
|
|
2347
2407
|
* @remarks
|
|
2348
|
-
*
|
|
2349
|
-
*
|
|
2408
|
+
* Every `index.ts` follows barrel validation. Each selected sheet's `sheet.ts` entry requires
|
|
2409
|
+
* exactly one bare `./index.scss` import, including styles extensions and themes.
|
|
2350
2410
|
*/
|
|
2351
2411
|
export function readPolicySurface(root: string): PolicySurfacePopulation {
|
|
2352
2412
|
const files: Record<string, string> = {}
|
|
2353
2413
|
for (const path of globSync('src/**/*.ts', { cwd: root }).map(normalizePolicyPath).sort()) {
|
|
2354
2414
|
files[path] = readFileSync(join(root, path), 'utf8')
|
|
2355
2415
|
}
|
|
2356
|
-
const barrels = Object.keys(files).filter(
|
|
2357
|
-
(path) => path.endsWith('/index.ts') && path !== POLICY_SURFACE_STYLES_ENTRY,
|
|
2358
|
-
)
|
|
2416
|
+
const barrels = Object.keys(files).filter((path) => path.endsWith('/index.ts'))
|
|
2359
2417
|
const targets = new Set<string>()
|
|
2360
2418
|
const violations: PolicyViolation[] = []
|
|
2361
|
-
const
|
|
2362
|
-
|
|
2363
|
-
|
|
2419
|
+
const sheets = new Set([
|
|
2420
|
+
POLICY_SURFACE_STYLES_ENTRY,
|
|
2421
|
+
...collectSheets(root).map((face) => `src/${face}/sheet.ts`),
|
|
2422
|
+
'src/styles/themes/sheet.ts',
|
|
2423
|
+
])
|
|
2424
|
+
for (const path of sheets) {
|
|
2425
|
+
const stylesEntry = files[path]
|
|
2426
|
+
if (stylesEntry === undefined) continue
|
|
2427
|
+
const stylesSource = parseSync(path, stylesEntry)
|
|
2364
2428
|
const stylesStatement =
|
|
2365
2429
|
stylesSource.errors.length === 0 ? stylesSource.program.body[0] : undefined
|
|
2366
2430
|
const stylesValid =
|
|
@@ -2374,14 +2438,7 @@ export function readPolicySurface(root: string): PolicySurfacePopulation {
|
|
|
2374
2438
|
stylesStatement === undefined
|
|
2375
2439
|
? 1
|
|
2376
2440
|
: stylesEntry.slice(0, stylesStatement.start).split(/\r\n|\n/u).length
|
|
2377
|
-
violations.push(
|
|
2378
|
-
createPolicyViolation(
|
|
2379
|
-
'surface',
|
|
2380
|
-
POLICY_SURFACE_STYLES_ENTRY,
|
|
2381
|
-
POLICY_SURFACE_STYLES_MESSAGE,
|
|
2382
|
-
line,
|
|
2383
|
-
),
|
|
2384
|
-
)
|
|
2441
|
+
violations.push(createPolicyViolation('surface', path, POLICY_SURFACE_STYLES_MESSAGE, line))
|
|
2385
2442
|
}
|
|
2386
2443
|
}
|
|
2387
2444
|
for (const path of barrels) {
|
|
@@ -2676,6 +2733,176 @@ export const POLICY_CONTROLS: readonly PolicyControl[] = Object.freeze([
|
|
|
2676
2733
|
},
|
|
2677
2734
|
])
|
|
2678
2735
|
|
|
2736
|
+
/** Lists root setup mirror controls and their expected mirror readings. */
|
|
2737
|
+
export const SETUP_POLICY_CONTROLS: readonly PolicyControl[] = Object.freeze([
|
|
2738
|
+
{
|
|
2739
|
+
label: 'rejects an exporting setup module without proof',
|
|
2740
|
+
membership: 'target-owned exporting root setup modules',
|
|
2741
|
+
rule: 'mirror',
|
|
2742
|
+
files: [{ path: 'tests/setupCanvas.ts', content: 'export const CANVAS = 1\n' }],
|
|
2743
|
+
violations: [
|
|
2744
|
+
{
|
|
2745
|
+
rule: 'mirror',
|
|
2746
|
+
path: 'tests/setupCanvas.ts',
|
|
2747
|
+
message:
|
|
2748
|
+
'exporting setup module requires its sibling proof or an import from tests/setup.test.ts',
|
|
2749
|
+
},
|
|
2750
|
+
],
|
|
2751
|
+
},
|
|
2752
|
+
{
|
|
2753
|
+
label: 'admits a non-exporting setup module without proof',
|
|
2754
|
+
membership: 'root setup modules without exports',
|
|
2755
|
+
rule: 'mirror',
|
|
2756
|
+
files: [{ path: 'tests/setupCanvas.ts', content: 'const canvas = 1\nvoid canvas\n' }],
|
|
2757
|
+
violations: [],
|
|
2758
|
+
},
|
|
2759
|
+
{
|
|
2760
|
+
label: 'rejects a setup proof without its module',
|
|
2761
|
+
membership: 'target-owned root setup proofs',
|
|
2762
|
+
rule: 'mirror',
|
|
2763
|
+
files: [{ path: 'tests/setupCanvas.test.ts', content: '' }],
|
|
2764
|
+
violations: [
|
|
2765
|
+
{
|
|
2766
|
+
rule: 'mirror',
|
|
2767
|
+
path: 'tests/setupCanvas.test.ts',
|
|
2768
|
+
message: 'setup proof requires its module: tests/setupCanvas.ts',
|
|
2769
|
+
},
|
|
2770
|
+
],
|
|
2771
|
+
},
|
|
2772
|
+
{
|
|
2773
|
+
label: 'admits a module imported by the shared setup proof',
|
|
2774
|
+
membership: 'root setup modules with shared proof imports',
|
|
2775
|
+
rule: 'mirror',
|
|
2776
|
+
files: [
|
|
2777
|
+
{ path: 'tests/setupCanvas.ts', content: 'export const CANVAS = 1\n' },
|
|
2778
|
+
{ path: 'tests/setup.ts', content: '' },
|
|
2779
|
+
{ path: 'tests/setup.test.ts', content: "import { CANVAS } from './setupCanvas.js'\n" },
|
|
2780
|
+
],
|
|
2781
|
+
violations: [],
|
|
2782
|
+
},
|
|
2783
|
+
{
|
|
2784
|
+
label: 'rejects a commented import in the shared setup proof',
|
|
2785
|
+
membership: 'exporting root setup modules named only by comments',
|
|
2786
|
+
rule: 'mirror',
|
|
2787
|
+
files: [
|
|
2788
|
+
{ path: 'tests/setupCanvas.ts', content: 'export const CANVAS = 1\n' },
|
|
2789
|
+
{ path: 'tests/setup.ts', content: '' },
|
|
2790
|
+
{
|
|
2791
|
+
path: 'tests/setup.test.ts',
|
|
2792
|
+
content: "/*\nimport { CANVAS } from './setupCanvas.js'\n*/\nexport {}\n",
|
|
2793
|
+
},
|
|
2794
|
+
],
|
|
2795
|
+
violations: [
|
|
2796
|
+
{
|
|
2797
|
+
rule: 'mirror',
|
|
2798
|
+
path: 'tests/setupCanvas.ts',
|
|
2799
|
+
message:
|
|
2800
|
+
'exporting setup module requires its sibling proof or an import from tests/setup.test.ts',
|
|
2801
|
+
},
|
|
2802
|
+
],
|
|
2803
|
+
},
|
|
2804
|
+
{
|
|
2805
|
+
label: 'admits an inventory-vendored setup module without proof',
|
|
2806
|
+
membership: 'host-inventory setup modules outside the mirror population',
|
|
2807
|
+
rule: 'mirror',
|
|
2808
|
+
files: [{ path: 'tests/setupPolicy.ts', content: 'export const POLICY = 1\n' }],
|
|
2809
|
+
violations: [],
|
|
2810
|
+
},
|
|
2811
|
+
])
|
|
2812
|
+
|
|
2813
|
+
/** Lists standalone sheet selections and their core-bearing control for the vendored proof. */
|
|
2814
|
+
export const SHEET_POLICY_SELECTIONS: ReadonlyArray<
|
|
2815
|
+
Pick<Blueprint, 'src' | 'styles' | 'themes'> & {
|
|
2816
|
+
readonly conformance?: boolean
|
|
2817
|
+
readonly integration?: boolean
|
|
2818
|
+
readonly files?: readonly PolicySource[]
|
|
2819
|
+
readonly control?: {
|
|
2820
|
+
readonly before: string
|
|
2821
|
+
readonly after: string
|
|
2822
|
+
readonly failure: string
|
|
2823
|
+
}
|
|
2824
|
+
}
|
|
2825
|
+
> = Object.freeze([
|
|
2826
|
+
{ src: [], styles: true, themes: true },
|
|
2827
|
+
{ src: [], styles: false, themes: true },
|
|
2828
|
+
{ src: [], styles: false, themes: true, integration: true },
|
|
2829
|
+
{ src: ['core'], styles: true, themes: true },
|
|
2830
|
+
{
|
|
2831
|
+
src: ['core', 'browser'],
|
|
2832
|
+
styles: true,
|
|
2833
|
+
themes: true,
|
|
2834
|
+
conformance: true,
|
|
2835
|
+
integration: true,
|
|
2836
|
+
files: [
|
|
2837
|
+
{
|
|
2838
|
+
path: 'src/styles/_tokens.scss',
|
|
2839
|
+
content:
|
|
2840
|
+
'// Every published sheet opens with the same full statement so load order cannot change layer order.\n@layer reset, base, bootstrap, theme, elements, components, surfaces, composables, modifiers, utilities;\n',
|
|
2841
|
+
},
|
|
2842
|
+
{
|
|
2843
|
+
path: 'src/styles/themes/index.scss',
|
|
2844
|
+
content:
|
|
2845
|
+
"// Tokens declares only the shared layer order, so this sheet carries no styles defaults.\n@use '../tokens';\n@use 'default';\n",
|
|
2846
|
+
},
|
|
2847
|
+
{ path: 'tests/conformance.test.ts', content: 'export {}\n' },
|
|
2848
|
+
{ path: 'tests/integration.test.ts', content: 'export {}\n' },
|
|
2849
|
+
{ path: 'tests/setupServer.ts', content: 'export {}\n' },
|
|
2850
|
+
],
|
|
2851
|
+
},
|
|
2852
|
+
{
|
|
2853
|
+
src: ['core'],
|
|
2854
|
+
styles: false,
|
|
2855
|
+
themes: false,
|
|
2856
|
+
conformance: true,
|
|
2857
|
+
files: [{ path: 'tests/conformance.test.ts', content: 'export {}\n' }],
|
|
2858
|
+
control: {
|
|
2859
|
+
before: "setupFiles: ['./tests/setup.ts', './tests/setupServer.ts']",
|
|
2860
|
+
after: "setupFiles: ['./tests/setup.ts']",
|
|
2861
|
+
failure: './tests/setupServer.ts',
|
|
2862
|
+
},
|
|
2863
|
+
},
|
|
2864
|
+
{
|
|
2865
|
+
src: ['core'],
|
|
2866
|
+
styles: true,
|
|
2867
|
+
themes: false,
|
|
2868
|
+
integration: true,
|
|
2869
|
+
control: {
|
|
2870
|
+
before:
|
|
2871
|
+
"setupFiles: ['./tests/setup.ts', './tests/setupBrowser.ts', './tests/setupStyles.ts']",
|
|
2872
|
+
after: "setupFiles: ['./tests/setup.ts', './tests/setupBrowser.ts']",
|
|
2873
|
+
failure: './tests/setupStyles.ts',
|
|
2874
|
+
},
|
|
2875
|
+
},
|
|
2876
|
+
{
|
|
2877
|
+
src: ['core', 'browser'],
|
|
2878
|
+
styles: false,
|
|
2879
|
+
themes: false,
|
|
2880
|
+
integration: true,
|
|
2881
|
+
control: {
|
|
2882
|
+
before: "include: ['tests/integration.test.ts'],\n\t\t\tsetupFiles: ['./tests/setup.ts']",
|
|
2883
|
+
after:
|
|
2884
|
+
"include: ['tests/integration.test.ts'],\n\t\t\tsetupFiles: ['./tests/setup.ts', './tests/setupBrowser.ts']",
|
|
2885
|
+
failure: './tests/setupBrowser.ts',
|
|
2886
|
+
},
|
|
2887
|
+
},
|
|
2888
|
+
])
|
|
2889
|
+
|
|
2890
|
+
/** Matches the required opening themes directives without fixing quote style. */
|
|
2891
|
+
export const SHEET_POLICY_BARREL_PATTERN =
|
|
2892
|
+
/^@use\s+(['"])\.\.\/tokens\1;\s*@use\s+(['"])default\2;/u
|
|
2893
|
+
|
|
2894
|
+
/** Matches a sheet's opening layer order with at least two authored names. */
|
|
2895
|
+
export const SHEET_POLICY_ORDER_PATTERN = /^@layer\s+[-\w.]+(?:\s*,\s*[-\w.]+)+\s*;/u
|
|
2896
|
+
|
|
2897
|
+
/**
|
|
2898
|
+
* Reads a stylesheet after its leading comments and whitespace.
|
|
2899
|
+
* @param text - The authored stylesheet.
|
|
2900
|
+
* @returns The stylesheet beginning at its first directive or rule.
|
|
2901
|
+
*/
|
|
2902
|
+
export function readSheetPrelude(text: string): string {
|
|
2903
|
+
return text.replace(/^(?:\s|\/\/[^\r\n]*(?:\r\n|\n|$)|\/\*[\s\S]*?\*\/)+/u, '')
|
|
2904
|
+
}
|
|
2905
|
+
|
|
2679
2906
|
/** Lists the physical in-family controls for every skill-family assertion class. */
|
|
2680
2907
|
export const SKILL_POLICY_CONTROLS: readonly PolicyControl[] = Object.freeze([
|
|
2681
2908
|
{
|
|
@@ -3628,3 +3855,150 @@ export const PORTABILITY_POLICY_CONTROLS: readonly PolicyControl[] = Object.free
|
|
|
3628
3855
|
],
|
|
3629
3856
|
},
|
|
3630
3857
|
])
|
|
3858
|
+
|
|
3859
|
+
/**
|
|
3860
|
+
* Collects sheet faces from their source markers.
|
|
3861
|
+
* @param directory - The workspace root.
|
|
3862
|
+
* @returns Source-selected sheet names in sorted order.
|
|
3863
|
+
*/
|
|
3864
|
+
export function collectSheets(directory: string): readonly string[] {
|
|
3865
|
+
if (!existsSync(resolve(directory, 'src'))) return []
|
|
3866
|
+
return readdirSync(resolve(directory, 'src'), { withFileTypes: true })
|
|
3867
|
+
.filter(
|
|
3868
|
+
(entry) =>
|
|
3869
|
+
entry.isDirectory() &&
|
|
3870
|
+
existsSync(resolve(directory, 'src', entry.name, 'index.scss')) &&
|
|
3871
|
+
existsSync(resolve(directory, 'src', entry.name, 'sheet.ts')),
|
|
3872
|
+
)
|
|
3873
|
+
.map((entry) => entry.name)
|
|
3874
|
+
.sort()
|
|
3875
|
+
}
|
|
3876
|
+
|
|
3877
|
+
/**
|
|
3878
|
+
* Collects occupied framework faces from the source and application trees.
|
|
3879
|
+
* @param directory - The workspace root.
|
|
3880
|
+
* @returns Occupied axis and framework paths.
|
|
3881
|
+
*/
|
|
3882
|
+
export function collectFrameworks(directory: string): readonly string[] {
|
|
3883
|
+
return ['src', 'app'].flatMap((axis) =>
|
|
3884
|
+
existsSync(resolve(directory, axis))
|
|
3885
|
+
? readdirSync(resolve(directory, axis), { withFileTypes: true })
|
|
3886
|
+
.filter((entry) => entry.isDirectory() && entry.name === 'vue')
|
|
3887
|
+
.map((entry) => `${axis}/${entry.name}`)
|
|
3888
|
+
: [],
|
|
3889
|
+
)
|
|
3890
|
+
}
|
|
3891
|
+
|
|
3892
|
+
/**
|
|
3893
|
+
* Reads a configuration record without asserting its type.
|
|
3894
|
+
* @param value - The configuration value to validate.
|
|
3895
|
+
* @returns The record's own entries; throws for a non-record.
|
|
3896
|
+
*/
|
|
3897
|
+
export function readConfigRecord(value: unknown): Readonly<Record<string, unknown>> {
|
|
3898
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
3899
|
+
throw new Error('Expected a configuration record')
|
|
3900
|
+
return Object.fromEntries(Object.entries(value))
|
|
3901
|
+
}
|
|
3902
|
+
|
|
3903
|
+
/**
|
|
3904
|
+
* Reads a declared script and refuses an absent script.
|
|
3905
|
+
* @param scripts - The manifest's script record.
|
|
3906
|
+
* @param name - The required script name.
|
|
3907
|
+
* @returns The declared command; throws for a missing command.
|
|
3908
|
+
*/
|
|
3909
|
+
export function readConfigScript(scripts: Readonly<Record<string, unknown>>, name: string): string {
|
|
3910
|
+
const script = scripts[name]
|
|
3911
|
+
if (typeof script !== 'string') throw new Error(`Missing script ${name}`)
|
|
3912
|
+
return script
|
|
3913
|
+
}
|
|
3914
|
+
|
|
3915
|
+
/**
|
|
3916
|
+
* Reads source-selected wrappers and refuses a missing wrapper.
|
|
3917
|
+
* @param directory - The workspace root.
|
|
3918
|
+
* @returns Required wrapper paths; throws for a missing path.
|
|
3919
|
+
*/
|
|
3920
|
+
export function collectFaceWrappers(directory: string): readonly string[] {
|
|
3921
|
+
const wrappers = [
|
|
3922
|
+
...collectSheets(directory).map((face) => `configs/src/vite.${face}.config.ts`),
|
|
3923
|
+
...collectFrameworks(directory).map(
|
|
3924
|
+
(face) => `configs/${face.replace('/', '/vite.')}.config.ts`,
|
|
3925
|
+
),
|
|
3926
|
+
]
|
|
3927
|
+
for (const wrapper of wrappers)
|
|
3928
|
+
if (!existsSync(resolve(directory, wrapper))) throw new Error(`Missing wrapper ${wrapper}`)
|
|
3929
|
+
return wrappers
|
|
3930
|
+
}
|
|
3931
|
+
|
|
3932
|
+
/**
|
|
3933
|
+
* Inspects sheet setup membership and themes ordering from configuration data.
|
|
3934
|
+
* @param project - The resolved sheet project.
|
|
3935
|
+
* @param script - The optional chained styles and themes build command.
|
|
3936
|
+
* @returns Missing setup paths and invalid isolation or ordering fields.
|
|
3937
|
+
*/
|
|
3938
|
+
export function inspectSheetConfiguration(project: unknown, script?: string): readonly string[] {
|
|
3939
|
+
const test = readConfigRecord(readConfigRecord(project).test)
|
|
3940
|
+
const setup = test.setupFiles
|
|
3941
|
+
const failures: string[] = []
|
|
3942
|
+
for (const path of ['./tests/setup.ts', './tests/setupBrowser.ts', './tests/setupStyles.ts'])
|
|
3943
|
+
if (!Array.isArray(setup) || !setup.includes(path)) failures.push(path)
|
|
3944
|
+
if (test.isolate !== false) failures.push('isolate')
|
|
3945
|
+
if (
|
|
3946
|
+
script !== undefined &&
|
|
3947
|
+
(!script.includes('vite.styles.config.ts') ||
|
|
3948
|
+
!script.includes('vite.themes.config.ts') ||
|
|
3949
|
+
script.indexOf('vite.styles.config.ts') > script.indexOf('vite.themes.config.ts'))
|
|
3950
|
+
)
|
|
3951
|
+
failures.push('themes order')
|
|
3952
|
+
return failures
|
|
3953
|
+
}
|
|
3954
|
+
|
|
3955
|
+
/**
|
|
3956
|
+
* Reads import restrictions and collection sentinels from the installed linter.
|
|
3957
|
+
* @param directory - The fixture workspace containing the lint configuration.
|
|
3958
|
+
* @param paths - The fixtures carrying debugger collection sentinels.
|
|
3959
|
+
* @returns Diagnostic codes paired with workspace-relative paths.
|
|
3960
|
+
*/
|
|
3961
|
+
export function readImportDiagnostics(
|
|
3962
|
+
directory: string,
|
|
3963
|
+
paths: readonly string[],
|
|
3964
|
+
): ReadonlySet<string> {
|
|
3965
|
+
const manifestPath = createRequire(import.meta.url).resolve('oxlint/package.json')
|
|
3966
|
+
const manifest = readConfigRecord(JSON.parse(readFileSync(manifestPath, 'utf8')))
|
|
3967
|
+
const entry =
|
|
3968
|
+
typeof manifest.bin === 'string' ? manifest.bin : readConfigRecord(manifest.bin).oxlint
|
|
3969
|
+
if (typeof entry !== 'string') throw new Error('Missing Oxlint binary')
|
|
3970
|
+
const result = spawnSync(
|
|
3971
|
+
process.execPath,
|
|
3972
|
+
[
|
|
3973
|
+
resolve(dirname(manifestPath), entry),
|
|
3974
|
+
'--config',
|
|
3975
|
+
resolve(directory, '.oxlintrc.json'),
|
|
3976
|
+
'--no-ignore',
|
|
3977
|
+
'--format',
|
|
3978
|
+
'json',
|
|
3979
|
+
...paths,
|
|
3980
|
+
],
|
|
3981
|
+
{ cwd: directory, encoding: 'utf8', timeout: 15_000, windowsHide: true },
|
|
3982
|
+
)
|
|
3983
|
+
if (result.error !== undefined) throw result.error
|
|
3984
|
+
if (result.status !== 1)
|
|
3985
|
+
throw new Error(
|
|
3986
|
+
JSON.stringify({ status: result.status, stdout: result.stdout, stderr: result.stderr }),
|
|
3987
|
+
)
|
|
3988
|
+
const diagnostics = readConfigRecord(JSON.parse(result.stdout)).diagnostics
|
|
3989
|
+
if (!Array.isArray(diagnostics)) throw new Error('Missing lint diagnostics')
|
|
3990
|
+
const codes = new Set<string>()
|
|
3991
|
+
for (const value of diagnostics) {
|
|
3992
|
+
const diagnostic = readConfigRecord(value)
|
|
3993
|
+
if (typeof diagnostic.code !== 'string' || typeof diagnostic.filename !== 'string')
|
|
3994
|
+
throw new Error('Missing diagnostic location')
|
|
3995
|
+
codes.add(
|
|
3996
|
+
diagnostic.code +
|
|
3997
|
+
' ' +
|
|
3998
|
+
normalizePolicyFilename(realpathSync.native(directory), diagnostic.filename),
|
|
3999
|
+
)
|
|
4000
|
+
}
|
|
4001
|
+
for (const path of paths)
|
|
4002
|
+
if (!codes.has('eslint(no-debugger) ' + path)) throw new Error(`Uncollected fixture ${path}`)
|
|
4003
|
+
return codes
|
|
4004
|
+
}
|