@reliverse/dler 1.7.10 → 1.7.12
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/README.md +216 -10
- package/bin/app/build/cmd.js +11 -2
- package/bin/app/build/impl.d.ts +3 -2
- package/bin/app/build/impl.js +52 -36
- package/bin/app/merge/cmd.d.ts +30 -5
- package/bin/app/merge/cmd.js +586 -258
- package/bin/app/mkdist/cmd.d.ts +0 -4
- package/bin/app/mkdist/cmd.js +0 -4
- package/bin/app/mock/cmd.d.ts +41 -0
- package/bin/app/mock/cmd.js +284 -0
- package/bin/app/mock/mock.d.ts +11 -0
- package/bin/app/mock/mock.js +97 -0
- package/bin/app/pub/cmd.js +10 -1
- package/bin/libs/sdk/sdk-impl/build/build-library.js +22 -20
- package/bin/libs/sdk/sdk-impl/build/build-regular.js +47 -42
- package/bin/libs/sdk/sdk-impl/build/bundlers/unified/build.d.ts +4 -1
- package/bin/libs/sdk/sdk-impl/build/bundlers/unified/build.js +6 -7
- package/bin/libs/sdk/sdk-impl/build/bundlers/unified/mkdist/mkdist-impl/loaders/sass.js +1 -1
- package/bin/libs/sdk/sdk-impl/build/bundlers/unified/mkdist/mkdist-impl/make.js +1 -1
- package/bin/libs/sdk/sdk-impl/build/bundlers/unified/mkdist/mkdist-impl/utils/dts.js +1 -1
- package/bin/libs/sdk/sdk-impl/build/bundlers/unified/mkdist/mkdist-impl/utils/vue-dts.js +1 -1
- package/bin/libs/sdk/sdk-impl/cfg/info.js +1 -1
- package/bin/libs/sdk/sdk-impl/library-flow.js +2 -16
- package/bin/libs/sdk/sdk-impl/pub/pub-library.js +2 -9
- package/bin/libs/sdk/sdk-impl/pub/pub-regular.js +2 -14
- package/bin/libs/sdk/sdk-impl/spell/spell-parser.d.ts +1 -1
- package/bin/libs/sdk/sdk-impl/spell/spell-parser.js +2 -2
- package/bin/libs/sdk/sdk-impl/spell/spell-types.d.ts +27 -1
- package/bin/libs/sdk/sdk-impl/utils/b-exts.d.ts +5 -0
- package/bin/libs/sdk/sdk-impl/utils/b-exts.js +406 -0
- package/bin/libs/sdk/sdk-impl/utils/binary.d.ts +4 -0
- package/bin/libs/sdk/sdk-impl/utils/binary.js +11 -0
- package/bin/libs/sdk/sdk-impl/utils/file-type.d.ts +21 -0
- package/bin/libs/sdk/sdk-impl/utils/file-type.js +78 -0
- package/bin/libs/sdk/sdk-impl/utils/finalize.js +3 -3
- package/bin/libs/sdk/sdk-impl/utils/utils-clean.js +0 -1
- package/bin/libs/sdk/sdk-impl/utils/utils-error.d.ts +3 -2
- package/bin/libs/sdk/sdk-impl/utils/utils-error.js +10 -10
- package/bin/libs/sdk/sdk-impl/utils/utils-fs.d.ts +28 -6
- package/bin/libs/sdk/sdk-impl/utils/utils-fs.js +73 -124
- package/bin/libs/sdk/sdk-impl/utils/utils-jsr-json.js +20 -2
- package/bin/libs/sdk/sdk-impl/utils/utils-security.d.ts +15 -0
- package/bin/libs/sdk/sdk-impl/utils/utils-security.js +102 -0
- package/bin/libs/sdk/sdk-mod.d.ts +3 -3
- package/bin/libs/sdk/sdk-mod.js +3 -2
- package/bin/libs/sdk/sdk-types.d.ts +15 -4
- package/bin/templates/App.css +31 -0
- package/bin/templates/App.tsx +21 -0
- package/bin/templates/DOCS.md +31 -0
- package/bin/templates/globals.css +27 -0
- package/bin/templates/hello/world.ts +1 -0
- package/bin/templates/index.html +12 -0
- package/bin/templates/main.tsx +11 -0
- package/package.json +3 -3
- package/bin/app/conv/README.md +0 -3
- package/bin/app/merge/README.md +0 -125
- package/bin/app/split/README.md +0 -13
- /package/bin/libs/sdk/sdk-impl/utils/{tools/tools-agg.d.ts → tools-agg.d.ts} +0 -0
- /package/bin/libs/sdk/sdk-impl/utils/{tools/tools-agg.js → tools-agg.js} +0 -0
- /package/bin/libs/sdk/sdk-impl/utils/{tools/tools-impl.d.ts → tools-impl.d.ts} +0 -0
- /package/bin/libs/sdk/sdk-impl/utils/{tools/tools-impl.js → tools-impl.js} +0 -0
package/bin/app/merge/cmd.js
CHANGED
|
@@ -2,45 +2,28 @@ import path from "@reliverse/pathkit";
|
|
|
2
2
|
import { glob } from "@reliverse/reglob";
|
|
3
3
|
import fs from "@reliverse/relifso";
|
|
4
4
|
import { relinka } from "@reliverse/relinka";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
defineCommand,
|
|
7
|
+
inputPrompt,
|
|
8
|
+
confirmPrompt,
|
|
9
|
+
multiselectPrompt,
|
|
10
|
+
defineArgs
|
|
11
|
+
} from "@reliverse/rempts";
|
|
12
|
+
import MagicString from "magic-string";
|
|
13
|
+
import { Bundle } from "magic-string";
|
|
6
14
|
import pMap from "p-map";
|
|
7
15
|
import prettyMilliseconds from "pretty-ms";
|
|
16
|
+
import { isBinaryExt } from "../../libs/sdk/sdk-impl/utils/binary.js";
|
|
8
17
|
import { createPerfTimer, getElapsedPerfTime } from "../../libs/sdk/sdk-impl/utils/utils-perf.js";
|
|
18
|
+
import {
|
|
19
|
+
checkPermissions,
|
|
20
|
+
checkFileSize,
|
|
21
|
+
handleError,
|
|
22
|
+
validateFileExists,
|
|
23
|
+
sanitizeInput,
|
|
24
|
+
validateMergeOperation
|
|
25
|
+
} from "../../libs/sdk/sdk-impl/utils/utils-security.js";
|
|
9
26
|
const DEFAULT_IGNORES = ["**/.git/**", "**/node_modules/**"];
|
|
10
|
-
const BINARY_EXTS = [
|
|
11
|
-
"png",
|
|
12
|
-
"jpg",
|
|
13
|
-
"jpeg",
|
|
14
|
-
"gif",
|
|
15
|
-
"bmp",
|
|
16
|
-
"webp",
|
|
17
|
-
"svg",
|
|
18
|
-
"ico",
|
|
19
|
-
"mp4",
|
|
20
|
-
"mov",
|
|
21
|
-
"avi",
|
|
22
|
-
"mkv",
|
|
23
|
-
"mp3",
|
|
24
|
-
"wav",
|
|
25
|
-
"flac",
|
|
26
|
-
"ogg",
|
|
27
|
-
"pdf",
|
|
28
|
-
"zip",
|
|
29
|
-
"gz",
|
|
30
|
-
"tar",
|
|
31
|
-
"rar",
|
|
32
|
-
"7z",
|
|
33
|
-
"exe",
|
|
34
|
-
"dll",
|
|
35
|
-
"bin",
|
|
36
|
-
"woff",
|
|
37
|
-
"woff2",
|
|
38
|
-
"ttf",
|
|
39
|
-
"eot",
|
|
40
|
-
"class",
|
|
41
|
-
"jar"
|
|
42
|
-
];
|
|
43
|
-
const BINARY_SET = new Set(BINARY_EXTS);
|
|
44
27
|
const COMMENT_MAP = {
|
|
45
28
|
js: "// ",
|
|
46
29
|
jsx: "// ",
|
|
@@ -77,94 +60,208 @@ const COMMENT_MAP = {
|
|
|
77
60
|
};
|
|
78
61
|
const DEFAULT_COMMENT = "// ";
|
|
79
62
|
const DEFAULT_SEPARATOR_RAW = "\\n\\n";
|
|
80
|
-
const
|
|
81
|
-
const
|
|
82
|
-
|
|
63
|
+
const normalizeGlobPattern = (pattern) => {
|
|
64
|
+
const sanitizedPattern = sanitizeInput(pattern);
|
|
65
|
+
if (!sanitizedPattern.includes("*") && !sanitizedPattern.includes("?") && !sanitizedPattern.endsWith("/")) {
|
|
66
|
+
return `${sanitizedPattern}/**/*`;
|
|
67
|
+
}
|
|
68
|
+
return sanitizedPattern;
|
|
83
69
|
};
|
|
84
|
-
const
|
|
85
|
-
`;
|
|
86
|
-
const parseCSV = (s) => s.split(",").map((t) => t.trim()).filter(Boolean);
|
|
70
|
+
const parseCSV = (s) => s.split(",").map((t) => sanitizeInput(t.trim())).filter(Boolean);
|
|
87
71
|
const unescape = (s) => s.replace(/\\n/g, "\n").replace(/\\t/g, " ");
|
|
88
|
-
const maybePrompt = async (
|
|
89
|
-
if (
|
|
72
|
+
const maybePrompt = async (interactive, value, promptFn) => {
|
|
73
|
+
if (!interactive || value !== void 0) return value;
|
|
90
74
|
return promptFn();
|
|
91
75
|
};
|
|
92
76
|
const collectFiles = async (include, extraIgnore, recursive, sortBy) => {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
77
|
+
try {
|
|
78
|
+
const normalizedInclude = include.map(normalizeGlobPattern);
|
|
79
|
+
const files = await glob(normalizedInclude, {
|
|
80
|
+
ignore: [...DEFAULT_IGNORES, ...extraIgnore.map(sanitizeInput)],
|
|
81
|
+
absolute: true,
|
|
82
|
+
onlyFiles: true,
|
|
83
|
+
deep: recursive ? void 0 : 1
|
|
84
|
+
});
|
|
85
|
+
for (const file of files) {
|
|
86
|
+
await validateFileExists(file, "merge");
|
|
87
|
+
await checkFileSize(file);
|
|
88
|
+
await checkPermissions(file, "read");
|
|
89
|
+
}
|
|
90
|
+
let filtered = [...new Set(files)];
|
|
91
|
+
if (sortBy === "name") {
|
|
92
|
+
filtered.sort((a, b) => path.basename(a).localeCompare(path.basename(b)));
|
|
93
|
+
} else if (sortBy === "path") {
|
|
94
|
+
filtered.sort();
|
|
95
|
+
} else if (sortBy === "mtime") {
|
|
96
|
+
filtered = await pMap(filtered, async (f) => ({ f, mtime: (await fs.stat(f)).mtimeMs }), {
|
|
97
|
+
concurrency: 8
|
|
98
|
+
}).then((arr) => arr.sort((a, b) => a.mtime - b.mtime).map((x) => x.f));
|
|
99
|
+
}
|
|
100
|
+
return filtered;
|
|
101
|
+
} catch (error) {
|
|
102
|
+
handleError(error, "collectFiles");
|
|
103
|
+
return [];
|
|
108
104
|
}
|
|
109
|
-
return filtered;
|
|
110
105
|
};
|
|
111
|
-
const writeResult = async (sections,
|
|
112
|
-
|
|
106
|
+
const writeResult = async (sections, _separator, toFile, toStdout, dryRun, backup, generateSourceMap = false) => {
|
|
107
|
+
try {
|
|
108
|
+
const bundle = new Bundle();
|
|
109
|
+
for (const section of sections) {
|
|
110
|
+
bundle.addSource({
|
|
111
|
+
content: new MagicString(section)
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
const content = bundle.toString();
|
|
115
|
+
const finalContent = `${content}
|
|
113
116
|
`;
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
117
|
+
if (toStdout || !toFile) {
|
|
118
|
+
process.stdout.write(finalContent);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const sanitizedPath = sanitizeInput(toFile);
|
|
122
|
+
const dir = path.dirname(sanitizedPath);
|
|
123
|
+
if (dir && dir !== ".") {
|
|
124
|
+
await fs.ensureDir(dir);
|
|
125
|
+
await checkPermissions(dir, "write");
|
|
126
|
+
}
|
|
127
|
+
if (backup && await fs.pathExists(sanitizedPath)) {
|
|
128
|
+
const backupPath = `${sanitizedPath}.${Date.now()}.bak`;
|
|
129
|
+
await checkPermissions(sanitizedPath, "read");
|
|
130
|
+
await fs.copyFile(sanitizedPath, backupPath);
|
|
131
|
+
}
|
|
132
|
+
if (!dryRun) {
|
|
133
|
+
await checkPermissions(sanitizedPath, "write");
|
|
134
|
+
await fs.writeFile(sanitizedPath, finalContent, "utf8");
|
|
135
|
+
if (generateSourceMap) {
|
|
136
|
+
const map = bundle.generateMap({
|
|
137
|
+
file: path.basename(sanitizedPath),
|
|
138
|
+
source: sanitizedPath,
|
|
139
|
+
includeContent: true,
|
|
140
|
+
hires: true
|
|
141
|
+
});
|
|
142
|
+
const mapPath = `${sanitizedPath}.map`;
|
|
143
|
+
await fs.writeFile(mapPath, map.toString(), "utf8");
|
|
144
|
+
const sourceMapRef = `
|
|
145
|
+
//# sourceMappingURL=${path.basename(mapPath)}`;
|
|
146
|
+
await fs.appendFile(sanitizedPath, sourceMapRef, "utf8");
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
} catch (error) {
|
|
150
|
+
handleError(error, "writeResult");
|
|
126
151
|
}
|
|
127
152
|
};
|
|
128
153
|
const writeFilesPreserveStructure = async (files, outDir, preserveStructure, increment, concurrency, dryRun, backup) => {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
154
|
+
try {
|
|
155
|
+
if (!files?.length) {
|
|
156
|
+
throw new Error("No files provided for merge operation");
|
|
157
|
+
}
|
|
158
|
+
const cwd = process.cwd();
|
|
159
|
+
const fileNameCounts = /* @__PURE__ */ new Map();
|
|
160
|
+
await validateMergeOperation(files);
|
|
161
|
+
await pMap(
|
|
162
|
+
files,
|
|
163
|
+
async (file) => {
|
|
164
|
+
const sanitizedFile = sanitizeInput(file);
|
|
165
|
+
const relPath = preserveStructure ? path.relative(cwd, sanitizedFile) : path.basename(sanitizedFile);
|
|
166
|
+
let destPath = path.join(outDir, relPath);
|
|
167
|
+
if (increment) {
|
|
168
|
+
const dir = path.dirname(destPath);
|
|
169
|
+
const base = path.basename(destPath);
|
|
170
|
+
let dirMap = fileNameCounts.get(dir);
|
|
171
|
+
if (!dirMap) {
|
|
172
|
+
dirMap = /* @__PURE__ */ new Map();
|
|
173
|
+
fileNameCounts.set(dir, dirMap);
|
|
174
|
+
}
|
|
175
|
+
const count = dirMap.get(base) || 0;
|
|
176
|
+
if (count > 0) {
|
|
177
|
+
const extMatch = base.match(/(.*)(\.[^./\\]+)$/);
|
|
178
|
+
let newBase;
|
|
179
|
+
if (extMatch) {
|
|
180
|
+
newBase = `${extMatch[1]}-${count + 1}${extMatch[2]}`;
|
|
181
|
+
} else {
|
|
182
|
+
newBase = `${base}-${count + 1}`;
|
|
183
|
+
}
|
|
184
|
+
destPath = path.join(dir, newBase);
|
|
152
185
|
}
|
|
153
|
-
|
|
186
|
+
dirMap.set(base, count + 1);
|
|
187
|
+
}
|
|
188
|
+
await fs.ensureDir(path.dirname(destPath));
|
|
189
|
+
if (backup && await fs.pathExists(destPath)) {
|
|
190
|
+
const backupPath = `${destPath}.${Date.now()}.bak`;
|
|
191
|
+
await checkPermissions(destPath, "read");
|
|
192
|
+
await fs.copyFile(destPath, backupPath);
|
|
193
|
+
}
|
|
194
|
+
if (!dryRun) {
|
|
195
|
+
await checkPermissions(destPath, "write");
|
|
196
|
+
await fs.copyFile(sanitizedFile, destPath);
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
{ concurrency }
|
|
200
|
+
);
|
|
201
|
+
} catch (error) {
|
|
202
|
+
handleError(error, "writeFilesPreserveStructure");
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
const processSection = (raw, rel, prefix, pathAbove, injectPath) => {
|
|
206
|
+
const magic = new MagicString(raw);
|
|
207
|
+
if (pathAbove) {
|
|
208
|
+
magic.prepend(`${prefix}${rel}
|
|
209
|
+
`);
|
|
210
|
+
}
|
|
211
|
+
if (injectPath) {
|
|
212
|
+
magic.append(`
|
|
213
|
+
${prefix}${rel}`);
|
|
214
|
+
}
|
|
215
|
+
return magic.toString();
|
|
216
|
+
};
|
|
217
|
+
const updateTemplateInFile = async (templateName, templateContent, targetFile, dryRun, backup, generateSourceMap = false) => {
|
|
218
|
+
try {
|
|
219
|
+
const fileContent = await fs.readFile(targetFile, "utf8");
|
|
220
|
+
const magic = new MagicString(fileContent);
|
|
221
|
+
const templateStart = fileContent.indexOf(`export const ${templateName}: Template = {`);
|
|
222
|
+
if (templateStart === -1) {
|
|
223
|
+
throw new Error(`Template ${templateName} not found in file ${targetFile}`);
|
|
224
|
+
}
|
|
225
|
+
let currentBracketCount = 0;
|
|
226
|
+
let endIndex = templateStart;
|
|
227
|
+
for (let i = templateStart; i < fileContent.length; i++) {
|
|
228
|
+
const char = fileContent[i];
|
|
229
|
+
if (char === "{") {
|
|
230
|
+
currentBracketCount++;
|
|
231
|
+
} else if (char === "}") {
|
|
232
|
+
currentBracketCount--;
|
|
233
|
+
if (currentBracketCount === 0) {
|
|
234
|
+
endIndex = i + 1;
|
|
235
|
+
break;
|
|
154
236
|
}
|
|
155
|
-
dirMap.set(base, count + 1);
|
|
156
|
-
}
|
|
157
|
-
await fs.ensureDir(path.dirname(destPath));
|
|
158
|
-
if (backup && await fs.pathExists(destPath)) {
|
|
159
|
-
const backupPath = `${destPath}.${Date.now()}.bak`;
|
|
160
|
-
await fs.copyFile(destPath, backupPath);
|
|
161
|
-
}
|
|
162
|
-
if (!dryRun) {
|
|
163
|
-
await fs.copyFile(file, destPath);
|
|
164
237
|
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
|
|
238
|
+
}
|
|
239
|
+
magic.overwrite(templateStart, endIndex, templateContent);
|
|
240
|
+
if (dryRun) {
|
|
241
|
+
relinka("verbose", `[DRY RUN] Would update template ${templateName} in ${targetFile}`);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
if (backup) {
|
|
245
|
+
const backupPath = `${targetFile}.${Date.now()}.bak`;
|
|
246
|
+
await fs.copyFile(targetFile, backupPath);
|
|
247
|
+
}
|
|
248
|
+
await fs.writeFile(targetFile, magic.toString(), "utf8");
|
|
249
|
+
if (generateSourceMap) {
|
|
250
|
+
const map = magic.generateMap({
|
|
251
|
+
file: path.basename(targetFile),
|
|
252
|
+
source: targetFile,
|
|
253
|
+
includeContent: true,
|
|
254
|
+
hires: true
|
|
255
|
+
});
|
|
256
|
+
const mapPath = `${targetFile}.map`;
|
|
257
|
+
await fs.writeFile(mapPath, map.toString(), "utf8");
|
|
258
|
+
const sourceMapRef = `
|
|
259
|
+
//# sourceMappingURL=${path.basename(mapPath)}`;
|
|
260
|
+
await fs.appendFile(targetFile, sourceMapRef, "utf8");
|
|
261
|
+
}
|
|
262
|
+
} catch (error) {
|
|
263
|
+
handleError(error, "updateTemplateInFile");
|
|
264
|
+
}
|
|
168
265
|
};
|
|
169
266
|
export default defineCommand({
|
|
170
267
|
meta: {
|
|
@@ -172,7 +269,8 @@ export default defineCommand({
|
|
|
172
269
|
version: "1.0.0",
|
|
173
270
|
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."
|
|
174
271
|
},
|
|
175
|
-
args: {
|
|
272
|
+
args: defineArgs({
|
|
273
|
+
dev: { type: "boolean", description: "Generate template for development" },
|
|
176
274
|
s: { type: "array", description: "Input glob patterns" },
|
|
177
275
|
d: { type: "string", description: "Output file path or directory" },
|
|
178
276
|
ignore: { type: "array", description: "Extra ignore patterns" },
|
|
@@ -219,8 +317,7 @@ export default defineCommand({
|
|
|
219
317
|
},
|
|
220
318
|
increment: {
|
|
221
319
|
type: "boolean",
|
|
222
|
-
description: "Attach an incrementing index to each output filename if set (default: false)"
|
|
223
|
-
default: false
|
|
320
|
+
description: "Attach an incrementing index to each output filename if set (default: false)"
|
|
224
321
|
},
|
|
225
322
|
concurrency: {
|
|
226
323
|
type: "number",
|
|
@@ -234,18 +331,15 @@ export default defineCommand({
|
|
|
234
331
|
},
|
|
235
332
|
dryRun: {
|
|
236
333
|
type: "boolean",
|
|
237
|
-
description: "Show what would be done, but don't write files"
|
|
238
|
-
default: false
|
|
334
|
+
description: "Show what would be done, but don't write files"
|
|
239
335
|
},
|
|
240
336
|
backup: {
|
|
241
337
|
type: "boolean",
|
|
242
|
-
description: "Backup output files before overwriting"
|
|
243
|
-
default: false
|
|
338
|
+
description: "Backup output files before overwriting"
|
|
244
339
|
},
|
|
245
340
|
dedupe: {
|
|
246
341
|
type: "boolean",
|
|
247
|
-
description: "Remove duplicate file contents in merge"
|
|
248
|
-
default: false
|
|
342
|
+
description: "Remove duplicate file contents in merge"
|
|
249
343
|
},
|
|
250
344
|
header: {
|
|
251
345
|
type: "string",
|
|
@@ -255,165 +349,399 @@ export default defineCommand({
|
|
|
255
349
|
type: "string",
|
|
256
350
|
description: "Footer text to add at the end of merged output"
|
|
257
351
|
},
|
|
352
|
+
"select-files": {
|
|
353
|
+
type: "boolean",
|
|
354
|
+
description: "Prompt for file selection before merging"
|
|
355
|
+
},
|
|
258
356
|
interactive: {
|
|
259
357
|
type: "boolean",
|
|
260
|
-
description: "
|
|
261
|
-
|
|
358
|
+
description: "Enable interactive mode with prompts (default: false)"
|
|
359
|
+
},
|
|
360
|
+
"as-template": {
|
|
361
|
+
type: "boolean",
|
|
362
|
+
description: "Generate a TypeScript file with MOCK_TEMPLATES structure"
|
|
363
|
+
},
|
|
364
|
+
"custom-template-name": {
|
|
365
|
+
type: "string",
|
|
366
|
+
description: "Custom template name when using --as-template"
|
|
367
|
+
},
|
|
368
|
+
whitelabel: {
|
|
369
|
+
type: "string",
|
|
370
|
+
description: "Custom prefix to use instead of 'DLER' in template generation",
|
|
371
|
+
default: "DLER"
|
|
372
|
+
},
|
|
373
|
+
sourcemap: {
|
|
374
|
+
type: "boolean",
|
|
375
|
+
description: "Generate source map for the merged output"
|
|
376
|
+
},
|
|
377
|
+
"update-template": {
|
|
378
|
+
type: "string",
|
|
379
|
+
description: "Update specific template in existing mock template file",
|
|
380
|
+
dependencies: ["as-template"]
|
|
262
381
|
}
|
|
263
|
-
},
|
|
382
|
+
}),
|
|
264
383
|
async run({ args }) {
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
const
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
384
|
+
const customTemplateName = args["custom-template-name"];
|
|
385
|
+
try {
|
|
386
|
+
const timer = createPerfTimer();
|
|
387
|
+
const interactive = args.interactive ?? false;
|
|
388
|
+
const isDev = args.dev ?? false;
|
|
389
|
+
const whitelabel = args.whitelabel ?? "DLER";
|
|
390
|
+
let include = args.s ?? [];
|
|
391
|
+
if (include.length === 0) {
|
|
392
|
+
const raw = await maybePrompt(
|
|
393
|
+
interactive,
|
|
394
|
+
void 0,
|
|
395
|
+
() => inputPrompt({
|
|
396
|
+
title: "Input glob patterns (comma separated)",
|
|
397
|
+
placeholder: "src/**/*.ts, !**/*.test.ts"
|
|
398
|
+
})
|
|
399
|
+
);
|
|
400
|
+
if (raw) include = parseCSV(raw);
|
|
401
|
+
}
|
|
402
|
+
if (include.length === 0) {
|
|
403
|
+
throw new Error("No input patterns supplied and prompts disabled");
|
|
404
|
+
}
|
|
405
|
+
let ignore = args.ignore ?? [];
|
|
406
|
+
if (ignore.length === 0) {
|
|
407
|
+
const raw = await maybePrompt(
|
|
408
|
+
interactive,
|
|
409
|
+
void 0,
|
|
410
|
+
() => inputPrompt({
|
|
411
|
+
title: "Ignore patterns (comma separated, blank for none)",
|
|
412
|
+
placeholder: "**/*.d.ts"
|
|
413
|
+
})
|
|
414
|
+
);
|
|
415
|
+
if (raw) ignore = parseCSV(raw);
|
|
416
|
+
}
|
|
417
|
+
let customComment = args.comment;
|
|
418
|
+
if (customComment === void 0) {
|
|
419
|
+
const want = await maybePrompt(
|
|
420
|
+
interactive,
|
|
421
|
+
void 0,
|
|
422
|
+
() => confirmPrompt({
|
|
423
|
+
title: "Provide custom comment prefix?",
|
|
424
|
+
defaultValue: false
|
|
425
|
+
})
|
|
426
|
+
);
|
|
427
|
+
if (want) {
|
|
428
|
+
customComment = await inputPrompt({
|
|
429
|
+
title: "Custom comment prefix (include trailing space if needed)",
|
|
430
|
+
placeholder: "# "
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
const forceComment = args.forceComment ?? false;
|
|
435
|
+
const injectPath = !args.noPath;
|
|
436
|
+
const pathAbove = args.pathAbove ?? true;
|
|
437
|
+
const sepRaw = args.separator ?? await maybePrompt(
|
|
438
|
+
interactive,
|
|
284
439
|
void 0,
|
|
285
440
|
() => inputPrompt({
|
|
286
|
-
title: "
|
|
287
|
-
placeholder:
|
|
441
|
+
title: "Separator between files (\\n for newline, blank \u2192 blank line)",
|
|
442
|
+
placeholder: DEFAULT_SEPARATOR_RAW
|
|
288
443
|
})
|
|
289
|
-
);
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
void 0,
|
|
297
|
-
() => confirmPrompt({
|
|
298
|
-
title: "Provide custom comment prefix?",
|
|
444
|
+
) ?? DEFAULT_SEPARATOR_RAW;
|
|
445
|
+
const separator = unescape(sepRaw);
|
|
446
|
+
let stdoutFlag = args.stdout ?? false;
|
|
447
|
+
let outFile = args.d;
|
|
448
|
+
if (!stdoutFlag && !outFile && interactive) {
|
|
449
|
+
stdoutFlag = await confirmPrompt({
|
|
450
|
+
title: "Print result to stdout?",
|
|
299
451
|
defaultValue: false
|
|
300
|
-
})
|
|
301
|
-
);
|
|
302
|
-
if (want) {
|
|
303
|
-
customComment = await inputPrompt({
|
|
304
|
-
title: "Custom comment prefix (include trailing space if needed)",
|
|
305
|
-
placeholder: "# "
|
|
306
452
|
});
|
|
453
|
+
if (!stdoutFlag) {
|
|
454
|
+
outFile = await inputPrompt({
|
|
455
|
+
title: "Output file path (blank \u2192 merged.<ext>)",
|
|
456
|
+
placeholder: ""
|
|
457
|
+
});
|
|
458
|
+
if (!outFile) {
|
|
459
|
+
const ext = await inputPrompt({
|
|
460
|
+
title: "File extension",
|
|
461
|
+
placeholder: args.format
|
|
462
|
+
});
|
|
463
|
+
outFile = `merged.${(ext || args.format).replace(/^\./, "")}`;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
307
466
|
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
placeholder: ""
|
|
467
|
+
const recursive = args.recursive ?? true;
|
|
468
|
+
const preserveStructure = args.preserveStructure ?? true;
|
|
469
|
+
const increment = args.increment ?? false;
|
|
470
|
+
const concurrency = args.concurrency ?? 8;
|
|
471
|
+
const sortBy = args.sort;
|
|
472
|
+
const dryRun = args.dryRun ?? false;
|
|
473
|
+
const backup = args.backup ?? false;
|
|
474
|
+
const dedupe = args.dedupe ?? false;
|
|
475
|
+
const header = args.header;
|
|
476
|
+
const footer = args.footer;
|
|
477
|
+
const selectFiles = args["select-files"] ?? false;
|
|
478
|
+
const asTemplate = args["as-template"] ?? false;
|
|
479
|
+
let files = await collectFiles(include, ignore, recursive, sortBy);
|
|
480
|
+
if (files.length === 0) {
|
|
481
|
+
throw new Error("No text files matched given patterns (binary/media files are skipped)");
|
|
482
|
+
}
|
|
483
|
+
if (selectFiles && interactive) {
|
|
484
|
+
const selected = await multiselectPrompt({
|
|
485
|
+
title: "Select files to merge",
|
|
486
|
+
options: files.map((f) => ({
|
|
487
|
+
label: path.relative(process.cwd(), f),
|
|
488
|
+
value: f
|
|
489
|
+
}))
|
|
332
490
|
});
|
|
491
|
+
files = Array.isArray(selected) ? selected : [selected];
|
|
492
|
+
if (files.length === 0) {
|
|
493
|
+
throw new Error("No files selected for merging");
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
if (args["update-template"]) {
|
|
333
497
|
if (!outFile) {
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
498
|
+
throw new Error("Output file path required for template update");
|
|
499
|
+
}
|
|
500
|
+
const templateName = args["update-template"];
|
|
501
|
+
const templateData = {
|
|
502
|
+
name: templateName.replace(/_DLER_TEMPLATE$/, "").replace(/_/g, " ").toLowerCase(),
|
|
503
|
+
// Convert REACT_DLER_TEMPLATE to "react"
|
|
504
|
+
description: `Template generated from ${files.length} files`,
|
|
505
|
+
config: {
|
|
506
|
+
files: {}
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
for (const file of files) {
|
|
510
|
+
const relPath = path.relative(process.cwd(), file);
|
|
511
|
+
const ext = path.extname(file).slice(1).toLowerCase();
|
|
512
|
+
const isBinary = await isBinaryExt(file);
|
|
513
|
+
const fileName = path.basename(file).toLowerCase();
|
|
514
|
+
let content = "";
|
|
515
|
+
let type = "binary";
|
|
516
|
+
if (!isBinary) {
|
|
517
|
+
try {
|
|
518
|
+
const fileContent = await fs.readFile(file, "utf8");
|
|
519
|
+
if (ext === "json") {
|
|
520
|
+
const jsonContent = JSON.parse(fileContent);
|
|
521
|
+
if (fileName === "package.json") {
|
|
522
|
+
content = {
|
|
523
|
+
...jsonContent
|
|
524
|
+
};
|
|
525
|
+
} else if (fileName === "tsconfig.json") {
|
|
526
|
+
content = {
|
|
527
|
+
...jsonContent
|
|
528
|
+
};
|
|
529
|
+
} else {
|
|
530
|
+
content = jsonContent;
|
|
531
|
+
}
|
|
532
|
+
type = "json";
|
|
533
|
+
} else {
|
|
534
|
+
content = fileContent;
|
|
535
|
+
type = "text";
|
|
536
|
+
}
|
|
537
|
+
} catch (error) {
|
|
538
|
+
type = "binary";
|
|
539
|
+
if (asTemplate || args["update-template"]) {
|
|
540
|
+
relinka(
|
|
541
|
+
"warn",
|
|
542
|
+
`Skipped file "${relPath}" due to error: ${error instanceof Error ? error.message : "unknown error"}`
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
} else if (asTemplate || args["update-template"]) {
|
|
547
|
+
relinka("warn", `Skipped binary file "${relPath}"`);
|
|
548
|
+
}
|
|
549
|
+
templateData.config.files[relPath] = {
|
|
550
|
+
content,
|
|
551
|
+
type
|
|
552
|
+
};
|
|
339
553
|
}
|
|
554
|
+
const templateContent = `export const ${templateName}: Template = ${JSON.stringify(
|
|
555
|
+
templateData,
|
|
556
|
+
null,
|
|
557
|
+
2
|
|
558
|
+
).replace(/"([^"]+)":/g, (_, key) => {
|
|
559
|
+
return /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(key) ? `${key}:` : `"${key}":`;
|
|
560
|
+
})};`;
|
|
561
|
+
await updateTemplateInFile(
|
|
562
|
+
templateName,
|
|
563
|
+
templateContent,
|
|
564
|
+
outFile,
|
|
565
|
+
dryRun,
|
|
566
|
+
backup,
|
|
567
|
+
args.sourcemap
|
|
568
|
+
);
|
|
569
|
+
const elapsed2 = getElapsedPerfTime(timer);
|
|
570
|
+
relinka(
|
|
571
|
+
"success",
|
|
572
|
+
`Successfully ${dryRun ? "would update" : "updated"} template "${templateName}" in "${outFile}" (in ${prettyMilliseconds(elapsed2)})`
|
|
573
|
+
);
|
|
574
|
+
return;
|
|
340
575
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
576
|
+
if (asTemplate) {
|
|
577
|
+
const timer2 = {
|
|
578
|
+
startTime: performance.now(),
|
|
579
|
+
pausedAt: null,
|
|
580
|
+
pausedDuration: 0
|
|
581
|
+
};
|
|
582
|
+
if (!outFile) {
|
|
583
|
+
outFile = "template.ts";
|
|
584
|
+
} else if (!outFile.endsWith(".ts")) {
|
|
585
|
+
outFile = `${outFile}.ts`;
|
|
586
|
+
}
|
|
587
|
+
const templateName = customTemplateName || path.basename(outFile, ".ts");
|
|
588
|
+
const templateConstName = templateName.replace(/[^a-zA-Z0-9]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "").replace(/[A-Z]/g, (letter) => `_${letter}`).replace(/^_/, "").toUpperCase() + `_${whitelabel}_TEMPLATE`;
|
|
589
|
+
const templateKey = templateName.replace(/[^a-zA-Z0-9]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "").toLowerCase();
|
|
590
|
+
const template = {
|
|
591
|
+
name: templateName.toLowerCase(),
|
|
592
|
+
// Ensure name is lowercase
|
|
593
|
+
description: `Template generated from ${files.length} files`,
|
|
594
|
+
config: {
|
|
595
|
+
files: {}
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
for (const file of files) {
|
|
599
|
+
const relPath = path.relative(process.cwd(), file);
|
|
600
|
+
const ext = path.extname(file).slice(1).toLowerCase();
|
|
601
|
+
const isBinary = await isBinaryExt(file);
|
|
602
|
+
const fileName = path.basename(file).toLowerCase();
|
|
603
|
+
let content = "";
|
|
604
|
+
let type = "binary";
|
|
605
|
+
if (!isBinary) {
|
|
606
|
+
try {
|
|
607
|
+
const fileContent = await fs.readFile(file, "utf8");
|
|
608
|
+
if (ext === "json") {
|
|
609
|
+
const jsonContent = JSON.parse(fileContent);
|
|
610
|
+
if (fileName === "package.json") {
|
|
611
|
+
content = {
|
|
612
|
+
...jsonContent
|
|
613
|
+
};
|
|
614
|
+
} else if (fileName === "tsconfig.json") {
|
|
615
|
+
content = {
|
|
616
|
+
...jsonContent
|
|
617
|
+
};
|
|
618
|
+
} else {
|
|
619
|
+
content = jsonContent;
|
|
620
|
+
}
|
|
621
|
+
type = "json";
|
|
622
|
+
} else {
|
|
623
|
+
content = fileContent;
|
|
624
|
+
type = "text";
|
|
625
|
+
}
|
|
626
|
+
} catch (error) {
|
|
627
|
+
type = "binary";
|
|
628
|
+
if (asTemplate || args["update-template"]) {
|
|
629
|
+
relinka(
|
|
630
|
+
"warn",
|
|
631
|
+
`Skipped file "${relPath}" due to error: ${error instanceof Error ? error.message : "unknown error"}`
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
} else if (asTemplate || args["update-template"]) {
|
|
636
|
+
relinka("warn", `Skipped binary file "${relPath}"`);
|
|
637
|
+
}
|
|
638
|
+
template.config.files[relPath] = {
|
|
639
|
+
content,
|
|
640
|
+
type
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
const tsContent = `import type { Template } from "${isDev ? "~/libs/sdk/sdk-types" : "@reliverse/dler-sdk"}";
|
|
644
|
+
${(() => {
|
|
645
|
+
const files2 = template.config.files;
|
|
646
|
+
if (!files2) return "";
|
|
647
|
+
const hasPackageJson = Object.values(files2).some(
|
|
648
|
+
(f) => f.type === "json" && f.content
|
|
649
|
+
);
|
|
650
|
+
const hasTSConfig = Object.values(files2).some(
|
|
651
|
+
(f) => f.type === "json" && f.content
|
|
652
|
+
);
|
|
653
|
+
if (!hasPackageJson && !hasTSConfig) return "";
|
|
654
|
+
const imports = [];
|
|
655
|
+
if (hasPackageJson) imports.push("PackageJson");
|
|
656
|
+
if (hasTSConfig) imports.push("TSConfig");
|
|
657
|
+
return `import type { ${imports.join(", ")} } from "pkg-types";
|
|
658
|
+
`;
|
|
659
|
+
})()}
|
|
660
|
+
export const ${templateConstName}: Template = ${JSON.stringify(template, null, 2).replace(/"([^"]+)":/g, (_, key) => {
|
|
661
|
+
return /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(key) ? `${key}:` : `"${key}":`;
|
|
662
|
+
}).replace(/"([^"]+)":/g, (_, key) => {
|
|
663
|
+
return /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(key) ? `${key}:` : `"${key}":`;
|
|
664
|
+
}).replace(/"([^"]+)":/g, (_, key) => {
|
|
665
|
+
return /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(key) ? `${key}:` : `"${key}":`;
|
|
666
|
+
})};
|
|
667
|
+
export const ${whitelabel}_TEMPLATES = {
|
|
668
|
+
${templateKey}: ${templateConstName},
|
|
669
|
+
} as const;
|
|
670
|
+
export type ${whitelabel}_TEMPLATE_NAMES = keyof typeof ${whitelabel}_TEMPLATES;
|
|
671
|
+
export const ${whitelabel.toLowerCase()}TemplatesMap: Record<string, ${whitelabel}_TEMPLATE_NAMES> = {
|
|
672
|
+
${templateConstName}: "${templateKey}",
|
|
673
|
+
};
|
|
674
|
+
`;
|
|
675
|
+
if (dryRun) {
|
|
676
|
+
relinka("verbose", `[DRY RUN] Would write template file: ${outFile}`);
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
const dir = path.dirname(outFile);
|
|
680
|
+
if (dir && dir !== ".") await fs.ensureDir(dir);
|
|
681
|
+
if (backup && await fs.pathExists(outFile)) {
|
|
682
|
+
const backupPath = `${outFile}.${Date.now()}.bak`;
|
|
683
|
+
await fs.copyFile(outFile, backupPath);
|
|
684
|
+
}
|
|
685
|
+
await fs.writeFile(outFile, tsContent, "utf8");
|
|
686
|
+
const elapsed2 = getElapsedPerfTime(timer2);
|
|
687
|
+
relinka(
|
|
688
|
+
"success",
|
|
689
|
+
`Successfully ${dryRun ? "would generate" : "generated"} template file "${outFile}" (in ${prettyMilliseconds(elapsed2)})`
|
|
690
|
+
);
|
|
691
|
+
return;
|
|
368
692
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
693
|
+
const getPrefix = (filePath) => {
|
|
694
|
+
if (forceComment && customComment) return customComment;
|
|
695
|
+
const ext = path.extname(filePath).slice(1).toLowerCase();
|
|
696
|
+
return COMMENT_MAP[ext] ?? customComment ?? DEFAULT_COMMENT;
|
|
697
|
+
};
|
|
698
|
+
if (outFile && await fs.pathExists(outFile) && (await fs.stat(outFile)).isDirectory()) {
|
|
699
|
+
await writeFilesPreserveStructure(
|
|
700
|
+
files,
|
|
701
|
+
outFile,
|
|
702
|
+
preserveStructure,
|
|
703
|
+
increment,
|
|
704
|
+
concurrency,
|
|
705
|
+
dryRun,
|
|
706
|
+
backup
|
|
707
|
+
);
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
const cwd = process.cwd();
|
|
711
|
+
const seen = /* @__PURE__ */ new Set();
|
|
712
|
+
const sections = await pMap(
|
|
377
713
|
files,
|
|
714
|
+
async (f) => {
|
|
715
|
+
const raw = await fs.readFile(f, "utf8");
|
|
716
|
+
if (dedupe) {
|
|
717
|
+
const hash = raw.trim();
|
|
718
|
+
if (seen.has(hash)) return null;
|
|
719
|
+
seen.add(hash);
|
|
720
|
+
}
|
|
721
|
+
const rel = path.relative(cwd, f);
|
|
722
|
+
const prefix = getPrefix(f);
|
|
723
|
+
return processSection(raw, rel, prefix, pathAbove, injectPath);
|
|
724
|
+
},
|
|
725
|
+
{ concurrency }
|
|
726
|
+
);
|
|
727
|
+
const filteredSections = sections.filter(Boolean);
|
|
728
|
+
if (header) filteredSections.unshift(header);
|
|
729
|
+
if (footer) filteredSections.push(footer);
|
|
730
|
+
await writeResult(
|
|
731
|
+
filteredSections,
|
|
732
|
+
separator,
|
|
378
733
|
outFile,
|
|
379
|
-
|
|
380
|
-
increment,
|
|
381
|
-
concurrency,
|
|
734
|
+
stdoutFlag,
|
|
382
735
|
dryRun,
|
|
383
|
-
backup
|
|
736
|
+
backup,
|
|
737
|
+
args.sourcemap
|
|
384
738
|
);
|
|
385
|
-
|
|
739
|
+
const elapsed = getElapsedPerfTime(timer);
|
|
740
|
+
relinka("success", `Merge completed in ${prettyMilliseconds(elapsed)}`);
|
|
741
|
+
} catch (error) {
|
|
742
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
743
|
+
relinka("error", `Error during merge operation: ${errorMessage}`);
|
|
744
|
+
process.exit(1);
|
|
386
745
|
}
|
|
387
|
-
const cwd = process.cwd();
|
|
388
|
-
const seen = /* @__PURE__ */ new Set();
|
|
389
|
-
const sections = await pMap(
|
|
390
|
-
files,
|
|
391
|
-
async (f) => {
|
|
392
|
-
const raw = await fs.readFile(f, "utf8");
|
|
393
|
-
if (dedupe) {
|
|
394
|
-
const hash = raw.trim();
|
|
395
|
-
if (seen.has(hash)) return null;
|
|
396
|
-
seen.add(hash);
|
|
397
|
-
}
|
|
398
|
-
const rel = path.relative(cwd, f);
|
|
399
|
-
const prefix = getPrefix(f);
|
|
400
|
-
let section = raw;
|
|
401
|
-
if (pathAbove) {
|
|
402
|
-
section = `${prefix}${rel}
|
|
403
|
-
${section}`;
|
|
404
|
-
}
|
|
405
|
-
if (injectPath) {
|
|
406
|
-
section = `${ensureTrailingNL(section)}${prefix}${rel}`;
|
|
407
|
-
}
|
|
408
|
-
return section;
|
|
409
|
-
},
|
|
410
|
-
{ concurrency }
|
|
411
|
-
);
|
|
412
|
-
const filteredSections = sections.filter(Boolean);
|
|
413
|
-
if (header) filteredSections.unshift(header);
|
|
414
|
-
if (footer) filteredSections.push(footer);
|
|
415
|
-
await writeResult(filteredSections, separator, outFile, stdoutFlag, dryRun, backup);
|
|
416
|
-
const elapsed = getElapsedPerfTime(timer);
|
|
417
|
-
relinka("success", `Merge completed in ${prettyMilliseconds(elapsed)}`);
|
|
418
746
|
}
|
|
419
747
|
});
|