@orkestrel/scaffold 0.0.92 → 0.0.94

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.
@@ -361,7 +361,7 @@ These invariants hold across `src/core` / `src/browser` / `src/server` ↔
361
361
  stated earlier.
362
362
  22. **Streaming both ways.** `buildRequest` streams a body-carrying method's
363
363
  message into the `Request` through a manual `ReadableStream` pump — a `for
364
- await` loop over the `IncomingMessage` enqueueing each chunk, with
364
+ await` loop over the `IncomingMessage` enqueueing each chunk, with
365
365
  `duplex: 'half'` set as Node's fetch implementation requires for a
366
366
  streamed request body; `sendResponse` streams a non-`null` `Response`
367
367
  body back to the `ServerResponse` chunk by chunk, ending the target when
@@ -2098,8 +2098,13 @@ Nothing is fixed except the manifest.
2098
2098
  once into `browserOptions` and passes that to every `playwright()` provider it configures. The
2099
2099
  precedence is `PLAYWRIGHT_EXECUTABLE_PATH`, `PLAYWRIGHT_WS_ENDPOINT`, `PLAYWRIGHT_CHANNEL`, the
2100
2100
  managed Playwright Chromium, the container's bundled Chromium, a verified system channel, then the
2101
- platform default. An installed pinned revision returns empty options, so Playwright keeps its own
2102
- launch defaults. A pinned revision that is not installed falls through to a `chromium` alias or a
2101
+ platform default. Set `PLAYWRIGHT_SCROLLBARS=classic` only for runs that measure scrollbar
2102
+ compensation, never for a standing gate. Every Chromium launch in that run omits Playwright's
2103
+ `--hide-scrollbars` default argument so classic scrollbars can take layout space in every case.
2104
+ An empty or unset value preserves the launch defaults; any other value throws an `Error`.
2105
+ Pairing `classic` with `PLAYWRIGHT_WS_ENDPOINT` throws an `Error`, even with an executable
2106
+ override, because the server owns the launch. An installed pinned revision returns empty options
2107
+ unless `classic` is set. A pinned revision that is not installed falls through to a `chromium` alias or a
2103
2108
  sibling `chromium-*` revision under the same browsers directory, because a managed container ships
2104
2109
  one usable build for many Playwright versions. It is its own file rather than a block in the
2105
2110
  vendored `configs/helpers.ts`, which every workspace receives byte-identical while only a browser
@@ -164,10 +164,14 @@ These invariants hold across `src/core` ↔ `worker.md`:
164
164
  `null` reaches Queue or Pool validation. Queue's integer timeout contract is preserved:
165
165
  `timeout` must be in `0..2_147_483_647` milliseconds, and `0` disables the deadline.
166
166
  Queue is constructed successfully before the
167
- pool option is read, and every declared pool member (`max`, `on`, `error`, `create`,
168
- `destroy`, `validate`) is then captured once by direct access, preserving inherited and
169
- non-enumerable structural options. Pool `max` still defaults to concurrency, so resources
170
- match the jobs in flight by default. `stop` / `abort` / `clear` return the
167
+ pool option is read, and every declared pool member (`max`, `min`, `restarts`, `watch`,
168
+ `on`, `error`, `create`, `destroy`, `validate`) is then captured once by direct access,
169
+ preserving inherited and non-enumerable structural options. Pool validates the forwarded
170
+ members. Pool `max` defaults to concurrency only when both `max` and `min` are absent.
171
+ With `min`, Pool defaults `max` to `min` and refuses unequal explicit values or a missing
172
+ `restarts` bound. The worker starts the pool at construction, so a floor warms before
173
+ work arrives and can exceed queue concurrency. A spent floor's startup rejection is
174
+ observed internally; acquire reports the pool failure to jobs. `stop` / `abort` / `clear` return the
171
175
  queue's exact cleanup barriers. `destroy` installs one stable barrier before it invokes
172
176
  queue teardown (including synchronous abort-event reentry), awaits queue then pool
173
177
  settlement serially, aggregates the queue's failure and the pool's in that order, and
@@ -195,7 +199,7 @@ These invariants hold across `src/core` ↔ `worker.md`:
195
199
  `TResult` infer from the `input` and `result` guards, so a call site needs no type
196
200
  argument. The structured-clone boundary is crossed with no `as`: a `Dispatch` narrows each reply
197
201
  value through `options.result` (a value that fails it rejects with `'reply did not
198
- satisfy result guard'`), and the worker side narrows each payload through
202
+ satisfy result guard'`), and the worker side narrows each payload through
199
203
  `options.input` (a bad input replies `'input did not satisfy input guard'`) —
200
204
  `TInput` / `TResult` are reconstructed by validation, never asserted. A throwing
201
205
  caller guard is contained and rejects only that job; the pooled worker remains usable.
@@ -383,6 +387,33 @@ See [queue.md](queue.md) / [pool.md](pool.md) for the underlying
383
387
  listener's throw never prevents a sibling listener, and the throw reaches the emitter's
384
388
  `error` handler, so a buggy worker observer leaves the inner queue and pool intact.
385
389
 
390
+ ## Pool options
391
+
392
+ The `pool` option takes Pool's resource options and refuses the `capacity` option, so each
393
+ resource serves one job at a time. Pool owns validation of the forwarded options:
394
+
395
+ | Member | Behavior |
396
+ | ---------- | ---------------------------------------------------------------------------------------------------- |
397
+ | `create` | Creates a resource on demand or while filling a configured floor. |
398
+ | `destroy` | Disposes each resource during pool cleanup or loss. |
399
+ | `validate` | Checks an idle resource before reuse. |
400
+ | `max` | Caps pool capacity; defaults to `concurrency` only when `min` is absent. |
401
+ | `min` | Sets a positive safe integer warm floor; Pool defaults `max` to it and requires equality. |
402
+ | `restarts` | Sets the required non-negative safe integer refill bound with `min`; invalid without `min`. |
403
+ | `watch` | Observes each created resource; settlement declares loss while live, and disposal aborts its signal. |
404
+ | `on` | Installs initial pool lifecycle listeners. |
405
+ | `error` | Receives pool listener failures and live watch rejections or throws. |
406
+
407
+ Pool `start()` fills the floor when the worker is constructed. Without `min`, resources remain
408
+ lazy. A `restarts: 1` bound permits another create after the first failure and spends the
409
+ floor after the second failure. Jobs then receive Pool's `create` error with the last cause.
410
+ A resource lost while idle adds a strike even if it served a job before its last release.
411
+ With `min: 1` and `restarts: 0`, that loss spends the floor, and the next job rejects with
412
+ Pool's `create` error without creating a replacement.
413
+ Worker `start()` restarts queue loops only; it does not reset a spent pool's restart bound.
414
+ See [Pool](pool.md), under “Warm floor and loss”, for refill, retained
415
+ cleanup failures, and watch listener cleanup.
416
+
386
417
  ## Patterns
387
418
 
388
419
  ### A resource-backed worker
@@ -479,8 +510,9 @@ Follow these practices when you run a worker in production:
479
510
  - **Honour `context.signal`** — pass it through to the resource's operation and bail
480
511
  out when it fires, so timeouts and aborts actually stop work rather than abandoning
481
512
  its result.
482
- - **Size the pool through `concurrency`** — the pool's `max` defaults to it; override
483
- `pool.max` explicitly only when the resource cap must diverge from the job cap.
513
+ - **Size the pool through `concurrency`** — when neither `pool.max` nor `pool.min` is given,
514
+ the pool's `max` defaults to it. Set `pool.max` for a separate lazy resource cap, or
515
+ `pool.min` with `pool.restarts` for a warm floor independent of queue concurrency.
484
516
  Concurrency must be a positive safe integer; invalid values are rejected by Queue and
485
517
  are never normalized.
486
518
  - **`abort` is terminal** — a worker-level abort cancels in-flight work and stops the
@@ -524,6 +556,10 @@ These tests pin the behaviour this guide documents:
524
556
  returns the queue's cleanup barriers; constructor options and every declared pool option are
525
557
  captured once (including inherited / non-enumerable structural options), only explicit
526
558
  `undefined` defaults, runtime `null` reaches the Queue/Pool diagnostic,
559
+ the pool option type excludes `capacity`, `min` warms before work with Pool's `max` default,
560
+ `restarts` bounds failed creates and loss of a previously used idle resource,
561
+ `watch` receives each resource and an abort signal before disposal, conflicting `min`
562
+ and `max` values refuse at construction with Pool's error,
527
563
  and invalid Queue concurrency wins before the pool option is read; strict concurrency
528
564
  rejects zero, negative, fractional, `NaN`, and infinite values instead of normalizing
529
565
  them; `destroy` keeps one
@@ -34,7 +34,7 @@
34
34
  "storage": "agents/skills/enterprise-bootstrap/references/bootstrap-reference.md",
35
35
  "destination": ".agents/skills/enterprise-bootstrap/references/bootstrap-reference.md",
36
36
  "executable": false,
37
- "digest": "08336f02280eef5c7266052bc3cbe4d85c4268eb82dbb198b680940dd62d83ed"
37
+ "digest": "5fe9a1fc675fa566e44816cedf0a8421f387a5276409000ea20c98c876e3ac80"
38
38
  },
39
39
  {
40
40
  "storage": "agents/skills/enterprise-bootstrap/references/color-modes.md",
@@ -70,13 +70,13 @@
70
70
  "storage": "agents/skills/enterprise-bootstrap/references/responsive-layout.md",
71
71
  "destination": ".agents/skills/enterprise-bootstrap/references/responsive-layout.md",
72
72
  "executable": false,
73
- "digest": "44e709e8df187495e6343048891ca9ff1ff9d3f30d86e00ad8cbd97c8e29bf20"
73
+ "digest": "f401dd3aa97b1098a6d97c8862bc7be7713a8a98ae1546a918abb66ee25cd8ad"
74
74
  },
75
75
  {
76
76
  "storage": "agents/skills/enterprise-bootstrap/references/utilities.md",
77
77
  "destination": ".agents/skills/enterprise-bootstrap/references/utilities.md",
78
78
  "executable": false,
79
- "digest": "8cec05a193672bc543439a3aa46cf7b4ffc5573527b3fe047ad575fbe73f4a2c"
79
+ "digest": "84a7c3eec5d280ba67b103418a69b4643434b9fa9fe39ee8c1b305f1ba4f94c0"
80
80
  },
81
81
  {
82
82
  "storage": "agents/skills/orkestrel-align/SKILL.md",
@@ -322,7 +322,7 @@
322
322
  "storage": "agents/skills/orkestrel-journey/references/statechart.md",
323
323
  "destination": ".agents/skills/orkestrel-journey/references/statechart.md",
324
324
  "executable": false,
325
- "digest": "89e53c15a8f02dfa335611b45c39d05cc3509e390156d6e569582227f0487802"
325
+ "digest": "b5866b5a756e2b42feb24e7ea2bdcb7c2c0ecc8d9863488263aeddffb63a44da"
326
326
  },
327
327
  {
328
328
  "storage": "agents/skills/orkestrel-journey/references/styles.md",
@@ -496,7 +496,7 @@
496
496
  "storage": "claude/agents/orkestrel.md",
497
497
  "destination": ".claude/agents/orkestrel.md",
498
498
  "executable": false,
499
- "digest": "36afc2242e9becbb7662aeece1b0f81a12162aacfd360a48a07a3ec7eaf7d639"
499
+ "digest": "7c081ea4deffe54305f4a43685adbde2a5941d4f69c3ed386690ab409372a638"
500
500
  },
501
501
  {
502
502
  "storage": "claude/agents/planner.md",
@@ -580,7 +580,7 @@
580
580
  "storage": "claude/rules/styles.md",
581
581
  "destination": ".claude/rules/styles.md",
582
582
  "executable": false,
583
- "digest": "ae1c97f23d71c53ef117b2cd661df6258712988ec86de6c60a1cbad4670eb996"
583
+ "digest": "156572b7c26740411e64726255f76e0f6e6e4e55e800a26262a923210866eb77"
584
584
  },
585
585
  {
586
586
  "storage": "claude/rules/tests.md",
@@ -826,7 +826,7 @@
826
826
  "storage": "dotfiles/oxlintrc.json",
827
827
  "destination": ".oxlintrc.json",
828
828
  "executable": false,
829
- "digest": "043a5c3eb0fb52d6cd841590e77c94ce03797b4b6a742fd57127bc9b9242153c"
829
+ "digest": "c3777d98a2c48a4007397ef90912b2530d7e40bc4d2f85155830e6d1135dc241"
830
830
  },
831
831
  {
832
832
  "storage": "dotfiles/prettierignore",
@@ -862,7 +862,7 @@
862
862
  "storage": "guides/browser.md",
863
863
  "destination": "guides/browser.md",
864
864
  "executable": false,
865
- "digest": "deec731bfc8733f880021cbf9a8cf450e51a93bc23f2f963fb3e1bf750554ba8"
865
+ "digest": "dfeb5feef6751bac487cf0205736a6763995b259ef1ee9bee8a606bb12f50e33"
866
866
  },
867
867
  {
868
868
  "storage": "guides/budget.md",
@@ -952,7 +952,7 @@
952
952
  "storage": "guides/mcp.md",
953
953
  "destination": "guides/mcp.md",
954
954
  "executable": false,
955
- "digest": "8cc580a9819d11771b585e8a915fcc6b7bd3ff4a575b6c6482cd76c501367664"
955
+ "digest": "d7363cdbde8cde24503915b0fdc78e30543e3b81e68e105500dfda3ffee7702e"
956
956
  },
957
957
  {
958
958
  "storage": "guides/middleware.md",
@@ -982,13 +982,13 @@
982
982
  "storage": "guides/pool.md",
983
983
  "destination": "guides/pool.md",
984
984
  "executable": false,
985
- "digest": "c49d3d2d3d5a2dc4518a8cebb94c82ba2dbcdc03222d5ad3885df13c46dfe8e2"
985
+ "digest": "a8bbdd91006afeb577d9e43a3f04ffe37cae989db954058b2e67197a3ca62572"
986
986
  },
987
987
  {
988
988
  "storage": "guides/probe.md",
989
989
  "destination": "guides/probe.md",
990
990
  "executable": false,
991
- "digest": "8082bfb642ba16d64e06aba0e1c5d0774ecebd77443d500821e4482bb0916cbe"
991
+ "digest": "11ddbf59ab6dd29e24f25ad1076dd6976b6e3bcdda1d95cb5186871b082ce560"
992
992
  },
993
993
  {
994
994
  "storage": "guides/process.md",
@@ -1036,13 +1036,13 @@
1036
1036
  "storage": "guides/router.md",
1037
1037
  "destination": "guides/router.md",
1038
1038
  "executable": false,
1039
- "digest": "73c88d2692d756c7f902424e75ba45c09c64c3ba78bdad5e800c6abe18551f26"
1039
+ "digest": "890c46d9749875a48f5ec1981e9a8da90c93ad961b2ed092549172d83546f4e9"
1040
1040
  },
1041
1041
  {
1042
1042
  "storage": "guides/scaffold.md",
1043
1043
  "destination": "guides/scaffold.md",
1044
1044
  "executable": false,
1045
- "digest": "02922fe230a3d85e58ad16aa9e3b2e93afd42e9d3d66b88f60bbd68b1b90e19e"
1045
+ "digest": "a5c97efa111d3fe4832380638c025b9cd4f536819b595753c463d7ef2f094889"
1046
1046
  },
1047
1047
  {
1048
1048
  "storage": "guides/sea.md",
@@ -1126,7 +1126,7 @@
1126
1126
  "storage": "guides/worker.md",
1127
1127
  "destination": "guides/worker.md",
1128
1128
  "executable": false,
1129
- "digest": "b72964712b7187a987e4ba08bec95d72487c4afeb6e4c43c631d14b4c3cbff63"
1129
+ "digest": "441c0c0a3a5b425fb91034d953e2e6a1d05d3dcb29702c600a43e048bac24afd"
1130
1130
  },
1131
1131
  {
1132
1132
  "storage": "guides/workflow.md",
@@ -1168,7 +1168,7 @@
1168
1168
  "storage": "tests/config.test.ts",
1169
1169
  "destination": "tests/config.test.ts",
1170
1170
  "executable": false,
1171
- "digest": "ac12462cfc786b77c23e8996c0a050f9719d870db08abe7f3a8994cbbf778e2d"
1171
+ "digest": "6d5079e1eed5b45c74ef788c966527e336444f32dc15afde19b7ae360e046854"
1172
1172
  },
1173
1173
  {
1174
1174
  "storage": "tests/policy.test.ts",
@@ -2144,5 +2144,5 @@
2144
2144
  ]
2145
2145
  }
2146
2146
  ],
2147
- "digest": "09b047e9598599db3dca2e26b55b897293e600defd7c35147dbab8a771816c0b"
2147
+ "digest": "ffc221eacdfb7e9c96f1374ebb04666839c32a4fe53d02979f8cae0a0e2b4e7b"
2148
2148
  }
File without changes
File without changes
File without changes
File without changes
@@ -2595,7 +2595,7 @@ createSomething({
2595
2595
  const pattern = readConfigRecord(patternValue)
2596
2596
  const message = pattern.message
2597
2597
  if (typeof message !== 'string') throw new Error('Missing pattern message')
2598
- const sources: ReadonlyArray<readonly [string, boolean]> = message.includes('URL schemes')
2598
+ let sources: ReadonlyArray<readonly [string, boolean]> = message.includes('URL schemes')
2599
2599
  ? [
2600
2600
  ['https://host/x', true],
2601
2601
  ['data:text/plain,x', true],
@@ -2661,6 +2661,24 @@ createSomething({
2661
2661
  true,
2662
2662
  ],
2663
2663
  ]
2664
+ if (message.includes('another styles face')) {
2665
+ const face =
2666
+ typeof pattern.regex === 'string'
2667
+ ? /@src\/(bootstrap|tailwindcss|styles)\(/u.exec(pattern.regex)?.[1]
2668
+ : undefined
2669
+ if (face === undefined) throw new Error('Missing styles face in restriction pattern')
2670
+ sources = [
2671
+ [`@src/${face}`, true],
2672
+ [`@src/${face}?raw`, true],
2673
+ [`@orkestrel/x/${face}`, true],
2674
+ [`../${face}/index.js`, true],
2675
+ [`../src/${face}/index.js`, true],
2676
+ [`../../src/${face}/index.js`, true],
2677
+ ['@src/browser', false],
2678
+ [`@src/${face}x`, false],
2679
+ [`../${face}.ext/index.js`, false],
2680
+ ]
2681
+ }
2664
2682
  const owner =
2665
2683
  Array.isArray(block.files) && typeof block.files[0] === 'string'
2666
2684
  ? block.files[0].split('/**')[0]
@@ -2730,6 +2748,9 @@ createSomething({
2730
2748
  'src/vue',
2731
2749
  'app/vue',
2732
2750
  'src/browser',
2751
+ 'src/bootstrap',
2752
+ 'src/tailwindcss',
2753
+ 'src/styles',
2733
2754
  'app/browser',
2734
2755
  'src/core',
2735
2756
  'app/core',
@@ -2737,7 +2758,7 @@ createSomething({
2737
2758
  'app/server',
2738
2759
  'src/bin',
2739
2760
  ]) {
2740
- const refused = owner.endsWith('/vue')
2761
+ const refused: Array<string | readonly [string, string]> = owner.endsWith('/vue')
2741
2762
  ? [
2742
2763
  'node:fs',
2743
2764
  '@src/server',
@@ -2773,6 +2794,24 @@ createSomething({
2773
2794
  : []),
2774
2795
  ]
2775
2796
  : ['@src/core']
2797
+ if (owner === 'src/core') refused.push('@src/browser', './index.css')
2798
+ if (['src/bootstrap', 'src/tailwindcss', 'src/styles'].includes(owner)) {
2799
+ refused.push('node:fs', '@src/server', '../server/index.js', '@app/core')
2800
+ for (const face of ['bootstrap', 'tailwindcss', 'styles']) {
2801
+ if (owner === `src/${face}`) continue
2802
+ refused.push(
2803
+ `@src/${face}`,
2804
+ `@orkestrel/x/${face}`,
2805
+ `../${face}/index.js`,
2806
+ `../src/${face}/index.js`,
2807
+ `../../src/${face}/index.js`,
2808
+ ['export * from', `@src/${face}`],
2809
+ ['import type * as boundary from', `@src/${face}`],
2810
+ )
2811
+ admitted.push(`@src/${face}x`, `../${face}.ext/index.js`)
2812
+ }
2813
+ admitted.push('@src/browser', './sheet.js', '@orkestrel/contract')
2814
+ }
2776
2815
  for (const [sources, refusal] of [
2777
2816
  [
2778
2817
  [
@@ -2799,7 +2838,11 @@ createSomething({
2799
2838
  mkdirSync(dirname(resolve(scratch, file)), { recursive: true })
2800
2839
  writeFileSync(
2801
2840
  resolve(scratch, file),
2802
- 'import * as boundary from ' + JSON.stringify(source) + '\nvoid boundary\ndebugger\n',
2841
+ typeof source === 'string'
2842
+ ? 'import * as boundary from ' +
2843
+ JSON.stringify(source) +
2844
+ '\nvoid boundary\ndebugger\n'
2845
+ : source[0] + ' ' + JSON.stringify(source[1]) + '\ndebugger\n',
2803
2846
  )
2804
2847
  expected.set(file, refusal)
2805
2848
  }
@@ -4,7 +4,7 @@ let _orkestrel_template = require("@orkestrel/template");
4
4
  let _orkestrel_emitter = require("@orkestrel/emitter");
5
5
  var package_default = {
6
6
  name: "@orkestrel/scaffold",
7
- version: "0.0.92",
7
+ version: "0.0.94",
8
8
  description: "Scaffold workspaces with commands: new, audit, repair, catalog, and overwrite.",
9
9
  keywords: [
10
10
  "audit",
@@ -103,16 +103,16 @@ var package_default = {
103
103
  "@orkestrel/template": "^0.0.9"
104
104
  },
105
105
  devDependencies: {
106
- "@microsoft/api-extractor": "^7.59.3",
107
- "@orkestrel/browser": "^0.0.24",
106
+ "@microsoft/api-extractor": "^7.59.4",
107
+ "@orkestrel/browser": "^0.0.26",
108
108
  "@orkestrel/guide": "^0.0.24",
109
109
  "@orkestrel/html": "^0.0.12",
110
- "@orkestrel/probe": "^0.0.20",
110
+ "@orkestrel/probe": "^0.0.21",
111
111
  "@orkestrel/test": "^0.0.24",
112
112
  "@types/node": "^26.6.4",
113
113
  "@vitest/browser-playwright": "^4.1.11",
114
- "oxfmt": "^0.71.0",
115
- "oxlint": "^1.86.0",
114
+ "oxfmt": "^0.72.0",
115
+ "oxlint": "^1.87.0",
116
116
  "playwright": "^1.63.0",
117
117
  "typescript": "^6.0.3",
118
118
  "vite": "^8.3.2",
@@ -2171,11 +2171,18 @@ export function resolveSystemBrowser(
2171
2171
  * discovery and is returned exactly as given: none of those environment values is checked
2172
2172
  * against the filesystem, because verifying an override would defeat the override. The pinned
2173
2173
  * managed revision outranks anything found on the host because it is deterministic. The installed
2174
- * pinned revision returns empty options so Playwright keeps its own default launch semantics. Only
2175
- * a discovered system channel is verified before it is named. The platform default is unverified
2174
+ * pinned revision returns empty options unless \`PLAYWRIGHT_SCROLLBARS=classic\` is set. Only a
2175
+ * discovered system channel is verified before it is named. The platform default is unverified
2176
2176
  * as well and exists only as a last resort: Windows takes \`msedge\`, which ships with the OS and
2177
2177
  * never collides with a foreground Chrome.
2178
2178
  *
2179
+ * Set \`PLAYWRIGHT_SCROLLBARS=classic\` only for runs that measure scrollbar compensation, never
2180
+ * for a standing gate. Every Chromium launch in that run omits Playwright's \`--hide-scrollbars\`
2181
+ * default argument so classic scrollbars can take layout space in every case. An empty or unset
2182
+ * value preserves the launch defaults; any other value throws an \`Error\`. Pairing \`classic\`
2183
+ * with \`PLAYWRIGHT_WS_ENDPOINT\` throws an \`Error\`, even with an executable override, because the
2184
+ * server owns the launch.
2185
+ *
2179
2186
  * The resolver and the gate cover Chromium alone. Reopen engine selection when another
2180
2187
  * Playwright engine is installed and launches on the host with a \`captureFrame\` reading
2181
2188
  * back at its declared size, or when a journey or style divergence is recorded.
@@ -2191,26 +2198,38 @@ export function resolveBrowser(
2191
2198
  environment: NodeJS.ProcessEnv,
2192
2199
  root: string = BUNDLED_BROWSERS_ROOT,
2193
2200
  ): PlaywrightProviderOptions {
2201
+ const scrollbars = environment.PLAYWRIGHT_SCROLLBARS
2202
+ if (scrollbars !== undefined && scrollbars.length > 0 && scrollbars !== 'classic') {
2203
+ throw new Error('PLAYWRIGHT_SCROLLBARS must be classic or empty')
2204
+ }
2205
+ const endpoint = environment.PLAYWRIGHT_WS_ENDPOINT
2206
+ if (scrollbars === 'classic' && endpoint !== undefined && endpoint.length > 0) {
2207
+ throw new Error('PLAYWRIGHT_SCROLLBARS=classic cannot be used with PLAYWRIGHT_WS_ENDPOINT')
2208
+ }
2209
+ const launch: PlaywrightProviderOptions['launchOptions'] =
2210
+ scrollbars === 'classic' ? { ignoreDefaultArgs: ['--hide-scrollbars'] } : {}
2194
2211
  const executable = environment.PLAYWRIGHT_EXECUTABLE_PATH
2195
2212
  if (executable !== undefined && executable.length > 0) {
2196
- return { launchOptions: { executablePath: executable } }
2213
+ return { launchOptions: { executablePath: executable, ...launch } }
2197
2214
  }
2198
- const endpoint = environment.PLAYWRIGHT_WS_ENDPOINT
2199
2215
  if (endpoint !== undefined && endpoint.length > 0) {
2200
2216
  return { connectOptions: { wsEndpoint: endpoint } }
2201
2217
  }
2202
2218
  const requested = environment.PLAYWRIGHT_CHANNEL
2203
2219
  if (requested !== undefined && requested.length > 0) {
2204
- return { launchOptions: { channel: requested } }
2220
+ return { launchOptions: { channel: requested, ...launch } }
2205
2221
  }
2206
2222
  const managed = pinned === undefined ? undefined : resolveManagedBrowser(pinned)
2207
2223
  if (managed !== undefined) {
2208
- return managed === pinned ? {} : { launchOptions: { executablePath: managed } }
2224
+ if (managed === pinned) return scrollbars === 'classic' ? { launchOptions: launch } : {}
2225
+ return { launchOptions: { executablePath: managed, ...launch } }
2209
2226
  }
2210
2227
  const bundled = resolveBundledBrowser(platform, root)
2211
- if (bundled !== undefined) return { launchOptions: { executablePath: bundled } }
2228
+ if (bundled !== undefined) return { launchOptions: { executablePath: bundled, ...launch } }
2212
2229
  const fallback = platform === 'win32' ? 'msedge' : 'chrome'
2213
- return { launchOptions: { channel: resolveSystemBrowser(platform, environment) ?? fallback } }
2230
+ return {
2231
+ launchOptions: { channel: resolveSystemBrowser(platform, environment) ?? fallback, ...launch },
2232
+ }
2214
2233
  }
2215
2234
  `
2216
2235
  });