package-registry-manager 0.22.8 → 0.22.10
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 +20 -2
- package/package.json +4 -4
- package/src/cli-main.mjs +540 -0
- package/src/cli.mjs +15 -534
- package/src/default-scope.mjs +53 -0
- package/src/discovery.mjs +9 -1
- package/src/flows.mjs +10 -5
- package/src/node-version.mjs +14 -0
- package/src/organization-scan.mjs +131 -56
- package/src/organization-setup.mjs +51 -13
- package/src/prerequisites.mjs +11 -9
- package/src/registry-state.mjs +75 -44
- package/src/release-runs.mjs +117 -0
- package/src/repository-repair.mjs +11 -23
- package/src/setup.mjs +21 -11
package/README.md
CHANGED
|
@@ -33,7 +33,25 @@ package-registry-manager setup --org link-foundation --all --execute --browser-i
|
|
|
33
33
|
Scans read matched manifests/workflows through gh-manager. Account setup shares
|
|
34
34
|
one browser, groups npm approvals, and uses CI evidence to preserve healthy tokens
|
|
35
35
|
or verify replacements before revocation. `--secret-name '{REGISTRY}_TOKEN_{REPO}'`
|
|
36
|
-
selects custom credential names.
|
|
37
|
-
|
|
36
|
+
selects custom credential names.
|
|
37
|
+
|
|
38
|
+
Account scans print progress to stderr (`[37/169] owner/repository`) and stream
|
|
39
|
+
text inspection results as each repository finishes. JSON inspection remains one
|
|
40
|
+
report on stdout. Repository, manifest, registry lookup, and release-log failures are
|
|
41
|
+
included in each repository's `error` and structured `errors` fields; valid
|
|
42
|
+
packages and later repositories are retained. The command exits nonzero after
|
|
43
|
+
printing all results, and `setup --org ORG --all` still plans the usable packages.
|
|
44
|
+
Release health checks only the latest default-branch push or manual run of each
|
|
45
|
+
detected publishing workflow; it downloads logs only when that run failed.
|
|
46
|
+
Expired logs retain the failed run's URL and conclusion alongside the error.
|
|
47
|
+
Account `plan` and `setup` JSON reports also include a `plans` array.
|
|
48
|
+
|
|
49
|
+
Without `--repository`, online commands prefer the current repository's
|
|
50
|
+
organization, then a sole organization membership, then the signed-in GitHub
|
|
51
|
+
user. Multiple memberships fall back to the user; select an organization with
|
|
52
|
+
`--org`. If GitHub account discovery is unavailable, the current directory is
|
|
53
|
+
inspected. Explicit `--repository` (including `--repository .`), `--ref`, and
|
|
54
|
+
local `--offline` commands keep local scope. Both CLIs' GitHub dependency needs
|
|
55
|
+
Node.js 22.12 or newer and npm; JavaScript checks the runtime before loading it.
|
|
38
56
|
|
|
39
57
|
See the [bootstrap, wrapper and credential guide](../docs/registry-setup.md) for branch/PR setup, dual npm names, registry policies and integration limits.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "package-registry-manager",
|
|
3
|
-
"version": "0.22.
|
|
3
|
+
"version": "0.22.10",
|
|
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/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 && node --check src/repository-identity.mjs && node --check src/repository-repair.mjs && node --check src/manifest-proposal.mjs",
|
|
31
|
+
"syntax": "node --check src/cli.mjs && node --check src/cli-main.mjs && node --check src/node-version.mjs && node --check src/default-scope.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 && node --check src/repository-identity.mjs && node --check src/repository-repair.mjs && node --check src/manifest-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 .",
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@link-foundation/gh-manager": "https://codeload.github.com/link-foundation/gh-manager/tar.gz/29808e088ac8fd8374361920bbebab07a4d63ac6",
|
|
44
|
-
"browser-commander": "0.
|
|
45
|
-
"command-stream": "^2.
|
|
44
|
+
"browser-commander": "^0.31.0",
|
|
45
|
+
"command-stream": "^2.1.0",
|
|
46
46
|
"playwright": "^1.63.0",
|
|
47
47
|
"selenium-webdriver": "^4.50.0"
|
|
48
48
|
},
|
package/src/cli-main.mjs
ADDED
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { stat } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import process from "node:process";
|
|
5
|
+
import { parseArgs } from "node:util";
|
|
6
|
+
|
|
7
|
+
import { launchChannels } from "./browser-catalogue.mjs";
|
|
8
|
+
import { parseBrowserOptions } from "./browser-options.mjs";
|
|
9
|
+
import { inspectReference } from "./bootstrap-reference.mjs";
|
|
10
|
+
import { inspectRepository } from "./discovery.mjs";
|
|
11
|
+
import { parseRegistry } from "./model.mjs";
|
|
12
|
+
import { buildPlans } from "./plan.mjs";
|
|
13
|
+
import { probeEnvironment, renderPrerequisites } from "./prerequisites.mjs";
|
|
14
|
+
import { probeRegistryState } from "./registry-state.mjs";
|
|
15
|
+
import {
|
|
16
|
+
BROWSER_MODES,
|
|
17
|
+
defaultBrowserProfile,
|
|
18
|
+
executePlans,
|
|
19
|
+
} from "./setup.mjs";
|
|
20
|
+
import { importSources } from "./sign-in-import.mjs";
|
|
21
|
+
import { accountCommand } from "./organization-setup.mjs";
|
|
22
|
+
import { defaultScope } from "./default-scope.mjs";
|
|
23
|
+
|
|
24
|
+
const HELP = `Usage: package-registry-manager-js [global options] <command>
|
|
25
|
+
|
|
26
|
+
Commands:
|
|
27
|
+
inspect Discover supported package manifests
|
|
28
|
+
plan [--registry <registry>] Print ordered setup plans
|
|
29
|
+
setup --registry <registry> Bootstrap or attach trusted publishing
|
|
30
|
+
setup --all Set up every publishable package in one session
|
|
31
|
+
|
|
32
|
+
Global options:
|
|
33
|
+
-h, --help Print this help
|
|
34
|
+
-V, --version Print the version
|
|
35
|
+
--repository <path> Inspect only this repository (default: widest scope)
|
|
36
|
+
--org <org> | --user <login> Scan repositories through gh-manager
|
|
37
|
+
--secret-name <template> Secret name; supports {REGISTRY}, {REPO}, {ORG}
|
|
38
|
+
--format <text|json> Output format (default: text)
|
|
39
|
+
--offline Do not look up packages on registries
|
|
40
|
+
--verbose Print command details and output, and
|
|
41
|
+
list the manifests inspection skipped
|
|
42
|
+
|
|
43
|
+
Plan and setup options:
|
|
44
|
+
--verify-release Also watch the release workflow and confirm
|
|
45
|
+
that the next version has provenance
|
|
46
|
+
--workflow <file> Trusted-publisher workflow file in
|
|
47
|
+
.github/workflows, when detection finds
|
|
48
|
+
several or the wrong one
|
|
49
|
+
--add-publish-job Propose missing publishing jobs in a draft PR;
|
|
50
|
+
--workflow chooses its target file
|
|
51
|
+
--environment <name> GitHub environment of the trusted publisher
|
|
52
|
+
--ref <branch|pr> Bootstrap a pushed branch or PR head before merge
|
|
53
|
+
--package <name> Plan only this package, or select it for
|
|
54
|
+
setup when a registry has several
|
|
55
|
+
|
|
56
|
+
Setup options:
|
|
57
|
+
--dry-run Print the flow without running it (default)
|
|
58
|
+
--execute Run the flow and open a visible browser
|
|
59
|
+
--yes Confirm publishing, secret changes, and
|
|
60
|
+
form submission in advance
|
|
61
|
+
--no-browser Print the setup URL instead
|
|
62
|
+
--open-with <app> Open sign-in and approval pages in this
|
|
63
|
+
application instead of the default browser,
|
|
64
|
+
such as "Google Chrome" (macOS) or firefox
|
|
65
|
+
--manual crates.io: create, use, and revoke the
|
|
66
|
+
first-publish token by hand instead of
|
|
67
|
+
through the crates.io API
|
|
68
|
+
--keep-session Stay signed in after setup: npm keeps its
|
|
69
|
+
session token in npm's user configuration
|
|
70
|
+
until npm logout; crates.io keeps only the
|
|
71
|
+
automated profile's browser session
|
|
72
|
+
--browser <default|automated> Open sign-in and approval pages in your
|
|
73
|
+
default browser, or in the automated
|
|
74
|
+
profile too (default: default)
|
|
75
|
+
--browser-channel <channel> Installed browser channel (default: chrome):
|
|
76
|
+
${launchChannels().join(", ")}
|
|
77
|
+
--browser-executable <path> Installed browser executable to launch
|
|
78
|
+
instead of the channel's
|
|
79
|
+
--browser-profile <path> Dedicated automation profile, used to fill
|
|
80
|
+
forms (default: per-user state directory)
|
|
81
|
+
--browser-import <browser>[:<profile>]|default|auto
|
|
82
|
+
Copy data from your installed browser profile:
|
|
83
|
+
${importSources().join(", ")}
|
|
84
|
+
into the automated profile; default takes the
|
|
85
|
+
system default browser, auto the first
|
|
86
|
+
browser signed in to the registry
|
|
87
|
+
--browser-import-scope <full|domains>
|
|
88
|
+
Import the whole profile, or only the
|
|
89
|
+
registry's sign-in cookies (default: full
|
|
90
|
+
for a named browser, domains otherwise)
|
|
91
|
+
--browser-attach <mode> Fill forms in your own browser instead:
|
|
92
|
+
snapshot[:<profile>] launches a temporary
|
|
93
|
+
copy of your profile, extension drives your
|
|
94
|
+
running browser through the Browser
|
|
95
|
+
Commander extension
|
|
96
|
+
--browser-pref <key=value> Browser preference for the automated
|
|
97
|
+
profile, such as intl.accept_languages=en;
|
|
98
|
+
may be repeated
|
|
99
|
+
--browser-restriction <name> Launch restriction or preset, such as
|
|
100
|
+
no-extensions; may be repeated
|
|
101
|
+
`;
|
|
102
|
+
|
|
103
|
+
export async function main(args = process.argv.slice(2)) {
|
|
104
|
+
const { values, positionals } = parseArgs({
|
|
105
|
+
args,
|
|
106
|
+
allowPositionals: true,
|
|
107
|
+
strict: true,
|
|
108
|
+
options: {
|
|
109
|
+
ref: { type: "string" },
|
|
110
|
+
repository: { type: "string" },
|
|
111
|
+
org: { type: "string" },
|
|
112
|
+
user: { type: "string" },
|
|
113
|
+
"secret-name": { type: "string" },
|
|
114
|
+
format: { type: "string", default: "text" },
|
|
115
|
+
verbose: { type: "boolean", default: false },
|
|
116
|
+
offline: { type: "boolean", default: false },
|
|
117
|
+
"verify-release": { type: "boolean", default: false },
|
|
118
|
+
workflow: { type: "string" },
|
|
119
|
+
"add-publish-job": { type: "boolean", default: false },
|
|
120
|
+
environment: { type: "string" },
|
|
121
|
+
"dry-run": { type: "boolean", default: false },
|
|
122
|
+
registry: { type: "string", multiple: true },
|
|
123
|
+
package: { type: "string" },
|
|
124
|
+
execute: { type: "boolean", default: false },
|
|
125
|
+
all: { type: "boolean", default: false },
|
|
126
|
+
yes: { type: "boolean", default: false },
|
|
127
|
+
"no-browser": { type: "boolean", default: false },
|
|
128
|
+
"open-with": { type: "string" },
|
|
129
|
+
"keep-session": { type: "boolean", default: false },
|
|
130
|
+
manual: { type: "boolean", default: false },
|
|
131
|
+
browser: { type: "string", default: "default" },
|
|
132
|
+
"browser-channel": { type: "string", default: "chrome" },
|
|
133
|
+
"browser-profile": { type: "string" },
|
|
134
|
+
"browser-executable": { type: "string" },
|
|
135
|
+
"browser-import": { type: "string" },
|
|
136
|
+
"browser-import-scope": { type: "string" },
|
|
137
|
+
"browser-attach": { type: "string" },
|
|
138
|
+
"browser-pref": { type: "string", multiple: true },
|
|
139
|
+
"browser-restriction": { type: "string", multiple: true },
|
|
140
|
+
help: { type: "boolean", short: "h", default: false },
|
|
141
|
+
version: { type: "boolean", short: "V", default: false },
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
if (values.help) {
|
|
145
|
+
process.stdout.write(HELP);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (values.version) {
|
|
149
|
+
process.stdout.write(`package-registry-manager ${packageVersion()}\n`);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (positionals.length !== 1) {
|
|
153
|
+
throw new Error("expected exactly one command: inspect, plan, or setup");
|
|
154
|
+
}
|
|
155
|
+
if (!["text", "json"].includes(values.format)) {
|
|
156
|
+
throw new Error("--format must be 'text' or 'json'");
|
|
157
|
+
}
|
|
158
|
+
if (!BROWSER_MODES.includes(values.browser)) {
|
|
159
|
+
throw new Error("--browser must be 'default' or 'automated'");
|
|
160
|
+
}
|
|
161
|
+
const browserOptions = parseBrowserOptions({
|
|
162
|
+
channel: values["browser-channel"],
|
|
163
|
+
executable: values["browser-executable"],
|
|
164
|
+
importFrom: values["browser-import"],
|
|
165
|
+
importScope: values["browser-import-scope"],
|
|
166
|
+
attach: values["browser-attach"],
|
|
167
|
+
preferences: values["browser-pref"],
|
|
168
|
+
restrictions: values["browser-restriction"],
|
|
169
|
+
profileGiven: values["browser-profile"] !== undefined,
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
if (!values.org && !values.user) {
|
|
173
|
+
Object.assign(values, await defaultScope(values));
|
|
174
|
+
}
|
|
175
|
+
const repository = path.resolve(values.repository ?? ".");
|
|
176
|
+
const command = positionals[0];
|
|
177
|
+
if (values.org || values.user) {
|
|
178
|
+
if (values.org && values.user) {
|
|
179
|
+
throw new Error("--org and --user are mutually exclusive");
|
|
180
|
+
}
|
|
181
|
+
if (values.repository !== undefined || values.ref) {
|
|
182
|
+
throw new Error("account scans cannot use --repository or --ref");
|
|
183
|
+
}
|
|
184
|
+
if (!["inspect", "plan", "setup"].includes(command)) {
|
|
185
|
+
throw new Error(`unknown command '${command}'`);
|
|
186
|
+
}
|
|
187
|
+
if (command === "setup" && (!values.all || values.package)) {
|
|
188
|
+
throw new Error("account setup requires --all without --package");
|
|
189
|
+
}
|
|
190
|
+
if (values["dry-run"] && values.execute) {
|
|
191
|
+
throw new Error("--dry-run and --execute are mutually exclusive");
|
|
192
|
+
}
|
|
193
|
+
if (values.yes && !values.execute) {
|
|
194
|
+
throw new Error("--yes requires --execute");
|
|
195
|
+
}
|
|
196
|
+
if (values["no-browser"] && !values.execute) {
|
|
197
|
+
throw new Error("--no-browser requires --execute");
|
|
198
|
+
}
|
|
199
|
+
if (values["open-with"] !== undefined && values["no-browser"]) {
|
|
200
|
+
throw new Error("--open-with and --no-browser are mutually exclusive");
|
|
201
|
+
}
|
|
202
|
+
if (!browserOptions.attach) {
|
|
203
|
+
browserOptions.import ??= { browser: "auto", profile: null };
|
|
204
|
+
}
|
|
205
|
+
return accountCommand(
|
|
206
|
+
command,
|
|
207
|
+
{
|
|
208
|
+
org: values.org,
|
|
209
|
+
user: values.user,
|
|
210
|
+
format: values.format,
|
|
211
|
+
offline: values.offline,
|
|
212
|
+
verbose: values.verbose,
|
|
213
|
+
registries: (values.registry ?? []).map(parseRegistry),
|
|
214
|
+
execute: values.execute,
|
|
215
|
+
yes: values.yes,
|
|
216
|
+
noBrowser: values["no-browser"],
|
|
217
|
+
secretName: values["secret-name"],
|
|
218
|
+
package: values.package,
|
|
219
|
+
browser: values.browser,
|
|
220
|
+
browserOptions,
|
|
221
|
+
browserProfile: path.resolve(
|
|
222
|
+
values["browser-profile"] ??
|
|
223
|
+
defaultBrowserProfile({ channel: browserOptions.channel }),
|
|
224
|
+
),
|
|
225
|
+
openWith: nonEmpty(values["open-with"], "--open-with"),
|
|
226
|
+
keepSession: values["keep-session"],
|
|
227
|
+
planOptions: {
|
|
228
|
+
workflow: values.workflow,
|
|
229
|
+
publisherEnvironment: values.environment,
|
|
230
|
+
addPublishJob: values["add-publish-job"],
|
|
231
|
+
manual: values.manual,
|
|
232
|
+
verifyRelease: values["verify-release"],
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
outputPlans,
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
const discovered = values.ref
|
|
239
|
+
? await inspectReference(repository, values.ref, {
|
|
240
|
+
includeSkipped: values.verbose,
|
|
241
|
+
})
|
|
242
|
+
: await inspectRepository(repository, { includeSkipped: values.verbose });
|
|
243
|
+
const inspection = values.offline
|
|
244
|
+
? discovered
|
|
245
|
+
: await probeRegistryState(discovered, { verbose: values.verbose });
|
|
246
|
+
if (command === "inspect") {
|
|
247
|
+
outputInspection(inspection, values.format);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const registries = (values.registry ?? []).map(parseRegistry);
|
|
252
|
+
if (!["plan", "setup"].includes(command)) {
|
|
253
|
+
throw new Error(`unknown command '${command}'`);
|
|
254
|
+
}
|
|
255
|
+
const environment = await probeEnvironment({
|
|
256
|
+
offline: values.offline,
|
|
257
|
+
verbose: values.verbose,
|
|
258
|
+
python: inspection.packages.some(
|
|
259
|
+
(item) =>
|
|
260
|
+
item.registry === "pypi" &&
|
|
261
|
+
item.publishable &&
|
|
262
|
+
(registries.length === 0 || registries.includes("pypi")),
|
|
263
|
+
),
|
|
264
|
+
npm: inspection.packages.some(
|
|
265
|
+
(item) =>
|
|
266
|
+
item.registry === "npm" &&
|
|
267
|
+
item.publishable &&
|
|
268
|
+
(registries.length === 0 || registries.includes("npm")),
|
|
269
|
+
),
|
|
270
|
+
});
|
|
271
|
+
const planOptions = {
|
|
272
|
+
ref: values.ref,
|
|
273
|
+
verifyRelease: values["verify-release"],
|
|
274
|
+
manual: values.manual,
|
|
275
|
+
workflow: await workflowOverride(
|
|
276
|
+
repository,
|
|
277
|
+
values.workflow,
|
|
278
|
+
values["add-publish-job"],
|
|
279
|
+
),
|
|
280
|
+
addPublishJob: values["add-publish-job"],
|
|
281
|
+
publisherEnvironment: nonEmpty(values.environment, "--environment"),
|
|
282
|
+
environment,
|
|
283
|
+
browser: browserSummary(command, values, browserOptions),
|
|
284
|
+
};
|
|
285
|
+
if (command === "plan") {
|
|
286
|
+
const plans = filterPlans(
|
|
287
|
+
buildPlans(inspection, registries, planOptions),
|
|
288
|
+
values.package,
|
|
289
|
+
);
|
|
290
|
+
outputPlans(plans, values.format);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
if (values.all && values.package) {
|
|
294
|
+
throw new Error("--all and --package are mutually exclusive");
|
|
295
|
+
}
|
|
296
|
+
if (!values.all && registries.length !== 1) {
|
|
297
|
+
throw new Error("setup requires exactly one --registry <registry>");
|
|
298
|
+
}
|
|
299
|
+
if (values["dry-run"] && values.execute) {
|
|
300
|
+
throw new Error("--dry-run and --execute are mutually exclusive");
|
|
301
|
+
}
|
|
302
|
+
if (values.yes && !values.execute) {
|
|
303
|
+
throw new Error("--yes requires --execute");
|
|
304
|
+
}
|
|
305
|
+
if (values["no-browser"] && !values.execute) {
|
|
306
|
+
throw new Error("--no-browser requires --execute");
|
|
307
|
+
}
|
|
308
|
+
if (values["open-with"] !== undefined && values["no-browser"]) {
|
|
309
|
+
throw new Error("--open-with and --no-browser are mutually exclusive");
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const plans = buildPlans(inspection, registries, planOptions);
|
|
313
|
+
const selected = values.all
|
|
314
|
+
? plans.filter((plan) => plan.package.publishable)
|
|
315
|
+
: [selectPlan(plans, values.package)];
|
|
316
|
+
outputPlans(selected, values.format);
|
|
317
|
+
await executePlans(
|
|
318
|
+
selected,
|
|
319
|
+
{
|
|
320
|
+
inspection,
|
|
321
|
+
workflow: planOptions.workflow,
|
|
322
|
+
publisherEnvironment: planOptions.publisherEnvironment,
|
|
323
|
+
repository,
|
|
324
|
+
execute: values.execute,
|
|
325
|
+
yes: values.yes,
|
|
326
|
+
noBrowser: values["no-browser"],
|
|
327
|
+
openWith: nonEmpty(values["open-with"], "--open-with"),
|
|
328
|
+
keepSession: values["keep-session"],
|
|
329
|
+
secretName: values["secret-name"],
|
|
330
|
+
quietBrowser: selected.some((plan) =>
|
|
331
|
+
plan.steps.some((step) => step.id === "manage-registry-token"),
|
|
332
|
+
),
|
|
333
|
+
browser: values.browser,
|
|
334
|
+
browserOptions,
|
|
335
|
+
browserProfile: path.resolve(
|
|
336
|
+
values["browser-profile"] ??
|
|
337
|
+
defaultBrowserProfile({ channel: browserOptions.channel }),
|
|
338
|
+
),
|
|
339
|
+
verbose: values.verbose,
|
|
340
|
+
verifyRelease: values["verify-release"],
|
|
341
|
+
},
|
|
342
|
+
values.all,
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
async function workflowOverride(repository, workflow, adding = false) {
|
|
347
|
+
if (workflow === undefined) {
|
|
348
|
+
return undefined;
|
|
349
|
+
}
|
|
350
|
+
if (!/^[\w.-]+\.ya?ml$/.test(workflow)) {
|
|
351
|
+
throw new Error(
|
|
352
|
+
"--workflow must be a workflow file name in .github/workflows, such as release.yml",
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
const file = path.join(repository, ".github/workflows", workflow);
|
|
356
|
+
const found = await stat(file).then(
|
|
357
|
+
(metadata) => metadata.isFile(),
|
|
358
|
+
() => false,
|
|
359
|
+
);
|
|
360
|
+
if (!found && !adding) {
|
|
361
|
+
throw new Error(`--workflow: .github/workflows/${workflow} does not exist`);
|
|
362
|
+
}
|
|
363
|
+
return workflow;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function nonEmpty(value, option) {
|
|
367
|
+
if (value !== undefined && !value.trim()) {
|
|
368
|
+
throw new Error(`${option} must not be empty`);
|
|
369
|
+
}
|
|
370
|
+
return value?.trim();
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function browserSummary(command, values, browserOptions) {
|
|
374
|
+
if (command === "setup" && values["no-browser"]) {
|
|
375
|
+
return { mode: "none" };
|
|
376
|
+
}
|
|
377
|
+
if (command !== "setup") {
|
|
378
|
+
return { mode: "default", channel: values["browser-channel"] };
|
|
379
|
+
}
|
|
380
|
+
return {
|
|
381
|
+
mode: values.browser,
|
|
382
|
+
channel: browserOptions.channel,
|
|
383
|
+
profile:
|
|
384
|
+
values.browser === "automated" && !browserOptions.attach
|
|
385
|
+
? path.resolve(
|
|
386
|
+
values["browser-profile"] ??
|
|
387
|
+
defaultBrowserProfile({ channel: browserOptions.channel }),
|
|
388
|
+
)
|
|
389
|
+
: undefined,
|
|
390
|
+
import: browserOptions.import ?? undefined,
|
|
391
|
+
attach: browserOptions.attach ?? undefined,
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/** Keeps the plans of `packageName`, or every plan without one. */
|
|
396
|
+
function filterPlans(plans, packageName) {
|
|
397
|
+
if (plans.length === 0) {
|
|
398
|
+
throw new Error("no matching package manifests were found");
|
|
399
|
+
}
|
|
400
|
+
if (!packageName) {
|
|
401
|
+
return plans;
|
|
402
|
+
}
|
|
403
|
+
const named = plans.filter((plan) => plan.package.name === packageName);
|
|
404
|
+
if (named.length === 0) {
|
|
405
|
+
throw new Error(`package '${packageName}' was not found`);
|
|
406
|
+
}
|
|
407
|
+
return named;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function selectPlan(plans, packageName) {
|
|
411
|
+
if (packageName) {
|
|
412
|
+
const plan = plans.find(
|
|
413
|
+
(candidate) => candidate.package.name === packageName,
|
|
414
|
+
);
|
|
415
|
+
if (!plan) {
|
|
416
|
+
throw new Error(
|
|
417
|
+
`package '${packageName}' was not found for this registry`,
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
return plan;
|
|
421
|
+
}
|
|
422
|
+
if (plans.length === 0) {
|
|
423
|
+
throw new Error("no matching package manifests were found");
|
|
424
|
+
}
|
|
425
|
+
const publishable = plans.filter((plan) => plan.package.publishable);
|
|
426
|
+
const candidates = publishable.length > 0 ? publishable : plans;
|
|
427
|
+
if (candidates.length > 1) {
|
|
428
|
+
throw new Error(
|
|
429
|
+
"multiple packages use this registry; select one with --package <name>",
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
return candidates[0];
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function outputInspection(inspection, format) {
|
|
436
|
+
if (format === "json") {
|
|
437
|
+
process.stdout.write(`${JSON.stringify(inspection, null, 2)}\n`);
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
process.stdout.write(`Repository: ${inspection.repository.root}\n`);
|
|
441
|
+
if (inspection.packages.length === 0) {
|
|
442
|
+
process.stdout.write("No supported package manifests found.\n");
|
|
443
|
+
}
|
|
444
|
+
for (const packageInfo of inspection.packages) {
|
|
445
|
+
const details = [
|
|
446
|
+
packageInfo.manifest,
|
|
447
|
+
packageInfo.publishable ? "publishable" : "not publishable",
|
|
448
|
+
];
|
|
449
|
+
if (packageInfo.exists_on_registry !== undefined) {
|
|
450
|
+
details.push(
|
|
451
|
+
packageInfo.exists_on_registry ? "published" : "not published yet",
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
if (packageInfo.trusted_publishing) {
|
|
455
|
+
details.push("trusted publishing");
|
|
456
|
+
}
|
|
457
|
+
process.stdout.write(
|
|
458
|
+
`- ${packageInfo.registry}: ${packageInfo.name} (${details.join(", ")})\n`,
|
|
459
|
+
);
|
|
460
|
+
if (packageInfo.workflow && packageInfo.publishable) {
|
|
461
|
+
process.stdout.write(` workflow: ${publisherSummary(packageInfo)}\n`);
|
|
462
|
+
}
|
|
463
|
+
for (const warning of packageInfo.warnings ?? []) {
|
|
464
|
+
process.stdout.write(` warning: ${warning}\n`);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
for (const item of inspection.skipped ?? []) {
|
|
468
|
+
process.stdout.write(`skipped ${item.manifest}: ${item.reason}\n`);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function publisherSummary({ workflow, workflow_jobs, environment }) {
|
|
473
|
+
const details = [];
|
|
474
|
+
if (workflow_jobs?.length) {
|
|
475
|
+
details.push(
|
|
476
|
+
`${workflow_jobs.length > 1 ? "jobs" : "job"} ${workflow_jobs.join(", ")}`,
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
if (environment) {
|
|
480
|
+
details.push(`environment ${environment}`);
|
|
481
|
+
}
|
|
482
|
+
return details.length > 0 ? `${workflow} (${details.join("; ")})` : workflow;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function outputPlans(plans, format) {
|
|
486
|
+
if (format === "json") {
|
|
487
|
+
process.stdout.write(`${JSON.stringify(plans, null, 2)}\n`);
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
for (const plan of plans) {
|
|
491
|
+
const mode = plan.mode ? ` (${plan.mode})` : "";
|
|
492
|
+
process.stdout.write(`${plan.registry}: ${plan.package.name}${mode}\n`);
|
|
493
|
+
process.stdout.write(
|
|
494
|
+
` credentials: ${plan.credential_policy.description}\n`,
|
|
495
|
+
);
|
|
496
|
+
if (plan.mode === "complete") {
|
|
497
|
+
process.stdout.write(" trusted publishing is already in use\n");
|
|
498
|
+
}
|
|
499
|
+
if (plan.skipped_reason) {
|
|
500
|
+
process.stdout.write(` skipped: ${plan.skipped_reason}\n`);
|
|
501
|
+
}
|
|
502
|
+
if (plan.trusted_publisher) {
|
|
503
|
+
process.stdout.write(
|
|
504
|
+
` trusted publisher: ${publisherSummary({
|
|
505
|
+
...plan.package,
|
|
506
|
+
workflow: plan.trusted_publisher.workflow,
|
|
507
|
+
environment: plan.trusted_publisher.environment,
|
|
508
|
+
workflow_jobs:
|
|
509
|
+
plan.trusted_publisher.workflow === plan.package.workflow
|
|
510
|
+
? plan.package.workflow_jobs
|
|
511
|
+
: undefined,
|
|
512
|
+
})}\n`,
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
for (const warning of plan.package.warnings ?? []) {
|
|
516
|
+
process.stdout.write(` warning: ${warning}\n`);
|
|
517
|
+
}
|
|
518
|
+
for (const line of renderPrerequisites(plan.prerequisites ?? [])) {
|
|
519
|
+
process.stdout.write(`${line}\n`);
|
|
520
|
+
}
|
|
521
|
+
plan.steps.forEach((step, index) => {
|
|
522
|
+
const when = step.when ? ` [when ${step.when}]` : "";
|
|
523
|
+
process.stdout.write(` ${index + 1}. ${step.title}${when}\n`);
|
|
524
|
+
if (step.command) {
|
|
525
|
+
const cwd = step.cwd && step.cwd !== "." ? `(in ${step.cwd}) ` : "";
|
|
526
|
+
process.stdout.write(
|
|
527
|
+
` $ ${cwd}${step.command.program} ${step.command.args.join(" ")}\n`,
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
if (step.url) {
|
|
531
|
+
process.stdout.write(` ${step.url}\n`);
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
function packageVersion() {
|
|
538
|
+
const manifest = new URL("../package.json", import.meta.url);
|
|
539
|
+
return JSON.parse(readFileSync(manifest, "utf8")).version;
|
|
540
|
+
}
|