@git.zone/cli 4.0.0 → 6.0.0
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/.smartconfig.json +0 -1
- package/assets/templates/ci_default/.gitea/workflows/default_tags.yaml +0 -21
- package/assets/templates/ci_default_gitlab/.gitlab-ci.yml +0 -13
- package/assets/templates/ci_default_private/.gitea/workflows/default_tags.yaml +0 -21
- package/assets/templates/ci_default_private_gitlab/.gitlab-ci.yml +0 -13
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/helpers.climode.js +31 -2
- package/dist_ts/helpers.workflow.d.ts +10 -2
- package/dist_ts/helpers.workflow.js +102 -19
- package/dist_ts/mod_commit/mod.helpers.d.ts +3 -3
- package/dist_ts/mod_commit/mod.helpers.js +10 -10
- package/dist_ts/mod_config/index.js +22 -23
- package/dist_ts/mod_release/classes.releasejournal.d.ts +78 -0
- package/dist_ts/mod_release/classes.releasejournal.js +511 -0
- package/dist_ts/mod_release/helpers.npmartifact.d.ts +32 -0
- package/dist_ts/mod_release/helpers.npmartifact.js +358 -0
- package/dist_ts/mod_release/helpers.releasebranch.d.ts +47 -0
- package/dist_ts/mod_release/helpers.releasebranch.js +627 -0
- package/dist_ts/mod_release/helpers.releasepublication.d.ts +24 -0
- package/dist_ts/mod_release/helpers.releasepublication.js +293 -0
- package/dist_ts/mod_release/index.d.ts +1 -1
- package/dist_ts/mod_release/index.js +539 -209
- package/package.json +1 -1
- package/readme.hints.md +47 -1
- package/readme.md +81 -38
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/helpers.climode.ts +34 -1
- package/ts/helpers.workflow.ts +155 -19
- package/ts/mod_commit/mod.helpers.ts +12 -8
- package/ts/mod_config/index.ts +21 -22
- package/ts/mod_release/classes.releasejournal.ts +740 -0
- package/ts/mod_release/helpers.npmartifact.ts +553 -0
- package/ts/mod_release/helpers.releasebranch.ts +1344 -0
- package/ts/mod_release/helpers.releasepublication.ts +641 -0
- package/ts/mod_release/index.ts +906 -262
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@git.zone/cli",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "6.0.0",
|
|
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
|
-
-
|
|
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
|
@@ -133,25 +133,31 @@ Commit flags:
|
|
|
133
133
|
|
|
134
134
|
## Release Workflow
|
|
135
135
|
|
|
136
|
-
`gitzone release`
|
|
136
|
+
`gitzone release` releases from `main`. Running it on another branch fails before release metadata or ref mutation unless the bare `--merge` flag is explicitly supplied.
|
|
137
|
+
|
|
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
|
+
|
|
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.
|
|
137
141
|
|
|
138
142
|
The release core is not configurable plumbing. It always follows the same professional release transaction:
|
|
139
143
|
|
|
140
|
-
1.
|
|
141
|
-
2. Read `changelog.md` `## Pending` entries.
|
|
142
|
-
3.
|
|
143
|
-
4.
|
|
144
|
-
5.
|
|
145
|
-
6.
|
|
146
|
-
7. Create the local release tag
|
|
144
|
+
1. Verify the release branch, clean state, and `main` worktree ownership.
|
|
145
|
+
2. Read `changelog.md` `## Pending` entries and infer or accept a semver bump.
|
|
146
|
+
3. Run configured tests.
|
|
147
|
+
4. With `--merge`, fast-forward and lease-push `main` before release metadata is created.
|
|
148
|
+
5. Update version files and baked commit info.
|
|
149
|
+
6. Move pending changelog entries into the new version section.
|
|
150
|
+
7. Create the local release commit and tag on `main`.
|
|
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.
|
|
147
153
|
|
|
148
154
|
Targets decide what happens after that:
|
|
149
155
|
|
|
150
156
|
| Target | What it does |
|
|
151
157
|
| --- | --- |
|
|
152
|
-
| `git` |
|
|
153
|
-
| `npm` | Publishes the
|
|
154
|
-
| `docker` |
|
|
158
|
+
| `git` | Atomically pushes the exact `main` release commit and new tag, often triggering remote CI release builds |
|
|
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 |
|
|
155
161
|
|
|
156
162
|
```bash
|
|
157
163
|
# Preview the resolved release plan
|
|
@@ -160,13 +166,13 @@ gitzone release --plan
|
|
|
160
166
|
# Release to configured targets
|
|
161
167
|
gitzone release
|
|
162
168
|
|
|
169
|
+
# From a clean feature branch already rebased onto current main
|
|
170
|
+
gitzone release --merge
|
|
171
|
+
|
|
163
172
|
# Release only to npm
|
|
164
173
|
gitzone release --target npm
|
|
165
174
|
|
|
166
|
-
#
|
|
167
|
-
gitzone release --target git,docker
|
|
168
|
-
|
|
169
|
-
# Skip package/container publishing and keep only git target
|
|
175
|
+
# Remove npm and Docker from the resolved targets; Git remains only if configured
|
|
170
176
|
gitzone release --no-publish
|
|
171
177
|
|
|
172
178
|
# Override inferred semver level
|
|
@@ -179,15 +185,58 @@ Release flags:
|
|
|
179
185
|
| --- | --- |
|
|
180
186
|
| `-y`, `--yes` | Run without interactive confirmation |
|
|
181
187
|
| `-t`, `--test` | Enable preflight tests |
|
|
182
|
-
| `-b`, `--build` | Enable
|
|
183
|
-
| `-p`, `--push` |
|
|
184
|
-
| `--target <csv>` |
|
|
185
|
-
| `--npm` |
|
|
186
|
-
| `--docker` |
|
|
187
|
-
| `--no-publish` |
|
|
188
|
-
| `--no-build` | Disable
|
|
188
|
+
| `-b`, `--build` | Enable the release build after local release metadata is created |
|
|
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 |
|
|
194
|
+
| `--no-build` | Disable the post-metadata release build for this run |
|
|
195
|
+
| `--merge` | Fast-forward and lease-push a cleanly rebased feature branch into `main`, then release from `main` |
|
|
189
196
|
| `--major`, `--minor`, `--patch` | Override inferred semver level |
|
|
190
|
-
| `--plan` | Show resolved workflow
|
|
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.
|
|
191
240
|
|
|
192
241
|
## Standard Changelog
|
|
193
242
|
|
|
@@ -209,7 +258,7 @@ The standard buckets are `Breaking Changes`, `Features`, `Fixes`, `Documentation
|
|
|
209
258
|
|
|
210
259
|
## Configuration
|
|
211
260
|
|
|
212
|
-
CLI workflow config lives under `@git.zone/cli` in `.smartconfig.json`. Docker build and registry
|
|
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.
|
|
213
262
|
|
|
214
263
|
```json
|
|
215
264
|
{
|
|
@@ -223,7 +272,6 @@ CLI workflow config lives under `@git.zone/cli` in `.smartconfig.json`. Docker b
|
|
|
223
272
|
"release": {
|
|
224
273
|
"confirmation": "prompt",
|
|
225
274
|
"preflight": {
|
|
226
|
-
"requireCleanTree": true,
|
|
227
275
|
"test": false,
|
|
228
276
|
"build": true
|
|
229
277
|
},
|
|
@@ -241,7 +289,7 @@ CLI workflow config lives under `@git.zone/cli` in `.smartconfig.json`. Docker b
|
|
|
241
289
|
"alreadyPublished": "success"
|
|
242
290
|
},
|
|
243
291
|
"docker": {
|
|
244
|
-
"enabled":
|
|
292
|
+
"enabled": false,
|
|
245
293
|
"engine": "tsdocker",
|
|
246
294
|
"registry": "registry.gitlab.com",
|
|
247
295
|
"buildRegistries": ["registry.gitlab.com"],
|
|
@@ -276,23 +324,18 @@ values should be hosts without `http://` or `https://`:
|
|
|
276
324
|
@git.zone/tsdocker.registries
|
|
277
325
|
```
|
|
278
326
|
|
|
279
|
-
|
|
280
|
-
`pnpm exec
|
|
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:
|
|
281
330
|
|
|
282
331
|
```bash
|
|
283
332
|
pnpm add --save-dev @git.zone/tsdocker
|
|
284
333
|
```
|
|
285
334
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
only the configured registries used by private base images; an empty list keeps
|
|
291
|
-
the compatibility default of authenticating every configured registry. Bounded
|
|
292
|
-
build authentication requires project-local `@git.zone/tsdocker` 3.3.0 or newer.
|
|
293
|
-
Set `release.targets.docker.test` to `true` to require every selected image test
|
|
294
|
-
to pass before destination publication. This requires project-local
|
|
295
|
-
`@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.
|
|
296
339
|
|
|
297
340
|
Useful config commands:
|
|
298
341
|
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@git.zone/cli',
|
|
6
|
-
version: '
|
|
6
|
+
version: '6.0.0',
|
|
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
|
}
|
package/ts/helpers.climode.ts
CHANGED
|
@@ -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
|
|
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;
|
package/ts/helpers.workflow.ts
CHANGED
|
@@ -70,7 +70,6 @@ export interface IReleaseWorkflowConfig {
|
|
|
70
70
|
source?: "pendingChangelog" | "manual";
|
|
71
71
|
};
|
|
72
72
|
preflight?: {
|
|
73
|
-
requireCleanTree?: boolean;
|
|
74
73
|
test?: boolean;
|
|
75
74
|
build?: boolean;
|
|
76
75
|
testCommand?: string;
|
|
@@ -101,7 +100,6 @@ export interface IResolvedReleaseWorkflow {
|
|
|
101
100
|
confirmation: TConfirmationMode;
|
|
102
101
|
plan: string[];
|
|
103
102
|
targets: TReleaseTarget[];
|
|
104
|
-
requireCleanTree: boolean;
|
|
105
103
|
runTests: boolean;
|
|
106
104
|
runBuild: boolean;
|
|
107
105
|
testCommand: string;
|
|
@@ -129,6 +127,13 @@ export interface IResolvedReleaseWorkflow {
|
|
|
129
127
|
dockerNoBuild: boolean;
|
|
130
128
|
}
|
|
131
129
|
|
|
130
|
+
export interface IResolvedReleaseResumeConfiguration {
|
|
131
|
+
gitRemote: string;
|
|
132
|
+
npmRegistries: string[];
|
|
133
|
+
npmAccessLevel: "public" | "private";
|
|
134
|
+
npmAlreadyPublished: "success" | "error";
|
|
135
|
+
}
|
|
136
|
+
|
|
132
137
|
interface ICliWorkflowConfig {
|
|
133
138
|
commit?: ICommitWorkflowConfig;
|
|
134
139
|
release?: IReleaseWorkflowConfig;
|
|
@@ -202,6 +207,22 @@ export const resolveDockerTestSetting = (valueArg: unknown): boolean => {
|
|
|
202
207
|
return valueArg;
|
|
203
208
|
};
|
|
204
209
|
|
|
210
|
+
const resolveGitPushSetting = (
|
|
211
|
+
valueArg: unknown,
|
|
212
|
+
settingArg: "pushBranch" | "pushTags",
|
|
213
|
+
gitEnabledArg: boolean,
|
|
214
|
+
): boolean => {
|
|
215
|
+
if (!gitEnabledArg) {
|
|
216
|
+
return valueArg === undefined ? true : valueArg === true;
|
|
217
|
+
}
|
|
218
|
+
if (valueArg !== undefined && valueArg !== true) {
|
|
219
|
+
throw new Error(
|
|
220
|
+
`release.targets.git.${settingArg} must be true when the Git target is enabled`,
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
return true;
|
|
224
|
+
};
|
|
225
|
+
|
|
205
226
|
const isStringRecord = (valueArg: unknown): valueArg is Record<string, unknown> => {
|
|
206
227
|
return typeof valueArg === "object" && valueArg !== null && !Array.isArray(valueArg);
|
|
207
228
|
};
|
|
@@ -325,12 +346,31 @@ const normalizeCommitSteps = (rawSteps: TCommitStep[]): TCommitStep[] => {
|
|
|
325
346
|
|
|
326
347
|
const getTargetOverride = (argvArg: any): TReleaseTarget[] | undefined => {
|
|
327
348
|
const validTargets: TReleaseTarget[] = ["git", "npm", "docker"];
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
|
|
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
|
|
331
360
|
.split(",")
|
|
332
|
-
.map((target) => target.trim())
|
|
333
|
-
|
|
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[];
|
|
334
374
|
}
|
|
335
375
|
|
|
336
376
|
const targets: TReleaseTarget[] = [];
|
|
@@ -340,17 +380,23 @@ const getTargetOverride = (argvArg: any): TReleaseTarget[] | undefined => {
|
|
|
340
380
|
return targets.length > 0 ? targets : undefined;
|
|
341
381
|
};
|
|
342
382
|
|
|
383
|
+
const releaseTargetOrder: TReleaseTarget[] = ["git", "npm", "docker"];
|
|
384
|
+
|
|
343
385
|
const buildReleasePlan = (options: {
|
|
344
|
-
|
|
386
|
+
mergeRequested: boolean;
|
|
345
387
|
runTests: boolean;
|
|
346
388
|
runBuild: boolean;
|
|
347
389
|
targets: TReleaseTarget[];
|
|
348
390
|
}): string[] => {
|
|
349
|
-
const plan: string[] = [];
|
|
350
|
-
if (options.requireCleanTree) plan.push("preflight.cleanTree");
|
|
391
|
+
const plan: string[] = ["preflight.branch", "preflight.cleanTree"];
|
|
351
392
|
if (options.runTests) plan.push("preflight.test");
|
|
393
|
+
if (options.mergeRequested) {
|
|
394
|
+
plan.push("integration.main", "integration.pushMain");
|
|
395
|
+
}
|
|
352
396
|
plan.push("core.version", "core.changelog", "core.commit", "core.tag");
|
|
353
397
|
if (options.runBuild) plan.push("core.build");
|
|
398
|
+
if (options.targets.includes("npm")) plan.push("core.packNpmArtifact");
|
|
399
|
+
plan.push("core.writeReleaseJournal");
|
|
354
400
|
for (const target of options.targets) {
|
|
355
401
|
plan.push(`target.${target}`);
|
|
356
402
|
}
|
|
@@ -417,10 +463,23 @@ export const resolveReleaseWorkflow = async (argvArg: any): Promise<IResolvedRel
|
|
|
417
463
|
const gitConfig = targetConfig.git || {};
|
|
418
464
|
const npmConfig = targetConfig.npm || {};
|
|
419
465
|
const dockerConfig = targetConfig.docker || {};
|
|
420
|
-
const
|
|
421
|
-
const
|
|
466
|
+
const rawNpmRegistries = npmConfig.registries;
|
|
467
|
+
const npmRegistryConfigurationPresent = Array.isArray(rawNpmRegistries)
|
|
468
|
+
? rawNpmRegistries.length > 0
|
|
469
|
+
: rawNpmRegistries !== undefined;
|
|
470
|
+
const npmEnabled = npmConfig.enabled ?? npmRegistryConfigurationPresent;
|
|
422
471
|
const gitEnabled = gitConfig.enabled ?? true;
|
|
423
472
|
const dockerEnabled = dockerConfig.enabled ?? false;
|
|
473
|
+
const pushBranch = resolveGitPushSetting(
|
|
474
|
+
gitConfig.pushBranch,
|
|
475
|
+
"pushBranch",
|
|
476
|
+
gitEnabled,
|
|
477
|
+
);
|
|
478
|
+
const pushTags = resolveGitPushSetting(
|
|
479
|
+
gitConfig.pushTags,
|
|
480
|
+
"pushTags",
|
|
481
|
+
gitEnabled,
|
|
482
|
+
);
|
|
424
483
|
|
|
425
484
|
let confirmation = normalizeConfirmation(releaseConfig.confirmation, "prompt");
|
|
426
485
|
if (argvArg.plan) {
|
|
@@ -429,14 +488,12 @@ export const resolveReleaseWorkflow = async (argvArg: any): Promise<IResolvedRel
|
|
|
429
488
|
confirmation = "auto";
|
|
430
489
|
}
|
|
431
490
|
|
|
432
|
-
let requireCleanTree = releaseConfig.preflight?.requireCleanTree ?? true;
|
|
433
491
|
let runTests = releaseConfig.preflight?.test ?? false;
|
|
434
492
|
let runBuild = releaseConfig.preflight?.build ?? true;
|
|
435
493
|
if (argvArg.t || argvArg.test) runTests = true;
|
|
436
494
|
if (argvArg.b || argvArg.build) runBuild = true;
|
|
437
495
|
if (isDisabled(argvArg, "test")) runTests = false;
|
|
438
496
|
if (isDisabled(argvArg, "build")) runBuild = false;
|
|
439
|
-
if (isDisabled(argvArg, "preflight")) requireCleanTree = false;
|
|
440
497
|
|
|
441
498
|
const configuredTargets: TReleaseTarget[] = [];
|
|
442
499
|
if (gitEnabled) configuredTargets.push("git");
|
|
@@ -449,13 +506,32 @@ export const resolveReleaseWorkflow = async (argvArg: any): Promise<IResolvedRel
|
|
|
449
506
|
if (isDisabled(argvArg, "publish")) {
|
|
450
507
|
targets = targets.filter((target) => target === "git");
|
|
451
508
|
}
|
|
452
|
-
|
|
509
|
+
const selectedTargets = unique(targets);
|
|
510
|
+
targets = releaseTargetOrder.filter((target) =>
|
|
511
|
+
selectedTargets.includes(target),
|
|
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
|
+
}
|
|
529
|
+
const mergeRequested = argvArg.merge === true;
|
|
453
530
|
|
|
454
531
|
return {
|
|
455
532
|
confirmation,
|
|
456
|
-
plan: buildReleasePlan({
|
|
533
|
+
plan: buildReleasePlan({ mergeRequested, runTests, runBuild, targets }),
|
|
457
534
|
targets,
|
|
458
|
-
requireCleanTree,
|
|
459
535
|
runTests,
|
|
460
536
|
runBuild,
|
|
461
537
|
testCommand: releaseConfig.preflight?.testCommand || "pnpm test",
|
|
@@ -465,8 +541,8 @@ export const resolveReleaseWorkflow = async (argvArg: any): Promise<IResolvedRel
|
|
|
465
541
|
changelogVersionHeading: "## {{date}} - {{version}}",
|
|
466
542
|
gitEnabled,
|
|
467
543
|
gitRemote: gitConfig.remote || "origin",
|
|
468
|
-
pushBranch
|
|
469
|
-
pushTags
|
|
544
|
+
pushBranch,
|
|
545
|
+
pushTags,
|
|
470
546
|
npmEnabled,
|
|
471
547
|
npmRegistries,
|
|
472
548
|
npmAccessLevel: npmConfig.accessLevel || "public",
|
|
@@ -492,3 +568,63 @@ export const resolveReleaseWorkflow = async (argvArg: any): Promise<IResolvedRel
|
|
|
492
568
|
dockerNoBuild: dockerConfig.noBuild ?? false,
|
|
493
569
|
};
|
|
494
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
|
+
};
|
|
@@ -37,9 +37,9 @@ export async function detectCurrentBranch(): Promise<string> {
|
|
|
37
37
|
* Detects the project type based on presence of package.json and/or deno.json
|
|
38
38
|
* @returns The project type
|
|
39
39
|
*/
|
|
40
|
-
export async function detectProjectType(): Promise<ProjectType> {
|
|
41
|
-
const packageJsonPath = plugins.path.join(
|
|
42
|
-
const denoJsonPath = plugins.path.join(
|
|
40
|
+
export async function detectProjectType(cwdArg = paths.cwd): Promise<ProjectType> {
|
|
41
|
+
const packageJsonPath = plugins.path.join(cwdArg, 'package.json');
|
|
42
|
+
const denoJsonPath = plugins.path.join(cwdArg, 'deno.json');
|
|
43
43
|
|
|
44
44
|
const hasPackageJson = await plugins.smartfs.file(packageJsonPath).exists();
|
|
45
45
|
const hasDenoJson = await plugins.smartfs.file(denoJsonPath).exists();
|
|
@@ -96,9 +96,12 @@ export function calculateNewVersion(currentVersion: string, versionType: Version
|
|
|
96
96
|
* @param projectType The project type to determine which file to read
|
|
97
97
|
* @returns The current version string
|
|
98
98
|
*/
|
|
99
|
-
export async function readCurrentVersion(
|
|
99
|
+
export async function readCurrentVersion(
|
|
100
|
+
projectType: ProjectType,
|
|
101
|
+
cwdArg = paths.cwd,
|
|
102
|
+
): Promise<string> {
|
|
100
103
|
if (projectType === 'npm' || projectType === 'both') {
|
|
101
|
-
const packageJsonPath = plugins.path.join(
|
|
104
|
+
const packageJsonPath = plugins.path.join(cwdArg, 'package.json');
|
|
102
105
|
const content = (await plugins.smartfs
|
|
103
106
|
.file(packageJsonPath)
|
|
104
107
|
.encoding('utf8')
|
|
@@ -110,7 +113,7 @@ export async function readCurrentVersion(projectType: ProjectType): Promise<stri
|
|
|
110
113
|
}
|
|
111
114
|
return packageJson.version;
|
|
112
115
|
} else {
|
|
113
|
-
const denoJsonPath = plugins.path.join(
|
|
116
|
+
const denoJsonPath = plugins.path.join(cwdArg, 'deno.json');
|
|
114
117
|
const content = (await plugins.smartfs
|
|
115
118
|
.file(denoJsonPath)
|
|
116
119
|
.encoding('utf8')
|
|
@@ -148,10 +151,11 @@ export async function updateVersionFile(filePath: string, newVersion: string): P
|
|
|
148
151
|
export async function updateProjectVersionFiles(
|
|
149
152
|
projectType: ProjectType,
|
|
150
153
|
newVersion: string,
|
|
154
|
+
cwdArg = paths.cwd,
|
|
151
155
|
): Promise<string[]> {
|
|
152
156
|
const filesToUpdate: string[] = [];
|
|
153
|
-
const packageJsonPath = plugins.path.join(
|
|
154
|
-
const denoJsonPath = plugins.path.join(
|
|
157
|
+
const packageJsonPath = plugins.path.join(cwdArg, 'package.json');
|
|
158
|
+
const denoJsonPath = plugins.path.join(cwdArg, 'deno.json');
|
|
155
159
|
|
|
156
160
|
if (projectType === 'npm' || projectType === 'both') {
|
|
157
161
|
await updateVersionFile(packageJsonPath, newVersion);
|