@haposoft/cafekit 0.11.7 → 0.11.9
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/bin/lib/i18n.js +3 -0
- package/bin/lib/skill-deps.js +101 -14
- package/bin/phases/skills-setup.js +25 -10
- package/package.json +1 -1
- package/src/claude/skills/chrome-devtools/SKILL.md +33 -0
- package/src/claude/skills/chrome-devtools/scripts/.env.example +20 -0
- package/src/claude/skills/chrome-devtools/scripts/lib/browser.js +61 -0
package/bin/lib/i18n.js
CHANGED
|
@@ -75,6 +75,7 @@ const MESSAGES = {
|
|
|
75
75
|
chromiumInstalling: 'Downloading Chromium for chrome-devtools',
|
|
76
76
|
chromiumReady: 'Chromium ready',
|
|
77
77
|
chromiumSkipped: 'Chromium download skipped — chrome-devtools will prompt on first use.',
|
|
78
|
+
chromiumSystemChromeFound: 'System Chrome detected ({path}), skipping download',
|
|
78
79
|
optionalToolsTitle: 'Optional tools to install manually',
|
|
79
80
|
|
|
80
81
|
// Summary stats
|
|
@@ -180,6 +181,7 @@ const MESSAGES = {
|
|
|
180
181
|
chromiumInstalling: 'chrome-devtools 用 Chromium をダウンロード中',
|
|
181
182
|
chromiumReady: 'Chromium の準備ができました',
|
|
182
183
|
chromiumSkipped: 'Chromium のダウンロードをスキップ — 初回使用時に chrome-devtools が自動取得します。',
|
|
184
|
+
chromiumSystemChromeFound: 'システムChromeを検出 ({path})、ダウンロードをスキップします',
|
|
183
185
|
optionalToolsTitle: '手動でインストールが必要なオプションツール',
|
|
184
186
|
|
|
185
187
|
summaryTitle: 'インストール完了',
|
|
@@ -282,6 +284,7 @@ const MESSAGES = {
|
|
|
282
284
|
chromiumInstalling: 'Đang tải Chromium cho chrome-devtools',
|
|
283
285
|
chromiumReady: 'Chromium đã sẵn sàng',
|
|
284
286
|
chromiumSkipped: 'Bỏ qua tải Chromium — chrome-devtools sẽ tự tải khi dùng lần đầu.',
|
|
287
|
+
chromiumSystemChromeFound: 'Đã phát hiện Chrome hệ thống ({path}), bỏ qua tải Chromium',
|
|
285
288
|
optionalToolsTitle: 'Công cụ tuỳ chọn cần cài thủ công',
|
|
286
289
|
|
|
287
290
|
summaryTitle: 'Cài đặt hoàn tất',
|
package/bin/lib/skill-deps.js
CHANGED
|
@@ -13,11 +13,11 @@
|
|
|
13
13
|
|
|
14
14
|
const fs = require('fs');
|
|
15
15
|
const path = require('path');
|
|
16
|
-
const { spawnSync } = require('child_process');
|
|
16
|
+
const { spawnSync, spawn } = require('child_process');
|
|
17
17
|
|
|
18
18
|
const isWindows = process.platform === 'win32';
|
|
19
19
|
|
|
20
|
-
/** Run a command, return { ok, stdout, stderr, status }. Never throws. */
|
|
20
|
+
/** Run a command synchronously, return { ok, stdout, stderr, status }. Never throws. */
|
|
21
21
|
function run(cmd, args, opts = {}) {
|
|
22
22
|
const r = spawnSync(cmd, args, { encoding: 'utf8', ...opts });
|
|
23
23
|
return {
|
|
@@ -28,6 +28,31 @@ function run(cmd, args, opts = {}) {
|
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Run a command asynchronously — keeps the event loop alive so spinners can animate.
|
|
33
|
+
* Returns a Promise<{ ok, stdout, stderr, status }>. Never rejects.
|
|
34
|
+
*/
|
|
35
|
+
function runAsync(cmd, args, opts = {}) {
|
|
36
|
+
return new Promise((resolve) => {
|
|
37
|
+
const child = spawn(cmd, args, { stdio: 'pipe', ...opts });
|
|
38
|
+
let stdout = '';
|
|
39
|
+
let stderr = '';
|
|
40
|
+
if (child.stdout) child.stdout.on('data', (d) => { stdout += d.toString(); });
|
|
41
|
+
if (child.stderr) child.stderr.on('data', (d) => { stderr += d.toString(); });
|
|
42
|
+
child.on('close', (code) => {
|
|
43
|
+
resolve({
|
|
44
|
+
ok: code === 0,
|
|
45
|
+
status: code,
|
|
46
|
+
stdout,
|
|
47
|
+
stderr
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
child.on('error', (err) => {
|
|
51
|
+
resolve({ ok: false, status: null, stdout, stderr: stderr || err.message });
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
31
56
|
/** Is a command available on PATH? */
|
|
32
57
|
function hasCmd(name) {
|
|
33
58
|
const probe = isWindows ? run('where', [name]) : run('which', [name]);
|
|
@@ -76,26 +101,37 @@ function pipInstall(skillsDir, requirementsPath, upgrade = false) {
|
|
|
76
101
|
return run(venvPython(skillsDir), args).ok;
|
|
77
102
|
}
|
|
78
103
|
|
|
79
|
-
/** `npm install` (fresh) or `npm update` (existing) in a skill scripts dir. */
|
|
80
|
-
function npmInstall(dir, upgrade = false) {
|
|
104
|
+
/** `npm install` (fresh) or `npm update` (existing) in a skill scripts dir. Async — keeps spinner alive. */
|
|
105
|
+
async function npmInstall(dir, upgrade = false, extraEnv = {}) {
|
|
81
106
|
const args = upgrade ? ['update', '--no-audit', '--no-fund'] : ['install', '--no-audit', '--no-fund'];
|
|
82
|
-
|
|
107
|
+
const env = { ...process.env, ...extraEnv };
|
|
108
|
+
const r = await runAsync('npm', args, { cwd: dir, env });
|
|
109
|
+
return r.ok;
|
|
83
110
|
}
|
|
84
111
|
|
|
85
112
|
/**
|
|
86
113
|
* Download the Puppeteer Chromium browser for chrome-devtools.
|
|
87
114
|
* Prefer puppeteer's own install script (cache-aware, same as its postinstall);
|
|
88
|
-
* fall back to the bundled @puppeteer/browsers CLI.
|
|
89
|
-
*
|
|
90
|
-
*
|
|
115
|
+
* fall back to the bundled @puppeteer/browsers CLI.
|
|
116
|
+
*
|
|
117
|
+
* Non-interactive (default): async — keeps spinner alive during download.
|
|
118
|
+
* Interactive: sync with stdio inherit — native progress bar renders in TTY.
|
|
91
119
|
*/
|
|
92
|
-
function installChromium(scriptsDir) {
|
|
120
|
+
async function installChromium(scriptsDir, interactive = false) {
|
|
93
121
|
// Absolute path so it resolves regardless of the child process cwd.
|
|
94
122
|
const installMjs = path.resolve(scriptsDir, 'node_modules', 'puppeteer', 'install.mjs');
|
|
95
123
|
if (fs.existsSync(installMjs)) {
|
|
96
|
-
|
|
124
|
+
if (interactive) {
|
|
125
|
+
return run('node', [installMjs], { cwd: scriptsDir, stdio: 'inherit' }).ok;
|
|
126
|
+
}
|
|
127
|
+
const r = await runAsync('node', [installMjs], { cwd: scriptsDir });
|
|
128
|
+
return r.ok;
|
|
129
|
+
}
|
|
130
|
+
if (interactive) {
|
|
131
|
+
return run('npx', ['--yes', '@puppeteer/browsers', 'install', 'chrome'], { cwd: scriptsDir, stdio: 'inherit' }).ok;
|
|
97
132
|
}
|
|
98
|
-
|
|
133
|
+
const r = await runAsync('npx', ['--yes', '@puppeteer/browsers', 'install', 'chrome'], { cwd: scriptsDir });
|
|
134
|
+
return r.ok;
|
|
99
135
|
}
|
|
100
136
|
|
|
101
137
|
/** Does a skill scripts dir declare a given npm dependency? */
|
|
@@ -108,9 +144,59 @@ function pkgHasDep(scriptsDir, name) {
|
|
|
108
144
|
}
|
|
109
145
|
}
|
|
110
146
|
|
|
111
|
-
/** Download the Playwright Chromium browser (for the pptx html2pptx workflow). */
|
|
112
|
-
function installPlaywrightBrowser(scriptsDir) {
|
|
113
|
-
|
|
147
|
+
/** Download the Playwright Chromium browser (for the pptx html2pptx workflow). Async — keeps spinner alive. */
|
|
148
|
+
async function installPlaywrightBrowser(scriptsDir) {
|
|
149
|
+
const r = await runAsync('npx', ['--yes', 'playwright', 'install', 'chromium'], { cwd: scriptsDir });
|
|
150
|
+
return r.ok;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Detect system Chrome/Chromium installation (cross-platform).
|
|
155
|
+
* Returns the executable path if found, undefined otherwise.
|
|
156
|
+
* Supports macOS, Windows, Linux (including snap + PATH fallback).
|
|
157
|
+
*/
|
|
158
|
+
function detectSystemChrome() {
|
|
159
|
+
const candidates = (() => {
|
|
160
|
+
switch (process.platform) {
|
|
161
|
+
case 'darwin':
|
|
162
|
+
return [
|
|
163
|
+
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
164
|
+
'/Applications/Chromium.app/Contents/MacOS/Chromium',
|
|
165
|
+
];
|
|
166
|
+
case 'win32': {
|
|
167
|
+
const pf = process.env.PROGRAMFILES;
|
|
168
|
+
const pf86 = process.env['PROGRAMFILES(X86)'];
|
|
169
|
+
const local = process.env.LOCALAPPDATA;
|
|
170
|
+
return [
|
|
171
|
+
pf && `${pf}\\Google\\Chrome\\Application\\chrome.exe`,
|
|
172
|
+
pf86 && `${pf86}\\Google\\Chrome\\Application\\chrome.exe`,
|
|
173
|
+
local && `${local}\\Google\\Chrome\\Application\\chrome.exe`,
|
|
174
|
+
].filter(Boolean);
|
|
175
|
+
}
|
|
176
|
+
default: // Linux & others
|
|
177
|
+
return [
|
|
178
|
+
'/usr/bin/google-chrome-stable',
|
|
179
|
+
'/usr/bin/google-chrome',
|
|
180
|
+
'/usr/bin/chromium-browser',
|
|
181
|
+
'/usr/bin/chromium',
|
|
182
|
+
'/snap/bin/chromium',
|
|
183
|
+
];
|
|
184
|
+
}
|
|
185
|
+
})();
|
|
186
|
+
|
|
187
|
+
for (const p of candidates) {
|
|
188
|
+
try { if (fs.existsSync(p)) return p; } catch { /* skip */ }
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Linux: last resort — check PATH
|
|
192
|
+
if (process.platform === 'linux') {
|
|
193
|
+
for (const cmd of ['google-chrome', 'google-chrome-stable', 'chromium-browser', 'chromium']) {
|
|
194
|
+
const r = run(isWindows ? 'where' : 'which', [cmd]);
|
|
195
|
+
if (r.ok && r.stdout.trim()) return r.stdout.trim();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return undefined;
|
|
114
200
|
}
|
|
115
201
|
|
|
116
202
|
/**
|
|
@@ -176,6 +262,7 @@ module.exports = {
|
|
|
176
262
|
pipInstall,
|
|
177
263
|
npmInstall,
|
|
178
264
|
installChromium,
|
|
265
|
+
detectSystemChrome,
|
|
179
266
|
collectRequirements,
|
|
180
267
|
collectSkillPackages,
|
|
181
268
|
pkgHasDep,
|
|
@@ -65,10 +65,21 @@ function setupPython(ctx, skillsDir) {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
/** Run skill-local npm installs/upgrades + browser binaries for a platform's skills. */
|
|
68
|
-
function setupNode(ctx, skillsDir) {
|
|
68
|
+
async function setupNode(ctx, skillsDir) {
|
|
69
|
+
// Detect system Chrome before npm install — skip ~300MB Chromium download if found
|
|
70
|
+
const chromeScripts = path.join(skillsDir, 'chrome-devtools', 'scripts');
|
|
71
|
+
const hasChromePkg = fs.existsSync(path.join(chromeScripts, 'package.json'));
|
|
72
|
+
const systemChrome = hasChromePkg ? dep.detectSystemChrome() : null;
|
|
73
|
+
|
|
69
74
|
for (const { skill, dir, hasNodeModules } of dep.collectSkillPackages(skillsDir)) {
|
|
70
75
|
ctx.ui.startSpinner(ctx.t('npmInstalling', { skill }));
|
|
71
|
-
|
|
76
|
+
|
|
77
|
+
// chrome-devtools: skip puppeteer postinstall Chromium download if system Chrome exists
|
|
78
|
+
const extraEnv = (skill === 'chrome-devtools' && systemChrome)
|
|
79
|
+
? { PUPPETEER_SKIP_DOWNLOAD: 'true' }
|
|
80
|
+
: {};
|
|
81
|
+
const ok = await dep.npmInstall(dir, hasNodeModules, extraEnv);
|
|
82
|
+
|
|
72
83
|
if (ok) {
|
|
73
84
|
ctx.ui.stopSpinner(ctx.t('npmInstalled', { skill }));
|
|
74
85
|
} else {
|
|
@@ -79,17 +90,21 @@ function setupNode(ctx, skillsDir) {
|
|
|
79
90
|
// Skills using Playwright (e.g. pptx) also need a browser binary.
|
|
80
91
|
if (dep.pkgHasDep(dir, 'playwright')) {
|
|
81
92
|
ctx.ui.startSpinner(ctx.t('playwrightInstalling', { skill }));
|
|
82
|
-
const pw = dep.installPlaywrightBrowser(dir);
|
|
93
|
+
const pw = await dep.installPlaywrightBrowser(dir);
|
|
83
94
|
ctx.ui.stopSpinner(pw ? ctx.t('playwrightReady', { skill }) : ctx.t('playwrightSkipped', { skill }));
|
|
84
95
|
}
|
|
85
96
|
}
|
|
86
97
|
|
|
87
|
-
// Chromium for chrome-devtools (puppeteer-based).
|
|
88
|
-
|
|
89
|
-
if (
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
98
|
+
// Chromium for chrome-devtools (puppeteer-based, ~300MB).
|
|
99
|
+
// Uses stdio:inherit for native progress bar when downloading (TTY-only).
|
|
100
|
+
if (hasChromePkg) {
|
|
101
|
+
if (systemChrome) {
|
|
102
|
+
ctx.ui.info(ctx.t('chromiumSystemChromeFound', { path: systemChrome }));
|
|
103
|
+
} else {
|
|
104
|
+
ctx.ui.info(ctx.t('chromiumInstalling'));
|
|
105
|
+
const ok = await dep.installChromium(chromeScripts, true);
|
|
106
|
+
ctx.ui.info(ok ? '✓ ' + ctx.t('chromiumReady') : ctx.t('chromiumSkipped'));
|
|
107
|
+
}
|
|
93
108
|
}
|
|
94
109
|
}
|
|
95
110
|
|
|
@@ -113,7 +128,7 @@ async function setupSkillDeps(ctx) {
|
|
|
113
128
|
|
|
114
129
|
for (const key of ctx.platforms) {
|
|
115
130
|
setupPython(ctx, PLATFORMS[key].skillsDir);
|
|
116
|
-
setupNode(ctx, PLATFORMS[key].skillsDir);
|
|
131
|
+
await setupNode(ctx, PLATFORMS[key].skillsDir);
|
|
117
132
|
}
|
|
118
133
|
|
|
119
134
|
guideManual(ctx);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@haposoft/cafekit",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.9",
|
|
4
4
|
"description": "Claude Code-first spec-driven workflow for AI coding assistants. Bundles CafeKit hapo: skills, runtime hooks, agents, and installer scaffolding.",
|
|
5
5
|
"author": "Haposoft <nghialt@haposoft.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -191,6 +191,39 @@ node "$SKILL_DIR/navigate.js" --url https://example.com
|
|
|
191
191
|
|
|
192
192
|
**Linux/WSL only**: Run `"$SKILL_DIR/install-deps.sh"` first for Chrome system libraries.
|
|
193
193
|
|
|
194
|
+
## Chrome/Chromium Configuration
|
|
195
|
+
|
|
196
|
+
By default, chrome-devtools bundles Chromium via puppeteer (~300MB download on first install). To use your existing Chrome installation instead:
|
|
197
|
+
|
|
198
|
+
### Auto-Detection
|
|
199
|
+
|
|
200
|
+
chrome-devtools auto-detects Chrome at standard OS paths and uses it automatically:
|
|
201
|
+
|
|
202
|
+
| OS | Checked Paths |
|
|
203
|
+
|----|---------------|
|
|
204
|
+
| macOS | `/Applications/Google Chrome.app/...`, `/Applications/Chromium.app/...` |
|
|
205
|
+
| Windows | `%PROGRAMFILES%\Google\Chrome\...`, `%PROGRAMFILES(X86)%\...`, `%LOCALAPPDATA%\...` |
|
|
206
|
+
| Linux | `/usr/bin/google-chrome*`, `/usr/bin/chromium*`, `/snap/bin/chromium` |
|
|
207
|
+
|
|
208
|
+
### Manual Configuration
|
|
209
|
+
|
|
210
|
+
To force a specific Chrome installation, copy `.env.example` to `.env` and set `CHROME_EXECUTABLE_PATH`:
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
cp "$SKILL_DIR/.env.example" "$SKILL_DIR/.env"
|
|
214
|
+
# Edit .env and set CHROME_EXECUTABLE_PATH to your Chrome executable
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Skip Chromium Download
|
|
218
|
+
|
|
219
|
+
To skip the ~300MB download during `npm install` when Chrome is already available:
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
PUPPETEER_SKIP_DOWNLOAD=true npm install --prefix "$SKILL_DIR"
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
If no system Chrome is found, puppeteer's bundled Chromium is used as a fallback.
|
|
226
|
+
|
|
194
227
|
## Session Persistence
|
|
195
228
|
|
|
196
229
|
Browser state persists across script executions via WebSocket endpoint file (`.browser-session.json`).
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Chrome/Chromium Configuration
|
|
2
|
+
# ============================================================================
|
|
3
|
+
|
|
4
|
+
# Custom Chrome/Chromium executable path (optional)
|
|
5
|
+
# chrome-devtools auto-detects system Chrome at standard OS paths.
|
|
6
|
+
# Set this only if Chrome is installed in a non-standard location.
|
|
7
|
+
#
|
|
8
|
+
# macOS:
|
|
9
|
+
# CHROME_EXECUTABLE_PATH=/Applications/Google Chrome.app/Contents/MacOS/Google Chrome
|
|
10
|
+
#
|
|
11
|
+
# Windows:
|
|
12
|
+
# CHROME_EXECUTABLE_PATH=C:\Program Files\Google\Chrome\Application\chrome.exe
|
|
13
|
+
#
|
|
14
|
+
# Linux:
|
|
15
|
+
# CHROME_EXECUTABLE_PATH=/usr/bin/google-chrome
|
|
16
|
+
|
|
17
|
+
# Skip puppeteer's Chromium download during npm install (optional)
|
|
18
|
+
# Set to true if you already have Chrome/Chromium installed and want to skip
|
|
19
|
+
# the ~300MB Chromium download during `npm install`.
|
|
20
|
+
# PUPPETEER_SKIP_DOWNLOAD=true
|
|
@@ -48,6 +48,63 @@ export function resolveHeadless(value) {
|
|
|
48
48
|
return false;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Resolve Chrome/Chromium executable path.
|
|
53
|
+
* Fallback chain: CHROME_EXECUTABLE_PATH → PUPPETEER_EXECUTABLE_PATH →
|
|
54
|
+
* OS auto-detect → undefined (puppeteer bundled Chromium).
|
|
55
|
+
* @returns {string|undefined}
|
|
56
|
+
*/
|
|
57
|
+
export function resolveExecutablePath() {
|
|
58
|
+
// 1. Env-var overrides (CafeKit + Puppeteer conventions)
|
|
59
|
+
for (const key of ['CHROME_EXECUTABLE_PATH', 'PUPPETEER_EXECUTABLE_PATH']) {
|
|
60
|
+
const p = process.env[key];
|
|
61
|
+
if (!p) continue;
|
|
62
|
+
try {
|
|
63
|
+
if (fs.existsSync(p)) { log(`Chrome (${key}): ${p}`); return p; }
|
|
64
|
+
log(`${key} set but not found: ${p}`);
|
|
65
|
+
} catch { /* permission error — skip */ }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 2. OS auto-detection
|
|
69
|
+
const candidates = (() => {
|
|
70
|
+
switch (process.platform) {
|
|
71
|
+
case 'darwin':
|
|
72
|
+
return [
|
|
73
|
+
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
74
|
+
'/Applications/Chromium.app/Contents/MacOS/Chromium',
|
|
75
|
+
];
|
|
76
|
+
case 'win32': {
|
|
77
|
+
const pf = process.env.PROGRAMFILES;
|
|
78
|
+
const pf86 = process.env['PROGRAMFILES(X86)'];
|
|
79
|
+
const local = process.env.LOCALAPPDATA;
|
|
80
|
+
return [
|
|
81
|
+
pf && `${pf}\\Google\\Chrome\\Application\\chrome.exe`,
|
|
82
|
+
pf86 && `${pf86}\\Google\\Chrome\\Application\\chrome.exe`,
|
|
83
|
+
local && `${local}\\Google\\Chrome\\Application\\chrome.exe`,
|
|
84
|
+
].filter(Boolean);
|
|
85
|
+
}
|
|
86
|
+
default: // Linux & others
|
|
87
|
+
return [
|
|
88
|
+
'/usr/bin/google-chrome-stable',
|
|
89
|
+
'/usr/bin/google-chrome',
|
|
90
|
+
'/usr/bin/chromium-browser',
|
|
91
|
+
'/usr/bin/chromium',
|
|
92
|
+
'/snap/bin/chromium',
|
|
93
|
+
];
|
|
94
|
+
}
|
|
95
|
+
})();
|
|
96
|
+
|
|
97
|
+
for (const p of candidates) {
|
|
98
|
+
try {
|
|
99
|
+
if (fs.existsSync(p)) { log(`Chrome auto-detected: ${p}`); return p; }
|
|
100
|
+
} catch { /* skip */ }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 3. Bundled Chromium fallback
|
|
104
|
+
log('No system Chrome found, using bundled Chromium');
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
|
|
51
108
|
/**
|
|
52
109
|
* Get default Chrome profile path based on OS
|
|
53
110
|
* @returns {string} - Path to Chrome's default user data directory
|
|
@@ -253,12 +310,16 @@ export async function getBrowser(options = {}) {
|
|
|
253
310
|
log(`Using default Chrome profile: ${userDataDir}`);
|
|
254
311
|
}
|
|
255
312
|
|
|
313
|
+
// Resolve Chrome executable — env var or OS auto-detect (fallback: bundled Chromium)
|
|
314
|
+
const executablePath = resolveExecutablePath();
|
|
315
|
+
|
|
256
316
|
// Destructure known properties — only pass Puppeteer-valid options to launch()
|
|
257
317
|
const { headless, args: extraArgs, viewport, useDefaultProfile, profile, browserUrl, wsEndpoint: _ws, userDataDir: _udd, ...restOptions } = options;
|
|
258
318
|
|
|
259
319
|
// Launch new browser
|
|
260
320
|
const launchOptions = {
|
|
261
321
|
headless: resolveHeadless(headless),
|
|
322
|
+
...(executablePath && { executablePath }),
|
|
262
323
|
args: [
|
|
263
324
|
'--no-sandbox',
|
|
264
325
|
'--disable-setuid-sandbox',
|