@helix3/helix-cli 0.1.18-helix3.345 → 0.1.18-helix3.347

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.
@@ -0,0 +1,9 @@
1
+ export declare function hostBrowserCap(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): number;
2
+ /** Pure-ish: is this slot file's owner gone? `raw` is returned so the caller can confirm the file did not change before reclaiming. */
3
+ export declare function slotIsStale(file: string, alive?: (pid: number) => boolean | 'unknown'): {
4
+ stale: boolean;
5
+ raw: string | null;
6
+ };
7
+ /** Take this process's browser slot (idempotent) and wait while the host is at its cap. Resolves to the slot file, or undefined when uncapped. */
8
+ export declare function acquireHostBrowserSlot(): Promise<string | undefined>;
9
+ export declare function releaseHostBrowserSlot(): void;
@@ -0,0 +1,145 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.hostBrowserCap = hostBrowserCap;
7
+ exports.slotIsStale = slotIsStale;
8
+ exports.acquireHostBrowserSlot = acquireHostBrowserSlot;
9
+ exports.releaseHostBrowserSlot = releaseHostBrowserSlot;
10
+ // Host-wide cap on concurrent automated browsers. 8-16 headless QA Chromes at once cost ~5 cores and up to 22 GB on the
11
+ // Mac (docs/compute-offload-model.md in the HELIX workspace), and a single `helix item visual-qa` fan-out started four.
12
+ // Every PROCESS that opens a browser takes ONE slot for its whole life (released at exit), so a run that opens several
13
+ // browsers can never deadlock against itself. Slots are files in ~/.cache/helix-headless-slots/ — the same protocol as the
14
+ // workspace's scripts/lib/headless-browser.mjs, so scripts and this CLI share one budget. A slot whose owner pid is dead (or
15
+ // was reused: start time differs) is reclaimed; nothing is ever signalled.
16
+ // HELIX_HEADLESS_MAX=<n> cap (default 4 on macOS; unlimited elsewhere and when CI is set; 0 = off)
17
+ // HELIX_HEADLESS_WAIT_MS=<ms> give up waiting after this long (default 30 min) and throw
18
+ // HELIX_HEADLESS_SLOT_DIR=<dir> override the slot directory (tests)
19
+ const node_child_process_1 = require("node:child_process");
20
+ const node_fs_1 = __importDefault(require("node:fs"));
21
+ const node_os_1 = __importDefault(require("node:os"));
22
+ const node_path_1 = __importDefault(require("node:path"));
23
+ let held;
24
+ function hostBrowserCap(env = process.env, platform = process.platform) {
25
+ const v = env.HELIX_HEADLESS_MAX;
26
+ if (v !== undefined && v !== '') {
27
+ const n = Number(v);
28
+ return Number.isFinite(n) && n > 0 ? Math.floor(n) : 0;
29
+ }
30
+ return platform !== 'darwin' || env.CI ? 0 : 4;
31
+ }
32
+ function startStamp(pid) {
33
+ try {
34
+ return (0, node_child_process_1.execFileSync)('ps', ['-o', 'lstart=', '-p', String(pid)], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
35
+ }
36
+ catch {
37
+ return '';
38
+ }
39
+ }
40
+ /** Pure-ish: is this slot file's owner gone? `raw` is returned so the caller can confirm the file did not change before reclaiming. */
41
+ function slotIsStale(file, alive = pidAlive) {
42
+ let raw;
43
+ try {
44
+ raw = node_fs_1.default.readFileSync(file, 'utf8');
45
+ }
46
+ catch {
47
+ return { stale: false, raw: null };
48
+ }
49
+ let owner;
50
+ try {
51
+ owner = JSON.parse(raw);
52
+ }
53
+ catch {
54
+ owner = null;
55
+ }
56
+ if (!owner || !Number.isInteger(owner.pid)) {
57
+ try {
58
+ return { stale: Date.now() - node_fs_1.default.statSync(file).mtimeMs > 5000, raw };
59
+ }
60
+ catch {
61
+ return { stale: false, raw };
62
+ }
63
+ }
64
+ const state = alive(owner.pid);
65
+ if (state === false)
66
+ return { stale: true, raw };
67
+ if (state === 'unknown')
68
+ return { stale: false, raw };
69
+ const now = startStamp(owner.pid);
70
+ return { stale: !!owner.start && !!now && owner.start !== now, raw };
71
+ }
72
+ function pidAlive(pid) {
73
+ try {
74
+ process.kill(pid, 0);
75
+ return true;
76
+ }
77
+ catch (e) {
78
+ return e.code === 'ESRCH' ? false : 'unknown';
79
+ }
80
+ }
81
+ /** Take this process's browser slot (idempotent) and wait while the host is at its cap. Resolves to the slot file, or undefined when uncapped. */
82
+ async function acquireHostBrowserSlot() {
83
+ if (held)
84
+ return held;
85
+ const cap = hostBrowserCap();
86
+ if (!cap)
87
+ return undefined;
88
+ const dir = process.env.HELIX_HEADLESS_SLOT_DIR ?? node_path_1.default.join(node_os_1.default.homedir(), '.cache/helix-headless-slots');
89
+ node_fs_1.default.mkdirSync(dir, { recursive: true });
90
+ const me = JSON.stringify({ pid: process.pid, start: startStamp(process.pid), script: process.argv[1] ?? null, at: new Date().toISOString() });
91
+ const waitMs = Number(process.env.HELIX_HEADLESS_WAIT_MS ?? 30 * 60_000);
92
+ const began = Date.now();
93
+ let lastNote = 0;
94
+ for (;;) {
95
+ const retries = {};
96
+ for (let k = 0; k < cap; k++) {
97
+ const file = node_path_1.default.join(dir, `slot-${k}`);
98
+ try {
99
+ node_fs_1.default.writeFileSync(file, me, { flag: 'wx' });
100
+ held = file;
101
+ process.once('exit', releaseHostBrowserSlot);
102
+ return file;
103
+ }
104
+ catch (e) {
105
+ if (e.code !== 'EEXIST')
106
+ throw e;
107
+ }
108
+ const { stale, raw } = slotIsStale(file);
109
+ retries[k] = (retries[k] ?? 0) + 1;
110
+ if (stale && retries[k] <= 5) {
111
+ const tomb = `${file}.reclaimed-${process.pid}-${Date.now()}`;
112
+ try {
113
+ if (node_fs_1.default.readFileSync(file, 'utf8') === raw)
114
+ node_fs_1.default.renameSync(file, tomb);
115
+ node_fs_1.default.rmSync(tomb, { force: true });
116
+ }
117
+ catch { /* another reclaimer won */ }
118
+ k -= 1;
119
+ }
120
+ }
121
+ for (const f of node_fs_1.default.readdirSync(dir))
122
+ if (f.includes('.reclaimed-'))
123
+ node_fs_1.default.rmSync(node_path_1.default.join(dir, f), { force: true });
124
+ if (Date.now() - began > waitMs)
125
+ throw new Error(`No browser slot free after ${Math.round(waitMs / 1000)}s (host cap ${cap} in ${dir}). Other QA runs hold them; wait, or raise HELIX_HEADLESS_MAX.`);
126
+ if (Date.now() - lastNote > 30_000) {
127
+ lastNote = Date.now();
128
+ console.error(`[visual-qa] waiting for a browser slot: host cap is ${cap} concurrent automated browsers (HELIX_HEADLESS_MAX overrides)`);
129
+ }
130
+ await new Promise((r) => setTimeout(r, 500));
131
+ }
132
+ }
133
+ function releaseHostBrowserSlot() {
134
+ if (!held)
135
+ return;
136
+ const file = held;
137
+ held = undefined;
138
+ try {
139
+ const owner = JSON.parse(node_fs_1.default.readFileSync(file, 'utf8'));
140
+ if (owner.pid === process.pid)
141
+ node_fs_1.default.rmSync(file, { force: true });
142
+ }
143
+ catch { /* already gone */ }
144
+ }
145
+ //# sourceMappingURL=hostSlots.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hostSlots.js","sourceRoot":"","sources":["../../src/browser/hostSlots.ts"],"names":[],"mappings":";;;;;AAgBA,wCAOC;AAOD,kCAaC;AAOD,wDAyCC;AAED,wDAQC;AArGD,wHAAwH;AACxH,wHAAwH;AACxH,uHAAuH;AACvH,2HAA2H;AAC3H,6HAA6H;AAC7H,2EAA2E;AAC3E,4GAA4G;AAC5G,6FAA6F;AAC7F,sEAAsE;AACtE,2DAAkD;AAClD,sDAAyB;AACzB,sDAAyB;AACzB,0DAA6B;AAE7B,IAAI,IAAwB,CAAC;AAE7B,SAAgB,cAAc,CAAC,MAAyB,OAAO,CAAC,GAAG,EAAE,WAA4B,OAAO,CAAC,QAAQ;IAC/G,MAAM,CAAC,GAAG,GAAG,CAAC,kBAAkB,CAAC;IACjC,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,EAAE,EAAE,CAAC;QAChC,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;QACpB,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACzD,CAAC;IACD,OAAO,QAAQ,KAAK,QAAQ,IAAI,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACjD,CAAC;AAED,SAAS,UAAU,CAAC,GAAW;IAC7B,IAAI,CAAC;QAAC,OAAO,IAAA,iCAAY,EAAC,IAAI,EAAE,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,EAAE,CAAC;IAAC,CAAC;AACjK,CAAC;AAED,uIAAuI;AACvI,SAAgB,WAAW,CAAC,IAAY,EAAE,QAA8C,QAAQ;IAC9F,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QAAC,GAAG,GAAG,iBAAE,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;IAAC,CAAC;IAC1F,IAAI,KAA8C,CAAC;IACnD,IAAI,CAAC;QAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAqC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,KAAK,GAAG,IAAI,CAAC;IAAC,CAAC;IAC5F,IAAI,CAAC,KAAK,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QAC3C,IAAI,CAAC;YAAC,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,iBAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,GAAG,IAAI,EAAE,GAAG,EAAE,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC;YAAC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;QAAC,CAAC;IACvH,CAAC;IACD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAa,CAAC,CAAC;IACzC,IAAI,KAAK,KAAK,KAAK;QAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;IACjD,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;IACtD,MAAM,GAAG,GAAG,UAAU,CAAC,KAAK,CAAC,GAAa,CAAC,CAAC;IAC5C,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,IAAI,KAAK,CAAC,KAAK,KAAK,GAAG,EAAE,GAAG,EAAE,CAAC;AACvE,CAAC;AAED,SAAS,QAAQ,CAAC,GAAW;IAC3B,IAAI,CAAC;QAAC,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QAAC,OAAO,IAAI,CAAC;IAAC,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QAAC,OAAQ,CAA2B,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;IAAC,CAAC;AACpI,CAAC;AAED,kJAAkJ;AAC3I,KAAK,UAAU,sBAAsB;IAC1C,IAAI,IAAI;QAAE,OAAO,IAAI,CAAC;IACtB,MAAM,GAAG,GAAG,cAAc,EAAE,CAAC;IAC7B,IAAI,CAAC,GAAG;QAAE,OAAO,SAAS,CAAC;IAC3B,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,uBAAuB,IAAI,mBAAI,CAAC,IAAI,CAAC,iBAAE,CAAC,OAAO,EAAE,EAAE,6BAA6B,CAAC,CAAC;IAC1G,iBAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACvC,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,KAAK,EAAE,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;IAC/I,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,sBAAsB,IAAI,EAAE,GAAG,MAAM,CAAC,CAAC;IACzE,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACzB,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,SAAS,CAAC;QACR,MAAM,OAAO,GAA2B,EAAE,CAAC;QAC3C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;YAC7B,MAAM,IAAI,GAAG,mBAAI,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC;YACzC,IAAI,CAAC;gBACH,iBAAE,CAAC,aAAa,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;gBAC3C,IAAI,GAAG,IAAI,CAAC;gBACZ,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC;gBAC7C,OAAO,IAAI,CAAC;YACd,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,IAAK,CAA2B,CAAC,IAAI,KAAK,QAAQ;oBAAE,MAAM,CAAC,CAAC;YAC9D,CAAC;YACD,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;YACzC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;YACnC,IAAI,KAAK,IAAI,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC7B,MAAM,IAAI,GAAG,GAAG,IAAI,cAAc,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;gBAC9D,IAAI,CAAC;oBACH,IAAI,iBAAE,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,GAAG;wBAAE,iBAAE,CAAC,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;oBACrE,iBAAE,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;gBACnC,CAAC;gBAAC,MAAM,CAAC,CAAC,2BAA2B,CAAC,CAAC;gBACvC,CAAC,IAAI,CAAC,CAAC;YACT,CAAC;QACH,CAAC;QACD,KAAK,MAAM,CAAC,IAAI,iBAAE,CAAC,WAAW,CAAC,GAAG,CAAC;YAAE,IAAI,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC;gBAAE,iBAAE,CAAC,MAAM,CAAC,mBAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAClH,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,GAAG,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,8BAA8B,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,eAAe,GAAG,OAAO,GAAG,gEAAgE,CAAC,CAAC;QACtM,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,GAAG,MAAM,EAAE,CAAC;YACnC,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACtB,OAAO,CAAC,KAAK,CAAC,uDAAuD,GAAG,+DAA+D,CAAC,CAAC;QAC3I,CAAC;QACD,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IAC/C,CAAC;AACH,CAAC;AAED,SAAgB,sBAAsB;IACpC,IAAI,CAAC,IAAI;QAAE,OAAO;IAClB,MAAM,IAAI,GAAG,IAAI,CAAC;IAClB,IAAI,GAAG,SAAS,CAAC;IACjB,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,iBAAE,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAqB,CAAC;QAC5E,IAAI,KAAK,CAAC,GAAG,KAAK,OAAO,CAAC,GAAG;YAAE,iBAAE,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAClE,CAAC;IAAC,MAAM,CAAC,CAAC,kBAAkB,CAAC,CAAC;AAChC,CAAC"}
@@ -1,15 +1,16 @@
1
1
  import type { LaunchOptions } from 'playwright';
2
2
  export declare const HEADLESS_GL_ARGS: string[];
3
3
  export declare const GPU_GL_ARGS: string[];
4
- export type BrowserKey = 'bundled' | 'chrome' | 'msedge';
4
+ export type BrowserKey = 'bundled' | 'chromium' | 'chrome' | 'msedge';
5
5
  export type BrowserCandidate = {
6
6
  key: BrowserKey;
7
7
  label: string;
8
8
  launchOptions: LaunchOptions;
9
9
  };
10
- export declare function browserCandidates(): BrowserCandidate[];
11
- export declare function gpuBrowserCandidates(): BrowserCandidate[];
12
- export declare function selectBrowserCandidates(browser: BrowserKey | undefined): BrowserCandidate[];
10
+ export declare function cachedChromiumCandidate(args: string[], cached?: string | undefined): BrowserCandidate[];
11
+ export declare function browserCandidates(cached?: string): BrowserCandidate[];
12
+ export declare function gpuBrowserCandidates(cached?: string): BrowserCandidate[];
13
+ export declare function selectBrowserCandidates(browser: BrowserKey | undefined, cached?: string): BrowserCandidate[];
13
14
  export type LaunchAttempt = {
14
15
  key: BrowserKey;
15
16
  label: string;
@@ -1,37 +1,48 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.GPU_GL_ARGS = exports.HEADLESS_GL_ARGS = void 0;
4
+ exports.cachedChromiumCandidate = cachedChromiumCandidate;
4
5
  exports.browserCandidates = browserCandidates;
5
6
  exports.gpuBrowserCandidates = gpuBrowserCandidates;
6
7
  exports.selectBrowserCandidates = selectBrowserCandidates;
7
8
  exports.composeLaunchFailure = composeLaunchFailure;
8
9
  exports.composeTimeoutDiagnosisWith = composeTimeoutDiagnosisWith;
10
+ const nonSystemChrome_1 = require("./nonSystemChrome");
9
11
  // Software-GL flags: headless Chromium has no real GPU, so WebGL must render via SwiftShader.
10
12
  exports.HEADLESS_GL_ARGS = ['--use-angle=swiftshader', '--enable-unsafe-swiftshader'];
11
13
  // Real-GPU flags: nothing forces software GL, and the blocklist is ignored so a headless run still gets the
12
14
  // hardware backend on the driver/VM combinations Chrome distrusts.
13
15
  exports.GPU_GL_ARGS = ['--ignore-gpu-blocklist'];
16
+ // macOS only: a cached Playwright Chromium ("Google Chrome for Testing", its own bundle id) is tried before the user's
17
+ // system Chrome, because an automated headless /Applications/Google Chrome.app registers as the user's own Chrome in
18
+ // LaunchServices and hijacks their Dock icon and Cmd-Q. `cached` is injectable so the chain stays unit-testable.
19
+ function cachedChromiumCandidate(args, cached = process.platform === 'darwin' ? (0, nonSystemChrome_1.findCachedAutomationChromium)() : undefined) {
20
+ return cached ? [{ key: 'chromium', label: 'Playwright Chromium (Chrome for Testing)', launchOptions: { headless: true, args, executablePath: cached } }] : [];
21
+ }
14
22
  // Ordered fallback chain for `chromium.launch()`: the pinned bundled build first (most
15
- // deterministic), then whichever system browser is already installed — so first-run works
16
- // without the ~150 MB download on machines that already have Chrome or Edge.
17
- function browserCandidates() {
23
+ // deterministic), then a cached Playwright Chromium on macOS, then whichever system browser is already
24
+ // installed — so first-run works without the ~150 MB download on machines that already have Chrome or Edge.
25
+ function browserCandidates(cached) {
18
26
  return [
19
27
  { key: 'bundled', label: 'bundled Chromium', launchOptions: { headless: true, args: exports.HEADLESS_GL_ARGS } },
28
+ ...cachedChromiumCandidate(exports.HEADLESS_GL_ARGS, cached),
20
29
  { key: 'chrome', label: 'system Chrome', launchOptions: { headless: true, args: exports.HEADLESS_GL_ARGS, channel: 'chrome' } },
21
30
  { key: 'msedge', label: 'system Edge', launchOptions: { headless: true, args: exports.HEADLESS_GL_ARGS, channel: 'msedge' } },
22
31
  ];
23
32
  }
24
- // The GPU chain: system browsers only. Playwright's bundled build is the headless SHELL, which carries no
25
- // hardware GL backend at all — it renders through SwiftShader whatever flags it is handed.
26
- function gpuBrowserCandidates() {
33
+ // The GPU chain: full browsers only. Playwright's bundled build is the headless SHELL, which carries no
34
+ // hardware GL backend at all — it renders through SwiftShader whatever flags it is handed. On macOS the cached
35
+ // Chrome for Testing (full Chromium, real Metal/WebGPU) leads, so the user's own Chrome is only a last resort.
36
+ function gpuBrowserCandidates(cached) {
27
37
  return [
38
+ ...cachedChromiumCandidate(exports.GPU_GL_ARGS, cached),
28
39
  { key: 'chrome', label: 'system Chrome', launchOptions: { headless: true, args: exports.GPU_GL_ARGS, channel: 'chrome' } },
29
40
  { key: 'msedge', label: 'system Edge', launchOptions: { headless: true, args: exports.GPU_GL_ARGS, channel: 'msedge' } },
30
41
  ];
31
42
  }
32
43
  // Pure so the "forced candidate, no fallback" plan is unit-testable without Playwright.
33
- function selectBrowserCandidates(browser) {
34
- return browser ? browserCandidates().filter((c) => c.key === browser) : browserCandidates();
44
+ function selectBrowserCandidates(browser, cached) {
45
+ return browser ? browserCandidates(cached).filter((c) => c.key === browser) : browserCandidates(cached);
35
46
  }
36
47
  // Pure so the "everything failed" message is unit-testable without Playwright. The install hint only
37
48
  // makes sense when the bundled build was among the attempts — forcing a system browser that's simply
@@ -1 +1 @@
1
- {"version":3,"file":"launch.js","sourceRoot":"","sources":["../../src/browser/launch.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"launch.js","sourceRoot":"","sources":["../../src/browser/launch.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /** Throws when an executable path or Playwright channel would launch the user's branded Chrome. */
2
+ export declare function assertNotSystemChrome(executablePath?: string, channel?: string, platform?: NodeJS.Platform): void;
3
+ /** Newest Chrome for Testing / Chromium in the Playwright browser cache, regardless of which Playwright installed it. */
4
+ export declare function findCachedAutomationChromium(home?: string, env?: NodeJS.ProcessEnv): string | undefined;
5
+ /**
6
+ * The browser a macOS automated run should use: HELIX_VQA_CHROME, else the Playwright build's own Chromium, else any
7
+ * cached Chrome for Testing. Never the system Chrome. Returns undefined off macOS (callers keep their platform default).
8
+ */
9
+ export declare function resolveAutomationChromium(opts?: {
10
+ override?: string;
11
+ playwrightDefault?: string;
12
+ platform?: NodeJS.Platform;
13
+ home?: string;
14
+ env?: NodeJS.ProcessEnv;
15
+ }): string | undefined;
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.assertNotSystemChrome = assertNotSystemChrome;
7
+ exports.findCachedAutomationChromium = findCachedAutomationChromium;
8
+ exports.resolveAutomationChromium = resolveAutomationChromium;
9
+ // On macOS an automated headless /Applications/Google Chrome.app registers with LaunchServices under the
10
+ // user's own bundle id (com.google.Chrome) and can be promoted to the Foreground app: the Dock icon and
11
+ // Cmd-Q then target an invisible headless instance and the user's real Chrome "is active" with no UI and
12
+ // will not quit. Automation therefore runs on a binary with a different bundle id — Playwright's Chromium,
13
+ // which on macOS is "Google Chrome for Testing" (com.google.chrome.for.testing) and, unlike the headless
14
+ // shell, has a real Metal/WebGPU adapter.
15
+ const node_fs_1 = __importDefault(require("node:fs"));
16
+ const node_os_1 = __importDefault(require("node:os"));
17
+ const node_path_1 = __importDefault(require("node:path"));
18
+ const SYSTEM_CHROME_MARKERS = ['/Applications/Google Chrome.app', '/Google Chrome.app/'];
19
+ /** Throws when an executable path or Playwright channel would launch the user's branded Chrome. */
20
+ function assertNotSystemChrome(executablePath, channel, platform = process.platform) {
21
+ if (platform !== 'darwin')
22
+ return; // the LaunchServices hijack is macOS-only; Linux/Windows hosts keep their own browsers
23
+ if (channel && /^chrome/i.test(channel)) {
24
+ throw new Error(`Refusing Playwright channel '${channel}': it launches the system Google Chrome and hijacks the Dock Chrome. Use Playwright's Chromium (npx playwright install chromium).`);
25
+ }
26
+ if (executablePath && SYSTEM_CHROME_MARKERS.some((m) => executablePath.includes(m))) {
27
+ throw new Error(`Refusing to automate ${executablePath}: never launch /Applications/Google Chrome.app for automation. Use Playwright's Chromium / Chrome for Testing (npx playwright install chromium).`);
28
+ }
29
+ }
30
+ const CFT_REL = [
31
+ 'chrome-mac-arm64/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing',
32
+ 'chrome-mac-x64/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing',
33
+ 'chrome-mac/Chromium.app/Contents/MacOS/Chromium',
34
+ ];
35
+ /** Newest Chrome for Testing / Chromium in the Playwright browser cache, regardless of which Playwright installed it. */
36
+ function findCachedAutomationChromium(home = node_os_1.default.homedir(), env = process.env) {
37
+ const roots = [env.PLAYWRIGHT_BROWSERS_PATH && env.PLAYWRIGHT_BROWSERS_PATH !== '0' ? env.PLAYWRIGHT_BROWSERS_PATH : undefined, node_path_1.default.join(home, 'Library/Caches/ms-playwright')].filter((r) => !!r);
38
+ for (const root of roots) {
39
+ let dirs = [];
40
+ try {
41
+ dirs = node_fs_1.default.readdirSync(root).filter((n) => /^chromium-\d+$/.test(n));
42
+ }
43
+ catch {
44
+ continue;
45
+ }
46
+ dirs.sort((a, b) => Number(b.slice('chromium-'.length)) - Number(a.slice('chromium-'.length)));
47
+ for (const d of dirs)
48
+ for (const rel of CFT_REL) {
49
+ const p = node_path_1.default.join(root, d, rel);
50
+ if (node_fs_1.default.existsSync(p))
51
+ return p;
52
+ }
53
+ }
54
+ return undefined;
55
+ }
56
+ /**
57
+ * The browser a macOS automated run should use: HELIX_VQA_CHROME, else the Playwright build's own Chromium, else any
58
+ * cached Chrome for Testing. Never the system Chrome. Returns undefined off macOS (callers keep their platform default).
59
+ */
60
+ function resolveAutomationChromium(opts = {}) {
61
+ const platform = opts.platform ?? process.platform;
62
+ const env = opts.env ?? process.env;
63
+ const override = opts.override ?? env.HELIX_VQA_CHROME;
64
+ if (override) {
65
+ assertNotSystemChrome(override, undefined, platform);
66
+ return override;
67
+ }
68
+ if (platform !== 'darwin')
69
+ return undefined;
70
+ if (opts.playwrightDefault && node_fs_1.default.existsSync(opts.playwrightDefault)) {
71
+ assertNotSystemChrome(opts.playwrightDefault, undefined, platform);
72
+ return opts.playwrightDefault;
73
+ }
74
+ const cached = findCachedAutomationChromium(opts.home, env);
75
+ if (cached)
76
+ return cached;
77
+ throw new Error('No Playwright Chromium / Chrome for Testing found. Run `npx playwright install chromium` (the system Google Chrome is never used for automation on macOS).');
78
+ }
79
+ //# sourceMappingURL=nonSystemChrome.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nonSystemChrome.js","sourceRoot":"","sources":["../../src/browser/nonSystemChrome.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- export {};
2
+ import './stdioBlocking';
package/dist/index.js CHANGED
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  };
35
35
  })();
36
36
  Object.defineProperty(exports, "__esModule", { value: true });
37
+ require("./stdioBlocking");
37
38
  const baseColorGuard_1 = require("./character/baseColorGuard");
38
39
  const normalMapGuard_1 = require("./character/normalMapGuard");
39
40
  const itemSetVersion_1 = require("./itemSetVersion");
@@ -1766,7 +1767,7 @@ item
1766
1767
  .option('--host <id...>', 'only fit onto these host/base item ids (add-ons, wearables)')
1767
1768
  .option('--package-version <version>', 'render this version of the item\'s pinned package instead of the pin: a version move judged before the pointer moves (vehicles, avatars, add-ons, wearables)')
1768
1769
  .option('--vehicle-package <file>', 'with --package-version on a vehicle: the version move\'s vehicle package JSON, served by Try Now after server-side preparation')
1769
- .option('--chrome <path>', 'Chrome/Chromium executable (default: system Chrome, headless)')
1770
+ .option('--chrome <path>', 'Chromium executable (default: Playwright Chromium / Chrome for Testing, headless; the macOS system Chrome is refused)')
1770
1771
  .option('--api-url <url>', 'platform API base URL')
1771
1772
  .option('--web-url <url>', 'website base URL (default: the site that serves --api-url)')
1772
1773
  .option('--json', 'print the outcome as JSON')
@@ -1777,6 +1778,9 @@ item
1777
1778
  const flagError = validateCandidateFlags({ packageVersion: opts.packageVersion, vehiclePackageFile: opts.vehiclePackage, submitHosted: !!(opts.hosted || opts.status || opts.hostedWorker) });
1778
1779
  if (flagError)
1779
1780
  fail(`✖ ${flagError}`);
1781
+ if (process.platform === 'darwin' && !opts.hosted && !opts.status && !opts.hostedWorker && !opts.packageVersion && !opts.json) {
1782
+ console.error('tip: a published version can render on the platform GPU with --hosted (no local Chrome); local renders share this Mac\'s cap of 4 concurrent automated browsers.');
1783
+ }
1780
1784
  const vehiclePackage = opts.vehiclePackage ? parseVehiclePackage((0, node_fs_1.readFileSync)(opts.vehiclePackage, 'utf8'), opts.vehiclePackage) : null;
1781
1785
  const creds = opts.submit || opts.hosted ? requireAuth() : (0, config_1.loadCredentials)();
1782
1786
  const apiBase = opts.apiUrl ?? creds?.apiUrl ?? config_1.DEFAULT_API_URL;