@git.zone/cli 5.0.0 → 6.0.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.
Files changed (29) hide show
  1. package/assets/templates/ci_default/.gitea/workflows/default_tags.yaml +0 -21
  2. package/assets/templates/ci_default_gitlab/.gitlab-ci.yml +0 -13
  3. package/assets/templates/ci_default_private/.gitea/workflows/default_tags.yaml +0 -21
  4. package/assets/templates/ci_default_private_gitlab/.gitlab-ci.yml +0 -13
  5. package/dist_ts/00_commitinfo_data.js +1 -1
  6. package/dist_ts/helpers.climode.js +31 -2
  7. package/dist_ts/helpers.workflow.d.ts +10 -0
  8. package/dist_ts/helpers.workflow.js +80 -8
  9. package/dist_ts/mod_release/classes.releasejournal.d.ts +78 -0
  10. package/dist_ts/mod_release/classes.releasejournal.js +511 -0
  11. package/dist_ts/mod_release/helpers.npmartifact.d.ts +32 -0
  12. package/dist_ts/mod_release/helpers.npmartifact.js +360 -0
  13. package/dist_ts/mod_release/helpers.releasebranch.d.ts +1 -0
  14. package/dist_ts/mod_release/helpers.releasebranch.js +54 -6
  15. package/dist_ts/mod_release/helpers.releasepublication.d.ts +24 -0
  16. package/dist_ts/mod_release/helpers.releasepublication.js +293 -0
  17. package/dist_ts/mod_release/index.d.ts +1 -11
  18. package/dist_ts/mod_release/index.js +410 -288
  19. package/package.json +1 -1
  20. package/readme.hints.md +47 -1
  21. package/readme.md +61 -26
  22. package/ts/00_commitinfo_data.ts +1 -1
  23. package/ts/helpers.climode.ts +34 -1
  24. package/ts/helpers.workflow.ts +114 -7
  25. package/ts/mod_release/classes.releasejournal.ts +740 -0
  26. package/ts/mod_release/helpers.npmartifact.ts +555 -0
  27. package/ts/mod_release/helpers.releasebranch.ts +67 -5
  28. package/ts/mod_release/helpers.releasepublication.ts +641 -0
  29. package/ts/mod_release/index.ts +576 -411
@@ -0,0 +1,641 @@
1
+ import * as plugins from "./mod.plugins.js";
2
+ import {
3
+ ReleaseJournalStore,
4
+ createReleaseAttempt,
5
+ finalizeJournalCompletion,
6
+ type IReleaseJournal,
7
+ type IReleaseTargetStatus,
8
+ type TReleaseErrorCode,
9
+ type TReleaseTargetState,
10
+ } from "./classes.releasejournal.js";
11
+ import {
12
+ probeAnonymousNpmArtifact,
13
+ publishNpmArtifact,
14
+ verifyStoredNpmArtifact,
15
+ waitForAnonymousNpmArtifact,
16
+ type INpmArtifactProbeOptions,
17
+ type INpmArtifactProbeResult,
18
+ } from "./helpers.npmartifact.js";
19
+ import { releaseGitEnv } from "./helpers.releasebranch.js";
20
+
21
+ const gitProbeTimeoutMs = 60_000;
22
+ const gitPushTimeoutMs = 5 * 60_000;
23
+
24
+ export interface IBuildReleaseGitPushArgsOptions {
25
+ destination: string;
26
+ newVersion: string;
27
+ pushBranch: boolean;
28
+ pushTags: boolean;
29
+ mainOid: string;
30
+ tagOid: string;
31
+ expectedRemoteMainOid?: string;
32
+ }
33
+
34
+ export function buildReleaseGitPushArgs(
35
+ optionsArg: IBuildReleaseGitPushArgsOptions,
36
+ ): string[] {
37
+ const refspecs: string[] = [];
38
+ if (optionsArg.pushBranch) {
39
+ refspecs.push(`${optionsArg.mainOid}:refs/heads/main`);
40
+ }
41
+ if (optionsArg.pushTags) {
42
+ refspecs.push(
43
+ `${optionsArg.tagOid}:refs/tags/v${optionsArg.newVersion}`,
44
+ );
45
+ }
46
+ const args = ["push", "--no-follow-tags"];
47
+ if (refspecs.length > 1) {
48
+ args.push("--atomic");
49
+ }
50
+ if (optionsArg.pushBranch && optionsArg.expectedRemoteMainOid) {
51
+ args.push(
52
+ `--force-with-lease=refs/heads/main:${optionsArg.expectedRemoteMainOid}`,
53
+ );
54
+ }
55
+ args.push(optionsArg.destination, ...refspecs);
56
+ return args;
57
+ }
58
+
59
+ type TReleaseTargetSelector =
60
+ | { kind: "git" }
61
+ | { kind: "npm"; registry: string };
62
+
63
+ type TGitProbeStatus = "pending" | "exact" | "conflict" | "inconclusive";
64
+
65
+ interface IGitProbeResult {
66
+ status: TGitProbeStatus;
67
+ }
68
+
69
+ export interface IReleasePublicationOptions {
70
+ smartshell: plugins.smartshell.Smartshell;
71
+ cwd: string;
72
+ store: ReleaseJournalStore;
73
+ journal: IReleaseJournal;
74
+ pushUrl?: string;
75
+ recoveryAttemptId?: string;
76
+ npmProbeOptions?: INpmArtifactProbeOptions;
77
+ ensurePublicationState: (expectedRemoteMainOidArg?: string) => Promise<void>;
78
+ }
79
+
80
+ const getTargetStatus = (
81
+ journalArg: IReleaseJournal,
82
+ selectorArg: TReleaseTargetSelector,
83
+ ): IReleaseTargetStatus => {
84
+ if (selectorArg.kind === "git") {
85
+ return journalArg.git;
86
+ }
87
+ const registry = journalArg.npm.registries.find(
88
+ (registryArg) => registryArg.registry === selectorArg.registry,
89
+ );
90
+ if (!registry) {
91
+ throw new Error(`Release journal does not contain npm registry ${selectorArg.registry}.`);
92
+ }
93
+ return registry;
94
+ };
95
+
96
+ const updateTarget = async (
97
+ storeArg: ReleaseJournalStore,
98
+ journalArg: IReleaseJournal,
99
+ selectorArg: TReleaseTargetSelector,
100
+ expectedStateArg: TReleaseTargetState | TReleaseTargetState[],
101
+ expectedAttemptIdArg: string | null,
102
+ updateArg: (statusArg: IReleaseTargetStatus) => void,
103
+ ): Promise<IReleaseJournal> => {
104
+ const expectedStates = Array.isArray(expectedStateArg)
105
+ ? expectedStateArg
106
+ : [expectedStateArg];
107
+ return storeArg.transact(
108
+ journalArg.release.version,
109
+ journalArg.revision,
110
+ (currentArg) => {
111
+ const status = getTargetStatus(currentArg, selectorArg);
112
+ if (!expectedStates.includes(status.state)) {
113
+ throw new Error("Release target state changed concurrently.");
114
+ }
115
+ if ((status.attempt?.id || null) !== expectedAttemptIdArg) {
116
+ throw new Error("Release target attempt ownership changed concurrently.");
117
+ }
118
+ updateArg(status);
119
+ return finalizeJournalCompletion(currentArg);
120
+ },
121
+ );
122
+ };
123
+
124
+ const claimTarget = async (
125
+ storeArg: ReleaseJournalStore,
126
+ journalArg: IReleaseJournal,
127
+ selectorArg: TReleaseTargetSelector,
128
+ ): Promise<IReleaseJournal> =>
129
+ updateTarget(
130
+ storeArg,
131
+ journalArg,
132
+ selectorArg,
133
+ ["pending", "failed"],
134
+ null,
135
+ (statusArg) => {
136
+ statusArg.state = "publishing";
137
+ statusArg.attempts += 1;
138
+ statusArg.attempt = createReleaseAttempt();
139
+ statusArg.error = null;
140
+ },
141
+ );
142
+
143
+ const finishPublishingTarget = async (
144
+ storeArg: ReleaseJournalStore,
145
+ journalArg: IReleaseJournal,
146
+ selectorArg: TReleaseTargetSelector,
147
+ stateArg: "verified" | "failed" | "conflict",
148
+ errorArg: TReleaseErrorCode | null,
149
+ ): Promise<IReleaseJournal> => {
150
+ const attemptId = getTargetStatus(journalArg, selectorArg).attempt?.id;
151
+ if (!attemptId) {
152
+ throw new Error("Release target has no active publication attempt.");
153
+ }
154
+ return updateTarget(
155
+ storeArg,
156
+ journalArg,
157
+ selectorArg,
158
+ "publishing",
159
+ attemptId,
160
+ (statusArg) => {
161
+ statusArg.state = stateArg;
162
+ statusArg.attempt = null;
163
+ statusArg.error = errorArg;
164
+ },
165
+ );
166
+ };
167
+
168
+ const markUnclaimedTarget = async (
169
+ storeArg: ReleaseJournalStore,
170
+ journalArg: IReleaseJournal,
171
+ selectorArg: TReleaseTargetSelector,
172
+ expectedStatesArg: TReleaseTargetState[],
173
+ stateArg: "verified" | "failed" | "conflict",
174
+ errorArg: TReleaseErrorCode | null,
175
+ ): Promise<IReleaseJournal> =>
176
+ updateTarget(
177
+ storeArg,
178
+ journalArg,
179
+ selectorArg,
180
+ expectedStatesArg,
181
+ null,
182
+ (statusArg) => {
183
+ statusArg.state = stateArg;
184
+ statusArg.error = errorArg;
185
+ },
186
+ );
187
+
188
+ const recoverPublishingTarget = async (
189
+ storeArg: ReleaseJournalStore,
190
+ journalArg: IReleaseJournal,
191
+ selectorArg: TReleaseTargetSelector,
192
+ recoveryAttemptIdArg: string | undefined,
193
+ ): Promise<IReleaseJournal> => {
194
+ const status = getTargetStatus(journalArg, selectorArg);
195
+ const attemptId = status.attempt?.id;
196
+ if (!attemptId || recoveryAttemptIdArg !== attemptId) {
197
+ throw new Error(
198
+ `Release target has unresolved publication attempt ${attemptId || "unknown"}. ` +
199
+ `After proving its publisher stopped, retry with --recover-attempt=${attemptId || "<attempt-id>"}.`,
200
+ );
201
+ }
202
+ return finishPublishingTarget(
203
+ storeArg,
204
+ journalArg,
205
+ selectorArg,
206
+ "failed",
207
+ "attempt-recovery-required",
208
+ );
209
+ };
210
+
211
+ const inspectRemoteRelease = async (
212
+ smartshellArg: plugins.smartshell.Smartshell,
213
+ cwdArg: string,
214
+ destinationArg: string,
215
+ journalArg: IReleaseJournal,
216
+ ): Promise<IGitProbeResult> => {
217
+ const tagRef = `refs/tags/${journalArg.release.tag}`;
218
+ const result = await smartshellArg.execSpawn(
219
+ "git",
220
+ [
221
+ "ls-remote",
222
+ destinationArg,
223
+ "refs/heads/main",
224
+ tagRef,
225
+ `${tagRef}^{}`,
226
+ ],
227
+ {
228
+ cwd: cwdArg,
229
+ env: releaseGitEnv,
230
+ silent: true,
231
+ timeout: gitProbeTimeoutMs,
232
+ timeoutKillGraceMs: 5_000,
233
+ },
234
+ );
235
+ if (result.exitCode !== 0) {
236
+ return { status: "inconclusive" };
237
+ }
238
+ const refs = new Map<string, string>();
239
+ for (const line of result.stdout.split("\n")) {
240
+ const match = line.trim().match(/^([0-9a-f]{40}|[0-9a-f]{64})\s+(.+)$/);
241
+ if (match) refs.set(match[2], match[1]);
242
+ }
243
+ const mainOid = refs.get("refs/heads/main");
244
+ const tagOid = refs.get(tagRef);
245
+ const tagCommitOid = refs.get(`${tagRef}^{}`);
246
+ if (
247
+ mainOid === journalArg.release.mainOid &&
248
+ tagOid === journalArg.release.tagOid &&
249
+ tagCommitOid === journalArg.release.mainOid
250
+ ) {
251
+ return { status: "exact" };
252
+ }
253
+ if (
254
+ mainOid === journalArg.git.expectedRemoteMainOid &&
255
+ tagOid === undefined &&
256
+ tagCommitOid === undefined
257
+ ) {
258
+ return { status: "pending" };
259
+ }
260
+ return { status: "conflict" };
261
+ };
262
+
263
+ const executeGitTarget = async (
264
+ optionsArg: IReleasePublicationOptions,
265
+ journalArg: IReleaseJournal,
266
+ ): Promise<IReleaseJournal> => {
267
+ const selector: TReleaseTargetSelector = { kind: "git" };
268
+ if (journalArg.git.state === "skipped") {
269
+ return journalArg;
270
+ }
271
+ const expectedRemoteMainOid = journalArg.git.expectedRemoteMainOid;
272
+ if (!optionsArg.pushUrl || !expectedRemoteMainOid) {
273
+ throw new Error("Active Git release journal lacks a verified destination.");
274
+ }
275
+
276
+ let journal = journalArg;
277
+ let probe = await inspectRemoteRelease(
278
+ optionsArg.smartshell,
279
+ optionsArg.cwd,
280
+ optionsArg.pushUrl,
281
+ journal,
282
+ );
283
+ if (journal.git.state === "verified") {
284
+ if (probe.status === "exact") {
285
+ await optionsArg.ensurePublicationState(journal.release.mainOid);
286
+ return journal;
287
+ }
288
+ if (probe.status === "conflict") {
289
+ await markUnclaimedTarget(
290
+ optionsArg.store,
291
+ journal,
292
+ selector,
293
+ ["verified"],
294
+ "conflict",
295
+ "destination-conflict",
296
+ );
297
+ throw new Error("Previously verified Git release refs no longer match.");
298
+ }
299
+ throw new Error(
300
+ "Previously verified Git release refs cannot currently be verified.",
301
+ );
302
+ }
303
+
304
+ if (journal.git.state === "publishing") {
305
+ if (probe.status === "exact") {
306
+ journal = await finishPublishingTarget(
307
+ optionsArg.store,
308
+ journal,
309
+ selector,
310
+ "verified",
311
+ null,
312
+ );
313
+ await optionsArg.ensurePublicationState(journal.release.mainOid);
314
+ return journal;
315
+ }
316
+ if (probe.status === "conflict") {
317
+ await finishPublishingTarget(
318
+ optionsArg.store,
319
+ journal,
320
+ selector,
321
+ "conflict",
322
+ "destination-conflict",
323
+ );
324
+ throw new Error("Remote Git refs conflict with the release journal.");
325
+ }
326
+ if (probe.status === "inconclusive") {
327
+ throw new Error("Remote Git publication state is inconclusive.");
328
+ }
329
+ journal = await recoverPublishingTarget(
330
+ optionsArg.store,
331
+ journal,
332
+ selector,
333
+ optionsArg.recoveryAttemptId,
334
+ );
335
+ }
336
+
337
+ if (journal.git.state === "conflict") {
338
+ throw new Error("Git release target is in conflict and cannot resume automatically.");
339
+ }
340
+ probe = await inspectRemoteRelease(
341
+ optionsArg.smartshell,
342
+ optionsArg.cwd,
343
+ optionsArg.pushUrl,
344
+ journal,
345
+ );
346
+ if (probe.status === "exact") {
347
+ journal = await markUnclaimedTarget(
348
+ optionsArg.store,
349
+ journal,
350
+ selector,
351
+ ["pending", "failed"],
352
+ "verified",
353
+ null,
354
+ );
355
+ await optionsArg.ensurePublicationState(journal.release.mainOid);
356
+ return journal;
357
+ }
358
+ if (probe.status === "conflict") {
359
+ await markUnclaimedTarget(
360
+ optionsArg.store,
361
+ journal,
362
+ selector,
363
+ ["pending", "failed"],
364
+ "conflict",
365
+ "destination-conflict",
366
+ );
367
+ throw new Error("Remote Git refs conflict with the release journal.");
368
+ }
369
+ if (probe.status === "inconclusive") {
370
+ await markUnclaimedTarget(
371
+ optionsArg.store,
372
+ journal,
373
+ selector,
374
+ ["pending", "failed"],
375
+ "failed",
376
+ "verification-inconclusive",
377
+ );
378
+ throw new Error("Remote Git publication state is inconclusive.");
379
+ }
380
+
381
+ await optionsArg.ensurePublicationState(expectedRemoteMainOid);
382
+ journal = await claimTarget(optionsArg.store, journal, selector);
383
+ const pushResult = await optionsArg.smartshell.execSpawn(
384
+ "git",
385
+ buildReleaseGitPushArgs({
386
+ destination: optionsArg.pushUrl,
387
+ newVersion: journal.release.version,
388
+ pushBranch: true,
389
+ pushTags: true,
390
+ mainOid: journal.release.mainOid,
391
+ tagOid: journal.release.tagOid,
392
+ expectedRemoteMainOid,
393
+ }),
394
+ {
395
+ cwd: optionsArg.cwd,
396
+ env: releaseGitEnv,
397
+ timeout: gitPushTimeoutMs,
398
+ timeoutKillGraceMs: 5_000,
399
+ },
400
+ );
401
+ probe = await inspectRemoteRelease(
402
+ optionsArg.smartshell,
403
+ optionsArg.cwd,
404
+ optionsArg.pushUrl,
405
+ journal,
406
+ );
407
+ if (probe.status === "exact") {
408
+ journal = await finishPublishingTarget(
409
+ optionsArg.store,
410
+ journal,
411
+ selector,
412
+ "verified",
413
+ null,
414
+ );
415
+ await optionsArg.ensurePublicationState(journal.release.mainOid);
416
+ return journal;
417
+ }
418
+ if (probe.status === "conflict") {
419
+ await finishPublishingTarget(
420
+ optionsArg.store,
421
+ journal,
422
+ selector,
423
+ "conflict",
424
+ "destination-conflict",
425
+ );
426
+ throw new Error("Git publication produced conflicting remote refs.");
427
+ }
428
+ await finishPublishingTarget(
429
+ optionsArg.store,
430
+ journal,
431
+ selector,
432
+ "failed",
433
+ pushResult.exitCode === 0
434
+ ? "verification-inconclusive"
435
+ : "command-failed",
436
+ );
437
+ throw new Error("Git publication did not produce verifiable remote refs.");
438
+ };
439
+
440
+ const probeNpm = async (
441
+ journalArg: IReleaseJournal,
442
+ registryArg: string,
443
+ optionsArg: IReleasePublicationOptions,
444
+ waitArg = false,
445
+ ): Promise<INpmArtifactProbeResult> => {
446
+ if (!journalArg.artifact) {
447
+ throw new Error("npm release target has no exact artifact.");
448
+ }
449
+ return waitArg
450
+ ? waitForAnonymousNpmArtifact(
451
+ registryArg,
452
+ journalArg.artifact,
453
+ journalArg.npm.tag,
454
+ optionsArg.npmProbeOptions,
455
+ )
456
+ : probeAnonymousNpmArtifact(
457
+ registryArg,
458
+ journalArg.artifact,
459
+ journalArg.npm.tag,
460
+ optionsArg.npmProbeOptions,
461
+ );
462
+ };
463
+
464
+ const markNpmProbeFailure = async (
465
+ optionsArg: IReleasePublicationOptions,
466
+ journalArg: IReleaseJournal,
467
+ selectorArg: TReleaseTargetSelector,
468
+ expectedStatesArg: TReleaseTargetState[],
469
+ probeArg: INpmArtifactProbeResult,
470
+ ): Promise<IReleaseJournal> => {
471
+ const conflict = probeArg.status === "conflict";
472
+ return markUnclaimedTarget(
473
+ optionsArg.store,
474
+ journalArg,
475
+ selectorArg,
476
+ expectedStatesArg,
477
+ conflict ? "conflict" : "failed",
478
+ conflict ? "artifact-conflict" : "verification-inconclusive",
479
+ );
480
+ };
481
+
482
+ const executeNpmRegistry = async (
483
+ optionsArg: IReleasePublicationOptions,
484
+ journalArg: IReleaseJournal,
485
+ registryArg: string,
486
+ ): Promise<IReleaseJournal> => {
487
+ const selector: TReleaseTargetSelector = { kind: "npm", registry: registryArg };
488
+ let journal = journalArg;
489
+ let status = getTargetStatus(journal, selector);
490
+ let probe = await probeNpm(journal, registryArg, optionsArg);
491
+
492
+ if (status.state === "verified") {
493
+ if (probe.status === "exact") {
494
+ return journal;
495
+ }
496
+ if (probe.status === "conflict") {
497
+ await markUnclaimedTarget(
498
+ optionsArg.store,
499
+ journal,
500
+ selector,
501
+ ["verified"],
502
+ "conflict",
503
+ "artifact-conflict",
504
+ );
505
+ throw new Error(`Previously verified npm registry drifted: ${registryArg}`);
506
+ }
507
+ throw new Error(
508
+ `Previously verified npm registry cannot currently be verified: ${registryArg}`,
509
+ );
510
+ }
511
+ if (status.state === "publishing") {
512
+ if (probe.status === "exact") {
513
+ return finishPublishingTarget(
514
+ optionsArg.store,
515
+ journal,
516
+ selector,
517
+ "verified",
518
+ null,
519
+ );
520
+ }
521
+ if (probe.status === "conflict") {
522
+ await finishPublishingTarget(
523
+ optionsArg.store,
524
+ journal,
525
+ selector,
526
+ "conflict",
527
+ "artifact-conflict",
528
+ );
529
+ throw new Error(`npm registry contains conflicting bytes: ${registryArg}`);
530
+ }
531
+ if (probe.status === "inconclusive") {
532
+ throw new Error(`npm registry publication is inconclusive: ${registryArg}`);
533
+ }
534
+ journal = await recoverPublishingTarget(
535
+ optionsArg.store,
536
+ journal,
537
+ selector,
538
+ optionsArg.recoveryAttemptId,
539
+ );
540
+ status = getTargetStatus(journal, selector);
541
+ }
542
+ if (status.state === "conflict") {
543
+ throw new Error(`npm registry target is in conflict: ${registryArg}`);
544
+ }
545
+
546
+ probe = await probeNpm(journal, registryArg, optionsArg);
547
+ if (probe.status === "exact") {
548
+ if (status.attempts === 0 && journal.npm.alreadyPublished === "error") {
549
+ await markUnclaimedTarget(
550
+ optionsArg.store,
551
+ journal,
552
+ selector,
553
+ ["pending", "failed"],
554
+ "conflict",
555
+ "already-published",
556
+ );
557
+ throw new Error(`npm version was already published: ${registryArg}`);
558
+ }
559
+ return markUnclaimedTarget(
560
+ optionsArg.store,
561
+ journal,
562
+ selector,
563
+ ["pending", "failed"],
564
+ "verified",
565
+ null,
566
+ );
567
+ }
568
+ if (probe.status !== "absent") {
569
+ await markNpmProbeFailure(
570
+ optionsArg,
571
+ journal,
572
+ selector,
573
+ ["pending", "failed"],
574
+ probe,
575
+ );
576
+ throw new Error(`npm registry cannot be verified safely: ${registryArg}`);
577
+ }
578
+
579
+ const artifactPath = optionsArg.store.getArtifactPath(journal.release.version);
580
+ if (!journal.artifact) {
581
+ throw new Error("npm release journal has no artifact.");
582
+ }
583
+ await verifyStoredNpmArtifact(artifactPath, journal.artifact);
584
+ journal = await claimTarget(optionsArg.store, journal, selector);
585
+ const publishResult = await publishNpmArtifact(
586
+ optionsArg.smartshell,
587
+ optionsArg.cwd,
588
+ artifactPath,
589
+ registryArg,
590
+ );
591
+ probe = await probeNpm(journal, registryArg, optionsArg, true);
592
+ if (probe.status === "exact") {
593
+ return finishPublishingTarget(
594
+ optionsArg.store,
595
+ journal,
596
+ selector,
597
+ "verified",
598
+ null,
599
+ );
600
+ }
601
+ if (probe.status === "conflict") {
602
+ await finishPublishingTarget(
603
+ optionsArg.store,
604
+ journal,
605
+ selector,
606
+ "conflict",
607
+ "artifact-conflict",
608
+ );
609
+ throw new Error(`npm registry published conflicting bytes: ${registryArg}`);
610
+ }
611
+ await finishPublishingTarget(
612
+ optionsArg.store,
613
+ journal,
614
+ selector,
615
+ "failed",
616
+ publishResult.exitCode === 0
617
+ ? "verification-inconclusive"
618
+ : "command-failed",
619
+ );
620
+ throw new Error(`npm publication could not be verified: ${registryArg}`);
621
+ };
622
+
623
+ export const executeReleasePublication = async (
624
+ optionsArg: IReleasePublicationOptions,
625
+ ): Promise<IReleaseJournal> => {
626
+ let journal = optionsArg.journal;
627
+ journal = await executeGitTarget(optionsArg, journal);
628
+ const expectedRemoteMainOid =
629
+ journal.git.state === "verified"
630
+ ? journal.release.mainOid
631
+ : journal.git.expectedRemoteMainOid || undefined;
632
+ for (const registry of journal.npm.registries) {
633
+ await optionsArg.ensurePublicationState(expectedRemoteMainOid);
634
+ journal = await executeNpmRegistry(
635
+ optionsArg,
636
+ journal,
637
+ registry.registry,
638
+ );
639
+ }
640
+ return journal;
641
+ };