@git.zone/cli 6.6.2 → 6.7.2

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.
Files changed (50) hide show
  1. package/.smartconfig.json +2 -1
  2. package/dist_ts/00_commitinfo_data.js +1 -1
  3. package/dist_ts/gitzone.cli.js +2 -2
  4. package/dist_ts/helpers.workflow.d.ts +3 -0
  5. package/dist_ts/helpers.workflow.js +14 -1
  6. package/dist_ts/mod_config/index.js +4 -1
  7. package/dist_ts/mod_deprecate/helpers.deprecation.d.ts +12 -0
  8. package/dist_ts/mod_deprecate/helpers.deprecation.js +87 -0
  9. package/dist_ts/mod_deprecate/index.d.ts +1 -1
  10. package/dist_ts/mod_deprecate/index.js +77 -40
  11. package/dist_ts/mod_format/classes.baseformatter.d.ts +6 -0
  12. package/dist_ts/mod_format/classes.baseformatter.js +61 -1
  13. package/dist_ts/mod_format/classes.formatplanner.js +14 -3
  14. package/dist_ts/mod_format/classes.formatstats.d.ts +4 -1
  15. package/dist_ts/mod_format/classes.formatstats.js +11 -1
  16. package/dist_ts/mod_format/formatters/readme.formatter.d.ts +2 -1
  17. package/dist_ts/mod_format/formatters/readme.formatter.js +80 -14
  18. package/dist_ts/mod_format/index.js +2 -1
  19. package/dist_ts/mod_format/interfaces.format.d.ts +6 -2
  20. package/dist_ts/mod_format/interfaces.format.js +1 -1
  21. package/dist_ts/mod_release/classes.releasejournal.d.ts +17 -3
  22. package/dist_ts/mod_release/classes.releasejournal.js +144 -12
  23. package/dist_ts/mod_release/helpers.npmartifact.d.ts +3 -1
  24. package/dist_ts/mod_release/helpers.npmartifact.js +64 -5
  25. package/dist_ts/mod_release/helpers.releasepublication.js +72 -20
  26. package/dist_ts/mod_release/index.d.ts +24 -0
  27. package/dist_ts/mod_release/index.js +82 -22
  28. package/dist_ts/plugins.d.ts +2 -1
  29. package/dist_ts/plugins.js +3 -2
  30. package/package.json +2 -2
  31. package/readme.md +86 -3
  32. package/ts/00_commitinfo_data.ts +1 -1
  33. package/ts/gitzone.cli.ts +1 -1
  34. package/ts/helpers.workflow.ts +17 -0
  35. package/ts/mod_config/index.ts +3 -0
  36. package/ts/mod_deprecate/helpers.deprecation.ts +151 -0
  37. package/ts/mod_deprecate/index.ts +92 -41
  38. package/ts/mod_format/classes.baseformatter.ts +71 -0
  39. package/ts/mod_format/classes.formatplanner.ts +16 -3
  40. package/ts/mod_format/classes.formatstats.ts +14 -1
  41. package/ts/mod_format/formatters/readme.formatter.ts +89 -14
  42. package/ts/mod_format/index.ts +1 -0
  43. package/ts/mod_format/interfaces.format.ts +7 -2
  44. package/ts/mod_release/classes.releasejournal.ts +219 -20
  45. package/ts/mod_release/helpers.npmartifact.ts +120 -23
  46. package/ts/mod_release/helpers.releasepublication.ts +131 -16
  47. package/ts/mod_release/index.ts +127 -30
  48. package/ts/plugins.ts +2 -0
  49. package/readme.hints.md +0 -596
  50. package/readme.plan.md +0 -176
@@ -5,7 +5,12 @@ import {
5
5
  } from "./classes.releasejournal.js";
6
6
  import { assertCredentialFreeGitPushDestination } from "./helpers.releasebranch.js";
7
7
 
8
- const supportedPnpmVersions = ["11.25.0", "12.1.0", "12.2.1", "12.3.4"] as const;
8
+ const supportedPnpmVersions = [
9
+ "11.25.0",
10
+ "12.1.0",
11
+ "12.2.1",
12
+ "12.3.4",
13
+ ] as const;
9
14
  const pnpmHelpRequirements = {
10
15
  "11.25.0": {
11
16
  pack: ["--out <path>", "--json"],
@@ -62,17 +67,15 @@ const maximumMetadataResponseBytes = 8 * 1024 * 1024;
62
67
  const capabilityCommandTimeoutMs = 30_000;
63
68
  const packCommandTimeoutMs = 10 * 60_000;
64
69
  const publishCommandTimeoutMs = 5 * 60_000;
65
- const packageNameRegex = /^(?:@[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._-]*|[a-z0-9][a-z0-9._-]*)$/;
70
+ const packageNameRegex =
71
+ /^(?:@[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._-]*|[a-z0-9][a-z0-9._-]*)$/;
66
72
 
67
73
  export interface IPnpmReleaseCapability {
68
74
  version: (typeof supportedPnpmVersions)[number];
69
75
  }
70
76
 
71
77
  export type TNpmArtifactProbeStatus =
72
- | "absent"
73
- | "exact"
74
- | "conflict"
75
- | "inconclusive";
78
+ "absent" | "exact" | "conflict" | "inconclusive";
76
79
 
77
80
  export interface INpmArtifactProbeResult {
78
81
  status: TNpmArtifactProbeStatus;
@@ -99,7 +102,10 @@ const readPackageIdentity = async (
99
102
  let packageJson: unknown;
100
103
  try {
101
104
  packageJson = JSON.parse(
102
- await plugins.fs.readFile(plugins.path.join(cwdArg, "package.json"), "utf8"),
105
+ await plugins.fs.readFile(
106
+ plugins.path.join(cwdArg, "package.json"),
107
+ "utf8",
108
+ ),
103
109
  );
104
110
  } catch (error) {
105
111
  throw new Error("Unable to read package.json for npm release packaging.", {
@@ -190,12 +196,67 @@ export const assertPnpmReleaseCapability = async (
190
196
  return { version: supportedVersion };
191
197
  };
192
198
 
199
+ /**
200
+ * Development documents must never reach a published tarball.
201
+ *
202
+ * npm-packlist force-includes every root level file whose name starts with
203
+ * "readme" (case insensitive), so neither the package.json files array nor
204
+ * .npmignore can keep readme.plan.md or readme.hints.md out of the package.
205
+ * plan.md and hints.md are not force-included but a broad files array can still
206
+ * capture them, so both spellings are refused here.
207
+ */
208
+ const developmentDocumentPatterns = [
209
+ /^readme\.plan(?:\..+)?$/i,
210
+ /^readme\.hints(?:\..+)?$/i,
211
+ /^plan\.md$/i,
212
+ /^hints\.md$/i,
213
+ /^readme\..+\.md$/i,
214
+ ] as const;
215
+
216
+ export const findDevelopmentDocumentsInPackage = (
217
+ filePathsArg: readonly string[],
218
+ ): string[] => {
219
+ const offenders = new Set<string>();
220
+ for (const filePath of filePathsArg) {
221
+ if (typeof filePath !== "string" || !filePath) continue;
222
+ const normalized = filePath.replace(/\\/g, "/").replace(/^\.\//, "");
223
+ // Only root level files matter, npm force-inclusion is a root level rule.
224
+ if (normalized.includes("/")) continue;
225
+ if (
226
+ developmentDocumentPatterns.some((patternArg) =>
227
+ patternArg.test(normalized),
228
+ )
229
+ ) {
230
+ offenders.add(normalized);
231
+ }
232
+ }
233
+ return [...offenders].sort();
234
+ };
235
+
236
+ export const assertNoDevelopmentDocumentsInPackage = (
237
+ filePathsArg: readonly string[],
238
+ ): void => {
239
+ const offenders = findDevelopmentDocumentsInPackage(filePathsArg);
240
+ if (offenders.length === 0) {
241
+ return;
242
+ }
243
+ throw new Error(
244
+ `The npm package would publish development documents: ${offenders.join(", ")}. ` +
245
+ 'npm force-includes every root file whose name starts with "readme", so neither the ' +
246
+ "package.json files array nor .npmignore can exclude them. Rename them to names that are " +
247
+ "not published (readme.plan.md -> plan.md, readme.hints.md -> hints.md; `gitzone format` " +
248
+ "performs this rename), or drop them from the files array, then release again.",
249
+ );
250
+ };
251
+
193
252
  const legacyPublishWorkflowPaths = [
194
253
  ".gitea/workflows/default_tags.yaml",
195
254
  ".gitlab-ci.yml",
196
255
  ] as const;
197
256
 
198
- export const assertNoLegacyNpmPublisher = async (cwdArg: string): Promise<void> => {
257
+ export const assertNoLegacyNpmPublisher = async (
258
+ cwdArg: string,
259
+ ): Promise<void> => {
199
260
  for (const relativePath of legacyPublishWorkflowPaths) {
200
261
  const filePath = plugins.path.join(cwdArg, relativePath);
201
262
  let content: string;
@@ -205,9 +266,12 @@ export const assertNoLegacyNpmPublisher = async (cwdArg: string): Promise<void>
205
266
  if ((error as NodeJS.ErrnoException).code === "ENOENT") {
206
267
  continue;
207
268
  }
208
- throw new Error(`Unable to inspect ${relativePath} for legacy npm publication.`, {
209
- cause: error,
210
- });
269
+ throw new Error(
270
+ `Unable to inspect ${relativePath} for legacy npm publication.`,
271
+ {
272
+ cause: error,
273
+ },
274
+ );
211
275
  }
212
276
  const activeContent = content
213
277
  .split("\n")
@@ -288,17 +352,21 @@ export const packNpmArtifact = async (
288
352
  cwdArg: string,
289
353
  destinationDirectoryArg: string,
290
354
  expectedVersionArg: string,
355
+ fileArg: string = releaseArtifactFileName,
291
356
  ): Promise<IReleaseArtifact> => {
357
+ if (
358
+ fileArg !== releaseArtifactFileName &&
359
+ !/^package\.[0-9a-f]{64}\.tgz$/.test(fileArg)
360
+ ) {
361
+ throw new Error("Invalid release artifact filename.");
362
+ }
292
363
  const identity = await readPackageIdentity(cwdArg);
293
364
  if (identity.version !== expectedVersionArg) {
294
365
  throw new Error(
295
366
  `package.json version ${identity.version} does not match release ${expectedVersionArg}.`,
296
367
  );
297
368
  }
298
- const artifactPath = plugins.path.join(
299
- destinationDirectoryArg,
300
- releaseArtifactFileName,
301
- );
369
+ const artifactPath = plugins.path.join(destinationDirectoryArg, fileArg);
302
370
  const result = await smartshellArg.execSpawn(
303
371
  "pnpm",
304
372
  ["pack", "--out", artifactPath, "--json"],
@@ -309,13 +377,17 @@ export const packNpmArtifact = async (
309
377
  },
310
378
  );
311
379
  if (result.exitCode !== 0) {
312
- throw new Error("pnpm pack failed while creating the exact release artifact.");
380
+ throw new Error(
381
+ "pnpm pack failed while creating the exact release artifact.",
382
+ );
313
383
  }
314
384
  let report: unknown;
315
385
  try {
316
386
  const stdout = result.stdout.trim();
317
387
  const reportStart = stdout.lastIndexOf("\n{");
318
- report = JSON.parse(reportStart === -1 ? stdout : stdout.slice(reportStart + 1));
388
+ report = JSON.parse(
389
+ reportStart === -1 ? stdout : stdout.slice(reportStart + 1),
390
+ );
319
391
  } catch (error) {
320
392
  throw new Error("pnpm pack did not return its required JSON report.", {
321
393
  cause: error,
@@ -330,12 +402,26 @@ export const packNpmArtifact = async (
330
402
  ) {
331
403
  throw new Error("pnpm pack reported a different package identity.");
332
404
  }
405
+ const reportedFiles = (report as { files?: unknown }).files;
406
+ if (!Array.isArray(reportedFiles)) {
407
+ throw new Error(
408
+ "pnpm pack did not report the packed file list, so the package contents cannot be verified.",
409
+ );
410
+ }
411
+ assertNoDevelopmentDocumentsInPackage(
412
+ reportedFiles.map((entryArg) =>
413
+ typeof entryArg === "string"
414
+ ? entryArg
415
+ : String((entryArg as { path?: unknown })?.path ?? ""),
416
+ ),
417
+ );
333
418
  await syncFile(artifactPath);
334
- return calculateNpmArtifact(
419
+ const artifact = await calculateNpmArtifact(
335
420
  artifactPath,
336
421
  identity.packageName,
337
422
  identity.version,
338
423
  );
424
+ return { ...artifact, file: fileArg };
339
425
  };
340
426
 
341
427
  export const verifyStoredNpmArtifact = async (
@@ -353,7 +439,9 @@ export const verifyStoredNpmArtifact = async (
353
439
  actual.sha256 !== expectedArtifactArg.sha256 ||
354
440
  actual.integrity !== expectedArtifactArg.integrity
355
441
  ) {
356
- throw new Error("Stored npm artifact no longer matches its release journal identity.");
442
+ throw new Error(
443
+ "Stored npm artifact no longer matches its release journal identity.",
444
+ );
357
445
  }
358
446
  };
359
447
 
@@ -451,10 +539,15 @@ const readBoundedResponse = async (
451
539
  }
452
540
  reader.releaseLock();
453
541
  }
454
- return Buffer.concat(chunks.map((chunkArg) => Buffer.from(chunkArg)), size);
542
+ return Buffer.concat(
543
+ chunks.map((chunkArg) => Buffer.from(chunkArg)),
544
+ size,
545
+ );
455
546
  };
456
547
 
457
- const readBoundedJsonResponse = async (responseArg: Response): Promise<unknown> =>
548
+ const readBoundedJsonResponse = async (
549
+ responseArg: Response,
550
+ ): Promise<unknown> =>
458
551
  JSON.parse(
459
552
  (
460
553
  await readBoundedResponse(
@@ -561,7 +654,9 @@ export const probeAnonymousNpmArtifact = async (
561
654
  cancelResponseBody(packageResponse);
562
655
  return { status: "inconclusive", code: "registry-response" };
563
656
  }
564
- const packageMetadata = (await readBoundedJsonResponse(packageResponse)) as {
657
+ const packageMetadata = (await readBoundedJsonResponse(
658
+ packageResponse,
659
+ )) as {
565
660
  ["dist-tags"]?: Record<string, unknown>;
566
661
  };
567
662
  if (packageMetadata["dist-tags"]?.[tagArg] !== artifactArg.version) {
@@ -582,7 +677,9 @@ export const waitForAnonymousNpmArtifact = async (
582
677
  const attempts = optionsArg.attempts ?? 10;
583
678
  const delayMs = optionsArg.delayMs ?? 1_000;
584
679
  if (!Number.isSafeInteger(attempts) || attempts < 1) {
585
- throw new Error("npm verification attempts must be a positive safe integer.");
680
+ throw new Error(
681
+ "npm verification attempts must be a positive safe integer.",
682
+ );
586
683
  }
587
684
  let lastResult: INpmArtifactProbeResult = {
588
685
  status: "inconclusive",
@@ -5,6 +5,8 @@ import {
5
5
  createReleaseDockerPromotions,
6
6
  finalizeJournalCompletion,
7
7
  getReleaseDockerPromotionContext,
8
+ getReleaseArtifacts,
9
+ getReleaseNpmArtifact,
8
10
  type TReleaseJournal,
9
11
  type IReleaseJournalV2,
10
12
  type IReleaseTargetStatus,
@@ -71,7 +73,7 @@ export function buildReleaseGitPushArgs(
71
73
 
72
74
  type TReleaseTargetSelector =
73
75
  | { kind: "git" }
74
- | { kind: "npm"; registry: string }
76
+ | { kind: "npm"; registry: string; packageName?: string }
75
77
  | { kind: "docker-qualification" }
76
78
  | { kind: "docker-promotion"; promotionId: string }
77
79
  | { kind: "docker-cleanup" };
@@ -128,7 +130,9 @@ const getTargetStatus = (
128
130
  return promotion;
129
131
  }
130
132
  const registry = journalArg.npm.registries.find(
131
- (registryArg) => registryArg.registry === selectorArg.registry,
133
+ (registryArg) =>
134
+ registryArg.registry === selectorArg.registry &&
135
+ registryArg.packageName === selectorArg.packageName,
132
136
  );
133
137
  if (!registry) {
134
138
  throw new Error(
@@ -486,20 +490,19 @@ const probeNpm = async (
486
490
  registryArg: string,
487
491
  optionsArg: IReleasePublicationOptions,
488
492
  waitArg = false,
493
+ packageNameArg?: string,
489
494
  ): Promise<INpmArtifactProbeResult> => {
490
- if (!journalArg.artifact) {
491
- throw new Error("npm release target has no exact artifact.");
492
- }
495
+ const artifact = getReleaseNpmArtifact(journalArg, packageNameArg);
493
496
  return waitArg
494
497
  ? waitForAnonymousNpmArtifact(
495
498
  registryArg,
496
- journalArg.artifact,
499
+ artifact,
497
500
  journalArg.npm.tag,
498
501
  optionsArg.npmProbeOptions,
499
502
  )
500
503
  : probeAnonymousNpmArtifact(
501
504
  registryArg,
502
- journalArg.artifact,
505
+ artifact,
503
506
  journalArg.npm.tag,
504
507
  optionsArg.npmProbeOptions,
505
508
  );
@@ -527,14 +530,23 @@ const executeNpmRegistry = async (
527
530
  optionsArg: IReleasePublicationOptions,
528
531
  journalArg: TReleaseJournal,
529
532
  registryArg: string,
533
+ packageNameArg?: string,
534
+ deferVerificationArg = false,
530
535
  ): Promise<TReleaseJournal> => {
531
536
  const selector: TReleaseTargetSelector = {
532
537
  kind: "npm",
533
538
  registry: registryArg,
539
+ packageName: packageNameArg,
534
540
  };
535
541
  let journal = journalArg;
536
542
  let status = getTargetStatus(journal, selector);
537
- let probe = await probeNpm(journal, registryArg, optionsArg);
543
+ let probe = await probeNpm(
544
+ journal,
545
+ registryArg,
546
+ optionsArg,
547
+ false,
548
+ packageNameArg,
549
+ );
538
550
 
539
551
  if (status.state === "verified") {
540
552
  if (probe.status === "exact") {
@@ -596,7 +608,64 @@ const executeNpmRegistry = async (
596
608
  throw new Error(`npm registry target is in conflict: ${registryArg}`);
597
609
  }
598
610
 
599
- probe = await probeNpm(journal, registryArg, optionsArg);
611
+ // A component submission can succeed before npm makes it installable. Keep
612
+ // that uncertainty durable without retaining a live publisher or resending
613
+ // the accepted artifact on resume. Older root/Docker journal behavior stays
614
+ // unchanged.
615
+ if (
616
+ journal.schemaVersion === 3 &&
617
+ status.state === "failed" &&
618
+ status.attempts > 0 &&
619
+ status.error === "verification-inconclusive" &&
620
+ probe.status !== "exact" &&
621
+ probe.status !== "conflict"
622
+ ) {
623
+ if (deferVerificationArg) return journal;
624
+ console.log(
625
+ `Waiting for npm availability: ${packageNameArg} on ${registryArg}`,
626
+ );
627
+ probe = await probeNpm(
628
+ journal,
629
+ registryArg,
630
+ {
631
+ ...optionsArg,
632
+ npmProbeOptions: {
633
+ attempts: 91,
634
+ delayMs: 10_000,
635
+ ...optionsArg.npmProbeOptions,
636
+ },
637
+ },
638
+ true,
639
+ packageNameArg,
640
+ );
641
+ await optionsArg.ensurePublicationState(
642
+ journal.git.state === "verified"
643
+ ? journal.release.mainOid
644
+ : journal.git.expectedRemoteMainOid || undefined,
645
+ );
646
+ if (probe.status === "exact") {
647
+ return markUnclaimedTarget(
648
+ optionsArg.store,
649
+ journal,
650
+ selector,
651
+ ["failed"],
652
+ "verified",
653
+ null,
654
+ );
655
+ }
656
+ await markNpmProbeFailure(optionsArg, journal, selector, ["failed"], probe);
657
+ throw new Error(
658
+ `npm availability could not be verified: ${packageNameArg} on ${registryArg}`,
659
+ );
660
+ }
661
+
662
+ probe = await probeNpm(
663
+ journal,
664
+ registryArg,
665
+ optionsArg,
666
+ false,
667
+ packageNameArg,
668
+ );
600
669
  if (probe.status === "exact") {
601
670
  if (status.attempts === 0 && journal.npm.alreadyPublished === "error") {
602
671
  await markUnclaimedTarget(
@@ -619,6 +688,15 @@ const executeNpmRegistry = async (
619
688
  );
620
689
  }
621
690
  if (probe.status !== "absent") {
691
+ if (
692
+ journal.schemaVersion === 3 &&
693
+ status.state === "failed" &&
694
+ probe.status === "inconclusive"
695
+ ) {
696
+ // Preserve a prior command failure: a later network failure must not
697
+ // turn it into evidence that the registry accepted the submission.
698
+ throw new Error(`npm registry cannot be verified safely: ${registryArg}`);
699
+ }
622
700
  await markNpmProbeFailure(
623
701
  optionsArg,
624
702
  journal,
@@ -629,13 +707,12 @@ const executeNpmRegistry = async (
629
707
  throw new Error(`npm registry cannot be verified safely: ${registryArg}`);
630
708
  }
631
709
 
710
+ const artifact = getReleaseNpmArtifact(journal, packageNameArg);
632
711
  const artifactPath = optionsArg.store.getArtifactPath(
633
712
  journal.release.version,
713
+ artifact.file,
634
714
  );
635
- if (!journal.artifact) {
636
- throw new Error("npm release journal has no artifact.");
637
- }
638
- await verifyStoredNpmArtifact(artifactPath, journal.artifact);
715
+ await verifyStoredNpmArtifact(artifactPath, artifact);
639
716
  journal = await claimTarget(optionsArg.store, journal, selector);
640
717
  const publishResult = await publishNpmArtifact(
641
718
  optionsArg.smartshell,
@@ -643,7 +720,13 @@ const executeNpmRegistry = async (
643
720
  artifactPath,
644
721
  registryArg,
645
722
  );
646
- probe = await probeNpm(journal, registryArg, optionsArg, true);
723
+ probe = await probeNpm(
724
+ journal,
725
+ registryArg,
726
+ optionsArg,
727
+ !deferVerificationArg,
728
+ packageNameArg,
729
+ );
647
730
  if (probe.status === "exact") {
648
731
  return finishPublishingTarget(
649
732
  optionsArg.store,
@@ -663,7 +746,7 @@ const executeNpmRegistry = async (
663
746
  );
664
747
  throw new Error(`npm registry published conflicting bytes: ${registryArg}`);
665
748
  }
666
- await finishPublishingTarget(
749
+ journal = await finishPublishingTarget(
667
750
  optionsArg.store,
668
751
  journal,
669
752
  selector,
@@ -672,6 +755,7 @@ const executeNpmRegistry = async (
672
755
  ? "verification-inconclusive"
673
756
  : "command-failed",
674
757
  );
758
+ if (deferVerificationArg && publishResult.exitCode === 0) return journal;
675
759
  throw new Error(`npm publication could not be verified: ${registryArg}`);
676
760
  };
677
761
 
@@ -1346,6 +1430,13 @@ export const executeReleasePublication = async (
1346
1430
  optionsArg: IReleasePublicationOptions,
1347
1431
  ): Promise<TReleaseJournal> => {
1348
1432
  let journal = optionsArg.journal;
1433
+ // Validate the whole stored set before Git or any registry can change.
1434
+ for (const artifact of getReleaseArtifacts(journal)) {
1435
+ await verifyStoredNpmArtifact(
1436
+ optionsArg.store.getArtifactPath(journal.release.version, artifact.file),
1437
+ artifact,
1438
+ );
1439
+ }
1349
1440
  if (journal.schemaVersion === 2) {
1350
1441
  journal = await executeDockerQualification(optionsArg, journal);
1351
1442
  if (
@@ -1362,7 +1453,31 @@ export const executeReleasePublication = async (
1362
1453
  : journal.git.expectedRemoteMainOid || undefined;
1363
1454
  for (const registry of journal.npm.registries) {
1364
1455
  await optionsArg.ensurePublicationState(expectedRemoteMainOid);
1365
- journal = await executeNpmRegistry(optionsArg, journal, registry.registry);
1456
+ journal = await executeNpmRegistry(
1457
+ optionsArg,
1458
+ journal,
1459
+ registry.registry,
1460
+ registry.packageName,
1461
+ journal.schemaVersion === 3,
1462
+ );
1463
+ }
1464
+ // Submit in dependency order before waiting for npm's publish-time scans.
1465
+ // Success still requires every package/registry cell to verify exactly.
1466
+ if (journal.schemaVersion === 3) {
1467
+ for (const registry of journal.npm.registries) {
1468
+ if (
1469
+ registry.state !== "failed" ||
1470
+ registry.error !== "verification-inconclusive"
1471
+ )
1472
+ continue;
1473
+ await optionsArg.ensurePublicationState(expectedRemoteMainOid);
1474
+ journal = await executeNpmRegistry(
1475
+ optionsArg,
1476
+ journal,
1477
+ registry.registry,
1478
+ registry.packageName,
1479
+ );
1480
+ }
1366
1481
  }
1367
1482
  if (
1368
1483
  journal.schemaVersion === 2 &&