@narumitw/pi-chrome-devtools 0.51.0 → 0.52.1
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 +74 -42
- package/dist/chunks/browser-settings-menu-L4CZINWO.ts +266 -0
- package/dist/chunks/browser-settings-menu-L4CZINWO.ts.map +7 -0
- package/dist/chunks/chunk-RM7OGXVL.ts +1607 -0
- package/dist/chunks/chunk-RM7OGXVL.ts.map +7 -0
- package/dist/chunks/menu-U4AIAN7V.ts +397 -0
- package/dist/chunks/menu-U4AIAN7V.ts.map +7 -0
- package/dist/index.ts +762 -0
- package/dist/index.ts.map +7 -0
- package/package.json +11 -7
- package/src/browser-manager.ts +3 -5
- package/src/browser-settings-menu.ts +311 -0
- package/src/chrome-devtools.ts +22 -2
- package/src/menu.ts +12 -14
- package/src/runtime.ts +28 -23
- package/src/settings.ts +204 -38
- package/src/tool-selector.ts +3 -1
package/src/runtime.ts
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import type { ChildProcess } from "node:child_process";
|
|
2
|
-
import
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_BROWSER_HOST,
|
|
4
|
+
DEFAULT_BROWSER_PORT,
|
|
5
|
+
type EffectiveBrowserSettings,
|
|
6
|
+
parseConfiguredPort,
|
|
7
|
+
} from "./settings.js";
|
|
3
8
|
|
|
4
|
-
export const DEFAULT_HOST =
|
|
5
|
-
export const DEFAULT_PORT =
|
|
9
|
+
export const DEFAULT_HOST = DEFAULT_BROWSER_HOST;
|
|
10
|
+
export const DEFAULT_PORT = DEFAULT_BROWSER_PORT;
|
|
6
11
|
export const DEFAULT_TIMEOUT_MS = 10_000;
|
|
7
12
|
export const DEFAULT_HTTP_TIMEOUT_MS = 1_000;
|
|
8
13
|
export const DEFAULT_ENDPOINT_WAIT_MS = 5_000;
|
|
@@ -26,6 +31,8 @@ export interface ChromeDevToolsState {
|
|
|
26
31
|
hostConfigured: boolean;
|
|
27
32
|
portConfigured: boolean;
|
|
28
33
|
autoLaunchEnabled: boolean;
|
|
34
|
+
endpointSource: EffectiveBrowserSettings["endpointSource"];
|
|
35
|
+
autoLaunchSource: EffectiveBrowserSettings["autoLaunchSource"];
|
|
29
36
|
browserExecutable?: string;
|
|
30
37
|
extensionPaths: string[];
|
|
31
38
|
browserExecutableSource: EffectiveBrowserSettings["executablePathSource"];
|
|
@@ -71,29 +78,19 @@ export interface BrowserCandidate extends BrowserCandidateDefinition {
|
|
|
71
78
|
resolvedExecutable: string;
|
|
72
79
|
}
|
|
73
80
|
|
|
74
|
-
export
|
|
75
|
-
if (value === undefined) return undefined;
|
|
76
|
-
const trimmedValue = value.trim();
|
|
77
|
-
if (!/^\d+$/.test(trimmedValue)) return undefined;
|
|
78
|
-
const port = Number(trimmedValue);
|
|
79
|
-
if (!Number.isInteger(port) || port < 1 || port > 65_535) return undefined;
|
|
80
|
-
return port;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const configuredHost = process.env.PI_CHROME_DEVTOOLS_HOST ?? DEFAULT_HOST;
|
|
84
|
-
const configuredPortOverride = parseConfiguredPort(process.env.PI_CHROME_DEVTOOLS_PORT);
|
|
85
|
-
const configuredPort = configuredPortOverride ?? DEFAULT_PORT;
|
|
81
|
+
export { parseConfiguredPort };
|
|
86
82
|
|
|
87
83
|
export const state: ChromeDevToolsState = {
|
|
88
|
-
host:
|
|
89
|
-
port:
|
|
90
|
-
configuredPort,
|
|
91
|
-
hostConfigured:
|
|
92
|
-
portConfigured:
|
|
93
|
-
autoLaunchEnabled:
|
|
94
|
-
|
|
84
|
+
host: DEFAULT_HOST,
|
|
85
|
+
port: DEFAULT_PORT,
|
|
86
|
+
configuredPort: DEFAULT_PORT,
|
|
87
|
+
hostConfigured: false,
|
|
88
|
+
portConfigured: false,
|
|
89
|
+
autoLaunchEnabled: true,
|
|
90
|
+
endpointSource: "default",
|
|
91
|
+
autoLaunchSource: "default",
|
|
95
92
|
extensionPaths: [],
|
|
96
|
-
browserExecutableSource:
|
|
93
|
+
browserExecutableSource: "default",
|
|
97
94
|
extensionPathsSource: "default",
|
|
98
95
|
projectSettingsTrusted: false,
|
|
99
96
|
shuttingDown: false,
|
|
@@ -106,6 +103,14 @@ export function applyRuntimeBrowserSettings(
|
|
|
106
103
|
paths: { user: string; project?: string },
|
|
107
104
|
projectTrusted: boolean,
|
|
108
105
|
) {
|
|
106
|
+
state.host = browser.host;
|
|
107
|
+
state.port = browser.port;
|
|
108
|
+
state.configuredPort = browser.port;
|
|
109
|
+
state.hostConfigured = browser.hostConfigured;
|
|
110
|
+
state.portConfigured = browser.portConfigured;
|
|
111
|
+
state.autoLaunchEnabled = browser.autoLaunchEnabled;
|
|
112
|
+
state.endpointSource = browser.endpointSource;
|
|
113
|
+
state.autoLaunchSource = browser.autoLaunchSource;
|
|
109
114
|
state.browserExecutable = browser.executablePath;
|
|
110
115
|
state.extensionPaths = [...browser.extensionPaths];
|
|
111
116
|
state.browserExecutableSource = browser.executablePathSource;
|
package/src/settings.ts
CHANGED
|
@@ -23,11 +23,36 @@ export interface ChromeDevToolsSettings {
|
|
|
23
23
|
updatedAt: number;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
export const DEFAULT_BROWSER_HOST = "127.0.0.1";
|
|
27
|
+
export const DEFAULT_BROWSER_PORT = 9222;
|
|
28
|
+
export const DEFAULT_BROWSER_ENDPOINT = `http://${DEFAULT_BROWSER_HOST}:${DEFAULT_BROWSER_PORT}`;
|
|
29
|
+
|
|
26
30
|
export type BrowserSettingsSource = "default" | "environment" | "project" | "user";
|
|
27
31
|
|
|
32
|
+
export interface UserBrowserSettings {
|
|
33
|
+
endpoint?: string;
|
|
34
|
+
autoLaunch?: boolean;
|
|
35
|
+
executablePath?: string;
|
|
36
|
+
extensionPaths?: string[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface BrowserSettingsPatch {
|
|
40
|
+
endpoint?: string | null;
|
|
41
|
+
autoLaunch?: boolean | null;
|
|
42
|
+
executablePath?: string | null;
|
|
43
|
+
}
|
|
44
|
+
|
|
28
45
|
export interface EffectiveBrowserSettings {
|
|
46
|
+
endpoint: string;
|
|
47
|
+
host: string;
|
|
48
|
+
port: number;
|
|
49
|
+
hostConfigured: boolean;
|
|
50
|
+
portConfigured: boolean;
|
|
51
|
+
autoLaunchEnabled: boolean;
|
|
29
52
|
executablePath?: string;
|
|
30
53
|
extensionPaths: string[];
|
|
54
|
+
endpointSource: BrowserSettingsSource;
|
|
55
|
+
autoLaunchSource: BrowserSettingsSource;
|
|
31
56
|
executablePathSource: BrowserSettingsSource;
|
|
32
57
|
extensionPathsSource: BrowserSettingsSource;
|
|
33
58
|
}
|
|
@@ -43,9 +68,14 @@ export interface SettingsLoadOptions {
|
|
|
43
68
|
projectTrusted?: boolean;
|
|
44
69
|
}
|
|
45
70
|
|
|
71
|
+
export type UserSettingsFileStatus =
|
|
72
|
+
| { kind: "missing" | "valid" }
|
|
73
|
+
| { kind: "invalid"; reason: string };
|
|
74
|
+
|
|
46
75
|
interface SettingsLoadBase {
|
|
47
76
|
effectiveBrowser: EffectiveBrowserSettings;
|
|
48
77
|
paths: { user: string; project?: string };
|
|
78
|
+
userFile: UserSettingsFileStatus;
|
|
49
79
|
warnings: string[];
|
|
50
80
|
notice?: string;
|
|
51
81
|
}
|
|
@@ -60,15 +90,10 @@ export interface SettingsFileOperations {
|
|
|
60
90
|
rename(source: string, destination: string): Promise<void>;
|
|
61
91
|
}
|
|
62
92
|
|
|
63
|
-
interface NormalizedBrowserSection {
|
|
64
|
-
executablePath?: string;
|
|
65
|
-
extensionPaths?: string[];
|
|
66
|
-
}
|
|
67
|
-
|
|
68
93
|
interface NormalizedSettingsDocument {
|
|
69
94
|
tools?: ChromeDevToolsToolName[];
|
|
70
95
|
updatedAt?: number;
|
|
71
|
-
browser?:
|
|
96
|
+
browser?: UserBrowserSettings;
|
|
72
97
|
}
|
|
73
98
|
|
|
74
99
|
interface SettingsDocumentResult {
|
|
@@ -86,6 +111,10 @@ const DEFAULT_FILE_OPERATIONS: SettingsFileOperations = {
|
|
|
86
111
|
|
|
87
112
|
let settingsSaveQueue = Promise.resolve();
|
|
88
113
|
|
|
114
|
+
export async function waitForSettingsWrites() {
|
|
115
|
+
await settingsSaveQueue;
|
|
116
|
+
}
|
|
117
|
+
|
|
89
118
|
export async function loadSettings(options: SettingsLoadOptions = {}): Promise<SettingsLoadResult> {
|
|
90
119
|
await settingsSaveQueue;
|
|
91
120
|
const userPath = settingsFilePath();
|
|
@@ -127,10 +156,11 @@ export async function loadSettings(options: SettingsLoadOptions = {}): Promise<S
|
|
|
127
156
|
warnings.push(...project.warnings);
|
|
128
157
|
}
|
|
129
158
|
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
159
|
+
const environmentWarning = deprecatedEnvironmentWarning();
|
|
160
|
+
if (environmentWarning) warnings.push(environmentWarning);
|
|
161
|
+
|
|
162
|
+
const userBrowser = user.normalized?.browser ?? {};
|
|
163
|
+
const effectiveBrowser = resolveEffectiveBrowser(userBrowser, project.normalized?.browser);
|
|
134
164
|
const settings = resolveSettings(user.normalized, project.normalized, effectiveBrowser);
|
|
135
165
|
const recognized =
|
|
136
166
|
settings.tools !== undefined ||
|
|
@@ -140,9 +170,17 @@ export async function loadSettings(options: SettingsLoadOptions = {}): Promise<S
|
|
|
140
170
|
.filter((result) => result.kind === "invalid")
|
|
141
171
|
.map((result) => result.reason)
|
|
142
172
|
.filter((reason): reason is string => Boolean(reason));
|
|
173
|
+
const userFile: UserSettingsFileStatus =
|
|
174
|
+
user.kind === "invalid"
|
|
175
|
+
? {
|
|
176
|
+
kind: "invalid",
|
|
177
|
+
reason: user.reason ?? `${userPath}: invalid settings`,
|
|
178
|
+
}
|
|
179
|
+
: { kind: user.kind };
|
|
143
180
|
const base = {
|
|
144
181
|
effectiveBrowser,
|
|
145
182
|
paths,
|
|
183
|
+
userFile,
|
|
146
184
|
warnings,
|
|
147
185
|
...(warnings.length > 0 ? { notice: warnings.join("\n") } : {}),
|
|
148
186
|
};
|
|
@@ -166,28 +204,69 @@ function resolveSettings(
|
|
|
166
204
|
}
|
|
167
205
|
|
|
168
206
|
function resolveEffectiveBrowser(
|
|
169
|
-
user:
|
|
170
|
-
project:
|
|
207
|
+
user: UserBrowserSettings,
|
|
208
|
+
project: UserBrowserSettings | undefined,
|
|
171
209
|
): EffectiveBrowserSettings {
|
|
210
|
+
const configuredEndpoint = parseBrowserEndpoint(user.endpoint ?? DEFAULT_BROWSER_ENDPOINT);
|
|
211
|
+
const environmentHost = process.env.PI_CHROME_DEVTOOLS_HOST;
|
|
212
|
+
const environmentPortValue = process.env.PI_CHROME_DEVTOOLS_PORT;
|
|
213
|
+
const environmentPort = parseConfiguredPort(environmentPortValue);
|
|
214
|
+
const environmentEndpointConfigured =
|
|
215
|
+
environmentHost !== undefined || environmentPort !== undefined;
|
|
216
|
+
const host = environmentHost ?? configuredEndpoint.host;
|
|
217
|
+
const port = environmentPort ?? configuredEndpoint.port;
|
|
218
|
+
const environmentAutoLaunch = process.env.PI_CHROME_DEVTOOLS_AUTO_LAUNCH;
|
|
172
219
|
const environmentExecutable = process.env.PI_CHROME_DEVTOOLS_BROWSER;
|
|
173
|
-
const executablePath = environmentExecutable || user
|
|
174
|
-
const extensionPaths = project?.extensionPaths ?? user
|
|
220
|
+
const executablePath = environmentExecutable || user.executablePath;
|
|
221
|
+
const extensionPaths = project?.extensionPaths ?? user.extensionPaths ?? [];
|
|
222
|
+
const endpointSource: BrowserSettingsSource = environmentEndpointConfigured
|
|
223
|
+
? "environment"
|
|
224
|
+
: user.endpoint
|
|
225
|
+
? "user"
|
|
226
|
+
: "default";
|
|
175
227
|
return {
|
|
228
|
+
endpoint: formatBrowserEndpoint(host, port),
|
|
229
|
+
host,
|
|
230
|
+
port,
|
|
231
|
+
hostConfigured: environmentHost !== undefined || user.endpoint !== undefined,
|
|
232
|
+
portConfigured: environmentPort !== undefined || user.endpoint !== undefined,
|
|
233
|
+
autoLaunchEnabled:
|
|
234
|
+
environmentAutoLaunch === undefined
|
|
235
|
+
? (user.autoLaunch ?? true)
|
|
236
|
+
: environmentAutoLaunch !== "0",
|
|
176
237
|
...(executablePath ? { executablePath } : {}),
|
|
177
238
|
extensionPaths: [...extensionPaths],
|
|
239
|
+
endpointSource,
|
|
240
|
+
autoLaunchSource:
|
|
241
|
+
environmentAutoLaunch !== undefined
|
|
242
|
+
? "environment"
|
|
243
|
+
: user.autoLaunch !== undefined
|
|
244
|
+
? "user"
|
|
245
|
+
: "default",
|
|
178
246
|
executablePathSource: environmentExecutable
|
|
179
247
|
? "environment"
|
|
180
|
-
: user
|
|
248
|
+
: user.executablePath
|
|
181
249
|
? "user"
|
|
182
250
|
: "default",
|
|
183
251
|
extensionPathsSource: project?.extensionPaths
|
|
184
252
|
? "project"
|
|
185
|
-
: user
|
|
253
|
+
: user.extensionPaths
|
|
186
254
|
? "user"
|
|
187
255
|
: "default",
|
|
188
256
|
};
|
|
189
257
|
}
|
|
190
258
|
|
|
259
|
+
function deprecatedEnvironmentWarning() {
|
|
260
|
+
const configuredNames = [
|
|
261
|
+
"PI_CHROME_DEVTOOLS_HOST",
|
|
262
|
+
"PI_CHROME_DEVTOOLS_PORT",
|
|
263
|
+
"PI_CHROME_DEVTOOLS_AUTO_LAUNCH",
|
|
264
|
+
"PI_CHROME_DEVTOOLS_BROWSER",
|
|
265
|
+
].filter((name) => process.env[name] !== undefined);
|
|
266
|
+
if (configuredNames.length === 0) return undefined;
|
|
267
|
+
return `Chrome DevTools environment settings are deprecated and will be removed in a future version: ${configuredNames.join(", ")}. Move them to browser.endpoint, browser.autoLaunch, and browser.executablePath in ${settingsFilePath()}. Environment values remain active overrides during the deprecation period.`;
|
|
268
|
+
}
|
|
269
|
+
|
|
191
270
|
async function readSettingsDocument(
|
|
192
271
|
filePath: string,
|
|
193
272
|
scope: "project" | "user",
|
|
@@ -214,7 +293,7 @@ async function readSettingsDocument(
|
|
|
214
293
|
return { kind: "invalid", reason, warnings: [`Chrome DevTools settings ignored: ${reason}`] };
|
|
215
294
|
}
|
|
216
295
|
|
|
217
|
-
const scopeWarnings =
|
|
296
|
+
const scopeWarnings = projectMachineSettingsWarnings(parsed, scope, filePath);
|
|
218
297
|
try {
|
|
219
298
|
const normalized = await normalizeSettingsDocument(parsed, scope, filePath, cwd);
|
|
220
299
|
return { kind: "valid", document: { ...parsed }, normalized, warnings: scopeWarnings };
|
|
@@ -228,21 +307,19 @@ async function readSettingsDocument(
|
|
|
228
307
|
}
|
|
229
308
|
}
|
|
230
309
|
|
|
231
|
-
function
|
|
310
|
+
function projectMachineSettingsWarnings(
|
|
232
311
|
document: Record<string, unknown>,
|
|
233
312
|
scope: "project" | "user",
|
|
234
313
|
filePath: string,
|
|
235
314
|
) {
|
|
236
|
-
if (
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
`Chrome DevTools project browser.executablePath ignored in ${filePath}; configure the machine-owned executable in ${settingsFilePath()}.`,
|
|
245
|
-
];
|
|
315
|
+
if (scope !== "project" || !isRecord(document.browser)) return [];
|
|
316
|
+
const browser = document.browser;
|
|
317
|
+
return ["endpoint", "autoLaunch", "executablePath"]
|
|
318
|
+
.filter((field) => browser[field] !== undefined)
|
|
319
|
+
.map(
|
|
320
|
+
(field) =>
|
|
321
|
+
`Chrome DevTools project browser.${field} ignored in ${filePath}; configure this machine-owned setting in ${settingsFilePath()}.`,
|
|
322
|
+
);
|
|
246
323
|
}
|
|
247
324
|
|
|
248
325
|
async function normalizeSettingsDocument(
|
|
@@ -277,8 +354,20 @@ async function normalizeBrowserSection(
|
|
|
277
354
|
scope: "project" | "user",
|
|
278
355
|
_filePath: string,
|
|
279
356
|
cwd?: string,
|
|
280
|
-
): Promise<
|
|
281
|
-
const normalized:
|
|
357
|
+
): Promise<UserBrowserSettings> {
|
|
358
|
+
const normalized: UserBrowserSettings = {};
|
|
359
|
+
if (scope === "user" && browser.endpoint !== undefined) {
|
|
360
|
+
if (typeof browser.endpoint !== "string") {
|
|
361
|
+
throw new Error("expected browser.endpoint to be an HTTP URL with an explicit port");
|
|
362
|
+
}
|
|
363
|
+
normalized.endpoint = parseBrowserEndpoint(browser.endpoint).endpoint;
|
|
364
|
+
}
|
|
365
|
+
if (scope === "user" && browser.autoLaunch !== undefined) {
|
|
366
|
+
if (typeof browser.autoLaunch !== "boolean") {
|
|
367
|
+
throw new Error("expected browser.autoLaunch to be a boolean");
|
|
368
|
+
}
|
|
369
|
+
normalized.autoLaunch = browser.autoLaunch;
|
|
370
|
+
}
|
|
282
371
|
if (scope === "user" && browser.executablePath !== undefined) {
|
|
283
372
|
if (typeof browser.executablePath !== "string" || browser.executablePath.length === 0) {
|
|
284
373
|
throw new Error("expected browser.executablePath to be a non-empty absolute path");
|
|
@@ -350,6 +439,52 @@ async function validateUnpackedExtensionPath(extensionPath: string) {
|
|
|
350
439
|
return canonicalPath;
|
|
351
440
|
}
|
|
352
441
|
|
|
442
|
+
export function parseBrowserEndpoint(value: string) {
|
|
443
|
+
const trimmedValue = value.trim();
|
|
444
|
+
const explicitPort = trimmedValue.match(/^http:\/\/(?:\[[^\]]+\]|[^/:?#]+):(\d+)\/?$/i)?.[1];
|
|
445
|
+
let endpoint: URL;
|
|
446
|
+
try {
|
|
447
|
+
endpoint = new URL(trimmedValue);
|
|
448
|
+
} catch {
|
|
449
|
+
throw new Error("browser.endpoint must be a valid HTTP URL with an explicit port");
|
|
450
|
+
}
|
|
451
|
+
if (
|
|
452
|
+
endpoint.protocol !== "http:" ||
|
|
453
|
+
endpoint.username ||
|
|
454
|
+
endpoint.password ||
|
|
455
|
+
!endpoint.hostname ||
|
|
456
|
+
!explicitPort ||
|
|
457
|
+
(endpoint.pathname !== "/" && endpoint.pathname !== "") ||
|
|
458
|
+
endpoint.search ||
|
|
459
|
+
endpoint.hash
|
|
460
|
+
) {
|
|
461
|
+
throw new Error(
|
|
462
|
+
"browser.endpoint must be an HTTP origin with an explicit port and no credentials, path, query, or fragment",
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
const port = parseConfiguredPort(explicitPort);
|
|
466
|
+
if (port === undefined) {
|
|
467
|
+
throw new Error("browser.endpoint must use a port from 1 through 65535");
|
|
468
|
+
}
|
|
469
|
+
const host = endpoint.hostname;
|
|
470
|
+
return { endpoint: formatBrowserEndpoint(host, port), host, port };
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
export function parseConfiguredPort(value: string | undefined) {
|
|
474
|
+
if (value === undefined) return undefined;
|
|
475
|
+
const trimmedValue = value.trim();
|
|
476
|
+
if (!/^\d+$/.test(trimmedValue)) return undefined;
|
|
477
|
+
const port = Number(trimmedValue);
|
|
478
|
+
if (!Number.isInteger(port) || port < 1 || port > 65_535) return undefined;
|
|
479
|
+
return port;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function formatBrowserEndpoint(host: string, port: number) {
|
|
483
|
+
const formattedHost =
|
|
484
|
+
host.startsWith("[") && host.endsWith("]") ? host : host.includes(":") ? `[${host}]` : host;
|
|
485
|
+
return `http://${formattedHost}:${port}`;
|
|
486
|
+
}
|
|
487
|
+
|
|
353
488
|
export function normalizeChromeDevtoolsSettings(
|
|
354
489
|
value: unknown,
|
|
355
490
|
): ChromeDevToolsSettings | undefined {
|
|
@@ -381,13 +516,48 @@ export function saveSettings(
|
|
|
381
516
|
settings: ChromeDevToolsSettings,
|
|
382
517
|
operations: Partial<SettingsFileOperations> = {},
|
|
383
518
|
): Promise<void> {
|
|
384
|
-
|
|
519
|
+
return queueSettingsMutation(
|
|
520
|
+
(current) => ({
|
|
521
|
+
...current,
|
|
522
|
+
tools: [...settings.tools],
|
|
523
|
+
updatedAt: settings.updatedAt,
|
|
524
|
+
}),
|
|
525
|
+
operations,
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
export function saveBrowserSettings(
|
|
530
|
+
patch: BrowserSettingsPatch,
|
|
531
|
+
operations: Partial<SettingsFileOperations> = {},
|
|
532
|
+
): Promise<void> {
|
|
533
|
+
return queueSettingsMutation(async (current) => {
|
|
534
|
+
const browser = isRecord(current.browser) ? { ...current.browser } : {};
|
|
535
|
+
for (const field of ["endpoint", "autoLaunch", "executablePath"] as const) {
|
|
536
|
+
const value = patch[field];
|
|
537
|
+
if (value === undefined) continue;
|
|
538
|
+
if (value === null) delete browser[field];
|
|
539
|
+
else browser[field] = value;
|
|
540
|
+
}
|
|
541
|
+
await normalizeBrowserSection(browser, "user", settingsFilePath());
|
|
542
|
+
return { ...current, browser };
|
|
543
|
+
}, operations);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
function queueSettingsMutation(
|
|
547
|
+
mutate: (
|
|
548
|
+
current: Record<string, unknown>,
|
|
549
|
+
) => Record<string, unknown> | Promise<Record<string, unknown>>,
|
|
550
|
+
operations: Partial<SettingsFileOperations>,
|
|
551
|
+
) {
|
|
552
|
+
const operation = settingsSaveQueue.then(() => saveSettingsMutationNow(mutate, operations));
|
|
385
553
|
settingsSaveQueue = operation.catch(() => undefined);
|
|
386
554
|
return operation;
|
|
387
555
|
}
|
|
388
556
|
|
|
389
|
-
async function
|
|
390
|
-
|
|
557
|
+
async function saveSettingsMutationNow(
|
|
558
|
+
mutate: (
|
|
559
|
+
current: Record<string, unknown>,
|
|
560
|
+
) => Record<string, unknown> | Promise<Record<string, unknown>>,
|
|
391
561
|
operations: Partial<SettingsFileOperations>,
|
|
392
562
|
): Promise<void> {
|
|
393
563
|
const filePath = settingsFilePath();
|
|
@@ -397,11 +567,7 @@ async function saveSettingsNow(
|
|
|
397
567
|
if (current.kind === "invalid") {
|
|
398
568
|
throw new Error(`Cannot save Chrome DevTools settings until you repair ${current.reason}`);
|
|
399
569
|
}
|
|
400
|
-
const nextDocument = {
|
|
401
|
-
...(current.document ?? {}),
|
|
402
|
-
tools: [...settings.tools],
|
|
403
|
-
updatedAt: settings.updatedAt,
|
|
404
|
-
};
|
|
570
|
+
const nextDocument = await mutate(current.document ?? {});
|
|
405
571
|
await mkdir(dirname(filePath), { recursive: true });
|
|
406
572
|
const tempFile = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
|
|
407
573
|
try {
|
package/src/tool-selector.ts
CHANGED
|
@@ -268,10 +268,11 @@ function browserSettingsStatusLines() {
|
|
|
268
268
|
`Project settings: ${state.projectSettingsFilePath} (${state.projectSettingsTrusted ? "trusted" : "untrusted; ignored"})`,
|
|
269
269
|
]
|
|
270
270
|
: []),
|
|
271
|
+
`Auto-launch: ${state.autoLaunchEnabled ? "on" : "off"} (${state.autoLaunchSource})`,
|
|
271
272
|
`Browser executable: ${state.browserExecutable ?? "automatic discovery"} (${state.browserExecutableSource})`,
|
|
272
273
|
`Unpacked extensions (${state.extensionPathsSource}):`,
|
|
273
274
|
...extensionLines,
|
|
274
|
-
"
|
|
275
|
+
"Confirmed menu settings apply before the next browser connection; manual JSON edits require /reload or session replacement.",
|
|
275
276
|
...(state.extensionPaths.length > 0
|
|
276
277
|
? [
|
|
277
278
|
"Unpacked extensions require Chrome for Testing or Chromium and execute trusted browser code.",
|
|
@@ -287,6 +288,7 @@ export function buildCommandGuide() {
|
|
|
287
288
|
"/chrome-devtools help — show command usage",
|
|
288
289
|
"/chrome-devtools quickstart — show endpoint and launch help",
|
|
289
290
|
"/chrome-devtools status — show tool and settings status",
|
|
291
|
+
"/chrome-devtools settings — edit browser connection settings",
|
|
290
292
|
"/chrome-devtools tools — choose tools available to lazy-load",
|
|
291
293
|
"/chrome-devtools toggle|select — compatibility aliases for tools",
|
|
292
294
|
"/chrome-devtools enable|on — make all Chrome DevTools tools available to lazy-load",
|