@orkestrel/scaffold 0.0.80 → 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/agents/orkestrel.md +52 -52
- 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 +27 -27
- 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 +1263 -161
- 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 +1243 -162
- package/dist/src/core/index.js.map +1 -1
- package/package.json +3 -3
|
@@ -4,7 +4,7 @@ paths:
|
|
|
4
4
|
- 'app/**/*'
|
|
5
5
|
- 'tests/**/*'
|
|
6
6
|
- 'configs/**/*'
|
|
7
|
-
- '
|
|
7
|
+
- 'showcase/**/*'
|
|
8
8
|
- 'package.json'
|
|
9
9
|
- 'tsconfig.json'
|
|
10
10
|
- 'vite.config.ts'
|
|
@@ -16,23 +16,27 @@ Use only the environments a project needs, and keep the root dependency model in
|
|
|
16
16
|
|
|
17
17
|
## Environments
|
|
18
18
|
|
|
19
|
-
| Path | Purpose
|
|
20
|
-
| -------------- |
|
|
21
|
-
| `src/core/` | Published host-independent library
|
|
22
|
-
| `src/browser/` | Published browser-only library
|
|
23
|
-
| `src/server/` | Published Node-only library
|
|
24
|
-
| `src/styles/` | Optional
|
|
25
|
-
| `src
|
|
26
|
-
| `
|
|
27
|
-
| `
|
|
28
|
-
| `app/
|
|
29
|
-
| `
|
|
30
|
-
| `
|
|
19
|
+
| Path | Purpose |
|
|
20
|
+
| -------------- | ------------------------------------------------------------------ |
|
|
21
|
+
| `src/core/` | Published host-independent library |
|
|
22
|
+
| `src/browser/` | Published browser-only library |
|
|
23
|
+
| `src/server/` | Published Node-only library |
|
|
24
|
+
| `src/styles/` | Optional styles surface: the base sheet face producing `index.css` |
|
|
25
|
+
| `src/<name>/` | Styles extension: a named sheet face beside `src/styles/` |
|
|
26
|
+
| `src/vue/` | Browser extension: the published `vue` face over `src/browser/` |
|
|
27
|
+
| `src/bin/` | Optional executable; `main.ts` entry, never a public barrel |
|
|
28
|
+
| `app/core/` | Shared application logic with an `index.ts` barrel |
|
|
29
|
+
| `app/browser/` | Browser app; `main.ts` entry, not a barrel |
|
|
30
|
+
| `app/vue/` | Browser extension: the Vue app beside `app/browser/`; `main.ts` |
|
|
31
|
+
| `app/server/` | Node server app; `main.ts` entry |
|
|
32
|
+
| `tests/` | Mirrors src/app environments; root holds cross-cutting proofs |
|
|
33
|
+
| `configs/` | Thin target wrappers around root configs |
|
|
31
34
|
|
|
32
35
|
- Dependency direction is the root project model in `AGENTS.md` and is not restated here; this file governs where the environments live and how they are configured.
|
|
33
36
|
- Typical browser-app domains: `components/`, `pages/`, `composables/`, `controllers/`, `services/`, `stores/`.
|
|
34
37
|
- Typical server-app domains: `handlers.ts`, `middlewares.ts`, `routes.ts`.
|
|
35
|
-
- `src/styles
|
|
38
|
+
- A sheet face (`src/styles/` and each `src/<name>/` styles extension) builds from `sheet.ts`, which imports `./index.scss` alone; its `index.ts` star-exports `./sheet.js`. The themes target builds from `src/styles/themes/sheet.ts` the same way.
|
|
39
|
+
- Name a styles extension with a name the `NAME_PATTERN` constant admits, and never `core`, `browser`, `server`, `bin`, `styles`, `themes`, or `vue`.
|
|
36
40
|
- `src/bin/main.ts` is the executable entry, built to `dist/bin/main.js`. The name is fixed, as it
|
|
37
41
|
is for `app/browser/main.ts` and `app/server/main.ts`, so every runtime entry in a workspace is
|
|
38
42
|
found at the same name.
|
|
@@ -47,11 +51,14 @@ Use only the environments a project needs, and keep the root dependency model in
|
|
|
47
51
|
| `@src/browser` | `src/browser/index.ts` |
|
|
48
52
|
| `@src/server` | `src/server/index.ts` |
|
|
49
53
|
| `@src/styles` | `src/styles/index.ts` |
|
|
54
|
+
| `@src/<name>` | `src/<name>/index.ts` |
|
|
55
|
+
| `@src/vue` | `src/vue/index.ts` |
|
|
50
56
|
| `@app/core` | `app/core/index.ts` |
|
|
51
57
|
| `@app/browser` | `app/browser/index.ts` |
|
|
58
|
+
| `@app/vue` | `app/vue/index.ts` |
|
|
52
59
|
| `@app/server` | `app/server/index.ts` |
|
|
53
60
|
|
|
54
|
-
Define aliases in `tsconfig.json` first. `vite.config.ts` derives from `compilerOptions.paths`; keep both aligned.
|
|
61
|
+
Give every selected environment and every face an alias: `src/styles`, each `src/<name>` styles extension, and each axis the `vue` extension occupies. Define aliases in `tsconfig.json` first. `vite.config.ts` derives from `compilerOptions.paths`; keep both aligned.
|
|
55
62
|
|
|
56
63
|
## Configuration authority
|
|
57
64
|
|
|
@@ -65,8 +72,8 @@ Define aliases in `tsconfig.json` first. `vite.config.ts` derives from `compiler
|
|
|
65
72
|
under `configs/`. Each imports nothing from the workspace, which is what keeps it a leaf, so no
|
|
66
73
|
`configs/types.ts` exists for one to import: each keeps its own types, data, and functions in its
|
|
67
74
|
one file, and the centralized-kind placement in `.claude/rules/architecture.md` does not reach a
|
|
68
|
-
leaf. Each `configs/src/*.config.ts` imports the root config
|
|
69
|
-
|
|
75
|
+
leaf. Each `configs/src/*.config.ts` wrapper imports the root config and may import the permitted
|
|
76
|
+
leaves; keep shared build and project composition in the root config.
|
|
70
77
|
- Keep `configs/helpers.ts` free of any dependency a core-only workspace does not declare. It is
|
|
71
78
|
vendored byte-identical to every workspace, so an import there must resolve in all of them.
|
|
72
79
|
`configs/browsers.ts` exists for that reason: it imports `playwright` and
|
|
@@ -92,39 +99,55 @@ Environment rules:
|
|
|
92
99
|
|
|
93
100
|
## Build outputs
|
|
94
101
|
|
|
95
|
-
| Output
|
|
96
|
-
|
|
|
97
|
-
| `dist/src/core`
|
|
98
|
-
| `dist/src/browser`
|
|
99
|
-
| `dist/src/server`
|
|
100
|
-
| `dist/src/
|
|
101
|
-
| `dist/
|
|
102
|
-
| `dist/
|
|
103
|
-
| `dist/
|
|
104
|
-
| `dist/
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
102
|
+
| Output | Content | Format |
|
|
103
|
+
| ----------------------------- | -------------------------------- | ------------------------- |
|
|
104
|
+
| `dist/src/core` | Core library + declarations | ES and CJS |
|
|
105
|
+
| `dist/src/browser` | Browser library + declarations | ES |
|
|
106
|
+
| `dist/src/server` | Server library + declarations | ES and CJS |
|
|
107
|
+
| `dist/src/vue` | Vue face + declarations | ES |
|
|
108
|
+
| `dist/src/styles` | Compiled `index.css` | CSS |
|
|
109
|
+
| `dist/src/styles/themes` | Compiled themes `index.css` | CSS |
|
|
110
|
+
| `dist/src/<name>` | Compiled `index.css` | CSS |
|
|
111
|
+
| `dist/bin` | Optional executable `main.js` | ES with shebang |
|
|
112
|
+
| `dist/app/browser` | Browser application | target-defined |
|
|
113
|
+
| `dist/app/vue` | Vue application | target-defined |
|
|
114
|
+
| `dist/app/server` | Server application | CJS |
|
|
115
|
+
| `showcase/<application>.html` | Single-file page per application | self-contained, committed |
|
|
116
|
+
|
|
117
|
+
- Roll up each published TypeScript face's declarations in its Vite wrapper. A face that imports `@src/core` or `@src/browser` rewrites those specifiers to the published subpaths in its emitted declarations.
|
|
118
|
+
- A sheet face ships CSS, not declarations, and its JavaScript build stub stays out of `files`.
|
|
119
|
+
- Build each sheet face with `cssMinify: false`, bounded to its own output directory.
|
|
120
|
+
- Build `dist/src/styles/themes` after `dist/src/styles`; the themes build empties only its own directory.
|
|
121
|
+
- Build each selected showcase through `appShowcase(mode)`, `configs/app/vite.showcase.config.ts`, and `vite-plugin-singlefile` into root `showcase/<application>.html`; preserve sibling pages. Use `app/browser/index.html` and `browser.html` for the base modes, and `app/vue/index.html` and `vue.html` for `--mode vue`.
|
|
122
|
+
- Stamp each page after inlining with a `build-id` meta line whose value is the SHA-256 digest of the final page without that line.
|
|
123
|
+
- The showcase is outside the default build, and no test reads its pages.
|
|
110
124
|
- Use Oxc for showcase JS minification and Lightning CSS for CSS.
|
|
111
|
-
- Inject a `build-id` meta stamp so rebuilt `file://` demos cache-bust.
|
|
112
125
|
|
|
113
126
|
## Test project matrix
|
|
114
127
|
|
|
115
128
|
`vite.config.ts` defines Vitest projects on an environment axis and a workspace-proof axis. The
|
|
116
|
-
environment axis is one project per src/app axis × environment:
|
|
117
|
-
|
|
118
|
-
| Project | Files | Environment
|
|
119
|
-
| ------------- | ---------------------- |
|
|
120
|
-
| `src:core` | `tests/src/core/**` | Node
|
|
121
|
-
| `src:browser` | `tests/src/browser/**` | Playwright Chromium
|
|
122
|
-
| `src:
|
|
123
|
-
| `src:
|
|
124
|
-
| `src:
|
|
125
|
-
| `
|
|
126
|
-
| `
|
|
127
|
-
| `app:
|
|
129
|
+
environment axis is one project per src/app axis × environment, plus one per extension face:
|
|
130
|
+
|
|
131
|
+
| Project | Files | Environment | Setup |
|
|
132
|
+
| ------------- | ---------------------- | ------------------------------------- | ----------------------------------------------- |
|
|
133
|
+
| `src:core` | `tests/src/core/**` | Node | `setup.ts` |
|
|
134
|
+
| `src:browser` | `tests/src/browser/**` | Playwright Chromium | `setup.ts`, `setupBrowser.ts` |
|
|
135
|
+
| `src:vue` | `tests/src/vue/**` | Playwright Chromium | `setup.ts`, `setupBrowser.ts` |
|
|
136
|
+
| `src:server` | `tests/src/server/**` | Node | `setup.ts`, `setupServer.ts` |
|
|
137
|
+
| `src:styles` | `tests/src/styles/**` | Playwright Chromium, `isolate: false` | `setup.ts`, `setupBrowser.ts`, `setupStyles.ts` |
|
|
138
|
+
| `src:<name>` | `tests/src/<name>/**` | Playwright Chromium, `isolate: false` | `setup.ts`, `setupBrowser.ts`, `setupStyles.ts` |
|
|
139
|
+
| `src:bin` | `tests/src/bin/**` | Node | `setup.ts`, `setupServer.ts` |
|
|
140
|
+
| `app:core` | `tests/app/core/**` | Node | `setup.ts` |
|
|
141
|
+
| `app:browser` | `tests/app/browser/**` | Playwright Chromium | `setup.ts`, `setupBrowser.ts` |
|
|
142
|
+
| `app:vue` | `tests/app/vue/**` | Playwright Chromium | `setup.ts`, `setupBrowser.ts` |
|
|
143
|
+
| `app:server` | `tests/app/server/**` | Node | `setup.ts`, `setupServer.ts` |
|
|
144
|
+
|
|
145
|
+
- Compose every sheet-face project (`src:styles` and each `src:<name>`) in its own wrapper through
|
|
146
|
+
the one root `sheetProject` factory, and run it through its `test:src:<face>` script, which builds
|
|
147
|
+
that face first.
|
|
148
|
+
- Give every browser project `optimizeDeps.include` of `@orkestrel/test`, `@orkestrel/test/browser`,
|
|
149
|
+
`@orkestrel/contract` where the manifest declares it, and `vue` where the project renders Vue.
|
|
150
|
+
Give a Node project no `optimizeDeps` setting.
|
|
128
151
|
|
|
129
152
|
The workspace-proof axis is cross-cutting. Each proof covers the whole workspace rather than
|
|
130
153
|
one environment, so each is its own project:
|
|
@@ -133,9 +156,9 @@ one environment, so each is its own project:
|
|
|
133
156
|
| ------------------- | -------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------- |
|
|
134
157
|
| `policy` | `tests/policy.test.ts` | The path- and text-shaped policy laws: mirrors, suppressions, the rule map, filenames, manifest scripts, skills, and bridges | `test` |
|
|
135
158
|
| `config` | `tests/config.test.ts` | Root configuration resolves its aliases, projects, and outputs | `test` |
|
|
136
|
-
| `setup` | `tests/setup*.test.ts
|
|
137
|
-
| `setup:browser` | `tests/setupBrowser.test.ts`
|
|
138
|
-
| `journey:<variant>` | `tests/app
|
|
159
|
+
| `setup` | `tests/setup*.test.ts` other than either `setup:browser` proof | Prove root setup behavior in Node with `setup.ts`. | `test` |
|
|
160
|
+
| `setup:browser` | `tests/setupBrowser.test.ts`, `tests/setupStyles.test.ts` | Prove browser and style setup behavior in Playwright Chromium with `setup.ts` and `setupBrowser.ts`. | `test` |
|
|
161
|
+
| `journey:<variant>` | `tests/app/<application>/integration.test.ts` | Drive the application the Vite mode selects at the declared variant viewport in Playwright Chromium with `setup.ts` and `setupBrowser.ts`. | `test` through the journey scripts |
|
|
139
162
|
| `guides` | `tests/guides.test.ts` | Every documented API exists, every public API is documented, every compared summary, example, and pitch equals its source, and every executable fence returns what the guide says it returns | `test` |
|
|
140
163
|
| `conformance` | `tests/conformance.test.ts` | Where this package drifts from the official tooling it tracks | `test` |
|
|
141
164
|
| `skills` | `tests/agents/**/*.test.ts` | Each skill script under `.agents/skills/*/scripts/` does what its `SKILL.md` states, driven as a child process against a scratch fixture from its mirrored proof; `configs/agents/tsconfig.skills.json` selects the project | `test` |
|
|
@@ -143,19 +166,22 @@ one environment, so each is its own project:
|
|
|
143
166
|
| `integration` | `tests/integration.test.ts` | The package's features work together end to end across environments | `test` |
|
|
144
167
|
| `service` | `tests/service/**/*.test.ts` | The live external services this package drives, driven for real | `prepublishOnly`; `test` when private |
|
|
145
168
|
|
|
146
|
-
- Define the Node `setup` project
|
|
147
|
-
exact-case, other than `tests/setupBrowser.test.ts
|
|
148
|
-
`tests/
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
script
|
|
169
|
+
- Define the Node `setup` project when `global` selects its seeded proof or a root file matches
|
|
170
|
+
`tests/setup*.test.ts`, exact-case, other than `tests/setupBrowser.test.ts` and
|
|
171
|
+
`tests/setupStyles.test.ts`. Include those matching files and exclude both browser proofs.
|
|
172
|
+
Define `setup:browser` when a sheet face or the themes target selects its seeded proof, or either
|
|
173
|
+
exact-case browser proof exists; collect those browser proof paths alone. For each registered project, emit
|
|
174
|
+
its `test:setup` or `test:setup:browser` script and run it from `test`; otherwise emit neither its
|
|
175
|
+
project nor its script.
|
|
152
176
|
- When `tests/setupGlobal.ts` exists, give `src:browser`, `setup:browser`, and `integration` that
|
|
153
177
|
module as their `globalSetup` option, and give no other project a global setup.
|
|
154
178
|
- When a browser application selects the journey axis, register `journey:<variant>` projects
|
|
155
179
|
through the birth-owned `configs/app/vite.journey.config.ts` wrapper. Keep the adopter's variant
|
|
156
|
-
list there and compose each project through the root `appJourney
|
|
157
|
-
`
|
|
158
|
-
|
|
180
|
+
list there and compose each project through the root `appJourney(variant, variants, mode?)`
|
|
181
|
+
factory, which resolves the Vite mode to `browser` or an app-side browser extension and collects
|
|
182
|
+
`tests/app/<application>/integration.test.ts` for that application. Exclude each collected suite
|
|
183
|
+
from its application project, and run `test:journey` and one `test:journey:<framework>` per
|
|
184
|
+
app-side browser extension after the application projects in `test`.
|
|
159
185
|
|
|
160
186
|
`conformance`, `integration`, `distribution`, and `service` are separate subjects, not names for
|
|
161
187
|
one.
|
|
@@ -189,14 +215,13 @@ ignored by git; and `.claude/rules/tests.md` governs what may live there.
|
|
|
189
215
|
|
|
190
216
|
Setup assets:
|
|
191
217
|
|
|
192
|
-
-
|
|
193
|
-
-
|
|
194
|
-
- Styles setup loads `setup.css` and the compiled cascade.
|
|
218
|
+
- Load only the setup assets and compiled sheets the selected proofs require.
|
|
219
|
+
- Import `tailwindcss` from a setup asset only where an authored proof declares it.
|
|
195
220
|
|
|
196
|
-
Scope with `test:src`, `test:src:core`, `test:app`, `test:app:server`, and
|
|
197
|
-
cross-cutting project has its own script too: `test:policy`,
|
|
198
|
-
`test:setup:browser`, `test:journey`, `test:
|
|
199
|
-
`test:conformance`, `test:distribution`, `test:integration`, `test:service`.
|
|
221
|
+
Scope with `test:src`, `test:src:core`, `test:src:<face>`, `test:app`, `test:app:server`, and
|
|
222
|
+
equivalent scripts. Each cross-cutting project has its own script too: `test:policy`,
|
|
223
|
+
`test:config`, `test:setup`, `test:setup:browser`, `test:journey`, `test:journey:<framework>`,
|
|
224
|
+
`test:guides`, `test:conformance`, `test:distribution`, `test:integration`, `test:service`.
|
|
200
225
|
|
|
201
226
|
## Typechecking and environment isolation
|
|
202
227
|
|
|
@@ -214,12 +239,14 @@ then runs the configured scoped checks that prove environment isolation.
|
|
|
214
239
|
- Lint is a separate complementary gate; neither lint nor root checking replaces
|
|
215
240
|
environment-isolation checks.
|
|
216
241
|
|
|
217
|
-
| Scope | `lib` | `types`
|
|
218
|
-
| ---------------------------- | --------------------------------- |
|
|
219
|
-
| `src:core`, `app:core` | `["ESNext","WebWorker"]` | `[]`
|
|
220
|
-
| `src:browser`, `app:browser` | `["ESNext","DOM","DOM.Iterable"]` | default
|
|
221
|
-
| `src:server`, `app:server` | `["ESNext"]` | `["node"]`
|
|
222
|
-
| `src:styles`
|
|
242
|
+
| Scope | `lib` | `types` | Permitted host globals |
|
|
243
|
+
| ---------------------------- | --------------------------------- | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
|
244
|
+
| `src:core`, `app:core` | `["ESNext","WebWorker"]` | `[]` | WHATWG web interop: fetch family, streams, URL, Abort, encoders, crypto, timers, console, DOMException, structuredClone; no DOM, no Node |
|
|
245
|
+
| `src:browser`, `app:browser` | `["ESNext","DOM","DOM.Iterable"]` | default | DOM; no Node |
|
|
246
|
+
| `src:server`, `app:server` | `["ESNext"]` | `["node"]` | Node; no DOM |
|
|
247
|
+
| `src:styles`, `src:<name>` | `["ESNext"]` | `["vite/client"]` | Vite SCSS module declaration only |
|
|
248
|
+
| `src:vue` | `["ESNext","DOM","DOM.Iterable"]` | `["vite/client"]` | DOM; no Node |
|
|
249
|
+
| `app:vue` | `["ESNext","DOM","DOM.Iterable"]` | `["vite/client","vue"]` | DOM and Vue; no Node |
|
|
223
250
|
|
|
224
251
|
Strict core is load-bearing:
|
|
225
252
|
|
|
@@ -228,35 +255,39 @@ Strict core is load-bearing:
|
|
|
228
255
|
|
|
229
256
|
Build/check config alignment:
|
|
230
257
|
|
|
231
|
-
- `configs/src/tsconfig.{core,browser,server}.json` serves emit and scoped checking.
|
|
232
|
-
- `configs/src/tsconfig.styles.json`
|
|
258
|
+
- `configs/src/tsconfig.{core,browser,vue,server}.json` serves emit and scoped checking.
|
|
259
|
+
- `configs/src/tsconfig.styles.json` and each `configs/src/tsconfig.<name>.json` of a styles
|
|
260
|
+
extension are check-only.
|
|
233
261
|
- `configs/app/tsconfig.core.json` is check-only.
|
|
234
|
-
- `configs/app/tsconfig.{browser,server}.json` is check-only.
|
|
262
|
+
- `configs/app/tsconfig.{browser,vue,server}.json` is check-only.
|
|
235
263
|
- Root `tsconfig.json` keeps all libs/types for IDE and comprehensive checking; scoped configs tighten each environment.
|
|
236
264
|
|
|
237
265
|
## Script intent
|
|
238
266
|
|
|
239
|
-
| Script
|
|
240
|
-
|
|
|
241
|
-
| `dev`
|
|
242
|
-
| `build`
|
|
243
|
-
| `serve` / `serve:build`
|
|
244
|
-
| `showcase`
|
|
245
|
-
| `
|
|
246
|
-
| `
|
|
247
|
-
| `
|
|
248
|
-
| `lint
|
|
249
|
-
| `check` |
|
|
250
|
-
| `check
|
|
251
|
-
| `
|
|
252
|
-
| `format
|
|
253
|
-
| `
|
|
254
|
-
| `
|
|
255
|
-
| `
|
|
256
|
-
| `
|
|
257
|
-
| `
|
|
258
|
-
|
|
259
|
-
|
|
267
|
+
| Script | Contract |
|
|
268
|
+
| ---------------------------- | -------------------------------------------------------------------------- |
|
|
269
|
+
| `dev` | Browser development entry |
|
|
270
|
+
| `build` | Build configured library/application targets |
|
|
271
|
+
| `serve` / `serve:build` | Run built server / build then run |
|
|
272
|
+
| `showcase` | Showcase dev server of the base mode |
|
|
273
|
+
| `showcase:<framework>` | Showcase dev server of that framework's mode |
|
|
274
|
+
| `build:showcase` | Build `showcase/browser.html` |
|
|
275
|
+
| `build:showcase:<framework>` | Build `showcase/<framework>.html` |
|
|
276
|
+
| `lint` | `oxlint --config .oxlintrc.json --fix .`; separate from typecheck |
|
|
277
|
+
| `lint:check` | Non-mutating whole-tree lint gate |
|
|
278
|
+
| `check` | Comprehensive root typecheck plus configured isolation scopes |
|
|
279
|
+
| `check:<scope>` | On-demand environment-isolation pass |
|
|
280
|
+
| `format` | Format all files |
|
|
281
|
+
| `format:check` | Non-mutating whole-tree format gate |
|
|
282
|
+
| `test` | Environment projects plus non-isolated cross-cutting proofs |
|
|
283
|
+
| `clean` | Remove `dist/` |
|
|
284
|
+
| `copy <from> <to>` | Copy while creating parent directories |
|
|
285
|
+
| `prepublishOnly` | Publishing workspaces only: the gate chain, then isolated proofs |
|
|
286
|
+
| `prepack` | Publishing workspaces only: rebuild `dist/` so a pack ships current output |
|
|
287
|
+
|
|
288
|
+
- In a publishing workspace, `prepublishOnly` runs `build:showcase` and every
|
|
289
|
+
`build:showcase:<framework>` script after `npm run build`.
|
|
290
|
+
- List `showcase/` in `.prettierignore`, so formatting never rewrites a committed page.
|
|
260
291
|
|
|
261
292
|
## Tooling
|
|
262
293
|
|
|
@@ -266,7 +297,7 @@ Run `show` only **after** formatting. The committed `demo/showcase.html` is gene
|
|
|
266
297
|
- Bundler: Vite.
|
|
267
298
|
- Tests: Vitest; `@vitest/browser-playwright` for browser projects.
|
|
268
299
|
- Node build targets derive from the package's declared supported runtime. Keep `engines`, bundler targets, scoped configs, tests, and documentation aligned; never hard-code one Node version line-wide.
|
|
269
|
-
- Browser framework:
|
|
300
|
+
- Browser framework: the `vue` extension where selected.
|
|
270
301
|
|
|
271
302
|
Policy instruments:
|
|
272
303
|
|
|
@@ -278,9 +309,8 @@ Policy instruments:
|
|
|
278
309
|
thing it polices. A file-level `oxlint-disable` silently defeats every lint rule in its file,
|
|
279
310
|
plugin rules included, and nothing inside a file can suppress the sweep.
|
|
280
311
|
- Write each visitor in the plugin's visitor table as a one-line context-binding arrow delegating to
|
|
281
|
-
a named module-scope `report{Noun}` function. Never write rule logic inline in the table.
|
|
282
|
-
|
|
283
|
-
`.claude/rules/architecture.md` for exactly that table.
|
|
312
|
+
a named module-scope `report{Noun}` function. Never write rule logic inline in the table. Treat the
|
|
313
|
+
visitor table as a returned object literal whose members are callbacks.
|
|
284
314
|
- Name an individual rule id here only where the rule reads its evidence from outside the workspace
|
|
285
315
|
its instrument runs in. This section fixes the instruments and how work is assigned between them;
|
|
286
316
|
each rule's substance stays with the law it enforces.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: orkestrel-journey
|
|
3
|
-
description: Prove a browser application the way a person uses it — real keystrokes, clicks, and Tab/Enter against only what is visible and reachable — through the journey layer @orkestrel/test/browser publishes, and generate the capture portfolio, the resolved-style matrix, and the statechart outcome from those same journeys. Use when accepting a UI build, proving an application end to end, deciding whether a
|
|
3
|
+
description: Prove a browser application the way a person uses it — real keystrokes, clicks, and Tab/Enter against only what is visible and reachable — through the journey layer @orkestrel/test/browser publishes, and generate the capture portfolio, the resolved-style matrix, and the statechart outcome from those same journeys. Use when accepting a UI build, proving an application end to end, deciding whether a screen is reachable by keyboard alone, proving what a screen refuses as well as what it does, proving the styles a browser actually resolved under each theme and viewport, driving a transition table through the interface and watching it run, auditing whether the interface speaks the user's vocabulary rather than the engine's, producing the screenshots a design review judges, routing a rendered question to an artifact a model can read, or whenever the only evidence a screen works is a test that drove it through JavaScript instead of through the interface.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Load the canonical workflow
|
|
@@ -2,6 +2,7 @@ import type { Plugin, Rolldown } from 'vite'
|
|
|
2
2
|
import { parseSync, transformWithOxc, Visitor } from 'vite'
|
|
3
3
|
import { fileURLToPath } from 'node:url'
|
|
4
4
|
import { spawnSync } from 'node:child_process'
|
|
5
|
+
import { createHash } from 'node:crypto'
|
|
5
6
|
import { createRequire, isBuiltin } from 'node:module'
|
|
6
7
|
import { tmpdir } from 'node:os'
|
|
7
8
|
import {
|
|
@@ -55,6 +56,69 @@ export const WORKSPACE_ROOT = realpathSync.native(
|
|
|
55
56
|
resolvePath(dirname(fileURLToPath(import.meta.url)), '..'),
|
|
56
57
|
)
|
|
57
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Resolves a Vite mode to a declared application.
|
|
61
|
+
* @param mode - The requested mode; undefined, development, production, and test select browser.
|
|
62
|
+
* @param applications - The declared application factories.
|
|
63
|
+
* @returns The selected application name.
|
|
64
|
+
* @throws Thrown when the selected application is not declared.
|
|
65
|
+
* @example
|
|
66
|
+
* ```ts
|
|
67
|
+
* resolveApplication('test', { browser: true }) // 'browser'
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export function resolveApplication<Application extends string>(
|
|
71
|
+
mode: string | undefined,
|
|
72
|
+
applications: Readonly<Record<Application, unknown>>,
|
|
73
|
+
): Application {
|
|
74
|
+
const selected =
|
|
75
|
+
mode === undefined || mode === 'development' || mode === 'production' || mode === 'test'
|
|
76
|
+
? 'browser'
|
|
77
|
+
: mode
|
|
78
|
+
for (const name in applications) {
|
|
79
|
+
if (Object.hasOwn(applications, name) && name === selected) return name
|
|
80
|
+
}
|
|
81
|
+
throw new Error(`The application mode "${selected}" is not declared.`)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Computes the SHA-256 digest of a page with its build stamp line removed.
|
|
86
|
+
* @param text - The page text, with or without its stamp line.
|
|
87
|
+
* @returns The lowercase hexadecimal digest.
|
|
88
|
+
* @example
|
|
89
|
+
* ```ts
|
|
90
|
+
* computeStamp('') // 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
|
|
91
|
+
* computeStamp('abc') // 'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad'
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
export function computeStamp(text: string): string {
|
|
95
|
+
return createHash('sha256')
|
|
96
|
+
.update(text.replace(/^[\t ]*<meta name="build-id" content="[^"\r\n]*" \/>\r?\n/gmu, ''))
|
|
97
|
+
.digest('hex')
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Stamps a final inlined page with its content digest on a separate line.
|
|
102
|
+
* @param html - The final page with a head closing on its own line.
|
|
103
|
+
* @returns The page with exactly one build stamp line.
|
|
104
|
+
* @throws Thrown when a stamp is malformed or repeated, or the head closing line is absent.
|
|
105
|
+
* @example
|
|
106
|
+
* ```ts
|
|
107
|
+
* computeStamp(stampPage('abc\n</head>\n')) === computeStamp('abc\n</head>\n') // true
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
export function stampPage(html: string): string {
|
|
111
|
+
const stamps = html.match(/<meta\b[^>]*\bname=["']build-id["'][^>]*>/gu) ?? []
|
|
112
|
+
const lines = html.match(/^[\t ]*<meta name="build-id" content="[^"\r\n]*" \/>\r?\n/gmu) ?? []
|
|
113
|
+
if (stamps.length > 1 || stamps.length !== lines.length) {
|
|
114
|
+
throw new Error('A showcase page must carry at most one well-formed build stamp line.')
|
|
115
|
+
}
|
|
116
|
+
const text = html.replace(/^[\t ]*<meta name="build-id" content="[^"\r\n]*" \/>\r?\n/gmu, '')
|
|
117
|
+
const close = /^[\t ]*<\/head>[\t ]*(?:\r?\n|$)/mu.exec(text)
|
|
118
|
+
if (close === null) throw new Error('A showcase page must close its head on its own line.')
|
|
119
|
+
return `${text.slice(0, close.index)}\t\t<meta name="build-id" content="${computeStamp(text)}" />\n${text.slice(close.index)}`
|
|
120
|
+
}
|
|
121
|
+
|
|
58
122
|
export function fileSystemPath(pathname: string): string {
|
|
59
123
|
if (!pathname.startsWith('/@fs/')) return pathname
|
|
60
124
|
const candidate = pathname.slice('/@fs/'.length)
|
|
@@ -132,7 +196,7 @@ export function isWorkspaceBoundaryModule(id: string): boolean {
|
|
|
132
196
|
} catch {
|
|
133
197
|
return false
|
|
134
198
|
}
|
|
135
|
-
const rootRelative = /^\/(?:app|src)\/(?:core|browser|server)\//.test(candidate)
|
|
199
|
+
const rootRelative = /^\/(?:app|src)\/(?:core|browser|server|vue)\//.test(candidate)
|
|
136
200
|
const absoluteCandidate = rootRelative
|
|
137
201
|
? resolvePath(WORKSPACE_ROOT, candidate.slice(1))
|
|
138
202
|
: isAbsolute(candidate)
|
|
@@ -143,7 +207,7 @@ export function isWorkspaceBoundaryModule(id: string): boolean {
|
|
|
143
207
|
relativeId !== '..' &&
|
|
144
208
|
!relativeId.startsWith('../') &&
|
|
145
209
|
!isAbsolute(relativeId) &&
|
|
146
|
-
/^(?:app|src)\/(?:core|browser|server)\//.test(relativeId)
|
|
210
|
+
/^(?:app|src)\/(?:core|browser|server|vue)\//.test(relativeId)
|
|
147
211
|
)
|
|
148
212
|
}
|
|
149
213
|
|
|
@@ -312,9 +376,62 @@ export function isStylesheetPath(path: string): boolean {
|
|
|
312
376
|
return /\.(?:css|less|sass|scss|styl|stylus|pcss|postcss|sss)(?:[?#]|$)/.test(path)
|
|
313
377
|
}
|
|
314
378
|
|
|
379
|
+
/**
|
|
380
|
+
* Defines the manifest peers, refused packages, and published sibling entries for a build.
|
|
381
|
+
* @example Refuse framework imports until a peer is declared
|
|
382
|
+
* ```ts
|
|
383
|
+
* const options: ExternalOptions = { peers: [], refused: ['vue', '@vue/'], siblings: [] }
|
|
384
|
+
* ```
|
|
385
|
+
*/
|
|
386
|
+
export interface ExternalOptions {
|
|
387
|
+
readonly peers: readonly string[]
|
|
388
|
+
readonly refused: readonly string[]
|
|
389
|
+
readonly siblings: readonly string[]
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Resolves whether a build leaves a module external under its manifest and face boundaries.
|
|
394
|
+
* @param id - The module identifier supplied by the bundler.
|
|
395
|
+
* @param options - The peer names, refused package names or scope prefixes, and sibling entries.
|
|
396
|
+
* @returns True if the module stays external; false if the build bundles it.
|
|
397
|
+
* @throws Thrown when an import names a refused scope or a refused package without an admitting peer.
|
|
398
|
+
* @remarks
|
|
399
|
+
* A peer admits its own subpaths. A refused scope remains refused even when a package inside it
|
|
400
|
+
* is a peer. Node builtins and fleet packages retain their published identities. Workspace aliases
|
|
401
|
+
* resolve before a sibling entry can be recognized.
|
|
402
|
+
* @example Externalize a declared peer
|
|
403
|
+
* ```ts
|
|
404
|
+
* resolveExternal('vue/runtime-dom', { peers: ['vue'], refused: ['vue', '@vue/'], siblings: [] })
|
|
405
|
+
* // true
|
|
406
|
+
* ```
|
|
407
|
+
*/
|
|
408
|
+
export function resolveExternal(id: string, options: ExternalOptions): boolean {
|
|
409
|
+
const peer = options.peers.some((name) => id === name || id.startsWith(`${name}/`))
|
|
410
|
+
const refused = options.refused.find((name) =>
|
|
411
|
+
name.endsWith('/') ? id.startsWith(name) : id === name || id.startsWith(`${name}/`),
|
|
412
|
+
)
|
|
413
|
+
if (refused?.endsWith('/')) {
|
|
414
|
+
throw new Error(
|
|
415
|
+
`[orkestrel-build] The import ${id} is refused; import from ${refused.slice(1, -1)} instead of the ${refused} implementation scope.`,
|
|
416
|
+
)
|
|
417
|
+
}
|
|
418
|
+
if (refused !== undefined && !peer) {
|
|
419
|
+
throw new Error(
|
|
420
|
+
`[orkestrel-build] The import ${id} is refused; declare its public package in peerDependencies with peerDependenciesMeta marking it optional.`,
|
|
421
|
+
)
|
|
422
|
+
}
|
|
423
|
+
if (id.startsWith('@src/') || id.startsWith('@app/')) return false
|
|
424
|
+
return (
|
|
425
|
+
id.startsWith('node:') ||
|
|
426
|
+
id.startsWith('@orkestrel/') ||
|
|
427
|
+
peer ||
|
|
428
|
+
options.siblings.some((sibling) => sibling.replaceAll('\\', '/') === id.replaceAll('\\', '/'))
|
|
429
|
+
)
|
|
430
|
+
}
|
|
431
|
+
|
|
315
432
|
export function environmentPathError(owner: string, target: string): string | undefined {
|
|
316
433
|
const targetApplication = target.startsWith('app/')
|
|
317
|
-
const targetBrowser =
|
|
434
|
+
const targetBrowser = /^(?:app|src)\/(?:browser|vue)\//.test(target)
|
|
318
435
|
const targetServer = target.startsWith('app/server/') || target.startsWith('src/server/')
|
|
319
436
|
const stylesheet = isStylesheetPath(target)
|
|
320
437
|
if (owner.startsWith('src/') && targetApplication) {
|
|
@@ -323,7 +440,7 @@ export function environmentPathError(owner: string, target: string): string | un
|
|
|
323
440
|
if (owner.endsWith('/core') && (stylesheet || targetBrowser || targetServer)) {
|
|
324
441
|
return 'Core modules must remain host-independent'
|
|
325
442
|
}
|
|
326
|
-
if (owner.endsWith('/browser') && targetServer) {
|
|
443
|
+
if ((owner.endsWith('/browser') || owner.endsWith('/vue')) && targetServer) {
|
|
327
444
|
return 'Browser modules cannot depend on Node or server-only modules'
|
|
328
445
|
}
|
|
329
446
|
if (owner.endsWith('/server') && (stylesheet || targetBrowser)) {
|
|
@@ -346,7 +463,7 @@ export function environmentSourceError(owner: string, source: string): string |
|
|
|
346
463
|
!/^file:/i.test(sourcePath) &&
|
|
347
464
|
!/^[A-Za-z]:\//.test(sourcePath)
|
|
348
465
|
const browserPackage =
|
|
349
|
-
/^(?:(?:vue|vite)(?:[/?#]|$)|@(?:vue|vitejs)\/|@(?:app|src)\/browser(?:[/?#]|$)|@orkestrel\/[^/]+\/browser(?:[/?#]|$))/.test(
|
|
466
|
+
/^(?:(?:vue|vite)(?:[/?#]|$)|@(?:vue|vitejs)\/|@(?:app|src)\/(?:browser|vue)(?:[/?#]|$)|@orkestrel\/[^/]+\/(?:browser|vue)(?:[/?#]|$))/.test(
|
|
350
467
|
normalizedSource,
|
|
351
468
|
)
|
|
352
469
|
const serverPackage =
|
|
@@ -361,7 +478,7 @@ export function environmentSourceError(owner: string, source: string): string |
|
|
|
361
478
|
if (owner.endsWith('/core') && (builtin || browserPackage || serverPackage || stylesheet)) {
|
|
362
479
|
return 'Core modules must remain host-independent'
|
|
363
480
|
}
|
|
364
|
-
if (owner.endsWith('/browser') && (builtin || serverPackage)) {
|
|
481
|
+
if ((owner.endsWith('/browser') || owner.endsWith('/vue')) && (builtin || serverPackage)) {
|
|
365
482
|
return 'Browser modules cannot depend on Node or server-only modules'
|
|
366
483
|
}
|
|
367
484
|
if (owner.endsWith('/server') && (browserPackage || stylesheet)) {
|
|
@@ -623,6 +740,26 @@ export function rewriteCoreSpecifier(content: string): string {
|
|
|
623
740
|
return content.replaceAll(/(?:\.\.\/)+core\/index\.[jt]s/g, name).replaceAll('@src/core', name)
|
|
624
741
|
}
|
|
625
742
|
|
|
743
|
+
/**
|
|
744
|
+
* Rewrites browser specifiers in declarations to the workspace package's browser export.
|
|
745
|
+
* @param content - The finished declaration roll-up.
|
|
746
|
+
* @returns The roll-up with browser aliases and relative entry paths replaced.
|
|
747
|
+
* @throws Thrown when the workspace manifest names no package.
|
|
748
|
+
* @example Rewrite a browser alias
|
|
749
|
+
* ```ts
|
|
750
|
+
* rewriteBrowserSpecifier("from '@src/browser'")
|
|
751
|
+
* ```
|
|
752
|
+
*/
|
|
753
|
+
export function rewriteBrowserSpecifier(content: string): string {
|
|
754
|
+
const name = packageManifestName(WORKSPACE_ROOT)
|
|
755
|
+
if (name === undefined) {
|
|
756
|
+
throw new Error('[orkestrel-declaration-rollup] The workspace manifest names no package')
|
|
757
|
+
}
|
|
758
|
+
return content
|
|
759
|
+
.replaceAll(/(?:\.\.\/)+browser\/index\.[jt]s/g, `${name}/browser`)
|
|
760
|
+
.replaceAll('@src/browser', `${name}/browser`)
|
|
761
|
+
}
|
|
762
|
+
|
|
626
763
|
/**
|
|
627
764
|
* Rolls one published face's declarations into the single file that face ships.
|
|
628
765
|
*
|
|
@@ -843,7 +980,15 @@ export async function environmentAssetSources(
|
|
|
843
980
|
}
|
|
844
981
|
|
|
845
982
|
export function environmentBoundary(
|
|
846
|
-
owner:
|
|
983
|
+
owner:
|
|
984
|
+
| 'src/core'
|
|
985
|
+
| 'src/browser'
|
|
986
|
+
| 'src/server'
|
|
987
|
+
| 'src/vue'
|
|
988
|
+
| 'app/core'
|
|
989
|
+
| 'app/browser'
|
|
990
|
+
| 'app/server'
|
|
991
|
+
| 'app/vue',
|
|
847
992
|
): Plugin {
|
|
848
993
|
const trustedPackageRoots = new Set<string>()
|
|
849
994
|
let environmentRoot = WORKSPACE_ROOT
|
|
@@ -867,7 +1012,10 @@ export function environmentBoundary(
|
|
|
867
1012
|
if (
|
|
868
1013
|
importerPackageRoot === undefined &&
|
|
869
1014
|
((layer !== 'app' && layer !== 'src') ||
|
|
870
|
-
(environment !== 'core' &&
|
|
1015
|
+
(environment !== 'core' &&
|
|
1016
|
+
environment !== 'browser' &&
|
|
1017
|
+
environment !== 'server' &&
|
|
1018
|
+
environment !== 'vue'))
|
|
871
1019
|
) {
|
|
872
1020
|
return null
|
|
873
1021
|
}
|
|
@@ -1037,7 +1185,7 @@ export function environmentBoundary(
|
|
|
1037
1185
|
if (pathError !== undefined) this.error(pathError)
|
|
1038
1186
|
}
|
|
1039
1187
|
const environmentModule =
|
|
1040
|
-
target !== undefined && /^(?:app|src)\/(?:core|browser|server)\//.test(target)
|
|
1188
|
+
target !== undefined && /^(?:app|src)\/(?:core|browser|server|vue)\//.test(target)
|
|
1041
1189
|
if (!environmentModule && importerPackageRoot === undefined) return null
|
|
1042
1190
|
for (const source of await environmentAssetSources(code, id)) {
|
|
1043
1191
|
const normalizedSource = source.replaceAll('\\', '/')
|