browser-commander 0.21.2 → 0.22.0

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,399 @@
1
+ {
2
+ "comment": [
3
+ "Catalogue of installed browsers Browser Commander can import from (issue",
4
+ "#114). Each entry says where a browser keeps its profiles on every",
5
+ "platform, how its Chromium Safe Storage key is named, and how the OS",
6
+ "records it as the default browser, so profile discovery, cookie",
7
+ "decryption and `browser: 'default'` resolution are all data-driven and",
8
+ "identical across JavaScript, Python and Rust.",
9
+ "",
10
+ "Root templates use these placeholders, expanded per platform:",
11
+ " {home} the user home directory",
12
+ " {appSupport} {home}/Library/Application Support (darwin only)",
13
+ " {localAppData} %LOCALAPPDATA% (win32 only)",
14
+ " {appData} %APPDATA% / roaming (win32 only)",
15
+ " {config} {home}/.config (linux only)",
16
+ "A browser with \"singleProfile\": true keeps its data in the root itself",
17
+ "(one profile named \"Default\"); otherwise the root holds Chromium profile",
18
+ "subdirectories (Default, Profile 1, ...) or a Firefox profiles.ini.",
19
+ "",
20
+ "This file is shared, not translated: Python and Rust ship byte-identical",
21
+ "copies, checked by scripts/check-shared-fingerprint-assets.sh."
22
+ ],
23
+ "browsers": [
24
+ {
25
+ "id": "chrome",
26
+ "family": "chromium",
27
+ "aliases": ["google-chrome", "google chrome"],
28
+ "roots": {
29
+ "darwin": ["{appSupport}/Google/Chrome"],
30
+ "win32": ["{localAppData}/Google/Chrome/User Data"],
31
+ "linux": ["{config}/google-chrome"]
32
+ },
33
+ "safeStorage": {
34
+ "service": "Chrome Safe Storage",
35
+ "application": "chrome",
36
+ "folder": "Chrome Keys"
37
+ },
38
+ "default": {
39
+ "darwin": ["com.google.chrome"],
40
+ "linux": ["google-chrome.desktop", "google-chrome-stable.desktop"],
41
+ "win32": ["ChromeHTML"]
42
+ }
43
+ },
44
+ {
45
+ "id": "chrome-beta",
46
+ "family": "chromium",
47
+ "aliases": ["google-chrome-beta"],
48
+ "roots": {
49
+ "darwin": ["{appSupport}/Google/Chrome Beta"],
50
+ "win32": ["{localAppData}/Google/Chrome Beta/User Data"],
51
+ "linux": ["{config}/google-chrome-beta"]
52
+ },
53
+ "safeStorage": {
54
+ "service": "Chrome Safe Storage",
55
+ "application": "google-chrome-beta",
56
+ "folder": "Chrome Keys"
57
+ },
58
+ "default": {
59
+ "darwin": ["com.google.chrome.beta"],
60
+ "linux": ["google-chrome-beta.desktop"],
61
+ "win32": ["ChromeBHTML"]
62
+ }
63
+ },
64
+ {
65
+ "id": "chrome-dev",
66
+ "family": "chromium",
67
+ "aliases": ["google-chrome-dev", "google-chrome-unstable"],
68
+ "roots": {
69
+ "darwin": ["{appSupport}/Google/Chrome Dev"],
70
+ "win32": ["{localAppData}/Google/Chrome Dev/User Data"],
71
+ "linux": ["{config}/google-chrome-unstable"]
72
+ },
73
+ "safeStorage": {
74
+ "service": "Chrome Safe Storage",
75
+ "application": "google-chrome-unstable",
76
+ "folder": "Chrome Keys"
77
+ },
78
+ "default": {
79
+ "darwin": ["com.google.chrome.dev"],
80
+ "linux": ["google-chrome-unstable.desktop"],
81
+ "win32": ["ChromeDHTML"]
82
+ }
83
+ },
84
+ {
85
+ "id": "chrome-canary",
86
+ "family": "chromium",
87
+ "aliases": ["google-chrome-canary"],
88
+ "roots": {
89
+ "darwin": ["{appSupport}/Google/Chrome Canary"],
90
+ "win32": ["{localAppData}/Google/Chrome SxS/User Data"]
91
+ },
92
+ "safeStorage": {
93
+ "service": "Chrome Safe Storage",
94
+ "application": "chrome",
95
+ "folder": "Chrome Keys"
96
+ },
97
+ "default": {
98
+ "darwin": ["com.google.chrome.canary"],
99
+ "win32": ["ChromeSSHTM"]
100
+ }
101
+ },
102
+ {
103
+ "id": "chromium",
104
+ "family": "chromium",
105
+ "aliases": [],
106
+ "roots": {
107
+ "darwin": ["{appSupport}/Chromium"],
108
+ "win32": ["{localAppData}/Chromium/User Data"],
109
+ "linux": ["{config}/chromium"]
110
+ },
111
+ "safeStorage": {
112
+ "service": "Chromium Safe Storage",
113
+ "application": "chromium",
114
+ "folder": "Chromium Keys"
115
+ },
116
+ "default": {
117
+ "darwin": ["org.chromium.chromium"],
118
+ "linux": ["chromium.desktop", "chromium-browser.desktop"]
119
+ }
120
+ },
121
+ {
122
+ "id": "edge",
123
+ "family": "chromium",
124
+ "aliases": ["msedge", "microsoft-edge"],
125
+ "roots": {
126
+ "darwin": ["{appSupport}/Microsoft Edge"],
127
+ "win32": ["{localAppData}/Microsoft/Edge/User Data"],
128
+ "linux": ["{config}/microsoft-edge"]
129
+ },
130
+ "safeStorage": {
131
+ "service": "Microsoft Edge Safe Storage",
132
+ "application": "microsoft-edge",
133
+ "folder": "Microsoft Edge Keys"
134
+ },
135
+ "default": {
136
+ "darwin": ["com.microsoft.edgemac"],
137
+ "linux": ["microsoft-edge.desktop"],
138
+ "win32": ["MSEdgeHTM"]
139
+ }
140
+ },
141
+ {
142
+ "id": "edge-beta",
143
+ "family": "chromium",
144
+ "aliases": ["microsoft-edge-beta"],
145
+ "roots": {
146
+ "darwin": ["{appSupport}/Microsoft Edge Beta"],
147
+ "win32": ["{localAppData}/Microsoft/Edge Beta/User Data"],
148
+ "linux": ["{config}/microsoft-edge-beta"]
149
+ },
150
+ "safeStorage": {
151
+ "service": "Microsoft Edge Safe Storage",
152
+ "application": "microsoft-edge-beta",
153
+ "folder": "Microsoft Edge Keys"
154
+ },
155
+ "default": {
156
+ "darwin": ["com.microsoft.edgemac.beta"],
157
+ "linux": ["microsoft-edge-beta.desktop"]
158
+ }
159
+ },
160
+ {
161
+ "id": "edge-dev",
162
+ "family": "chromium",
163
+ "aliases": ["microsoft-edge-dev"],
164
+ "roots": {
165
+ "darwin": ["{appSupport}/Microsoft Edge Dev"],
166
+ "win32": ["{localAppData}/Microsoft/Edge Dev/User Data"],
167
+ "linux": ["{config}/microsoft-edge-dev"]
168
+ },
169
+ "safeStorage": {
170
+ "service": "Microsoft Edge Safe Storage",
171
+ "application": "microsoft-edge-dev",
172
+ "folder": "Microsoft Edge Keys"
173
+ },
174
+ "default": {
175
+ "darwin": ["com.microsoft.edgemac.dev"],
176
+ "linux": ["microsoft-edge-dev.desktop"]
177
+ }
178
+ },
179
+ {
180
+ "id": "brave",
181
+ "family": "chromium",
182
+ "aliases": ["brave-browser"],
183
+ "roots": {
184
+ "darwin": ["{appSupport}/BraveSoftware/Brave-Browser"],
185
+ "win32": ["{localAppData}/BraveSoftware/Brave-Browser/User Data"],
186
+ "linux": ["{config}/BraveSoftware/Brave-Browser"]
187
+ },
188
+ "safeStorage": {
189
+ "service": "Brave Safe Storage",
190
+ "application": "brave",
191
+ "folder": "Brave Keys"
192
+ },
193
+ "default": {
194
+ "darwin": ["com.brave.browser"],
195
+ "linux": ["brave-browser.desktop", "brave.desktop"],
196
+ "win32": ["BraveHTML"]
197
+ }
198
+ },
199
+ {
200
+ "id": "vivaldi",
201
+ "family": "chromium",
202
+ "aliases": [],
203
+ "roots": {
204
+ "darwin": ["{appSupport}/Vivaldi"],
205
+ "win32": ["{localAppData}/Vivaldi/User Data"],
206
+ "linux": ["{config}/vivaldi"]
207
+ },
208
+ "safeStorage": {
209
+ "service": "Vivaldi Safe Storage",
210
+ "application": "vivaldi",
211
+ "folder": "Vivaldi Keys"
212
+ },
213
+ "default": {
214
+ "darwin": ["com.vivaldi.vivaldi"],
215
+ "linux": ["vivaldi.desktop", "vivaldi-stable.desktop"],
216
+ "win32": ["VivaldiHTM"]
217
+ }
218
+ },
219
+ {
220
+ "id": "opera",
221
+ "family": "chromium",
222
+ "aliases": [],
223
+ "singleProfile": true,
224
+ "roots": {
225
+ "darwin": ["{appSupport}/com.operasoftware.Opera"],
226
+ "win32": ["{appData}/Opera Software/Opera Stable"],
227
+ "linux": ["{config}/opera"]
228
+ },
229
+ "safeStorage": {
230
+ "service": "Opera Safe Storage",
231
+ "application": "opera",
232
+ "folder": "Opera Keys"
233
+ },
234
+ "default": {
235
+ "darwin": ["com.operasoftware.opera"],
236
+ "linux": ["opera.desktop", "opera_opera.desktop"],
237
+ "win32": ["OperaStable"]
238
+ }
239
+ },
240
+ {
241
+ "id": "opera-gx",
242
+ "family": "chromium",
243
+ "aliases": ["operagx"],
244
+ "singleProfile": true,
245
+ "roots": {
246
+ "darwin": ["{appSupport}/com.operasoftware.OperaGX"],
247
+ "win32": ["{appData}/Opera Software/Opera GX Stable"],
248
+ "linux": ["{config}/opera-gx"]
249
+ },
250
+ "safeStorage": {
251
+ "service": "Opera Safe Storage",
252
+ "application": "opera-gx",
253
+ "folder": "Opera Keys"
254
+ },
255
+ "default": {
256
+ "darwin": ["com.operasoftware.operagx"],
257
+ "win32": ["OperaGXStable"]
258
+ }
259
+ },
260
+ {
261
+ "id": "yandex",
262
+ "family": "chromium",
263
+ "aliases": ["yandex-browser", "yandexbrowser"],
264
+ "roots": {
265
+ "darwin": ["{appSupport}/Yandex/YandexBrowser"],
266
+ "win32": ["{localAppData}/Yandex/YandexBrowser/User Data"],
267
+ "linux": ["{config}/yandex-browser"]
268
+ },
269
+ "safeStorage": {
270
+ "service": "Yandex Safe Storage",
271
+ "application": "yandex-browser",
272
+ "folder": "Yandex Keys"
273
+ },
274
+ "default": {
275
+ "darwin": ["ru.yandex.desktop.yandex-browser"],
276
+ "linux": ["yandex-browser.desktop"],
277
+ "win32": ["YandexHTML"]
278
+ }
279
+ },
280
+ {
281
+ "id": "arc",
282
+ "family": "chromium",
283
+ "aliases": [],
284
+ "roots": {
285
+ "darwin": ["{appSupport}/Arc/User Data"],
286
+ "win32": [
287
+ "{localAppData}/Packages/TheBrowserCompany.Arc_ttt1ap7aakyb4/LocalCache/Local/Arc/User Data"
288
+ ]
289
+ },
290
+ "safeStorage": {
291
+ "service": "Arc Safe Storage",
292
+ "application": "arc",
293
+ "folder": "Arc Keys"
294
+ },
295
+ "default": {
296
+ "darwin": ["company.thebrowser.browser"],
297
+ "win32": ["ArcHTML"]
298
+ }
299
+ },
300
+ {
301
+ "id": "firefox",
302
+ "family": "firefox",
303
+ "aliases": ["mozilla-firefox"],
304
+ "roots": {
305
+ "darwin": ["{appSupport}/Firefox"],
306
+ "win32": ["{appData}/Mozilla/Firefox"],
307
+ "linux": ["{home}/.mozilla/firefox"]
308
+ },
309
+ "default": {
310
+ "darwin": ["org.mozilla.firefox"],
311
+ "linux": ["firefox.desktop", "firefox-esr.desktop"],
312
+ "win32": ["FirefoxHTML", "FirefoxURL"]
313
+ }
314
+ },
315
+ {
316
+ "id": "firefox-developer",
317
+ "family": "firefox",
318
+ "aliases": ["firefox-dev", "firefox-developer-edition"],
319
+ "roots": {
320
+ "darwin": ["{appSupport}/Firefox"],
321
+ "win32": ["{appData}/Mozilla/Firefox"],
322
+ "linux": ["{home}/.mozilla/firefox"]
323
+ },
324
+ "default": {
325
+ "darwin": ["org.mozilla.firefoxdeveloperedition"]
326
+ }
327
+ },
328
+ {
329
+ "id": "firefox-nightly",
330
+ "family": "firefox",
331
+ "aliases": ["nightly"],
332
+ "roots": {
333
+ "darwin": ["{appSupport}/Firefox"],
334
+ "win32": ["{appData}/Mozilla/Firefox"],
335
+ "linux": ["{home}/.mozilla/firefox"]
336
+ },
337
+ "default": {
338
+ "darwin": ["org.mozilla.nightly"],
339
+ "linux": ["firefox-nightly.desktop"]
340
+ }
341
+ },
342
+ {
343
+ "id": "librewolf",
344
+ "family": "firefox",
345
+ "aliases": [],
346
+ "roots": {
347
+ "darwin": ["{appSupport}/LibreWolf"],
348
+ "win32": ["{appData}/librewolf"],
349
+ "linux": ["{home}/.librewolf"]
350
+ },
351
+ "default": {
352
+ "darwin": ["io.gitlab.librewolf-community"],
353
+ "linux": ["librewolf.desktop"]
354
+ }
355
+ },
356
+ {
357
+ "id": "waterfox",
358
+ "family": "firefox",
359
+ "aliases": [],
360
+ "roots": {
361
+ "darwin": ["{appSupport}/Waterfox"],
362
+ "win32": ["{appData}/Waterfox"],
363
+ "linux": ["{home}/.waterfox"]
364
+ },
365
+ "default": {
366
+ "darwin": ["net.waterfox.waterfox"],
367
+ "linux": ["waterfox.desktop"]
368
+ }
369
+ },
370
+ {
371
+ "id": "zen",
372
+ "family": "firefox",
373
+ "aliases": ["zen-browser"],
374
+ "roots": {
375
+ "darwin": ["{appSupport}/zen"],
376
+ "win32": ["{appData}/zen"],
377
+ "linux": ["{home}/.zen"]
378
+ },
379
+ "default": {
380
+ "darwin": ["app.zen-browser.zen"],
381
+ "linux": ["zen.desktop", "zen-browser.desktop"]
382
+ }
383
+ },
384
+ {
385
+ "id": "floorp",
386
+ "family": "firefox",
387
+ "aliases": [],
388
+ "roots": {
389
+ "darwin": ["{appSupport}/Floorp"],
390
+ "win32": ["{appData}/Floorp"],
391
+ "linux": ["{home}/.floorp"]
392
+ },
393
+ "default": {
394
+ "darwin": ["org.floorp.Floorp"],
395
+ "linux": ["floorp.desktop"]
396
+ }
397
+ }
398
+ ]
399
+ }
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Resolve the operating-system default web browser to a canonical browser id
3
+ * from the shared catalogue (#114), so `browser: 'default'` imports from
4
+ * whichever browser a person actually uses.
5
+ *
6
+ * Each platform records the default differently:
7
+ *
8
+ * - macOS keeps it in the LaunchServices database as the `https` URL-scheme
9
+ * handler's bundle id (`defaults read
10
+ * com.apple.LaunchServices/com.apple.launchservices.secure LSHandlers`).
11
+ * - Linux reports it through `xdg-settings get default-web-browser` (a
12
+ * `.desktop` file name), falling back to the `x-scheme-handler/https`
13
+ * association from `xdg-mime`.
14
+ * - Windows stores the `https` UserChoice ProgId under
15
+ * `HKCU\...\UrlAssociations\https\UserChoice`.
16
+ *
17
+ * Every lookup runs through an injected command runner so the resolver is
18
+ * deterministic in tests, and the raw identifier is matched against the
19
+ * `default` identifiers each browser declares in `browser-sources.json`.
20
+ */
21
+ import { runCommand as runSubprocess } from '../utilities/subprocess.js';
22
+ import { BROWSER_SOURCES } from './browser-sources.js';
23
+
24
+ async function defaultRunCommand(command, args, environment) {
25
+ const { stdout } = await runSubprocess(command, args, {
26
+ env: environment,
27
+ check: false,
28
+ });
29
+ return stdout;
30
+ }
31
+
32
+ /**
33
+ * Match a raw OS identifier (bundle id, `.desktop` name or ProgId) to a
34
+ * canonical browser id. Comparison is case-insensitive because the registry
35
+ * and the operating systems disagree on casing.
36
+ */
37
+ function browserForIdentifier(identifier, platform) {
38
+ if (!identifier) {
39
+ return null;
40
+ }
41
+ const needle = identifier.trim().toLowerCase();
42
+ if (!needle) {
43
+ return null;
44
+ }
45
+ for (const source of BROWSER_SOURCES) {
46
+ const identifiers = source.default?.[platform] ?? [];
47
+ if (identifiers.some((candidate) => candidate.toLowerCase() === needle)) {
48
+ return source.id;
49
+ }
50
+ }
51
+ return null;
52
+ }
53
+
54
+ function parseMacLaunchServicesHandler(output) {
55
+ // `defaults read` prints NeXTSTEP-style plist text; each handler is a brace
56
+ // block that may carry an LSHandlerURLScheme and an LSHandlerRoleAll bundle
57
+ // id. Find the block that handles https and read its bundle id.
58
+ for (const block of output.split('}')) {
59
+ if (!/LSHandlerURLScheme\s*=\s*"?https"?\s*;/.test(block)) {
60
+ continue;
61
+ }
62
+ const match = /LSHandlerRoleAll\s*=\s*"?([^";]+)"?\s*;/.exec(block);
63
+ if (match) {
64
+ return match[1].trim();
65
+ }
66
+ }
67
+ return null;
68
+ }
69
+
70
+ async function resolveDarwinDefault(runCommand, environment) {
71
+ const output = await runCommand(
72
+ 'defaults',
73
+ [
74
+ 'read',
75
+ 'com.apple.LaunchServices/com.apple.launchservices.secure',
76
+ 'LSHandlers',
77
+ ],
78
+ environment
79
+ );
80
+ return browserForIdentifier(parseMacLaunchServicesHandler(output), 'darwin');
81
+ }
82
+
83
+ async function resolveLinuxDefault(runCommand, environment) {
84
+ let desktop = '';
85
+ try {
86
+ desktop = (
87
+ await runCommand(
88
+ 'xdg-settings',
89
+ ['get', 'default-web-browser'],
90
+ environment
91
+ )
92
+ ).trim();
93
+ } catch {
94
+ // xdg-settings may be absent; fall through to the xdg-mime query.
95
+ }
96
+ let resolved = browserForIdentifier(desktop, 'linux');
97
+ if (resolved) {
98
+ return resolved;
99
+ }
100
+ try {
101
+ const fallback = (
102
+ await runCommand(
103
+ 'xdg-mime',
104
+ ['query', 'default', 'x-scheme-handler/https'],
105
+ environment
106
+ )
107
+ ).trim();
108
+ resolved = browserForIdentifier(fallback, 'linux');
109
+ } catch {
110
+ resolved = null;
111
+ }
112
+ return resolved;
113
+ }
114
+
115
+ function parseWindowsProgId(output) {
116
+ // `reg query` prints ` ProgId REG_SZ FirefoxHTML`; take the last
117
+ // whitespace-separated token of the ProgId line.
118
+ for (const line of output.split(/\r?\n/)) {
119
+ if (/\bProgId\b/i.test(line)) {
120
+ const tokens = line.trim().split(/\s+/);
121
+ return tokens[tokens.length - 1];
122
+ }
123
+ }
124
+ return null;
125
+ }
126
+
127
+ async function resolveWindowsDefault(runCommand, environment) {
128
+ let output;
129
+ try {
130
+ output = await runCommand(
131
+ 'reg',
132
+ [
133
+ 'query',
134
+ 'HKCU\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\https\\UserChoice',
135
+ '/v',
136
+ 'ProgId',
137
+ ],
138
+ environment
139
+ );
140
+ } catch {
141
+ return null;
142
+ }
143
+ return browserForIdentifier(parseWindowsProgId(output), 'win32');
144
+ }
145
+
146
+ /**
147
+ * Resolve the system default browser to a canonical catalogue id.
148
+ *
149
+ * @param {Object} [options]
150
+ * @param {string} [options.platform=process.platform]
151
+ * @param {Object<string,string>} [options.environment=process.env]
152
+ * @param {Function} [options.runCommand] - `(command, args, env) => stdout`
153
+ * @returns {Promise<string|null>} The browser id, or null when it cannot be resolved
154
+ */
155
+ export async function resolveDefaultBrowser({
156
+ platform = process.platform,
157
+ environment = process.env,
158
+ runCommand = defaultRunCommand,
159
+ } = {}) {
160
+ if (platform === 'darwin') {
161
+ return await resolveDarwinDefault(runCommand, environment);
162
+ }
163
+ if (platform === 'linux') {
164
+ return await resolveLinuxDefault(runCommand, environment);
165
+ }
166
+ if (platform === 'win32') {
167
+ return await resolveWindowsDefault(runCommand, environment);
168
+ }
169
+ return null;
170
+ }
171
+
172
+ export {
173
+ browserForIdentifier,
174
+ parseMacLaunchServicesHandler,
175
+ parseWindowsProgId,
176
+ };
@@ -116,6 +116,9 @@ export async function migrateCookies({
116
116
  const batch = await readCookies({
117
117
  browser,
118
118
  profile,
119
+ // Read from the exact directory the migration resolved (which honours a
120
+ // custom `userDataDir`) instead of re-resolving the default profile.
121
+ profileDir: sourceProfileDir,
119
122
  domainFilter: domainFilter ?? undefined,
120
123
  ignoreDecryptionErrors: true,
121
124
  ...readerOptions,
@@ -1,9 +1,10 @@
1
1
  import path from 'node:path';
2
2
  import os from 'node:os';
3
3
 
4
+ import { resolveImportSource } from '../browser-cookies.js';
5
+ import { browserFamily, isSingleProfileBrowser } from '../browser-sources.js';
4
6
  import {
5
7
  browserProfileRoot,
6
- normalizeCookieBrowser,
7
8
  resolveBrowserProfile,
8
9
  } from '../browser-profiles.js';
9
10
  import { migrateBookmarks } from './bookmarks.js';
@@ -48,7 +49,17 @@ export const ALL_DATA_CLASSES = Object.freeze([
48
49
  'extensions',
49
50
  ]);
50
51
 
51
- const CHROMIUM_BROWSERS = new Set(['chrome', 'chromium', 'brave', 'edge']);
52
+ // Classification is driven by the shared catalogue so every Chromium variant
53
+ // (vivaldi, opera, arc, the chrome/edge channels, …) and every Firefox fork
54
+ // (librewolf, waterfox, zen, floorp, the firefox channels, …) is handled the
55
+ // same way as its canonical engine, not just the original four browsers.
56
+ function isChromiumBrowser(browser) {
57
+ return browserFamily(browser) === 'chromium';
58
+ }
59
+
60
+ function isFirefoxBrowser(browser) {
61
+ return browserFamily(browser) === 'firefox';
62
+ }
52
63
 
53
64
  function emptyMigrated() {
54
65
  return {
@@ -69,21 +80,23 @@ async function resolveSourceProfileDir({
69
80
  homeDir,
70
81
  environment,
71
82
  }) {
83
+ // A Chromium profile lives in a named subdirectory of the user data dir,
84
+ // except in single-profile browsers (Opera) that keep it in the root; for
85
+ // Firefox the userDataDir already points at the profile.
86
+ const nestsProfiles =
87
+ isChromiumBrowser(browser) && !isSingleProfileBrowser(browser);
72
88
  if (userDataDir) {
73
- // For Chromium a profile lives in a named subdirectory; for Firefox the
74
- // userDataDir already points at the profile.
75
- if (CHROMIUM_BROWSERS.has(browser)) {
76
- return path.join(userDataDir, profile ?? 'Default');
77
- }
78
- return userDataDir;
89
+ return nestsProfiles
90
+ ? path.join(userDataDir, profile ?? 'Default')
91
+ : userDataDir;
79
92
  }
80
- if (CHROMIUM_BROWSERS.has(browser)) {
93
+ if (isChromiumBrowser(browser)) {
81
94
  const root = browserProfileRoot(browser, {
82
95
  platform,
83
96
  homeDir,
84
97
  environment,
85
98
  });
86
- return path.join(root, profile ?? 'Default');
99
+ return nestsProfiles ? path.join(root, profile ?? 'Default') : root;
87
100
  }
88
101
  const resolved = await resolveBrowserProfile({
89
102
  browser,
@@ -120,7 +133,7 @@ async function resolvePasswordKeys({
120
133
  platform,
121
134
  environment,
122
135
  });
123
- const resolveSourceKey = CHROMIUM_BROWSERS.has(browser)
136
+ const resolveSourceKey = isChromiumBrowser(browser)
124
137
  ? createSourceKeyResolver({
125
138
  browser,
126
139
  platform,
@@ -156,6 +169,7 @@ export async function migrateProfile({
156
169
  keys,
157
170
  homeDir = os.homedir(),
158
171
  environment = process.env,
172
+ runCommand,
159
173
  }) {
160
174
  if (!from?.browser) {
161
175
  throw new TypeError('migrateProfile requires from.browser');
@@ -163,9 +177,19 @@ export async function migrateProfile({
163
177
  if (!to) {
164
178
  throw new TypeError('migrateProfile requires a target directory (to)');
165
179
  }
166
- const browser = normalizeCookieBrowser(from.browser);
167
- const profile = from.profile ?? 'Default';
168
- const isFirefox = browser === 'firefox';
180
+ // An explicit userDataDir names the source, so only an installed-browser
181
+ // import is steered towards the profile holding the requested domains.
182
+ const source = await resolveImportSource({
183
+ browser: from.browser,
184
+ domains: from.userDataDir ? undefined : domains,
185
+ platform,
186
+ homeDir,
187
+ environment,
188
+ runCommand,
189
+ });
190
+ const { browser } = source;
191
+ const profile = from.profile ?? source.profile ?? 'Default';
192
+ const isFirefox = isFirefoxBrowser(browser);
169
193
  const sourceProfileDir = await resolveSourceProfileDir({
170
194
  browser,
171
195
  profile,
@@ -183,6 +207,9 @@ export async function migrateProfile({
183
207
  warnings: [],
184
208
  cookies: [],
185
209
  };
210
+ if (source.warning) {
211
+ report.warnings.push(source.warning);
212
+ }
186
213
 
187
214
  if (selected.has('cookies')) {
188
215
  if (isFirefox) {