package-registry-manager 0.21.0 → 0.21.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "package-registry-manager",
3
- "version": "0.21.0",
3
+ "version": "0.21.1",
4
4
  "description": "Inspect repositories and guide secure package-registry setup",
5
5
  "type": "module",
6
6
  "bin": {
package/src/flows.mjs CHANGED
@@ -226,13 +226,15 @@ export function npmFlow(packageInfo, context) {
226
226
  const name = packageInfo.name;
227
227
  const registry = registryEndpoint("npm");
228
228
  const trustNpm = context.trustNpm ?? DEFAULT_TRUST_NPM;
229
+ // npm trust list needs a 2FA approval; with --json npm holds the approval
230
+ // URL back until it exits, so the human output is read instead (#24).
229
231
  const trustList = command("npx", [
230
232
  "-y",
231
233
  trustNpm,
232
234
  "trust",
233
235
  "list",
234
236
  name,
235
- "--json",
237
+ "--browser=false",
236
238
  ]);
237
239
  const steps = [
238
240
  step(
package/src/setup.mjs CHANGED
@@ -43,6 +43,10 @@ const INTERACTIVE_CHECKS = new Set(["check-trust", "verify-trusted-publisher"]);
43
43
  const PAGES_CHANGES = new Set(["enable-pages", "use-pages-workflow"]);
44
44
  /** Checks whose failure is an answer, so their output is not echoed. */
45
45
  const QUIET_CHECKS = new Set(["check-sign-in", "check-pages"]);
46
+ /** A trusted publisher in `npm trust list` output, human-readable or JSON. */
47
+ const LISTED_PUBLISHER = /^\s*(?:id|type|file):\s*\S|"(?:id|type|file)"\s*:/m;
48
+ /** What `npm trust github` prints once the registry stored the publisher. */
49
+ const TRUST_CREATED = /Trust configuration created successfully/i;
46
50
  /** npm's answer when a workflow publishes without an attached trusted publisher. */
47
51
  const PUBLISH_REJECTED = /\bE404\b|404 Not Found|invalid-publisher/i;
48
52
  /** Where `--browser` opens URLs that need no automation. */
@@ -107,7 +111,6 @@ class SetupSession {
107
111
  this.conditions.add("trust-missing");
108
112
  }
109
113
  this.values = {};
110
- this.deferred = [];
111
114
  this.automation = null;
112
115
  this.temporary = null;
113
116
  this.shim = null;
@@ -116,7 +119,7 @@ class SetupSession {
116
119
  async run() {
117
120
  for (const step of this.plan.steps) {
118
121
  if (CLEANUP_CONDITIONS.has(step.when)) {
119
- this.deferred.push(step);
122
+ // cleanup() runs these, even after a failed step.
120
123
  } else if (!step.when || this.conditions.has(step.when)) {
121
124
  this.log(`==> ${step.title}`);
122
125
  await this.runStep(step);
@@ -126,8 +129,12 @@ class SetupSession {
126
129
  }
127
130
  }
128
131
 
132
+ /**
133
+ * Runs every cleanup step of the plan whose condition holds, including
134
+ * those after a step that failed (#24).
135
+ */
129
136
  async cleanup() {
130
- for (const step of this.deferred) {
137
+ for (const step of cleanupSteps(this.plan)) {
131
138
  if (step.id === "sign-out" && this.keepsSession(step)) {
132
139
  console.log(
133
140
  "Keeping the npm session (--keep-session): its token stays in npm's user configuration until you run npm logout, and the next run reuses it while npm whoami succeeds.",
@@ -226,8 +233,7 @@ class SetupSession {
226
233
  return;
227
234
  case "check-trust":
228
235
  case "verify-trusted-publisher": {
229
- const trusted =
230
- result.code === 0 && /"(?:id|type|file)"\s*:/.test(output);
236
+ const trusted = result.code === 0 && listsTrustedPublisher(output);
231
237
  toggle(this.conditions, "trust-missing", !trusted);
232
238
  if (step.id === "verify-trusted-publisher" && !trusted) {
233
239
  throw new Error(
@@ -483,7 +489,9 @@ class SetupSession {
483
489
  `${step.command.program} exited with status ${result.code}`,
484
490
  );
485
491
  }
486
- if (step.id === "sign-in") {
492
+ if (step.id === "attach-trusted-publisher") {
493
+ this.confirmTrustCreated(result.stdout);
494
+ } else if (step.id === "sign-in") {
487
495
  this.conditions.add("tool-signed-in");
488
496
  } else if (step.id === "prepare-worktree") {
489
497
  this.conditions.add("worktree-created");
@@ -497,6 +505,17 @@ class SetupSession {
497
505
  }
498
506
  }
499
507
 
508
+ /**
509
+ * npm prints the stored configuration once `npm trust github` succeeds, so
510
+ * that answer verifies it without a second 2FA-gated `npm trust list`.
511
+ */
512
+ confirmTrustCreated(output) {
513
+ if (TRUST_CREATED.test(stripAnsi(output))) {
514
+ console.log(" npm stored the trusted publisher.");
515
+ this.conditions.delete("trust-missing");
516
+ }
517
+ }
518
+
500
519
  recordPack(output) {
501
520
  const packed = packedEntry(output);
502
521
  for (const file of packed.files ?? []) {
@@ -730,6 +749,20 @@ class SetupSession {
730
749
  }
731
750
  }
732
751
 
752
+ /** The plan's cleanup steps, which run after every other step, even a failed one. */
753
+ export function cleanupSteps(plan) {
754
+ return plan.steps.filter((step) => CLEANUP_CONDITIONS.has(step.when));
755
+ }
756
+
757
+ /** Whether `npm trust list` output names a trusted publisher. */
758
+ export function listsTrustedPublisher(output) {
759
+ return LISTED_PUBLISHER.test(stripAnsi(output));
760
+ }
761
+
762
+ function stripAnsi(text) {
763
+ return String(text ?? "").replaceAll(ANSI, "");
764
+ }
765
+
733
766
  /**
734
767
  * npm's warnings from `npm pack`, such as fields it auto-corrected or removed
735
768
  * from the packed package.json, without npm's advice to run its fixer, which