@git.zone/cli 2.18.1 → 2.19.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.
- package/.smartconfig.json +2 -1
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/gitzone.cli.d.ts +1 -1
- package/dist_ts/gitzone.cli.js +100 -126
- package/dist_ts/helpers.climode.d.ts +2 -0
- package/dist_ts/helpers.climode.js +31 -2
- package/dist_ts/helpers.smartconfigmigrations.js +53 -4
- package/dist_ts/helpers.workflow.d.ts +12 -2
- package/dist_ts/helpers.workflow.js +9 -2
- package/dist_ts/mod_config/index.js +254 -29
- package/dist_ts/mod_format/classes.baseformatter.d.ts +3 -1
- package/dist_ts/mod_format/classes.baseformatter.js +7 -1
- package/dist_ts/mod_format/classes.formatplanner.d.ts +2 -0
- package/dist_ts/mod_format/classes.formatplanner.js +48 -4
- package/dist_ts/mod_format/formatters/license.formatter.d.ts +4 -1
- package/dist_ts/mod_format/formatters/license.formatter.js +32 -10
- package/dist_ts/mod_format/formatters/prettier.formatter.d.ts +0 -3
- package/dist_ts/mod_format/formatters/prettier.formatter.js +53 -62
- package/dist_ts/mod_format/index.d.ts +1 -1
- package/dist_ts/mod_format/index.js +237 -8
- package/dist_ts/mod_format/interfaces.format.d.ts +7 -11
- package/dist_ts/mod_format/interfaces.format.js +1 -1
- package/dist_ts/mod_release/index.js +50 -23
- package/dist_ts/mod_standard/index.js +2 -1
- package/package.json +1 -1
- package/readme.hints.md +10 -0
- package/readme.md +31 -4
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/gitzone.cli.ts +104 -144
- package/ts/helpers.climode.ts +36 -1
- package/ts/helpers.smartconfigmigrations.ts +57 -3
- package/ts/helpers.workflow.ts +20 -3
- package/ts/mod_config/index.ts +278 -29
- package/ts/mod_format/classes.baseformatter.ts +13 -1
- package/ts/mod_format/classes.formatplanner.ts +66 -4
- package/ts/mod_format/formatters/license.formatter.ts +43 -15
- package/ts/mod_format/formatters/prettier.formatter.ts +54 -66
- package/ts/mod_format/index.ts +289 -8
- package/ts/mod_format/interfaces.format.ts +8 -11
- package/ts/mod_release/index.ts +46 -23
- package/ts/mod_standard/index.ts +1 -0
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import * as plugins from './mod.plugins.js';
|
|
2
2
|
import { FormatContext } from './classes.formatcontext.js';
|
|
3
3
|
import { BaseFormatter } from './classes.baseformatter.js';
|
|
4
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
IFormatPlan,
|
|
6
|
+
IPlannedChange,
|
|
7
|
+
IFormatWarning,
|
|
8
|
+
} from './interfaces.format.js';
|
|
5
9
|
import { getModuleIcon } from './interfaces.format.js';
|
|
6
10
|
import { logger } from '../gitzone.logging.js';
|
|
7
11
|
import { DiffReporter } from './classes.diffreporter.js';
|
|
@@ -42,15 +46,21 @@ export class FormatPlanner {
|
|
|
42
46
|
break;
|
|
43
47
|
}
|
|
44
48
|
}
|
|
49
|
+
|
|
50
|
+
const warnings = await module.validate();
|
|
51
|
+
plan.warnings.push(...warnings);
|
|
45
52
|
} catch (error) {
|
|
53
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
46
54
|
plan.warnings.push({
|
|
47
55
|
level: 'error',
|
|
48
|
-
message: `Failed to analyze module ${module.name}: ${
|
|
56
|
+
message: `Failed to analyze module ${module.name}: ${errorMessage}`,
|
|
49
57
|
module: module.name,
|
|
50
58
|
});
|
|
51
59
|
}
|
|
52
60
|
}
|
|
53
61
|
|
|
62
|
+
plan.warnings.push(...this.detectConflictingChanges(plan.changes));
|
|
63
|
+
|
|
54
64
|
plan.summary.totalFiles =
|
|
55
65
|
plan.summary.filesAdded +
|
|
56
66
|
plan.summary.filesModified +
|
|
@@ -65,11 +75,12 @@ export class FormatPlanner {
|
|
|
65
75
|
context: FormatContext,
|
|
66
76
|
): Promise<void> {
|
|
67
77
|
const startTime = Date.now();
|
|
78
|
+
const changesByModule = this.groupChangesByModule(plan.changes);
|
|
68
79
|
|
|
69
80
|
for (const module of modules) {
|
|
70
|
-
const changes =
|
|
81
|
+
const changes = changesByModule.get(module.name) || [];
|
|
71
82
|
|
|
72
|
-
if (changes.length > 0) {
|
|
83
|
+
if (changes.length > 0 || module.runsWithoutChanges) {
|
|
73
84
|
logger.log('info', `Executing ${module.name} formatter...`);
|
|
74
85
|
await module.execute(changes);
|
|
75
86
|
}
|
|
@@ -138,4 +149,55 @@ export class FormatPlanner {
|
|
|
138
149
|
return '❌';
|
|
139
150
|
}
|
|
140
151
|
}
|
|
152
|
+
|
|
153
|
+
private groupChangesByModule(
|
|
154
|
+
changes: IPlannedChange[],
|
|
155
|
+
): Map<string, IPlannedChange[]> {
|
|
156
|
+
const changesByModule = new Map<string, IPlannedChange[]>();
|
|
157
|
+
for (const change of changes) {
|
|
158
|
+
const moduleChanges = changesByModule.get(change.module) || [];
|
|
159
|
+
moduleChanges.push(change);
|
|
160
|
+
changesByModule.set(change.module, moduleChanges);
|
|
161
|
+
}
|
|
162
|
+
return changesByModule;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private detectConflictingChanges(
|
|
166
|
+
changes: IPlannedChange[],
|
|
167
|
+
): IFormatWarning[] {
|
|
168
|
+
const warnings: IFormatWarning[] = [];
|
|
169
|
+
const changesByPath = new Map<string, IPlannedChange[]>();
|
|
170
|
+
|
|
171
|
+
for (const change of changes) {
|
|
172
|
+
if (!change.path || change.path === '<various files>') {
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const pathChanges = changesByPath.get(change.path) || [];
|
|
177
|
+
pathChanges.push(change);
|
|
178
|
+
changesByPath.set(change.path, pathChanges);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
for (const [path, pathChanges] of changesByPath) {
|
|
182
|
+
const modules = [...new Set(pathChanges.map((change) => change.module))];
|
|
183
|
+
if (modules.length < 2) {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const hasDelete = pathChanges.some((change) => change.type === 'delete');
|
|
188
|
+
const plannedContents = pathChanges
|
|
189
|
+
.map((change) => change.content)
|
|
190
|
+
.filter((content): content is string => content !== undefined);
|
|
191
|
+
const uniqueContents = new Set(plannedContents);
|
|
192
|
+
const level = hasDelete || uniqueContents.size > 1 ? 'warning' : 'info';
|
|
193
|
+
|
|
194
|
+
warnings.push({
|
|
195
|
+
level,
|
|
196
|
+
module: 'planner',
|
|
197
|
+
message: `Multiple formatters plan changes for ${path}: ${modules.join(', ')}. They will run in formatter order.`,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return warnings;
|
|
202
|
+
}
|
|
141
203
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BaseFormatter } from '../classes.baseformatter.js';
|
|
2
|
-
import type { IPlannedChange } from '../interfaces.format.js';
|
|
2
|
+
import type { IFormatWarning, IPlannedChange } from '../interfaces.format.js';
|
|
3
3
|
import * as plugins from '../mod.plugins.js';
|
|
4
4
|
import * as paths from '../../paths.js';
|
|
5
5
|
import { logger } from '../../gitzone.logging.js';
|
|
@@ -11,6 +11,10 @@ export class LicenseFormatter extends BaseFormatter {
|
|
|
11
11
|
return 'license';
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
get runsWithoutChanges(): boolean {
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
|
|
14
18
|
async analyze(): Promise<IPlannedChange[]> {
|
|
15
19
|
// License formatter only checks for incompatible licenses
|
|
16
20
|
// It does not modify any files, so return empty array
|
|
@@ -18,29 +22,34 @@ export class LicenseFormatter extends BaseFormatter {
|
|
|
18
22
|
return [];
|
|
19
23
|
}
|
|
20
24
|
|
|
25
|
+
async validate(): Promise<IFormatWarning[]> {
|
|
26
|
+
const result = await this.checkLicenses();
|
|
27
|
+
if (!result || result.failingModules.length === 0) {
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return [
|
|
32
|
+
{
|
|
33
|
+
level: 'error',
|
|
34
|
+
module: this.name,
|
|
35
|
+
message: `License check failed for ${result.failingModules.length} module(s): ${result.failingModules
|
|
36
|
+
.map((failedModule) => `${failedModule.name} (${failedModule.license})`)
|
|
37
|
+
.join(', ')}`,
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
}
|
|
41
|
+
|
|
21
42
|
async execute(changes: IPlannedChange[]): Promise<void> {
|
|
22
43
|
const startTime = this.stats.moduleStartTime(this.name);
|
|
23
44
|
this.stats.startModule(this.name);
|
|
24
45
|
|
|
25
46
|
try {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const nodeModulesExists = await plugins.smartfs
|
|
29
|
-
.directory(nodeModulesPath)
|
|
30
|
-
.exists();
|
|
31
|
-
|
|
32
|
-
if (!nodeModulesExists) {
|
|
47
|
+
const licenseCheckResult = await this.checkLicenses();
|
|
48
|
+
if (!licenseCheckResult) {
|
|
33
49
|
logger.log('warn', 'No node_modules found. Skipping license check');
|
|
34
50
|
return;
|
|
35
51
|
}
|
|
36
52
|
|
|
37
|
-
// Run license check
|
|
38
|
-
const licenseChecker = await plugins.smartlegal.createLicenseChecker();
|
|
39
|
-
const licenseCheckResult = await licenseChecker.excludeLicenseWithinPath(
|
|
40
|
-
paths.cwd,
|
|
41
|
-
INCOMPATIBLE_LICENSES,
|
|
42
|
-
);
|
|
43
|
-
|
|
44
53
|
if (licenseCheckResult.failingModules.length === 0) {
|
|
45
54
|
logger.log('info', 'License check passed - no incompatible licenses found');
|
|
46
55
|
} else {
|
|
@@ -59,4 +68,23 @@ export class LicenseFormatter extends BaseFormatter {
|
|
|
59
68
|
async applyChange(change: IPlannedChange): Promise<void> {
|
|
60
69
|
// No file changes for license formatter
|
|
61
70
|
}
|
|
71
|
+
|
|
72
|
+
private async checkLicenses(): Promise<{
|
|
73
|
+
failingModules: Array<{ name: string; license: string }>;
|
|
74
|
+
} | undefined> {
|
|
75
|
+
const nodeModulesPath = plugins.path.join(paths.cwd, 'node_modules');
|
|
76
|
+
const nodeModulesExists = await plugins.smartfs
|
|
77
|
+
.directory(nodeModulesPath)
|
|
78
|
+
.exists();
|
|
79
|
+
|
|
80
|
+
if (!nodeModulesExists) {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const licenseChecker = await plugins.smartlegal.createLicenseChecker();
|
|
85
|
+
return await licenseChecker.excludeLicenseWithinPath(
|
|
86
|
+
paths.cwd,
|
|
87
|
+
INCOMPATIBLE_LICENSES,
|
|
88
|
+
);
|
|
89
|
+
}
|
|
62
90
|
}
|
|
@@ -56,7 +56,8 @@ export class PrettierFormatter extends BaseFormatter {
|
|
|
56
56
|
);
|
|
57
57
|
allFiles.push(...filteredFiles);
|
|
58
58
|
} catch (error) {
|
|
59
|
-
|
|
59
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
60
|
+
logVerbose(`Skipping directory ${dir}: ${errorMessage}`);
|
|
60
61
|
}
|
|
61
62
|
}
|
|
62
63
|
|
|
@@ -72,7 +73,8 @@ export class PrettierFormatter extends BaseFormatter {
|
|
|
72
73
|
const rootLevelFiles = rootFiles.filter((f) => !f.includes('/'));
|
|
73
74
|
allFiles.push(...rootLevelFiles);
|
|
74
75
|
} catch (error) {
|
|
75
|
-
|
|
76
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
77
|
+
logVerbose(`Skipping pattern ${pattern}: ${errorMessage}`);
|
|
76
78
|
}
|
|
77
79
|
}
|
|
78
80
|
|
|
@@ -89,20 +91,46 @@ export class PrettierFormatter extends BaseFormatter {
|
|
|
89
91
|
}
|
|
90
92
|
} catch (error) {
|
|
91
93
|
// Skip files that can't be accessed
|
|
92
|
-
|
|
94
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
95
|
+
logVerbose(`Skipping ${file} - cannot access: ${errorMessage}`);
|
|
93
96
|
}
|
|
94
97
|
}
|
|
95
98
|
|
|
99
|
+
const prettier = await import('prettier');
|
|
100
|
+
const prettierConfig = await this.getPrettierConfig();
|
|
101
|
+
|
|
96
102
|
for (const file of validFiles) {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
+
try {
|
|
104
|
+
const fileExt = plugins.path.extname(file).toLowerCase();
|
|
105
|
+
if (!fileExt) {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const content = (await plugins.smartfs
|
|
110
|
+
.file(file)
|
|
111
|
+
.encoding('utf8')
|
|
112
|
+
.read()) as string;
|
|
113
|
+
const formatted = await prettier.format(content, {
|
|
114
|
+
filepath: file,
|
|
115
|
+
...prettierConfig,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
if (formatted !== content) {
|
|
119
|
+
changes.push({
|
|
120
|
+
type: 'modify',
|
|
121
|
+
path: file,
|
|
122
|
+
module: this.name,
|
|
123
|
+
description: 'Format with Prettier',
|
|
124
|
+
content: formatted,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
} catch (error) {
|
|
128
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
129
|
+
logVerbose(`Skipping Prettier analysis for ${file}: ${errorMessage}`);
|
|
130
|
+
}
|
|
103
131
|
}
|
|
104
132
|
|
|
105
|
-
logger.log('info', `Found ${changes.length} files
|
|
133
|
+
logger.log('info', `Found ${changes.length} files needing Prettier`);
|
|
106
134
|
return changes;
|
|
107
135
|
}
|
|
108
136
|
|
|
@@ -127,9 +155,10 @@ export class PrettierFormatter extends BaseFormatter {
|
|
|
127
155
|
this.stats.recordFileOperation(this.name, change.type, true);
|
|
128
156
|
} catch (error) {
|
|
129
157
|
this.stats.recordFileOperation(this.name, change.type, false);
|
|
158
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
130
159
|
logger.log(
|
|
131
160
|
'error',
|
|
132
|
-
`Failed to format ${change.path}: ${
|
|
161
|
+
`Failed to format ${change.path}: ${errorMessage}`,
|
|
133
162
|
);
|
|
134
163
|
// Don't throw - continue with other files
|
|
135
164
|
}
|
|
@@ -192,28 +221,32 @@ export class PrettierFormatter extends BaseFormatter {
|
|
|
192
221
|
logVerbose(`No formatting changes for ${change.path}`);
|
|
193
222
|
}
|
|
194
223
|
} catch (prettierError) {
|
|
224
|
+
const prettierErrorMessage = prettierError instanceof Error
|
|
225
|
+
? prettierError.message
|
|
226
|
+
: String(prettierError);
|
|
195
227
|
// Check if it's a parser error
|
|
196
|
-
if (
|
|
197
|
-
|
|
198
|
-
prettierError.message.includes('No parser could be inferred')
|
|
199
|
-
) {
|
|
200
|
-
logVerbose(`Skipping ${change.path} - ${prettierError.message}`);
|
|
228
|
+
if (prettierErrorMessage.includes('No parser could be inferred')) {
|
|
229
|
+
logVerbose(`Skipping ${change.path} - ${prettierErrorMessage}`);
|
|
201
230
|
return; // Skip this file silently
|
|
202
231
|
}
|
|
203
232
|
throw prettierError;
|
|
204
233
|
}
|
|
205
234
|
} catch (error) {
|
|
235
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
236
|
+
const errorStack = error instanceof Error ? error.stack : undefined;
|
|
206
237
|
// Log the full error stack for debugging mkdir issues
|
|
207
|
-
if (
|
|
238
|
+
if (errorMessage.includes('mkdir')) {
|
|
208
239
|
logger.log(
|
|
209
240
|
'error',
|
|
210
|
-
`Failed to format ${change.path}: ${
|
|
241
|
+
`Failed to format ${change.path}: ${errorMessage}`,
|
|
211
242
|
);
|
|
212
|
-
|
|
243
|
+
if (errorStack) {
|
|
244
|
+
logger.log('error', `Error stack: ${errorStack}`);
|
|
245
|
+
}
|
|
213
246
|
} else {
|
|
214
247
|
logger.log(
|
|
215
248
|
'error',
|
|
216
|
-
`Failed to format ${change.path}: ${
|
|
249
|
+
`Failed to format ${change.path}: ${errorMessage}`,
|
|
217
250
|
);
|
|
218
251
|
}
|
|
219
252
|
throw error;
|
|
@@ -234,52 +267,7 @@ export class PrettierFormatter extends BaseFormatter {
|
|
|
234
267
|
});
|
|
235
268
|
}
|
|
236
269
|
|
|
237
|
-
/**
|
|
238
|
-
* Override check() to compute diffs on-the-fly by running prettier
|
|
239
|
-
*/
|
|
240
270
|
async check(): Promise<ICheckResult> {
|
|
241
|
-
|
|
242
|
-
const diffs: ICheckResult['diffs'] = [];
|
|
243
|
-
|
|
244
|
-
for (const change of changes) {
|
|
245
|
-
if (change.type !== 'modify') continue;
|
|
246
|
-
|
|
247
|
-
try {
|
|
248
|
-
// Read current content
|
|
249
|
-
const currentContent = (await plugins.smartfs
|
|
250
|
-
.file(change.path)
|
|
251
|
-
.encoding('utf8')
|
|
252
|
-
.read()) as string;
|
|
253
|
-
|
|
254
|
-
// Skip files without extension (prettier can't infer parser)
|
|
255
|
-
const fileExt = plugins.path.extname(change.path).toLowerCase();
|
|
256
|
-
if (!fileExt) continue;
|
|
257
|
-
|
|
258
|
-
// Format with prettier to get what it would produce
|
|
259
|
-
const prettier = await import('prettier');
|
|
260
|
-
const formatted = await prettier.format(currentContent, {
|
|
261
|
-
filepath: change.path,
|
|
262
|
-
...(await this.getPrettierConfig()),
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
// Only add to diffs if content differs
|
|
266
|
-
if (formatted !== currentContent) {
|
|
267
|
-
diffs.push({
|
|
268
|
-
path: change.path,
|
|
269
|
-
type: 'modify',
|
|
270
|
-
before: currentContent,
|
|
271
|
-
after: formatted,
|
|
272
|
-
});
|
|
273
|
-
}
|
|
274
|
-
} catch (error) {
|
|
275
|
-
// Skip files that can't be processed
|
|
276
|
-
logVerbose(`Skipping diff for ${change.path}: ${error.message}`);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
return {
|
|
281
|
-
hasDiff: diffs.length > 0,
|
|
282
|
-
diffs,
|
|
283
|
-
};
|
|
271
|
+
return await super.check();
|
|
284
272
|
}
|
|
285
273
|
}
|