package-registry-manager 0.22.3 → 0.22.5
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 +6 -3
- package/package.json +8 -6
- package/src/automation.mjs +21 -11
- package/src/browser-catalogue.mjs +81 -5
- package/src/browser-options.mjs +30 -1
- package/src/cli.mjs +59 -23
- package/src/discovery.mjs +16 -1
- package/src/index.mjs +1 -1
- package/src/plan.mjs +23 -0
- package/src/prerequisites.mjs +2 -0
- package/src/profile.mjs +10 -3
- package/src/publishers.mjs +1 -1
- package/src/python.mjs +149 -0
- package/src/setup-batch.mjs +174 -0
- package/src/setup.mjs +30 -57
- package/src/webdriver-automation.mjs +125 -0
- package/src/workflow-proposal.mjs +333 -0
package/README.md
CHANGED
|
@@ -10,12 +10,15 @@ browser, filling forms in a dedicated visible browser profile.
|
|
|
10
10
|
npx package-registry-manager inspect --repository /path/to/repository
|
|
11
11
|
npx package-registry-manager plan --repository /path/to/repository --format json
|
|
12
12
|
npx package-registry-manager setup --repository /path/to/repository --registry npm
|
|
13
|
+
npx package-registry-manager setup --repository /path/to/repository --all --execute
|
|
13
14
|
```
|
|
14
15
|
|
|
15
16
|
The package installs the command as both `package-registry-manager` and
|
|
16
17
|
`package-registry-manager-js`; use the second name when the Rust command of the
|
|
17
18
|
same name is also on your `PATH`.
|
|
18
19
|
|
|
19
|
-
Setup is a dry run unless `--execute` is present
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
Setup is a dry run unless `--execute` is present. It can publish the first
|
|
21
|
+
version after confirmation; later versions publish through CI. Missing CI
|
|
22
|
+
publishing jobs are offered in a draft pull request, and PyPI builds select an
|
|
23
|
+
interpreter satisfying `requires-python`. See the repository README for the
|
|
24
|
+
registry walkthroughs and safety details.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "package-registry-manager",
|
|
3
|
-
"version": "0.22.
|
|
3
|
+
"version": "0.22.5",
|
|
4
4
|
"description": "Inspect repositories and guide secure package-registry setup",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -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/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",
|
|
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/webdriver-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 && node --check src/python.mjs && node --check src/setup-batch.mjs && node --check src/workflow-proposal.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 .",
|
|
@@ -36,12 +36,14 @@
|
|
|
36
36
|
"check:duplication": "jscpd src",
|
|
37
37
|
"check": "npm run syntax && npm run lint && npm run format:check && npm run check:duplication",
|
|
38
38
|
"test": "node --test --test-timeout=30000",
|
|
39
|
-
"pack:check": "npm pack --dry-run"
|
|
39
|
+
"pack:check": "npm pack --dry-run",
|
|
40
|
+
"audit": "node scripts/npm-audit.mjs"
|
|
40
41
|
},
|
|
41
42
|
"dependencies": {
|
|
42
|
-
"browser-commander": "^0.
|
|
43
|
-
"command-stream": "^1.
|
|
44
|
-
"playwright": "^1.63.0"
|
|
43
|
+
"browser-commander": "^0.26.0",
|
|
44
|
+
"command-stream": "^1.6.2",
|
|
45
|
+
"playwright": "^1.63.0",
|
|
46
|
+
"selenium-webdriver": "^4.50.0"
|
|
45
47
|
},
|
|
46
48
|
"devDependencies": {
|
|
47
49
|
"@eslint/js": "^10.0.1",
|
package/src/automation.mjs
CHANGED
|
@@ -5,8 +5,11 @@ import {
|
|
|
5
5
|
attachViaExtension,
|
|
6
6
|
EXTENSION_DIRECTORY,
|
|
7
7
|
launchRealBrowser,
|
|
8
|
+
findBrowserSource,
|
|
8
9
|
} from "browser-commander";
|
|
9
10
|
|
|
11
|
+
import { connectWebDriverAutomation } from "./webdriver-automation.mjs";
|
|
12
|
+
|
|
10
13
|
import { snapshotBrowser } from "./browser-options.mjs";
|
|
11
14
|
import { defaultBrowserProfile, ensureProfileIgnored } from "./profile.mjs";
|
|
12
15
|
import { IMPORT_CHOICES, migrationFor } from "./sign-in-import.mjs";
|
|
@@ -28,7 +31,10 @@ export function relayExtensionDirectory() {
|
|
|
28
31
|
*/
|
|
29
32
|
export function launchOptions(browser, profile, verbose = false, domains = []) {
|
|
30
33
|
const options = {
|
|
31
|
-
engine:
|
|
34
|
+
engine:
|
|
35
|
+
findBrowserSource(browser.channel)?.controlProtocol === "cdp"
|
|
36
|
+
? "playwright"
|
|
37
|
+
: "selenium",
|
|
32
38
|
channel: browser.channel,
|
|
33
39
|
headless: false,
|
|
34
40
|
verbose,
|
|
@@ -75,25 +81,29 @@ export function extensionInstructions(directory) {
|
|
|
75
81
|
* Connects the browser that fills forms and returns a page with `goto`,
|
|
76
82
|
* `evaluate`, `close`, and, for a launched profile, `clearCookies(domains)`.
|
|
77
83
|
*/
|
|
78
|
-
export async function connectAutomation(
|
|
79
|
-
browser,
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
domains = [],
|
|
83
|
-
}) {
|
|
84
|
+
export async function connectAutomation(
|
|
85
|
+
{ browser, profile, verbose = false, domains = [] },
|
|
86
|
+
dependencies = {},
|
|
87
|
+
) {
|
|
84
88
|
if (browser.attach?.mode === "extension") {
|
|
85
89
|
return connectExtension();
|
|
86
90
|
}
|
|
91
|
+
if (findBrowserSource(browser.channel)?.controlProtocol === "bidi") {
|
|
92
|
+
return connectWebDriverAutomation(
|
|
93
|
+
{ browser, profile, verbose, domains },
|
|
94
|
+
dependencies,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
87
97
|
if (!browser.attach) {
|
|
88
98
|
await ensureProfileIgnored(profile, { verbose });
|
|
89
99
|
}
|
|
90
|
-
const connection = await
|
|
91
|
-
|
|
92
|
-
);
|
|
100
|
+
const connection = await (
|
|
101
|
+
dependencies.launchRealBrowser ?? launchRealBrowser
|
|
102
|
+
)(launchOptions(browser, profile, verbose, domains));
|
|
93
103
|
return {
|
|
94
104
|
goto: (url) => connection.page.goto(url),
|
|
95
105
|
evaluate: (script) => connection.page.evaluate(script),
|
|
96
|
-
close: () => connection.
|
|
106
|
+
close: () => connection.close(),
|
|
97
107
|
async clearCookies(names) {
|
|
98
108
|
for (const name of names) {
|
|
99
109
|
const escaped = name.replaceAll(".", "\\.");
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
1
|
+
import { constants, existsSync, realpathSync } from "node:fs";
|
|
2
|
+
import { access } from "node:fs/promises";
|
|
2
3
|
import os from "node:os";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
|
|
@@ -7,10 +8,10 @@ import { BROWSER_IDS, findBrowserSource } from "browser-commander";
|
|
|
7
8
|
/** Catalogue entries reached through Browser Commander's public exports. */
|
|
8
9
|
export const browserSources = () => BROWSER_IDS.map(findBrowserSource);
|
|
9
10
|
|
|
10
|
-
/**
|
|
11
|
+
/** Catalogue channels with a launch control protocol. */
|
|
11
12
|
export function launchChannels() {
|
|
12
13
|
return browserSources()
|
|
13
|
-
.filter((source) => source.controlProtocol
|
|
14
|
+
.filter((source) => ["cdp", "bidi"].includes(source.controlProtocol))
|
|
14
15
|
.flatMap((source) => [source.id, ...(source.aliases ?? [])]);
|
|
15
16
|
}
|
|
16
17
|
|
|
@@ -121,13 +122,88 @@ export function installedDescription(options = {}) {
|
|
|
121
122
|
/** Reject unavailable engines before creating an automated profile. */
|
|
122
123
|
export function validateChannel(channel) {
|
|
123
124
|
const known = findBrowserSource(channel);
|
|
124
|
-
if (known?.controlProtocol
|
|
125
|
+
if (["cdp", "bidi"].includes(known?.controlProtocol)) {
|
|
125
126
|
return channel;
|
|
126
127
|
}
|
|
127
128
|
const reason = known
|
|
128
|
-
? `${channel}
|
|
129
|
+
? `${channel} has no launch control protocol supported by this manager${known.family === "safari" ? " (Safari setup: https://github.com/link-foundation/browser-commander/issues/126)" : ""}`
|
|
129
130
|
: `unknown --browser-channel '${channel}'`;
|
|
130
131
|
throw new Error(
|
|
131
132
|
`${reason}; choose from ${launchChannels().join(", ")}. ${installedDescription()}`,
|
|
132
133
|
);
|
|
133
134
|
}
|
|
135
|
+
|
|
136
|
+
/** Resolves the selected installed binary using catalogue paths and PATH. */
|
|
137
|
+
export async function resolveExecutable(
|
|
138
|
+
{ channel, executablePath },
|
|
139
|
+
discovery = {},
|
|
140
|
+
) {
|
|
141
|
+
const platform = discovery.platform ?? process.platform;
|
|
142
|
+
const environment = discovery.environment ?? process.env;
|
|
143
|
+
const options = {
|
|
144
|
+
...discovery,
|
|
145
|
+
platform,
|
|
146
|
+
environment:
|
|
147
|
+
platform === "win32"
|
|
148
|
+
? Object.fromEntries(
|
|
149
|
+
Object.entries(environment).map(([key, value]) => [
|
|
150
|
+
key.toUpperCase(),
|
|
151
|
+
value,
|
|
152
|
+
]),
|
|
153
|
+
)
|
|
154
|
+
: environment,
|
|
155
|
+
};
|
|
156
|
+
const source = findBrowserSource(channel);
|
|
157
|
+
const candidates = executablePath
|
|
158
|
+
? [executablePath]
|
|
159
|
+
: installedCandidates({ ...source, roots: {} }, options);
|
|
160
|
+
for (const candidate of candidates) {
|
|
161
|
+
try {
|
|
162
|
+
await (discovery.access ?? access)(candidate, constants.X_OK);
|
|
163
|
+
return candidate;
|
|
164
|
+
} catch {
|
|
165
|
+
// Continue through the catalogue's executable paths and PATH names.
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
throw new Error(
|
|
169
|
+
`could not find an installed ${channel} browser; use --browser-executable`,
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** Refuses a source profile even when reached through a symlink. */
|
|
174
|
+
export function assertDedicatedProfile(profile, discovery = {}) {
|
|
175
|
+
const physical = (file) => {
|
|
176
|
+
try {
|
|
177
|
+
return realpathSync(file);
|
|
178
|
+
} catch (error) {
|
|
179
|
+
if (error.code !== "ENOENT") {
|
|
180
|
+
throw error;
|
|
181
|
+
}
|
|
182
|
+
return path.join(physical(path.dirname(file)), path.basename(file));
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
const requested = physical(path.resolve(profile));
|
|
186
|
+
const roots = browserSources().flatMap((source) =>
|
|
187
|
+
installedCandidates(
|
|
188
|
+
{
|
|
189
|
+
...source,
|
|
190
|
+
executables: {},
|
|
191
|
+
executableNames: [],
|
|
192
|
+
},
|
|
193
|
+
discovery,
|
|
194
|
+
),
|
|
195
|
+
);
|
|
196
|
+
for (const root of roots) {
|
|
197
|
+
const relative = path.relative(physical(path.resolve(root)), requested);
|
|
198
|
+
if (
|
|
199
|
+
relative === "" ||
|
|
200
|
+
(!relative.startsWith(`..${path.sep}`) &&
|
|
201
|
+
relative !== ".." &&
|
|
202
|
+
!path.isAbsolute(relative))
|
|
203
|
+
) {
|
|
204
|
+
throw new Error(
|
|
205
|
+
"automation requires a dedicated profile, not a browser default profile; use --browser-import to reuse sign-in",
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
package/src/browser-options.mjs
CHANGED
|
@@ -55,7 +55,11 @@ export function parseBrowserOptions({
|
|
|
55
55
|
channel: attach === "extension" ? channel : validateChannel(channel),
|
|
56
56
|
executable: executable ? path.resolve(executable) : null,
|
|
57
57
|
import: importFrom ? parseImport(importFrom) : null,
|
|
58
|
-
importScope:
|
|
58
|
+
importScope:
|
|
59
|
+
(importScope === undefined || importScope === null) &&
|
|
60
|
+
findBrowserSource(channel)?.controlProtocol === "bidi"
|
|
61
|
+
? "domains"
|
|
62
|
+
: parseImportScope(importScope, importFrom),
|
|
59
63
|
attach: attach ? parseAttach(attach) : null,
|
|
60
64
|
preferences: parsePreferences(preferences),
|
|
61
65
|
restrictions: validateRestrictions(restrictions),
|
|
@@ -80,9 +84,34 @@ export function parseBrowserOptions({
|
|
|
80
84
|
"--browser-attach extension cannot be combined with --browser-executable, --browser-pref, or --browser-restriction",
|
|
81
85
|
);
|
|
82
86
|
}
|
|
87
|
+
validateEngineOptions(options);
|
|
83
88
|
return options;
|
|
84
89
|
}
|
|
85
90
|
|
|
91
|
+
/** Rejects capabilities the installed upstream non-CDP launchers lack. */
|
|
92
|
+
function validateEngineOptions(options) {
|
|
93
|
+
if (
|
|
94
|
+
options.attach?.mode === "extension" ||
|
|
95
|
+
findBrowserSource(options.channel)?.controlProtocol === "cdp"
|
|
96
|
+
) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const unsupported = options.attach
|
|
100
|
+
? "--browser-attach snapshot"
|
|
101
|
+
: options.restrictions.length
|
|
102
|
+
? "--browser-restriction"
|
|
103
|
+
: Object.keys(options.preferences).length
|
|
104
|
+
? "--browser-pref"
|
|
105
|
+
: options.import && options.importScope === "full"
|
|
106
|
+
? "--browser-import-scope full"
|
|
107
|
+
: null;
|
|
108
|
+
if (unsupported) {
|
|
109
|
+
throw new Error(
|
|
110
|
+
`${unsupported} is unavailable for ${options.channel} in the installed browser-commander; use --browser-import-scope domains for sign-in cookies`,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
86
115
|
/** Parses `<browser>[:profile]`, `default`, or `auto`. */
|
|
87
116
|
export function parseImport(spec, discovery = {}) {
|
|
88
117
|
const separator = spec.indexOf(":");
|
package/src/cli.mjs
CHANGED
|
@@ -14,7 +14,11 @@ import { parseRegistry } from "./model.mjs";
|
|
|
14
14
|
import { buildPlans } from "./plan.mjs";
|
|
15
15
|
import { probeEnvironment, renderPrerequisites } from "./prerequisites.mjs";
|
|
16
16
|
import { probeRegistryState } from "./registry-state.mjs";
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
BROWSER_MODES,
|
|
19
|
+
defaultBrowserProfile,
|
|
20
|
+
executePlans,
|
|
21
|
+
} from "./setup.mjs";
|
|
18
22
|
import { importSources } from "./sign-in-import.mjs";
|
|
19
23
|
|
|
20
24
|
export { isDirectExecution };
|
|
@@ -25,6 +29,7 @@ Commands:
|
|
|
25
29
|
inspect Discover supported package manifests
|
|
26
30
|
plan [--registry <registry>] Print ordered setup plans
|
|
27
31
|
setup --registry <registry> Bootstrap or attach trusted publishing
|
|
32
|
+
setup --all Set up every publishable package in one session
|
|
28
33
|
|
|
29
34
|
Global options:
|
|
30
35
|
-h, --help Print this help
|
|
@@ -41,6 +46,8 @@ Plan and setup options:
|
|
|
41
46
|
--workflow <file> Trusted-publisher workflow file in
|
|
42
47
|
.github/workflows, when detection finds
|
|
43
48
|
several or the wrong one
|
|
49
|
+
--add-publish-job Propose missing publishing jobs in a draft PR;
|
|
50
|
+
--workflow chooses its target file
|
|
44
51
|
--environment <name> GitHub environment of the trusted publisher
|
|
45
52
|
--package <name> Plan only this package, or select it for
|
|
46
53
|
setup when a registry has several
|
|
@@ -104,11 +111,13 @@ export async function main(args = process.argv.slice(2)) {
|
|
|
104
111
|
offline: { type: "boolean", default: false },
|
|
105
112
|
"verify-release": { type: "boolean", default: false },
|
|
106
113
|
workflow: { type: "string" },
|
|
114
|
+
"add-publish-job": { type: "boolean", default: false },
|
|
107
115
|
environment: { type: "string" },
|
|
108
116
|
"dry-run": { type: "boolean", default: false },
|
|
109
117
|
registry: { type: "string", multiple: true },
|
|
110
118
|
package: { type: "string" },
|
|
111
119
|
execute: { type: "boolean", default: false },
|
|
120
|
+
all: { type: "boolean", default: false },
|
|
112
121
|
yes: { type: "boolean", default: false },
|
|
113
122
|
"no-browser": { type: "boolean", default: false },
|
|
114
123
|
"open-with": { type: "string" },
|
|
@@ -175,6 +184,12 @@ export async function main(args = process.argv.slice(2)) {
|
|
|
175
184
|
const environment = await probeEnvironment({
|
|
176
185
|
offline: values.offline,
|
|
177
186
|
verbose: values.verbose,
|
|
187
|
+
python: inspection.packages.some(
|
|
188
|
+
(item) =>
|
|
189
|
+
item.registry === "pypi" &&
|
|
190
|
+
item.publishable &&
|
|
191
|
+
(registries.length === 0 || registries.includes("pypi")),
|
|
192
|
+
),
|
|
178
193
|
npm: inspection.packages.some(
|
|
179
194
|
(item) =>
|
|
180
195
|
item.registry === "npm" &&
|
|
@@ -185,7 +200,12 @@ export async function main(args = process.argv.slice(2)) {
|
|
|
185
200
|
const planOptions = {
|
|
186
201
|
verifyRelease: values["verify-release"],
|
|
187
202
|
manual: values.manual,
|
|
188
|
-
workflow: await workflowOverride(
|
|
203
|
+
workflow: await workflowOverride(
|
|
204
|
+
repository,
|
|
205
|
+
values.workflow,
|
|
206
|
+
values["add-publish-job"],
|
|
207
|
+
),
|
|
208
|
+
addPublishJob: values["add-publish-job"],
|
|
189
209
|
publisherEnvironment: nonEmpty(values.environment, "--environment"),
|
|
190
210
|
environment,
|
|
191
211
|
browser: browserSummary(command, values, browserOptions),
|
|
@@ -198,7 +218,10 @@ export async function main(args = process.argv.slice(2)) {
|
|
|
198
218
|
outputPlans(plans, values.format);
|
|
199
219
|
return;
|
|
200
220
|
}
|
|
201
|
-
if (
|
|
221
|
+
if (values.all && values.package) {
|
|
222
|
+
throw new Error("--all and --package are mutually exclusive");
|
|
223
|
+
}
|
|
224
|
+
if (!values.all && registries.length !== 1) {
|
|
202
225
|
throw new Error("setup requires exactly one --registry <registry>");
|
|
203
226
|
}
|
|
204
227
|
if (values["dry-run"] && values.execute) {
|
|
@@ -215,26 +238,36 @@ export async function main(args = process.argv.slice(2)) {
|
|
|
215
238
|
}
|
|
216
239
|
|
|
217
240
|
const plans = buildPlans(inspection, registries, planOptions);
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
values
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
241
|
+
const selected = values.all
|
|
242
|
+
? plans.filter((plan) => plan.package.publishable)
|
|
243
|
+
: [selectPlan(plans, values.package)];
|
|
244
|
+
outputPlans(selected, values.format);
|
|
245
|
+
await executePlans(
|
|
246
|
+
selected,
|
|
247
|
+
{
|
|
248
|
+
inspection,
|
|
249
|
+
workflow: planOptions.workflow,
|
|
250
|
+
publisherEnvironment: planOptions.publisherEnvironment,
|
|
251
|
+
repository,
|
|
252
|
+
execute: values.execute,
|
|
253
|
+
yes: values.yes,
|
|
254
|
+
noBrowser: values["no-browser"],
|
|
255
|
+
openWith: nonEmpty(values["open-with"], "--open-with"),
|
|
256
|
+
keepSession: values["keep-session"],
|
|
257
|
+
browser: values.browser,
|
|
258
|
+
browserOptions,
|
|
259
|
+
browserProfile: path.resolve(
|
|
260
|
+
values["browser-profile"] ??
|
|
261
|
+
defaultBrowserProfile({ channel: browserOptions.channel }),
|
|
262
|
+
),
|
|
263
|
+
verbose: values.verbose,
|
|
264
|
+
verifyRelease: values["verify-release"],
|
|
265
|
+
},
|
|
266
|
+
values.all,
|
|
267
|
+
);
|
|
235
268
|
}
|
|
236
269
|
|
|
237
|
-
async function workflowOverride(repository, workflow) {
|
|
270
|
+
async function workflowOverride(repository, workflow, adding = false) {
|
|
238
271
|
if (workflow === undefined) {
|
|
239
272
|
return undefined;
|
|
240
273
|
}
|
|
@@ -248,7 +281,7 @@ async function workflowOverride(repository, workflow) {
|
|
|
248
281
|
(metadata) => metadata.isFile(),
|
|
249
282
|
() => false,
|
|
250
283
|
);
|
|
251
|
-
if (!found) {
|
|
284
|
+
if (!found && !adding) {
|
|
252
285
|
throw new Error(`--workflow: .github/workflows/${workflow} does not exist`);
|
|
253
286
|
}
|
|
254
287
|
return workflow;
|
|
@@ -273,7 +306,10 @@ function browserSummary(command, values, browserOptions) {
|
|
|
273
306
|
channel: browserOptions.channel,
|
|
274
307
|
profile:
|
|
275
308
|
values.browser === "automated" && !browserOptions.attach
|
|
276
|
-
? path.resolve(
|
|
309
|
+
? path.resolve(
|
|
310
|
+
values["browser-profile"] ??
|
|
311
|
+
defaultBrowserProfile({ channel: browserOptions.channel }),
|
|
312
|
+
)
|
|
277
313
|
: undefined,
|
|
278
314
|
import: browserOptions.import ?? undefined,
|
|
279
315
|
attach: browserOptions.attach ?? undefined,
|
package/src/discovery.mjs
CHANGED
|
@@ -178,6 +178,11 @@ function withPublisher(item, workflows, publishers) {
|
|
|
178
178
|
const secrets = tokenSecrets(workflows, item.registry);
|
|
179
179
|
const warnings = [
|
|
180
180
|
...publisher.warnings,
|
|
181
|
+
...(!publisher.workflow && publisher.candidates.length === 0
|
|
182
|
+
? [
|
|
183
|
+
`no workflow publishes ${item.name} to ${item.registry}; CI releases will not reach it`,
|
|
184
|
+
]
|
|
185
|
+
: []),
|
|
181
186
|
...secrets.map((secret) => tokenSecretWarning(item.registry, secret)),
|
|
182
187
|
];
|
|
183
188
|
if (warnings.length > 0) {
|
|
@@ -291,7 +296,17 @@ function parsePyproject(contents, manifest) {
|
|
|
291
296
|
if (!name) {
|
|
292
297
|
return null;
|
|
293
298
|
}
|
|
294
|
-
|
|
299
|
+
const item = packageInfo(
|
|
300
|
+
"pypi",
|
|
301
|
+
name,
|
|
302
|
+
tomlString(section, "version"),
|
|
303
|
+
manifest,
|
|
304
|
+
);
|
|
305
|
+
const required = tomlString(section, "requires-python");
|
|
306
|
+
if (required) {
|
|
307
|
+
item.requires_python = required;
|
|
308
|
+
}
|
|
309
|
+
return item;
|
|
295
310
|
}
|
|
296
311
|
|
|
297
312
|
function parseDotnet(contents, manifestPath, manifest) {
|
package/src/index.mjs
CHANGED
|
@@ -3,4 +3,4 @@ export { inspectRepository } from "./discovery.mjs";
|
|
|
3
3
|
export { REGISTRIES, parseRegistry } from "./model.mjs";
|
|
4
4
|
export { buildPlans, packageDirectory } from "./plan.mjs";
|
|
5
5
|
export { ensureProfileIgnored } from "./profile.mjs";
|
|
6
|
-
export { defaultBrowserProfile, executePlan } from "./setup.mjs";
|
|
6
|
+
export { defaultBrowserProfile, executePlan, executePlans } from "./setup.mjs";
|
package/src/plan.mjs
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
import { pagesSteps } from "./pages.mjs";
|
|
10
10
|
import { planPrerequisites } from "./prerequisites.mjs";
|
|
11
11
|
import { TRUSTED_REGISTRIES } from "./publishers.mjs";
|
|
12
|
+
import { applyPython } from "./python.mjs";
|
|
12
13
|
|
|
13
14
|
const FLOWS = new Map([
|
|
14
15
|
["npm", npmFlow],
|
|
@@ -166,6 +167,27 @@ function flowPlan(inspection, packageInfo, options) {
|
|
|
166
167
|
skipped_reason: `several workflows publish to ${packageInfo.registry} (${packageInfo.workflow_candidates.join(", ")}); pass --workflow <file> to choose the trusted publisher`,
|
|
167
168
|
};
|
|
168
169
|
}
|
|
170
|
+
if (
|
|
171
|
+
trusted &&
|
|
172
|
+
(!workflow ||
|
|
173
|
+
(options.addPublishJob &&
|
|
174
|
+
!packageInfo.workflow &&
|
|
175
|
+
!packageInfo.workflow_candidates?.length))
|
|
176
|
+
) {
|
|
177
|
+
return {
|
|
178
|
+
...basePlan(inspection, packageInfo, [
|
|
179
|
+
{
|
|
180
|
+
id: "add-publishing-workflow",
|
|
181
|
+
title: "Offer a publishing job in a reviewed pull request",
|
|
182
|
+
kind: "manual",
|
|
183
|
+
description:
|
|
184
|
+
"Add the missing publishing job to the workflow used by the other registries, on a new branch and pull request. Review and merge it, then re-run setup to attach the trusted publisher.",
|
|
185
|
+
confirm: true,
|
|
186
|
+
},
|
|
187
|
+
]),
|
|
188
|
+
skipped_reason: `no workflow publishes ${packageInfo.name} to ${packageInfo.registry}; CI releases will not reach it; add a publishing job or pass --workflow <file> before attaching a trusted publisher`,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
169
191
|
const context = {
|
|
170
192
|
directory: packageDirectory(packageInfo.manifest),
|
|
171
193
|
slug,
|
|
@@ -213,6 +235,7 @@ function flowPlan(inspection, packageInfo, options) {
|
|
|
213
235
|
plan.trusted_publisher.project = packageInfo.name;
|
|
214
236
|
}
|
|
215
237
|
}
|
|
238
|
+
applyPython(plan, options.environment?.python);
|
|
216
239
|
return plan;
|
|
217
240
|
}
|
|
218
241
|
|
package/src/prerequisites.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { exec } from "command-stream";
|
|
2
2
|
|
|
3
3
|
import { automatedDescription } from "./browser-options.mjs";
|
|
4
|
+
import { probePython } from "./python.mjs";
|
|
4
5
|
import { getJson, registryEndpoint } from "./registry-state.mjs";
|
|
5
6
|
|
|
6
7
|
/** The Node.js versions npm 11 runs on. */
|
|
@@ -154,6 +155,7 @@ export async function probeEnvironment(options = {}) {
|
|
|
154
155
|
: null;
|
|
155
156
|
return {
|
|
156
157
|
offline,
|
|
158
|
+
python: options.python ? await probePython({ verbose }) : undefined,
|
|
157
159
|
node: nodeVersion,
|
|
158
160
|
npm: npmVersion?.code === 0 ? npmVersion.stdout.trim() : null,
|
|
159
161
|
trustNpm: spec,
|
package/src/profile.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import os from "node:os";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
|
|
5
5
|
import { exec } from "command-stream";
|
|
6
|
+
import { findBrowserSource } from "browser-commander";
|
|
6
7
|
|
|
7
8
|
const APPLICATION = "package-registry-manager";
|
|
8
9
|
const PROFILE = "browser-profile";
|
|
@@ -17,20 +18,26 @@ export function defaultBrowserProfile({
|
|
|
17
18
|
platform = process.platform,
|
|
18
19
|
env = process.env,
|
|
19
20
|
home = os.homedir(),
|
|
21
|
+
channel = "chrome",
|
|
20
22
|
} = {}) {
|
|
23
|
+
const source = findBrowserSource(channel);
|
|
24
|
+
const profile =
|
|
25
|
+
source?.controlProtocol && source.controlProtocol !== "cdp"
|
|
26
|
+
? `${PROFILE}-${source.id}`
|
|
27
|
+
: PROFILE;
|
|
21
28
|
const join = platform === "win32" ? path.win32.join : path.posix.join;
|
|
22
29
|
if (platform === "win32") {
|
|
23
30
|
const base = env.LOCALAPPDATA || join(home, "AppData", "Local");
|
|
24
|
-
return join(base, APPLICATION,
|
|
31
|
+
return join(base, APPLICATION, profile);
|
|
25
32
|
}
|
|
26
33
|
if (platform === "darwin") {
|
|
27
|
-
return join(home, "Library", "Application Support", APPLICATION,
|
|
34
|
+
return join(home, "Library", "Application Support", APPLICATION, profile);
|
|
28
35
|
}
|
|
29
36
|
// The XDG specification ignores relative values.
|
|
30
37
|
const state = path.posix.isAbsolute(env.XDG_STATE_HOME ?? "")
|
|
31
38
|
? env.XDG_STATE_HOME
|
|
32
39
|
: join(home, ".local", "state");
|
|
33
|
-
return join(state, APPLICATION,
|
|
40
|
+
return join(state, APPLICATION, profile);
|
|
34
41
|
}
|
|
35
42
|
|
|
36
43
|
/** The profile location used before it moved out of the repository. */
|
package/src/publishers.mjs
CHANGED
|
@@ -15,7 +15,7 @@ const JOB_PATTERNS = new Map([
|
|
|
15
15
|
],
|
|
16
16
|
[
|
|
17
17
|
"pypi",
|
|
18
|
-
/pypa\/gh-action-pypi-publish|\
|
|
18
|
+
/pypa\/gh-action-pypi-publish|\b(?:python(?:3(?:\.\d+)?)?\s+-m\s+)?twine\s+upload\b|\b(?:uv|poetry|hatch|flit|pdm)\s+publish\b/,
|
|
19
19
|
],
|
|
20
20
|
]);
|
|
21
21
|
|