package-registry-manager 0.22.2 → 0.22.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "package-registry-manager",
3
- "version": "0.22.2",
3
+ "version": "0.22.3",
4
4
  "description": "Inspect repositories and guide secure package-registry setup",
5
5
  "type": "module",
6
6
  "bin": {
@@ -9,7 +9,7 @@
9
9
  },
10
10
  "exports": "./src/index.mjs",
11
11
  "engines": {
12
- "node": ">=22"
12
+ "node": ">=22.12.0"
13
13
  },
14
14
  "repository": {
15
15
  "type": "git",
@@ -28,7 +28,7 @@
28
28
  "provenance": true
29
29
  },
30
30
  "scripts": {
31
- "syntax": "node --check src/cli.mjs && node --check src/direct-execution.mjs && node --check src/index.mjs && node --check src/browser.mjs && node --check src/browser-options.mjs && node --check src/automation.mjs && node --check src/profile.mjs && node --check src/setup.mjs && node --check src/auth-urls.mjs && node --check src/containers.mjs && node --check src/flows.mjs && node --check src/prerequisites.mjs && node --check src/registry-state.mjs && node --check src/workflows.mjs && node --check src/publishers.mjs && node --check src/skips.mjs && node --check src/tokens.mjs && node --check src/approvals.mjs && node --check src/default-browser.mjs && node --check src/pages.mjs && node --check src/crates-api.mjs && node --check src/sign-in-import.mjs && node --check scripts/publish-to-npm.mjs && node --check scripts/setup-npm.mjs && node --check scripts/sanitize-npm-userconfig.mjs",
31
+ "syntax": "node --check src/cli.mjs && node --check src/direct-execution.mjs && node --check src/index.mjs && node --check src/browser.mjs && node --check src/browser-options.mjs && node --check src/browser-catalogue.mjs && node --check src/automation.mjs && node --check src/profile.mjs && node --check src/setup.mjs && node --check src/auth-urls.mjs && node --check src/containers.mjs && node --check src/flows.mjs && node --check src/prerequisites.mjs && node --check src/registry-state.mjs && node --check src/workflows.mjs && node --check src/publishers.mjs && node --check src/skips.mjs && node --check src/tokens.mjs && node --check src/approvals.mjs && node --check src/default-browser.mjs && node --check src/pages.mjs && node --check src/crates-api.mjs && node --check src/sign-in-import.mjs && node --check scripts/publish-to-npm.mjs && node --check scripts/setup-npm.mjs && node --check scripts/sanitize-npm-userconfig.mjs",
32
32
  "lint": "eslint src test scripts --max-warnings 0",
33
33
  "lint:fix": "eslint src test scripts --fix --max-warnings 0",
34
34
  "format": "prettier --write .",
@@ -39,13 +39,13 @@
39
39
  "pack:check": "npm pack --dry-run"
40
40
  },
41
41
  "dependencies": {
42
- "browser-commander": "^0.22.0",
43
- "command-stream": "^1.3.0",
42
+ "browser-commander": "^0.25.0",
43
+ "command-stream": "^1.5.0",
44
44
  "playwright": "^1.63.0"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@eslint/js": "^10.0.1",
48
- "eslint": "^10.11.0",
48
+ "eslint": "^10.12.0",
49
49
  "eslint-config-prettier": "^10.1.8",
50
50
  "jscpd": "^5.4.0",
51
51
  "prettier": "^3.9.9"
@@ -0,0 +1,133 @@
1
+ import { existsSync } from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+
5
+ import { BROWSER_IDS, findBrowserSource } from "browser-commander";
6
+
7
+ /** Catalogue entries reached through Browser Commander's public exports. */
8
+ export const browserSources = () => BROWSER_IDS.map(findBrowserSource);
9
+
10
+ /** The installed channels supported by Browser Commander's real launcher. */
11
+ export function launchChannels() {
12
+ return browserSources()
13
+ .filter((source) => source.controlProtocol === "cdp")
14
+ .flatMap((source) => [source.id, ...(source.aliases ?? [])]);
15
+ }
16
+
17
+ /** A readable name from the catalogue's application path or canonical alias. */
18
+ export function catalogueName(id) {
19
+ const source = findBrowserSource(id);
20
+ if (!source) {
21
+ return id;
22
+ }
23
+ const app = source.executables?.darwin?.[0]?.match(/\/([^/]+)\.app\//)?.[1];
24
+ // Preserve the established short name of Brave.
25
+ if (app) {
26
+ return app === "Brave Browser" ? "Brave" : app;
27
+ }
28
+ const label = [source.id, ...(source.aliases ?? [])]
29
+ .map((name) => name.replace(/^apple-/, ""))
30
+ .reduce((longest, name) => (name.length > longest.length ? name : longest));
31
+ return label
32
+ .replace(/^apple-/, "")
33
+ .split("-")
34
+ .map((word) =>
35
+ word.length <= 2
36
+ ? word.toUpperCase()
37
+ : word[0].toUpperCase() + word.slice(1),
38
+ )
39
+ .join(" ")
40
+ .replace("Duckduckgo", "DuckDuckGo");
41
+ }
42
+
43
+ /** Installed executables or existing profile roots; never reads cookie data. */
44
+ export function installedBrowsers(options = {}) {
45
+ const exists = options.exists ?? existsSync;
46
+ const platform = options.platform ?? process.platform;
47
+ const environment = options.environment ?? process.env;
48
+ const discovery = {
49
+ ...options,
50
+ platform,
51
+ environment:
52
+ platform === "win32"
53
+ ? Object.fromEntries(
54
+ Object.entries(environment).map(([key, value]) => [
55
+ key.toUpperCase(),
56
+ value,
57
+ ]),
58
+ )
59
+ : environment,
60
+ };
61
+ return browserSources()
62
+ .filter((source) =>
63
+ installedCandidates(source, discovery).some((candidate) =>
64
+ exists(candidate),
65
+ ),
66
+ )
67
+ .map((source) => source.id);
68
+ }
69
+
70
+ function installedCandidates(
71
+ source,
72
+ {
73
+ platform = process.platform,
74
+ homeDir = os.homedir(),
75
+ environment = process.env,
76
+ },
77
+ ) {
78
+ const pathApi = platform === "win32" ? path.win32 : path.posix;
79
+ const variables = {
80
+ home: homeDir,
81
+ config: environment.XDG_CONFIG_HOME ?? pathApi.join(homeDir, ".config"),
82
+ appSupport: pathApi.join(homeDir, "Library", "Application Support"),
83
+ appData: environment.APPDATA ?? pathApi.join(homeDir, "AppData", "Roaming"),
84
+ localAppData:
85
+ environment.LOCALAPPDATA ?? pathApi.join(homeDir, "AppData", "Local"),
86
+ programFiles: environment.PROGRAMFILES,
87
+ programFilesX86: environment["PROGRAMFILES(X86)"],
88
+ };
89
+ const templates = [
90
+ ...(source.executables?.[platform] ?? []),
91
+ ...(source.roots?.[platform] ?? []),
92
+ ];
93
+ const candidates = templates.flatMap((template) => {
94
+ const match = /^\{(\w+)\}\/?(.*)$/.exec(template);
95
+ if (!match) {
96
+ return [template];
97
+ }
98
+ const base = variables[match[1]];
99
+ return base === undefined
100
+ ? []
101
+ : [pathApi.join(base, ...match[2].split("/"))];
102
+ });
103
+ for (const directory of (environment.PATH ?? "")
104
+ .split(platform === "win32" ? ";" : ":")
105
+ .filter(Boolean)) {
106
+ for (const name of source.executableNames ?? []) {
107
+ candidates.push(
108
+ pathApi.join(directory, platform === "win32" ? `${name}.exe` : name),
109
+ );
110
+ }
111
+ }
112
+ return candidates;
113
+ }
114
+
115
+ /** Discovery information included when an import or channel is invalid. */
116
+ export function installedDescription(options = {}) {
117
+ const found = installedBrowsers(options);
118
+ return `Installed browsers found: ${found.join(", ") || "none"}.`;
119
+ }
120
+
121
+ /** Reject unavailable engines before creating an automated profile. */
122
+ export function validateChannel(channel) {
123
+ const known = findBrowserSource(channel);
124
+ if (known?.controlProtocol === "cdp") {
125
+ return channel;
126
+ }
127
+ const reason = known
128
+ ? `${channel} requires ${known.controlProtocol ?? known.family} control, which browser-commander's real launcher does not support yet (https://github.com/link-foundation/browser-commander/issues/114)`
129
+ : `unknown --browser-channel '${channel}'`;
130
+ throw new Error(
131
+ `${reason}; choose from ${launchChannels().join(", ")}. ${installedDescription()}`,
132
+ );
133
+ }
@@ -1,10 +1,12 @@
1
1
  import path from "node:path";
2
2
 
3
3
  import {
4
+ findBrowserSource,
4
5
  LAUNCH_RESTRICTION_PRESETS,
5
6
  LAUNCH_RESTRICTIONS,
6
7
  } from "browser-commander";
7
8
 
9
+ import { installedDescription, validateChannel } from "./browser-catalogue.mjs";
8
10
  import {
9
11
  IMPORT_CHOICES,
10
12
  IMPORT_SCOPES,
@@ -50,7 +52,7 @@ export function parseBrowserOptions({
50
52
  profileGiven = false,
51
53
  } = {}) {
52
54
  const options = {
53
- channel,
55
+ channel: attach === "extension" ? channel : validateChannel(channel),
54
56
  executable: executable ? path.resolve(executable) : null,
55
57
  import: importFrom ? parseImport(importFrom) : null,
56
58
  importScope: parseImportScope(importScope, importFrom),
@@ -82,21 +84,18 @@ export function parseBrowserOptions({
82
84
  }
83
85
 
84
86
  /** Parses `<browser>[:profile]`, `default`, or `auto`. */
85
- export function parseImport(spec) {
87
+ export function parseImport(spec, discovery = {}) {
86
88
  const separator = spec.indexOf(":");
87
89
  const browser = separator === -1 ? spec : spec.slice(0, separator);
88
90
  const profile = separator === -1 ? null : spec.slice(separator + 1);
89
91
  const choice = IMPORT_CHOICES.includes(browser);
90
- if (
91
- !importBrowsers().includes(browser) ||
92
- profile === "" ||
93
- (choice && profile !== null)
94
- ) {
92
+ const source = findBrowserSource(browser);
93
+ if ((!source && !choice) || profile === "" || (choice && profile !== null)) {
95
94
  throw new Error(
96
- `--browser-import must be <${importSources().join("|")}>[:profile], default, or auto`,
95
+ `--browser-import must be <${importSources().join("|")}>[:profile], default, or auto. ${installedDescription(discovery)}`,
97
96
  );
98
97
  }
99
- return { browser, profile };
98
+ return { browser: source?.id ?? browser, profile };
100
99
  }
101
100
 
102
101
  /**
@@ -184,10 +183,7 @@ export function validateRestrictions(names) {
184
183
 
185
184
  /** The installed browser a snapshot copies for a launch channel. */
186
185
  export function snapshotBrowser(channel) {
187
- if (channel.startsWith("msedge")) {
188
- return "edge";
189
- }
190
- return ["brave", "chromium"].includes(channel) ? channel : "chrome";
186
+ return findBrowserSource(channel)?.id ?? channel;
191
187
  }
192
188
 
193
189
  /** Describes where forms are filled, for the browser prerequisite. */
package/src/cli.mjs CHANGED
@@ -6,6 +6,7 @@ import path from "node:path";
6
6
  import process from "node:process";
7
7
  import { parseArgs } from "node:util";
8
8
 
9
+ import { launchChannels } from "./browser-catalogue.mjs";
9
10
  import { parseBrowserOptions } from "./browser-options.mjs";
10
11
  import { isDirectExecution } from "./direct-execution.mjs";
11
12
  import { inspectRepository } from "./discovery.mjs";
@@ -14,6 +15,7 @@ import { buildPlans } from "./plan.mjs";
14
15
  import { probeEnvironment, renderPrerequisites } from "./prerequisites.mjs";
15
16
  import { probeRegistryState } from "./registry-state.mjs";
16
17
  import { BROWSER_MODES, defaultBrowserProfile, executePlan } from "./setup.mjs";
18
+ import { importSources } from "./sign-in-import.mjs";
17
19
 
18
20
  export { isDirectExecution };
19
21
 
@@ -62,17 +64,16 @@ Setup options:
62
64
  --browser <default|automated> Open sign-in and approval pages in your
63
65
  default browser, or in the automated
64
66
  profile too (default: default)
65
- --browser-channel <channel> Installed browser channel: chrome, chromium,
66
- brave, msedge, msedge-beta, msedge-dev, or
67
- msedge-canary (default: chrome)
67
+ --browser-channel <channel> Installed browser channel (default: chrome):
68
+ ${launchChannels().join(", ")}
68
69
  --browser-executable <path> Installed browser executable to launch
69
70
  instead of the channel's
70
71
  --browser-profile <path> Dedicated automation profile, used to fill
71
72
  forms (default: per-user state directory)
72
73
  --browser-import <browser>[:<profile>]|default|auto
73
- Copy data from your chrome, edge, brave,
74
- chromium, or firefox profile into the
75
- automated profile first; default takes the
74
+ Copy data from your installed browser profile:
75
+ ${importSources().join(", ")}
76
+ into the automated profile; default takes the
76
77
  system default browser, auto the first
77
78
  browser signed in to the registry
78
79
  --browser-import-scope <full|domains>
@@ -1,22 +1,6 @@
1
1
  import { exec } from "command-stream";
2
2
 
3
- /**
4
- * Names of common browsers by macOS bundle id, Linux desktop entry, and
5
- * Windows URL ProgId, so the tool can say which browser it opened.
6
- */
7
- const KNOWN_BROWSERS = [
8
- [/^com\.apple\.safari$|^SafariURL/i, "Safari"],
9
- [/^com\.google\.chrome\.canary$/i, "Google Chrome Canary"],
10
- [/^com\.google\.chrome$|google-chrome|^ChromeHTML$/i, "Google Chrome"],
11
- [/^org\.chromium\.chromium$|chromium|^ChromiumHTM/i, "Chromium"],
12
- [/^org\.mozilla\.firefox$|firefox|^FirefoxURL/i, "Firefox"],
13
- [/^com\.microsoft\.edgemac$|microsoft-edge|^MSEdgeHTM$/i, "Microsoft Edge"],
14
- [/^com\.brave\.browser$|brave-browser|^BraveHTML$/i, "Brave"],
15
- [/^company\.thebrowser\.browser$/i, "Arc"],
16
- [/^com\.operasoftware\.opera$|opera|^OperaStable$/i, "Opera"],
17
- [/^com\.vivaldi\.vivaldi$|vivaldi|^VivaldiHTM/i, "Vivaldi"],
18
- [/^IE\.HTTP/i, "Internet Explorer"],
19
- ];
3
+ import { browserSources, catalogueName } from "./browser-catalogue.mjs";
20
4
 
21
5
  /** The command that reports the default browser, as an exact argument vector. */
22
6
  export function defaultBrowserQuery(platform = process.platform) {
@@ -100,8 +84,40 @@ export function browserName(id) {
100
84
  if (!id) {
101
85
  return undefined;
102
86
  }
103
- const known = KNOWN_BROWSERS.find(([pattern]) => pattern.test(id));
104
- return known?.[1];
87
+ const needle = id.trim().toLowerCase();
88
+ const sources = browserSources();
89
+ const known =
90
+ sources.find((source) =>
91
+ Object.values(source.default ?? {})
92
+ .flat()
93
+ .some((candidate) => needle === candidate.toLowerCase()),
94
+ ) ??
95
+ sources.find(
96
+ (source) =>
97
+ (source.default?.win32 ?? []).some((candidate) =>
98
+ needle.startsWith(candidate.toLowerCase()),
99
+ ) ||
100
+ (needle.endsWith(".desktop") &&
101
+ (source.executableNames ?? []).some((name) =>
102
+ ["-", "_", "."].some((separator) =>
103
+ needle.startsWith(`${name.toLowerCase()}${separator}`),
104
+ ),
105
+ )),
106
+ );
107
+ if (known) {
108
+ return catalogueName(known.id);
109
+ }
110
+ // Legacy ProgIds absent from the upstream catalogue remain recognizable.
111
+ if (/^safariurl/i.test(id)) {
112
+ return catalogueName("safari");
113
+ }
114
+ if (/^chromiumhtm/i.test(id)) {
115
+ return catalogueName("chromium");
116
+ }
117
+ if (/^ie\.http/i.test(id)) {
118
+ return "Internet Explorer";
119
+ }
120
+ return undefined;
105
121
  }
106
122
 
107
123
  /**
@@ -5,10 +5,9 @@
5
5
  // printed, logged, or cached: they are only counted.
6
6
  import * as commander from "browser-commander";
7
7
 
8
+ import { catalogueName } from "./browser-catalogue.mjs";
8
9
  import { detectDefaultBrowser } from "./default-browser.mjs";
9
10
 
10
- /** Browser Commander's cookie sources, should a release stop exporting them. */
11
- const FALLBACK_SOURCES = ["chrome", "edge", "brave", "chromium", "firefox"];
12
11
  /** `--browser-import` values that pick the source browser for the user. */
13
12
  export const IMPORT_CHOICES = ["default", "auto"];
14
13
  /** `--browser-import-scope` values. */
@@ -21,22 +20,9 @@ export const SIGN_IN_DOMAINS = {
21
20
  pypi: ["pypi.org", "github.com"],
22
21
  };
23
22
 
24
- /** Display names of the source browsers, as the default-browser query names them. */
25
- const SOURCE_NAMES = {
26
- chrome: "Google Chrome",
27
- edge: "Microsoft Edge",
28
- brave: "Brave",
29
- chromium: "Chromium",
30
- firefox: "Firefox",
31
- };
32
-
33
- /**
34
- * Every browser `--browser-import` can read, from Browser Commander. Safari,
35
- * Opera, Vivaldi, and Arc follow once Browser Commander reads them
36
- * (link-foundation/browser-commander#114).
37
- */
23
+ /** Every import id from Browser Commander's public catalogue. */
38
24
  export function importSources() {
39
- return [...(commander.SUPPORTED_COOKIE_BROWSERS ?? FALLBACK_SOURCES)];
25
+ return [...commander.BROWSER_IDS];
40
26
  }
41
27
 
42
28
  /** The sign-in domains of a registry, or none. */
@@ -46,15 +32,12 @@ export function signInDomains(registry) {
46
32
 
47
33
  /** The display name of a source browser. */
48
34
  export function sourceName(browser) {
49
- return SOURCE_NAMES[browser] ?? browser;
35
+ return catalogueName(browser);
50
36
  }
51
37
 
52
38
  /** The source id of a default-browser display name, when it can be imported. */
53
39
  export function sourceId(displayName) {
54
- const entry = Object.entries(SOURCE_NAMES).find(
55
- ([, name]) => name === displayName,
56
- );
57
- return entry && importSources().includes(entry[0]) ? entry[0] : undefined;
40
+ return importSources().find((id) => sourceName(id) === displayName);
58
41
  }
59
42
 
60
43
  /**
@@ -87,7 +70,12 @@ export async function findSignInSources(domains, options = {}) {
87
70
  );
88
71
  const found = [];
89
72
  for (const browser of browsers) {
90
- const profiles = await list({ browser }).catch(() => []);
73
+ const profiles = await list({ browser }).catch((error) => {
74
+ if (options.verbose) {
75
+ console.error(`could not list ${browser} profiles: ${error.message}`);
76
+ }
77
+ return [];
78
+ });
91
79
  for (const profile of profiles) {
92
80
  const matched = [];
93
81
  for (const domain of domains) {
@@ -171,15 +159,15 @@ export async function chooseSignInSource(sources, domains, ask, auto) {
171
159
  * Resolves `--browser-import default|auto` to an installed source browser:
172
160
  * the system default browser, or for `auto` the first browser with a
173
161
  * sign-in for `domains`. Throws when `default` names a browser Browser
174
- * Commander cannot read yet, such as Safari.
162
+ * Commander cannot identify.
175
163
  */
176
164
  export async function resolveImportChoice(choice, domains, options = {}) {
177
165
  const preferred = await defaultSource(options);
178
166
  if (choice === "default") {
179
167
  if (!preferred) {
180
168
  throw new Error(
181
- "--browser-import default: the default browser cannot be imported yet (Browser Commander reads " +
182
- `${importSources().join(", ")}; see link-foundation/browser-commander#114)`,
169
+ "--browser-import default: the default browser is unknown or cannot be imported (Browser Commander sources: " +
170
+ `${importSources().join(", ")})`,
183
171
  );
184
172
  }
185
173
  return { browser: preferred, profile: null };