@git.zone/cli 4.0.0 → 5.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/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/helpers.workflow.d.ts +0 -2
- package/dist_ts/helpers.workflow.js +23 -12
- 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/helpers.releasebranch.d.ts +46 -0
- package/dist_ts/mod_release/helpers.releasebranch.js +579 -0
- package/dist_ts/mod_release/index.d.ts +10 -0
- package/dist_ts/mod_release/index.js +300 -92
- package/package.json +1 -1
- package/readme.md +21 -13
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/helpers.workflow.ts +41 -12
- package/ts/mod_commit/mod.helpers.ts +12 -8
- package/ts/mod_config/index.ts +21 -22
- package/ts/mod_release/helpers.releasebranch.ts +1282 -0
- package/ts/mod_release/index.ts +608 -129
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@git.zone/cli",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "5.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.md
CHANGED
|
@@ -133,23 +133,28 @@ 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 before npm or Docker 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.
|
|
147
152
|
|
|
148
153
|
Targets decide what happens after that:
|
|
149
154
|
|
|
150
155
|
| Target | What it does |
|
|
151
156
|
| --- | --- |
|
|
152
|
-
| `git` |
|
|
157
|
+
| `git` | Atomically pushes the exact `main` release commit and new tag, often triggering remote CI release builds |
|
|
153
158
|
| `npm` | Publishes the package to configured npm registries |
|
|
154
159
|
| `docker` | Delegates container builds and pushes to `tsdocker` |
|
|
155
160
|
|
|
@@ -160,6 +165,9 @@ gitzone release --plan
|
|
|
160
165
|
# Release to configured targets
|
|
161
166
|
gitzone release
|
|
162
167
|
|
|
168
|
+
# From a clean feature branch already rebased onto current main
|
|
169
|
+
gitzone release --merge
|
|
170
|
+
|
|
163
171
|
# Release only to npm
|
|
164
172
|
gitzone release --target npm
|
|
165
173
|
|
|
@@ -179,15 +187,16 @@ Release flags:
|
|
|
179
187
|
| --- | --- |
|
|
180
188
|
| `-y`, `--yes` | Run without interactive confirmation |
|
|
181
189
|
| `-t`, `--test` | Enable preflight tests |
|
|
182
|
-
| `-b`, `--build` | Enable
|
|
190
|
+
| `-b`, `--build` | Enable the release build after local release metadata is created |
|
|
183
191
|
| `-p`, `--push` | Enable the `git` target |
|
|
184
192
|
| `--target <csv>` | Use only selected targets, e.g. `git,npm` |
|
|
185
193
|
| `--npm` | Enable the `npm` target |
|
|
186
194
|
| `--docker` | Enable the `docker` target |
|
|
187
195
|
| `--no-publish` | Keep release core and `git` target only |
|
|
188
|
-
| `--no-build` | Disable
|
|
196
|
+
| `--no-build` | Disable the post-metadata release build for this run |
|
|
197
|
+
| `--merge` | Fast-forward and lease-push a cleanly rebased feature branch into `main`, then release from `main` |
|
|
189
198
|
| `--major`, `--minor`, `--patch` | Override inferred semver level |
|
|
190
|
-
| `--plan` | Show resolved workflow
|
|
199
|
+
| `--plan` | Show the resolved workflow without fetching or mutating refs, files, the index, or worktrees |
|
|
191
200
|
|
|
192
201
|
## Standard Changelog
|
|
193
202
|
|
|
@@ -223,7 +232,6 @@ CLI workflow config lives under `@git.zone/cli` in `.smartconfig.json`. Docker b
|
|
|
223
232
|
"release": {
|
|
224
233
|
"confirmation": "prompt",
|
|
225
234
|
"preflight": {
|
|
226
|
-
"requireCleanTree": true,
|
|
227
235
|
"test": false,
|
|
228
236
|
"build": true
|
|
229
237
|
},
|
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: '5.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.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;
|
|
@@ -202,6 +200,22 @@ export const resolveDockerTestSetting = (valueArg: unknown): boolean => {
|
|
|
202
200
|
return valueArg;
|
|
203
201
|
};
|
|
204
202
|
|
|
203
|
+
const resolveGitPushSetting = (
|
|
204
|
+
valueArg: unknown,
|
|
205
|
+
settingArg: "pushBranch" | "pushTags",
|
|
206
|
+
gitEnabledArg: boolean,
|
|
207
|
+
): boolean => {
|
|
208
|
+
if (!gitEnabledArg) {
|
|
209
|
+
return valueArg === undefined ? true : valueArg === true;
|
|
210
|
+
}
|
|
211
|
+
if (valueArg !== undefined && valueArg !== true) {
|
|
212
|
+
throw new Error(
|
|
213
|
+
`release.targets.git.${settingArg} must be true when the Git target is enabled`,
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
return true;
|
|
217
|
+
};
|
|
218
|
+
|
|
205
219
|
const isStringRecord = (valueArg: unknown): valueArg is Record<string, unknown> => {
|
|
206
220
|
return typeof valueArg === "object" && valueArg !== null && !Array.isArray(valueArg);
|
|
207
221
|
};
|
|
@@ -340,15 +354,19 @@ const getTargetOverride = (argvArg: any): TReleaseTarget[] | undefined => {
|
|
|
340
354
|
return targets.length > 0 ? targets : undefined;
|
|
341
355
|
};
|
|
342
356
|
|
|
357
|
+
const releaseTargetOrder: TReleaseTarget[] = ["git", "npm", "docker"];
|
|
358
|
+
|
|
343
359
|
const buildReleasePlan = (options: {
|
|
344
|
-
|
|
360
|
+
mergeRequested: boolean;
|
|
345
361
|
runTests: boolean;
|
|
346
362
|
runBuild: boolean;
|
|
347
363
|
targets: TReleaseTarget[];
|
|
348
364
|
}): string[] => {
|
|
349
|
-
const plan: string[] = [];
|
|
350
|
-
if (options.requireCleanTree) plan.push("preflight.cleanTree");
|
|
365
|
+
const plan: string[] = ["preflight.branch", "preflight.cleanTree"];
|
|
351
366
|
if (options.runTests) plan.push("preflight.test");
|
|
367
|
+
if (options.mergeRequested) {
|
|
368
|
+
plan.push("integration.main", "integration.pushMain");
|
|
369
|
+
}
|
|
352
370
|
plan.push("core.version", "core.changelog", "core.commit", "core.tag");
|
|
353
371
|
if (options.runBuild) plan.push("core.build");
|
|
354
372
|
for (const target of options.targets) {
|
|
@@ -421,6 +439,16 @@ export const resolveReleaseWorkflow = async (argvArg: any): Promise<IResolvedRel
|
|
|
421
439
|
const npmEnabled = npmConfig.enabled ?? npmRegistries.length > 0;
|
|
422
440
|
const gitEnabled = gitConfig.enabled ?? true;
|
|
423
441
|
const dockerEnabled = dockerConfig.enabled ?? false;
|
|
442
|
+
const pushBranch = resolveGitPushSetting(
|
|
443
|
+
gitConfig.pushBranch,
|
|
444
|
+
"pushBranch",
|
|
445
|
+
gitEnabled,
|
|
446
|
+
);
|
|
447
|
+
const pushTags = resolveGitPushSetting(
|
|
448
|
+
gitConfig.pushTags,
|
|
449
|
+
"pushTags",
|
|
450
|
+
gitEnabled,
|
|
451
|
+
);
|
|
424
452
|
|
|
425
453
|
let confirmation = normalizeConfirmation(releaseConfig.confirmation, "prompt");
|
|
426
454
|
if (argvArg.plan) {
|
|
@@ -429,14 +457,12 @@ export const resolveReleaseWorkflow = async (argvArg: any): Promise<IResolvedRel
|
|
|
429
457
|
confirmation = "auto";
|
|
430
458
|
}
|
|
431
459
|
|
|
432
|
-
let requireCleanTree = releaseConfig.preflight?.requireCleanTree ?? true;
|
|
433
460
|
let runTests = releaseConfig.preflight?.test ?? false;
|
|
434
461
|
let runBuild = releaseConfig.preflight?.build ?? true;
|
|
435
462
|
if (argvArg.t || argvArg.test) runTests = true;
|
|
436
463
|
if (argvArg.b || argvArg.build) runBuild = true;
|
|
437
464
|
if (isDisabled(argvArg, "test")) runTests = false;
|
|
438
465
|
if (isDisabled(argvArg, "build")) runBuild = false;
|
|
439
|
-
if (isDisabled(argvArg, "preflight")) requireCleanTree = false;
|
|
440
466
|
|
|
441
467
|
const configuredTargets: TReleaseTarget[] = [];
|
|
442
468
|
if (gitEnabled) configuredTargets.push("git");
|
|
@@ -449,13 +475,16 @@ export const resolveReleaseWorkflow = async (argvArg: any): Promise<IResolvedRel
|
|
|
449
475
|
if (isDisabled(argvArg, "publish")) {
|
|
450
476
|
targets = targets.filter((target) => target === "git");
|
|
451
477
|
}
|
|
452
|
-
|
|
478
|
+
const selectedTargets = unique(targets);
|
|
479
|
+
targets = releaseTargetOrder.filter((target) =>
|
|
480
|
+
selectedTargets.includes(target),
|
|
481
|
+
);
|
|
482
|
+
const mergeRequested = argvArg.merge === true;
|
|
453
483
|
|
|
454
484
|
return {
|
|
455
485
|
confirmation,
|
|
456
|
-
plan: buildReleasePlan({
|
|
486
|
+
plan: buildReleasePlan({ mergeRequested, runTests, runBuild, targets }),
|
|
457
487
|
targets,
|
|
458
|
-
requireCleanTree,
|
|
459
488
|
runTests,
|
|
460
489
|
runBuild,
|
|
461
490
|
testCommand: releaseConfig.preflight?.testCommand || "pnpm test",
|
|
@@ -465,8 +494,8 @@ export const resolveReleaseWorkflow = async (argvArg: any): Promise<IResolvedRel
|
|
|
465
494
|
changelogVersionHeading: "## {{date}} - {{version}}",
|
|
466
495
|
gitEnabled,
|
|
467
496
|
gitRemote: gitConfig.remote || "origin",
|
|
468
|
-
pushBranch
|
|
469
|
-
pushTags
|
|
497
|
+
pushBranch,
|
|
498
|
+
pushTags,
|
|
470
499
|
npmEnabled,
|
|
471
500
|
npmRegistries,
|
|
472
501
|
npmAccessLevel: npmConfig.accessLevel || "public",
|
|
@@ -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);
|
package/ts/mod_config/index.ts
CHANGED
|
@@ -293,7 +293,7 @@ async function handleShow(mode: ICliMode): Promise<void> {
|
|
|
293
293
|
const targets = release.targets || {};
|
|
294
294
|
printConfigSection("Release Workflow", [
|
|
295
295
|
["confirmation", formatValue(release.confirmation)],
|
|
296
|
-
["
|
|
296
|
+
["clean tree", "required"],
|
|
297
297
|
["run tests", formatValue(release.preflight?.test)],
|
|
298
298
|
["run build", formatValue(release.preflight?.build)],
|
|
299
299
|
["test command", formatValue(release.preflight?.testCommand)],
|
|
@@ -773,12 +773,6 @@ async function handleRelease(mode: ICliMode): Promise<void> {
|
|
|
773
773
|
choices: ["prompt", "auto", "plan"],
|
|
774
774
|
default: currentRelease.confirmation || "prompt",
|
|
775
775
|
});
|
|
776
|
-
const requireCleanTree = await askValue<boolean>(interactInstance, {
|
|
777
|
-
type: "confirm",
|
|
778
|
-
name: "requireCleanTree",
|
|
779
|
-
message: "Require a clean git tree before release?",
|
|
780
|
-
default: currentRelease.preflight?.requireCleanTree ?? true,
|
|
781
|
-
});
|
|
782
776
|
const runTests = await askValue<boolean>(interactInstance, {
|
|
783
777
|
type: "confirm",
|
|
784
778
|
name: "runTests",
|
|
@@ -828,18 +822,8 @@ async function handleRelease(mode: ICliMode): Promise<void> {
|
|
|
828
822
|
message: "Git remote:",
|
|
829
823
|
default: currentTargets.git?.remote || "origin",
|
|
830
824
|
}),
|
|
831
|
-
pushBranch:
|
|
832
|
-
|
|
833
|
-
name: "pushBranch",
|
|
834
|
-
message: "Push release commit branch?",
|
|
835
|
-
default: currentTargets.git?.pushBranch ?? true,
|
|
836
|
-
}),
|
|
837
|
-
pushTags: await askValue<boolean>(interactInstance, {
|
|
838
|
-
type: "confirm",
|
|
839
|
-
name: "pushTags",
|
|
840
|
-
message: "Push release tags?",
|
|
841
|
-
default: currentTargets.git?.pushTags ?? true,
|
|
842
|
-
}),
|
|
825
|
+
pushBranch: true,
|
|
826
|
+
pushTags: true,
|
|
843
827
|
};
|
|
844
828
|
} else {
|
|
845
829
|
releaseTargets.git = { ...(currentTargets.git || {}), enabled: false };
|
|
@@ -947,12 +931,13 @@ async function handleRelease(mode: ICliMode): Promise<void> {
|
|
|
947
931
|
}
|
|
948
932
|
|
|
949
933
|
setCliConfigValueInData(smartconfigData, "schemaVersion", CURRENT_GITZONE_CLI_SCHEMA_VERSION);
|
|
934
|
+
const currentPreflight = { ...(currentRelease.preflight || {}) };
|
|
935
|
+
delete currentPreflight.requireCleanTree;
|
|
950
936
|
setCliConfigValueInData(smartconfigData, "release", {
|
|
951
937
|
...currentRelease,
|
|
952
938
|
confirmation,
|
|
953
939
|
preflight: {
|
|
954
|
-
...
|
|
955
|
-
requireCleanTree,
|
|
940
|
+
...currentPreflight,
|
|
956
941
|
test: runTests,
|
|
957
942
|
build: runBuild,
|
|
958
943
|
testCommand: testCommand.trim(),
|
|
@@ -1704,7 +1689,7 @@ async function validateReleaseConfig(
|
|
|
1704
1689
|
if (preflight.build === true && preflight.buildCommand === "") {
|
|
1705
1690
|
findings.push({
|
|
1706
1691
|
level: "warn",
|
|
1707
|
-
message: "Release build
|
|
1692
|
+
message: "Release build has an empty command",
|
|
1708
1693
|
fix: "Set release.preflight.buildCommand or unset it to use pnpm build.",
|
|
1709
1694
|
});
|
|
1710
1695
|
}
|
|
@@ -1816,6 +1801,20 @@ async function validateGitTarget(
|
|
|
1816
1801
|
});
|
|
1817
1802
|
return;
|
|
1818
1803
|
}
|
|
1804
|
+
if (gitTarget.pushBranch !== undefined && gitTarget.pushBranch !== true) {
|
|
1805
|
+
findings.push({
|
|
1806
|
+
level: "error",
|
|
1807
|
+
message: "Enabled Git release targets must push main",
|
|
1808
|
+
fix: "Set release.targets.git.pushBranch to true or disable the Git target.",
|
|
1809
|
+
});
|
|
1810
|
+
}
|
|
1811
|
+
if (gitTarget.pushTags !== undefined && gitTarget.pushTags !== true) {
|
|
1812
|
+
findings.push({
|
|
1813
|
+
level: "error",
|
|
1814
|
+
message: "Enabled Git release targets must push release tags",
|
|
1815
|
+
fix: "Set release.targets.git.pushTags to true or disable the Git target.",
|
|
1816
|
+
});
|
|
1817
|
+
}
|
|
1819
1818
|
findings.push({
|
|
1820
1819
|
level: "ok",
|
|
1821
1820
|
message: `Git release target is enabled (${gitTarget.remote || "origin"})`,
|