@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.
Files changed (61) hide show
  1. package/README.md +216 -10
  2. package/bin/app/build/cmd.js +11 -2
  3. package/bin/app/build/impl.d.ts +3 -2
  4. package/bin/app/build/impl.js +52 -36
  5. package/bin/app/merge/cmd.d.ts +30 -5
  6. package/bin/app/merge/cmd.js +586 -258
  7. package/bin/app/mkdist/cmd.d.ts +0 -4
  8. package/bin/app/mkdist/cmd.js +0 -4
  9. package/bin/app/mock/cmd.d.ts +41 -0
  10. package/bin/app/mock/cmd.js +284 -0
  11. package/bin/app/mock/mock.d.ts +11 -0
  12. package/bin/app/mock/mock.js +97 -0
  13. package/bin/app/pub/cmd.js +10 -1
  14. package/bin/libs/sdk/sdk-impl/build/build-library.js +22 -20
  15. package/bin/libs/sdk/sdk-impl/build/build-regular.js +47 -42
  16. package/bin/libs/sdk/sdk-impl/build/bundlers/unified/build.d.ts +4 -1
  17. package/bin/libs/sdk/sdk-impl/build/bundlers/unified/build.js +6 -7
  18. package/bin/libs/sdk/sdk-impl/build/bundlers/unified/mkdist/mkdist-impl/loaders/sass.js +1 -1
  19. package/bin/libs/sdk/sdk-impl/build/bundlers/unified/mkdist/mkdist-impl/make.js +1 -1
  20. package/bin/libs/sdk/sdk-impl/build/bundlers/unified/mkdist/mkdist-impl/utils/dts.js +1 -1
  21. package/bin/libs/sdk/sdk-impl/build/bundlers/unified/mkdist/mkdist-impl/utils/vue-dts.js +1 -1
  22. package/bin/libs/sdk/sdk-impl/cfg/info.js +1 -1
  23. package/bin/libs/sdk/sdk-impl/library-flow.js +2 -16
  24. package/bin/libs/sdk/sdk-impl/pub/pub-library.js +2 -9
  25. package/bin/libs/sdk/sdk-impl/pub/pub-regular.js +2 -14
  26. package/bin/libs/sdk/sdk-impl/spell/spell-parser.d.ts +1 -1
  27. package/bin/libs/sdk/sdk-impl/spell/spell-parser.js +2 -2
  28. package/bin/libs/sdk/sdk-impl/spell/spell-types.d.ts +27 -1
  29. package/bin/libs/sdk/sdk-impl/utils/b-exts.d.ts +5 -0
  30. package/bin/libs/sdk/sdk-impl/utils/b-exts.js +406 -0
  31. package/bin/libs/sdk/sdk-impl/utils/binary.d.ts +4 -0
  32. package/bin/libs/sdk/sdk-impl/utils/binary.js +11 -0
  33. package/bin/libs/sdk/sdk-impl/utils/file-type.d.ts +21 -0
  34. package/bin/libs/sdk/sdk-impl/utils/file-type.js +78 -0
  35. package/bin/libs/sdk/sdk-impl/utils/finalize.js +3 -3
  36. package/bin/libs/sdk/sdk-impl/utils/utils-clean.js +0 -1
  37. package/bin/libs/sdk/sdk-impl/utils/utils-error.d.ts +3 -2
  38. package/bin/libs/sdk/sdk-impl/utils/utils-error.js +10 -10
  39. package/bin/libs/sdk/sdk-impl/utils/utils-fs.d.ts +28 -6
  40. package/bin/libs/sdk/sdk-impl/utils/utils-fs.js +73 -124
  41. package/bin/libs/sdk/sdk-impl/utils/utils-jsr-json.js +20 -2
  42. package/bin/libs/sdk/sdk-impl/utils/utils-security.d.ts +15 -0
  43. package/bin/libs/sdk/sdk-impl/utils/utils-security.js +102 -0
  44. package/bin/libs/sdk/sdk-mod.d.ts +3 -3
  45. package/bin/libs/sdk/sdk-mod.js +3 -2
  46. package/bin/libs/sdk/sdk-types.d.ts +15 -4
  47. package/bin/templates/App.css +31 -0
  48. package/bin/templates/App.tsx +21 -0
  49. package/bin/templates/DOCS.md +31 -0
  50. package/bin/templates/globals.css +27 -0
  51. package/bin/templates/hello/world.ts +1 -0
  52. package/bin/templates/index.html +12 -0
  53. package/bin/templates/main.tsx +11 -0
  54. package/package.json +3 -3
  55. package/bin/app/conv/README.md +0 -3
  56. package/bin/app/merge/README.md +0 -125
  57. package/bin/app/split/README.md +0 -13
  58. /package/bin/libs/sdk/sdk-impl/utils/{tools/tools-agg.d.ts → tools-agg.d.ts} +0 -0
  59. /package/bin/libs/sdk/sdk-impl/utils/{tools/tools-agg.js → tools-agg.js} +0 -0
  60. /package/bin/libs/sdk/sdk-impl/utils/{tools/tools-impl.d.ts → tools-impl.d.ts} +0 -0
  61. /package/bin/libs/sdk/sdk-impl/utils/{tools/tools-impl.js → tools-impl.js} +0 -0
@@ -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 { defineCommand, inputPrompt, confirmPrompt, multiselectPrompt } from "@reliverse/rempts";
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 isBinaryExt = (file) => {
81
- const ext = path.extname(file).slice(1).toLowerCase();
82
- return BINARY_SET.has(ext);
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 ensureTrailingNL = (s) => s.endsWith("\n") ? s : `${s}
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 (batch, value, promptFn) => {
89
- if (batch || value !== void 0) return value;
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
- const files = await glob(include, {
94
- ignore: [...DEFAULT_IGNORES, ...extraIgnore],
95
- absolute: true,
96
- onlyFiles: true,
97
- deep: recursive ? void 0 : 1
98
- });
99
- let filtered = [...new Set(files)].filter((f) => !isBinaryExt(f));
100
- if (sortBy === "name") {
101
- filtered.sort((a, b) => path.basename(a).localeCompare(path.basename(b)));
102
- } else if (sortBy === "path") {
103
- filtered.sort();
104
- } else if (sortBy === "mtime") {
105
- filtered = await pMap(filtered, async (f) => ({ f, mtime: (await fs.stat(f)).mtimeMs }), {
106
- concurrency: 8
107
- }).then((arr) => arr.sort((a, b) => a.mtime - b.mtime).map((x) => x.f));
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, separator, toFile, toStdout, dryRun, backup) => {
112
- const content = `${sections.join(separator)}
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
- if (toStdout || !toFile) {
115
- process.stdout.write(content);
116
- return;
117
- }
118
- const dir = path.dirname(toFile);
119
- if (dir && dir !== ".") await fs.ensureDir(dir);
120
- if (backup && await fs.pathExists(toFile)) {
121
- const backupPath = `${toFile}.${Date.now()}.bak`;
122
- await fs.copyFile(toFile, backupPath);
123
- }
124
- if (!dryRun) {
125
- await fs.writeFile(toFile, content, "utf8");
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
- const cwd = process.cwd();
130
- const fileNameCounts = /* @__PURE__ */ new Map();
131
- await pMap(
132
- files,
133
- async (file) => {
134
- const relPath = preserveStructure ? path.relative(cwd, file) : path.basename(file);
135
- let destPath = path.join(outDir, relPath);
136
- if (increment) {
137
- const dir = path.dirname(destPath);
138
- const base = path.basename(destPath);
139
- let dirMap = fileNameCounts.get(dir);
140
- if (!dirMap) {
141
- dirMap = /* @__PURE__ */ new Map();
142
- fileNameCounts.set(dir, dirMap);
143
- }
144
- const count = dirMap.get(base) || 0;
145
- if (count > 0) {
146
- const extMatch = base.match(/(.*)(\.[^./\\]+)$/);
147
- let newBase;
148
- if (extMatch) {
149
- newBase = `${extMatch[1]}-${count + 1}${extMatch[2]}`;
150
- } else {
151
- newBase = `${base}-${count + 1}`;
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
- destPath = path.join(dir, newBase);
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
- { concurrency }
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: "Prompt for file selection before merging",
261
- default: false
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 timer = createPerfTimer();
266
- const batch = Boolean(args.batch);
267
- let include = args.s ?? [];
268
- if (include.length === 0) {
269
- const raw = await maybePrompt(
270
- batch,
271
- void 0,
272
- () => inputPrompt({
273
- title: "Input glob patterns (comma separated)",
274
- placeholder: "src/**/*.ts, !**/*.test.ts"
275
- })
276
- );
277
- if (raw) include = parseCSV(raw);
278
- }
279
- if (include.length === 0) throw new Error("No input patterns supplied and prompts disabled");
280
- let ignore = args.ignore ?? [];
281
- if (ignore.length === 0) {
282
- const raw = await maybePrompt(
283
- batch,
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: "Ignore patterns (comma separated, blank for none)",
287
- placeholder: "**/*.d.ts"
441
+ title: "Separator between files (\\n for newline, blank \u2192 blank line)",
442
+ placeholder: DEFAULT_SEPARATOR_RAW
288
443
  })
289
- );
290
- if (raw) ignore = parseCSV(raw);
291
- }
292
- let customComment = args.comment;
293
- if (customComment === void 0) {
294
- const want = await maybePrompt(
295
- batch,
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
- const forceComment = args.forceComment ?? false;
310
- const injectPath = !args.noPath;
311
- const pathAbove = args.pathAbove ?? true;
312
- const sepRaw = args.separator ?? await maybePrompt(
313
- batch,
314
- void 0,
315
- () => inputPrompt({
316
- title: "Separator between files (\\n for newline, blank \u2192 blank line)",
317
- placeholder: DEFAULT_SEPARATOR_RAW
318
- })
319
- ) ?? DEFAULT_SEPARATOR_RAW;
320
- const separator = unescape(sepRaw);
321
- let stdoutFlag = args.stdout ?? false;
322
- let outFile = args.d;
323
- if (!stdoutFlag && !outFile && !batch) {
324
- stdoutFlag = await confirmPrompt({
325
- title: "Print result to stdout?",
326
- defaultValue: false
327
- });
328
- if (!stdoutFlag) {
329
- outFile = await inputPrompt({
330
- title: "Output file path (blank \u2192 merged.<ext>)",
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
- const ext = await inputPrompt({
335
- title: "File extension",
336
- placeholder: args.format
337
- });
338
- outFile = `merged.${(ext || args.format).replace(/^\./, "")}`;
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
- const recursive = args.recursive ?? true;
343
- const preserveStructure = args.preserveStructure ?? true;
344
- const increment = args.increment ?? false;
345
- const concurrency = args.concurrency ?? 8;
346
- const sortBy = args.sort;
347
- const dryRun = args.dryRun ?? false;
348
- const backup = args.backup ?? false;
349
- const dedupe = args.dedupe ?? false;
350
- const header = args.header;
351
- const footer = args.footer;
352
- const interactive = args.interactive ?? false;
353
- let files = await collectFiles(include, ignore, recursive, sortBy);
354
- if (files.length === 0) {
355
- throw new Error("No text files matched given patterns (binary/media files are skipped)");
356
- }
357
- if (interactive && !batch) {
358
- const selected = await multiselectPrompt({
359
- title: "Select files to merge",
360
- options: files.map((f) => ({
361
- label: path.relative(process.cwd(), f),
362
- value: f
363
- }))
364
- });
365
- files = Array.isArray(selected) ? selected : [selected];
366
- if (files.length === 0) {
367
- throw new Error("No files selected for merging");
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
- const getPrefix = (filePath) => {
371
- if (forceComment && customComment) return customComment;
372
- const ext = path.extname(filePath).slice(1).toLowerCase();
373
- return COMMENT_MAP[ext] ?? customComment ?? DEFAULT_COMMENT;
374
- };
375
- if (outFile && await fs.pathExists(outFile) && (await fs.stat(outFile)).isDirectory()) {
376
- await writeFilesPreserveStructure(
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
- preserveStructure,
380
- increment,
381
- concurrency,
734
+ stdoutFlag,
382
735
  dryRun,
383
- backup
736
+ backup,
737
+ args.sourcemap
384
738
  );
385
- return;
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
  });