@orkestrel/scaffold 0.0.28 → 0.0.29
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/host/AGENTS.md +12 -0
- package/dist/host/agents/orchestration.md +25 -11
- package/dist/host/claude/rules/workspace.md +7 -3
- package/dist/host/guides/scaffold.md +12 -0
- package/dist/src/core/index.cjs +381 -18
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +35 -4
- package/dist/src/core/index.d.ts +35 -4
- package/dist/src/core/index.js +381 -19
- package/dist/src/core/index.js.map +1 -1
- package/package.json +1 -1
package/dist/host/AGENTS.md
CHANGED
|
@@ -157,3 +157,15 @@ This governs prose everywhere: chat replies, instruction files, guides, TSDoc, c
|
|
|
157
157
|
- Keep all substance, nuance, and precision. Cut only what makes text hard to read.
|
|
158
158
|
- Present a tradeoff as option, cost, and recommendation — not as a balanced meditation.
|
|
159
159
|
- Write requirements so they are specific and testable. Replace evaluative words such as "user friendly" or "hardened further" with the concrete condition that closes them.
|
|
160
|
+
|
|
161
|
+
#### Instruction files
|
|
162
|
+
|
|
163
|
+
`AGENTS.md`, `.claude/rules/*`, `.agents/*`, `.claude/agents/*`, and every skill are executed, not
|
|
164
|
+
read. An agent loads them mid-task and acts on them. Write them for that reader.
|
|
165
|
+
|
|
166
|
+
- Write every line as a directive: what to do, what to check, or what to refuse. Delete a line that does none of the three.
|
|
167
|
+
- Name the observable trigger and the required action. "When X, do Y" is actionable; "X matters" is not.
|
|
168
|
+
- State the finding as the rule. Never record how it was found, which session found it, what was tried first, or what a probe proved. That history belongs in the commit message.
|
|
169
|
+
- Cut any clause written to persuade, reassure, or explain the rule to a person. An agent needs the rule and its trigger, not agreement with it.
|
|
170
|
+
- Give a rule one home. Restating it elsewhere creates two copies that drift, and an agent reading the stale one is following this file.
|
|
171
|
+
- Keep an example only when it disambiguates the rule. Delete an example that merely illustrates it.
|
|
@@ -91,6 +91,12 @@ Record the substitution.
|
|
|
91
91
|
- Never assign Grok to either lane in Claude Code or Codex. If the remaining native engine is also
|
|
92
92
|
unavailable there, the pass cannot run: stop and report rather than substituting Grok.
|
|
93
93
|
- Grok takes both lanes only in Cursor, and only when Opus 5 and Sol are both unavailable.
|
|
94
|
+
- Treat a lane that returns no verdicts as a lane that did not run. A bench lane reporting that its
|
|
95
|
+
driver executed and its engine was never reached is a dark bench, not a result. Record the bench
|
|
96
|
+
dark from that report, re-run the lane on the substitute engine from the table above, and name in
|
|
97
|
+
the routing ledger which lane ran on which engine. Never accept a round with one lane empty.
|
|
98
|
+
- Re-read bench liveness at dispatch, not at session start. A bench that probed live can be dark when
|
|
99
|
+
the lane launches.
|
|
94
100
|
|
|
95
101
|
## Tedious work goes to Grok
|
|
96
102
|
|
|
@@ -617,17 +623,25 @@ flag is what stops the gate chain running a second time inside the five minutes.
|
|
|
617
623
|
|
|
618
624
|
### Reaching the approval
|
|
619
625
|
|
|
620
|
-
-
|
|
621
|
-
|
|
622
|
-
- `npm login`
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
626
|
+
- Run `npm login` before any publish. `npm publish` does not open the browser flow: unauthenticated
|
|
627
|
+
it returns `E404` on `PUT`, which reads as a missing package rather than a missing credential.
|
|
628
|
+
- Pass `--browser=false` to `npm login` and to every `npm publish`. Without it npm prints
|
|
629
|
+
`Press ENTER to open in the browser...` and blocks. Never answer that prompt with a newline: the
|
|
630
|
+
web flow consumes the newline on a later read, drops to a legacy `Username:` prompt, and exits
|
|
631
|
+
**zero** without authenticating. With the flag npm prints the URL and polls, and stdin stays
|
|
632
|
+
untouched.
|
|
633
|
+
- Hold stdin open and write nothing to it. Use a fifo held open by a long `sleep`. EOF drops npm to
|
|
634
|
+
the same legacy prompt a stray newline does.
|
|
635
|
+
- Run the login and every publish under `script -qfc '<command>' <log>`. npm offers the approval only
|
|
636
|
+
when it sees a TTY; without one it fails `EOTP` with no way to answer.
|
|
637
|
+
- Expect two approvals. `npmjs.com/login/cli/<id>` authenticates the session; `npmjs.com/auth/cli/<id>`
|
|
638
|
+
authorizes the publish and opens the five-minute window. Tell the user both are coming, or the
|
|
639
|
+
second link reads as the first having failed.
|
|
640
|
+
- Confirm authentication with `npm whoami`, never with an exit code. The legacy fallthrough exits
|
|
641
|
+
zero.
|
|
642
|
+
- Re-probe `whoami` immediately before opening the window. A stored credential expires mid-session,
|
|
643
|
+
so a session-start answer does not hold.
|
|
644
|
+
- Surface each approval URL the moment it appears in the log. Say that approving the publish one
|
|
631
645
|
opens a five-minute window covering the rest of the layer.
|
|
632
646
|
|
|
633
647
|
### Spending the window
|
|
@@ -60,9 +60,13 @@ Define aliases in `tsconfig.json` first. `vite.config.ts` derives from `compiler
|
|
|
60
60
|
- `*/types.ts`: public API contracts.
|
|
61
61
|
- `configs/src/` and `configs/app/`: thin per-target wrappers, including optional
|
|
62
62
|
`configs/src/*bin*` files. Shared logic remains in root configs.
|
|
63
|
-
- `configs/helpers.ts`: the
|
|
64
|
-
workspace, which is what keeps it a leaf. Each `configs/src/*.config.ts`
|
|
65
|
-
rather than
|
|
63
|
+
- `configs/helpers.ts` and `configs/browsers.ts`: the only permitted leaves under `configs/`. Each
|
|
64
|
+
imports nothing from the workspace, which is what keeps it a leaf. Each `configs/src/*.config.ts`
|
|
65
|
+
imports the root config rather than a leaf, so shared build logic stays in one place.
|
|
66
|
+
- Keep `configs/helpers.ts` free of any dependency a core-only workspace does not declare. It is
|
|
67
|
+
vendored byte-identical to every workspace, so an import there must resolve in all of them.
|
|
68
|
+
`configs/browsers.ts` exists for that reason: it imports `playwright` and
|
|
69
|
+
`@vitest/browser-playwright`, and only a workspace with a browser environment is given it.
|
|
66
70
|
|
|
67
71
|
Environment rules:
|
|
68
72
|
|
|
@@ -191,6 +191,7 @@ Exported from `@orkestrel/scaffold`, and reachable from
|
|
|
191
191
|
| `matchesOrchestrationPath` | function | Test whether a path instructs or wires an agent rather than the toolchain. |
|
|
192
192
|
| `matchesRange` | function | Test whether a declared range already admits a published version. |
|
|
193
193
|
| `nameToGuide` | function | Derive the guide mirror path a package name answers for. |
|
|
194
|
+
| `nameToRewrite` | function | Derive the declaration rewrite a published face roll-up applies. |
|
|
194
195
|
| `planToSummary` | function | Project a plan into its tally by artifact origin. |
|
|
195
196
|
| `selectGroups` | function | Select the groups a compile covers, in plan order. |
|
|
196
197
|
| `selectHostPaths` | function | Select the host paths a named workspace vendors. |
|
|
@@ -826,6 +827,17 @@ except the manifest.
|
|
|
826
827
|
- One template artifact per configuration file the selection needs: the root `tsconfig.json` and
|
|
827
828
|
`vite.config.ts`, plus a Vite config and a scoped TypeScript config per selected environment, and
|
|
828
829
|
two more when `bin` is set.
|
|
830
|
+
- One template artifact, `configs/browsers.ts`, for a workspace selecting `browser` on either axis.
|
|
831
|
+
It resolves the Chromium the Playwright provider launches, and the root `vite.config.ts` calls it
|
|
832
|
+
once into `browserOptions` and passes that to every `playwright()` provider it configures. The
|
|
833
|
+
precedence is `PLAYWRIGHT_EXECUTABLE_PATH`, `PLAYWRIGHT_WS_ENDPOINT`, `PLAYWRIGHT_CHANNEL`, the
|
|
834
|
+
managed Playwright Chromium, the container's bundled Chromium, a verified system channel, then the
|
|
835
|
+
platform default. An installed pinned revision returns empty options, so Playwright keeps its own
|
|
836
|
+
launch defaults. A pinned revision that is not installed falls through to a `chromium` alias or a
|
|
837
|
+
sibling `chromium-*` revision under the same browsers directory, because a managed container ships
|
|
838
|
+
one usable build for many Playwright versions. It is its own file rather than a block in the
|
|
839
|
+
vendored `configs/helpers.ts`, which every workspace receives byte-identical while only a browser
|
|
840
|
+
selection declares the `playwright` this module imports.
|
|
829
841
|
- One template artifact per source and test file the selection needs: an `index.ts` barrel per
|
|
830
842
|
selected environment, `main.ts` and `index.html` for an application browser, `tests/setup.ts`
|
|
831
843
|
plus the host setup modules the selection reaches, and one entry test per axis project.
|
package/dist/src/core/index.cjs
CHANGED
|
@@ -290,7 +290,7 @@ var DEFAULT_ENGINES = `>=${MINIMUM_NODE_VERSION}`;
|
|
|
290
290
|
var BASE_DEV_DEPENDENCIES = Object.freeze({
|
|
291
291
|
"@microsoft/api-extractor": "^7.58.12",
|
|
292
292
|
"@orkestrel/guide": "^0.0.10",
|
|
293
|
-
"@orkestrel/scaffold": "^0.0.
|
|
293
|
+
"@orkestrel/scaffold": "^0.0.29",
|
|
294
294
|
"@types/node": "^26.2.0",
|
|
295
295
|
oxfmt: "^0.62.0",
|
|
296
296
|
oxlint: "^1.77.0",
|
|
@@ -386,11 +386,11 @@ var CONFIG_TEMPLATES = Object.freeze({
|
|
|
386
386
|
vite: `import type { {{viteTypes}} } from 'vite'
|
|
387
387
|
{{imports}}import { defineConfig, mergeConfig } from 'vitest/config'
|
|
388
388
|
import tsconfig from './tsconfig.json' with { type: 'json' }
|
|
389
|
-
{{helpers}}import { lstatSync, readdirSync, realpathSync } from 'node:fs'
|
|
389
|
+
{{helpers}}{{browsers}}import { lstatSync, readdirSync, realpathSync } from 'node:fs'
|
|
390
390
|
import { basename, join, parse, relative, resolve as resolvePath, sep } from 'node:path'
|
|
391
391
|
import { fileURLToPath, URL } from 'node:url'
|
|
392
392
|
|
|
393
|
-
export function resolveWorkspacePath(relativePath: string): string {
|
|
393
|
+
{{options}}export function resolveWorkspacePath(relativePath: string): string {
|
|
394
394
|
return fileURLToPath(new URL(relativePath, import.meta.url))
|
|
395
395
|
}
|
|
396
396
|
|
|
@@ -484,7 +484,7 @@ const resolve = {
|
|
|
484
484
|
{{exclude}} setupFiles: ['./tests/setup.ts', './tests/setupBrowser.ts'],
|
|
485
485
|
browser: {
|
|
486
486
|
enabled: true,
|
|
487
|
-
provider: playwright(),
|
|
487
|
+
provider: playwright(browserOptions),
|
|
488
488
|
instances: [{ browser: 'chromium', headless: true }],
|
|
489
489
|
},
|
|
490
490
|
fileParallelism: false,
|
|
@@ -595,7 +595,7 @@ const resolve = {
|
|
|
595
595
|
setupFiles: ['./tests/setup.ts', './tests/setupBrowser.ts'],
|
|
596
596
|
browser: {
|
|
597
597
|
enabled: true,
|
|
598
|
-
provider: playwright(),
|
|
598
|
+
provider: playwright(browserOptions),
|
|
599
599
|
instances: [{ browser: 'chromium', headless: true }],
|
|
600
600
|
},
|
|
601
601
|
fileParallelism: false,
|
|
@@ -920,12 +920,22 @@ export default defineConfig(
|
|
|
920
920
|
import dts from 'vite-plugin-dts'
|
|
921
921
|
import { srcBrowser, resolveWorkspacePath } from '../../vite.config.ts'
|
|
922
922
|
|
|
923
|
+
// vite-plugin-dts rolls this face into one declaration, and the roll-up reaches
|
|
924
|
+
// src/core through a relative source path the tarball does not carry. The path
|
|
925
|
+
// keeps each source module's own depth, so a module in a browser subfolder emits
|
|
926
|
+
// one that leaves dist/src entirely. The rewrite below externalizes core through
|
|
927
|
+
// the package's own published root export, on the final roll-up only.
|
|
923
928
|
export default defineConfig(
|
|
924
929
|
srcBrowser({
|
|
925
930
|
plugins: [
|
|
926
931
|
dts({
|
|
927
932
|
tsconfigPath: resolveWorkspacePath('configs/src/tsconfig.browser.json'),
|
|
928
933
|
bundleTypes: true,
|
|
934
|
+
beforeWriteFile: (path, content) => ({
|
|
935
|
+
content: /[\\\\/]dist[\\\\/]src[\\\\/]browser[\\\\/]index\\.d\\.ts$/.test(path)
|
|
936
|
+
{{replacement}}
|
|
937
|
+
: content,
|
|
938
|
+
}),
|
|
929
939
|
}),
|
|
930
940
|
],
|
|
931
941
|
}),
|
|
@@ -994,7 +1004,319 @@ import { appShowcase } from '../../vite.config.ts'
|
|
|
994
1004
|
export default defineConfig(appShowcase())
|
|
995
1005
|
`
|
|
996
1006
|
})
|
|
997
|
-
})
|
|
1007
|
+
}),
|
|
1008
|
+
browsers: `// A generated browser workspace resolves its own Chromium here rather than in
|
|
1009
|
+
// \`configs/helpers.ts\`, because that leaf is vendored byte-identical to every
|
|
1010
|
+
// workspace and most of them declare no \`playwright\` to import.
|
|
1011
|
+
|
|
1012
|
+
import type { PlaywrightProviderOptions } from '@vitest/browser-playwright'
|
|
1013
|
+
import { chromium } from 'playwright'
|
|
1014
|
+
import { accessSync, constants as FS_CONSTANTS, globSync, readdirSync, statSync } from 'node:fs'
|
|
1015
|
+
import { basename, dirname, join, resolve as resolvePath } from 'node:path'
|
|
1016
|
+
|
|
1017
|
+
/**
|
|
1018
|
+
* Chromium executable layouts inside a \`chromium-<revision>\` browsers-directory entry, per
|
|
1019
|
+
* platform.
|
|
1020
|
+
*
|
|
1021
|
+
* @remarks
|
|
1022
|
+
* The current Playwright build ships Chrome for Testing on macOS. The trailing \`Chromium.app\`
|
|
1023
|
+
* layouts are what earlier builds shipped, so the list spans Playwright versions instead of
|
|
1024
|
+
* pinning to the installed one.
|
|
1025
|
+
*/
|
|
1026
|
+
export const CHROMIUM_LAYOUTS = Object.freeze([
|
|
1027
|
+
'chrome-linux/chrome',
|
|
1028
|
+
'chrome-linux64/chrome',
|
|
1029
|
+
'chrome-win/chrome.exe',
|
|
1030
|
+
'chrome-win64/chrome.exe',
|
|
1031
|
+
'chrome-mac-x64/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing',
|
|
1032
|
+
'chrome-mac-arm64/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing',
|
|
1033
|
+
'chrome-mac/Chromium.app/Contents/MacOS/Chromium',
|
|
1034
|
+
'chrome-mac-arm64/Chromium.app/Contents/MacOS/Chromium',
|
|
1035
|
+
])
|
|
1036
|
+
|
|
1037
|
+
/** The \`chromium-<revision>\` entry name Playwright installs one managed build into. */
|
|
1038
|
+
export const CHROMIUM_ENTRY_PATTERN = /^chromium-\\d+$/
|
|
1039
|
+
|
|
1040
|
+
/** The revision number carried by any path containing a \`chromium-<revision>\` segment. */
|
|
1041
|
+
export const CHROMIUM_REVISION_PATTERN = /chromium-(\\d+)/
|
|
1042
|
+
|
|
1043
|
+
/** The directory a managed Linux container installs its bundled Playwright browsers into. */
|
|
1044
|
+
export const BUNDLED_BROWSERS_ROOT = '/opt/pw-browsers'
|
|
1045
|
+
|
|
1046
|
+
/**
|
|
1047
|
+
* Bundled Chromium layouts under the managed-container browsers root, as glob patterns.
|
|
1048
|
+
*
|
|
1049
|
+
* @remarks
|
|
1050
|
+
* The revision directory and its inner layout both drift across Playwright builds, and the
|
|
1051
|
+
* container also carries a top-level \`chromium\` alias, so every known shape is globbed.
|
|
1052
|
+
*/
|
|
1053
|
+
export const BUNDLED_CHROMIUM_LAYOUTS = Object.freeze([
|
|
1054
|
+
'chromium',
|
|
1055
|
+
'chromium-*/chrome-linux64/chrome',
|
|
1056
|
+
'chromium-*/chrome-linux/chrome',
|
|
1057
|
+
])
|
|
1058
|
+
|
|
1059
|
+
/** Stable Playwright Chromium channels and their standard executable layouts. */
|
|
1060
|
+
export const SYSTEM_BROWSER_CHANNELS = Object.freeze([
|
|
1061
|
+
Object.freeze({
|
|
1062
|
+
channel: 'chrome',
|
|
1063
|
+
layouts: Object.freeze({
|
|
1064
|
+
linux: '/opt/google/chrome/chrome',
|
|
1065
|
+
darwin: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
1066
|
+
win32: Object.freeze(['Google', 'Chrome', 'Application', 'chrome.exe']),
|
|
1067
|
+
}),
|
|
1068
|
+
}),
|
|
1069
|
+
Object.freeze({
|
|
1070
|
+
channel: 'msedge',
|
|
1071
|
+
layouts: Object.freeze({
|
|
1072
|
+
linux: '/opt/microsoft/msedge/msedge',
|
|
1073
|
+
darwin: '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
|
|
1074
|
+
win32: Object.freeze(['Microsoft', 'Edge', 'Application', 'msedge.exe']),
|
|
1075
|
+
}),
|
|
1076
|
+
}),
|
|
1077
|
+
])
|
|
1078
|
+
|
|
1079
|
+
/**
|
|
1080
|
+
* Determine whether a path identifies an executable regular file.
|
|
1081
|
+
*
|
|
1082
|
+
* @param path - The filesystem path to inspect.
|
|
1083
|
+
* @returns Whether the path is a regular file with execute access.
|
|
1084
|
+
*
|
|
1085
|
+
* @example
|
|
1086
|
+
* \`\`\`ts
|
|
1087
|
+
* isBrowserExecutable('/opt/google/chrome/chrome')
|
|
1088
|
+
* \`\`\`
|
|
1089
|
+
*/
|
|
1090
|
+
export function isBrowserExecutable(path: string): boolean {
|
|
1091
|
+
try {
|
|
1092
|
+
if (!statSync(path).isFile()) return false
|
|
1093
|
+
accessSync(path, FS_CONSTANTS.X_OK)
|
|
1094
|
+
return true
|
|
1095
|
+
} catch {
|
|
1096
|
+
return false
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
/**
|
|
1101
|
+
* Order two Chromium paths so the highest revision sorts first.
|
|
1102
|
+
*
|
|
1103
|
+
* @param left - The first path or directory entry to compare.
|
|
1104
|
+
* @param right - The second path or directory entry to compare.
|
|
1105
|
+
* @returns A negative number when \`left\` sorts first, positive when \`right\` does.
|
|
1106
|
+
*
|
|
1107
|
+
* @remarks
|
|
1108
|
+
* Revisions are numbers, so \`chromium-1200\` outranks \`chromium-999\` despite sorting below it
|
|
1109
|
+
* lexically. A path carrying no revision falls back to descending name order.
|
|
1110
|
+
*
|
|
1111
|
+
* @example
|
|
1112
|
+
* \`\`\`ts
|
|
1113
|
+
* ['chromium-999', 'chromium-1200'].sort(compareRevisions)
|
|
1114
|
+
* \`\`\`
|
|
1115
|
+
*/
|
|
1116
|
+
export function compareRevisions(left: string, right: string): number {
|
|
1117
|
+
const leftRevision = CHROMIUM_REVISION_PATTERN.exec(left)?.[1]
|
|
1118
|
+
const rightRevision = CHROMIUM_REVISION_PATTERN.exec(right)?.[1]
|
|
1119
|
+
if (leftRevision === undefined || rightRevision === undefined) return right.localeCompare(left)
|
|
1120
|
+
return Number(rightRevision) - Number(leftRevision)
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
/**
|
|
1124
|
+
* Read the executable path of Playwright's pinned Chromium revision.
|
|
1125
|
+
*
|
|
1126
|
+
* @returns The pinned executable path, or \`undefined\` when this platform has none.
|
|
1127
|
+
*
|
|
1128
|
+
* @remarks
|
|
1129
|
+
* Playwright throws rather than returning a path when the current platform carries no initialized
|
|
1130
|
+
* executable, and an unguarded call would fail configuration evaluation for every project.
|
|
1131
|
+
*
|
|
1132
|
+
* @example
|
|
1133
|
+
* \`\`\`ts
|
|
1134
|
+
* resolvePinnedBrowser()
|
|
1135
|
+
* \`\`\`
|
|
1136
|
+
*/
|
|
1137
|
+
export function resolvePinnedBrowser(): string | undefined {
|
|
1138
|
+
try {
|
|
1139
|
+
const pinned = chromium.executablePath()
|
|
1140
|
+
return pinned.length === 0 ? undefined : pinned
|
|
1141
|
+
} catch {
|
|
1142
|
+
return undefined
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
/**
|
|
1147
|
+
* Resolve a launchable Playwright-managed Chromium executable: the pinned revision when installed,
|
|
1148
|
+
* otherwise a \`chromium\` / \`chromium.exe\` alias or any other \`chromium-*\` revision under the same
|
|
1149
|
+
* Playwright browsers directory. A pinned-revision miss is not Chromium absence — managed
|
|
1150
|
+
* containers ship one usable build, often behind a revision-agnostic alias, for many Playwright
|
|
1151
|
+
* versions.
|
|
1152
|
+
*
|
|
1153
|
+
* @param pinned - The executable path for Playwright's pinned Chromium revision.
|
|
1154
|
+
* @returns The managed executable path, or \`undefined\` when none is executable.
|
|
1155
|
+
*
|
|
1156
|
+
* @example
|
|
1157
|
+
* \`\`\`ts
|
|
1158
|
+
* resolveManagedBrowser('/root/.cache/ms-playwright/chromium-1234/chrome-linux64/chrome')
|
|
1159
|
+
* \`\`\`
|
|
1160
|
+
*/
|
|
1161
|
+
export function resolveManagedBrowser(pinned: string): string | undefined {
|
|
1162
|
+
if (isBrowserExecutable(pinned)) return pinned
|
|
1163
|
+
let revisionRoot = dirname(pinned)
|
|
1164
|
+
for (;;) {
|
|
1165
|
+
if (CHROMIUM_ENTRY_PATTERN.test(basename(revisionRoot))) break
|
|
1166
|
+
const parent = dirname(revisionRoot)
|
|
1167
|
+
if (parent === revisionRoot) return undefined
|
|
1168
|
+
revisionRoot = parent
|
|
1169
|
+
}
|
|
1170
|
+
const browsersRoot = dirname(revisionRoot)
|
|
1171
|
+
for (const alias of ['chromium', 'chromium.exe']) {
|
|
1172
|
+
const candidate = resolvePath(browsersRoot, alias)
|
|
1173
|
+
if (isBrowserExecutable(candidate)) return candidate
|
|
1174
|
+
}
|
|
1175
|
+
let entries: readonly string[]
|
|
1176
|
+
try {
|
|
1177
|
+
entries = readdirSync(browsersRoot)
|
|
1178
|
+
} catch {
|
|
1179
|
+
return undefined
|
|
1180
|
+
}
|
|
1181
|
+
const revisions = entries
|
|
1182
|
+
.filter((entry) => CHROMIUM_ENTRY_PATTERN.test(entry))
|
|
1183
|
+
.sort(compareRevisions)
|
|
1184
|
+
for (const revision of revisions) {
|
|
1185
|
+
for (const layout of CHROMIUM_LAYOUTS) {
|
|
1186
|
+
const candidate = resolvePath(browsersRoot, revision, layout)
|
|
1187
|
+
if (isBrowserExecutable(candidate)) return candidate
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
return undefined
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
/**
|
|
1194
|
+
* Resolve the Chromium a managed Linux container bundles outside the Playwright cache.
|
|
1195
|
+
*
|
|
1196
|
+
* @param platform - The Node platform the container runs on.
|
|
1197
|
+
* @param root - The bundled browsers directory to search.
|
|
1198
|
+
* @returns The highest matching executable path, or \`undefined\` when none is executable.
|
|
1199
|
+
*
|
|
1200
|
+
* @example
|
|
1201
|
+
* \`\`\`ts
|
|
1202
|
+
* resolveBundledBrowser('linux', BUNDLED_BROWSERS_ROOT)
|
|
1203
|
+
* \`\`\`
|
|
1204
|
+
*/
|
|
1205
|
+
export function resolveBundledBrowser(platform: NodeJS.Platform, root: string): string | undefined {
|
|
1206
|
+
if (platform !== 'linux') return undefined
|
|
1207
|
+
for (const layout of BUNDLED_CHROMIUM_LAYOUTS) {
|
|
1208
|
+
let matches: readonly string[]
|
|
1209
|
+
try {
|
|
1210
|
+
matches = globSync(layout, { cwd: root })
|
|
1211
|
+
} catch {
|
|
1212
|
+
return undefined
|
|
1213
|
+
}
|
|
1214
|
+
for (const match of [...matches].sort(compareRevisions)) {
|
|
1215
|
+
const candidate = resolvePath(root, match)
|
|
1216
|
+
if (isBrowserExecutable(candidate)) return candidate
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
return undefined
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
/**
|
|
1223
|
+
* Resolve the first installed stable system Chromium channel.
|
|
1224
|
+
*
|
|
1225
|
+
* @param platform - The Node platform whose standard layouts should be probed.
|
|
1226
|
+
* @param environment - The process environment supplying Windows installation roots.
|
|
1227
|
+
* @returns \`chrome\`, then \`msedge\`, or \`undefined\` when neither is executable.
|
|
1228
|
+
*
|
|
1229
|
+
* @example
|
|
1230
|
+
* \`\`\`ts
|
|
1231
|
+
* resolveSystemBrowser(process.platform, process.env)
|
|
1232
|
+
* \`\`\`
|
|
1233
|
+
*/
|
|
1234
|
+
export function resolveSystemBrowser(
|
|
1235
|
+
platform: NodeJS.Platform,
|
|
1236
|
+
environment: NodeJS.ProcessEnv,
|
|
1237
|
+
): string | undefined {
|
|
1238
|
+
if (platform !== 'linux' && platform !== 'darwin' && platform !== 'win32') return undefined
|
|
1239
|
+
const roots = new Set<string>()
|
|
1240
|
+
if (platform === 'win32') {
|
|
1241
|
+
for (const root of [
|
|
1242
|
+
environment.LOCALAPPDATA,
|
|
1243
|
+
environment.PROGRAMFILES,
|
|
1244
|
+
environment['PROGRAMFILES(X86)'],
|
|
1245
|
+
]) {
|
|
1246
|
+
if (root !== undefined && root.length > 0) roots.add(root)
|
|
1247
|
+
}
|
|
1248
|
+
const homeDrive = environment.HOMEDRIVE
|
|
1249
|
+
if (homeDrive !== undefined && homeDrive.length > 0) {
|
|
1250
|
+
roots.add(join(homeDrive, 'Program Files'))
|
|
1251
|
+
roots.add(join(homeDrive, 'Program Files (x86)'))
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
for (const browser of SYSTEM_BROWSER_CHANNELS) {
|
|
1255
|
+
if (platform === 'win32') {
|
|
1256
|
+
for (const root of roots) {
|
|
1257
|
+
if (isBrowserExecutable(join(root, ...browser.layouts.win32))) return browser.channel
|
|
1258
|
+
}
|
|
1259
|
+
continue
|
|
1260
|
+
}
|
|
1261
|
+
if (isBrowserExecutable(browser.layouts[platform])) return browser.channel
|
|
1262
|
+
}
|
|
1263
|
+
return undefined
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
/**
|
|
1267
|
+
* Resolve Playwright provider options for whatever browser this host can actually launch.
|
|
1268
|
+
*
|
|
1269
|
+
* @param pinned - The executable path for Playwright's pinned Chromium revision, when it has one.
|
|
1270
|
+
* @param platform - The Node platform whose standard layouts should be probed.
|
|
1271
|
+
* @param environment - The process environment supplying operator overrides and Windows roots.
|
|
1272
|
+
* @param root - The managed-container bundled browsers directory to search.
|
|
1273
|
+
* @returns Provider options naming an executable, a WebSocket endpoint, or a channel.
|
|
1274
|
+
*
|
|
1275
|
+
* @remarks
|
|
1276
|
+
* Precedence, most important first: \`PLAYWRIGHT_EXECUTABLE_PATH\`, \`PLAYWRIGHT_WS_ENDPOINT\`,
|
|
1277
|
+
* \`PLAYWRIGHT_CHANNEL\`, the managed Playwright Chromium, the container's bundled Chromium, a
|
|
1278
|
+
* verified system channel, then the platform default channel. An operator override outranks
|
|
1279
|
+
* discovery and is returned exactly as given: none of those three environment values is checked
|
|
1280
|
+
* against the filesystem, because verifying an override would defeat the override. The pinned
|
|
1281
|
+
* managed revision outranks anything found on the host because it is deterministic. The installed
|
|
1282
|
+
* pinned revision returns empty options so Playwright keeps its own default launch semantics. Only
|
|
1283
|
+
* a discovered system channel is verified before it is named. The platform default is unverified
|
|
1284
|
+
* as well and exists only as a last resort: Windows takes \`msedge\`, which ships with the OS and
|
|
1285
|
+
* never collides with a foreground Chrome.
|
|
1286
|
+
*
|
|
1287
|
+
* @example
|
|
1288
|
+
* \`\`\`ts
|
|
1289
|
+
* resolveBrowser(resolvePinnedBrowser(), process.platform, process.env)
|
|
1290
|
+
* \`\`\`
|
|
1291
|
+
*/
|
|
1292
|
+
export function resolveBrowser(
|
|
1293
|
+
pinned: string | undefined,
|
|
1294
|
+
platform: NodeJS.Platform,
|
|
1295
|
+
environment: NodeJS.ProcessEnv,
|
|
1296
|
+
root: string = BUNDLED_BROWSERS_ROOT,
|
|
1297
|
+
): PlaywrightProviderOptions {
|
|
1298
|
+
const executable = environment.PLAYWRIGHT_EXECUTABLE_PATH
|
|
1299
|
+
if (executable !== undefined && executable.length > 0) {
|
|
1300
|
+
return { launchOptions: { executablePath: executable } }
|
|
1301
|
+
}
|
|
1302
|
+
const endpoint = environment.PLAYWRIGHT_WS_ENDPOINT
|
|
1303
|
+
if (endpoint !== undefined && endpoint.length > 0) {
|
|
1304
|
+
return { connectOptions: { wsEndpoint: endpoint } }
|
|
1305
|
+
}
|
|
1306
|
+
const requested = environment.PLAYWRIGHT_CHANNEL
|
|
1307
|
+
if (requested !== undefined && requested.length > 0) {
|
|
1308
|
+
return { launchOptions: { channel: requested } }
|
|
1309
|
+
}
|
|
1310
|
+
const managed = pinned === undefined ? undefined : resolveManagedBrowser(pinned)
|
|
1311
|
+
if (managed !== undefined) {
|
|
1312
|
+
return managed === pinned ? {} : { launchOptions: { executablePath: managed } }
|
|
1313
|
+
}
|
|
1314
|
+
const bundled = resolveBundledBrowser(platform, root)
|
|
1315
|
+
if (bundled !== undefined) return { launchOptions: { executablePath: bundled } }
|
|
1316
|
+
const fallback = platform === 'win32' ? 'msedge' : 'chrome'
|
|
1317
|
+
return { launchOptions: { channel: resolveSystemBrowser(platform, environment) ?? fallback } }
|
|
1318
|
+
}
|
|
1319
|
+
`
|
|
998
1320
|
});
|
|
999
1321
|
/**
|
|
1000
1322
|
* Formatter-stable template text for source, test, document, guide, and service artifacts.
|
|
@@ -2075,6 +2397,46 @@ function nameToGuide(name) {
|
|
|
2075
2397
|
return `guides/${name.slice(name.lastIndexOf("/") + 1)}.md`;
|
|
2076
2398
|
}
|
|
2077
2399
|
/**
|
|
2400
|
+
* Derive the declaration rewrite a published face's `beforeWriteFile` applies.
|
|
2401
|
+
*
|
|
2402
|
+
* @param name - The workspace's own bare package name.
|
|
2403
|
+
* @returns The ternary consequent an emitted `vite.{browser,server}.config.ts`
|
|
2404
|
+
* fills its `{{replacement}}` span with, indented for that span.
|
|
2405
|
+
*
|
|
2406
|
+
* @remarks
|
|
2407
|
+
* `vite-plugin-dts` rolls a face into one declaration and keeps each source
|
|
2408
|
+
* module's own relative depth, so a nested module emits a path that escapes
|
|
2409
|
+
* `dist/src` and a flat one resolves only by luck. Both faces rewrite the same
|
|
2410
|
+
* relative core path to the package's published root export, so the branch is
|
|
2411
|
+
* derived once here. The extension alternation is what the two permitted import
|
|
2412
|
+
* spellings produce: an `@src/core` alias resolves to the core source module and
|
|
2413
|
+
* prints `.ts`, while a relative import prints the `.js` specifier it was
|
|
2414
|
+
* written with. The formatter keeps the call on one line only while the line it
|
|
2415
|
+
* prints measures inside the vendored width, and the workspace name is what
|
|
2416
|
+
* varies, so the shape is chosen by measuring the candidate: a tab prints as the
|
|
2417
|
+
* vendored two columns, and the gate admits a name long enough to push the
|
|
2418
|
+
* joined call past 100.
|
|
2419
|
+
*
|
|
2420
|
+
* @example
|
|
2421
|
+
* ```ts
|
|
2422
|
+
* import { nameToRewrite } from '@orkestrel/scaffold'
|
|
2423
|
+
*
|
|
2424
|
+
* nameToRewrite('router').includes("'@orkestrel/router'") // true
|
|
2425
|
+
* ```
|
|
2426
|
+
*/
|
|
2427
|
+
function nameToRewrite(name) {
|
|
2428
|
+
const specifier = serializeTypeScriptString(`@orkestrel/${name}`);
|
|
2429
|
+
const pattern = "/(?:\\.\\.\\/)+core\\/index\\.[jt]s/g";
|
|
2430
|
+
const joined = `\t\t\t\t\t\t? content.replaceAll(${pattern}, ${specifier})`;
|
|
2431
|
+
if (joined.replaceAll(" ", " ").length <= 100) return joined;
|
|
2432
|
+
return [
|
|
2433
|
+
" ? content.replaceAll(",
|
|
2434
|
+
`\t\t\t\t\t\t\t\t${pattern},`,
|
|
2435
|
+
`\t\t\t\t\t\t\t\t${specifier},`,
|
|
2436
|
+
" )"
|
|
2437
|
+
].join("\n");
|
|
2438
|
+
}
|
|
2439
|
+
/**
|
|
2078
2440
|
* Select the host paths a named workspace vendors.
|
|
2079
2441
|
*
|
|
2080
2442
|
* @param paths - The candidate host paths, in their declared order.
|
|
@@ -3133,6 +3495,8 @@ ${projects.map((project) => `\t\t\t${project},`).join("\n")}
|
|
|
3133
3495
|
viteTypes: machinery.showcase ? "PluginOption, UserConfig" : "UserConfig",
|
|
3134
3496
|
imports: imports.length === 0 ? "" : `${imports.join("\n")}\n`,
|
|
3135
3497
|
helpers: boundaries.length === 0 ? "" : `import { ${boundaries.join(", ")} } from './configs/helpers.js'\n`,
|
|
3498
|
+
browsers: machinery.browser ? "import { resolveBrowser, resolvePinnedBrowser } from './configs/browsers.js'\n" : "",
|
|
3499
|
+
options: machinery.browser ? "const browserOptions = resolveBrowser(resolvePinnedBrowser(), process.platform, process.env)\n\n" : "",
|
|
3136
3500
|
factories: body,
|
|
3137
3501
|
projects: projectRows
|
|
3138
3502
|
});
|
|
@@ -3166,22 +3530,20 @@ function blueprintToConfigArtifacts(blueprint) {
|
|
|
3166
3530
|
origin: "template",
|
|
3167
3531
|
content: blueprintToRootVite(blueprint)
|
|
3168
3532
|
}];
|
|
3533
|
+
if (blueprintToMachinery(blueprint).browser) artifacts.push({
|
|
3534
|
+
path: "configs/browsers.ts",
|
|
3535
|
+
group: "configs",
|
|
3536
|
+
ownership: "content",
|
|
3537
|
+
origin: "template",
|
|
3538
|
+
content: CONFIG_TEMPLATES.browsers
|
|
3539
|
+
});
|
|
3169
3540
|
for (const environment of blueprint.src) for (const path of SRC_MATRIX[environment].configs) {
|
|
3170
3541
|
let content = CONFIG_TEMPLATES.vites.src.core;
|
|
3171
3542
|
if (path === "configs/src/tsconfig.core.json") content = CONFIG_TEMPLATES.tsconfigs.src.core;
|
|
3172
|
-
else if (path === "configs/src/vite.browser.config.ts") content = CONFIG_TEMPLATES.vites.src.browser;
|
|
3543
|
+
else if (path === "configs/src/vite.browser.config.ts") content = (0, _orkestrel_template.fillTemplate)(CONFIG_TEMPLATES.vites.src.browser, { replacement: nameToRewrite(blueprint.name) });
|
|
3173
3544
|
else if (path === "configs/src/tsconfig.browser.json") content = CONFIG_TEMPLATES.tsconfigs.src.browser;
|
|
3174
|
-
else if (path === "configs/src/vite.server.config.ts") {
|
|
3175
|
-
|
|
3176
|
-
const joined = `\t\t\t\t\t\t? content.replaceAll(/(?:\\.\\.\\/)+core\\/index\\.ts/g, ${packageName})`;
|
|
3177
|
-
const replacement = joined.replaceAll(" ", " ").length <= 100 ? joined : [
|
|
3178
|
-
" ? content.replaceAll(",
|
|
3179
|
-
" /(?:\\.\\.\\/)+core\\/index\\.ts/g,",
|
|
3180
|
-
`\t\t\t\t\t\t\t\t${packageName},`,
|
|
3181
|
-
" )"
|
|
3182
|
-
].join("\n");
|
|
3183
|
-
content = (0, _orkestrel_template.fillTemplate)(CONFIG_TEMPLATES.vites.src.server, { replacement });
|
|
3184
|
-
} else if (path === "configs/src/tsconfig.server.json") content = CONFIG_TEMPLATES.tsconfigs.src.server;
|
|
3545
|
+
else if (path === "configs/src/vite.server.config.ts") content = (0, _orkestrel_template.fillTemplate)(CONFIG_TEMPLATES.vites.src.server, { replacement: nameToRewrite(blueprint.name) });
|
|
3546
|
+
else if (path === "configs/src/tsconfig.server.json") content = CONFIG_TEMPLATES.tsconfigs.src.server;
|
|
3185
3547
|
artifacts.push({
|
|
3186
3548
|
path,
|
|
3187
3549
|
group: "configs",
|
|
@@ -4531,6 +4893,7 @@ exports.matchesOrchestrationPath = matchesOrchestrationPath;
|
|
|
4531
4893
|
exports.matchesRange = matchesRange;
|
|
4532
4894
|
exports.nameToGuide = nameToGuide;
|
|
4533
4895
|
exports.nameToHostArtifacts = nameToHostArtifacts;
|
|
4896
|
+
exports.nameToRewrite = nameToRewrite;
|
|
4534
4897
|
exports.overridesToQuestions = overridesToQuestions;
|
|
4535
4898
|
exports.parseBlueprint = parseBlueprint;
|
|
4536
4899
|
exports.parseCompilerOptions = parseCompilerOptions;
|