@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,44 +1,60 @@
1
- import * as plugins from './mod.plugins.js';
2
- import { Project } from '../classes.project.js';
3
- import { FormatContext } from './classes.formatcontext.js';
4
- import { FormatPlanner } from './classes.formatplanner.js';
5
- import { BaseFormatter } from './classes.baseformatter.js';
6
- import { logger, setVerboseMode } from '../gitzone.logging.js';
7
-
8
- import { CleanupFormatter } from './formatters/cleanup.formatter.js';
9
- import { SmartconfigFormatter } from './formatters/smartconfig.formatter.js';
10
- import { LicenseFormatter } from './formatters/license.formatter.js';
11
- import { PackageJsonFormatter } from './formatters/packagejson.formatter.js';
12
- import { TemplatesFormatter } from './formatters/templates.formatter.js';
13
- import { GitignoreFormatter } from './formatters/gitignore.formatter.js';
14
- import { TsconfigFormatter } from './formatters/tsconfig.formatter.js';
15
- import { PrettierFormatter } from './formatters/prettier.formatter.js';
16
- import { ReadmeFormatter } from './formatters/readme.formatter.js';
17
- import { CopyFormatter } from './formatters/copy.formatter.js';
1
+ import * as plugins from "./mod.plugins.js";
2
+ import { Project } from "../classes.project.js";
3
+ import { FormatContext } from "./classes.formatcontext.js";
4
+ import { FormatPlanner } from "./classes.formatplanner.js";
5
+ import { BaseFormatter } from "./classes.baseformatter.js";
6
+ import { logger, setVerboseMode } from "../gitzone.logging.js";
7
+ import type { ICliMode } from "../helpers.climode.js";
8
+ import {
9
+ getCliMode,
10
+ printJson,
11
+ runWithSuppressedOutput,
12
+ } from "../helpers.climode.js";
13
+ import { getCliConfigValue } from "../helpers.smartconfig.js";
14
+
15
+ import { CleanupFormatter } from "./formatters/cleanup.formatter.js";
16
+ import { SmartconfigFormatter } from "./formatters/smartconfig.formatter.js";
17
+ import { LicenseFormatter } from "./formatters/license.formatter.js";
18
+ import { PackageJsonFormatter } from "./formatters/packagejson.formatter.js";
19
+ import { TemplatesFormatter } from "./formatters/templates.formatter.js";
20
+ import { GitignoreFormatter } from "./formatters/gitignore.formatter.js";
21
+ import { TsconfigFormatter } from "./formatters/tsconfig.formatter.js";
22
+ import { PrettierFormatter } from "./formatters/prettier.formatter.js";
23
+ import { ReadmeFormatter } from "./formatters/readme.formatter.js";
24
+ import { CopyFormatter } from "./formatters/copy.formatter.js";
18
25
 
19
26
  /**
20
27
  * Rename npmextra.json or smartconfig.json to .smartconfig.json
21
28
  * before any formatter tries to read config.
22
29
  */
23
- async function migrateConfigFile(): Promise<void> {
24
- const target = '.smartconfig.json';
30
+ async function migrateConfigFile(allowWrite: boolean): Promise<void> {
31
+ const target = ".smartconfig.json";
25
32
  const targetExists = await plugins.smartfs.file(target).exists();
26
33
  if (targetExists) return;
27
34
 
28
- for (const oldName of ['smartconfig.json', 'npmextra.json']) {
35
+ for (const oldName of ["smartconfig.json", "npmextra.json"]) {
29
36
  const exists = await plugins.smartfs.file(oldName).exists();
30
37
  if (exists) {
31
- const content = await plugins.smartfs.file(oldName).encoding('utf8').read() as string;
32
- await plugins.smartfs.file(`./${target}`).encoding('utf8').write(content);
38
+ if (!allowWrite) {
39
+ return;
40
+ }
41
+ const content = (await plugins.smartfs
42
+ .file(oldName)
43
+ .encoding("utf8")
44
+ .read()) as string;
45
+ await plugins.smartfs.file(`./${target}`).encoding("utf8").write(content);
33
46
  await plugins.smartfs.file(oldName).delete();
34
- logger.log('info', `Migrated ${oldName} to ${target}`);
47
+ logger.log("info", `Migrated ${oldName} to ${target}`);
35
48
  return;
36
49
  }
37
50
  }
38
51
  }
39
52
 
40
53
  // Shared formatter class map used by both run() and runFormatter()
41
- const formatterMap: Record<string, new (ctx: FormatContext, proj: Project) => BaseFormatter> = {
54
+ const formatterMap: Record<
55
+ string,
56
+ new (ctx: FormatContext, proj: Project) => BaseFormatter
57
+ > = {
42
58
  cleanup: CleanupFormatter,
43
59
  smartconfig: SmartconfigFormatter,
44
60
  license: LicenseFormatter,
@@ -52,7 +68,104 @@ const formatterMap: Record<string, new (ctx: FormatContext, proj: Project) => Ba
52
68
  };
53
69
 
54
70
  // Formatters that don't require projectType to be set
55
- const formattersNotRequiringProjectType = ['smartconfig', 'prettier', 'cleanup', 'packagejson'];
71
+ const formattersNotRequiringProjectType = [
72
+ "smartconfig",
73
+ "prettier",
74
+ "cleanup",
75
+ "packagejson",
76
+ ];
77
+
78
+ const getFormatConfig = async () => {
79
+ const rawFormatConfig = await getCliConfigValue<Record<string, any>>(
80
+ "format",
81
+ {},
82
+ );
83
+ return {
84
+ interactive: true,
85
+ showDiffs: false,
86
+ autoApprove: false,
87
+ showStats: true,
88
+ modules: {
89
+ skip: [],
90
+ only: [],
91
+ ...(rawFormatConfig.modules || {}),
92
+ },
93
+ ...rawFormatConfig,
94
+ };
95
+ };
96
+
97
+ const createActiveFormatters = async (options: {
98
+ interactive: boolean;
99
+ jsonOutput: boolean;
100
+ }) => {
101
+ const project = await Project.fromCwd({ requireProjectType: false });
102
+ const context = new FormatContext(options);
103
+ const planner = new FormatPlanner();
104
+
105
+ const formatConfig = await getFormatConfig();
106
+ const formatters = Object.entries(formatterMap).map(
107
+ ([, FormatterClass]) => new FormatterClass(context, project),
108
+ );
109
+
110
+ const activeFormatters = formatters.filter((formatter) => {
111
+ if (formatConfig.modules.only.length > 0) {
112
+ return formatConfig.modules.only.includes(formatter.name);
113
+ }
114
+ if (formatConfig.modules.skip.includes(formatter.name)) {
115
+ return false;
116
+ }
117
+ return true;
118
+ });
119
+
120
+ return {
121
+ context,
122
+ planner,
123
+ formatConfig,
124
+ activeFormatters,
125
+ };
126
+ };
127
+
128
+ const buildFormatPlan = async (options: {
129
+ fromPlan?: string;
130
+ interactive: boolean;
131
+ jsonOutput: boolean;
132
+ }) => {
133
+ const { context, planner, formatConfig, activeFormatters } =
134
+ await createActiveFormatters({
135
+ interactive: options.interactive,
136
+ jsonOutput: options.jsonOutput,
137
+ });
138
+
139
+ const plan = options.fromPlan
140
+ ? JSON.parse(
141
+ (await plugins.smartfs
142
+ .file(options.fromPlan)
143
+ .encoding("utf8")
144
+ .read()) as string,
145
+ )
146
+ : await planner.planFormat(activeFormatters);
147
+
148
+ return {
149
+ context,
150
+ planner,
151
+ formatConfig,
152
+ activeFormatters,
153
+ plan,
154
+ };
155
+ };
156
+
157
+ const serializePlan = (plan: any) => {
158
+ return {
159
+ summary: plan.summary,
160
+ warnings: plan.warnings,
161
+ changes: plan.changes.map((change: any) => ({
162
+ type: change.type,
163
+ path: change.path,
164
+ module: change.module,
165
+ description: change.description,
166
+ })),
167
+ };
168
+ };
56
169
 
57
170
  export let run = async (
58
171
  options: {
@@ -66,62 +179,61 @@ export let run = async (
66
179
  interactive?: boolean;
67
180
  verbose?: boolean;
68
181
  diff?: boolean;
182
+ [key: string]: any;
69
183
  } = {},
70
184
  ): Promise<any> => {
185
+ const mode = await getCliMode(options as any);
186
+ const subcommand = (options as any)?._?.[1];
187
+
188
+ if (mode.help || subcommand === "help") {
189
+ showHelp(mode);
190
+ return;
191
+ }
192
+
71
193
  if (options.verbose) {
72
194
  setVerboseMode(true);
73
195
  }
74
196
 
75
- const shouldWrite = options.write ?? (options.dryRun === false);
197
+ const shouldWrite = options.write ?? options.dryRun === false;
198
+ const treatAsPlan = subcommand === "plan";
76
199
 
77
- // Migrate config file before anything reads it
78
- await migrateConfigFile();
79
-
80
- const project = await Project.fromCwd({ requireProjectType: false });
81
- const context = new FormatContext();
82
- const planner = new FormatPlanner();
200
+ if (mode.json && shouldWrite) {
201
+ printJson({
202
+ ok: false,
203
+ error:
204
+ "JSON output is only supported for read-only format planning. Use `gitzone format plan --json` or omit `--json` when applying changes.",
205
+ });
206
+ return;
207
+ }
83
208
 
84
- const smartconfigInstance = new plugins.smartconfig.Smartconfig();
85
- const formatConfig = smartconfigInstance.dataFor<any>('@git.zone/cli.format', {
86
- interactive: true,
87
- showDiffs: false,
88
- autoApprove: false,
89
- modules: {
90
- skip: [],
91
- only: [],
92
- },
93
- });
209
+ // Migrate config file before anything reads it
210
+ await migrateConfigFile(shouldWrite);
94
211
 
95
- const interactive = options.interactive ?? formatConfig.interactive;
212
+ const formatConfig = await getFormatConfig();
213
+ const interactive =
214
+ options.interactive ?? (mode.interactive && formatConfig.interactive);
96
215
  const autoApprove = options.yes ?? formatConfig.autoApprove;
97
216
 
98
217
  try {
99
- // Initialize formatters in execution order
100
- const formatters = Object.entries(formatterMap).map(
101
- ([, FormatterClass]) => new FormatterClass(context, project),
102
- );
103
-
104
- // Filter formatters based on configuration
105
- const activeFormatters = formatters.filter((formatter) => {
106
- if (formatConfig.modules.only.length > 0) {
107
- return formatConfig.modules.only.includes(formatter.name);
108
- }
109
- if (formatConfig.modules.skip.includes(formatter.name)) {
110
- return false;
111
- }
112
- return true;
113
- });
218
+ const planBuilder = async () => {
219
+ return await buildFormatPlan({
220
+ fromPlan: options.fromPlan,
221
+ interactive,
222
+ jsonOutput: mode.json,
223
+ });
224
+ };
114
225
 
115
- // Plan phase
116
- logger.log('info', 'Analyzing project for format operations...');
117
- let plan = options.fromPlan
118
- ? JSON.parse(
119
- (await plugins.smartfs
120
- .file(options.fromPlan)
121
- .encoding('utf8')
122
- .read()) as string,
123
- )
124
- : await planner.planFormat(activeFormatters);
226
+ if (!mode.json) {
227
+ logger.log("info", "Analyzing project for format operations...");
228
+ }
229
+ const { context, planner, activeFormatters, plan } = mode.json
230
+ ? await runWithSuppressedOutput(planBuilder)
231
+ : await planBuilder();
232
+
233
+ if (mode.json) {
234
+ printJson(serializePlan(plan));
235
+ return;
236
+ }
125
237
 
126
238
  // Display plan
127
239
  await planner.displayPlan(plan, options.detailed);
@@ -130,34 +242,35 @@ export let run = async (
130
242
  if (options.savePlan) {
131
243
  await plugins.smartfs
132
244
  .file(options.savePlan)
133
- .encoding('utf8')
245
+ .encoding("utf8")
134
246
  .write(JSON.stringify(plan, null, 2));
135
- logger.log('info', `Plan saved to ${options.savePlan}`);
247
+ logger.log("info", `Plan saved to ${options.savePlan}`);
136
248
  }
137
249
 
138
- if (options.planOnly) {
250
+ if (options.planOnly || treatAsPlan) {
139
251
  return;
140
252
  }
141
253
 
142
254
  // Show diffs if explicitly requested or before interactive write confirmation
143
- const showDiffs = options.diff || (shouldWrite && interactive && !autoApprove);
255
+ const showDiffs =
256
+ options.diff || (shouldWrite && interactive && !autoApprove);
144
257
  if (showDiffs) {
145
- logger.log('info', 'Showing file diffs:');
146
- console.log('');
258
+ logger.log("info", "Showing file diffs:");
259
+ console.log("");
147
260
 
148
261
  for (const formatter of activeFormatters) {
149
262
  const checkResult = await formatter.check();
150
263
  if (checkResult.hasDiff) {
151
- logger.log('info', `[${formatter.name}]`);
264
+ logger.log("info", `[${formatter.name}]`);
152
265
  formatter.displayAllDiffs(checkResult);
153
- console.log('');
266
+ console.log("");
154
267
  }
155
268
  }
156
269
  }
157
270
 
158
271
  // Dry-run mode (default behavior)
159
272
  if (!shouldWrite) {
160
- logger.log('info', 'Dry-run mode - use --write (-w) to apply changes');
273
+ logger.log("info", "Dry-run mode - use --write (-w) to apply changes");
161
274
  return;
162
275
  }
163
276
 
@@ -165,25 +278,25 @@ export let run = async (
165
278
  if (interactive && !autoApprove) {
166
279
  const interactInstance = new plugins.smartinteract.SmartInteract();
167
280
  const response = await interactInstance.askQuestion({
168
- type: 'confirm',
169
- name: 'proceed',
170
- message: 'Proceed with formatting?',
281
+ type: "confirm",
282
+ name: "proceed",
283
+ message: "Proceed with formatting?",
171
284
  default: true,
172
285
  });
173
286
 
174
287
  if (!(response as any).value) {
175
- logger.log('info', 'Format operation cancelled by user');
288
+ logger.log("info", "Format operation cancelled by user");
176
289
  return;
177
290
  }
178
291
  }
179
292
 
180
293
  // Execute phase
181
- logger.log('info', 'Executing format operations...');
294
+ logger.log("info", "Executing format operations...");
182
295
  await planner.executePlan(plan, activeFormatters, context);
183
296
 
184
297
  context.getFormatStats().finish();
185
298
 
186
- const showStats = smartconfigInstance.dataFor('gitzone.format.showStats', true);
299
+ const showStats = formatConfig.showStats ?? true;
187
300
  if (showStats) {
188
301
  context.getFormatStats().displayStats();
189
302
  }
@@ -193,14 +306,15 @@ export let run = async (
193
306
  await context.getFormatStats().saveReport(statsPath);
194
307
  }
195
308
 
196
- logger.log('success', 'Format operations completed successfully!');
309
+ logger.log("success", "Format operations completed successfully!");
197
310
  } catch (error) {
198
- logger.log('error', `Format operation failed: ${error.message}`);
311
+ const errorMessage = error instanceof Error ? error.message : String(error);
312
+ logger.log("error", `Format operation failed: ${errorMessage}`);
199
313
  throw error;
200
314
  }
201
315
  };
202
316
 
203
- import type { ICheckResult } from './interfaces.format.js';
317
+ import type { ICheckResult } from "./interfaces.format.js";
204
318
  export type { ICheckResult };
205
319
 
206
320
  /**
@@ -212,11 +326,12 @@ export const runFormatter = async (
212
326
  silent?: boolean;
213
327
  checkOnly?: boolean;
214
328
  showDiff?: boolean;
215
- } = {}
329
+ } = {},
216
330
  ): Promise<ICheckResult | void> => {
217
- const requireProjectType = !formattersNotRequiringProjectType.includes(formatterName);
331
+ const requireProjectType =
332
+ !formattersNotRequiringProjectType.includes(formatterName);
218
333
  const project = await Project.fromCwd({ requireProjectType });
219
- const context = new FormatContext();
334
+ const context = new FormatContext({ interactive: true, jsonOutput: false });
220
335
 
221
336
  const FormatterClass = formatterMap[formatterName];
222
337
  if (!FormatterClass) {
@@ -240,6 +355,80 @@ export const runFormatter = async (
240
355
  }
241
356
 
242
357
  if (!options.silent) {
243
- logger.log('success', `Formatter '${formatterName}' completed`);
358
+ logger.log("success", `Formatter '${formatterName}' completed`);
244
359
  }
245
360
  };
361
+
362
+ export function showHelp(mode?: ICliMode): void {
363
+ if (mode?.json) {
364
+ printJson({
365
+ command: "format",
366
+ usage: "gitzone format [plan] [options]",
367
+ description:
368
+ "Plans formatting changes by default and applies them only with --write.",
369
+ flags: [
370
+ { flag: "--write, -w", description: "Apply planned changes" },
371
+ {
372
+ flag: "--yes",
373
+ description: "Skip the interactive confirmation before writing",
374
+ },
375
+ {
376
+ flag: "--plan-only",
377
+ description: "Show the plan without applying changes",
378
+ },
379
+ {
380
+ flag: "--save-plan <file>",
381
+ description: "Write the format plan to a file",
382
+ },
383
+ {
384
+ flag: "--from-plan <file>",
385
+ description: "Load a previously saved plan",
386
+ },
387
+ {
388
+ flag: "--detailed",
389
+ description: "Show detailed diffs and save stats",
390
+ },
391
+ { flag: "--verbose", description: "Enable verbose logging" },
392
+ {
393
+ flag: "--diff",
394
+ description: "Show per-file diffs before applying changes",
395
+ },
396
+ { flag: "--json", description: "Emit a read-only format plan as JSON" },
397
+ ],
398
+ examples: [
399
+ "gitzone format",
400
+ "gitzone format plan --json",
401
+ "gitzone format --write --yes",
402
+ ],
403
+ });
404
+ return;
405
+ }
406
+
407
+ console.log("");
408
+ console.log("Usage: gitzone format [plan] [options]");
409
+ console.log("");
410
+ console.log(
411
+ "Plans formatting changes by default and applies them only with --write.",
412
+ );
413
+ console.log("");
414
+ console.log("Flags:");
415
+ console.log(" --write, -w Apply planned changes");
416
+ console.log(
417
+ " --yes Skip the interactive confirmation before writing",
418
+ );
419
+ console.log(" --plan-only Show the plan without applying changes");
420
+ console.log(" --save-plan <file> Write the format plan to a file");
421
+ console.log(" --from-plan <file> Load a previously saved plan");
422
+ console.log(" --detailed Show detailed diffs and save stats");
423
+ console.log(" --verbose Enable verbose logging");
424
+ console.log(
425
+ " --diff Show per-file diffs before applying changes",
426
+ );
427
+ console.log(" --json Emit a read-only format plan as JSON");
428
+ console.log("");
429
+ console.log("Examples:");
430
+ console.log(" gitzone format");
431
+ console.log(" gitzone format plan --json");
432
+ console.log(" gitzone format --write --yes");
433
+ console.log("");
434
+ }
@@ -5,7 +5,6 @@ import * as smartfile from '@push.rocks/smartfile';
5
5
  import * as smartinteract from '@push.rocks/smartinteract';
6
6
  import * as smartlegal from '@push.rocks/smartlegal';
7
7
  import * as smartobject from '@push.rocks/smartobject';
8
- import * as smartnpm from '@push.rocks/smartnpm';
9
8
  import * as smartconfig from '@push.rocks/smartconfig';
10
9
  import * as smartdiff from '@push.rocks/smartdiff';
11
10
  import * as smartscaf from '@push.rocks/smartscaf';
@@ -16,7 +15,6 @@ export {
16
15
  smartinteract,
17
16
  smartlegal,
18
17
  smartobject,
19
- smartnpm,
20
18
  smartconfig,
21
19
  smartdiff,
22
20
  smartscaf,