@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.
- package/LICENSE +21 -0
- package/README.md +312 -0
- package/bin/cli/args/agg/main.js +0 -0
- package/bin/cli/args/conv/README.md +3 -0
- package/bin/cli/args/conv/main.js +0 -0
- package/bin/cli/args/deps/analyzer.js +42 -0
- package/bin/cli/args/deps/filesystem.js +42 -0
- package/bin/cli/args/deps/formatter.js +65 -0
- package/bin/cli/args/deps/mod.js +48 -0
- package/bin/cli/args/deps/parser.js +59 -0
- package/bin/cli/args/deps/types.js +0 -0
- package/bin/cli/args/init/README.md +121 -0
- package/bin/cli/args/init/libs/reinit/reinit-main.js +5 -0
- package/bin/cli/args/init/libs/reinit/reint-impl/const.js +26 -0
- package/bin/cli/args/init/libs/reinit/reint-impl/mod.txt +395 -0
- package/bin/cli/args/init/libs/reinit/reint-impl/templates/t-gitignore.js +9 -0
- package/bin/cli/args/init/libs/reinit/reint-impl/templates/t-license.js +22 -0
- package/bin/cli/args/init/libs/reinit/reint-impl/templates/t-readme.js +59 -0
- package/bin/cli/args/init/libs/reinit/reint-impl/types.js +0 -0
- package/bin/cli/args/init/libs/reinit/reint-impl/utils.js +3 -0
- package/bin/cli/args/init/main.txt +121 -0
- package/bin/cli/args/init/types.js +1 -0
- package/bin/cli/args/inject/README.md +148 -0
- package/bin/cli/args/inject/arg-ts-expect-error.txt +49 -0
- package/bin/cli/args/inject/cli-mod.js +32 -0
- package/bin/cli/args/inject/main.txt +28 -0
- package/bin/cli/args/inject/reinject.config.js +4 -0
- package/bin/cli/args/inject/ts-expect-error.txt +277 -0
- package/bin/cli/args/merger/README.md +125 -0
- package/bin/cli/args/merger/main.txt +306 -0
- package/bin/cli/args/mono/main.js +0 -0
- package/bin/cli/args/spells/mod.js +44 -0
- package/bin/cli/args/split/README.md +13 -0
- package/bin/cli/args/split/split-main.js +26 -0
- package/bin/cli/args/split/split-mod.js +117 -0
- package/bin/cli/args/tools/index.js +81 -0
- package/bin/cli/args/tools/tools-impl.js +296 -0
- package/bin/cli.js +111 -0
- package/bin/init.js +157 -0
- package/bin/libs/cfg/cfg-default.js +50 -0
- package/bin/libs/cfg/cfg-main.js +1 -0
- package/bin/libs/cfg/cfg-mod.js +4 -0
- package/bin/libs/cfg/cfg-types.js +1 -0
- package/bin/libs/sdk/sdk-impl/build/build-library.js +865 -0
- package/bin/libs/sdk/sdk-impl/build/build-regular.js +373 -0
- package/bin/libs/sdk/sdk-impl/build/bundlers/unified/auto.js +110 -0
- package/bin/libs/sdk/sdk-impl/build/bundlers/unified/build.js +322 -0
- package/bin/libs/sdk/sdk-impl/build/bundlers/unified/copy/copy.js +62 -0
- package/bin/libs/sdk/sdk-impl/build/bundlers/unified/copy/types.js +0 -0
- package/bin/libs/sdk/sdk-impl/build/bundlers/unified/mkdist/mkdist.js +57 -0
- package/bin/libs/sdk/sdk-impl/build/bundlers/unified/mkdist/types.js +0 -0
- package/bin/libs/sdk/sdk-impl/build/bundlers/unified/rollup/build.js +104 -0
- package/bin/libs/sdk/sdk-impl/build/bundlers/unified/rollup/config.js +124 -0
- package/bin/libs/sdk/sdk-impl/build/bundlers/unified/rollup/plugins/cjs.js +48 -0
- package/bin/libs/sdk/sdk-impl/build/bundlers/unified/rollup/plugins/esbuild.js +91 -0
- package/bin/libs/sdk/sdk-impl/build/bundlers/unified/rollup/plugins/json.js +17 -0
- package/bin/libs/sdk/sdk-impl/build/bundlers/unified/rollup/plugins/raw.js +20 -0
- package/bin/libs/sdk/sdk-impl/build/bundlers/unified/rollup/plugins/shebang.js +42 -0
- package/bin/libs/sdk/sdk-impl/build/bundlers/unified/rollup/stub.js +137 -0
- package/bin/libs/sdk/sdk-impl/build/bundlers/unified/rollup/types.js +0 -0
- package/bin/libs/sdk/sdk-impl/build/bundlers/unified/rollup/utils.js +41 -0
- package/bin/libs/sdk/sdk-impl/build/bundlers/unified/rollup/watch.js +33 -0
- package/bin/libs/sdk/sdk-impl/build/bundlers/unified/types.js +6 -0
- package/bin/libs/sdk/sdk-impl/build/bundlers/unified/untyped/index.js +125 -0
- package/bin/libs/sdk/sdk-impl/build/bundlers/unified/untyped/types.js +0 -0
- package/bin/libs/sdk/sdk-impl/build/bundlers/unified/utils.js +158 -0
- package/bin/libs/sdk/sdk-impl/build/bundlers/unified/validate.js +68 -0
- package/bin/libs/sdk/sdk-impl/library-flow.js +169 -0
- package/bin/libs/sdk/sdk-impl/pub/pub-library.js +132 -0
- package/bin/libs/sdk/sdk-impl/pub/pub-regular.js +69 -0
- package/bin/libs/sdk/sdk-impl/regular-flow.js +219 -0
- package/bin/libs/sdk/sdk-impl/spells/spells-executors.js +307 -0
- package/bin/libs/sdk/sdk-impl/spells/spells-filesystem.js +72 -0
- package/bin/libs/sdk/sdk-impl/spells/spells-main.js +87 -0
- package/bin/libs/sdk/sdk-impl/spells/spells-parser.js +60 -0
- package/bin/libs/sdk/sdk-impl/spells/spells-types.js +0 -0
- package/bin/libs/sdk/sdk-impl/utils/tools/tools-agg.js +149 -0
- package/bin/libs/sdk/sdk-impl/utils/tools/tools-impl.js +21 -0
- package/bin/libs/sdk/sdk-impl/utils/utils-build.js +102 -0
- package/bin/libs/sdk/sdk-impl/utils/utils-bump.js +238 -0
- package/bin/libs/sdk/sdk-impl/utils/utils-clean.js +35 -0
- package/bin/libs/sdk/sdk-impl/utils/utils-consts.js +17 -0
- package/bin/libs/sdk/sdk-impl/utils/utils-cwd.js +36 -0
- package/bin/libs/sdk/sdk-impl/utils/utils-deps.js +73 -0
- package/bin/libs/sdk/sdk-impl/utils/utils-determine.js +25 -0
- package/bin/libs/sdk/sdk-impl/utils/utils-error.js +17 -0
- package/bin/libs/sdk/sdk-impl/utils/utils-fs.js +202 -0
- package/bin/libs/sdk/sdk-impl/utils/utils-info.js +42 -0
- package/bin/libs/sdk/sdk-impl/utils/utils-jsr-json.js +51 -0
- package/bin/libs/sdk/sdk-impl/utils/utils-paths.js +658 -0
- package/bin/libs/sdk/sdk-impl/utils/utils-perf.js +22 -0
- package/bin/libs/sdk/sdk-impl/utils/utils-pkg-json-libs.js +259 -0
- package/bin/libs/sdk/sdk-impl/utils/utils-pkg-json-reg.js +207 -0
- package/bin/libs/sdk/sdk-impl/utils/utils-tsconfig.js +44 -0
- package/bin/libs/sdk/sdk-main.js +114 -0
- package/bin/libs/sdk/sdk-types.js +1 -0
- package/bin/load.js +27 -0
- package/bin/main.js +46 -0
- package/bin/tools.txt +92 -0
- package/bin/types.js +0 -0
- 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
|
+
}
|