@reliverse/dler 1.7.2 → 1.7.4
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/LICENSES +16 -0
- package/README.md +93 -71
- package/bin/app/agg/run.js +2 -2
- package/bin/app/check/cmd.d.ts +37 -0
- package/bin/app/check/cmd.js +74 -4
- package/bin/app/cmds.d.ts +15 -20
- package/bin/app/cmds.js +16 -76
- package/bin/app/copy/cmd.d.ts +10 -5
- package/bin/app/copy/cmd.js +77 -47
- package/bin/app/inject/cmd.d.ts +13 -0
- package/bin/app/inject/cmd.js +33 -21
- package/bin/app/merge/cmd.d.ts +63 -5
- package/bin/app/merge/cmd.js +220 -25
- package/bin/app/migrate/cmd.d.ts +5 -0
- package/bin/app/migrate/cmd.js +17 -1
- package/bin/app/rempts/{cmdsTs/cmd.d.ts → cmd.d.ts} +13 -4
- package/bin/app/rempts/cmd.js +304 -0
- package/bin/cli.js +6 -11
- package/bin/libs/sdk/sdk-impl/build/build-library.js +1 -1
- package/bin/libs/sdk/sdk-impl/build/build-regular.js +1 -1
- package/bin/libs/sdk/sdk-impl/cfg/info.js +1 -1
- package/bin/libs/sdk/sdk-impl/utils/{utils-pkg-json-libs.js → utils-package-json-libraries.js} +18 -10
- package/bin/libs/sdk/sdk-impl/utils/{utils-pkg-json-reg.js → utils-package-json-regular.js} +19 -7
- package/bin/libs/sdk/sdk-mod.d.ts +2 -2
- package/bin/libs/sdk/sdk-mod.js +2 -2
- package/package.json +4 -3
- package/bin/app/deps/cmd.d.ts +0 -45
- package/bin/app/deps/cmd.js +0 -84
- package/bin/app/inject/README.md +0 -139
- package/bin/app/inject/expect/cmd.d.ts +0 -20
- package/bin/app/inject/expect/cmd.js +0 -43
- package/bin/app/rempts/README.md +0 -121
- package/bin/app/rempts/cmd/cmd.d.ts +0 -16
- package/bin/app/rempts/cmd/cmd.js +0 -157
- package/bin/app/rempts/cmd/templates.d.ts +0 -2
- package/bin/app/rempts/cmd/templates.js +0 -30
- package/bin/app/rempts/cmdsTs/cmd.js +0 -96
- package/bin/app/rempts/migrate/cmd.d.ts +0 -14
- package/bin/app/rempts/migrate/cmd.js +0 -38
- /package/bin/app/inject/{expect/impl.d.ts → impl.d.ts} +0 -0
- /package/bin/app/inject/{expect/impl.js → impl.js} +0 -0
- /package/bin/app/{rempts/migrate/impl/commander.d.ts → migrate/codemods/commander-rempts.d.ts} +0 -0
- /package/bin/app/{rempts/migrate/impl/commander.js → migrate/codemods/commander-rempts.js} +0 -0
- /package/bin/libs/sdk/sdk-impl/utils/{utils-pkg-json-libs.d.ts → utils-package-json-libraries.d.ts} +0 -0
- /package/bin/libs/sdk/sdk-impl/utils/{utils-pkg-json-reg.d.ts → utils-package-json-regular.d.ts} +0 -0
package/bin/app/copy/cmd.js
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { relinka } from "@reliverse/relinka";
|
|
2
2
|
import { defineCommand, selectPrompt, inputPrompt } from "@reliverse/rempts";
|
|
3
3
|
import { copyFile, access, mkdir } from "node:fs/promises";
|
|
4
|
-
import { join, dirname } from "node:path";
|
|
4
|
+
import { join, dirname, basename } from "node:path";
|
|
5
|
+
import pMap from "p-map";
|
|
6
|
+
import prettyMilliseconds from "pretty-ms";
|
|
5
7
|
import { glob } from "tinyglobby";
|
|
8
|
+
import {
|
|
9
|
+
createPerfTimer,
|
|
10
|
+
getElapsedPerfTime
|
|
11
|
+
} from "../../libs/sdk/sdk-impl/utils/utils-perf.js";
|
|
6
12
|
async function fileExists(path) {
|
|
7
13
|
try {
|
|
8
14
|
await access(path);
|
|
@@ -11,12 +17,6 @@ async function fileExists(path) {
|
|
|
11
17
|
return false;
|
|
12
18
|
}
|
|
13
19
|
}
|
|
14
|
-
async function safeCopy(source, destination) {
|
|
15
|
-
if (await fileExists(destination)) {
|
|
16
|
-
throw new Error(`Destination file already exists: ${destination}`);
|
|
17
|
-
}
|
|
18
|
-
await copyFile(source, destination);
|
|
19
|
-
}
|
|
20
20
|
async function ensureDir(path) {
|
|
21
21
|
try {
|
|
22
22
|
await access(path);
|
|
@@ -31,11 +31,11 @@ export default defineCommand({
|
|
|
31
31
|
description: "Copy files and directories"
|
|
32
32
|
},
|
|
33
33
|
args: {
|
|
34
|
-
|
|
34
|
+
s: {
|
|
35
35
|
type: "string",
|
|
36
36
|
description: "Source file or directory to copy (supports glob patterns)"
|
|
37
37
|
},
|
|
38
|
-
|
|
38
|
+
d: {
|
|
39
39
|
type: "string",
|
|
40
40
|
description: "Destination path for the copy operation"
|
|
41
41
|
},
|
|
@@ -44,27 +44,33 @@ export default defineCommand({
|
|
|
44
44
|
description: "Recursively process all files in subdirectories (default: true)",
|
|
45
45
|
default: true
|
|
46
46
|
},
|
|
47
|
-
force: {
|
|
48
|
-
type: "boolean",
|
|
49
|
-
description: "Overwrite existing files (default: false)",
|
|
50
|
-
default: false
|
|
51
|
-
},
|
|
52
47
|
preserveStructure: {
|
|
53
48
|
type: "boolean",
|
|
54
49
|
description: "Preserve source directory structure in destination (default: true)",
|
|
55
50
|
default: true
|
|
51
|
+
},
|
|
52
|
+
increment: {
|
|
53
|
+
type: "boolean",
|
|
54
|
+
description: "Attach an incrementing index to each destination filename before the extension if set (default: true)",
|
|
55
|
+
default: true
|
|
56
|
+
},
|
|
57
|
+
concurrency: {
|
|
58
|
+
type: "number",
|
|
59
|
+
description: "Number of concurrent copy operations (default: 8)",
|
|
60
|
+
default: 8
|
|
56
61
|
}
|
|
57
62
|
},
|
|
58
63
|
async run({ args }) {
|
|
59
64
|
const {
|
|
60
|
-
|
|
61
|
-
|
|
65
|
+
s,
|
|
66
|
+
d,
|
|
62
67
|
recursive = true,
|
|
63
|
-
|
|
64
|
-
|
|
68
|
+
preserveStructure = true,
|
|
69
|
+
increment = false,
|
|
70
|
+
concurrency = 8
|
|
65
71
|
} = args;
|
|
66
|
-
let finalSource =
|
|
67
|
-
let finalDestination =
|
|
72
|
+
let finalSource = s;
|
|
73
|
+
let finalDestination = d;
|
|
68
74
|
if (!finalSource) {
|
|
69
75
|
finalSource = await inputPrompt({
|
|
70
76
|
title: "Enter source file or directory (supports glob patterns)",
|
|
@@ -78,7 +84,7 @@ export default defineCommand({
|
|
|
78
84
|
});
|
|
79
85
|
}
|
|
80
86
|
if (!finalSource || !finalDestination) {
|
|
81
|
-
relinka("error", "Usage: dler copy <source> <destination>");
|
|
87
|
+
relinka("error", "Usage: dler copy --s <source> --d <destination>");
|
|
82
88
|
process.exit(1);
|
|
83
89
|
}
|
|
84
90
|
try {
|
|
@@ -103,37 +109,61 @@ export default defineCommand({
|
|
|
103
109
|
return;
|
|
104
110
|
}
|
|
105
111
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
const timer = createPerfTimer();
|
|
113
|
+
const fileNameCounts = /* @__PURE__ */ new Map();
|
|
114
|
+
await pMap(
|
|
115
|
+
files,
|
|
116
|
+
async (file) => {
|
|
117
|
+
let destPath;
|
|
118
|
+
if (preserveStructure) {
|
|
119
|
+
const match = file.match(/packages\/([^/]+)\/lib\/(.*)/);
|
|
120
|
+
if (match?.[1] && match?.[2]) {
|
|
121
|
+
const packageName = match[1];
|
|
122
|
+
const relativePath = match[2];
|
|
123
|
+
destPath = join(finalDestination, packageName, relativePath);
|
|
124
|
+
} else {
|
|
125
|
+
destPath = join(finalDestination, file);
|
|
126
|
+
}
|
|
114
127
|
} else {
|
|
115
|
-
destPath = join(finalDestination, file);
|
|
128
|
+
destPath = join(finalDestination, basename(file));
|
|
129
|
+
}
|
|
130
|
+
if (increment) {
|
|
131
|
+
const dir = dirname(destPath);
|
|
132
|
+
const base = basename(destPath);
|
|
133
|
+
let dirMap = fileNameCounts.get(dir);
|
|
134
|
+
if (!dirMap) {
|
|
135
|
+
dirMap = /* @__PURE__ */ new Map();
|
|
136
|
+
fileNameCounts.set(dir, dirMap);
|
|
137
|
+
}
|
|
138
|
+
const count = dirMap.get(base) || 0;
|
|
139
|
+
if (count > 0) {
|
|
140
|
+
const extMatch = base.match(/(.*)(\.[^./\\]+)$/);
|
|
141
|
+
let newBase;
|
|
142
|
+
if (extMatch) {
|
|
143
|
+
newBase = `${extMatch[1]}-${count + 1}${extMatch[2]}`;
|
|
144
|
+
} else {
|
|
145
|
+
newBase = `${base}-${count + 1}`;
|
|
146
|
+
}
|
|
147
|
+
destPath = join(dir, newBase);
|
|
148
|
+
}
|
|
149
|
+
dirMap.set(base, count + 1);
|
|
116
150
|
}
|
|
117
|
-
} else {
|
|
118
|
-
destPath = join(finalDestination, file);
|
|
119
|
-
}
|
|
120
|
-
try {
|
|
121
151
|
await ensureDir(dirname(destPath));
|
|
122
|
-
if (
|
|
123
|
-
|
|
124
|
-
} else {
|
|
125
|
-
await safeCopy(file, destPath);
|
|
152
|
+
if (await fileExists(destPath)) {
|
|
153
|
+
throw new Error(`Destination file already exists: ${destPath}`);
|
|
126
154
|
}
|
|
155
|
+
await copyFile(file, destPath);
|
|
127
156
|
relinka("log", `Copied '${file}' to '${destPath}'`);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
|
|
157
|
+
},
|
|
158
|
+
{ concurrency, stopOnError: true }
|
|
159
|
+
);
|
|
160
|
+
const elapsed = getElapsedPerfTime(timer);
|
|
161
|
+
relinka(
|
|
162
|
+
"log",
|
|
163
|
+
`Successfully copied ${files.length} file(s) in ${prettyMilliseconds(
|
|
164
|
+
elapsed
|
|
165
|
+
)}`
|
|
166
|
+
);
|
|
137
167
|
} catch (error) {
|
|
138
168
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
139
169
|
relinka("error", `Error during copy operation: ${errorMessage}`);
|
package/bin/app/inject/cmd.d.ts
CHANGED
|
@@ -7,5 +7,18 @@ declare const _default: import("@reliverse/rempts").Command<{
|
|
|
7
7
|
type: "string";
|
|
8
8
|
description: string;
|
|
9
9
|
};
|
|
10
|
+
files: {
|
|
11
|
+
type: "positional";
|
|
12
|
+
description: string;
|
|
13
|
+
default: string;
|
|
14
|
+
};
|
|
15
|
+
comment: {
|
|
16
|
+
type: "string";
|
|
17
|
+
description: string;
|
|
18
|
+
};
|
|
19
|
+
tscPaths: {
|
|
20
|
+
type: "string";
|
|
21
|
+
description: string;
|
|
22
|
+
};
|
|
10
23
|
}>;
|
|
11
24
|
export default _default;
|
package/bin/app/inject/cmd.js
CHANGED
|
@@ -1,35 +1,47 @@
|
|
|
1
1
|
import { relinka } from "@reliverse/relinka";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { defineArgs, defineCommand } from "@reliverse/rempts";
|
|
3
|
+
import { useTsExpectError } from "./impl.js";
|
|
4
4
|
export default defineCommand({
|
|
5
5
|
meta: {
|
|
6
|
-
name: "
|
|
7
|
-
|
|
6
|
+
name: "expect",
|
|
7
|
+
version: "1.0.0",
|
|
8
|
+
description: "Inject `@ts-expect-error` above lines where TS errors occur"
|
|
8
9
|
},
|
|
9
|
-
args: {
|
|
10
|
+
args: defineArgs({
|
|
10
11
|
dev: {
|
|
11
12
|
type: "boolean",
|
|
12
|
-
description: "
|
|
13
|
+
description: "Run the CLI in dev mode"
|
|
13
14
|
},
|
|
14
15
|
cwd: {
|
|
15
16
|
type: "string",
|
|
16
17
|
description: "The working directory to run the CLI in"
|
|
18
|
+
},
|
|
19
|
+
files: {
|
|
20
|
+
type: "positional",
|
|
21
|
+
description: `'auto' or path(s) to line references file(s)`,
|
|
22
|
+
default: "auto"
|
|
23
|
+
},
|
|
24
|
+
comment: {
|
|
25
|
+
type: "string",
|
|
26
|
+
description: "Override the comment line to insert. Default is `// @ts-expect-error TODO: fix ts`"
|
|
27
|
+
},
|
|
28
|
+
tscPaths: {
|
|
29
|
+
type: "string",
|
|
30
|
+
description: "Optional: specify path(s) to restrict TSC processing (only effective when using 'auto')"
|
|
17
31
|
}
|
|
18
|
-
},
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const cmd = await selectPrompt({
|
|
23
|
-
title: "Select a command",
|
|
24
|
-
options: [
|
|
25
|
-
{
|
|
26
|
-
value: "ts-expect-error",
|
|
27
|
-
label: "Inject `@ts-expect-error` above lines where TS errors occur"
|
|
28
|
-
}
|
|
29
|
-
]
|
|
30
|
-
});
|
|
31
|
-
if (cmd === "ts-expect-error") {
|
|
32
|
-
await runCmd(await getCmdInjectExpect(), []);
|
|
32
|
+
}),
|
|
33
|
+
async run({ args }) {
|
|
34
|
+
if (args.dev) {
|
|
35
|
+
relinka("verbose", "Using dev mode");
|
|
33
36
|
}
|
|
37
|
+
let pathsTsc = args.tscPaths;
|
|
38
|
+
if (pathsTsc === void 0 && args.files === "auto") {
|
|
39
|
+
pathsTsc = "./tsconfig.json";
|
|
40
|
+
}
|
|
41
|
+
await useTsExpectError({
|
|
42
|
+
files: [args.files],
|
|
43
|
+
comment: args.comment,
|
|
44
|
+
tscPaths: [pathsTsc]
|
|
45
|
+
});
|
|
34
46
|
}
|
|
35
47
|
});
|
package/bin/app/merge/cmd.d.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
declare const _default: import("@reliverse/rempts").Command<{
|
|
2
|
-
|
|
2
|
+
s: {
|
|
3
3
|
type: "array";
|
|
4
4
|
description: string;
|
|
5
5
|
};
|
|
6
|
-
|
|
7
|
-
type: "
|
|
6
|
+
d: {
|
|
7
|
+
type: "string";
|
|
8
8
|
description: string;
|
|
9
9
|
};
|
|
10
|
-
|
|
11
|
-
type: "
|
|
10
|
+
ignore: {
|
|
11
|
+
type: "array";
|
|
12
12
|
description: string;
|
|
13
13
|
};
|
|
14
14
|
format: {
|
|
@@ -24,6 +24,11 @@ declare const _default: import("@reliverse/rempts").Command<{
|
|
|
24
24
|
type: "boolean";
|
|
25
25
|
description: string;
|
|
26
26
|
};
|
|
27
|
+
pathAbove: {
|
|
28
|
+
type: "boolean";
|
|
29
|
+
description: string;
|
|
30
|
+
default: true;
|
|
31
|
+
};
|
|
27
32
|
separator: {
|
|
28
33
|
type: "string";
|
|
29
34
|
description: string;
|
|
@@ -40,5 +45,58 @@ declare const _default: import("@reliverse/rempts").Command<{
|
|
|
40
45
|
type: "boolean";
|
|
41
46
|
description: string;
|
|
42
47
|
};
|
|
48
|
+
recursive: {
|
|
49
|
+
type: "boolean";
|
|
50
|
+
description: string;
|
|
51
|
+
default: true;
|
|
52
|
+
};
|
|
53
|
+
preserveStructure: {
|
|
54
|
+
type: "boolean";
|
|
55
|
+
description: string;
|
|
56
|
+
default: true;
|
|
57
|
+
};
|
|
58
|
+
increment: {
|
|
59
|
+
type: "boolean";
|
|
60
|
+
description: string;
|
|
61
|
+
default: false;
|
|
62
|
+
};
|
|
63
|
+
concurrency: {
|
|
64
|
+
type: "number";
|
|
65
|
+
description: string;
|
|
66
|
+
default: number;
|
|
67
|
+
};
|
|
68
|
+
sort: {
|
|
69
|
+
type: "string";
|
|
70
|
+
description: string;
|
|
71
|
+
default: string;
|
|
72
|
+
};
|
|
73
|
+
dryRun: {
|
|
74
|
+
type: "boolean";
|
|
75
|
+
description: string;
|
|
76
|
+
default: false;
|
|
77
|
+
};
|
|
78
|
+
backup: {
|
|
79
|
+
type: "boolean";
|
|
80
|
+
description: string;
|
|
81
|
+
default: false;
|
|
82
|
+
};
|
|
83
|
+
dedupe: {
|
|
84
|
+
type: "boolean";
|
|
85
|
+
description: string;
|
|
86
|
+
default: false;
|
|
87
|
+
};
|
|
88
|
+
header: {
|
|
89
|
+
type: "string";
|
|
90
|
+
description: string;
|
|
91
|
+
};
|
|
92
|
+
footer: {
|
|
93
|
+
type: "string";
|
|
94
|
+
description: string;
|
|
95
|
+
};
|
|
96
|
+
interactive: {
|
|
97
|
+
type: "boolean";
|
|
98
|
+
description: string;
|
|
99
|
+
default: false;
|
|
100
|
+
};
|
|
43
101
|
}>;
|
|
44
102
|
export default _default;
|
package/bin/app/merge/cmd.js
CHANGED
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
import path from "@reliverse/pathkit";
|
|
2
2
|
import { glob } from "@reliverse/reglob";
|
|
3
3
|
import fs from "@reliverse/relifso";
|
|
4
|
-
import {
|
|
4
|
+
import { relinka } from "@reliverse/relinka";
|
|
5
|
+
import {
|
|
6
|
+
defineCommand,
|
|
7
|
+
inputPrompt,
|
|
8
|
+
confirmPrompt,
|
|
9
|
+
multiselectPrompt
|
|
10
|
+
} from "@reliverse/rempts";
|
|
11
|
+
import pMap from "p-map";
|
|
12
|
+
import prettyMilliseconds from "pretty-ms";
|
|
13
|
+
import {
|
|
14
|
+
createPerfTimer,
|
|
15
|
+
getElapsedPerfTime
|
|
16
|
+
} from "../../libs/sdk/sdk-impl/utils/utils-perf.js";
|
|
5
17
|
const DEFAULT_IGNORES = ["**/.git/**", "**/node_modules/**"];
|
|
6
18
|
const BINARY_EXTS = [
|
|
7
19
|
"png",
|
|
@@ -85,24 +97,28 @@ const maybePrompt = async (batch, value, promptFn) => {
|
|
|
85
97
|
if (batch || value !== void 0) return value;
|
|
86
98
|
return promptFn();
|
|
87
99
|
};
|
|
88
|
-
const collectFiles = async (include, extraIgnore) => {
|
|
100
|
+
const collectFiles = async (include, extraIgnore, recursive, sortBy) => {
|
|
89
101
|
const files = await glob(include, {
|
|
90
102
|
ignore: [...DEFAULT_IGNORES, ...extraIgnore],
|
|
91
|
-
absolute: true
|
|
103
|
+
absolute: true,
|
|
104
|
+
onlyFiles: true,
|
|
105
|
+
deep: recursive ? void 0 : 1
|
|
92
106
|
});
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
107
|
+
let filtered = [...new Set(files)].filter((f) => !isBinaryExt(f));
|
|
108
|
+
if (sortBy === "name") {
|
|
109
|
+
filtered.sort((a, b) => path.basename(a).localeCompare(path.basename(b)));
|
|
110
|
+
} else if (sortBy === "path") {
|
|
111
|
+
filtered.sort();
|
|
112
|
+
} else if (sortBy === "mtime") {
|
|
113
|
+
filtered = await pMap(
|
|
114
|
+
filtered,
|
|
115
|
+
async (f) => ({ f, mtime: (await fs.stat(f)).mtimeMs }),
|
|
116
|
+
{ concurrency: 8 }
|
|
117
|
+
).then((arr) => arr.sort((a, b) => a.mtime - b.mtime).map((x) => x.f));
|
|
118
|
+
}
|
|
119
|
+
return filtered;
|
|
104
120
|
};
|
|
105
|
-
const writeResult = async (sections, separator, toFile, toStdout) => {
|
|
121
|
+
const writeResult = async (sections, separator, toFile, toStdout, dryRun, backup) => {
|
|
106
122
|
const content = `${sections.join(separator)}
|
|
107
123
|
`;
|
|
108
124
|
if (toStdout || !toFile) {
|
|
@@ -111,18 +127,65 @@ const writeResult = async (sections, separator, toFile, toStdout) => {
|
|
|
111
127
|
}
|
|
112
128
|
const dir = path.dirname(toFile);
|
|
113
129
|
if (dir && dir !== ".") await fs.ensureDir(dir);
|
|
114
|
-
await fs.
|
|
130
|
+
if (backup && await fs.pathExists(toFile)) {
|
|
131
|
+
const backupPath = `${toFile}.${Date.now()}.bak`;
|
|
132
|
+
await fs.copyFile(toFile, backupPath);
|
|
133
|
+
}
|
|
134
|
+
if (!dryRun) {
|
|
135
|
+
await fs.writeFile(toFile, content, "utf8");
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
const writeFilesPreserveStructure = async (files, outDir, preserveStructure, increment, concurrency, dryRun, backup) => {
|
|
139
|
+
const cwd = process.cwd();
|
|
140
|
+
const fileNameCounts = /* @__PURE__ */ new Map();
|
|
141
|
+
await pMap(
|
|
142
|
+
files,
|
|
143
|
+
async (file) => {
|
|
144
|
+
const relPath = preserveStructure ? path.relative(cwd, file) : path.basename(file);
|
|
145
|
+
let destPath = path.join(outDir, relPath);
|
|
146
|
+
if (increment) {
|
|
147
|
+
const dir = path.dirname(destPath);
|
|
148
|
+
const base = path.basename(destPath);
|
|
149
|
+
let dirMap = fileNameCounts.get(dir);
|
|
150
|
+
if (!dirMap) {
|
|
151
|
+
dirMap = /* @__PURE__ */ new Map();
|
|
152
|
+
fileNameCounts.set(dir, dirMap);
|
|
153
|
+
}
|
|
154
|
+
const count = dirMap.get(base) || 0;
|
|
155
|
+
if (count > 0) {
|
|
156
|
+
const extMatch = base.match(/(.*)(\.[^./\\]+)$/);
|
|
157
|
+
let newBase;
|
|
158
|
+
if (extMatch) {
|
|
159
|
+
newBase = `${extMatch[1]}-${count + 1}${extMatch[2]}`;
|
|
160
|
+
} else {
|
|
161
|
+
newBase = `${base}-${count + 1}`;
|
|
162
|
+
}
|
|
163
|
+
destPath = path.join(dir, newBase);
|
|
164
|
+
}
|
|
165
|
+
dirMap.set(base, count + 1);
|
|
166
|
+
}
|
|
167
|
+
await fs.ensureDir(path.dirname(destPath));
|
|
168
|
+
if (backup && await fs.pathExists(destPath)) {
|
|
169
|
+
const backupPath = `${destPath}.${Date.now()}.bak`;
|
|
170
|
+
await fs.copyFile(destPath, backupPath);
|
|
171
|
+
}
|
|
172
|
+
if (!dryRun) {
|
|
173
|
+
await fs.copyFile(file, destPath);
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
{ concurrency }
|
|
177
|
+
);
|
|
115
178
|
};
|
|
116
179
|
export default defineCommand({
|
|
117
180
|
meta: {
|
|
118
|
-
name: "
|
|
181
|
+
name: "merge",
|
|
119
182
|
version: "1.0.0",
|
|
120
|
-
description: "Merge text files with optional commented path footer, skips binaries/media, built for CI & interactive use."
|
|
183
|
+
description: "Merge text files with optional commented path header/footer, skips binaries/media, built for CI & interactive use. Supports copy-like patterns and advanced options."
|
|
121
184
|
},
|
|
122
185
|
args: {
|
|
123
|
-
|
|
186
|
+
s: { type: "array", description: "Input glob patterns" },
|
|
187
|
+
d: { type: "string", description: "Output file path or directory" },
|
|
124
188
|
ignore: { type: "array", description: "Extra ignore patterns" },
|
|
125
|
-
out: { type: "string", description: "Output file path" },
|
|
126
189
|
format: {
|
|
127
190
|
type: "string",
|
|
128
191
|
default: "txt",
|
|
@@ -133,6 +196,11 @@ export default defineCommand({
|
|
|
133
196
|
type: "boolean",
|
|
134
197
|
description: "Don't inject relative path below each file"
|
|
135
198
|
},
|
|
199
|
+
pathAbove: {
|
|
200
|
+
type: "boolean",
|
|
201
|
+
description: "Print file path above each file's content (default: true)",
|
|
202
|
+
default: true
|
|
203
|
+
},
|
|
136
204
|
separator: {
|
|
137
205
|
type: "string",
|
|
138
206
|
description: `Custom separator (default ${DEFAULT_SEPARATOR_RAW})`
|
|
@@ -148,11 +216,65 @@ export default defineCommand({
|
|
|
148
216
|
batch: {
|
|
149
217
|
type: "boolean",
|
|
150
218
|
description: "Disable interactive prompts (CI/non-interactive mode)"
|
|
219
|
+
},
|
|
220
|
+
recursive: {
|
|
221
|
+
type: "boolean",
|
|
222
|
+
description: "Recursively process all files in subdirectories (default: true)",
|
|
223
|
+
default: true
|
|
224
|
+
},
|
|
225
|
+
preserveStructure: {
|
|
226
|
+
type: "boolean",
|
|
227
|
+
description: "Preserve source directory structure in output (default: true)",
|
|
228
|
+
default: true
|
|
229
|
+
},
|
|
230
|
+
increment: {
|
|
231
|
+
type: "boolean",
|
|
232
|
+
description: "Attach an incrementing index to each output filename if set (default: false)",
|
|
233
|
+
default: false
|
|
234
|
+
},
|
|
235
|
+
concurrency: {
|
|
236
|
+
type: "number",
|
|
237
|
+
description: "Number of concurrent file operations (default: 8)",
|
|
238
|
+
default: 8
|
|
239
|
+
},
|
|
240
|
+
sort: {
|
|
241
|
+
type: "string",
|
|
242
|
+
description: "Sort files by: name, path, mtime, none (default: path)",
|
|
243
|
+
default: "path"
|
|
244
|
+
},
|
|
245
|
+
dryRun: {
|
|
246
|
+
type: "boolean",
|
|
247
|
+
description: "Show what would be done, but don't write files",
|
|
248
|
+
default: false
|
|
249
|
+
},
|
|
250
|
+
backup: {
|
|
251
|
+
type: "boolean",
|
|
252
|
+
description: "Backup output files before overwriting",
|
|
253
|
+
default: false
|
|
254
|
+
},
|
|
255
|
+
dedupe: {
|
|
256
|
+
type: "boolean",
|
|
257
|
+
description: "Remove duplicate file contents in merge",
|
|
258
|
+
default: false
|
|
259
|
+
},
|
|
260
|
+
header: {
|
|
261
|
+
type: "string",
|
|
262
|
+
description: "Header text to add at the start of merged output"
|
|
263
|
+
},
|
|
264
|
+
footer: {
|
|
265
|
+
type: "string",
|
|
266
|
+
description: "Footer text to add at the end of merged output"
|
|
267
|
+
},
|
|
268
|
+
interactive: {
|
|
269
|
+
type: "boolean",
|
|
270
|
+
description: "Prompt for file selection before merging",
|
|
271
|
+
default: false
|
|
151
272
|
}
|
|
152
273
|
},
|
|
153
274
|
async run({ args }) {
|
|
275
|
+
const timer = createPerfTimer();
|
|
154
276
|
const batch = Boolean(args.batch);
|
|
155
|
-
let include = args.
|
|
277
|
+
let include = args.s ?? [];
|
|
156
278
|
if (include.length === 0) {
|
|
157
279
|
const raw = await maybePrompt(
|
|
158
280
|
batch,
|
|
@@ -197,6 +319,7 @@ export default defineCommand({
|
|
|
197
319
|
}
|
|
198
320
|
const forceComment = args.forceComment ?? false;
|
|
199
321
|
const injectPath = !args.noPath;
|
|
322
|
+
const pathAbove = args.pathAbove ?? true;
|
|
200
323
|
const sepRaw = args.separator ?? await maybePrompt(
|
|
201
324
|
batch,
|
|
202
325
|
void 0,
|
|
@@ -207,7 +330,7 @@ export default defineCommand({
|
|
|
207
330
|
) ?? DEFAULT_SEPARATOR_RAW;
|
|
208
331
|
const separator = unescape(sepRaw);
|
|
209
332
|
let stdoutFlag = args.stdout ?? false;
|
|
210
|
-
let outFile = args.
|
|
333
|
+
let outFile = args.d;
|
|
211
334
|
if (!stdoutFlag && !outFile && !batch) {
|
|
212
335
|
stdoutFlag = await confirmPrompt({
|
|
213
336
|
title: "Print result to stdout?",
|
|
@@ -227,18 +350,90 @@ export default defineCommand({
|
|
|
227
350
|
}
|
|
228
351
|
}
|
|
229
352
|
}
|
|
230
|
-
const
|
|
353
|
+
const recursive = args.recursive ?? true;
|
|
354
|
+
const preserveStructure = args.preserveStructure ?? true;
|
|
355
|
+
const increment = args.increment ?? false;
|
|
356
|
+
const concurrency = args.concurrency ?? 8;
|
|
357
|
+
const sortBy = args.sort;
|
|
358
|
+
const dryRun = args.dryRun ?? false;
|
|
359
|
+
const backup = args.backup ?? false;
|
|
360
|
+
const dedupe = args.dedupe ?? false;
|
|
361
|
+
const header = args.header;
|
|
362
|
+
const footer = args.footer;
|
|
363
|
+
const interactive = args.interactive ?? false;
|
|
364
|
+
let files = await collectFiles(include, ignore, recursive, sortBy);
|
|
231
365
|
if (files.length === 0) {
|
|
232
366
|
throw new Error(
|
|
233
367
|
"No text files matched given patterns (binary/media files are skipped)"
|
|
234
368
|
);
|
|
235
369
|
}
|
|
370
|
+
if (interactive && !batch) {
|
|
371
|
+
const selected = await multiselectPrompt({
|
|
372
|
+
title: "Select files to merge",
|
|
373
|
+
options: files.map((f) => ({
|
|
374
|
+
label: path.relative(process.cwd(), f),
|
|
375
|
+
value: f
|
|
376
|
+
}))
|
|
377
|
+
});
|
|
378
|
+
files = Array.isArray(selected) ? selected : [selected];
|
|
379
|
+
if (files.length === 0) {
|
|
380
|
+
throw new Error("No files selected for merging");
|
|
381
|
+
}
|
|
382
|
+
}
|
|
236
383
|
const getPrefix = (filePath) => {
|
|
237
384
|
if (forceComment && customComment) return customComment;
|
|
238
385
|
const ext = path.extname(filePath).slice(1).toLowerCase();
|
|
239
386
|
return COMMENT_MAP[ext] ?? customComment ?? DEFAULT_COMMENT;
|
|
240
387
|
};
|
|
241
|
-
|
|
242
|
-
|
|
388
|
+
if (outFile && await fs.pathExists(outFile) && (await fs.stat(outFile)).isDirectory()) {
|
|
389
|
+
await writeFilesPreserveStructure(
|
|
390
|
+
files,
|
|
391
|
+
outFile,
|
|
392
|
+
preserveStructure,
|
|
393
|
+
increment,
|
|
394
|
+
concurrency,
|
|
395
|
+
dryRun,
|
|
396
|
+
backup
|
|
397
|
+
);
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
const cwd = process.cwd();
|
|
401
|
+
const seen = /* @__PURE__ */ new Set();
|
|
402
|
+
const sections = await pMap(
|
|
403
|
+
files,
|
|
404
|
+
async (f) => {
|
|
405
|
+
const raw = await fs.readFile(f, "utf8");
|
|
406
|
+
if (dedupe) {
|
|
407
|
+
const hash = raw.trim();
|
|
408
|
+
if (seen.has(hash)) return null;
|
|
409
|
+
seen.add(hash);
|
|
410
|
+
}
|
|
411
|
+
const rel = path.relative(cwd, f);
|
|
412
|
+
const prefix = getPrefix(f);
|
|
413
|
+
let section = raw;
|
|
414
|
+
if (pathAbove) {
|
|
415
|
+
section = `${prefix}${rel}
|
|
416
|
+
${section}`;
|
|
417
|
+
}
|
|
418
|
+
if (injectPath) {
|
|
419
|
+
section = `${ensureTrailingNL(section)}${prefix}${rel}`;
|
|
420
|
+
}
|
|
421
|
+
return section;
|
|
422
|
+
},
|
|
423
|
+
{ concurrency }
|
|
424
|
+
);
|
|
425
|
+
const filteredSections = sections.filter(Boolean);
|
|
426
|
+
if (header) filteredSections.unshift(header);
|
|
427
|
+
if (footer) filteredSections.push(footer);
|
|
428
|
+
await writeResult(
|
|
429
|
+
filteredSections,
|
|
430
|
+
separator,
|
|
431
|
+
outFile,
|
|
432
|
+
stdoutFlag,
|
|
433
|
+
dryRun,
|
|
434
|
+
backup
|
|
435
|
+
);
|
|
436
|
+
const elapsed = getElapsedPerfTime(timer);
|
|
437
|
+
relinka("success", `Merge completed in ${prettyMilliseconds(elapsed)}`);
|
|
243
438
|
}
|
|
244
439
|
});
|