@git.zone/cli 2.13.15 → 2.14.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 (39) hide show
  1. package/dist_ts/00_commitinfo_data.js +2 -2
  2. package/dist_ts/gitzone.cli.js +46 -35
  3. package/dist_ts/helpers.climode.d.ts +22 -0
  4. package/dist_ts/helpers.climode.js +158 -0
  5. package/dist_ts/helpers.smartconfig.d.ts +11 -0
  6. package/dist_ts/helpers.smartconfig.js +139 -0
  7. package/dist_ts/mod_commit/index.d.ts +2 -0
  8. package/dist_ts/mod_commit/index.js +204 -92
  9. package/dist_ts/mod_config/index.d.ts +7 -2
  10. package/dist_ts/mod_config/index.js +390 -176
  11. package/dist_ts/mod_format/classes.formatcontext.d.ts +11 -2
  12. package/dist_ts/mod_format/classes.formatcontext.js +14 -4
  13. package/dist_ts/mod_format/formatters/packagejson.formatter.js +1 -67
  14. package/dist_ts/mod_format/formatters/smartconfig.formatter.d.ts +2 -2
  15. package/dist_ts/mod_format/formatters/smartconfig.formatter.js +46 -39
  16. package/dist_ts/mod_format/index.d.ts +4 -1
  17. package/dist_ts/mod_format/index.js +221 -75
  18. package/dist_ts/mod_format/mod.plugins.d.ts +1 -2
  19. package/dist_ts/mod_format/mod.plugins.js +2 -3
  20. package/dist_ts/mod_services/index.d.ts +2 -0
  21. package/dist_ts/mod_services/index.js +366 -168
  22. package/dist_ts/mod_standard/index.d.ts +3 -1
  23. package/dist_ts/mod_standard/index.js +159 -59
  24. package/package.json +1 -1
  25. package/readme.hints.md +25 -32
  26. package/readme.md +87 -65
  27. package/ts/00_commitinfo_data.ts +1 -1
  28. package/ts/gitzone.cli.ts +50 -38
  29. package/ts/helpers.climode.ts +212 -0
  30. package/ts/helpers.smartconfig.ts +192 -0
  31. package/ts/mod_commit/index.ts +325 -98
  32. package/ts/mod_config/index.ts +490 -182
  33. package/ts/mod_format/classes.formatcontext.ts +20 -3
  34. package/ts/mod_format/formatters/packagejson.formatter.ts +0 -91
  35. package/ts/mod_format/formatters/smartconfig.formatter.ts +55 -39
  36. package/ts/mod_format/index.ts +279 -90
  37. package/ts/mod_format/mod.plugins.ts +0 -2
  38. package/ts/mod_services/index.ts +550 -183
  39. package/ts/mod_standard/index.ts +191 -58
@@ -1,13 +1,41 @@
1
1
  // this file contains code to create commits in a consistent way
2
2
 
3
- import * as plugins from './mod.plugins.js';
4
- import * as paths from '../paths.js';
5
- import { logger } from '../gitzone.logging.js';
6
- import * as helpers from './mod.helpers.js';
7
- import * as ui from './mod.ui.js';
8
- import { ReleaseConfig } from '../mod_config/classes.releaseconfig.js';
3
+ import * as plugins from "./mod.plugins.js";
4
+ import * as paths from "../paths.js";
5
+ import { logger } from "../gitzone.logging.js";
6
+ import * as helpers from "./mod.helpers.js";
7
+ import * as ui from "./mod.ui.js";
8
+ import { ReleaseConfig } from "../mod_config/classes.releaseconfig.js";
9
+ import type { ICliMode } from "../helpers.climode.js";
10
+ import {
11
+ getCliMode,
12
+ printJson,
13
+ runWithSuppressedOutput,
14
+ } from "../helpers.climode.js";
9
15
 
10
16
  export const run = async (argvArg: any) => {
17
+ const mode = await getCliMode(argvArg);
18
+ const subcommand = argvArg._?.[1];
19
+
20
+ if (mode.help || subcommand === "help") {
21
+ showHelp(mode);
22
+ return;
23
+ }
24
+
25
+ if (subcommand === "recommend") {
26
+ await handleRecommend(mode);
27
+ return;
28
+ }
29
+
30
+ if (mode.json) {
31
+ printJson({
32
+ ok: false,
33
+ error:
34
+ "JSON output is only supported for the read-only recommendation flow. Use `gitzone commit recommend --json`.",
35
+ });
36
+ return;
37
+ }
38
+
11
39
  // Read commit config from .smartconfig.json
12
40
  const smartconfigInstance = new plugins.smartconfig.Smartconfig();
13
41
  const gitzoneConfig = smartconfigInstance.dataFor<{
@@ -15,7 +43,7 @@ export const run = async (argvArg: any) => {
15
43
  alwaysTest?: boolean;
16
44
  alwaysBuild?: boolean;
17
45
  };
18
- }>('@git.zone/cli', {});
46
+ }>("@git.zone/cli", {});
19
47
  const commitConfig = gitzoneConfig.commit || {};
20
48
 
21
49
  // Check flags and merge with config options
@@ -27,10 +55,12 @@ export const run = async (argvArg: any) => {
27
55
  if (wantsRelease) {
28
56
  releaseConfig = await ReleaseConfig.fromCwd();
29
57
  if (!releaseConfig.hasRegistries()) {
30
- logger.log('error', 'No release registries configured.');
31
- console.log('');
32
- console.log(' Run `gitzone config add <registry-url>` to add registries.');
33
- console.log('');
58
+ logger.log("error", "No release registries configured.");
59
+ console.log("");
60
+ console.log(
61
+ " Run `gitzone config add <registry-url>` to add registries.",
62
+ );
63
+ console.log("");
34
64
  process.exit(1);
35
65
  }
36
66
  }
@@ -47,26 +77,26 @@ export const run = async (argvArg: any) => {
47
77
  });
48
78
 
49
79
  if (argvArg.format) {
50
- const formatMod = await import('../mod_format/index.js');
80
+ const formatMod = await import("../mod_format/index.js");
51
81
  await formatMod.run();
52
82
  }
53
83
 
54
84
  // Run tests early to fail fast before analysis
55
85
  if (wantsTest) {
56
- ui.printHeader('🧪 Running tests...');
86
+ ui.printHeader("🧪 Running tests...");
57
87
  const smartshellForTest = new plugins.smartshell.Smartshell({
58
- executor: 'bash',
88
+ executor: "bash",
59
89
  sourceFilePaths: [],
60
90
  });
61
- const testResult = await smartshellForTest.exec('pnpm test');
91
+ const testResult = await smartshellForTest.exec("pnpm test");
62
92
  if (testResult.exitCode !== 0) {
63
- logger.log('error', 'Tests failed. Aborting commit.');
93
+ logger.log("error", "Tests failed. Aborting commit.");
64
94
  process.exit(1);
65
95
  }
66
- logger.log('success', 'All tests passed.');
96
+ logger.log("success", "All tests passed.");
67
97
  }
68
98
 
69
- ui.printHeader('🔍 Analyzing repository changes...');
99
+ ui.printHeader("🔍 Analyzing repository changes...");
70
100
 
71
101
  const aidoc = new plugins.tsdoc.AiDoc();
72
102
  await aidoc.start();
@@ -79,58 +109,63 @@ export const run = async (argvArg: any) => {
79
109
  recommendedNextVersion: nextCommitObject.recommendedNextVersion,
80
110
  recommendedNextVersionLevel: nextCommitObject.recommendedNextVersionLevel,
81
111
  recommendedNextVersionScope: nextCommitObject.recommendedNextVersionScope,
82
- recommendedNextVersionMessage: nextCommitObject.recommendedNextVersionMessage,
112
+ recommendedNextVersionMessage:
113
+ nextCommitObject.recommendedNextVersionMessage,
83
114
  });
84
115
 
85
116
  let answerBucket: plugins.smartinteract.AnswerBucket;
86
117
 
87
118
  // Check if -y/--yes flag is set AND version is not a breaking change
88
119
  // Breaking changes (major version bumps) always require manual confirmation
89
- const isBreakingChange = nextCommitObject.recommendedNextVersionLevel === 'BREAKING CHANGE';
120
+ const isBreakingChange =
121
+ nextCommitObject.recommendedNextVersionLevel === "BREAKING CHANGE";
90
122
  const canAutoAccept = (argvArg.y || argvArg.yes) && !isBreakingChange;
91
123
 
92
124
  if (canAutoAccept) {
93
125
  // Auto-mode: create AnswerBucket programmatically
94
- logger.log('info', '✓ Auto-accepting AI recommendations (--yes flag)');
126
+ logger.log("info", "✓ Auto-accepting AI recommendations (--yes flag)");
95
127
 
96
128
  answerBucket = new plugins.smartinteract.AnswerBucket();
97
129
  answerBucket.addAnswer({
98
- name: 'commitType',
130
+ name: "commitType",
99
131
  value: nextCommitObject.recommendedNextVersionLevel,
100
132
  });
101
133
  answerBucket.addAnswer({
102
- name: 'commitScope',
134
+ name: "commitScope",
103
135
  value: nextCommitObject.recommendedNextVersionScope,
104
136
  });
105
137
  answerBucket.addAnswer({
106
- name: 'commitDescription',
138
+ name: "commitDescription",
107
139
  value: nextCommitObject.recommendedNextVersionMessage,
108
140
  });
109
141
  answerBucket.addAnswer({
110
- name: 'pushToOrigin',
142
+ name: "pushToOrigin",
111
143
  value: !!(argvArg.p || argvArg.push), // Only push if -p flag also provided
112
144
  });
113
145
  answerBucket.addAnswer({
114
- name: 'createRelease',
146
+ name: "createRelease",
115
147
  value: wantsRelease,
116
148
  });
117
149
  } else {
118
150
  // Warn if --yes was provided but we're requiring confirmation due to breaking change
119
151
  if (isBreakingChange && (argvArg.y || argvArg.yes)) {
120
- logger.log('warn', '⚠️ BREAKING CHANGE detected - manual confirmation required');
152
+ logger.log(
153
+ "warn",
154
+ "⚠️ BREAKING CHANGE detected - manual confirmation required",
155
+ );
121
156
  }
122
157
  // Interactive mode: prompt user for input
123
158
  const commitInteract = new plugins.smartinteract.SmartInteract();
124
159
  commitInteract.addQuestions([
125
160
  {
126
- type: 'list',
161
+ type: "list",
127
162
  name: `commitType`,
128
163
  message: `Choose TYPE of the commit:`,
129
164
  choices: [`fix`, `feat`, `BREAKING CHANGE`],
130
165
  default: nextCommitObject.recommendedNextVersionLevel,
131
166
  },
132
167
  {
133
- type: 'input',
168
+ type: "input",
134
169
  name: `commitScope`,
135
170
  message: `What is the SCOPE of the commit:`,
136
171
  default: nextCommitObject.recommendedNextVersionScope,
@@ -142,13 +177,13 @@ export const run = async (argvArg: any) => {
142
177
  default: nextCommitObject.recommendedNextVersionMessage,
143
178
  },
144
179
  {
145
- type: 'confirm',
180
+ type: "confirm",
146
181
  name: `pushToOrigin`,
147
182
  message: `Do you want to push this version now?`,
148
183
  default: true,
149
184
  },
150
185
  {
151
- type: 'confirm',
186
+ type: "confirm",
152
187
  name: `createRelease`,
153
188
  message: `Do you want to publish to npm registries?`,
154
189
  default: wantsRelease,
@@ -157,40 +192,50 @@ export const run = async (argvArg: any) => {
157
192
  answerBucket = await commitInteract.runQueue();
158
193
  }
159
194
  const commitString = createCommitStringFromAnswerBucket(answerBucket);
160
- const commitVersionType = (() => {
161
- switch (answerBucket.getAnswerFor('commitType')) {
162
- case 'fix':
163
- return 'patch';
164
- case 'feat':
165
- return 'minor';
166
- case 'BREAKING CHANGE':
167
- return 'major';
168
- }
169
- })();
195
+ const commitType = answerBucket.getAnswerFor("commitType");
196
+ let commitVersionType: helpers.VersionType;
197
+ switch (commitType) {
198
+ case "fix":
199
+ commitVersionType = "patch";
200
+ break;
201
+ case "feat":
202
+ commitVersionType = "minor";
203
+ break;
204
+ case "BREAKING CHANGE":
205
+ commitVersionType = "major";
206
+ break;
207
+ default:
208
+ throw new Error(`Unsupported commit type: ${commitType}`);
209
+ }
170
210
 
171
- ui.printHeader('✨ Creating Semantic Commit');
211
+ ui.printHeader("✨ Creating Semantic Commit");
172
212
  ui.printCommitMessage(commitString);
173
213
  const smartshellInstance = new plugins.smartshell.Smartshell({
174
- executor: 'bash',
214
+ executor: "bash",
175
215
  sourceFilePaths: [],
176
216
  });
177
217
 
178
218
  // Load release config if user wants to release (interactively selected)
179
- if (answerBucket.getAnswerFor('createRelease') && !releaseConfig) {
219
+ if (answerBucket.getAnswerFor("createRelease") && !releaseConfig) {
180
220
  releaseConfig = await ReleaseConfig.fromCwd();
181
221
  if (!releaseConfig.hasRegistries()) {
182
- logger.log('error', 'No release registries configured.');
183
- console.log('');
184
- console.log(' Run `gitzone config add <registry-url>` to add registries.');
185
- console.log('');
222
+ logger.log("error", "No release registries configured.");
223
+ console.log("");
224
+ console.log(
225
+ " Run `gitzone config add <registry-url>` to add registries.",
226
+ );
227
+ console.log("");
186
228
  process.exit(1);
187
229
  }
188
230
  }
189
231
 
190
232
  // Determine total steps based on options
191
233
  // Note: test runs early (like format) so not counted in numbered steps
192
- const willPush = answerBucket.getAnswerFor('pushToOrigin') && !(process.env.CI === 'true');
193
- const willRelease = answerBucket.getAnswerFor('createRelease') && releaseConfig?.hasRegistries();
234
+ const willPush =
235
+ answerBucket.getAnswerFor("pushToOrigin") && !(process.env.CI === "true");
236
+ const willRelease =
237
+ answerBucket.getAnswerFor("createRelease") &&
238
+ releaseConfig?.hasRegistries();
194
239
  let totalSteps = 5; // Base steps: commitinfo, changelog, staging, commit, version
195
240
  if (wantsBuild) totalSteps += 2; // build step + verification step
196
241
  if (willPush) totalSteps++;
@@ -199,96 +244,156 @@ export const run = async (argvArg: any) => {
199
244
 
200
245
  // Step 1: Baking commitinfo
201
246
  currentStep++;
202
- ui.printStep(currentStep, totalSteps, '🔧 Baking commit info into code', 'in-progress');
247
+ ui.printStep(
248
+ currentStep,
249
+ totalSteps,
250
+ "🔧 Baking commit info into code",
251
+ "in-progress",
252
+ );
203
253
  const commitInfo = new plugins.commitinfo.CommitInfo(
204
254
  paths.cwd,
205
255
  commitVersionType,
206
256
  );
207
257
  await commitInfo.writeIntoPotentialDirs();
208
- ui.printStep(currentStep, totalSteps, '🔧 Baking commit info into code', 'done');
258
+ ui.printStep(
259
+ currentStep,
260
+ totalSteps,
261
+ "🔧 Baking commit info into code",
262
+ "done",
263
+ );
209
264
 
210
265
  // Step 2: Writing changelog
211
266
  currentStep++;
212
- ui.printStep(currentStep, totalSteps, '📄 Generating changelog.md', 'in-progress');
213
- let changelog = nextCommitObject.changelog;
267
+ ui.printStep(
268
+ currentStep,
269
+ totalSteps,
270
+ "📄 Generating changelog.md",
271
+ "in-progress",
272
+ );
273
+ let changelog = nextCommitObject.changelog || "# Changelog\n";
214
274
  changelog = changelog.replaceAll(
215
- '{{nextVersion}}',
275
+ "{{nextVersion}}",
216
276
  (await commitInfo.getNextPlannedVersion()).versionString,
217
277
  );
218
278
  changelog = changelog.replaceAll(
219
- '{{nextVersionScope}}',
220
- `${await answerBucket.getAnswerFor('commitType')}(${await answerBucket.getAnswerFor('commitScope')})`,
279
+ "{{nextVersionScope}}",
280
+ `${await answerBucket.getAnswerFor("commitType")}(${await answerBucket.getAnswerFor("commitScope")})`,
221
281
  );
222
282
  changelog = changelog.replaceAll(
223
- '{{nextVersionMessage}}',
283
+ "{{nextVersionMessage}}",
224
284
  nextCommitObject.recommendedNextVersionMessage,
225
285
  );
226
286
  if (nextCommitObject.recommendedNextVersionDetails?.length > 0) {
227
287
  changelog = changelog.replaceAll(
228
- '{{nextVersionDetails}}',
229
- '- ' + nextCommitObject.recommendedNextVersionDetails.join('\n- '),
288
+ "{{nextVersionDetails}}",
289
+ "- " + nextCommitObject.recommendedNextVersionDetails.join("\n- "),
230
290
  );
231
291
  } else {
232
- changelog = changelog.replaceAll('\n{{nextVersionDetails}}', '');
292
+ changelog = changelog.replaceAll("\n{{nextVersionDetails}}", "");
233
293
  }
234
294
 
235
295
  await plugins.smartfs
236
296
  .file(plugins.path.join(paths.cwd, `changelog.md`))
237
- .encoding('utf8')
297
+ .encoding("utf8")
238
298
  .write(changelog);
239
- ui.printStep(currentStep, totalSteps, '📄 Generating changelog.md', 'done');
299
+ ui.printStep(currentStep, totalSteps, "📄 Generating changelog.md", "done");
240
300
 
241
301
  // Step 3: Staging files
242
302
  currentStep++;
243
- ui.printStep(currentStep, totalSteps, '📦 Staging files', 'in-progress');
303
+ ui.printStep(currentStep, totalSteps, "📦 Staging files", "in-progress");
244
304
  await smartshellInstance.exec(`git add -A`);
245
- ui.printStep(currentStep, totalSteps, '📦 Staging files', 'done');
305
+ ui.printStep(currentStep, totalSteps, "📦 Staging files", "done");
246
306
 
247
307
  // Step 4: Creating commit
248
308
  currentStep++;
249
- ui.printStep(currentStep, totalSteps, '💾 Creating git commit', 'in-progress');
309
+ ui.printStep(
310
+ currentStep,
311
+ totalSteps,
312
+ "💾 Creating git commit",
313
+ "in-progress",
314
+ );
250
315
  await smartshellInstance.exec(`git commit -m "${commitString}"`);
251
- ui.printStep(currentStep, totalSteps, '💾 Creating git commit', 'done');
316
+ ui.printStep(currentStep, totalSteps, "💾 Creating git commit", "done");
252
317
 
253
318
  // Step 5: Bumping version
254
319
  currentStep++;
255
320
  const projectType = await helpers.detectProjectType();
256
- const newVersion = await helpers.bumpProjectVersion(projectType, commitVersionType, currentStep, totalSteps);
321
+ const newVersion = await helpers.bumpProjectVersion(
322
+ projectType,
323
+ commitVersionType,
324
+ currentStep,
325
+ totalSteps,
326
+ );
257
327
 
258
328
  // Step 6: Run build (optional)
259
329
  if (wantsBuild) {
260
330
  currentStep++;
261
- ui.printStep(currentStep, totalSteps, '🔨 Running build', 'in-progress');
262
- const buildResult = await smartshellInstance.exec('pnpm build');
331
+ ui.printStep(currentStep, totalSteps, "🔨 Running build", "in-progress");
332
+ const buildResult = await smartshellInstance.exec("pnpm build");
263
333
  if (buildResult.exitCode !== 0) {
264
- ui.printStep(currentStep, totalSteps, '🔨 Running build', 'error');
265
- logger.log('error', 'Build failed. Aborting release.');
334
+ ui.printStep(currentStep, totalSteps, "🔨 Running build", "error");
335
+ logger.log("error", "Build failed. Aborting release.");
266
336
  process.exit(1);
267
337
  }
268
- ui.printStep(currentStep, totalSteps, '🔨 Running build', 'done');
338
+ ui.printStep(currentStep, totalSteps, "🔨 Running build", "done");
269
339
 
270
340
  // Step 7: Verify no uncommitted changes
271
341
  currentStep++;
272
- ui.printStep(currentStep, totalSteps, '🔍 Verifying clean working tree', 'in-progress');
273
- const statusResult = await smartshellInstance.exec('git status --porcelain');
274
- if (statusResult.stdout.trim() !== '') {
275
- ui.printStep(currentStep, totalSteps, '🔍 Verifying clean working tree', 'error');
276
- logger.log('error', 'Build produced uncommitted changes. This usually means build output is not gitignored.');
277
- logger.log('error', 'Uncommitted files:');
342
+ ui.printStep(
343
+ currentStep,
344
+ totalSteps,
345
+ "🔍 Verifying clean working tree",
346
+ "in-progress",
347
+ );
348
+ const statusResult = await smartshellInstance.exec(
349
+ "git status --porcelain",
350
+ );
351
+ if (statusResult.stdout.trim() !== "") {
352
+ ui.printStep(
353
+ currentStep,
354
+ totalSteps,
355
+ "🔍 Verifying clean working tree",
356
+ "error",
357
+ );
358
+ logger.log(
359
+ "error",
360
+ "Build produced uncommitted changes. This usually means build output is not gitignored.",
361
+ );
362
+ logger.log("error", "Uncommitted files:");
278
363
  console.log(statusResult.stdout);
279
- logger.log('error', 'Aborting release. Please ensure build artifacts are in .gitignore');
364
+ logger.log(
365
+ "error",
366
+ "Aborting release. Please ensure build artifacts are in .gitignore",
367
+ );
280
368
  process.exit(1);
281
369
  }
282
- ui.printStep(currentStep, totalSteps, '🔍 Verifying clean working tree', 'done');
370
+ ui.printStep(
371
+ currentStep,
372
+ totalSteps,
373
+ "🔍 Verifying clean working tree",
374
+ "done",
375
+ );
283
376
  }
284
377
 
285
378
  // Step: Push to remote (optional)
286
379
  const currentBranch = await helpers.detectCurrentBranch();
287
380
  if (willPush) {
288
381
  currentStep++;
289
- ui.printStep(currentStep, totalSteps, `🚀 Pushing to origin/${currentBranch}`, 'in-progress');
290
- await smartshellInstance.exec(`git push origin ${currentBranch} --follow-tags`);
291
- ui.printStep(currentStep, totalSteps, `🚀 Pushing to origin/${currentBranch}`, 'done');
382
+ ui.printStep(
383
+ currentStep,
384
+ totalSteps,
385
+ `🚀 Pushing to origin/${currentBranch}`,
386
+ "in-progress",
387
+ );
388
+ await smartshellInstance.exec(
389
+ `git push origin ${currentBranch} --follow-tags`,
390
+ );
391
+ ui.printStep(
392
+ currentStep,
393
+ totalSteps,
394
+ `🚀 Pushing to origin/${currentBranch}`,
395
+ "done",
396
+ );
292
397
  }
293
398
 
294
399
  // Step 7: Publish to npm registries (optional)
@@ -296,51 +401,173 @@ export const run = async (argvArg: any) => {
296
401
  if (willRelease && releaseConfig) {
297
402
  currentStep++;
298
403
  const registries = releaseConfig.getRegistries();
299
- ui.printStep(currentStep, totalSteps, `📦 Publishing to ${registries.length} registr${registries.length === 1 ? 'y' : 'ies'}`, 'in-progress');
404
+ ui.printStep(
405
+ currentStep,
406
+ totalSteps,
407
+ `📦 Publishing to ${registries.length} registr${registries.length === 1 ? "y" : "ies"}`,
408
+ "in-progress",
409
+ );
300
410
 
301
411
  const accessLevel = releaseConfig.getAccessLevel();
302
412
  for (const registry of registries) {
303
413
  try {
304
- await smartshellInstance.exec(`npm publish --registry=${registry} --access=${accessLevel}`);
414
+ await smartshellInstance.exec(
415
+ `npm publish --registry=${registry} --access=${accessLevel}`,
416
+ );
305
417
  releasedRegistries.push(registry);
306
418
  } catch (error) {
307
- logger.log('error', `Failed to publish to ${registry}: ${error}`);
419
+ logger.log("error", `Failed to publish to ${registry}: ${error}`);
308
420
  }
309
421
  }
310
422
 
311
423
  if (releasedRegistries.length === registries.length) {
312
- ui.printStep(currentStep, totalSteps, `📦 Publishing to ${registries.length} registr${registries.length === 1 ? 'y' : 'ies'}`, 'done');
424
+ ui.printStep(
425
+ currentStep,
426
+ totalSteps,
427
+ `📦 Publishing to ${registries.length} registr${registries.length === 1 ? "y" : "ies"}`,
428
+ "done",
429
+ );
313
430
  } else {
314
- ui.printStep(currentStep, totalSteps, `📦 Publishing to ${registries.length} registr${registries.length === 1 ? 'y' : 'ies'}`, 'error');
431
+ ui.printStep(
432
+ currentStep,
433
+ totalSteps,
434
+ `📦 Publishing to ${registries.length} registr${registries.length === 1 ? "y" : "ies"}`,
435
+ "error",
436
+ );
315
437
  }
316
438
  }
317
439
 
318
- console.log(''); // Add spacing before summary
440
+ console.log(""); // Add spacing before summary
319
441
 
320
442
  // Get commit SHA for summary
321
- const commitShaResult = await smartshellInstance.exec('git rev-parse --short HEAD');
443
+ const commitShaResult = await smartshellInstance.exec(
444
+ "git rev-parse --short HEAD",
445
+ );
322
446
  const commitSha = commitShaResult.stdout.trim();
323
447
 
324
448
  // Print final summary
325
449
  ui.printSummary({
326
450
  projectType,
327
451
  branch: currentBranch,
328
- commitType: answerBucket.getAnswerFor('commitType'),
329
- commitScope: answerBucket.getAnswerFor('commitScope'),
330
- commitMessage: answerBucket.getAnswerFor('commitDescription'),
452
+ commitType: answerBucket.getAnswerFor("commitType"),
453
+ commitScope: answerBucket.getAnswerFor("commitScope"),
454
+ commitMessage: answerBucket.getAnswerFor("commitDescription"),
331
455
  newVersion: newVersion,
332
456
  commitSha: commitSha,
333
457
  pushed: willPush,
334
458
  released: releasedRegistries.length > 0,
335
- releasedRegistries: releasedRegistries.length > 0 ? releasedRegistries : undefined,
459
+ releasedRegistries:
460
+ releasedRegistries.length > 0 ? releasedRegistries : undefined,
336
461
  });
337
462
  };
338
463
 
464
+ async function handleRecommend(mode: ICliMode): Promise<void> {
465
+ const recommendationBuilder = async () => {
466
+ const aidoc = new plugins.tsdoc.AiDoc();
467
+ await aidoc.start();
468
+ try {
469
+ return await aidoc.buildNextCommitObject(paths.cwd);
470
+ } finally {
471
+ await aidoc.stop();
472
+ }
473
+ };
474
+
475
+ const recommendation = mode.json
476
+ ? await runWithSuppressedOutput(recommendationBuilder)
477
+ : await recommendationBuilder();
478
+
479
+ if (mode.json) {
480
+ printJson(recommendation);
481
+ return;
482
+ }
483
+
484
+ ui.printRecommendation({
485
+ recommendedNextVersion: recommendation.recommendedNextVersion,
486
+ recommendedNextVersionLevel: recommendation.recommendedNextVersionLevel,
487
+ recommendedNextVersionScope: recommendation.recommendedNextVersionScope,
488
+ recommendedNextVersionMessage: recommendation.recommendedNextVersionMessage,
489
+ });
490
+
491
+ console.log(
492
+ `Suggested commit: ${recommendation.recommendedNextVersionLevel}(${recommendation.recommendedNextVersionScope}): ${recommendation.recommendedNextVersionMessage}`,
493
+ );
494
+ }
495
+
339
496
  const createCommitStringFromAnswerBucket = (
340
497
  answerBucket: plugins.smartinteract.AnswerBucket,
341
498
  ) => {
342
- const commitType = answerBucket.getAnswerFor('commitType');
343
- const commitScope = answerBucket.getAnswerFor('commitScope');
344
- const commitDescription = answerBucket.getAnswerFor('commitDescription');
499
+ const commitType = answerBucket.getAnswerFor("commitType");
500
+ const commitScope = answerBucket.getAnswerFor("commitScope");
501
+ const commitDescription = answerBucket.getAnswerFor("commitDescription");
345
502
  return `${commitType}(${commitScope}): ${commitDescription}`;
346
503
  };
504
+
505
+ export function showHelp(mode?: ICliMode): void {
506
+ if (mode?.json) {
507
+ printJson({
508
+ command: "commit",
509
+ usage: "gitzone commit [recommend] [options]",
510
+ description:
511
+ "Creates semantic commits or emits a read-only recommendation.",
512
+ commands: [
513
+ {
514
+ name: "recommend",
515
+ description:
516
+ "Generate a commit recommendation without mutating the repository",
517
+ },
518
+ ],
519
+ flags: [
520
+ { flag: "-y, --yes", description: "Auto-accept AI recommendations" },
521
+ { flag: "-p, --push", description: "Push to origin after commit" },
522
+ { flag: "-t, --test", description: "Run tests before the commit flow" },
523
+ {
524
+ flag: "-b, --build",
525
+ description: "Run the build after the commit flow",
526
+ },
527
+ {
528
+ flag: "-r, --release",
529
+ description: "Publish to configured registries after push",
530
+ },
531
+ {
532
+ flag: "--format",
533
+ description: "Run gitzone format before committing",
534
+ },
535
+ {
536
+ flag: "--json",
537
+ description: "Emit JSON for `commit recommend` only",
538
+ },
539
+ ],
540
+ examples: [
541
+ "gitzone commit recommend --json",
542
+ "gitzone commit -y",
543
+ "gitzone commit -ypbr",
544
+ ],
545
+ });
546
+ return;
547
+ }
548
+
549
+ console.log("");
550
+ console.log("Usage: gitzone commit [recommend] [options]");
551
+ console.log("");
552
+ console.log("Commands:");
553
+ console.log(
554
+ " recommend Generate a commit recommendation without mutating the repository",
555
+ );
556
+ console.log("");
557
+ console.log("Flags:");
558
+ console.log(" -y, --yes Auto-accept AI recommendations");
559
+ console.log(" -p, --push Push to origin after commit");
560
+ console.log(" -t, --test Run tests before the commit flow");
561
+ console.log(" -b, --build Run the build after the commit flow");
562
+ console.log(
563
+ " -r, --release Publish to configured registries after push",
564
+ );
565
+ console.log(" --format Run gitzone format before committing");
566
+ console.log(" --json Emit JSON for `commit recommend` only");
567
+ console.log("");
568
+ console.log("Examples:");
569
+ console.log(" gitzone commit recommend --json");
570
+ console.log(" gitzone commit -y");
571
+ console.log(" gitzone commit -ypbr");
572
+ console.log("");
573
+ }