@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.
- 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 +34 -2
- package/dist_ts/mod_config/index.js +51 -10
- 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_standard/index.js +2 -1
- package/dist_ts/mod_tools/classes.packagemanager.d.ts +36 -1
- package/dist_ts/mod_tools/classes.packagemanager.js +541 -34
- package/dist_ts/mod_tools/index.js +128 -26
- package/dist_ts/plugins.d.ts +2 -1
- package/dist_ts/plugins.js +3 -2
- package/package.json +1 -1
- package/readme.hints.md +10 -0
- package/readme.md +11 -0
- 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 +37 -1
- package/ts/mod_config/index.ts +60 -9
- 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_standard/index.ts +1 -0
- package/ts/mod_tools/classes.packagemanager.ts +724 -34
- package/ts/mod_tools/index.ts +225 -45
- package/ts/plugins.ts +2 -0
package/ts/helpers.climode.ts
CHANGED
|
@@ -88,6 +88,41 @@ const parseRawArgv = (argv: string[]): TArgSource => {
|
|
|
88
88
|
return parsedArgv;
|
|
89
89
|
};
|
|
90
90
|
|
|
91
|
+
export const parseCliArgv = parseRawArgv;
|
|
92
|
+
|
|
93
|
+
export const getProcessUserArgv = (): string[] => {
|
|
94
|
+
const rawArgv = process.argv;
|
|
95
|
+
const argv0Base = (rawArgv[0] || "").split(/[\\/]/).pop()?.toLowerCase();
|
|
96
|
+
const runtimeNames = new Set([
|
|
97
|
+
"node",
|
|
98
|
+
"node.exe",
|
|
99
|
+
"nodejs",
|
|
100
|
+
"nodejs.exe",
|
|
101
|
+
"bun",
|
|
102
|
+
"bun.exe",
|
|
103
|
+
"deno",
|
|
104
|
+
"deno.exe",
|
|
105
|
+
"tsx",
|
|
106
|
+
"tsx.exe",
|
|
107
|
+
"ts-node",
|
|
108
|
+
"ts-node.exe",
|
|
109
|
+
]);
|
|
110
|
+
|
|
111
|
+
if (!runtimeNames.has(argv0Base || "")) {
|
|
112
|
+
return rawArgv.slice();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const firstUserArg = rawArgv[1] || "";
|
|
116
|
+
const firstUserArgLooksLikeScript =
|
|
117
|
+
firstUserArg.includes("/") ||
|
|
118
|
+
firstUserArg.endsWith(".js") ||
|
|
119
|
+
firstUserArg.endsWith(".ts") ||
|
|
120
|
+
firstUserArg.endsWith(".mjs") ||
|
|
121
|
+
firstUserArg.endsWith(".cjs");
|
|
122
|
+
|
|
123
|
+
return rawArgv.slice(firstUserArgLooksLikeScript ? 2 : 1);
|
|
124
|
+
};
|
|
125
|
+
|
|
91
126
|
const normalizeOutputMode = (value: unknown): TCliOutputMode | undefined => {
|
|
92
127
|
if (value === "human" || value === "plain" || value === "json") {
|
|
93
128
|
return value;
|
|
@@ -171,7 +206,7 @@ export const getCliMode = async (
|
|
|
171
206
|
|
|
172
207
|
export const getRawCliMode = async (): Promise<ICliMode> => {
|
|
173
208
|
const cliConfig = await getCliModeConfig();
|
|
174
|
-
const rawArgv = parseRawArgv(
|
|
209
|
+
const rawArgv = parseRawArgv(getProcessUserArgv());
|
|
175
210
|
return resolveCliMode(rawArgv, cliConfig);
|
|
176
211
|
};
|
|
177
212
|
|
|
@@ -19,6 +19,38 @@ const ensureObject = (parent: Record<string, any>, key: string): Record<string,
|
|
|
19
19
|
return parent[key];
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
+
const normalizeRegistryList = (registries: unknown[]): string[] => {
|
|
23
|
+
const result: string[] = [];
|
|
24
|
+
for (const registry of registries) {
|
|
25
|
+
if (typeof registry !== "string" || !registry.trim()) {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
const normalizedRegistry = normalizeRegistryUrl(registry);
|
|
29
|
+
if (!result.includes(normalizedRegistry)) {
|
|
30
|
+
result.push(normalizedRegistry);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const migrateLegacyReleaseArray = (smartconfigJson: Record<string, any>): boolean => {
|
|
37
|
+
const cliConfig = ensureObject(smartconfigJson, CLI_NAMESPACE);
|
|
38
|
+
if (!Array.isArray(cliConfig.release)) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const registries = normalizeRegistryList(cliConfig.release);
|
|
43
|
+
cliConfig.release = {
|
|
44
|
+
targets: {
|
|
45
|
+
npm: {
|
|
46
|
+
enabled: registries.length > 0,
|
|
47
|
+
registries,
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
return true;
|
|
52
|
+
};
|
|
53
|
+
|
|
22
54
|
const migrateNamespaceKeys = (smartconfigJson: Record<string, any>): boolean => {
|
|
23
55
|
let migrated = false;
|
|
24
56
|
const migrations = [
|
|
@@ -50,9 +82,9 @@ const migrateNamespaceKeys = (smartconfigJson: Record<string, any>): boolean =>
|
|
|
50
82
|
|
|
51
83
|
const migrateToV2 = (smartconfigJson: Record<string, any>): boolean => {
|
|
52
84
|
const cliConfig = ensureObject(smartconfigJson, CLI_NAMESPACE);
|
|
85
|
+
let migrated = migrateLegacyReleaseArray(smartconfigJson);
|
|
53
86
|
const releaseConfig = ensureObject(cliConfig, "release");
|
|
54
87
|
|
|
55
|
-
let migrated = false;
|
|
56
88
|
const targets = ensureObject(releaseConfig, "targets");
|
|
57
89
|
const shipzoneConfig = smartconfigJson["@ship.zone/szci"];
|
|
58
90
|
|
|
@@ -192,6 +224,10 @@ export const migrateSmartconfigData = (
|
|
|
192
224
|
const fromVersion = typeof cliConfig.schemaVersion === "number" ? cliConfig.schemaVersion : 1;
|
|
193
225
|
let currentVersion = fromVersion;
|
|
194
226
|
|
|
227
|
+
if (targetVersion >= 2) {
|
|
228
|
+
migrated = migrateLegacyReleaseArray(smartconfigJson) || migrated;
|
|
229
|
+
}
|
|
230
|
+
|
|
195
231
|
if (currentVersion < 2 && targetVersion >= 2) {
|
|
196
232
|
migrated = migrateToV2(smartconfigJson) || migrated;
|
|
197
233
|
currentVersion = 2;
|
package/ts/mod_config/index.ts
CHANGED
|
@@ -168,7 +168,7 @@ async function handleInteractiveMenu(): Promise<void> {
|
|
|
168
168
|
{ name: "Configure release workflow", value: "release" },
|
|
169
169
|
{ name: "Configure services", value: "services" },
|
|
170
170
|
{ name: "Validate configuration (doctor)", value: "doctor" },
|
|
171
|
-
{ name: "Fix configuration
|
|
171
|
+
{ name: "Fix configuration", value: "fix" },
|
|
172
172
|
{ name: "Add an npm target registry", value: "add" },
|
|
173
173
|
{ name: "Remove an npm target registry", value: "remove" },
|
|
174
174
|
{ name: "Clear npm target registries", value: "clear" },
|
|
@@ -939,8 +939,8 @@ async function handleFix(argvArg: any, mode: ICliMode): Promise<void> {
|
|
|
939
939
|
return;
|
|
940
940
|
}
|
|
941
941
|
|
|
942
|
-
|
|
943
|
-
|
|
942
|
+
let findings = await collectDoctorFindings();
|
|
943
|
+
let counts = countDoctorFindings(findings);
|
|
944
944
|
const extraInstructions = (argvArg._?.slice(2).join(" ") || "").trim();
|
|
945
945
|
const force = Boolean(argvArg.force);
|
|
946
946
|
|
|
@@ -954,10 +954,10 @@ async function handleFix(argvArg: any, mode: ICliMode): Promise<void> {
|
|
|
954
954
|
|
|
955
955
|
if (!mode.yes) {
|
|
956
956
|
if (!mode.interactive) {
|
|
957
|
-
throw new Error("Config fix requires an interactive terminal or `-y` to run
|
|
957
|
+
throw new Error("Config fix requires an interactive terminal or `-y` to run non-interactively.");
|
|
958
958
|
}
|
|
959
959
|
const confirmed = await plugins.smartinteract.SmartInteract.getCliConfirmation(
|
|
960
|
-
`Run
|
|
960
|
+
`Run configuration fixes for .smartconfig.json? (${counts.error} error, ${counts.warn} warning)`,
|
|
961
961
|
true,
|
|
962
962
|
);
|
|
963
963
|
if (!confirmed) {
|
|
@@ -966,6 +966,16 @@ async function handleFix(argvArg: any, mode: ICliMode): Promise<void> {
|
|
|
966
966
|
}
|
|
967
967
|
}
|
|
968
968
|
|
|
969
|
+
const appliedKnownFixes = await applyKnownConfigFixes(mode);
|
|
970
|
+
if (appliedKnownFixes) {
|
|
971
|
+
findings = await collectDoctorFindings();
|
|
972
|
+
counts = countDoctorFindings(findings);
|
|
973
|
+
if (counts.error === 0 && counts.warn === 0 && !extraInstructions && !force) {
|
|
974
|
+
printDoctorResult(findings, mode);
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
|
|
969
979
|
const opencodeArgs = [
|
|
970
980
|
"run",
|
|
971
981
|
"--title",
|
|
@@ -1004,6 +1014,33 @@ async function handleFix(argvArg: any, mode: ICliMode): Promise<void> {
|
|
|
1004
1014
|
printDoctorResult(finalFindings, mode);
|
|
1005
1015
|
}
|
|
1006
1016
|
|
|
1017
|
+
async function applyKnownConfigFixes(mode: ICliMode): Promise<boolean> {
|
|
1018
|
+
const smartconfigPath = getSmartconfigPath();
|
|
1019
|
+
if (!(await plugins.smartfs.file(smartconfigPath).exists())) {
|
|
1020
|
+
return false;
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
let smartconfigData: Record<string, any>;
|
|
1024
|
+
try {
|
|
1025
|
+
smartconfigData = await readSmartconfigFile();
|
|
1026
|
+
} catch {
|
|
1027
|
+
return false;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
const result = migrateSmartconfigData(smartconfigData);
|
|
1031
|
+
if (!result.migrated) {
|
|
1032
|
+
return false;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
await writeSmartconfigFile(smartconfigData);
|
|
1036
|
+
plugins.logger.log(
|
|
1037
|
+
"success",
|
|
1038
|
+
`Applied known .smartconfig.json migrations to schema v${result.toVersion}`,
|
|
1039
|
+
);
|
|
1040
|
+
await formatSmartconfigWithDiff(mode);
|
|
1041
|
+
return true;
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1007
1044
|
async function collectDoctorFindings(): Promise<IDoctorFinding[]> {
|
|
1008
1045
|
const findings: IDoctorFinding[] = [];
|
|
1009
1046
|
const smartconfigPath = getSmartconfigPath();
|
|
@@ -1071,7 +1108,7 @@ async function collectDoctorFindings(): Promise<IDoctorFinding[]> {
|
|
|
1071
1108
|
await validateDetectedProjectType(cliConfig, findings);
|
|
1072
1109
|
|
|
1073
1110
|
validateCommitConfig(cliConfig.commit || {}, findings);
|
|
1074
|
-
await validateReleaseConfig(cliConfig.release
|
|
1111
|
+
await validateReleaseConfig(cliConfig.release, smartconfigData, findings);
|
|
1075
1112
|
|
|
1076
1113
|
return findings;
|
|
1077
1114
|
}
|
|
@@ -1570,10 +1607,24 @@ function validateCommitConfig(
|
|
|
1570
1607
|
}
|
|
1571
1608
|
|
|
1572
1609
|
async function validateReleaseConfig(
|
|
1573
|
-
|
|
1610
|
+
rawReleaseConfig: unknown,
|
|
1574
1611
|
smartconfigData: Record<string, any>,
|
|
1575
1612
|
findings: IDoctorFinding[],
|
|
1576
1613
|
): Promise<void> {
|
|
1614
|
+
const releaseConfig = rawReleaseConfig === undefined ? {} : rawReleaseConfig;
|
|
1615
|
+
if (!isPlainObject(releaseConfig)) {
|
|
1616
|
+
findings.push({
|
|
1617
|
+
level: "error",
|
|
1618
|
+
message: `Release config must be an object, found ${
|
|
1619
|
+
Array.isArray(releaseConfig) ? "array" : typeof releaseConfig
|
|
1620
|
+
}`,
|
|
1621
|
+
fix: Array.isArray(releaseConfig)
|
|
1622
|
+
? "Run `gitzone config migrate` to move legacy registry arrays into release.targets.npm.registries."
|
|
1623
|
+
: "Set @git.zone/cli.release to an object or remove it.",
|
|
1624
|
+
});
|
|
1625
|
+
return;
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1577
1628
|
const confirmation = releaseConfig.confirmation;
|
|
1578
1629
|
if (confirmation === undefined || validConfirmationModes.includes(confirmation)) {
|
|
1579
1630
|
findings.push({ level: "ok", message: "Release confirmation mode is valid" });
|
|
@@ -1993,7 +2044,7 @@ export function showHelp(mode?: ICliMode): void {
|
|
|
1993
2044
|
{ name: "cli", description: "Configure CLI behavior interactively" },
|
|
1994
2045
|
{ name: "release", description: "Configure release workflow interactively" },
|
|
1995
2046
|
{ name: "doctor", description: "Validate .smartconfig.json" },
|
|
1996
|
-
{ name: "fix [instructions]", description: "
|
|
2047
|
+
{ name: "fix [instructions]", description: "Repair .smartconfig.json" },
|
|
1997
2048
|
{ name: "get <path>", description: "Read a single config value" },
|
|
1998
2049
|
{ name: "set <path> <value>", description: "Write a config value" },
|
|
1999
2050
|
{ name: "unset <path>", description: "Delete a config value" },
|
|
@@ -2038,7 +2089,7 @@ export function showHelp(mode?: ICliMode): void {
|
|
|
2038
2089
|
console.log(" cli Configure CLI behavior interactively");
|
|
2039
2090
|
console.log(" release Configure release workflow interactively");
|
|
2040
2091
|
console.log(" doctor Validate .smartconfig.json");
|
|
2041
|
-
console.log(" fix [instructions]
|
|
2092
|
+
console.log(" fix [instructions] Repair .smartconfig.json");
|
|
2042
2093
|
console.log(" get <path> Read a single config value");
|
|
2043
2094
|
console.log(" set <path> <value> Write a config value");
|
|
2044
2095
|
console.log(" unset <path> Delete a config value");
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import * as plugins from './mod.plugins.js';
|
|
2
2
|
import { FormatContext } from './classes.formatcontext.js';
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
IPlannedChange,
|
|
5
|
+
ICheckResult,
|
|
6
|
+
IFormatWarning,
|
|
7
|
+
} from './interfaces.format.js';
|
|
4
8
|
import { Project } from '../classes.project.js';
|
|
5
9
|
import { FormatStats } from './classes.formatstats.js';
|
|
6
10
|
|
|
@@ -19,6 +23,14 @@ export abstract class BaseFormatter {
|
|
|
19
23
|
abstract analyze(): Promise<IPlannedChange[]>;
|
|
20
24
|
abstract applyChange(change: IPlannedChange): Promise<void>;
|
|
21
25
|
|
|
26
|
+
get runsWithoutChanges(): boolean {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async validate(): Promise<IFormatWarning[]> {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
|
|
22
34
|
async execute(changes: IPlannedChange[]): Promise<void> {
|
|
23
35
|
const startTime = this.stats.moduleStartTime(this.name);
|
|
24
36
|
this.stats.startModule(this.name);
|
|
@@ -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
|
}
|