@git.zone/cli 6.6.2 → 6.7.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.js +2 -2
- package/dist_ts/helpers.workflow.d.ts +3 -0
- package/dist_ts/helpers.workflow.js +14 -1
- package/dist_ts/mod_config/index.js +4 -1
- package/dist_ts/mod_deprecate/helpers.deprecation.d.ts +12 -0
- package/dist_ts/mod_deprecate/helpers.deprecation.js +87 -0
- package/dist_ts/mod_deprecate/index.d.ts +1 -1
- package/dist_ts/mod_deprecate/index.js +77 -40
- package/dist_ts/mod_format/classes.baseformatter.d.ts +6 -0
- package/dist_ts/mod_format/classes.baseformatter.js +61 -1
- package/dist_ts/mod_format/classes.formatplanner.js +14 -3
- package/dist_ts/mod_format/classes.formatstats.d.ts +4 -1
- package/dist_ts/mod_format/classes.formatstats.js +11 -1
- package/dist_ts/mod_format/formatters/readme.formatter.d.ts +2 -1
- package/dist_ts/mod_format/formatters/readme.formatter.js +80 -14
- package/dist_ts/mod_format/index.js +2 -1
- package/dist_ts/mod_format/interfaces.format.d.ts +6 -2
- package/dist_ts/mod_format/interfaces.format.js +1 -1
- package/dist_ts/mod_release/classes.releasejournal.d.ts +17 -3
- package/dist_ts/mod_release/classes.releasejournal.js +144 -12
- package/dist_ts/mod_release/helpers.npmartifact.d.ts +3 -1
- package/dist_ts/mod_release/helpers.npmartifact.js +64 -5
- package/dist_ts/mod_release/helpers.releasepublication.js +21 -19
- package/dist_ts/mod_release/index.d.ts +24 -0
- package/dist_ts/mod_release/index.js +82 -22
- package/dist_ts/plugins.d.ts +2 -1
- package/dist_ts/plugins.js +3 -2
- package/package.json +2 -2
- package/readme.md +72 -3
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/gitzone.cli.ts +1 -1
- package/ts/helpers.workflow.ts +17 -0
- package/ts/mod_config/index.ts +3 -0
- package/ts/mod_deprecate/helpers.deprecation.ts +151 -0
- package/ts/mod_deprecate/index.ts +92 -41
- package/ts/mod_format/classes.baseformatter.ts +71 -0
- package/ts/mod_format/classes.formatplanner.ts +16 -3
- package/ts/mod_format/classes.formatstats.ts +14 -1
- package/ts/mod_format/formatters/readme.formatter.ts +89 -14
- package/ts/mod_format/index.ts +1 -0
- package/ts/mod_format/interfaces.format.ts +7 -2
- package/ts/mod_release/classes.releasejournal.ts +219 -20
- package/ts/mod_release/helpers.npmartifact.ts +120 -23
- package/ts/mod_release/helpers.releasepublication.ts +49 -15
- package/ts/mod_release/index.ts +127 -30
- package/ts/plugins.ts +2 -0
- package/readme.hints.md +0 -596
- package/readme.plan.md +0 -176
package/ts/helpers.workflow.ts
CHANGED
|
@@ -39,6 +39,7 @@ export interface IReleaseGitTargetConfig {
|
|
|
39
39
|
|
|
40
40
|
export interface IReleaseNpmTargetConfig {
|
|
41
41
|
enabled?: boolean;
|
|
42
|
+
packageSource?: "root" | "tspublish";
|
|
42
43
|
registries?: string[];
|
|
43
44
|
accessLevel?: "public" | "private";
|
|
44
45
|
alreadyPublished?: "success" | "error";
|
|
@@ -107,6 +108,7 @@ export interface IResolvedReleaseWorkflow {
|
|
|
107
108
|
pushTags: boolean;
|
|
108
109
|
npmEnabled: boolean;
|
|
109
110
|
npmRegistries: string[];
|
|
111
|
+
npmPackageSource: "root" | "tspublish";
|
|
110
112
|
npmAccessLevel: "public" | "private";
|
|
111
113
|
npmAlreadyPublished: "success" | "error";
|
|
112
114
|
dockerEnabled: boolean;
|
|
@@ -123,6 +125,7 @@ export interface IResolvedReleaseWorkflow {
|
|
|
123
125
|
|
|
124
126
|
export interface IResolvedReleaseResumeConfiguration {
|
|
125
127
|
gitRemote: string;
|
|
128
|
+
npmPackageSource: "root" | "tspublish";
|
|
126
129
|
npmRegistries: string[];
|
|
127
130
|
npmAccessLevel: "public" | "private";
|
|
128
131
|
npmAlreadyPublished: "success" | "error";
|
|
@@ -581,6 +584,14 @@ export const resolveCommitWorkflow = async (
|
|
|
581
584
|
};
|
|
582
585
|
};
|
|
583
586
|
|
|
587
|
+
const resolveNpmPackageSource = (valueArg: unknown): "root" | "tspublish" => {
|
|
588
|
+
if (valueArg === undefined || valueArg === "root") return "root";
|
|
589
|
+
if (valueArg === "tspublish") return valueArg;
|
|
590
|
+
throw new Error(
|
|
591
|
+
"release.targets.npm.packageSource must be root or tspublish",
|
|
592
|
+
);
|
|
593
|
+
};
|
|
594
|
+
|
|
584
595
|
export const resolveReleaseWorkflow = async (
|
|
585
596
|
argvArg: any,
|
|
586
597
|
): Promise<IResolvedReleaseWorkflow> => {
|
|
@@ -690,6 +701,9 @@ export const resolveReleaseWorkflow = async (
|
|
|
690
701
|
pushTags,
|
|
691
702
|
npmEnabled,
|
|
692
703
|
npmRegistries,
|
|
704
|
+
npmPackageSource: targets.includes("npm")
|
|
705
|
+
? resolveNpmPackageSource(npmConfig.packageSource)
|
|
706
|
+
: "root",
|
|
693
707
|
npmAccessLevel: npmConfig.accessLevel || "public",
|
|
694
708
|
npmAlreadyPublished: npmConfig.alreadyPublished || "success",
|
|
695
709
|
dockerEnabled,
|
|
@@ -759,6 +773,9 @@ export const resolveReleaseResumeConfiguration = async (optionsArg: {
|
|
|
759
773
|
return {
|
|
760
774
|
gitRemote,
|
|
761
775
|
npmRegistries,
|
|
776
|
+
npmPackageSource: optionsArg.npm
|
|
777
|
+
? resolveNpmPackageSource(npmConfig.packageSource)
|
|
778
|
+
: "root",
|
|
762
779
|
npmAccessLevel,
|
|
763
780
|
npmAlreadyPublished,
|
|
764
781
|
};
|
package/ts/mod_config/index.ts
CHANGED
|
@@ -2001,6 +2001,9 @@ async function validateNpmTarget(
|
|
|
2001
2001
|
fix: "Use success or error.",
|
|
2002
2002
|
});
|
|
2003
2003
|
}
|
|
2004
|
+
if (npmTarget.packageSource !== undefined && !["root", "tspublish"].includes(npmTarget.packageSource)) {
|
|
2005
|
+
findings.push({ level: "error", message: `Invalid npm package source: ${npmTarget.packageSource}`, fix: "Use root or tspublish." });
|
|
2006
|
+
}
|
|
2004
2007
|
|
|
2005
2008
|
const smartNetwork = new plugins.smartnetwork.SmartNetwork();
|
|
2006
2009
|
await Promise.all(
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import * as plugins from "./mod.plugins.js";
|
|
2
|
+
import { normalizeNpmRegistryUrl } from "../mod_release/helpers.npmartifact.js";
|
|
3
|
+
|
|
4
|
+
export interface IDeprecationPlan {
|
|
5
|
+
packageName: string;
|
|
6
|
+
replacement: string;
|
|
7
|
+
message: string;
|
|
8
|
+
registries: string[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const createDeprecationPlan = (
|
|
12
|
+
options: Omit<IDeprecationPlan, "message"> & { message?: string },
|
|
13
|
+
): IDeprecationPlan => {
|
|
14
|
+
const namePattern = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/;
|
|
15
|
+
if (
|
|
16
|
+
typeof options.packageName !== "string" ||
|
|
17
|
+
typeof options.replacement !== "string" ||
|
|
18
|
+
!namePattern.test(options.packageName) ||
|
|
19
|
+
!namePattern.test(options.replacement) ||
|
|
20
|
+
options.packageName === options.replacement
|
|
21
|
+
)
|
|
22
|
+
throw new Error(
|
|
23
|
+
"Deprecation requires distinct valid package and replacement names.",
|
|
24
|
+
);
|
|
25
|
+
if (!Array.isArray(options.registries))
|
|
26
|
+
throw new Error("Invalid deprecation registries.");
|
|
27
|
+
const registries = options.registries.map(normalizeNpmRegistryUrl);
|
|
28
|
+
if (!registries.length || new Set(registries).size !== registries.length)
|
|
29
|
+
throw new Error("Deprecation requires unique explicit registries.");
|
|
30
|
+
const message =
|
|
31
|
+
options.message ??
|
|
32
|
+
`Use ${options.replacement}. ${options.packageName} is deprecated.`;
|
|
33
|
+
if (
|
|
34
|
+
typeof message !== "string" ||
|
|
35
|
+
!message.trim() ||
|
|
36
|
+
message.length > 2000 ||
|
|
37
|
+
/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/.test(message)
|
|
38
|
+
) {
|
|
39
|
+
throw new Error("Invalid deprecation message.");
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
packageName: options.packageName,
|
|
43
|
+
replacement: options.replacement,
|
|
44
|
+
registries,
|
|
45
|
+
message,
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
interface IPackageMetadata {
|
|
50
|
+
name: string;
|
|
51
|
+
versions: Record<string, { deprecated?: string }>;
|
|
52
|
+
"dist-tags"?: Record<string, string>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const readMetadata = async (
|
|
56
|
+
registry: string,
|
|
57
|
+
packageName: string,
|
|
58
|
+
fetchImplementation: typeof fetch,
|
|
59
|
+
): Promise<IPackageMetadata> => {
|
|
60
|
+
const response = await fetchImplementation(
|
|
61
|
+
`${registry}/${encodeURIComponent(packageName)}`,
|
|
62
|
+
{
|
|
63
|
+
method: "GET",
|
|
64
|
+
redirect: "error",
|
|
65
|
+
signal: AbortSignal.timeout(30_000),
|
|
66
|
+
headers: { accept: "application/json" },
|
|
67
|
+
},
|
|
68
|
+
);
|
|
69
|
+
if (!response.ok)
|
|
70
|
+
throw new Error(
|
|
71
|
+
`Cannot verify ${packageName} on ${registry} (HTTP ${response.status}).`,
|
|
72
|
+
);
|
|
73
|
+
const metadata = await response.json();
|
|
74
|
+
if (
|
|
75
|
+
!metadata ||
|
|
76
|
+
metadata.name !== packageName ||
|
|
77
|
+
!metadata.versions ||
|
|
78
|
+
typeof metadata.versions !== "object" ||
|
|
79
|
+
Array.isArray(metadata.versions) ||
|
|
80
|
+
!Object.keys(metadata.versions).length ||
|
|
81
|
+
Object.values(metadata.versions).some(
|
|
82
|
+
(version) =>
|
|
83
|
+
!version || typeof version !== "object" || Array.isArray(version),
|
|
84
|
+
)
|
|
85
|
+
) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
`Invalid registry metadata for ${packageName} on ${registry}.`,
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
return metadata;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/** Verify replacements everywhere before changing metadata for any old package. */
|
|
94
|
+
export const executeDeprecation = async (
|
|
95
|
+
planArg: IDeprecationPlan,
|
|
96
|
+
shell: plugins.smartshell.Smartshell,
|
|
97
|
+
cwd: string,
|
|
98
|
+
fetchImplementation: typeof fetch = fetch,
|
|
99
|
+
): Promise<void> => {
|
|
100
|
+
const plan = createDeprecationPlan(planArg);
|
|
101
|
+
for (const registry of plan.registries) {
|
|
102
|
+
const replacement = await readMetadata(
|
|
103
|
+
registry,
|
|
104
|
+
plan.replacement,
|
|
105
|
+
fetchImplementation,
|
|
106
|
+
);
|
|
107
|
+
const latestTag = replacement["dist-tags"]?.latest;
|
|
108
|
+
const latest =
|
|
109
|
+
typeof latestTag === "string"
|
|
110
|
+
? replacement.versions[latestTag]
|
|
111
|
+
: undefined;
|
|
112
|
+
if (!latest || latest.deprecated)
|
|
113
|
+
throw new Error(
|
|
114
|
+
`Replacement ${plan.replacement} has no active latest version on ${registry}.`,
|
|
115
|
+
);
|
|
116
|
+
await readMetadata(registry, plan.packageName, fetchImplementation);
|
|
117
|
+
}
|
|
118
|
+
for (const registry of plan.registries) {
|
|
119
|
+
const result = await shell.execSpawn(
|
|
120
|
+
"pnpm",
|
|
121
|
+
[
|
|
122
|
+
"deprecate",
|
|
123
|
+
`${plan.packageName}@*`,
|
|
124
|
+
plan.message,
|
|
125
|
+
"--registry",
|
|
126
|
+
registry,
|
|
127
|
+
],
|
|
128
|
+
{
|
|
129
|
+
cwd,
|
|
130
|
+
timeout: 120_000,
|
|
131
|
+
timeoutKillGraceMs: 5_000,
|
|
132
|
+
},
|
|
133
|
+
);
|
|
134
|
+
if (result.exitCode !== 0)
|
|
135
|
+
throw new Error(`Deprecation failed on ${registry}.`);
|
|
136
|
+
const metadata = await readMetadata(
|
|
137
|
+
registry,
|
|
138
|
+
plan.packageName,
|
|
139
|
+
fetchImplementation,
|
|
140
|
+
);
|
|
141
|
+
const versions = Object.values(metadata.versions);
|
|
142
|
+
if (
|
|
143
|
+
!versions.length ||
|
|
144
|
+
versions.some((version) => version.deprecated !== plan.message)
|
|
145
|
+
) {
|
|
146
|
+
throw new Error(
|
|
147
|
+
`Deprecation is not yet verified for every version on ${registry}; rerunning the same deprecation is safe.`,
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
};
|
|
@@ -1,52 +1,103 @@
|
|
|
1
|
-
import * as plugins from
|
|
1
|
+
import * as plugins from "./mod.plugins.js";
|
|
2
2
|
|
|
3
|
-
import { logger } from
|
|
3
|
+
import { logger } from "../gitzone.logging.js";
|
|
4
|
+
import { getCliMode, printJson } from "../helpers.climode.js";
|
|
5
|
+
import {
|
|
6
|
+
createDeprecationPlan,
|
|
7
|
+
executeDeprecation,
|
|
8
|
+
} from "./helpers.deprecation.js";
|
|
4
9
|
|
|
5
|
-
export const run = async () => {
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
export const run = async (argvArg: any = {}) => {
|
|
11
|
+
const mode = await getCliMode(argvArg);
|
|
12
|
+
if (mode.help) {
|
|
13
|
+
console.log(
|
|
14
|
+
"gitzone deprecate --package <old> --replacement <new> --registries <url,url> [--message <text>] [--plan] [-y]",
|
|
15
|
+
);
|
|
16
|
+
console.log(
|
|
17
|
+
"Deprecates all versions after verifying the replacement on every registry.",
|
|
18
|
+
);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
let registryInput = argvArg.registries;
|
|
22
|
+
let oldPackageName = argvArg.package;
|
|
23
|
+
let newPackageName = argvArg.replacement;
|
|
24
|
+
if (!registryInput || !oldPackageName || !newPackageName) {
|
|
25
|
+
if (!mode.interactive || argvArg.plan || mode.json)
|
|
26
|
+
throw new Error("Specify --package, --replacement, and --registries.");
|
|
27
|
+
const smartInteract = new plugins.smartinteract.SmartInteract([
|
|
28
|
+
{
|
|
29
|
+
name: `registryUrls`,
|
|
30
|
+
message: `What are the comma separated registry URLs?`,
|
|
31
|
+
type: `input`,
|
|
32
|
+
default: `https://registry.npmjs.org`,
|
|
33
|
+
validate: (stringInput) => {
|
|
34
|
+
return stringInput !== "" && !process.env.CI;
|
|
35
|
+
},
|
|
14
36
|
},
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
37
|
+
{
|
|
38
|
+
name: `oldPackageName`,
|
|
39
|
+
message: `Whats the name of the OLD package?`,
|
|
40
|
+
type: `input`,
|
|
41
|
+
default: ``,
|
|
42
|
+
validate: (stringInput) => {
|
|
43
|
+
return stringInput !== "" && !process.env.CI;
|
|
44
|
+
},
|
|
23
45
|
},
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
46
|
+
{
|
|
47
|
+
name: `newPackageName`,
|
|
48
|
+
message: `Whats the name of the NEW package?`,
|
|
49
|
+
type: `input`,
|
|
50
|
+
default: ``,
|
|
51
|
+
validate: (stringInput) => {
|
|
52
|
+
return stringInput !== "" && !process.env.CI;
|
|
53
|
+
},
|
|
32
54
|
},
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
55
|
+
]);
|
|
56
|
+
const answerBucket = await smartInteract.runQueue();
|
|
57
|
+
registryInput = answerBucket.getAnswerFor("registryUrls");
|
|
58
|
+
oldPackageName = answerBucket.getAnswerFor("oldPackageName");
|
|
59
|
+
newPackageName = answerBucket.getAnswerFor("newPackageName");
|
|
60
|
+
}
|
|
61
|
+
if (
|
|
62
|
+
typeof registryInput !== "string" ||
|
|
63
|
+
typeof oldPackageName !== "string" ||
|
|
64
|
+
typeof newPackageName !== "string"
|
|
65
|
+
) {
|
|
66
|
+
throw new Error("Deprecation arguments must be strings.");
|
|
67
|
+
}
|
|
68
|
+
const plan = createDeprecationPlan({
|
|
69
|
+
packageName: oldPackageName,
|
|
70
|
+
replacement: newPackageName,
|
|
71
|
+
registries: registryInput.split(",").map((value) => value.trim()),
|
|
72
|
+
message: argvArg.message,
|
|
73
|
+
});
|
|
74
|
+
if (mode.json) {
|
|
75
|
+
printJson({ ok: true, plan });
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
console.log(JSON.stringify(plan, null, 2));
|
|
79
|
+
if (argvArg.plan) return;
|
|
80
|
+
if (!mode.yes) {
|
|
81
|
+
if (!mode.interactive)
|
|
82
|
+
throw new Error("Deprecation requires -y or interactive confirmation.");
|
|
83
|
+
if (
|
|
84
|
+
!(await plugins.smartinteract.SmartInteract.getCliConfirmation(
|
|
85
|
+
`Deprecate every version of ${oldPackageName}?`,
|
|
86
|
+
false,
|
|
87
|
+
))
|
|
88
|
+
)
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
39
91
|
logger.log(
|
|
40
|
-
|
|
92
|
+
"info",
|
|
41
93
|
`Deprecating package ${oldPackageName} in favour of ${newPackageName}`,
|
|
42
94
|
);
|
|
43
95
|
const smartshellInstance = new plugins.smartshell.Smartshell({
|
|
44
|
-
executor:
|
|
96
|
+
executor: "bash",
|
|
45
97
|
});
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
98
|
+
await executeDeprecation(plan, smartshellInstance, process.cwd());
|
|
99
|
+
logger.log(
|
|
100
|
+
"ok",
|
|
101
|
+
`Verified deprecation on ${plan.registries.length} registries.`,
|
|
102
|
+
);
|
|
52
103
|
};
|
|
@@ -124,6 +124,60 @@ export abstract class BaseFormatter {
|
|
|
124
124
|
await plugins.smartfs.file(filepath).delete();
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
+
/**
|
|
128
|
+
* Rename a file, preserving git history where the repository tracks it.
|
|
129
|
+
* Uses `git mv` first and only falls back to a plain filesystem rename when
|
|
130
|
+
* git refuses (untracked file, no repository, ...).
|
|
131
|
+
*/
|
|
132
|
+
protected async renameFile(fromPath: string, toPath: string): Promise<void> {
|
|
133
|
+
if (!fromPath || !toPath || fromPath === toPath) {
|
|
134
|
+
throw new Error(`Invalid rename from "${fromPath}" to "${toPath}"`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const sourceExists = await plugins.smartfs.file(fromPath).exists();
|
|
138
|
+
if (!sourceExists) {
|
|
139
|
+
throw new Error(`Cannot rename ${fromPath}: file does not exist`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const destinationExists = await plugins.smartfs.file(toPath).exists();
|
|
143
|
+
if (destinationExists) {
|
|
144
|
+
throw new Error(
|
|
145
|
+
`Cannot rename ${fromPath} to ${toPath}: destination already exists`,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const destinationDir = plugins.path.dirname(toPath);
|
|
150
|
+
if (destinationDir && destinationDir !== '.') {
|
|
151
|
+
await plugins.smartfs.directory(destinationDir).recursive().create();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
let gitRenamed = false;
|
|
155
|
+
try {
|
|
156
|
+
const smartshellInstance = new plugins.smartshell.Smartshell({
|
|
157
|
+
executor: 'bash',
|
|
158
|
+
sourceFilePaths: [],
|
|
159
|
+
});
|
|
160
|
+
const result = await smartshellInstance.execSpawn(
|
|
161
|
+
'git',
|
|
162
|
+
['mv', fromPath, toPath],
|
|
163
|
+
{ silent: true, timeout: 30_000, timeoutKillGraceMs: 5_000 },
|
|
164
|
+
);
|
|
165
|
+
gitRenamed = result.exitCode === 0;
|
|
166
|
+
} catch {
|
|
167
|
+
gitRenamed = false;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (!gitRenamed) {
|
|
171
|
+
// A killed or timed out `git mv` may still have moved the file.
|
|
172
|
+
const movedAnyway =
|
|
173
|
+
!(await plugins.smartfs.file(fromPath).exists()) &&
|
|
174
|
+
(await plugins.smartfs.file(toPath).exists());
|
|
175
|
+
if (!movedAnyway) {
|
|
176
|
+
await plugins.fs.rename(fromPath, toPath);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
127
181
|
/**
|
|
128
182
|
* Check for diffs without applying changes
|
|
129
183
|
*/
|
|
@@ -172,6 +226,19 @@ export abstract class BaseFormatter {
|
|
|
172
226
|
} catch {
|
|
173
227
|
// File doesn't exist, nothing to delete
|
|
174
228
|
}
|
|
229
|
+
} else if (change.type === 'rename') {
|
|
230
|
+
if (!change.fromPath) {
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
const sourceExists = await plugins.smartfs.file(change.fromPath).exists();
|
|
234
|
+
if (!sourceExists) {
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
diffs.push({
|
|
238
|
+
path: change.path,
|
|
239
|
+
type: 'rename',
|
|
240
|
+
fromPath: change.fromPath,
|
|
241
|
+
});
|
|
175
242
|
}
|
|
176
243
|
}
|
|
177
244
|
|
|
@@ -183,6 +250,10 @@ export abstract class BaseFormatter {
|
|
|
183
250
|
|
|
184
251
|
displayDiff(diff: ICheckResult['diffs'][0]): void {
|
|
185
252
|
console.log(`\n--- ${diff.path}`);
|
|
253
|
+
if (diff.type === 'rename') {
|
|
254
|
+
console.log(` (file will be renamed from ${diff.fromPath})`);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
186
257
|
if (diff.before !== undefined && diff.after !== undefined) {
|
|
187
258
|
console.log(plugins.smartdiff.formatUnifiedDiffForConsole(diff.before, diff.after, {
|
|
188
259
|
originalFileName: diff.path,
|
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
IFormatPlan,
|
|
6
6
|
IPlannedChange,
|
|
7
7
|
IFormatWarning,
|
|
8
|
+
TPlannedChangeType,
|
|
8
9
|
} from './interfaces.format.js';
|
|
9
10
|
import { getModuleIcon } from './interfaces.format.js';
|
|
10
11
|
import { logger } from '../gitzone.logging.js';
|
|
@@ -21,6 +22,7 @@ export class FormatPlanner {
|
|
|
21
22
|
filesAdded: 0,
|
|
22
23
|
filesModified: 0,
|
|
23
24
|
filesRemoved: 0,
|
|
25
|
+
filesRenamed: 0,
|
|
24
26
|
},
|
|
25
27
|
changes: [],
|
|
26
28
|
warnings: [],
|
|
@@ -44,6 +46,9 @@ export class FormatPlanner {
|
|
|
44
46
|
case 'delete':
|
|
45
47
|
plan.summary.filesRemoved++;
|
|
46
48
|
break;
|
|
49
|
+
case 'rename':
|
|
50
|
+
plan.summary.filesRenamed++;
|
|
51
|
+
break;
|
|
47
52
|
}
|
|
48
53
|
}
|
|
49
54
|
|
|
@@ -64,7 +69,8 @@ export class FormatPlanner {
|
|
|
64
69
|
plan.summary.totalFiles =
|
|
65
70
|
plan.summary.filesAdded +
|
|
66
71
|
plan.summary.filesModified +
|
|
67
|
-
plan.summary.filesRemoved
|
|
72
|
+
plan.summary.filesRemoved +
|
|
73
|
+
plan.summary.filesRenamed;
|
|
68
74
|
|
|
69
75
|
return plan;
|
|
70
76
|
}
|
|
@@ -100,6 +106,7 @@ export class FormatPlanner {
|
|
|
100
106
|
console.log(` • ${plan.summary.filesAdded} new files`);
|
|
101
107
|
console.log(` • ${plan.summary.filesModified} modified files`);
|
|
102
108
|
console.log(` • ${plan.summary.filesRemoved} deleted files`);
|
|
109
|
+
console.log(` • ${plan.summary.filesRenamed} renamed files`);
|
|
103
110
|
console.log('');
|
|
104
111
|
console.log('Changes by module:');
|
|
105
112
|
|
|
@@ -117,7 +124,11 @@ export class FormatPlanner {
|
|
|
117
124
|
|
|
118
125
|
for (const change of changes) {
|
|
119
126
|
const icon = this.getChangeIcon(change.type);
|
|
120
|
-
|
|
127
|
+
const pathLabel =
|
|
128
|
+
change.type === 'rename' && change.fromPath
|
|
129
|
+
? `${change.fromPath} -> ${change.path}`
|
|
130
|
+
: change.path;
|
|
131
|
+
console.log(` ${icon} ${pathLabel} - ${change.description}`);
|
|
121
132
|
|
|
122
133
|
if (detailed && change.type === 'modify') {
|
|
123
134
|
const diff = await this.diffReporter.generateDiffForChange(change);
|
|
@@ -139,7 +150,7 @@ export class FormatPlanner {
|
|
|
139
150
|
console.log('\n' + '━'.repeat(50));
|
|
140
151
|
}
|
|
141
152
|
|
|
142
|
-
private getChangeIcon(type:
|
|
153
|
+
private getChangeIcon(type: TPlannedChangeType): string {
|
|
143
154
|
switch (type) {
|
|
144
155
|
case 'create':
|
|
145
156
|
return '✅';
|
|
@@ -147,6 +158,8 @@ export class FormatPlanner {
|
|
|
147
158
|
return '✏️';
|
|
148
159
|
case 'delete':
|
|
149
160
|
return '❌';
|
|
161
|
+
case 'rename':
|
|
162
|
+
return '🔁';
|
|
150
163
|
}
|
|
151
164
|
}
|
|
152
165
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as plugins from './mod.plugins.js';
|
|
2
2
|
import { logger } from '../gitzone.logging.js';
|
|
3
3
|
import { getModuleIcon } from './interfaces.format.js';
|
|
4
|
+
import type { TPlannedChangeType } from './interfaces.format.js';
|
|
4
5
|
|
|
5
6
|
export interface IModuleStats {
|
|
6
7
|
name: string;
|
|
@@ -11,6 +12,7 @@ export interface IModuleStats {
|
|
|
11
12
|
filesCreated: number;
|
|
12
13
|
filesModified: number;
|
|
13
14
|
filesDeleted: number;
|
|
15
|
+
filesRenamed: number;
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
export interface IFormatStats {
|
|
@@ -23,6 +25,7 @@ export interface IFormatStats {
|
|
|
23
25
|
totalCreated: number;
|
|
24
26
|
totalModified: number;
|
|
25
27
|
totalDeleted: number;
|
|
28
|
+
totalRenamed: number;
|
|
26
29
|
totalErrors: number;
|
|
27
30
|
};
|
|
28
31
|
}
|
|
@@ -41,6 +44,7 @@ export class FormatStats {
|
|
|
41
44
|
totalCreated: 0,
|
|
42
45
|
totalModified: 0,
|
|
43
46
|
totalDeleted: 0,
|
|
47
|
+
totalRenamed: 0,
|
|
44
48
|
totalErrors: 0,
|
|
45
49
|
},
|
|
46
50
|
};
|
|
@@ -56,6 +60,7 @@ export class FormatStats {
|
|
|
56
60
|
filesCreated: 0,
|
|
57
61
|
filesModified: 0,
|
|
58
62
|
filesDeleted: 0,
|
|
63
|
+
filesRenamed: 0,
|
|
59
64
|
});
|
|
60
65
|
}
|
|
61
66
|
|
|
@@ -72,7 +77,7 @@ export class FormatStats {
|
|
|
72
77
|
|
|
73
78
|
recordFileOperation(
|
|
74
79
|
moduleName: string,
|
|
75
|
-
operation:
|
|
80
|
+
operation: TPlannedChangeType,
|
|
76
81
|
success: boolean = true,
|
|
77
82
|
): void {
|
|
78
83
|
const moduleStats = this.stats.moduleStats.get(moduleName);
|
|
@@ -97,6 +102,10 @@ export class FormatStats {
|
|
|
97
102
|
moduleStats.filesDeleted++;
|
|
98
103
|
this.stats.overallStats.totalDeleted++;
|
|
99
104
|
break;
|
|
105
|
+
case 'rename':
|
|
106
|
+
moduleStats.filesRenamed++;
|
|
107
|
+
this.stats.overallStats.totalRenamed++;
|
|
108
|
+
break;
|
|
100
109
|
}
|
|
101
110
|
} else {
|
|
102
111
|
moduleStats.errors++;
|
|
@@ -122,6 +131,7 @@ export class FormatStats {
|
|
|
122
131
|
console.log(` • Created: ${this.stats.overallStats.totalCreated}`);
|
|
123
132
|
console.log(` • Modified: ${this.stats.overallStats.totalModified}`);
|
|
124
133
|
console.log(` • Deleted: ${this.stats.overallStats.totalDeleted}`);
|
|
134
|
+
console.log(` • Renamed: ${this.stats.overallStats.totalRenamed}`);
|
|
125
135
|
console.log(` Errors: ${this.stats.overallStats.totalErrors}`);
|
|
126
136
|
|
|
127
137
|
// Module stats
|
|
@@ -150,6 +160,9 @@ export class FormatStats {
|
|
|
150
160
|
if (moduleStats.filesDeleted > 0) {
|
|
151
161
|
console.log(` • Deleted: ${moduleStats.filesDeleted}`);
|
|
152
162
|
}
|
|
163
|
+
if (moduleStats.filesRenamed > 0) {
|
|
164
|
+
console.log(` • Renamed: ${moduleStats.filesRenamed}`);
|
|
165
|
+
}
|
|
153
166
|
|
|
154
167
|
if (moduleStats.errors > 0) {
|
|
155
168
|
console.log(` ❌ Errors: ${moduleStats.errors}`);
|