@reliverse/dler 1.2.0

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 (101) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +312 -0
  3. package/bin/cli/args/agg/main.js +0 -0
  4. package/bin/cli/args/conv/README.md +3 -0
  5. package/bin/cli/args/conv/main.js +0 -0
  6. package/bin/cli/args/deps/analyzer.js +42 -0
  7. package/bin/cli/args/deps/filesystem.js +42 -0
  8. package/bin/cli/args/deps/formatter.js +65 -0
  9. package/bin/cli/args/deps/mod.js +48 -0
  10. package/bin/cli/args/deps/parser.js +59 -0
  11. package/bin/cli/args/deps/types.js +0 -0
  12. package/bin/cli/args/init/README.md +121 -0
  13. package/bin/cli/args/init/libs/reinit/reinit-main.js +5 -0
  14. package/bin/cli/args/init/libs/reinit/reint-impl/const.js +26 -0
  15. package/bin/cli/args/init/libs/reinit/reint-impl/mod.txt +395 -0
  16. package/bin/cli/args/init/libs/reinit/reint-impl/templates/t-gitignore.js +9 -0
  17. package/bin/cli/args/init/libs/reinit/reint-impl/templates/t-license.js +22 -0
  18. package/bin/cli/args/init/libs/reinit/reint-impl/templates/t-readme.js +59 -0
  19. package/bin/cli/args/init/libs/reinit/reint-impl/types.js +0 -0
  20. package/bin/cli/args/init/libs/reinit/reint-impl/utils.js +3 -0
  21. package/bin/cli/args/init/main.txt +121 -0
  22. package/bin/cli/args/init/types.js +1 -0
  23. package/bin/cli/args/inject/README.md +148 -0
  24. package/bin/cli/args/inject/arg-ts-expect-error.txt +49 -0
  25. package/bin/cli/args/inject/cli-mod.js +32 -0
  26. package/bin/cli/args/inject/main.txt +28 -0
  27. package/bin/cli/args/inject/reinject.config.js +4 -0
  28. package/bin/cli/args/inject/ts-expect-error.txt +277 -0
  29. package/bin/cli/args/merger/README.md +125 -0
  30. package/bin/cli/args/merger/main.txt +306 -0
  31. package/bin/cli/args/mono/main.js +0 -0
  32. package/bin/cli/args/spells/mod.js +44 -0
  33. package/bin/cli/args/split/README.md +13 -0
  34. package/bin/cli/args/split/split-main.js +26 -0
  35. package/bin/cli/args/split/split-mod.js +117 -0
  36. package/bin/cli/args/tools/index.js +81 -0
  37. package/bin/cli/args/tools/tools-impl.js +296 -0
  38. package/bin/cli.js +111 -0
  39. package/bin/init.js +157 -0
  40. package/bin/libs/cfg/cfg-default.js +50 -0
  41. package/bin/libs/cfg/cfg-main.js +1 -0
  42. package/bin/libs/cfg/cfg-mod.js +4 -0
  43. package/bin/libs/cfg/cfg-types.js +1 -0
  44. package/bin/libs/sdk/sdk-impl/build/build-library.js +865 -0
  45. package/bin/libs/sdk/sdk-impl/build/build-regular.js +373 -0
  46. package/bin/libs/sdk/sdk-impl/build/bundlers/unified/auto.js +110 -0
  47. package/bin/libs/sdk/sdk-impl/build/bundlers/unified/build.js +322 -0
  48. package/bin/libs/sdk/sdk-impl/build/bundlers/unified/copy/copy.js +62 -0
  49. package/bin/libs/sdk/sdk-impl/build/bundlers/unified/copy/types.js +0 -0
  50. package/bin/libs/sdk/sdk-impl/build/bundlers/unified/mkdist/mkdist.js +57 -0
  51. package/bin/libs/sdk/sdk-impl/build/bundlers/unified/mkdist/types.js +0 -0
  52. package/bin/libs/sdk/sdk-impl/build/bundlers/unified/rollup/build.js +104 -0
  53. package/bin/libs/sdk/sdk-impl/build/bundlers/unified/rollup/config.js +124 -0
  54. package/bin/libs/sdk/sdk-impl/build/bundlers/unified/rollup/plugins/cjs.js +48 -0
  55. package/bin/libs/sdk/sdk-impl/build/bundlers/unified/rollup/plugins/esbuild.js +91 -0
  56. package/bin/libs/sdk/sdk-impl/build/bundlers/unified/rollup/plugins/json.js +17 -0
  57. package/bin/libs/sdk/sdk-impl/build/bundlers/unified/rollup/plugins/raw.js +20 -0
  58. package/bin/libs/sdk/sdk-impl/build/bundlers/unified/rollup/plugins/shebang.js +42 -0
  59. package/bin/libs/sdk/sdk-impl/build/bundlers/unified/rollup/stub.js +137 -0
  60. package/bin/libs/sdk/sdk-impl/build/bundlers/unified/rollup/types.js +0 -0
  61. package/bin/libs/sdk/sdk-impl/build/bundlers/unified/rollup/utils.js +41 -0
  62. package/bin/libs/sdk/sdk-impl/build/bundlers/unified/rollup/watch.js +33 -0
  63. package/bin/libs/sdk/sdk-impl/build/bundlers/unified/types.js +6 -0
  64. package/bin/libs/sdk/sdk-impl/build/bundlers/unified/untyped/index.js +125 -0
  65. package/bin/libs/sdk/sdk-impl/build/bundlers/unified/untyped/types.js +0 -0
  66. package/bin/libs/sdk/sdk-impl/build/bundlers/unified/utils.js +158 -0
  67. package/bin/libs/sdk/sdk-impl/build/bundlers/unified/validate.js +68 -0
  68. package/bin/libs/sdk/sdk-impl/library-flow.js +169 -0
  69. package/bin/libs/sdk/sdk-impl/pub/pub-library.js +132 -0
  70. package/bin/libs/sdk/sdk-impl/pub/pub-regular.js +69 -0
  71. package/bin/libs/sdk/sdk-impl/regular-flow.js +219 -0
  72. package/bin/libs/sdk/sdk-impl/spells/spells-executors.js +307 -0
  73. package/bin/libs/sdk/sdk-impl/spells/spells-filesystem.js +72 -0
  74. package/bin/libs/sdk/sdk-impl/spells/spells-main.js +87 -0
  75. package/bin/libs/sdk/sdk-impl/spells/spells-parser.js +60 -0
  76. package/bin/libs/sdk/sdk-impl/spells/spells-types.js +0 -0
  77. package/bin/libs/sdk/sdk-impl/utils/tools/tools-agg.js +149 -0
  78. package/bin/libs/sdk/sdk-impl/utils/tools/tools-impl.js +21 -0
  79. package/bin/libs/sdk/sdk-impl/utils/utils-build.js +102 -0
  80. package/bin/libs/sdk/sdk-impl/utils/utils-bump.js +238 -0
  81. package/bin/libs/sdk/sdk-impl/utils/utils-clean.js +35 -0
  82. package/bin/libs/sdk/sdk-impl/utils/utils-consts.js +17 -0
  83. package/bin/libs/sdk/sdk-impl/utils/utils-cwd.js +36 -0
  84. package/bin/libs/sdk/sdk-impl/utils/utils-deps.js +73 -0
  85. package/bin/libs/sdk/sdk-impl/utils/utils-determine.js +25 -0
  86. package/bin/libs/sdk/sdk-impl/utils/utils-error.js +17 -0
  87. package/bin/libs/sdk/sdk-impl/utils/utils-fs.js +202 -0
  88. package/bin/libs/sdk/sdk-impl/utils/utils-info.js +42 -0
  89. package/bin/libs/sdk/sdk-impl/utils/utils-jsr-json.js +51 -0
  90. package/bin/libs/sdk/sdk-impl/utils/utils-paths.js +658 -0
  91. package/bin/libs/sdk/sdk-impl/utils/utils-perf.js +22 -0
  92. package/bin/libs/sdk/sdk-impl/utils/utils-pkg-json-libs.js +259 -0
  93. package/bin/libs/sdk/sdk-impl/utils/utils-pkg-json-reg.js +207 -0
  94. package/bin/libs/sdk/sdk-impl/utils/utils-tsconfig.js +44 -0
  95. package/bin/libs/sdk/sdk-main.js +114 -0
  96. package/bin/libs/sdk/sdk-types.js +1 -0
  97. package/bin/load.js +27 -0
  98. package/bin/main.js +46 -0
  99. package/bin/tools.txt +92 -0
  100. package/bin/types.js +0 -0
  101. package/package.json +93 -0
@@ -0,0 +1,125 @@
1
+ # @reliverse/remege
2
+
3
+ [📦 NPM](https://npmjs.com/package/@reliverse/remege) • [🌌 GitHub](https://github.com/reliverse/remege)
4
+
5
+ > @reliverse/remege is a tool to merge multiple text/code files into a single file or output stream. Skips binaries, adds optional path footers, supports CI & interactive mode. Built with love for developers who like clean structure.
6
+
7
+ ## Features
8
+
9
+ - 🧠 Smart filtering: skips binary/media files automatically
10
+ - 🧩 Flexible output: write to file or stream to `stdout`
11
+ - 💬 Optional footer: add a comment with the relative path after each file
12
+ - 🛠️ CI-friendly: use `--batch` to disable all prompts
13
+ - 📜 Comment-aware: supports language-specific footer prefixes
14
+ - 🪄 Interactive prompts if args are missing
15
+ - 🔁 Custom separators between sections (e.g. `\n`, `\t`, blank lines, etc.)
16
+
17
+ ## Installation
18
+
19
+ - You can run it via `bunx`: `bunx @reliverse/remege` (usage: `bunx @reliverse/remege --help`)
20
+ - Or by installing globally: `bun i -g @reliverse/remege` (usage: `remege --help`)
21
+ - Or install locally: `bun add -D @reliverse/remege` (usage: `bun remege --help`)
22
+
23
+ ## Tip
24
+
25
+ You can automate the process by adding a script to your `package.json`:
26
+
27
+ ```json
28
+ "scripts": {
29
+ // remege | bunx @reliverse/remege
30
+ "merge": "remege --in src/**/*.ts --out dist/combined.ts"
31
+ }
32
+ ```
33
+
34
+ ## Usage
35
+
36
+ Use it interactively:
37
+
38
+ ```bash
39
+ remege
40
+ ```
41
+
42
+ Or via command line flags:
43
+
44
+ ```bash
45
+ remege \
46
+ --in "src/**/*.ts" \
47
+ --ignore "**/*.test.ts" \
48
+ --out dist/merged.txt \
49
+ --separator "\n\n" \
50
+ --comment "// "
51
+ ```
52
+
53
+ ## Options
54
+
55
+ | Flag | Type | Description |
56
+ |------------------|-----------|-------------|
57
+ | `--in` | `string[]` | Input glob patterns (comma-separated or repeated) |
58
+ | `--ignore` | `string[]` | Extra ignore patterns |
59
+ | `--out` | `string` | Output file path |
60
+ | `--stdout` | `boolean` | Print merged result to stdout |
61
+ | `--separator` | `string` | Custom separator between sections (default: `\n\n`) |
62
+ | `--comment` | `string` | Custom comment prefix (e.g. `#`, `//`) |
63
+ | `--forceComment` | `boolean` | Use your custom comment for **all** file types |
64
+ | `--noPath` | `boolean` | Skip path footer (don't show file path after each section) |
65
+ | `--format` | `string` | Fallback extension for autogenerated file (default: `txt`) |
66
+ | `--batch` | `boolean` | Disable prompts (non-interactive mode) |
67
+
68
+ ## How It Works
69
+
70
+ - You provide `--in` globs like `src/**/*.ts`
71
+ - Optionally ignore files via `--ignore`
72
+ - All matching files are collected (except binaries)
73
+ - Each section is appended into the final output, optionally with a path footer like:
74
+
75
+ ```ts
76
+ // relative/path/to/file.ts
77
+ ```
78
+
79
+ ## Example
80
+
81
+ ```bash
82
+ bunx @reliverse/remege --in "src/**/*.js" --stdout --noPath
83
+ ```
84
+
85
+ Will print all JS files in `src` to the terminal, separated by blank lines, without paths.
86
+
87
+ ## Custom Separator Examples
88
+
89
+ ```bash
90
+ --separator "\n\n" # double newline
91
+ --separator "\n---\n" # markdown-style break
92
+ --separator "\n// -----\n" # JS-style
93
+ ```
94
+
95
+ > Use escaped characters like `\n`, `\t`. These will be converted to newlines, tabs, etc.
96
+
97
+ ## Binary Files That Are Skipped
98
+
99
+ File types like images, videos, fonts, compressed archives, and executables are skipped by default:
100
+
101
+ - `.png`, `.jpg`, `.gif`, `.mp4`, `.mp3`, `.pdf`, `.zip`, `.exe`, `.woff`, `.jar`, etc.
102
+
103
+ ## Language-Specific Comment Prefixes
104
+
105
+ These are automatically selected based on file extensions:
106
+
107
+ | Ext | Comment |
108
+ |-------|---------|
109
+ | `ts` | `//` |
110
+ | `py` | `#` |
111
+ | `html`| `<!--` |
112
+ | `sql` | `--` |
113
+ | `css` | `/*` |
114
+
115
+ You can override using `--comment`.
116
+
117
+ ## Use Cases Examples
118
+
119
+ - You want to send the contents of several files to AI
120
+ - Collecting code snippets into a teaching demo
121
+ - Merging many markdown files into a single file
122
+
123
+ ## License
124
+
125
+ 💖 2025 MIT [blefnk Nazar Kornienko](https://github.com/blefnk)
@@ -0,0 +1,306 @@
1
+ import { glob } from "@reliverse/reglob";
2
+ import { readFile, writeFile, ensureDir } from "@reliverse/relifso";
3
+ import {
4
+ defineCommand,
5
+ runMain,
6
+ inputPrompt,
7
+ confirmPrompt,
8
+ } from "@reliverse/rempts";
9
+ import path from "@reliverse/repath";
10
+
11
+ // ---------- constants ----------
12
+
13
+ // always-ignored directories
14
+ const DEFAULT_IGNORES = ["**/.git/**", "**/node_modules/**"] as const;
15
+
16
+ // binary / media extensions (stored in a Set for O(1) lookups)
17
+ const BINARY_EXTS = [
18
+ "png",
19
+ "jpg",
20
+ "jpeg",
21
+ "gif",
22
+ "bmp",
23
+ "webp",
24
+ "svg",
25
+ "ico",
26
+ "mp4",
27
+ "mov",
28
+ "avi",
29
+ "mkv",
30
+ "mp3",
31
+ "wav",
32
+ "flac",
33
+ "ogg",
34
+ "pdf",
35
+ "zip",
36
+ "gz",
37
+ "tar",
38
+ "rar",
39
+ "7z",
40
+ "exe",
41
+ "dll",
42
+ "bin",
43
+ "woff",
44
+ "woff2",
45
+ "ttf",
46
+ "eot",
47
+ "class",
48
+ "jar",
49
+ ] as const;
50
+ const BINARY_SET = new Set<string>(BINARY_EXTS);
51
+
52
+ // known comment prefixes per extension
53
+ const COMMENT_MAP: Record<string, string> = {
54
+ js: "// ",
55
+ jsx: "// ",
56
+ ts: "// ",
57
+ tsx: "// ",
58
+ c: "// ",
59
+ cpp: "// ",
60
+ h: "// ",
61
+ java: "// ",
62
+ go: "// ",
63
+ kt: "// ",
64
+ swift: "// ",
65
+ rs: "// ",
66
+ cs: "// ",
67
+ json: "// ",
68
+ proto: "// ",
69
+ dart: "// ",
70
+ py: "# ",
71
+ rb: "# ",
72
+ sh: "# ",
73
+ pl: "# ",
74
+ r: "# ",
75
+ yml: "# ",
76
+ yaml: "# ",
77
+ sql: "-- ",
78
+ lua: "-- ",
79
+ css: "/* ",
80
+ scss: "/* ",
81
+ less: "/* ",
82
+ html: "<!-- ",
83
+ htm: "<!-- ",
84
+ xml: "<!-- ",
85
+ md: "<!-- ",
86
+ };
87
+
88
+ const DEFAULT_COMMENT = "// ";
89
+ const DEFAULT_SEPARATOR_RAW = "\\n\\n"; // two newlines (escaped form)
90
+
91
+ // ---------- helpers ----------
92
+
93
+ const isBinaryExt = (file: string) => {
94
+ const ext = path.extname(file).slice(1).toLowerCase();
95
+ return BINARY_SET.has(ext);
96
+ };
97
+
98
+ const ensureTrailingNL = (s: string) => (s.endsWith("\n") ? s : `${s}\n`);
99
+
100
+ const parseCSV = (s: string) =>
101
+ s
102
+ .split(",")
103
+ .map((t) => t.trim())
104
+ .filter(Boolean);
105
+
106
+ const unescape = (s: string) => s.replace(/\\n/g, "\n").replace(/\\t/g, "\t");
107
+
108
+ // prompt wrappers that honour batch mode
109
+ const maybePrompt = async <T>(
110
+ batch: boolean,
111
+ value: T | undefined,
112
+ promptFn: () => Promise<T>,
113
+ ): Promise<T | undefined> => {
114
+ if (batch || value !== undefined) return value;
115
+ return promptFn();
116
+ };
117
+
118
+ // collect and pre-filter files (dedup + binary skip early)
119
+ const collectFiles = async (include: string[], extraIgnore: string[]) => {
120
+ const files = await glob(include, {
121
+ ignore: [...DEFAULT_IGNORES, ...extraIgnore],
122
+ absolute: true,
123
+ });
124
+ return [...new Set(files)].filter((f) => !isBinaryExt(f)).sort();
125
+ };
126
+
127
+ const readSections = async (
128
+ files: string[],
129
+ injectPath: boolean,
130
+ getPrefix: (f: string) => string,
131
+ ) => {
132
+ const cwd = process.cwd();
133
+ const reads = files.map(async (f) => {
134
+ const raw = (await readFile(f, "utf8")) as string;
135
+ if (!injectPath) return raw;
136
+ const rel = path.relative(cwd, f);
137
+ return `${ensureTrailingNL(raw)}${getPrefix(f)}${rel}`;
138
+ });
139
+ return Promise.all(reads);
140
+ };
141
+
142
+ const writeResult = async (
143
+ sections: string[],
144
+ separator: string,
145
+ toFile: string | undefined,
146
+ toStdout: boolean,
147
+ ) => {
148
+ const content = `${sections.join(separator)}\n`;
149
+ if (toStdout || !toFile) {
150
+ process.stdout.write(content);
151
+ return;
152
+ }
153
+ const dir = path.dirname(toFile);
154
+ if (dir && dir !== ".") await ensureDir(dir);
155
+ await writeFile(toFile, content, "utf8");
156
+ };
157
+
158
+ // ---------- command ----------
159
+
160
+ const cmd = defineCommand({
161
+ meta: {
162
+ name: "remege",
163
+ version: "1.0.0",
164
+ description:
165
+ "Merge text files with optional commented path footer, skips binaries/media, built for CI & interactive use.",
166
+ },
167
+ args: {
168
+ in: { type: "array", description: "Input glob patterns" },
169
+ ignore: { type: "array", description: "Extra ignore patterns" },
170
+ out: { type: "string", description: "Output file path" },
171
+ format: {
172
+ type: "string",
173
+ default: "txt",
174
+ description: "Fallback extension when output path is omitted",
175
+ },
176
+ stdout: { type: "boolean", description: "Print to stdout" },
177
+ noPath: {
178
+ type: "boolean",
179
+ description: "Don't inject relative path below each file",
180
+ },
181
+ separator: {
182
+ type: "string",
183
+ description: `Custom separator (default ${DEFAULT_SEPARATOR_RAW})`,
184
+ },
185
+ comment: {
186
+ type: "string",
187
+ description: "Custom comment prefix (e.g. '# ')",
188
+ },
189
+ forceComment: {
190
+ type: "boolean",
191
+ description: "Force custom comment prefix for all file types",
192
+ },
193
+ batch: {
194
+ type: "boolean",
195
+ description: "Disable interactive prompts (CI/non-interactive mode)",
196
+ },
197
+ },
198
+ async run({ args }) {
199
+ const batch = Boolean(args.batch);
200
+
201
+ // ----- include patterns -----
202
+ let include = args.in ?? [];
203
+ if (include.length === 0) {
204
+ const raw = await maybePrompt(batch, undefined, () =>
205
+ inputPrompt({
206
+ title: "Input glob patterns (comma separated)",
207
+ placeholder: "src/**/*.ts, !**/*.test.ts",
208
+ }),
209
+ );
210
+ if (raw) include = parseCSV(raw as string);
211
+ }
212
+ if (include.length === 0)
213
+ throw new Error("No input patterns supplied and prompts disabled");
214
+
215
+ // ----- ignore patterns -----
216
+ let ignore = args.ignore ?? [];
217
+ if (ignore.length === 0) {
218
+ const raw = await maybePrompt(batch, undefined, () =>
219
+ inputPrompt({
220
+ title: "Ignore patterns (comma separated, blank for none)",
221
+ placeholder: "**/*.d.ts",
222
+ }),
223
+ );
224
+ if (raw) ignore = parseCSV(raw as string);
225
+ }
226
+
227
+ // ----- comment settings -----
228
+ let customComment = args.comment;
229
+ if (customComment === undefined) {
230
+ const want = await maybePrompt(batch, undefined, () =>
231
+ confirmPrompt({
232
+ title: "Provide custom comment prefix?",
233
+ defaultValue: false,
234
+ }),
235
+ );
236
+ if (want) {
237
+ customComment = (await inputPrompt({
238
+ title: "Custom comment prefix (include trailing space if needed)",
239
+ placeholder: "# ",
240
+ })) as string;
241
+ }
242
+ }
243
+ const forceComment = args.forceComment ?? false;
244
+
245
+ // ----- path footer toggle -----
246
+ const injectPath = args.noPath ? false : true;
247
+
248
+ // ----- separator -----
249
+ const sepRaw =
250
+ args.separator ??
251
+ ((await maybePrompt(batch, undefined, () =>
252
+ inputPrompt({
253
+ title:
254
+ "Separator between files (\\n for newline, blank → blank line)",
255
+ placeholder: DEFAULT_SEPARATOR_RAW,
256
+ }),
257
+ )) as string | undefined) ??
258
+ DEFAULT_SEPARATOR_RAW;
259
+ const separator = unescape(sepRaw);
260
+
261
+ // ----- output location / stdout -----
262
+ let stdoutFlag = args.stdout ?? false;
263
+ let outFile = args.out;
264
+
265
+ if (!stdoutFlag && !outFile && !batch) {
266
+ stdoutFlag = await confirmPrompt({
267
+ title: "Print result to stdout?",
268
+ defaultValue: false,
269
+ });
270
+ if (!stdoutFlag) {
271
+ outFile = (await inputPrompt({
272
+ title: "Output file path (blank → merged.<ext>)",
273
+ placeholder: "",
274
+ })) as string;
275
+ if (!outFile) {
276
+ const ext = (await inputPrompt({
277
+ title: "File extension",
278
+ placeholder: args.format,
279
+ })) as string;
280
+ outFile = `merged.${(ext || args.format).replace(/^\./, "")}`;
281
+ }
282
+ }
283
+ }
284
+
285
+ // ----- gather files -----
286
+ const files = await collectFiles(include, ignore);
287
+ if (files.length === 0) {
288
+ throw new Error(
289
+ "No text files matched given patterns (binary/media files are skipped)",
290
+ );
291
+ }
292
+
293
+ // ----- comment prefix resolver -----
294
+ const getPrefix = (filePath: string): string => {
295
+ if (forceComment && customComment) return customComment;
296
+ const ext = path.extname(filePath).slice(1).toLowerCase();
297
+ return COMMENT_MAP[ext] ?? customComment ?? DEFAULT_COMMENT;
298
+ };
299
+
300
+ // ----- read, merge, write -----
301
+ const sections = await readSections(files, injectPath, getPrefix);
302
+ await writeResult(sections, separator, outFile, stdoutFlag);
303
+ },
304
+ });
305
+
306
+ await runMain(cmd);
File without changes
@@ -0,0 +1,44 @@
1
+ import { Command } from "commander";
2
+ import { spells } from "../../../libs/sdk/sdk-impl/spells/spells-main.js";
3
+ export const createCli = () => {
4
+ const program = new Command();
5
+ program.name("dler-spells").description("Execute magic spells in your codebase").version("1.0.0");
6
+ program.command("trigger").description("Trigger magic spells").option(
7
+ "--spells <spells>",
8
+ 'Comma-separated list of spells to execute (or "all")',
9
+ "all"
10
+ ).option(
11
+ "--files <files>",
12
+ "Comma-separated list of files to process (or all if not specified)"
13
+ ).option("--dry-run", "Preview changes without applying them", false).action(async (options) => {
14
+ const requestedSpells = options.spells ? options.spells.split(",") : ["all"];
15
+ const files = options.files ? options.files.split(",") : [];
16
+ console.log(`Triggering spells: ${requestedSpells.join(", ")}`);
17
+ if (files.length) {
18
+ console.log(`On files: ${files.join(", ")}`);
19
+ } else {
20
+ console.log("On all files");
21
+ }
22
+ if (options.dryRun) {
23
+ console.log("DRY RUN - No changes will be applied");
24
+ }
25
+ const results = await spells({
26
+ spells: requestedSpells,
27
+ files,
28
+ dryRun: options.dryRun
29
+ });
30
+ console.log("\nResults:");
31
+ for (const result of results) {
32
+ const status = result.success ? "\u2713" : "\u2717";
33
+ console.log(
34
+ `${status} ${result.file}: ${result.spell.type} - ${result.message}`
35
+ );
36
+ }
37
+ });
38
+ return program;
39
+ };
40
+ const cli = createCli();
41
+ async function main() {
42
+ cli.parse(process.argv);
43
+ }
44
+ await main();
@@ -0,0 +1,13 @@
1
+ # Splitter: dler Plugin (Tool)
2
+
3
+ **`splitter-dler-plugin`**: Splits large source files into smaller ones and large functions into smaller helper functions.
4
+
5
+ ## Project Status
6
+
7
+ 🏗️ **Work In Progress**: This project is currently in development and is not yet ready for use.
8
+
9
+ You can try current implementation by cloning the repo and running `bun src/split-large-files.ts --dir ./example --file-threshold 30 --func-threshold 20`.
10
+
11
+ ## License
12
+
13
+ 🩷 [MIT](./LICENSE) © [blefnk Nazar Kornienko](https://github.com/blefnk)
@@ -0,0 +1,26 @@
1
+ import fs from "fs-extra";
2
+ import {
3
+ getAllSourceFiles,
4
+ parseCommandLineArgs,
5
+ splitLargeFileByLines,
6
+ splitLargeFunctions
7
+ } from "./split-mod.js";
8
+ async function main() {
9
+ const { directory, fileLineThreshold, funcLineThreshold } = parseCommandLineArgs();
10
+ const allFiles = getAllSourceFiles(directory);
11
+ for (const filePath of allFiles) {
12
+ const lineCount = fs.readFileSync(filePath, "utf8").split("\n").length;
13
+ if (lineCount > fileLineThreshold) {
14
+ const newSplits = splitLargeFileByLines(filePath, fileLineThreshold);
15
+ newSplits.forEach((splitFilePath) => {
16
+ splitLargeFunctions(splitFilePath, funcLineThreshold);
17
+ });
18
+ } else {
19
+ splitLargeFunctions(filePath, funcLineThreshold);
20
+ }
21
+ }
22
+ }
23
+ main().catch((err) => {
24
+ console.error(err);
25
+ process.exit(1);
26
+ });
@@ -0,0 +1,117 @@
1
+ import fs from "fs-extra";
2
+ import path from "pathe";
3
+ import ts from "typescript";
4
+ export function parseCommandLineArgs() {
5
+ const args = process.argv.slice(2);
6
+ let directory = ".";
7
+ let fileLineThreshold = 300;
8
+ let funcLineThreshold = 50;
9
+ for (let i = 0; i < args.length; i++) {
10
+ const arg = args[i];
11
+ if (arg === "--dir" && args[i + 1]) {
12
+ directory = args[i + 1] ?? ".";
13
+ i++;
14
+ } else if (arg === "--file-threshold" && args[i + 1]) {
15
+ fileLineThreshold = Number(args[i + 1]);
16
+ i++;
17
+ } else if (arg === "--func-threshold" && args[i + 1]) {
18
+ funcLineThreshold = Number(args[i + 1]);
19
+ i++;
20
+ }
21
+ }
22
+ return { directory, fileLineThreshold, funcLineThreshold };
23
+ }
24
+ export function getAllSourceFiles(dir) {
25
+ const results = [];
26
+ function searchDirectory(directory) {
27
+ const files = fs.readdirSync(directory);
28
+ for (const file of files) {
29
+ const fullPath = path.join(directory, file);
30
+ if (fs.statSync(fullPath).isDirectory()) {
31
+ searchDirectory(fullPath);
32
+ } else if (/\.(ts|js)$/.test(file)) {
33
+ results.push(fullPath);
34
+ }
35
+ }
36
+ }
37
+ searchDirectory(dir);
38
+ return results;
39
+ }
40
+ export function splitLargeFileByLines(filePath, threshold) {
41
+ const originalContent = fs.readFileSync(filePath, "utf8");
42
+ const lines = originalContent.split("\n");
43
+ if (lines.length <= threshold) {
44
+ return [filePath];
45
+ }
46
+ console.log(`File "${filePath}" exceeds ${threshold} lines. Splitting...`);
47
+ const baseName = path.basename(filePath, path.extname(filePath));
48
+ const ext = path.extname(filePath);
49
+ const dirName = path.dirname(filePath);
50
+ const newFilePaths = [];
51
+ let chunkIndex = 0;
52
+ let i = 0;
53
+ while (i < lines.length) {
54
+ chunkIndex++;
55
+ const chunkLines = lines.slice(i, i + threshold);
56
+ const newFileName = `${baseName}.part${chunkIndex}${ext}`;
57
+ const newFileFullPath = path.join(dirName, newFileName);
58
+ fs.writeFileSync(newFileFullPath, chunkLines.join("\n"), "utf8");
59
+ newFilePaths.push(newFileFullPath);
60
+ i += threshold;
61
+ }
62
+ return newFilePaths;
63
+ }
64
+ export function splitLargeFunctions(filePath, funcLineThreshold) {
65
+ const sourceCode = fs.readFileSync(filePath, "utf8");
66
+ const sourceFile = ts.createSourceFile(
67
+ filePath,
68
+ sourceCode,
69
+ ts.ScriptTarget.ESNext,
70
+ true
71
+ );
72
+ let newSource = sourceCode;
73
+ const getLineOfPosition = (pos) => {
74
+ return sourceFile.getLineAndCharacterOfPosition(pos).line;
75
+ };
76
+ const largeFunctions = [];
77
+ function visit(node) {
78
+ if (ts.isFunctionDeclaration(node) && node.body) {
79
+ const startLine = getLineOfPosition(node.body.pos);
80
+ const endLine = getLineOfPosition(node.body.end);
81
+ const lineCount = endLine - startLine;
82
+ if (lineCount > funcLineThreshold) {
83
+ const name = node.name?.text || "<anonymous>";
84
+ largeFunctions.push({
85
+ name,
86
+ startPos: node.body.pos,
87
+ endPos: node.body.end,
88
+ lineCount
89
+ });
90
+ }
91
+ }
92
+ ts.forEachChild(node, visit);
93
+ }
94
+ visit(sourceFile);
95
+ if (largeFunctions.length === 0) {
96
+ return;
97
+ }
98
+ console.log(
99
+ `File "${filePath}" has ${largeFunctions.length} function(s) above ${funcLineThreshold} lines. Attempting to split...`
100
+ );
101
+ largeFunctions.sort((a, b) => b.startPos - a.startPos).forEach((fn) => {
102
+ const bodyText = sourceCode.slice(fn.startPos, fn.endPos);
103
+ const midIndex = Math.floor(bodyText.length / 2);
104
+ const helperFunctionName = `${fn.name}HelperAutoGen`;
105
+ const replacementText = `{
106
+ // Original function was split automatically
107
+ ${bodyText.slice(0, midIndex)}
108
+ }
109
+
110
+ function ${helperFunctionName}() {
111
+ // auto-generated second half
112
+ ${bodyText.slice(midIndex)}
113
+ `;
114
+ newSource = newSource.slice(0, fn.startPos) + replacementText + newSource.slice(fn.endPos);
115
+ });
116
+ fs.writeFileSync(filePath, newSource, "utf8");
117
+ }