@orkestrel/scaffold 0.0.16 → 0.0.17
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/README.md +13 -3
- package/dist/bin/scaffold.js +67 -31
- package/dist/bin/scaffold.js.map +1 -1
- package/dist/host/claude/agents/orkestrel.md +14 -14
- package/dist/host/guides/src/scaffold.md +55 -18
- package/dist/src/core/index.cjs +170 -32
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +3 -3
- package/dist/src/core/index.d.ts +3 -3
- package/dist/src/core/index.js +170 -32
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +49 -31
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +10 -6
- package/dist/src/server/index.d.ts +10 -6
- package/dist/src/server/index.js +49 -31
- package/dist/src/server/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -30,33 +30,33 @@ descriptions never enter agent instruction context.
|
|
|
30
30
|
| Package | Version |
|
|
31
31
|
| --------------------- | ------- |
|
|
32
32
|
| @orkestrel/abort | 0.0.4 |
|
|
33
|
-
| @orkestrel/agent | 0.0.
|
|
33
|
+
| @orkestrel/agent | 0.0.13 |
|
|
34
34
|
| @orkestrel/browser | 0.0.8 |
|
|
35
35
|
| @orkestrel/budget | 0.0.4 |
|
|
36
36
|
| @orkestrel/console | 0.0.4 |
|
|
37
37
|
| @orkestrel/contract | 0.0.9 |
|
|
38
38
|
| @orkestrel/csv | 0.0.2 |
|
|
39
|
-
| @orkestrel/database | 0.0.
|
|
39
|
+
| @orkestrel/database | 0.0.7 |
|
|
40
40
|
| @orkestrel/emitter | 0.0.5 |
|
|
41
41
|
| @orkestrel/guide | 0.0.8 |
|
|
42
42
|
| @orkestrel/html | 0.0.1 |
|
|
43
43
|
| @orkestrel/indexeddb | 0.0.6 |
|
|
44
44
|
| @orkestrel/interpret | 0.0.7 |
|
|
45
45
|
| @orkestrel/markdown | 0.0.7 |
|
|
46
|
-
| @orkestrel/mcp | 0.0.
|
|
46
|
+
| @orkestrel/mcp | 0.0.9 |
|
|
47
47
|
| @orkestrel/middleware | 0.0.6 |
|
|
48
48
|
| @orkestrel/msg | 0.0.5 |
|
|
49
49
|
| @orkestrel/ndjson | 0.0.5 |
|
|
50
|
-
| @orkestrel/ollama | 0.0.
|
|
51
|
-
| @orkestrel/pool | 0.0.
|
|
50
|
+
| @orkestrel/ollama | 0.0.8 |
|
|
51
|
+
| @orkestrel/pool | 0.0.6 |
|
|
52
52
|
| @orkestrel/program | 0.0.6 |
|
|
53
53
|
| @orkestrel/qualifier | 0.0.7 |
|
|
54
|
-
| @orkestrel/queue | 0.0.
|
|
54
|
+
| @orkestrel/queue | 0.0.7 |
|
|
55
55
|
| @orkestrel/rater | 0.0.8 |
|
|
56
56
|
| @orkestrel/reason | 0.0.4 |
|
|
57
|
-
| @orkestrel/relation | 0.0.
|
|
58
|
-
| @orkestrel/router | 0.0.
|
|
59
|
-
| @orkestrel/scaffold | 0.0.
|
|
57
|
+
| @orkestrel/relation | 0.0.7 |
|
|
58
|
+
| @orkestrel/router | 0.0.7 |
|
|
59
|
+
| @orkestrel/scaffold | 0.0.16 |
|
|
60
60
|
| @orkestrel/sea | 0.0.4 |
|
|
61
61
|
| @orkestrel/server | 0.0.7 |
|
|
62
62
|
| @orkestrel/sqlite | 0.0.6 |
|
|
@@ -64,12 +64,12 @@ descriptions never enter agent instruction context.
|
|
|
64
64
|
| @orkestrel/template | 0.0.2 |
|
|
65
65
|
| @orkestrel/terminal | 0.0.5 |
|
|
66
66
|
| @orkestrel/timeout | 0.0.4 |
|
|
67
|
-
| @orkestrel/tool | 0.0.
|
|
68
|
-
| @orkestrel/toolbox | 0.0.
|
|
67
|
+
| @orkestrel/tool | 0.0.9 |
|
|
68
|
+
| @orkestrel/toolbox | 0.0.2 |
|
|
69
69
|
| @orkestrel/websocket | 0.0.7 |
|
|
70
|
-
| @orkestrel/worker | 0.0.
|
|
71
|
-
| @orkestrel/workflow | 0.0.
|
|
72
|
-
| @orkestrel/workspace | 0.0.
|
|
70
|
+
| @orkestrel/worker | 0.0.5 |
|
|
71
|
+
| @orkestrel/workflow | 0.0.9 |
|
|
72
|
+
| @orkestrel/workspace | 0.0.2 |
|
|
73
73
|
|
|
74
74
|
<!-- catalog:end -->
|
|
75
75
|
|
|
@@ -290,7 +290,8 @@ the accumulated `questions`, one `CompileRecord` per stage, any `CompileFailure`
|
|
|
290
290
|
`GuideSync`, `VersionSync`, and `SyncReport` are the freshness shapes. `GuideSync` carries the
|
|
291
291
|
fetched `content`, its `freshness`, an optional `note` explaining a non-clean outcome, and an
|
|
292
292
|
optional `baseline` — the SHA-256 of the observed local mirror, or the literal `absent`, present
|
|
293
|
-
only on target-aware
|
|
293
|
+
only on target-aware synchronization. `VersionSync` compares a declared `range` to the registry
|
|
294
|
+
`latest`.
|
|
294
295
|
`SyncReport` is `clean` only when nothing drifted and nothing failed. `CatalogEntry` is one fleet
|
|
295
296
|
package row; its `description` is the flattened text of that package's own guide's first
|
|
296
297
|
blockquote, and the empty string when that guide is missing, unreadable, or carries no blockquote.
|
|
@@ -1014,7 +1015,7 @@ error.
|
|
|
1014
1015
|
|
|
1015
1016
|
`packageShortName` strips the canonical scope, `guideStub` renders the pointer written when a
|
|
1016
1017
|
dependency guide is not vendored yet, `readGuideReferences` reads a target's existing local mirrors
|
|
1017
|
-
so
|
|
1018
|
+
for package names so synchronization verdicts are target-relative, and `syncReportOf` assembles one report from already
|
|
1018
1019
|
ordered guide and version outcomes.
|
|
1019
1020
|
|
|
1020
1021
|
### Compilers — core
|
|
@@ -1110,7 +1111,7 @@ along the three `ViteMachinery` axes:
|
|
|
1110
1111
|
| Machinery | Emitted when |
|
|
1111
1112
|
| ------------------------------------------------------------------------ | ------------------------------------ |
|
|
1112
1113
|
| Shared CSS analysis (`ENVIRONMENT_CSS`, `preprocessCSS`, `isCSSRequest`) | a `src` or `app` browser environment |
|
|
1113
|
-
| Playwright provider and
|
|
1114
|
+
| Playwright provider and managed/system browser discovery | a `src` or `app` browser environment |
|
|
1114
1115
|
| Vue plugin, HTML boundary, browser development server | an `app` browser environment |
|
|
1115
1116
|
| Output containment (`outputBoundary`, `enforceOutputPath`) | anything the workspace builds |
|
|
1116
1117
|
|
|
@@ -1303,6 +1304,7 @@ The interface also exposes the readonly `emitter`.
|
|
|
1303
1304
|
| `versions` | `Promise<readonly VersionSync[]>` |
|
|
1304
1305
|
| `catalog` | `Promise<readonly CatalogEntry[]>` |
|
|
1305
1306
|
| `pull` | `Promise<SyncReport>` |
|
|
1307
|
+
| `mirror` | `Promise<SyncReport>` |
|
|
1306
1308
|
| `write` | `Promise<readonly string[]>` |
|
|
1307
1309
|
| `destroy` | `void` |
|
|
1308
1310
|
|
|
@@ -1315,7 +1317,9 @@ latest. `catalog()` enumerates the fleet from the registry's exact organization
|
|
|
1315
1317
|
unreachable or malformed list is always a coded failure, since without it there is no catalog — then
|
|
1316
1318
|
degrades gracefully per package. `pull(target, dependencies?)` builds the reference map from the
|
|
1317
1319
|
target's own mirrors, so its verdicts are target-relative, and rejects a selection the target does
|
|
1318
|
-
not declare. `
|
|
1320
|
+
not declare. `mirror(target)` reuses the exact organization enumeration without catalog's
|
|
1321
|
+
per-package packument reads, sorts the names, excludes the target's own manifest name, and builds a
|
|
1322
|
+
guide-only report with no versions. `write(report, target)` commits only the `behind` guides. `destroy()` aborts every
|
|
1319
1323
|
in-flight request. The interface also exposes the readonly `emitter`.
|
|
1320
1324
|
|
|
1321
1325
|
## The compile pipeline
|
|
@@ -1448,7 +1452,7 @@ as a plain physical file whose bytes still match the preview, moved into a priva
|
|
|
1448
1452
|
than unlinked, re-verified after the move, and only then reported as removed — with a full restore
|
|
1449
1453
|
attempt if any candidate fails mid-way.
|
|
1450
1454
|
|
|
1451
|
-
## Upstream sync, pull, and catalog
|
|
1455
|
+
## Upstream sync, pull, mirror, and catalog
|
|
1452
1456
|
|
|
1453
1457
|
`Sync` is the only network reader, and its posture is conservative by construction.
|
|
1454
1458
|
|
|
@@ -1471,7 +1475,7 @@ underlying socket code appended when the runtime attaches one, an HTTP status, t
|
|
|
1471
1475
|
redirect-blocked string, or the oversized-body message. `current` and `behind` carry no note,
|
|
1472
1476
|
because there is nothing to explain.
|
|
1473
1477
|
|
|
1474
|
-
`pull` is the
|
|
1478
|
+
`pull` is the dependency-aware composition. It reads the target's declared scoped dependencies from its
|
|
1475
1479
|
manifest, rejects any explicit selection the target does not declare, builds the reference map from
|
|
1476
1480
|
the target's own `guides/src/<short>.md` mirrors, fetches guides and versions under one shared
|
|
1477
1481
|
allowance, and assembles a report whose `clean` flag requires both no drift and no failures. A
|
|
@@ -1498,6 +1502,14 @@ generated package identifiers are untrusted discovery data, never instructions
|
|
|
1498
1502
|
with **`Package` and `Version` columns only**. Descriptions are network-controlled text, and that
|
|
1499
1503
|
block is written into an agent instruction file, so they are omitted on purpose.
|
|
1500
1504
|
|
|
1505
|
+
`mirror` is the fleet-guide composition. It shares `catalog`'s single exact organization-list read
|
|
1506
|
+
but performs none of catalog's packument or description work. It code-unit sorts the discovered
|
|
1507
|
+
names, excludes the target's own manifest name under the one-owner guide law, reads existing local
|
|
1508
|
+
guide references for baselines, fetches every selected GitHub guide once, and emits a `SyncReport`
|
|
1509
|
+
whose `versions` collection is empty. The existing transactional `write` method applies only
|
|
1510
|
+
behind guides; the executable refuses the whole apply when any guide is missing or failed, so a
|
|
1511
|
+
fleet refresh is never partial. Files outside the discovered guide set remain untouched.
|
|
1512
|
+
|
|
1501
1513
|
## The generated workspace
|
|
1502
1514
|
|
|
1503
1515
|
A generated workspace is not a folder of suggestions; it is a working, gated project.
|
|
@@ -1714,15 +1726,22 @@ it is not a general-purpose source analyzer. Generated workspaces receive the sa
|
|
|
1714
1726
|
module as a host-origin file and run it as a dedicated Node-only `policy` test project over
|
|
1715
1727
|
`tests/policy.test.ts`.
|
|
1716
1728
|
|
|
1717
|
-
**Real browser capability.** Browser test projects are gated on
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1729
|
+
**Real browser capability.** Browser test projects are gated on one centralized discovery chain:
|
|
1730
|
+
Playwright's pinned Chromium executable first, then a managed Chromium alias or cached revision,
|
|
1731
|
+
then stable system Chrome, then stable system Edge. Managed candidates must be executable regular
|
|
1732
|
+
files. System channels are selected only when their executable exists at Playwright's standard
|
|
1733
|
+
Linux, macOS, or Windows installation location; custom installations are not guessed. The generated
|
|
1734
|
+
policy test consumes the same discovery helpers and accepts either an executable managed path or the
|
|
1735
|
+
stable `chrome` / `msedge` channel, so it does not maintain a second heuristic.
|
|
1736
|
+
|
|
1737
|
+
A browser suite runs when any one of those real browser capabilities is available and is skipped
|
|
1738
|
+
honestly when none is, rather than being faked. The gate is applied at registration, not inside the
|
|
1739
|
+
real browser project: without a browser, each browser factory is replaced by a same-label
|
|
1740
|
+
Node/no-test placeholder, so generated `--project <label>` and `--project=<label>` filters still
|
|
1741
|
+
resolve while no browser code runs. The root permits an empty run only when every recognized exact
|
|
1742
|
+
project filter names one of those gated placeholders; an unreadable or mixed filter keeps the
|
|
1743
|
+
ordinary no-test failure semantics for its Node projects. One printed warning names every gated
|
|
1744
|
+
project label and says no Playwright Chromium, Chrome, or Edge was found. A machine with a browser
|
|
1726
1745
|
registers and runs the real browser suites unchanged; a machine without one runs the remaining
|
|
1727
1746
|
projects and says so.
|
|
1728
1747
|
|
|
@@ -1764,12 +1783,13 @@ each Codex agent's declared `sandbox_mode` is its mechanical permission floor, w
|
|
|
1764
1783
|
## The `scaffold` executable
|
|
1765
1784
|
|
|
1766
1785
|
The bin is a thin command-line shell over the two library faces. It exports nothing, so it carries
|
|
1767
|
-
no module API of its own.
|
|
1786
|
+
no module API of its own. Seven verbs:
|
|
1768
1787
|
|
|
1769
1788
|
| Verb | Purpose |
|
|
1770
1789
|
| --------- | -------------------------------------------------------- |
|
|
1771
1790
|
| `new` | scaffold a workspace into `./<name>` |
|
|
1772
1791
|
| `pull` | refresh vendored guides and versions, report drift |
|
|
1792
|
+
| `mirror` | refresh every published Orkestrel package guide |
|
|
1773
1793
|
| `audit` | whole-plan conformance report |
|
|
1774
1794
|
| `repair` | restore host-owned files and optional generated canon |
|
|
1775
1795
|
| `fleet` | audit or repair every workspace under the cwd's children |
|
|
@@ -1789,8 +1809,10 @@ extras so the workspace stays audit-clean.
|
|
|
1789
1809
|
repeatable and points at a local template or catalog source instead of the bundled one.
|
|
1790
1810
|
On `pull`, `--deps x,y` limits refresh to those declared Orkestrel dependencies; without it, every
|
|
1791
1811
|
declared dependency mirror is considered.
|
|
1812
|
+
`mirror` accepts no dependency selection: its exact npm organization discovery is the operation's
|
|
1813
|
+
scope, and it fetches guides without registry version or packument requests.
|
|
1792
1814
|
`--groups a,b` scopes an audit to artifact groups. `--live` adds an upstream freshness check to an
|
|
1793
|
-
audit. `--strict` makes a pull throw on a network fault. `--offline` restricts a catalog to local
|
|
1815
|
+
audit. `--strict` makes a pull or mirror throw on a network fault. `--offline` restricts a catalog to local
|
|
1794
1816
|
sources. `--prune` opts a repair or fleet run into deleting unexpected files under the three prune
|
|
1795
1817
|
directories. `--generated` opts a repair or fleet run into restoring generated canon except
|
|
1796
1818
|
`package.json`; on `audit`, it is inherited if the interactive repair hand-off is accepted.
|
|
@@ -2260,7 +2282,7 @@ isBehind(rangeToFreshness('^0.0.7', '0.0.9')) // true
|
|
|
2260
2282
|
|
|
2261
2283
|
packageShortName('@orkestrel/contract') // 'contract'
|
|
2262
2284
|
guideStub('guides/src/contract.md') // the local pointer content
|
|
2263
|
-
readGuideReferences('./packages/router', [
|
|
2285
|
+
readGuideReferences('./packages/router', ['@orkestrel/contract'])
|
|
2264
2286
|
syncReportOf('./packages/router', [], []) // { clean: true, failed: 0, … }
|
|
2265
2287
|
```
|
|
2266
2288
|
|
|
@@ -2325,9 +2347,24 @@ await sync.guides(deps)
|
|
|
2325
2347
|
await sync.versions(deps)
|
|
2326
2348
|
await sync.catalog()
|
|
2327
2349
|
|
|
2350
|
+
const mirror = await sync.mirror('.')
|
|
2351
|
+
if (mirror.failed === 0) await sync.write(mirror, '.')
|
|
2352
|
+
|
|
2328
2353
|
sync.destroy()
|
|
2329
2354
|
```
|
|
2330
2355
|
|
|
2356
|
+
Refresh the entire published guide mirror from an installed package:
|
|
2357
|
+
|
|
2358
|
+
```sh
|
|
2359
|
+
npx scaffold mirror --apply --yes
|
|
2360
|
+
```
|
|
2361
|
+
|
|
2362
|
+
Or from this checkout after building:
|
|
2363
|
+
|
|
2364
|
+
```sh
|
|
2365
|
+
node ./dist/bin/scaffold.js mirror --apply --yes
|
|
2366
|
+
```
|
|
2367
|
+
|
|
2331
2368
|
### Fleet discovery, prune scanning, and the local catalog
|
|
2332
2369
|
|
|
2333
2370
|
```ts
|
package/dist/src/core/index.cjs
CHANGED
|
@@ -227,7 +227,7 @@ var DEFAULT_VERSION = "0.0.1";
|
|
|
227
227
|
/** The `engines.node` range the `blueprint` builder fills. */
|
|
228
228
|
var DEFAULT_ENGINES = `>=${MINIMUM_NODE_VERSION}`;
|
|
229
229
|
/** The devDependency range generated packages pin `@orkestrel/scaffold` at. */
|
|
230
|
-
var SCAFFOLD_RANGE = "^0.0.
|
|
230
|
+
var SCAFFOLD_RANGE = "^0.0.17";
|
|
231
231
|
/** Tooling versions shared by scaffold and every generated workspace. */
|
|
232
232
|
var BASE_DEV_DEPENDENCIES = Object.freeze({
|
|
233
233
|
"@microsoft/api-extractor": "^7.58.12",
|
|
@@ -5873,7 +5873,7 @@ ${renderedProjects}
|
|
|
5873
5873
|
${fitsPrintWidth(inlineRegistrations) ? inlineRegistrations : ` [
|
|
5874
5874
|
${renderedRegistrations.map((registration) => ` ${registration},`).join("\n")}
|
|
5875
5875
|
],`}
|
|
5876
|
-
|
|
5876
|
+
browserOptions !== undefined,
|
|
5877
5877
|
process.argv,
|
|
5878
5878
|
),`;
|
|
5879
5879
|
}
|
|
@@ -5897,6 +5897,8 @@ ${renderedRegistrations.map((registration) => ` ${registration},`).join("\n")}
|
|
|
5897
5897
|
*/
|
|
5898
5898
|
function viteHeader(machinery) {
|
|
5899
5899
|
const { browser: needsBrowser, vue: needsVue, output: needsOutput } = machinery;
|
|
5900
|
+
const playwrightTypeImports = needsBrowser ? `import type { PlaywrightProviderOptions } from '@vitest/browser-playwright'
|
|
5901
|
+
` : "";
|
|
5900
5902
|
const playwrightImports = needsBrowser ? `import { playwright } from '@vitest/browser-playwright'
|
|
5901
5903
|
import { chromium } from 'playwright'
|
|
5902
5904
|
` : "";
|
|
@@ -6207,7 +6209,9 @@ import { parse as parseVue } from 'vue/compiler-sfc'
|
|
|
6207
6209
|
? undefined
|
|
6208
6210
|
: packageRootOf(packageName, physicalSource)
|
|
6209
6211
|
if (packageRoot === undefined || !containedPath(packageRoot, physicalSource)) {
|
|
6210
|
-
this.error(
|
|
6212
|
+
return this.error(
|
|
6213
|
+
'Resolved dependencies must remain inside their physical package root',
|
|
6214
|
+
)
|
|
6211
6215
|
}
|
|
6212
6216
|
trustedPackageRoots.add(packageRoot)
|
|
6213
6217
|
}
|
|
@@ -6215,7 +6219,7 @@ import { parse as parseVue } from 'vue/compiler-sfc'
|
|
|
6215
6219
|
}
|
|
6216
6220
|
const resolvedSource = workspacePath(physicalSource)
|
|
6217
6221
|
if (resolvedSource === undefined) {
|
|
6218
|
-
this.error('Environment modules cannot import files outside the workspace')
|
|
6222
|
+
return this.error('Environment modules cannot import files outside the workspace')
|
|
6219
6223
|
}
|
|
6220
6224
|
const assetError = environmentPathError(owner, resolvedSource)
|
|
6221
6225
|
if (assetError !== undefined) this.error(assetError)
|
|
@@ -7244,7 +7248,7 @@ ${needsVue ? ` configureServer(server) {
|
|
|
7244
7248
|
}
|
|
7245
7249
|
}
|
|
7246
7250
|
`;
|
|
7247
|
-
return `${viteTypeImports}
|
|
7251
|
+
return `${playwrightTypeImports}${viteTypeImports}
|
|
7248
7252
|
${needsBrowser ? `import { isCSSRequest, parseSync, preprocessCSS, transformWithOxc, Visitor } from 'vite'
|
|
7249
7253
|
` : `import { parseSync, transformWithOxc, Visitor } from 'vite'
|
|
7250
7254
|
`}import { defineConfig, mergeConfig } from 'vitest/config'
|
|
@@ -7252,7 +7256,7 @@ import tsconfig from './tsconfig.json' with { type: 'json' }
|
|
|
7252
7256
|
import { fileURLToPath, URL } from 'node:url'
|
|
7253
7257
|
import { isBuiltin } from 'node:module'
|
|
7254
7258
|
import {
|
|
7255
|
-
closeSync,
|
|
7259
|
+
${needsBrowser ? " accessSync,\n" : ""} closeSync,
|
|
7256
7260
|
constants as FS_CONSTANTS,
|
|
7257
7261
|
existsSync,
|
|
7258
7262
|
fstatSync,
|
|
@@ -7261,7 +7265,15 @@ import {
|
|
|
7261
7265
|
${needsBrowser ? " readdirSync,\n" : ""} readSync,
|
|
7262
7266
|
realpathSync,
|
|
7263
7267
|
${needsBrowser ? " statSync,\n" : ""}} from 'node:fs'
|
|
7264
|
-
|
|
7268
|
+
${needsBrowser ? `import {
|
|
7269
|
+
basename,
|
|
7270
|
+
join,
|
|
7271
|
+
dirname,
|
|
7272
|
+
isAbsolute,
|
|
7273
|
+
relative,
|
|
7274
|
+
resolve as resolvePath,
|
|
7275
|
+
sep,
|
|
7276
|
+
} from 'node:path'` : `import { dirname, isAbsolute, relative, resolve as resolvePath, sep } from 'node:path'`}
|
|
7265
7277
|
${playwrightImports}${vueImports}${needsBrowser ? `\n/** Chromium executable layouts inside a \`chromium-<revision>\` browsers-directory entry, per platform. */
|
|
7266
7278
|
${EXPORT_KEYWORD} ${CONST_KEYWORD} CHROMIUM_LAYOUTS = Object.freeze([
|
|
7267
7279
|
'chrome-linux/chrome',
|
|
@@ -7272,15 +7284,64 @@ ${EXPORT_KEYWORD} ${CONST_KEYWORD} CHROMIUM_LAYOUTS = Object.freeze([
|
|
|
7272
7284
|
'chrome-mac-arm64/Chromium.app/Contents/MacOS/Chromium',
|
|
7273
7285
|
])
|
|
7274
7286
|
|
|
7287
|
+
/** Stable Playwright Chromium channels and their standard executable layouts. */
|
|
7288
|
+
${EXPORT_KEYWORD} ${CONST_KEYWORD} SYSTEM_BROWSER_CHANNELS = Object.freeze([
|
|
7289
|
+
Object.freeze({
|
|
7290
|
+
channel: 'chrome',
|
|
7291
|
+
layouts: Object.freeze({
|
|
7292
|
+
linux: '/opt/google/chrome/chrome',
|
|
7293
|
+
darwin: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
7294
|
+
win32: Object.freeze(['Google', 'Chrome', 'Application', 'chrome.exe']),
|
|
7295
|
+
}),
|
|
7296
|
+
}),
|
|
7297
|
+
Object.freeze({
|
|
7298
|
+
channel: 'msedge',
|
|
7299
|
+
layouts: Object.freeze({
|
|
7300
|
+
linux: '/opt/microsoft/msedge/msedge',
|
|
7301
|
+
darwin: '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
|
|
7302
|
+
win32: Object.freeze(['Microsoft', 'Edge', 'Application', 'msedge.exe']),
|
|
7303
|
+
}),
|
|
7304
|
+
}),
|
|
7305
|
+
])
|
|
7306
|
+
|
|
7307
|
+
/**
|
|
7308
|
+
* Determine whether a path identifies an executable regular file.
|
|
7309
|
+
*
|
|
7310
|
+
* @param path - The filesystem path to inspect.
|
|
7311
|
+
* @returns Whether the path is a regular file with execute access.
|
|
7312
|
+
*
|
|
7313
|
+
* @example
|
|
7314
|
+
* \`\`\`ts
|
|
7315
|
+
* isBrowserExecutable('/opt/google/chrome/chrome')
|
|
7316
|
+
* \`\`\`
|
|
7317
|
+
*/
|
|
7318
|
+
${EXPORT_KEYWORD} function isBrowserExecutable(path: string): boolean {
|
|
7319
|
+
try {
|
|
7320
|
+
if (!statSync(path).isFile()) return false
|
|
7321
|
+
accessSync(path, FS_CONSTANTS.X_OK)
|
|
7322
|
+
return true
|
|
7323
|
+
} catch {
|
|
7324
|
+
return false
|
|
7325
|
+
}
|
|
7326
|
+
}
|
|
7327
|
+
|
|
7275
7328
|
/**
|
|
7276
|
-
* Resolve a launchable Chromium executable: the pinned revision when installed,
|
|
7329
|
+
* Resolve a launchable Playwright-managed Chromium executable: the pinned revision when installed,
|
|
7277
7330
|
* otherwise a \`chromium\` / \`chromium.exe\` alias or any other \`chromium-*\`
|
|
7278
7331
|
* revision under the same Playwright browsers directory. A pinned-revision miss
|
|
7279
7332
|
* is not Chromium absence — managed containers ship one usable build (often
|
|
7280
7333
|
* behind a revision-agnostic alias) for many Playwright versions.
|
|
7334
|
+
*
|
|
7335
|
+
* @param pinned - The executable path for Playwright's pinned Chromium revision.
|
|
7336
|
+
* @returns The managed executable path, or \`undefined\` when none is executable.
|
|
7337
|
+
*
|
|
7338
|
+
* @example
|
|
7339
|
+
* \`\`\`ts
|
|
7340
|
+
* resolveManagedBrowser(chromium.executablePath())
|
|
7341
|
+
* \`\`\`
|
|
7281
7342
|
*/
|
|
7282
|
-
${EXPORT_KEYWORD} function
|
|
7283
|
-
if (
|
|
7343
|
+
${EXPORT_KEYWORD} function resolveManagedBrowser(pinned: string): string | undefined {
|
|
7344
|
+
if (isBrowserExecutable(pinned)) return pinned
|
|
7284
7345
|
let revisionRoot = dirname(pinned)
|
|
7285
7346
|
for (;;) {
|
|
7286
7347
|
if (/^chromium-\\d+$/.test(basename(revisionRoot))) break
|
|
@@ -7291,7 +7352,7 @@ ${EXPORT_KEYWORD} function resolveChromium(pinned: string): string | undefined {
|
|
|
7291
7352
|
const browsersRoot = dirname(revisionRoot)
|
|
7292
7353
|
for (const alias of ['chromium', 'chromium.exe']) {
|
|
7293
7354
|
const candidate = resolvePath(browsersRoot, alias)
|
|
7294
|
-
if (
|
|
7355
|
+
if (isBrowserExecutable(candidate)) return candidate
|
|
7295
7356
|
}
|
|
7296
7357
|
let entries: readonly string[]
|
|
7297
7358
|
try {
|
|
@@ -7305,18 +7366,89 @@ ${EXPORT_KEYWORD} function resolveChromium(pinned: string): string | undefined {
|
|
|
7305
7366
|
for (const revision of revisions) {
|
|
7306
7367
|
for (const layout of CHROMIUM_LAYOUTS) {
|
|
7307
7368
|
const candidate = resolvePath(browsersRoot, revision, layout)
|
|
7308
|
-
if (
|
|
7369
|
+
if (isBrowserExecutable(candidate)) return candidate
|
|
7370
|
+
}
|
|
7371
|
+
}
|
|
7372
|
+
return undefined
|
|
7373
|
+
}
|
|
7374
|
+
|
|
7375
|
+
/**
|
|
7376
|
+
* Resolve the first installed stable system Chromium channel.
|
|
7377
|
+
*
|
|
7378
|
+
* @param platform - The Node platform whose standard layouts should be probed.
|
|
7379
|
+
* @param environment - The process environment supplying Windows installation roots.
|
|
7380
|
+
* @returns \`chrome\`, then \`msedge\`, or \`undefined\` when neither is executable.
|
|
7381
|
+
*
|
|
7382
|
+
* @example
|
|
7383
|
+
* \`\`\`ts
|
|
7384
|
+
* resolveSystemBrowser(process.platform, process.env)
|
|
7385
|
+
* \`\`\`
|
|
7386
|
+
*/
|
|
7387
|
+
${EXPORT_KEYWORD} function resolveSystemBrowser(
|
|
7388
|
+
platform: NodeJS.Platform,
|
|
7389
|
+
environment: NodeJS.ProcessEnv,
|
|
7390
|
+
): string | undefined {
|
|
7391
|
+
if (platform !== 'linux' && platform !== 'darwin' && platform !== 'win32') return undefined
|
|
7392
|
+
const roots = new Set<string>()
|
|
7393
|
+
if (platform === 'win32') {
|
|
7394
|
+
for (const root of [
|
|
7395
|
+
environment.LOCALAPPDATA,
|
|
7396
|
+
environment.PROGRAMFILES,
|
|
7397
|
+
environment['PROGRAMFILES(X86)'],
|
|
7398
|
+
]) {
|
|
7399
|
+
if (root !== undefined && root.length > 0) roots.add(root)
|
|
7400
|
+
}
|
|
7401
|
+
const homeDrive = environment.HOMEDRIVE
|
|
7402
|
+
if (homeDrive !== undefined && homeDrive.length > 0) {
|
|
7403
|
+
roots.add(join(homeDrive, 'Program Files'))
|
|
7404
|
+
roots.add(join(homeDrive, 'Program Files (x86)'))
|
|
7405
|
+
}
|
|
7406
|
+
}
|
|
7407
|
+
for (const browser of SYSTEM_BROWSER_CHANNELS) {
|
|
7408
|
+
if (platform === 'win32') {
|
|
7409
|
+
for (const root of roots) {
|
|
7410
|
+
if (isBrowserExecutable(join(root, ...browser.layouts.win32))) return browser.channel
|
|
7411
|
+
}
|
|
7412
|
+
continue
|
|
7309
7413
|
}
|
|
7414
|
+
if (isBrowserExecutable(browser.layouts[platform])) return browser.channel
|
|
7310
7415
|
}
|
|
7311
7416
|
return undefined
|
|
7312
7417
|
}
|
|
7313
7418
|
|
|
7314
|
-
|
|
7315
|
-
|
|
7316
|
-
|
|
7317
|
-
|
|
7318
|
-
|
|
7319
|
-
|
|
7419
|
+
/**
|
|
7420
|
+
* Resolve launch options for a managed Chromium or stable system browser.
|
|
7421
|
+
*
|
|
7422
|
+
* @param pinned - The executable path for Playwright's pinned Chromium revision.
|
|
7423
|
+
* @param platform - The Node platform whose standard system layouts should be probed.
|
|
7424
|
+
* @param environment - The process environment supplying Windows installation roots.
|
|
7425
|
+
* @returns Provider options for managed Chromium, Chrome, or Edge, or \`undefined\`.
|
|
7426
|
+
*
|
|
7427
|
+
* @remarks
|
|
7428
|
+
* An installed pinned revision returns an empty object so Playwright retains
|
|
7429
|
+
* its default launch semantics. A different managed executable is selected by
|
|
7430
|
+
* path; a system browser is selected by its stable Playwright channel.
|
|
7431
|
+
*
|
|
7432
|
+
* @example
|
|
7433
|
+
* \`\`\`ts
|
|
7434
|
+
* resolveBrowser(chromium.executablePath(), process.platform, process.env)
|
|
7435
|
+
* \`\`\`
|
|
7436
|
+
*/
|
|
7437
|
+
${EXPORT_KEYWORD} function resolveBrowser(
|
|
7438
|
+
pinned: string,
|
|
7439
|
+
platform: NodeJS.Platform,
|
|
7440
|
+
environment: NodeJS.ProcessEnv,
|
|
7441
|
+
): PlaywrightProviderOptions | undefined {
|
|
7442
|
+
const managed = resolveManagedBrowser(pinned)
|
|
7443
|
+
if (managed !== undefined) {
|
|
7444
|
+
return managed === pinned ? {} : { launchOptions: { executablePath: managed } }
|
|
7445
|
+
}
|
|
7446
|
+
const channel = resolveSystemBrowser(platform, environment)
|
|
7447
|
+
return channel === undefined ? undefined : { launchOptions: { channel } }
|
|
7448
|
+
}
|
|
7449
|
+
|
|
7450
|
+
${CONST_KEYWORD} browserPinned = chromium.executablePath()
|
|
7451
|
+
${CONST_KEYWORD} browserOptions = resolveBrowser(browserPinned, process.platform, process.env)
|
|
7320
7452
|
` : ""}
|
|
7321
7453
|
${EXPORT_KEYWORD} function resolveWorkspacePath(relativePath: string): string {
|
|
7322
7454
|
return fileURLToPath(new URL(relativePath, import.meta.url))
|
|
@@ -7365,7 +7497,9 @@ ${needsBrowser ? `${EXPORT_KEYWORD} function gateBrowserProjects(
|
|
|
7365
7497
|
projects.push(registration.project())
|
|
7366
7498
|
}
|
|
7367
7499
|
if (gated.length === 0) return { projects }
|
|
7368
|
-
console.warn(
|
|
7500
|
+
console.warn(
|
|
7501
|
+
\`browser projects skipped: no Playwright Chromium, Chrome, or Edge found (\${gated.join(', ')})\`,
|
|
7502
|
+
)
|
|
7369
7503
|
const filters: string[] = []
|
|
7370
7504
|
let readable = true
|
|
7371
7505
|
for (let index = 0; index < argv.length; index += 1) {
|
|
@@ -7656,7 +7790,7 @@ ${EXPORT_KEYWORD} const srcBrowser = (config?: UserConfig): UserConfig =>
|
|
|
7656
7790
|
}),
|
|
7657
7791
|
browser: {
|
|
7658
7792
|
enabled: true,
|
|
7659
|
-
provider: playwright(
|
|
7793
|
+
provider: playwright(browserOptions),
|
|
7660
7794
|
instances: [{ browser: 'chromium', headless: true }],
|
|
7661
7795
|
},
|
|
7662
7796
|
fileParallelism: false,
|
|
@@ -7788,7 +7922,7 @@ ${EXPORT_KEYWORD} const srcBrowser = (config?: UserConfig): UserConfig =>
|
|
|
7788
7922
|
}),
|
|
7789
7923
|
browser: {
|
|
7790
7924
|
enabled: true,
|
|
7791
|
-
provider: playwright(
|
|
7925
|
+
provider: playwright(browserOptions),
|
|
7792
7926
|
instances: [{ browser: 'chromium', headless: true }],
|
|
7793
7927
|
},
|
|
7794
7928
|
fileParallelism: false,
|
|
@@ -7953,7 +8087,7 @@ ${EXPORT_KEYWORD} const srcBrowser = (config?: UserConfig): UserConfig =>
|
|
|
7953
8087
|
}),
|
|
7954
8088
|
browser: {
|
|
7955
8089
|
enabled: true,
|
|
7956
|
-
provider: playwright(
|
|
8090
|
+
provider: playwright(browserOptions),
|
|
7957
8091
|
instances: [{ browser: 'chromium', headless: true }],
|
|
7958
8092
|
},
|
|
7959
8093
|
fileParallelism: false,
|
|
@@ -8082,7 +8216,7 @@ ${EXPORT_KEYWORD} function appBrowser(...config: readonly never[]): UserConfig {
|
|
|
8082
8216
|
},
|
|
8083
8217
|
browser: {
|
|
8084
8218
|
enabled: true,
|
|
8085
|
-
provider: playwright(
|
|
8219
|
+
provider: playwright(browserOptions),
|
|
8086
8220
|
instances: [{ browser: 'chromium', headless: true }],
|
|
8087
8221
|
},
|
|
8088
8222
|
fileParallelism: false,
|
|
@@ -8649,8 +8783,7 @@ function paritySpecifiers(spec) {
|
|
|
8649
8783
|
function testArtifacts(spec, pascal) {
|
|
8650
8784
|
const hasBrowser = spec.src.includes("browser") || spec.app.includes("browser");
|
|
8651
8785
|
const hasVue = spec.app.includes("browser");
|
|
8652
|
-
const
|
|
8653
|
-
const browserPolicyImport = hasBrowser ? "\nimport { chromium } from 'playwright'\nimport { resolveChromium } from '../vite.config.js'" : "";
|
|
8786
|
+
const browserPolicyImport = hasBrowser ? "\nimport { chromium } from 'playwright'\nimport { isBrowserExecutable, resolveBrowser, SYSTEM_BROWSER_CHANNELS } from '../vite.config.js'" : "";
|
|
8654
8787
|
const vuePolicyImport = hasVue ? "\nimport { parse as parseVue } from 'vue/compiler-sfc'" : "";
|
|
8655
8788
|
const workspacePolicyAssertion = hasVue ? `expect(
|
|
8656
8789
|
inspectCodingWorkspace(process.cwd(), (path, content) => {
|
|
@@ -8663,15 +8796,20 @@ function testArtifacts(spec, pascal) {
|
|
|
8663
8796
|
).toEqual([])` : "expect(inspectCodingWorkspace(process.cwd())).toEqual([])";
|
|
8664
8797
|
const browserPolicyTest = hasBrowser ? `
|
|
8665
8798
|
|
|
8666
|
-
it('resolves
|
|
8667
|
-
const
|
|
8668
|
-
|
|
8669
|
-
|
|
8670
|
-
|
|
8671
|
-
|
|
8799
|
+
it('resolves only a real managed executable or stable system browser channel', () => {
|
|
8800
|
+
const options = resolveBrowser(chromium.executablePath(), process.platform, process.env)
|
|
8801
|
+
let valid = options === undefined
|
|
8802
|
+
if (options !== undefined) {
|
|
8803
|
+
const channel = options.launchOptions?.channel
|
|
8804
|
+
valid =
|
|
8805
|
+
channel === undefined
|
|
8806
|
+
? isBrowserExecutable(options.launchOptions?.executablePath ?? chromium.executablePath())
|
|
8807
|
+
: SYSTEM_BROWSER_CHANNELS.some((browser) => browser.channel === channel)
|
|
8808
|
+
}
|
|
8809
|
+
expect(valid).toBe(true)
|
|
8672
8810
|
})` : "";
|
|
8673
8811
|
const artifacts = [fillArtifact("tests/setup.ts", "tests", "setup", {}), fillArtifact("tests/policy.test.ts", "tests", "policyTest", {
|
|
8674
|
-
browserPolicySpecifier,
|
|
8812
|
+
browserPolicySpecifier: "",
|
|
8675
8813
|
browserPolicyImport,
|
|
8676
8814
|
browserPolicyTest,
|
|
8677
8815
|
vuePolicyImport,
|