@git.zone/cli 2.14.3 → 2.16.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.
Files changed (55) hide show
  1. package/.smartconfig.json +36 -11
  2. package/assets/templates/service/npmextra.json +8 -2
  3. package/assets/templates/smartconfig/_smartconfig.json +5 -1
  4. package/assets/templates/website/npmextra.json +8 -2
  5. package/dist_ts/00_commitinfo_data.js +1 -1
  6. package/dist_ts/gitzone.cli.js +15 -1
  7. package/dist_ts/helpers.changelog.d.ts +16 -0
  8. package/dist_ts/helpers.changelog.js +114 -0
  9. package/dist_ts/helpers.smartconfigmigrations.d.ts +7 -0
  10. package/dist_ts/helpers.smartconfigmigrations.js +161 -0
  11. package/dist_ts/helpers.workflow.d.ts +97 -0
  12. package/dist_ts/helpers.workflow.js +258 -0
  13. package/dist_ts/mod_commit/index.js +190 -303
  14. package/dist_ts/mod_commit/mod.helpers.d.ts +23 -0
  15. package/dist_ts/mod_commit/mod.helpers.js +22 -15
  16. package/dist_ts/mod_commit/mod.ui.d.ts +1 -1
  17. package/dist_ts/mod_commit/mod.ui.js +7 -2
  18. package/dist_ts/mod_config/classes.commitconfig.d.ts +6 -0
  19. package/dist_ts/mod_config/classes.commitconfig.js +28 -4
  20. package/dist_ts/mod_config/classes.releaseconfig.js +13 -7
  21. package/dist_ts/mod_config/index.js +77 -29
  22. package/dist_ts/mod_format/formatters/smartconfig.formatter.js +3 -57
  23. package/dist_ts/mod_release/index.d.ts +3 -0
  24. package/dist_ts/mod_release/index.js +299 -0
  25. package/dist_ts/mod_release/mod.plugins.d.ts +3 -0
  26. package/dist_ts/mod_release/mod.plugins.js +4 -0
  27. package/dist_ts/mod_standard/index.js +29 -3
  28. package/dist_ts/mod_tools/classes.packagemanager.d.ts +27 -0
  29. package/dist_ts/mod_tools/classes.packagemanager.js +140 -0
  30. package/dist_ts/mod_tools/index.d.ts +4 -0
  31. package/dist_ts/mod_tools/index.js +290 -0
  32. package/dist_ts/mod_tools/mod.plugins.d.ts +1 -0
  33. package/dist_ts/mod_tools/mod.plugins.js +2 -0
  34. package/license +2 -2
  35. package/package.json +20 -30
  36. package/readme.hints.md +15 -17
  37. package/readme.md +251 -415
  38. package/ts/00_commitinfo_data.ts +1 -1
  39. package/ts/gitzone.cli.ts +16 -0
  40. package/ts/helpers.changelog.ts +165 -0
  41. package/ts/helpers.smartconfigmigrations.ts +192 -0
  42. package/ts/helpers.workflow.ts +387 -0
  43. package/ts/mod_commit/index.ts +233 -435
  44. package/ts/mod_commit/mod.helpers.ts +28 -16
  45. package/ts/mod_commit/mod.ui.ts +7 -2
  46. package/ts/mod_config/classes.commitconfig.ts +33 -3
  47. package/ts/mod_config/classes.releaseconfig.ts +14 -7
  48. package/ts/mod_config/index.ts +89 -28
  49. package/ts/mod_format/formatters/smartconfig.formatter.ts +2 -62
  50. package/ts/mod_release/index.ts +393 -0
  51. package/ts/mod_release/mod.plugins.ts +5 -0
  52. package/ts/mod_standard/index.ts +28 -2
  53. package/ts/mod_tools/classes.packagemanager.ts +176 -0
  54. package/ts/mod_tools/index.ts +359 -0
  55. package/ts/mod_tools/mod.plugins.ts +1 -0
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@git.zone/cli',
6
- version: '2.14.3',
6
+ version: '2.16.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/gitzone.cli.ts CHANGED
@@ -51,6 +51,14 @@ export let run = async () => {
51
51
  await modCommit.run(argvArg);
52
52
  });
53
53
 
54
+ /**
55
+ * create a release from pending changelog entries
56
+ */
57
+ gitzoneSmartcli.addCommand("release").subscribe(async (argvArg) => {
58
+ const modRelease = await import("./mod_release/index.js");
59
+ await modRelease.run(argvArg);
60
+ });
61
+
54
62
  /**
55
63
  * deprecate a package on npm
56
64
  */
@@ -129,6 +137,14 @@ export let run = async () => {
129
137
  modHelpers.run(argvArg);
130
138
  });
131
139
 
140
+ /**
141
+ * manage the global @git.zone toolchain
142
+ */
143
+ gitzoneSmartcli.addCommand("tools").subscribe(async (argvArg) => {
144
+ const modTools = await import("./mod_tools/index.js");
145
+ await modTools.run(argvArg);
146
+ });
147
+
132
148
  /**
133
149
  * manage release configuration
134
150
  */
@@ -0,0 +1,165 @@
1
+ import * as plugins from "./plugins.js";
2
+
3
+ export type TChangelogBucket =
4
+ | "Breaking Changes"
5
+ | "Features"
6
+ | "Fixes"
7
+ | "Documentation"
8
+ | "Maintenance";
9
+
10
+ export interface IChangelogEntry {
11
+ type: string;
12
+ scope: string;
13
+ message: string;
14
+ details?: string[];
15
+ }
16
+
17
+ export interface IPendingChangelog {
18
+ block: string;
19
+ isEmpty: boolean;
20
+ }
21
+
22
+ const bucketForCommitType = (commitType: string): TChangelogBucket => {
23
+ switch (commitType) {
24
+ case "BREAKING CHANGE":
25
+ return "Breaking Changes";
26
+ case "feat":
27
+ return "Features";
28
+ case "fix":
29
+ return "Fixes";
30
+ case "docs":
31
+ return "Documentation";
32
+ default:
33
+ return "Maintenance";
34
+ }
35
+ };
36
+
37
+ const readChangelog = async (filePath: string): Promise<string> => {
38
+ if (!(await plugins.smartfs.file(filePath).exists())) {
39
+ return "# Changelog\n\n";
40
+ }
41
+ return (await plugins.smartfs.file(filePath).encoding("utf8").read()) as string;
42
+ };
43
+
44
+ const writeChangelog = async (filePath: string, content: string): Promise<void> => {
45
+ await plugins.smartfs.file(filePath).encoding("utf8").write(content.endsWith("\n") ? content : `${content}\n`);
46
+ };
47
+
48
+ const findPendingSection = (
49
+ content: string,
50
+ sectionName: string,
51
+ ): { start: number; bodyStart: number; end: number } | null => {
52
+ const headingRegex = new RegExp(`^##\\s+${sectionName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*$`, "m");
53
+ const match = headingRegex.exec(content);
54
+ if (!match || match.index === undefined) {
55
+ return null;
56
+ }
57
+
58
+ const bodyStart = match.index + match[0].length;
59
+ const rest = content.slice(bodyStart);
60
+ const nextHeadingMatch = /^##\s+/m.exec(rest);
61
+ const end = nextHeadingMatch ? bodyStart + nextHeadingMatch.index : content.length;
62
+ return { start: match.index, bodyStart, end };
63
+ };
64
+
65
+ export const ensurePendingSection = async (
66
+ filePath: string,
67
+ sectionName = "Pending",
68
+ ): Promise<string> => {
69
+ let content = await readChangelog(filePath);
70
+ if (findPendingSection(content, sectionName)) {
71
+ return content;
72
+ }
73
+
74
+ const pendingSection = `## ${sectionName}\n\n`;
75
+ const titleMatch = /^#\s+.+$/m.exec(content);
76
+ if (titleMatch && titleMatch.index !== undefined) {
77
+ const insertAt = titleMatch.index + titleMatch[0].length;
78
+ content = `${content.slice(0, insertAt)}\n\n${pendingSection}${content.slice(insertAt).replace(/^\n+/, "")}`;
79
+ } else {
80
+ content = `# Changelog\n\n${pendingSection}${content}`;
81
+ }
82
+
83
+ await writeChangelog(filePath, content);
84
+ return content;
85
+ };
86
+
87
+ export const appendPendingChangelogEntry = async (
88
+ filePath: string,
89
+ sectionName: string,
90
+ entry: IChangelogEntry,
91
+ ): Promise<void> => {
92
+ let content = await ensurePendingSection(filePath, sectionName);
93
+ const pendingSection = findPendingSection(content, sectionName)!;
94
+ let pendingBody = content.slice(pendingSection.bodyStart, pendingSection.end);
95
+ const bucket = bucketForCommitType(entry.type);
96
+ const bucketHeading = `### ${bucket}`;
97
+
98
+ const entryLines = [`- ${entry.message}${entry.scope ? ` (${entry.scope})` : ""}`];
99
+ for (const detail of entry.details || []) {
100
+ entryLines.push(` - ${detail}`);
101
+ }
102
+ const renderedEntry = entryLines.join("\n");
103
+
104
+ const bucketRegex = new RegExp(`^###\\s+${bucket.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*$`, "m");
105
+ const bucketMatch = bucketRegex.exec(pendingBody);
106
+ if (!bucketMatch || bucketMatch.index === undefined) {
107
+ pendingBody = `${pendingBody.trimEnd()}\n\n${bucketHeading}\n\n${renderedEntry}\n`;
108
+ } else {
109
+ const bucketBodyStart = bucketMatch.index + bucketMatch[0].length;
110
+ const afterBucket = pendingBody.slice(bucketBodyStart);
111
+ const nextBucketMatch = /^###\s+/m.exec(afterBucket);
112
+ const insertAt = nextBucketMatch ? bucketBodyStart + nextBucketMatch.index : pendingBody.length;
113
+ const beforeInsert = pendingBody.slice(0, insertAt).trimEnd();
114
+ const afterInsert = pendingBody.slice(insertAt).replace(/^\n+/, "");
115
+ pendingBody = `${beforeInsert}\n${renderedEntry}\n\n${afterInsert}`;
116
+ }
117
+
118
+ content = `${content.slice(0, pendingSection.bodyStart)}\n${pendingBody.trim()}\n\n${content.slice(pendingSection.end).replace(/^\n+/, "")}`;
119
+ await writeChangelog(filePath, content);
120
+ };
121
+
122
+ export const readPendingChangelog = async (
123
+ filePath: string,
124
+ sectionName = "Pending",
125
+ ): Promise<IPendingChangelog> => {
126
+ const content = await ensurePendingSection(filePath, sectionName);
127
+ const pendingSection = findPendingSection(content, sectionName)!;
128
+ const block = content.slice(pendingSection.bodyStart, pendingSection.end).trim();
129
+ return {
130
+ block,
131
+ isEmpty: block.length === 0,
132
+ };
133
+ };
134
+
135
+ export const inferVersionTypeFromPending = (pendingBlock: string): "patch" | "minor" | "major" => {
136
+ if (/^###\s+Breaking Changes\s*$/m.test(pendingBlock)) {
137
+ return "major";
138
+ }
139
+ if (/^###\s+Features\s*$/m.test(pendingBlock)) {
140
+ return "minor";
141
+ }
142
+ return "patch";
143
+ };
144
+
145
+ export const movePendingToVersion = async (
146
+ filePath: string,
147
+ sectionName: string,
148
+ versionHeading: string,
149
+ version: string,
150
+ dateString: string,
151
+ ): Promise<void> => {
152
+ let content = await ensurePendingSection(filePath, sectionName);
153
+ const pendingSection = findPendingSection(content, sectionName)!;
154
+ const pendingBlock = content.slice(pendingSection.bodyStart, pendingSection.end).trim();
155
+ if (!pendingBlock) {
156
+ throw new Error("No pending changelog entries. Nothing to release.");
157
+ }
158
+
159
+ const renderedHeading = versionHeading
160
+ .replaceAll("{{version}}", version)
161
+ .replaceAll("{{date}}", dateString);
162
+ const nextContent = content.slice(pendingSection.end).replace(/^\n+/, "");
163
+ content = `${content.slice(0, pendingSection.bodyStart)}\n\n${renderedHeading}\n\n${pendingBlock}\n\n${nextContent}`;
164
+ await writeChangelog(filePath, content);
165
+ };
@@ -0,0 +1,192 @@
1
+ export const CURRENT_GITZONE_CLI_SCHEMA_VERSION = 2;
2
+
3
+ export interface ISmartconfigMigrationResult {
4
+ migrated: boolean;
5
+ fromVersion: number;
6
+ toVersion: number;
7
+ }
8
+
9
+ const CLI_NAMESPACE = "@git.zone/cli";
10
+
11
+ const isPlainObject = (value: unknown): value is Record<string, any> => {
12
+ return typeof value === "object" && value !== null && !Array.isArray(value);
13
+ };
14
+
15
+ const ensureObject = (parent: Record<string, any>, key: string): Record<string, any> => {
16
+ if (!isPlainObject(parent[key])) {
17
+ parent[key] = {};
18
+ }
19
+ return parent[key];
20
+ };
21
+
22
+ const migrateNamespaceKeys = (smartconfigJson: Record<string, any>): boolean => {
23
+ let migrated = false;
24
+ const migrations = [
25
+ { oldKey: "gitzone", newKey: CLI_NAMESPACE },
26
+ { oldKey: "tsdoc", newKey: "@git.zone/tsdoc" },
27
+ { oldKey: "npmdocker", newKey: "@git.zone/tsdocker" },
28
+ { oldKey: "npmci", newKey: "@ship.zone/szci" },
29
+ { oldKey: "szci", newKey: "@ship.zone/szci" },
30
+ ];
31
+
32
+ for (const { oldKey, newKey } of migrations) {
33
+ if (!isPlainObject(smartconfigJson[oldKey])) {
34
+ continue;
35
+ }
36
+ if (!isPlainObject(smartconfigJson[newKey])) {
37
+ smartconfigJson[newKey] = smartconfigJson[oldKey];
38
+ } else {
39
+ smartconfigJson[newKey] = {
40
+ ...smartconfigJson[oldKey],
41
+ ...smartconfigJson[newKey],
42
+ };
43
+ }
44
+ delete smartconfigJson[oldKey];
45
+ migrated = true;
46
+ }
47
+
48
+ return migrated;
49
+ };
50
+
51
+ const migrateToV2 = (smartconfigJson: Record<string, any>): boolean => {
52
+ const cliConfig = ensureObject(smartconfigJson, CLI_NAMESPACE);
53
+ const releaseConfig = ensureObject(cliConfig, "release");
54
+
55
+ let migrated = false;
56
+ const targets = ensureObject(releaseConfig, "targets");
57
+ const shipzoneConfig = smartconfigJson["@ship.zone/szci"];
58
+
59
+ if (isPlainObject(releaseConfig.git) && !isPlainObject(targets.git)) {
60
+ targets.git = releaseConfig.git;
61
+ delete releaseConfig.git;
62
+ migrated = true;
63
+ }
64
+
65
+ if (isPlainObject(releaseConfig.npm) && !isPlainObject(targets.npm)) {
66
+ targets.npm = releaseConfig.npm;
67
+ delete releaseConfig.npm;
68
+ migrated = true;
69
+ }
70
+
71
+ if (isPlainObject(releaseConfig.docker) && !isPlainObject(targets.docker)) {
72
+ targets.docker = releaseConfig.docker;
73
+ delete releaseConfig.docker;
74
+ migrated = true;
75
+ }
76
+
77
+ if (Array.isArray(releaseConfig.registries)) {
78
+ const npmTarget = ensureObject(targets, "npm");
79
+ if (!Array.isArray(npmTarget.registries)) {
80
+ npmTarget.registries = releaseConfig.registries;
81
+ }
82
+ delete releaseConfig.registries;
83
+ migrated = true;
84
+ }
85
+
86
+ if (releaseConfig.accessLevel) {
87
+ const npmTarget = ensureObject(targets, "npm");
88
+ if (!npmTarget.accessLevel) {
89
+ npmTarget.accessLevel = releaseConfig.accessLevel;
90
+ }
91
+ delete releaseConfig.accessLevel;
92
+ migrated = true;
93
+ }
94
+
95
+ if (isPlainObject(shipzoneConfig)) {
96
+ if (shipzoneConfig.npmAccessLevel) {
97
+ const npmTarget = ensureObject(targets, "npm");
98
+ if (!npmTarget.accessLevel) {
99
+ npmTarget.accessLevel = shipzoneConfig.npmAccessLevel;
100
+ }
101
+ delete shipzoneConfig.npmAccessLevel;
102
+ migrated = true;
103
+ }
104
+
105
+ if (shipzoneConfig.npmRegistryUrl) {
106
+ const npmTarget = ensureObject(targets, "npm");
107
+ const registry = normalizeRegistryUrl(shipzoneConfig.npmRegistryUrl);
108
+ const registries = Array.isArray(npmTarget.registries) ? npmTarget.registries : [];
109
+ if (!registries.includes(registry)) {
110
+ registries.push(registry);
111
+ }
112
+ npmTarget.registries = registries;
113
+ delete shipzoneConfig.npmRegistryUrl;
114
+ migrated = true;
115
+ }
116
+ }
117
+
118
+ if (Array.isArray(releaseConfig.steps)) {
119
+ const steps = releaseConfig.steps as string[];
120
+ const preflight = ensureObject(releaseConfig, "preflight");
121
+ if (steps.includes("test") && preflight.test === undefined) {
122
+ preflight.test = true;
123
+ }
124
+ if (steps.includes("build") && preflight.build === undefined) {
125
+ preflight.build = true;
126
+ }
127
+ if (steps.includes("push")) {
128
+ const gitTarget = ensureObject(targets, "git");
129
+ if (gitTarget.enabled === undefined) {
130
+ gitTarget.enabled = true;
131
+ }
132
+ }
133
+ if (steps.includes("publishNpm")) {
134
+ const npmTarget = ensureObject(targets, "npm");
135
+ if (npmTarget.enabled === undefined) {
136
+ npmTarget.enabled = true;
137
+ }
138
+ }
139
+ if (steps.includes("publishDocker")) {
140
+ const dockerTarget = ensureObject(targets, "docker");
141
+ if (dockerTarget.enabled === undefined) {
142
+ dockerTarget.enabled = true;
143
+ }
144
+ }
145
+ delete releaseConfig.steps;
146
+ migrated = true;
147
+ }
148
+
149
+ if (releaseConfig.changelog) {
150
+ delete releaseConfig.changelog;
151
+ migrated = true;
152
+ }
153
+
154
+ cliConfig.schemaVersion = 2;
155
+ return migrated || true;
156
+ };
157
+
158
+ const normalizeRegistryUrl = (url: string): string => {
159
+ let normalizedUrl = url.trim();
160
+ if (!normalizedUrl.startsWith("http://") && !normalizedUrl.startsWith("https://")) {
161
+ normalizedUrl = `https://${normalizedUrl}`;
162
+ }
163
+ return normalizedUrl.endsWith("/") ? normalizedUrl.slice(0, -1) : normalizedUrl;
164
+ };
165
+
166
+ export const migrateSmartconfigData = (
167
+ smartconfigJson: Record<string, any>,
168
+ targetVersion = CURRENT_GITZONE_CLI_SCHEMA_VERSION,
169
+ ): ISmartconfigMigrationResult => {
170
+ let migrated = false;
171
+ migrated = migrateNamespaceKeys(smartconfigJson) || migrated;
172
+
173
+ const cliConfig = ensureObject(smartconfigJson, CLI_NAMESPACE);
174
+ const fromVersion = typeof cliConfig.schemaVersion === "number" ? cliConfig.schemaVersion : 1;
175
+ let currentVersion = fromVersion;
176
+
177
+ if (currentVersion < 2 && targetVersion >= 2) {
178
+ migrated = migrateToV2(smartconfigJson) || migrated;
179
+ currentVersion = 2;
180
+ }
181
+
182
+ if (targetVersion === CURRENT_GITZONE_CLI_SCHEMA_VERSION && cliConfig.schemaVersion !== targetVersion) {
183
+ cliConfig.schemaVersion = targetVersion;
184
+ migrated = true;
185
+ }
186
+
187
+ return {
188
+ migrated,
189
+ fromVersion,
190
+ toVersion: Math.min(targetVersion, CURRENT_GITZONE_CLI_SCHEMA_VERSION),
191
+ };
192
+ };