@git.zone/cli 2.19.0 → 2.19.2

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 (43) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/gitzone.cli.d.ts +1 -1
  3. package/dist_ts/gitzone.cli.js +100 -126
  4. package/dist_ts/helpers.climode.d.ts +2 -0
  5. package/dist_ts/helpers.climode.js +31 -2
  6. package/dist_ts/helpers.smartconfigmigrations.js +34 -2
  7. package/dist_ts/mod_config/index.js +51 -10
  8. package/dist_ts/mod_format/classes.baseformatter.d.ts +3 -1
  9. package/dist_ts/mod_format/classes.baseformatter.js +7 -1
  10. package/dist_ts/mod_format/classes.formatplanner.d.ts +2 -0
  11. package/dist_ts/mod_format/classes.formatplanner.js +48 -4
  12. package/dist_ts/mod_format/formatters/license.formatter.d.ts +4 -1
  13. package/dist_ts/mod_format/formatters/license.formatter.js +32 -10
  14. package/dist_ts/mod_format/formatters/prettier.formatter.d.ts +0 -3
  15. package/dist_ts/mod_format/formatters/prettier.formatter.js +53 -62
  16. package/dist_ts/mod_format/index.d.ts +1 -1
  17. package/dist_ts/mod_format/index.js +237 -8
  18. package/dist_ts/mod_format/interfaces.format.d.ts +7 -11
  19. package/dist_ts/mod_format/interfaces.format.js +1 -1
  20. package/dist_ts/mod_standard/index.js +2 -1
  21. package/dist_ts/mod_tools/classes.packagemanager.d.ts +36 -1
  22. package/dist_ts/mod_tools/classes.packagemanager.js +541 -34
  23. package/dist_ts/mod_tools/index.js +128 -26
  24. package/dist_ts/plugins.d.ts +2 -1
  25. package/dist_ts/plugins.js +3 -2
  26. package/package.json +1 -1
  27. package/readme.hints.md +10 -0
  28. package/readme.md +11 -0
  29. package/ts/00_commitinfo_data.ts +1 -1
  30. package/ts/gitzone.cli.ts +104 -144
  31. package/ts/helpers.climode.ts +36 -1
  32. package/ts/helpers.smartconfigmigrations.ts +37 -1
  33. package/ts/mod_config/index.ts +60 -9
  34. package/ts/mod_format/classes.baseformatter.ts +13 -1
  35. package/ts/mod_format/classes.formatplanner.ts +66 -4
  36. package/ts/mod_format/formatters/license.formatter.ts +43 -15
  37. package/ts/mod_format/formatters/prettier.formatter.ts +54 -66
  38. package/ts/mod_format/index.ts +289 -8
  39. package/ts/mod_format/interfaces.format.ts +8 -11
  40. package/ts/mod_standard/index.ts +1 -0
  41. package/ts/mod_tools/classes.packagemanager.ts +724 -34
  42. package/ts/mod_tools/index.ts +225 -45
  43. package/ts/plugins.ts +2 -0
@@ -22,6 +22,7 @@ import { TsconfigFormatter } from "./formatters/tsconfig.formatter.js";
22
22
  import { PrettierFormatter } from "./formatters/prettier.formatter.js";
23
23
  import { ReadmeFormatter } from "./formatters/readme.formatter.js";
24
24
  import { CopyFormatter } from "./formatters/copy.formatter.js";
25
+ import type { ICheckResult, IFormatPlan } from "./interfaces.format.js";
25
26
 
26
27
  /**
27
28
  * Rename npmextra.json or smartconfig.json to .smartconfig.json
@@ -94,9 +95,39 @@ const getFormatConfig = async () => {
94
95
  };
95
96
  };
96
97
 
98
+ const normalizeModuleList = (value: unknown): string[] => {
99
+ if (Array.isArray(value)) {
100
+ return value.flatMap((item) => normalizeModuleList(item));
101
+ }
102
+ if (typeof value !== "string") {
103
+ return [];
104
+ }
105
+ return value
106
+ .split(",")
107
+ .map((item) => item.trim())
108
+ .filter(Boolean);
109
+ };
110
+
111
+ const getPlanStatus = (plan: IFormatPlan) => {
112
+ const errorWarnings = plan.warnings.filter(
113
+ (warning) => warning.level === "error",
114
+ );
115
+ const hasChanges = plan.summary.totalFiles > 0;
116
+ const hasErrors = errorWarnings.length > 0;
117
+
118
+ return {
119
+ ok: !hasChanges && !hasErrors,
120
+ hasChanges,
121
+ hasErrors,
122
+ errorCount: errorWarnings.length,
123
+ };
124
+ };
125
+
97
126
  const createActiveFormatters = async (options: {
98
127
  interactive: boolean;
99
128
  jsonOutput: boolean;
129
+ only?: string[];
130
+ skip?: string[];
100
131
  }) => {
101
132
  const project = await Project.fromCwd({ requireProjectType: false });
102
133
  const context = new FormatContext(options);
@@ -107,11 +138,19 @@ const createActiveFormatters = async (options: {
107
138
  ([, FormatterClass]) => new FormatterClass(context, project),
108
139
  );
109
140
 
141
+ const onlyModules = options.only?.length
142
+ ? options.only
143
+ : formatConfig.modules.only;
144
+ const skipModules = [
145
+ ...formatConfig.modules.skip,
146
+ ...(options.skip || []),
147
+ ];
148
+
110
149
  const activeFormatters = formatters.filter((formatter) => {
111
- if (formatConfig.modules.only.length > 0) {
112
- return formatConfig.modules.only.includes(formatter.name);
150
+ if (onlyModules.length > 0) {
151
+ return onlyModules.includes(formatter.name);
113
152
  }
114
- if (formatConfig.modules.skip.includes(formatter.name)) {
153
+ if (skipModules.includes(formatter.name)) {
115
154
  return false;
116
155
  }
117
156
  return true;
@@ -129,11 +168,15 @@ const buildFormatPlan = async (options: {
129
168
  fromPlan?: string;
130
169
  interactive: boolean;
131
170
  jsonOutput: boolean;
171
+ only?: string[];
172
+ skip?: string[];
132
173
  }) => {
133
174
  const { context, planner, formatConfig, activeFormatters } =
134
175
  await createActiveFormatters({
135
176
  interactive: options.interactive,
136
177
  jsonOutput: options.jsonOutput,
178
+ only: options.only,
179
+ skip: options.skip,
137
180
  });
138
181
 
139
182
  const plan = options.fromPlan
@@ -167,6 +210,182 @@ const serializePlan = (plan: any) => {
167
210
  };
168
211
  };
169
212
 
213
+ const buildFormatFixPrompt = (
214
+ plan: IFormatPlan,
215
+ extraInstructions: string,
216
+ ): string => {
217
+ const promptParts = [
218
+ "Other /c-* commands can be found at ~/.config/opencode/commands/*",
219
+ "# gitzone format fix",
220
+ "",
221
+ `Working directory: ${process.cwd()}`,
222
+ "",
223
+ "Repair project formatting so `gitzone format check --json` passes.",
224
+ "",
225
+ "Rules:",
226
+ "- Read `.smartconfig.json`, `package.json`, `tsconfig.json`, and the current format plan before editing.",
227
+ "- Prefer deterministic gitzone standards, bundled assets, and existing project conventions.",
228
+ "- Keep changes focused on formatting, metadata normalization, templates, and config consistency.",
229
+ "- Do not commit, release, install dependencies, or modify unrelated files.",
230
+ "- Use pnpm commands only if commands are needed.",
231
+ "- Run `gitzone format --write --yes` after changes.",
232
+ "- Run `gitzone format check --json` after changes and keep fixing until it passes.",
233
+ "- Run `git diff --check` after changes to catch whitespace problems.",
234
+ "",
235
+ "Current format plan:",
236
+ JSON.stringify(serializePlan(plan), null, 2),
237
+ ];
238
+
239
+ if (extraInstructions) {
240
+ promptParts.push("", "Additional user instructions:", extraInstructions);
241
+ }
242
+
243
+ return promptParts.join("\n");
244
+ };
245
+
246
+ const handleFormatFix = async (
247
+ options: Record<string, any>,
248
+ mode: ICliMode,
249
+ ): Promise<void> => {
250
+ if (mode.json) {
251
+ printJson({
252
+ ok: false,
253
+ error:
254
+ "JSON output is not supported for `gitzone format fix`. Use `gitzone format check --json` for machine-readable diagnostics.",
255
+ });
256
+ process.exitCode = 1;
257
+ return;
258
+ }
259
+
260
+ const extraInstructions = (options._?.slice(2).join(" ") || "").trim();
261
+ const force = Boolean(options.force);
262
+ const autoApprove = Boolean(options.yes || mode.yes);
263
+ const formatConfig = await getFormatConfig();
264
+ const interactive =
265
+ options.interactive ?? (mode.interactive && formatConfig.interactive);
266
+ const only = normalizeModuleList(options.only);
267
+ const skip = normalizeModuleList(options.skip);
268
+
269
+ const buildCurrentPlan = async () => {
270
+ return await buildFormatPlan({
271
+ interactive,
272
+ jsonOutput: false,
273
+ only,
274
+ skip,
275
+ });
276
+ };
277
+
278
+ logger.log("info", "Analyzing project for format fixes...");
279
+ let { plan } = await buildCurrentPlan();
280
+ let status = getPlanStatus(plan);
281
+
282
+ if (status.ok && !extraInstructions && !force) {
283
+ logger.log(
284
+ "success",
285
+ "Format check found no issues. Use `gitzone format fix --force` to run opencode anyway.",
286
+ );
287
+ return;
288
+ }
289
+
290
+ if (!autoApprove) {
291
+ if (!mode.interactive) {
292
+ throw new Error(
293
+ "Format fix requires an interactive terminal or `-y` to run non-interactively.",
294
+ );
295
+ }
296
+ const confirmed = await plugins.smartinteract.SmartInteract.getCliConfirmation(
297
+ `Run format fixes? (${plan.summary.totalFiles} planned change(s), ${status.errorCount} error warning(s))`,
298
+ true,
299
+ );
300
+ if (!confirmed) {
301
+ logger.log("info", "Format fix cancelled.");
302
+ return;
303
+ }
304
+ }
305
+
306
+ if (status.hasChanges) {
307
+ logger.log("info", "Applying deterministic format changes first...");
308
+ await run({
309
+ _: ["format"],
310
+ write: true,
311
+ yes: true,
312
+ interactive: false,
313
+ verbose: options.verbose,
314
+ detailed: options.detailed,
315
+ only: options.only,
316
+ skip: options.skip,
317
+ });
318
+
319
+ ({ plan } = await buildCurrentPlan());
320
+ status = getPlanStatus(plan);
321
+ if (status.ok && !extraInstructions && !force) {
322
+ logger.log("success", "Format fix completed successfully.");
323
+ return;
324
+ }
325
+ }
326
+
327
+ const opencodeArgs = [
328
+ "run",
329
+ "--title",
330
+ "gitzone format fix",
331
+ "--dir",
332
+ process.cwd(),
333
+ ];
334
+ if (autoApprove) {
335
+ opencodeArgs.push("--dangerously-skip-permissions");
336
+ }
337
+ opencodeArgs.push(buildFormatFixPrompt(plan, extraInstructions));
338
+
339
+ logger.log("info", "Starting opencode format fix...");
340
+ const smartshellInstance = new plugins.smartshell.Smartshell({
341
+ executor: "bash",
342
+ sourceFilePaths: [],
343
+ });
344
+
345
+ let result: plugins.smartshell.IExecResult;
346
+ try {
347
+ result = await smartshellInstance.execSpawn("opencode", opencodeArgs, {
348
+ stdio: "inherit",
349
+ });
350
+ } catch (error) {
351
+ throw new Error(
352
+ `Failed to run opencode: ${error instanceof Error ? error.message : String(error)}`,
353
+ );
354
+ }
355
+
356
+ if (result.exitCode !== 0) {
357
+ logger.log("error", `opencode exited with code ${result.exitCode}`);
358
+ process.exitCode = result.exitCode || 1;
359
+ return;
360
+ }
361
+
362
+ logger.log("info", "Running deterministic format pass after opencode...");
363
+ await run({
364
+ _: ["format"],
365
+ write: true,
366
+ yes: true,
367
+ interactive: false,
368
+ verbose: options.verbose,
369
+ detailed: options.detailed,
370
+ only: options.only,
371
+ skip: options.skip,
372
+ });
373
+
374
+ const { planner: finalPlanner, plan: finalPlan } = await buildCurrentPlan();
375
+ await finalPlanner.displayPlan(finalPlan, options.detailed);
376
+ const finalStatus = getPlanStatus(finalPlan);
377
+ if (finalStatus.ok) {
378
+ logger.log("success", "Format fix completed successfully.");
379
+ return;
380
+ }
381
+
382
+ logger.log(
383
+ "error",
384
+ `Format fix left ${finalPlan.summary.totalFiles} planned change(s) and ${finalStatus.errorCount} error warning(s).`,
385
+ );
386
+ process.exitCode = 1;
387
+ };
388
+
170
389
  export let run = async (
171
390
  options: {
172
391
  write?: boolean;
@@ -194,8 +413,25 @@ export let run = async (
194
413
  setVerboseMode(true);
195
414
  }
196
415
 
416
+ if (subcommand === "fix") {
417
+ await handleFormatFix(options, mode);
418
+ return;
419
+ }
420
+
197
421
  const shouldWrite = options.write ?? options.dryRun === false;
198
422
  const treatAsPlan = subcommand === "plan";
423
+ const treatAsCheck = subcommand === "check" || Boolean(options.check);
424
+
425
+ if (treatAsCheck && shouldWrite) {
426
+ const error = "`gitzone format check` is read-only and cannot be combined with --write.";
427
+ if (mode.json) {
428
+ printJson({ ok: false, error });
429
+ } else {
430
+ logger.log("error", error);
431
+ }
432
+ process.exitCode = 1;
433
+ return;
434
+ }
199
435
 
200
436
  if (mode.json && shouldWrite) {
201
437
  printJson({
@@ -212,7 +448,9 @@ export let run = async (
212
448
  const formatConfig = await getFormatConfig();
213
449
  const interactive =
214
450
  options.interactive ?? (mode.interactive && formatConfig.interactive);
215
- const autoApprove = options.yes ?? formatConfig.autoApprove;
451
+ const autoApprove = options.yes ?? (mode.yes || formatConfig.autoApprove);
452
+ const only = normalizeModuleList(options.only);
453
+ const skip = normalizeModuleList(options.skip);
216
454
 
217
455
  try {
218
456
  const planBuilder = async () => {
@@ -220,6 +458,8 @@ export let run = async (
220
458
  fromPlan: options.fromPlan,
221
459
  interactive,
222
460
  jsonOutput: mode.json,
461
+ only,
462
+ skip,
223
463
  });
224
464
  };
225
465
 
@@ -231,7 +471,16 @@ export let run = async (
231
471
  : await planBuilder();
232
472
 
233
473
  if (mode.json) {
234
- printJson(serializePlan(plan));
474
+ const serializedPlan = serializePlan(plan);
475
+ if (treatAsCheck) {
476
+ const status = getPlanStatus(plan);
477
+ printJson({ ok: status.ok, ...serializedPlan });
478
+ if (!status.ok) {
479
+ process.exitCode = 1;
480
+ }
481
+ return;
482
+ }
483
+ printJson(serializedPlan);
235
484
  return;
236
485
  }
237
486
 
@@ -251,6 +500,20 @@ export let run = async (
251
500
  return;
252
501
  }
253
502
 
503
+ if (treatAsCheck) {
504
+ const status = getPlanStatus(plan);
505
+ if (status.ok) {
506
+ logger.log("success", "Format check passed");
507
+ } else {
508
+ logger.log(
509
+ "error",
510
+ `Format check failed: ${plan.summary.totalFiles} planned change(s), ${status.errorCount} error warning(s)`,
511
+ );
512
+ process.exitCode = 1;
513
+ }
514
+ return;
515
+ }
516
+
254
517
  // Show diffs if explicitly requested or before interactive write confirmation
255
518
  const showDiffs =
256
519
  options.diff || (shouldWrite && interactive && !autoApprove);
@@ -314,7 +577,6 @@ export let run = async (
314
577
  }
315
578
  };
316
579
 
317
- import type { ICheckResult } from "./interfaces.format.js";
318
580
  export type { ICheckResult };
319
581
 
320
582
  /**
@@ -363,7 +625,7 @@ export function showHelp(mode?: ICliMode): void {
363
625
  if (mode?.json) {
364
626
  printJson({
365
627
  command: "format",
366
- usage: "gitzone format [plan] [options]",
628
+ usage: "gitzone format [plan|check|fix] [options]",
367
629
  description:
368
630
  "Plans formatting changes by default and applies them only with --write.",
369
631
  flags: [
@@ -393,19 +655,33 @@ export function showHelp(mode?: ICliMode): void {
393
655
  flag: "--diff",
394
656
  description: "Show per-file diffs before applying changes",
395
657
  },
658
+ {
659
+ flag: "--only <modules>",
660
+ description: "Run only the comma-separated formatter modules",
661
+ },
662
+ {
663
+ flag: "--skip <modules>",
664
+ description: "Skip the comma-separated formatter modules",
665
+ },
666
+ {
667
+ flag: "--force",
668
+ description: "Run `format fix` even when the deterministic plan is clean",
669
+ },
396
670
  { flag: "--json", description: "Emit a read-only format plan as JSON" },
397
671
  ],
398
672
  examples: [
399
673
  "gitzone format",
400
674
  "gitzone format plan --json",
675
+ "gitzone format check",
401
676
  "gitzone format --write --yes",
677
+ "gitzone format fix",
402
678
  ],
403
679
  });
404
680
  return;
405
681
  }
406
682
 
407
683
  console.log("");
408
- console.log("Usage: gitzone format [plan] [options]");
684
+ console.log("Usage: gitzone format [plan|check|fix] [options]");
409
685
  console.log("");
410
686
  console.log(
411
687
  "Plans formatting changes by default and applies them only with --write.",
@@ -424,11 +700,16 @@ export function showHelp(mode?: ICliMode): void {
424
700
  console.log(
425
701
  " --diff Show per-file diffs before applying changes",
426
702
  );
703
+ console.log(" --only <modules> Run only comma-separated formatter modules");
704
+ console.log(" --skip <modules> Skip comma-separated formatter modules");
705
+ console.log(" --force Run format fix even when the plan is clean");
427
706
  console.log(" --json Emit a read-only format plan as JSON");
428
707
  console.log("");
429
708
  console.log("Examples:");
430
709
  console.log(" gitzone format");
431
710
  console.log(" gitzone format plan --json");
711
+ console.log(" gitzone format check");
432
712
  console.log(" gitzone format --write --yes");
713
+ console.log(" gitzone format fix");
433
714
  console.log("");
434
715
  }
@@ -1,3 +1,9 @@
1
+ export type IFormatWarning = {
2
+ level: 'info' | 'warning' | 'error';
3
+ message: string;
4
+ module: string;
5
+ };
6
+
1
7
  export type IFormatPlan = {
2
8
  summary: {
3
9
  totalFiles: number;
@@ -5,17 +11,8 @@ export type IFormatPlan = {
5
11
  filesModified: number;
6
12
  filesRemoved: number;
7
13
  };
8
- changes: Array<{
9
- type: 'create' | 'modify' | 'delete';
10
- path: string;
11
- module: string;
12
- description: string;
13
- }>;
14
- warnings: Array<{
15
- level: 'info' | 'warning' | 'error';
16
- message: string;
17
- module: string;
18
- }>;
14
+ changes: IPlannedChange[];
15
+ warnings: IFormatWarning[];
19
16
  };
20
17
 
21
18
  export type IPlannedChange = {
@@ -202,6 +202,7 @@ export async function showHelp(
202
202
  console.log(" gitzone commit recommend --json");
203
203
  console.log(" gitzone release --plan");
204
204
  console.log(" gitzone format plan --json");
205
+ console.log(" gitzone format check");
205
206
  console.log(" gitzone services set mongodb,minio");
206
207
  console.log(" gitzone tools update");
207
208
  console.log("");