@git.zone/cli 2.19.0 → 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/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/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/gitzone.cli.ts
CHANGED
|
@@ -1,13 +1,107 @@
|
|
|
1
1
|
import * as plugins from "./plugins.js";
|
|
2
2
|
import * as paths from "./paths.js";
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import {
|
|
4
|
+
getProcessUserArgv,
|
|
5
|
+
getRawCliMode,
|
|
6
|
+
parseCliArgv,
|
|
7
|
+
} from "./helpers.climode.js";
|
|
5
8
|
import { commitinfo } from "./00_commitinfo_data.js";
|
|
6
9
|
|
|
7
|
-
const
|
|
10
|
+
const runParsedCommand = async (argvArg: any): Promise<void> => {
|
|
11
|
+
const command = argvArg._?.[0];
|
|
12
|
+
|
|
13
|
+
switch (command) {
|
|
14
|
+
case undefined:
|
|
15
|
+
case "help": {
|
|
16
|
+
const modStandard = await import("./mod_standard/index.js");
|
|
17
|
+
await modStandard.run(argvArg);
|
|
18
|
+
break;
|
|
19
|
+
}
|
|
20
|
+
case "commit": {
|
|
21
|
+
const modCommit = await import("./mod_commit/index.js");
|
|
22
|
+
await modCommit.run(argvArg);
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
case "release": {
|
|
26
|
+
const modRelease = await import("./mod_release/index.js");
|
|
27
|
+
await modRelease.run(argvArg);
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
case "deprecate": {
|
|
31
|
+
const modDeprecate = await import("./mod_deprecate/index.js");
|
|
32
|
+
await modDeprecate.run();
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
case "docker": {
|
|
36
|
+
const modDocker = await import("./mod_docker/index.js");
|
|
37
|
+
await modDocker.run(argvArg);
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
case "format": {
|
|
41
|
+
const modFormat = await import("./mod_format/index.js");
|
|
42
|
+
await modFormat.run({
|
|
43
|
+
...argvArg,
|
|
44
|
+
write: argvArg.write || argvArg.w,
|
|
45
|
+
dryRun: argvArg["dry-run"],
|
|
46
|
+
yes: argvArg.yes || argvArg.y,
|
|
47
|
+
planOnly: argvArg["plan-only"] || argvArg.planOnly,
|
|
48
|
+
savePlan: argvArg["save-plan"] || argvArg.savePlan,
|
|
49
|
+
fromPlan: argvArg["from-plan"] || argvArg.fromPlan,
|
|
50
|
+
detailed: argvArg.detailed,
|
|
51
|
+
interactive: argvArg.interactive !== false,
|
|
52
|
+
verbose: argvArg.verbose,
|
|
53
|
+
diff: argvArg.diff,
|
|
54
|
+
});
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
case "meta": {
|
|
58
|
+
const modMeta = await import("./mod_meta/index.js");
|
|
59
|
+
await modMeta.run(argvArg);
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
case "open": {
|
|
63
|
+
const modOpen = await import("./mod_open/index.js");
|
|
64
|
+
await modOpen.run(argvArg);
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
case "template": {
|
|
68
|
+
const modTemplate = await import("./mod_template/index.js");
|
|
69
|
+
await modTemplate.run(argvArg);
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
case "start": {
|
|
73
|
+
const modStart = await import("./mod_start/index.js");
|
|
74
|
+
await modStart.run(argvArg);
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
case "helpers": {
|
|
78
|
+
const modHelpers = await import("./mod_helpers/index.js");
|
|
79
|
+
await modHelpers.run(argvArg);
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
case "tools": {
|
|
83
|
+
const modTools = await import("./mod_tools/index.js");
|
|
84
|
+
await modTools.run(argvArg);
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
case "config": {
|
|
88
|
+
const modConfig = await import("./mod_config/index.js");
|
|
89
|
+
await modConfig.run(argvArg);
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
case "services": {
|
|
93
|
+
const modServices = await import("./mod_services/index.js");
|
|
94
|
+
await modServices.run(argvArg);
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
default: {
|
|
98
|
+
const modStandard = await import("./mod_standard/index.js");
|
|
99
|
+
await modStandard.run(argvArg);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
};
|
|
8
103
|
|
|
9
104
|
export let run = async () => {
|
|
10
|
-
const done = plugins.smartpromise.defer();
|
|
11
105
|
const rawCliMode = await getRawCliMode();
|
|
12
106
|
|
|
13
107
|
// get packageInfo
|
|
@@ -34,144 +128,10 @@ export let run = async () => {
|
|
|
34
128
|
if (rawCliMode.output === "human") {
|
|
35
129
|
console.log("---------------------------------------------");
|
|
36
130
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
*/
|
|
44
|
-
gitzoneSmartcli.standardCommand().subscribe(async (argvArg) => {
|
|
45
|
-
const modStandard = await import("./mod_standard/index.js");
|
|
46
|
-
await modStandard.run(argvArg);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
gitzoneSmartcli.addCommand("help").subscribe(async (argvArg) => {
|
|
50
|
-
const modStandard = await import("./mod_standard/index.js");
|
|
51
|
-
await modStandard.run(argvArg);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
// ======> Specific tasks <======
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* commit something
|
|
58
|
-
*/
|
|
59
|
-
gitzoneSmartcli.addCommand("commit").subscribe(async (argvArg) => {
|
|
60
|
-
const modCommit = await import("./mod_commit/index.js");
|
|
61
|
-
await modCommit.run(argvArg);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* create a release from pending changelog entries
|
|
66
|
-
*/
|
|
67
|
-
gitzoneSmartcli.addCommand("release").subscribe(async (argvArg) => {
|
|
68
|
-
const modRelease = await import("./mod_release/index.js");
|
|
69
|
-
await modRelease.run(argvArg);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* deprecate a package on npm
|
|
74
|
-
*/
|
|
75
|
-
gitzoneSmartcli.addCommand("deprecate").subscribe(async (argvArg) => {
|
|
76
|
-
const modDeprecate = await import("./mod_deprecate/index.js");
|
|
77
|
-
await modDeprecate.run();
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* docker
|
|
82
|
-
*/
|
|
83
|
-
gitzoneSmartcli.addCommand("docker").subscribe(async (argvArg) => {
|
|
84
|
-
const modDocker = await import("./mod_docker/index.js");
|
|
85
|
-
await modDocker.run(argvArg);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Update all files that comply with the gitzone standard
|
|
90
|
-
*/
|
|
91
|
-
gitzoneSmartcli.addCommand("format").subscribe(async (argvArg) => {
|
|
92
|
-
const config = GitzoneConfig.fromCwd();
|
|
93
|
-
const modFormat = await import("./mod_format/index.js");
|
|
94
|
-
|
|
95
|
-
// Handle format with options
|
|
96
|
-
// Default is dry-mode, use --write/-w to apply changes
|
|
97
|
-
await modFormat.run({
|
|
98
|
-
...argvArg,
|
|
99
|
-
write: argvArg.write || argvArg.w,
|
|
100
|
-
dryRun: argvArg["dry-run"],
|
|
101
|
-
yes: argvArg.yes,
|
|
102
|
-
planOnly: argvArg["plan-only"],
|
|
103
|
-
savePlan: argvArg["save-plan"],
|
|
104
|
-
fromPlan: argvArg["from-plan"],
|
|
105
|
-
detailed: argvArg.detailed,
|
|
106
|
-
interactive: argvArg.interactive !== false,
|
|
107
|
-
verbose: argvArg.verbose,
|
|
108
|
-
diff: argvArg.diff,
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* run meta commands
|
|
114
|
-
*/
|
|
115
|
-
gitzoneSmartcli.addCommand("meta").subscribe(async (argvArg) => {
|
|
116
|
-
const config = GitzoneConfig.fromCwd();
|
|
117
|
-
const modMeta = await import("./mod_meta/index.js");
|
|
118
|
-
modMeta.run(argvArg);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* open assets
|
|
123
|
-
*/
|
|
124
|
-
gitzoneSmartcli.addCommand("open").subscribe(async (argvArg) => {
|
|
125
|
-
const modOpen = await import("./mod_open/index.js");
|
|
126
|
-
modOpen.run(argvArg);
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* add a readme to a project
|
|
131
|
-
*/
|
|
132
|
-
gitzoneSmartcli.addCommand("template").subscribe(async (argvArg) => {
|
|
133
|
-
const modTemplate = await import("./mod_template/index.js");
|
|
134
|
-
modTemplate.run(argvArg);
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* start working on a project
|
|
139
|
-
*/
|
|
140
|
-
gitzoneSmartcli.addCommand("start").subscribe(async (argvArg) => {
|
|
141
|
-
const modTemplate = await import("./mod_start/index.js");
|
|
142
|
-
modTemplate.run(argvArg);
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
gitzoneSmartcli.addCommand("helpers").subscribe(async (argvArg) => {
|
|
146
|
-
const modHelpers = await import("./mod_helpers/index.js");
|
|
147
|
-
modHelpers.run(argvArg);
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* manage the global @git.zone toolchain
|
|
152
|
-
*/
|
|
153
|
-
gitzoneSmartcli.addCommand("tools").subscribe(async (argvArg) => {
|
|
154
|
-
const modTools = await import("./mod_tools/index.js");
|
|
155
|
-
await modTools.run(argvArg);
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* manage release configuration
|
|
160
|
-
*/
|
|
161
|
-
gitzoneSmartcli.addCommand("config").subscribe(async (argvArg) => {
|
|
162
|
-
const modConfig = await import("./mod_config/index.js");
|
|
163
|
-
await modConfig.run(argvArg);
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* manage development services (MongoDB, S3/MinIO)
|
|
168
|
-
*/
|
|
169
|
-
gitzoneSmartcli.addCommand("services").subscribe(async (argvArg) => {
|
|
170
|
-
const modServices = await import("./mod_services/index.js");
|
|
171
|
-
await modServices.run(argvArg);
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
// start parsing of the cli
|
|
175
|
-
gitzoneSmartcli.startParse();
|
|
176
|
-
return await done.promise;
|
|
131
|
+
const argvArg = parseCliArgv(getProcessUserArgv());
|
|
132
|
+
if (argvArg.v || argvArg.version) {
|
|
133
|
+
console.log(packageVersion);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
await runParsedCommand(argvArg);
|
|
177
137
|
};
|
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
|
}
|