@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
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@git.zone/cli",
3
3
  "private": false,
4
- "version": "5.0.0",
4
+ "version": "6.0.1",
5
5
  "description": "A comprehensive CLI tool for enhancing and managing local development workflows with gitzone utilities, focusing on project setup, version control, code formatting, and template management.",
6
6
  "main": "dist_ts/index.js",
7
7
  "typings": "dist_ts/index.d.ts",
package/readme.hints.md CHANGED
@@ -104,11 +104,57 @@ The commit module no longer bumps versions, creates tags, or publishes packages.
104
104
  - Release publishing is target-based under `@git.zone/cli.release.targets`.
105
105
  - NPM registries only live under `@git.zone/cli.release.targets.npm.registries`.
106
106
 
107
+ ### Exact artifact release journal v1
108
+
109
+ Every release that reaches final Git/npm publication atomically installs
110
+ canonical state below the Git common directory at
111
+ `gitzone/releases/v1/v<version>/`. npm releases contain exactly one
112
+ `package.tgz`; the journal binds that file's package identity, byte length,
113
+ SHA-1, SHA-256, SHA-512 integrity, release commit, annotated tag object, hashed
114
+ Git destination, registry list, and target attempts. The state is deliberately
115
+ outside the worktree so build cleanliness checks do not conflict with durable
116
+ recovery.
117
+
118
+ Journal writes are fsynced same-directory replacements under the shared
119
+ atomic-mkdir lock and require the caller's exact prior revision. Transactions
120
+ may update target attempts and completion state but cannot alter release,
121
+ artifact, destination, or target identity. A malformed, noncanonical, future,
122
+ or manually reformatted journal fails closed.
123
+
124
+ Selected Git publication requires a canonical remote name and validates its
125
+ single resolved push URL before remote contact, including during planning.
126
+ Embedded HTTP(S) credentials, passwords, parameters, and unsupported protocols
127
+ fail closed. Git publication then probes the exact branch, annotated tag
128
+ object, and peeled tag commit before and after an atomic leased push. npm publication always uses the
129
+ stored tarball and verifies each public registry anonymously with redirects
130
+ disabled. Verification covers metadata identity, integrity and shasum, bounded
131
+ downloaded bytes, SHA-256, and the `latest` dist-tag.
132
+
133
+ `gitzone release inspect [version]` is read-only. `gitzone release resume
134
+ <version>` rejects fresh-release overrides and never rebuilds or repacks. It
135
+ also rejects changed journaled configuration. A target left in `publishing`
136
+ state first reconciles exact remote
137
+ state. If the remote still looks absent, recovery requires the exact recorded
138
+ attempt ID and independent proof that its process stopped; no timeout or PID
139
+ guess reclaims publication authority.
140
+
141
+ Schema v1 recovery begins only after atomic journal installation. Local
142
+ version, changelog, commit, tag, build, and pack failures before that point, as
143
+ well as the pre-release `--merge` integration push, require explicit operator
144
+ reconciliation; they are not represented as publication attempts.
145
+
146
+ Schema v1 supports Git and public npm only and requires resolved pnpm 11.21.0.
147
+ Private npm cannot be anonymously verified. Docker releases remain blocked
148
+ until tSDocker returns structured immutable digest qualification and promotion
149
+ results. Legacy tag-triggered `npmci npm publish` jobs must be removed because
150
+ they bypass journal ownership.
151
+
107
152
  **Benefits:**
108
153
 
109
154
  - Commit is safer and has no publishing side effects.
110
155
  - Multiple source commits can accumulate into one release via `Pending`.
111
- - Per-artifact release results can distinguish published, already-published, skipped, and failed targets.
156
+ - Durable target states distinguish pending, publishing, verified, failed,
157
+ conflicting, and skipped publication while retaining exact attempt evidence.
112
158
 
113
159
  ### Auto-Accept Flag for Commits
114
160
 
package/readme.md CHANGED
@@ -137,7 +137,7 @@ Commit flags:
137
137
 
138
138
  `--merge` is intentionally strict: the source and existing `main` worktree must be clean, the source must be linearly rebased onto current remote `main`, and the Git target must push both the branch and tag. GitZone pins the single configured push URL, compare-and-swap fast-forwards local `main`, updates its verified worktree, and lease-pushes that exact source commit before creating release metadata. It rejects diverged or merged history, local replacement refs or grafts, stale worktrees, incomplete shallow history, changed destinations, and remote races. If the pre-release push fails, GitZone restores local `main` only when doing so cannot overwrite concurrent work. This plumbing-level integration intentionally does not run merge hooks or write `ORIG_HEAD`.
139
139
 
140
- `gitzone release` performs the release core once, then publishes to configured targets. An active Git target must succeed before npm or Docker publication can start.
140
+ `gitzone release` performs the release core once, then publishes to configured targets. An active Git target must succeed and be verified before npm publication can start.
141
141
 
142
142
  The release core is not configurable plumbing. It always follows the same professional release transaction:
143
143
 
@@ -149,14 +149,15 @@ The release core is not configurable plumbing. It always follows the same profes
149
149
  6. Move pending changelog entries into the new version section.
150
150
  7. Create the local release commit and tag on `main`.
151
151
  8. Run the configured release build, require a clean tree, and revalidate the release checkout.
152
+ 9. When npm is selected, pack it once into an exact tarball. Atomically install a durable journal for Git-only and npm releases before final Git release-ref or npm publication.
152
153
 
153
154
  Targets decide what happens after that:
154
155
 
155
156
  | Target | What it does |
156
157
  | --- | --- |
157
158
  | `git` | Atomically pushes the exact `main` release commit and new tag, often triggering remote CI release builds |
158
- | `npm` | Publishes the package to configured npm registries |
159
- | `docker` | Delegates container builds and pushes to `tsdocker` |
159
+ | `npm` | Publishes the same journaled tarball to every configured registry and verifies its bytes anonymously |
160
+ | `docker` | Fails closed in journal schema v1 until tSDocker provides structured digest-qualified publication results |
160
161
 
161
162
  ```bash
162
163
  # Preview the resolved release plan
@@ -171,10 +172,7 @@ gitzone release --merge
171
172
  # Release only to npm
172
173
  gitzone release --target npm
173
174
 
174
- # Release only to git and Docker
175
- gitzone release --target git,docker
176
-
177
- # Skip package/container publishing and keep only git target
175
+ # Remove npm and Docker from the resolved targets; Git remains only if configured
178
176
  gitzone release --no-publish
179
177
 
180
178
  # Override inferred semver level
@@ -188,15 +186,57 @@ Release flags:
188
186
  | `-y`, `--yes` | Run without interactive confirmation |
189
187
  | `-t`, `--test` | Enable preflight tests |
190
188
  | `-b`, `--build` | Enable the release build after local release metadata is created |
191
- | `-p`, `--push` | Enable the `git` target |
192
- | `--target <csv>` | Use only selected targets, e.g. `git,npm` |
193
- | `--npm` | Enable the `npm` target |
194
- | `--docker` | Enable the `docker` target |
195
- | `--no-publish` | Keep release core and `git` target only |
189
+ | `-p`, `--push` | Explicitly select the `git` target; combine with other target flags as needed |
190
+ | `--target <csv>` | Replace configured targets with a non-empty comma-separated subset of `git`, `npm`, and `docker`; empty or unknown elements fail |
191
+ | `--npm` | Explicitly select the `npm` target; combine with other target flags as needed |
192
+ | `--docker` | Explicitly select `docker`; planning is supported, but a mutating schema v1 release fails before integration or release metadata |
193
+ | `--no-publish` | Remove npm and Docker from the resolved target set without implicitly enabling Git |
196
194
  | `--no-build` | Disable the post-metadata release build for this run |
197
195
  | `--merge` | Fast-forward and lease-push a cleanly rebased feature branch into `main`, then release from `main` |
198
196
  | `--major`, `--minor`, `--patch` | Override inferred semver level |
199
197
  | `--plan` | Show the resolved workflow without fetching or mutating refs, files, the index, or worktrees |
198
+ | `inspect [version]` | Read one or all durable release journals; add `--json` for machine-readable output |
199
+ | `resume <version>` | Resume only journaled Git/npm publication; fresh-release overrides, including negated forms, are rejected, and `--json` remains inspect-only |
200
+ | `--recover-attempt <id>` | Recover the exact 32-character lowercase hexadecimal `attempt.id` shown by `inspect --json`, after independently proving its publisher stopped |
201
+
202
+ ### Exact npm artifacts and release journals
203
+
204
+ Journal schema v1 supports Git and public npm publication. Fresh and resumed npm publication requires the resolved `pnpm` binary to be exactly the version pinned by this package (`11.21.0`). Registries must be unique canonical credential-free HTTP(S) URLs, and `release.targets.npm.accessLevel` must be `public` so verification does not depend on publisher credentials.
205
+
206
+ A selected Git target requires a canonical remote name and exactly one resolved push URL. Before any remote contact, including during `--plan`, GitZone rejects HTTP(S) usernames or passwords, embedded passwords, query strings, fragments, and unsupported protocols. Canonical local paths and SSH destinations may retain their required SSH username.
207
+
208
+ Every release that reaches final Git/npm publication stores `journal.json` under the repository's Git common directory. npm releases additionally run `pnpm pack` once and store the resulting `package.tgz` beside it:
209
+
210
+ ```text
211
+ <git-common-dir>/gitzone/releases/v1/v<version>/package.tgz
212
+ <git-common-dir>/gitzone/releases/v1/v<version>/journal.json
213
+ ```
214
+
215
+ The canonical journal binds the release commit, annotated tag object, hashed Git destination, npm registries, per-target attempt states, and, when npm is selected, the package identity, tarball size, SHA-1, SHA-256, and SHA-512 integrity. Journal updates use revision compare-and-swap under an interprocess lock. Release identity and destinations cannot change after journal creation. Inspect and resume reject malformed, future, noncanonical, duplicate-key, or merely reformatted journal JSON rather than repairing or normalizing it.
216
+
217
+ Packing must leave the release tree clean. Each registry receives that exact stored tarball through `pnpm publish <tarball> --ignore-scripts`, so publish lifecycle scripts do not run. Success is recorded only after anonymous no-redirect probes verify version metadata, SHA-1 and integrity fields, the downloaded tarball bytes, and the `latest` dist-tag. A matching pre-existing version follows `release.targets.npm.alreadyPublished`; conflicting bytes always stop the release, while transient responses and dist-tag propagation remain retryable.
218
+
219
+ Independent tag-triggered npm publishers are incompatible with this transaction. GitZone rejects a release while `.gitea/workflows/default_tags.yaml` or `.gitlab-ci.yml` still contains the legacy `npmci npm publish` command. Apply the v6 Gitea workflow template or remove the legacy GitLab job and commit that change first.
220
+
221
+ Inspect and resume without regenerating release identity:
222
+
223
+ ```bash
224
+ # List journals or inspect one exact version
225
+ gitzone release inspect
226
+ gitzone release inspect 6.0.0 --json
227
+
228
+ # Reconcile remote state, then continue only unfinished targets
229
+ gitzone release resume 6.0.0 -y
230
+
231
+ # Only after proving the recorded publisher process has stopped
232
+ gitzone release resume 6.0.0 -y --recover-attempt="$ATTEMPT_ID"
233
+ ```
234
+
235
+ Resume requires the local `main` commit, annotated release tag, and each journaled destination setting to remain exact. The Git remote must still match when Git is journaled; npm registry, access, and already-published settings must still match when npm is journaled. Non-journaled target configuration is ignored, so releases created with a target subset remain resumable. Fresh-release overrides such as targets, integration, build, test, and version flags are rejected even in negated forms such as `--no-git`, `--no-publish`, and `--no-build`; JSON output is available only through `inspect --json`. Resume probes remote state before acting. Exact remote state is accepted without republishing; positive byte, metadata, or ref conflicts fail closed, while absent, pending, inconclusive, and dist-tag propagation results do not overwrite a previously verified state. A target left in `publishing` state is never reclaimed from an absent remote result unless the exact recorded 32-character lowercase hexadecimal `attempt.id` from `git.attempt` or an npm registry attempt is supplied.
236
+
237
+ Schema v1 resumes only final Git release-ref and npm publication after the journal is atomically installed. It does not recover local version, changelog, commit, tag, build, or pack failures that occur before installation, nor the pre-release `--merge` integration push. Those failures stop before the final release commit/tag or npm artifact is published and require explicit operator reconciliation.
238
+
239
+ Docker target configuration remains available for planning and future journal schemas, but a mutating v6 release with an active Docker target stops before release integration or metadata changes. This restriction remains until tSDocker owns structured immutable digest qualification and promotion.
200
240
 
201
241
  ## Standard Changelog
202
242
 
@@ -218,7 +258,7 @@ The standard buckets are `Breaking Changes`, `Features`, `Fixes`, `Documentation
218
258
 
219
259
  ## Configuration
220
260
 
221
- CLI workflow config lives under `@git.zone/cli` in `.smartconfig.json`. Docker build and registry behavior lives under `@git.zone/tsdocker` and is used by the Docker release target.
261
+ CLI workflow config lives under `@git.zone/cli` in `.smartconfig.json`. Dormant Docker build and registry settings remain under `@git.zone/tsdocker` for the later journal schema that will restore Docker publication.
222
262
 
223
263
  ```json
224
264
  {
@@ -249,7 +289,7 @@ CLI workflow config lives under `@git.zone/cli` in `.smartconfig.json`. Docker b
249
289
  "alreadyPublished": "success"
250
290
  },
251
291
  "docker": {
252
- "enabled": true,
292
+ "enabled": false,
253
293
  "engine": "tsdocker",
254
294
  "registry": "registry.gitlab.com",
255
295
  "buildRegistries": ["registry.gitlab.com"],
@@ -284,23 +324,18 @@ values should be hosts without `http://` or `https://`:
284
324
  @git.zone/tsdocker.registries
285
325
  ```
286
326
 
287
- The Docker release target runs the project's pinned `@git.zone/tsdocker` through
288
- `pnpm exec`; install it in each Docker-producing project:
327
+ Docker release configuration uses the project's pinned `@git.zone/tsdocker`
328
+ through `pnpm exec`. Install it in each Docker-producing project, but keep the
329
+ target disabled while release journal schema v1 is active:
289
330
 
290
331
  ```bash
291
332
  pnpm add --save-dev @git.zone/tsdocker
292
333
  ```
293
334
 
294
- Set `release.targets.docker.registry` to select one destination already declared
295
- by the tSDocker config when a release must not publish to every configured
296
- registry.
297
- Set `release.targets.docker.buildRegistries` when the build should authenticate
298
- only the configured registries used by private base images; an empty list keeps
299
- the compatibility default of authenticating every configured registry. Bounded
300
- build authentication requires project-local `@git.zone/tsdocker` 3.3.0 or newer.
301
- Set `release.targets.docker.test` to `true` to require every selected image test
302
- to pass before destination publication. This requires project-local
303
- `@git.zone/tsdocker` 3.4.0 or newer.
335
+ The dormant `release.targets.docker.registry`, `buildRegistries`, and `test`
336
+ settings describe the intended tSDocker destination, bounded base-image
337
+ authentication, and pre-publication image tests. They do not execute while
338
+ journal schema v1 is active.
304
339
 
305
340
  Useful config commands:
306
341
 
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@git.zone/cli',
6
- version: '5.0.0',
6
+ version: '6.0.1',
7
7
  description: 'A comprehensive CLI tool for enhancing and managing local development workflows with gitzone utilities, focusing on project setup, version control, code formatting, and template management.'
8
8
  }
@@ -24,6 +24,29 @@ interface ICliConfigSettings {
24
24
 
25
25
  type TArgSource = Record<string, any> & { _?: string[] };
26
26
 
27
+ const booleanLongFlags = new Set([
28
+ "agent",
29
+ "allow-breaking",
30
+ "allow-empty",
31
+ "build",
32
+ "check-updates",
33
+ "docker",
34
+ "help",
35
+ "interactive",
36
+ "json",
37
+ "major",
38
+ "merge",
39
+ "minor",
40
+ "npm",
41
+ "patch",
42
+ "plain",
43
+ "plan",
44
+ "push",
45
+ "quiet",
46
+ "test",
47
+ "yes",
48
+ ]);
49
+
27
50
  const camelCase = (value: string): string => {
28
51
  return value.replace(/-([a-z])/g, (_match, group: string) =>
29
52
  group.toUpperCase(),
@@ -55,13 +78,23 @@ const parseRawArgv = (argv: string[]): TArgSource => {
55
78
 
56
79
  if (currentArg.startsWith("--")) {
57
80
  const withoutPrefix = currentArg.slice(2);
58
- const [rawKey, inlineValue] = withoutPrefix.split("=", 2);
81
+ const equalsIndex = withoutPrefix.indexOf("=");
82
+ const rawKey =
83
+ equalsIndex === -1 ? withoutPrefix : withoutPrefix.slice(0, equalsIndex);
84
+ const inlineValue =
85
+ equalsIndex === -1 ? undefined : withoutPrefix.slice(equalsIndex + 1);
59
86
  if (inlineValue !== undefined) {
60
87
  parsedArgv[rawKey] = inlineValue;
61
88
  parsedArgv[camelCase(rawKey)] = inlineValue;
62
89
  continue;
63
90
  }
64
91
 
92
+ if (booleanLongFlags.has(rawKey)) {
93
+ parsedArgv[rawKey] = true;
94
+ parsedArgv[camelCase(rawKey)] = true;
95
+ continue;
96
+ }
97
+
65
98
  const nextArg = argv[i + 1];
66
99
  if (nextArg && !nextArg.startsWith("-")) {
67
100
  parsedArgv[rawKey] = nextArg;
@@ -127,6 +127,13 @@ export interface IResolvedReleaseWorkflow {
127
127
  dockerNoBuild: boolean;
128
128
  }
129
129
 
130
+ export interface IResolvedReleaseResumeConfiguration {
131
+ gitRemote: string;
132
+ npmRegistries: string[];
133
+ npmAccessLevel: "public" | "private";
134
+ npmAlreadyPublished: "success" | "error";
135
+ }
136
+
130
137
  interface ICliWorkflowConfig {
131
138
  commit?: ICommitWorkflowConfig;
132
139
  release?: IReleaseWorkflowConfig;
@@ -339,12 +346,31 @@ const normalizeCommitSteps = (rawSteps: TCommitStep[]): TCommitStep[] => {
339
346
 
340
347
  const getTargetOverride = (argvArg: any): TReleaseTarget[] | undefined => {
341
348
  const validTargets: TReleaseTarget[] = ["git", "npm", "docker"];
342
- const rawTargets = argvArg.target || argvArg.targets;
343
- if (typeof rawTargets === "string") {
344
- return rawTargets
349
+ const targetKey = ["target", "targets"].find((keyArg) =>
350
+ Object.prototype.hasOwnProperty.call(argvArg, keyArg),
351
+ );
352
+ if (targetKey) {
353
+ const rawTargets = argvArg[targetKey];
354
+ if (typeof rawTargets !== "string" || !rawTargets.trim()) {
355
+ throw new Error(
356
+ "--target requires a non-empty comma-separated list of release targets.",
357
+ );
358
+ }
359
+ const requestedTargets = rawTargets
345
360
  .split(",")
346
- .map((target) => target.trim())
347
- .filter((target): target is TReleaseTarget => validTargets.includes(target as TReleaseTarget));
361
+ .map((target) => target.trim());
362
+ if (requestedTargets.some((target) => !target)) {
363
+ throw new Error(
364
+ "--target requires a non-empty comma-separated list of release targets.",
365
+ );
366
+ }
367
+ const invalidTargets = requestedTargets.filter(
368
+ (target) => !validTargets.includes(target as TReleaseTarget),
369
+ );
370
+ if (invalidTargets.length > 0) {
371
+ throw new Error(`Unknown release target: ${invalidTargets.join(", ")}`);
372
+ }
373
+ return requestedTargets as TReleaseTarget[];
348
374
  }
349
375
 
350
376
  const targets: TReleaseTarget[] = [];
@@ -369,6 +395,8 @@ const buildReleasePlan = (options: {
369
395
  }
370
396
  plan.push("core.version", "core.changelog", "core.commit", "core.tag");
371
397
  if (options.runBuild) plan.push("core.build");
398
+ if (options.targets.includes("npm")) plan.push("core.packNpmArtifact");
399
+ plan.push("core.writeReleaseJournal");
372
400
  for (const target of options.targets) {
373
401
  plan.push(`target.${target}`);
374
402
  }
@@ -435,8 +463,11 @@ export const resolveReleaseWorkflow = async (argvArg: any): Promise<IResolvedRel
435
463
  const gitConfig = targetConfig.git || {};
436
464
  const npmConfig = targetConfig.npm || {};
437
465
  const dockerConfig = targetConfig.docker || {};
438
- const npmRegistries = (npmConfig.registries || []).map(normalizeRegistryUrl);
439
- const npmEnabled = npmConfig.enabled ?? npmRegistries.length > 0;
466
+ const rawNpmRegistries = npmConfig.registries;
467
+ const npmRegistryConfigurationPresent = Array.isArray(rawNpmRegistries)
468
+ ? rawNpmRegistries.length > 0
469
+ : rawNpmRegistries !== undefined;
470
+ const npmEnabled = npmConfig.enabled ?? npmRegistryConfigurationPresent;
440
471
  const gitEnabled = gitConfig.enabled ?? true;
441
472
  const dockerEnabled = dockerConfig.enabled ?? false;
442
473
  const pushBranch = resolveGitPushSetting(
@@ -479,6 +510,22 @@ export const resolveReleaseWorkflow = async (argvArg: any): Promise<IResolvedRel
479
510
  targets = releaseTargetOrder.filter((target) =>
480
511
  selectedTargets.includes(target),
481
512
  );
513
+ let npmRegistries: string[] = [];
514
+ if (targets.includes("npm")) {
515
+ if (
516
+ rawNpmRegistries !== undefined &&
517
+ (!Array.isArray(rawNpmRegistries) ||
518
+ rawNpmRegistries.some((registryArg) => typeof registryArg !== "string"))
519
+ ) {
520
+ throw new Error("release.targets.npm.registries must be an array of strings");
521
+ }
522
+ npmRegistries = (rawNpmRegistries || []).map(normalizeRegistryUrl);
523
+ if (new Set(npmRegistries).size !== npmRegistries.length) {
524
+ throw new Error(
525
+ "release.targets.npm.registries must be unique after normalization",
526
+ );
527
+ }
528
+ }
482
529
  const mergeRequested = argvArg.merge === true;
483
530
 
484
531
  return {
@@ -521,3 +568,63 @@ export const resolveReleaseWorkflow = async (argvArg: any): Promise<IResolvedRel
521
568
  dockerNoBuild: dockerConfig.noBuild ?? false,
522
569
  };
523
570
  };
571
+
572
+ export const resolveReleaseResumeConfiguration = async (optionsArg: {
573
+ git: boolean;
574
+ npm: boolean;
575
+ }): Promise<IResolvedReleaseResumeConfiguration> => {
576
+ const cliConfig = await readCliWorkflowConfig();
577
+ const releaseConfig = cliConfig.release || {};
578
+ const targetConfig = releaseConfig.targets || {};
579
+ const gitConfig = targetConfig.git || {};
580
+ const npmConfig = targetConfig.npm || {};
581
+
582
+ const gitRemote = optionsArg.git ? gitConfig.remote || "origin" : "origin";
583
+ if (typeof gitRemote !== "string" || !gitRemote.trim()) {
584
+ throw new Error("release.targets.git.remote must be a non-empty string");
585
+ }
586
+
587
+ let npmRegistries: string[] = [];
588
+ let npmAccessLevel: "public" | "private" = "public";
589
+ let npmAlreadyPublished: "success" | "error" = "success";
590
+ if (optionsArg.npm) {
591
+ if (
592
+ npmConfig.registries !== undefined &&
593
+ (!Array.isArray(npmConfig.registries) ||
594
+ npmConfig.registries.some((registryArg) => typeof registryArg !== "string"))
595
+ ) {
596
+ throw new Error("release.targets.npm.registries must be an array of strings");
597
+ }
598
+ npmRegistries = (npmConfig.registries || []).map(normalizeRegistryUrl);
599
+ if (new Set(npmRegistries).size !== npmRegistries.length) {
600
+ throw new Error(
601
+ "release.targets.npm.registries must be unique after normalization",
602
+ );
603
+ }
604
+ if (
605
+ npmConfig.accessLevel !== undefined &&
606
+ npmConfig.accessLevel !== "public" &&
607
+ npmConfig.accessLevel !== "private"
608
+ ) {
609
+ throw new Error("release.targets.npm.accessLevel must be public or private");
610
+ }
611
+ if (
612
+ npmConfig.alreadyPublished !== undefined &&
613
+ npmConfig.alreadyPublished !== "success" &&
614
+ npmConfig.alreadyPublished !== "error"
615
+ ) {
616
+ throw new Error(
617
+ "release.targets.npm.alreadyPublished must be success or error",
618
+ );
619
+ }
620
+ npmAccessLevel = npmConfig.accessLevel || "public";
621
+ npmAlreadyPublished = npmConfig.alreadyPublished || "success";
622
+ }
623
+
624
+ return {
625
+ gitRemote,
626
+ npmRegistries,
627
+ npmAccessLevel,
628
+ npmAlreadyPublished,
629
+ };
630
+ };