@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,121 @@
1
+ import { relinka } from "@reliverse/relinka";
2
+ import {
3
+ defineCommand,
4
+ errorHandler,
5
+ multiselectPrompt,
6
+ runMain,
7
+ selectPrompt,
8
+ } from "@reliverse/rempts";
9
+
10
+ import type {
11
+ FileType,
12
+ InitFileRequest,
13
+ } from "./libs/reinit/reint-impl/types.js";
14
+
15
+ import { FILE_TYPES } from "./libs/reinit/reint-impl/const.js";
16
+ import { initFile, initFiles } from "./libs/reinit/reint-impl/mod.js";
17
+
18
+ const main = defineCommand({
19
+ meta: {
20
+ name: "reinit",
21
+ version: "1.0.0",
22
+ description: "@reliverse/reinit-cli",
23
+ },
24
+ args: {
25
+ dev: {
26
+ type: "boolean",
27
+ description: "Runs the CLI in dev mode",
28
+ },
29
+ fileType: {
30
+ type: "string",
31
+ description: "File type to initialize (e.g. 'md:README')",
32
+ required: false,
33
+ },
34
+ destDir: {
35
+ type: "string",
36
+ description: "Destination directory",
37
+ default: ".",
38
+ required: false,
39
+ },
40
+ multiple: {
41
+ type: "boolean",
42
+ description: "Whether to select multiple file types from the library",
43
+ required: false,
44
+ default: false,
45
+ },
46
+ parallel: {
47
+ type: "boolean",
48
+ description: "Run tasks in parallel",
49
+ required: false,
50
+ default: false,
51
+ },
52
+ concurrency: {
53
+ type: "string",
54
+ description: "Concurrency limit if parallel is true",
55
+ required: false,
56
+ default: "4",
57
+ },
58
+ },
59
+ async run({ args }) {
60
+ const { fileType, destDir, multiple, parallel, concurrency } = args;
61
+ const concurrencyNum = Number(concurrency);
62
+
63
+ // throw error if fileType doesn't include FILE_TYPES.type
64
+ if (fileType && !FILE_TYPES.find((ft) => ft.type === fileType)) {
65
+ throw new Error(`Invalid file type: ${fileType}`);
66
+ }
67
+
68
+ const effectiveFileType: FileType = fileType as FileType;
69
+
70
+ if (multiple) {
71
+ // Let the user choose multiple file types from a prompt
72
+ const possibleTypes = FILE_TYPES.map((ft) => ft.type);
73
+ const chosen = await multiselectPrompt({
74
+ title: "Select file types to initialize",
75
+ options: possibleTypes.map((pt) => ({ label: pt, value: pt })),
76
+ });
77
+
78
+ if (chosen.length === 0) {
79
+ relinka("info", "No file types selected. Exiting...");
80
+ return;
81
+ }
82
+
83
+ // Construct an array of requests
84
+ const requests: InitFileRequest[] = chosen.map((ct) => ({
85
+ fileType: ct,
86
+ destDir,
87
+ }));
88
+
89
+ const results = await initFiles(requests, {
90
+ parallel,
91
+ concurrency: concurrencyNum,
92
+ });
93
+ relinka("verbose", `Multiple files result: ${JSON.stringify(results)}`);
94
+ } else {
95
+ // Single file approach
96
+ let finalFileType = effectiveFileType;
97
+ if (!finalFileType) {
98
+ // If user didn't specify, prompt for a single file type
99
+ const possibleTypes = FILE_TYPES.map((ft) => ft.type);
100
+ const picked = await selectPrompt({
101
+ title: "Pick a file type to initialize",
102
+ options: possibleTypes.map((pt) => ({ label: pt, value: pt })),
103
+ });
104
+ finalFileType = picked;
105
+ }
106
+
107
+ const result = await initFile({
108
+ fileType: finalFileType,
109
+ destDir,
110
+ });
111
+ relinka("verbose", `Single file result: ${JSON.stringify(result)}`);
112
+ }
113
+ },
114
+ });
115
+
116
+ await runMain(main).catch((error: unknown) => {
117
+ errorHandler(
118
+ error instanceof Error ? error : new Error(String(error)),
119
+ "An unhandled error occurred, please report it at https://github.com/reliverse/reinit",
120
+ );
121
+ });
@@ -0,0 +1 @@
1
+ export * from "./libs/reinit/reint-impl/types.js";
@@ -0,0 +1,148 @@
1
+ # @reliverse/reinject | Reinjection CLI & Core
2
+
3
+ [💖 GitHub Sponsors](https://github.com/sponsors/blefnk) • [💬 Discord](https://discord.gg/Pb8uKbwpsJ) • [📦 NPM](https://npmjs.com/@reliverse/reinject) • [📚 Docs](https://blefnk.reliverse.org/blog/my-products/reinject)
4
+
5
+ **@reliverse/reinject** handles the boring parts for you. For example:
6
+
7
+ - ✅ Need to insert `// @ts-expect-error` above a TypeScript error? Reinject’s got you.
8
+ - 🔜 Fixing repetitive warns, lint suppressions, or compiler nags? One-liner.
9
+ - 🔜 Even more features to come!
10
+
11
+ ## 🛠️ What it can do
12
+
13
+ - 🧠 Inject comments like `@ts-expect-error` or `eslint-disable` above problematic lines
14
+ - 📄 Works with linter logs (tsc, eslint, biome, etc.)
15
+ - 🚀 Processes entire projects or specific paths
16
+ - ✂️ Supports selective injection via filters or patterns
17
+ - 🤖 Ideal for AI-assisted workflows and auto-"fixing" legacy code
18
+
19
+ ## ⚡ Getting Started
20
+
21
+ Make sure you have Git, Node.js, and bun•pnpm•yarn•npm installed.
22
+
23
+ ### Installation
24
+
25
+ ```bash
26
+ bun i -g @reliverse/reinject
27
+ ```
28
+
29
+ Or use with `bun x` (or `npx`):
30
+
31
+ ```bash
32
+ bun x @reliverse/reinject
33
+ ```
34
+
35
+ ### Basic Usage
36
+
37
+ **User config** in `reinject.config.ts`:
38
+
39
+ ```ts
40
+ export default {
41
+ injectComment: "// @ts-expect-error TODO: fix ts",
42
+ tscCommand: "tsc --project ./tsconfig.json --noEmit"
43
+ }
44
+ ```
45
+
46
+ **Running**:
47
+
48
+ ```bash
49
+ # 1) Automatic mode:
50
+ reinject ts-expect-error auto
51
+ # => runs `tsc`, finds errors, injects comment above them.
52
+
53
+ # 2) Lines-file mode:
54
+ reinject ts-expect-error linesA.txt linesB.txt
55
+ # => no TSC, just parses references from lines files
56
+
57
+ # 3) Mixed:
58
+ reinject ts-expect-error auto lines.txt
59
+ # => merges TSC errors with references in lines.txt
60
+ ```
61
+
62
+ **And**:
63
+
64
+ ```bash
65
+ # When you need a custom comment:
66
+ reinject ts-expect-error auto --comment="// @ts-expect-error FIXME"
67
+ ```
68
+
69
+ Run on a TypeScript file with tsc output:
70
+
71
+ ```bash
72
+ reinject ts-expect-error src
73
+ ```
74
+
75
+ You can also run on a specific TypeScript file with manually generated tsc output:
76
+
77
+ ```bash
78
+ tsc --noEmit > tsc.log
79
+ reinject tsc.log
80
+ rm tsc.log
81
+ ```
82
+
83
+ You can also run it directly on output from stdin:
84
+
85
+ ```bash
86
+ tsc --noEmit | reinject
87
+ ```
88
+
89
+ Or use with other tools:
90
+
91
+ ```bash
92
+ eslint . | reinject
93
+ biome check . | reinject
94
+ ```
95
+
96
+ ### Filter by error code or rule ID
97
+
98
+ ```bash
99
+ reinject tsc.log --code TS2322
100
+ reinject eslint.log --rule no-unused-vars
101
+ ```
102
+
103
+ ## ✨ Examples
104
+
105
+ ```ts
106
+ const x: string = 123;
107
+ ```
108
+
109
+ ➡️ becomes:
110
+
111
+ ```ts
112
+ // @ts-expect-error: TS2322
113
+ const x: string = 123;
114
+ ```
115
+
116
+ ## 🧪 CLI Flags
117
+
118
+ | Flag | Description |
119
+ |------------------|--------------------------------------------------|
120
+ | `--code` | Filter by TS error code or ESLint rule name |
121
+ | `--paths` | Restrict injection to matching file paths |
122
+ | `--comment` | Custom comment instead of default |
123
+ | `--dry-run` | Preview changes without writing to disk |
124
+ | `--dev` | Run in dev mode |
125
+
126
+ ## ✅ TODO
127
+
128
+ - [x] Inject `@ts-expect-error`, `eslint-disable`, etc.
129
+ - [x] Parse output from multiple linters
130
+ - [ ] Add custom comment templates
131
+ - [ ] Improve support for multi-line diagnostics
132
+
133
+ ## 🔋 Powered by
134
+
135
+ - ⚡ [`@reliverse/rempts`](https://npmjs.com/@reliverse/rempts) — interactive prompts
136
+ - 🧠 Smart AST & text transformations
137
+
138
+ ## 🫶 Show some love
139
+
140
+ If `@reliverse/reinject` saved you time or sanity:
141
+
142
+ - ⭐ [Star the repo](https://github.com/reliverse/reinject)
143
+ - 💖 [Sponsor on GitHub](https://github.com/sponsors/blefnk)
144
+ - 🫶 Share it with a dev friend!
145
+
146
+ ## 📄 License
147
+
148
+ MIT © 2025 [blefnk (Nazar Kornienko)](https://github.com/blefnk)
@@ -0,0 +1,49 @@
1
+ import { relinka } from "@reliverse/relinka";
2
+ import { defineCommand } from "@reliverse/rempts";
3
+
4
+ import { useTsExpectError } from "./ts-expect-error.js";
5
+
6
+ export default defineCommand({
7
+ meta: {
8
+ name: "tee",
9
+ description: "Inject `@ts-expect-error` above lines where TS errors occur",
10
+ },
11
+ args: {
12
+ dev: {
13
+ type: "boolean",
14
+ description: "Run the CLI in dev mode",
15
+ },
16
+ files: {
17
+ type: "positional",
18
+ // array: true, // TODO: implement in dler
19
+ required: true,
20
+ description: `'auto' or path(s) to line references file(s)`,
21
+ },
22
+ comment: {
23
+ type: "string",
24
+ required: false,
25
+ description:
26
+ "Override the comment line to insert. Default is `// @ts-expect-error TODO: fix ts`",
27
+ },
28
+ tscPaths: {
29
+ type: "string",
30
+ // array: true,
31
+ required: false,
32
+ description:
33
+ "Optional: specify path(s) to restrict TSC processing (only effective when using 'auto')",
34
+ },
35
+ },
36
+ run: async ({ args }) => {
37
+ if (args.dev) {
38
+ relinka("verbose", "Using dev mode");
39
+ }
40
+
41
+ await useTsExpectError({
42
+ files: [args.files],
43
+ comment: args.comment,
44
+ tscPaths: [args.tscPaths],
45
+ });
46
+
47
+ process.exit(0);
48
+ },
49
+ });
@@ -0,0 +1,32 @@
1
+ import { relinka } from "@reliverse/relinka";
2
+ import { defineCommand, selectPrompt } from "@reliverse/rempts";
3
+ export default defineCommand({
4
+ meta: {
5
+ name: "cli",
6
+ description: "Runs the Reinject CLI interactive menu (displays list of available commands)"
7
+ },
8
+ args: {
9
+ dev: {
10
+ type: "boolean",
11
+ description: "Runs the CLI in dev mode"
12
+ },
13
+ cwd: {
14
+ type: "string",
15
+ description: "The working directory to run the CLI in",
16
+ required: false
17
+ }
18
+ },
19
+ run: async ({ args }) => {
20
+ const isDev = args.dev;
21
+ relinka("verbose", `Running in ${isDev ? "dev" : "prod"} mode`);
22
+ await selectPrompt({
23
+ title: "Select a command",
24
+ options: [
25
+ {
26
+ value: "ts-expect-error",
27
+ label: "Inject `@ts-expect-error` above lines where TS errors occur"
28
+ }
29
+ ]
30
+ });
31
+ }
32
+ });
@@ -0,0 +1,28 @@
1
+ // todo: implement
2
+ import { defineCommand, errorHandler, runMain } from "@reliverse/rempts";
3
+
4
+ const main = defineCommand({
5
+ meta: {
6
+ name: "reinject",
7
+ version: "1.0.0",
8
+ description: "@reliverse/reinject-cli",
9
+ },
10
+ args: {
11
+ dev: {
12
+ type: "boolean",
13
+ description: "Runs the CLI in dev mode",
14
+ },
15
+ },
16
+ subCommands: {
17
+ cli: () => import("./cli/cli-mod.js").then((r) => r.default),
18
+ tee: () =>
19
+ import("./cli/args/arg-ts-expect-error.js").then((r) => r.default),
20
+ },
21
+ });
22
+
23
+ await runMain(main).catch((error: unknown) => {
24
+ errorHandler(
25
+ error instanceof Error ? error : new Error(String(error)),
26
+ "An unhandled error occurred, please report it at https://github.com/reliverse/reinject",
27
+ );
28
+ });
@@ -0,0 +1,4 @@
1
+ export default {
2
+ injectComment: "// @ts-expect-error TODO: fix ts",
3
+ tscCommand: "tsc --project ./tsconfig.json --noEmit"
4
+ };
@@ -0,0 +1,277 @@
1
+ import { relinka } from "@reliverse/relinka";
2
+ import { loadConfig } from "c12";
3
+ import { execa } from "execa";
4
+ import fs from "fs-extra";
5
+ import path from "pathe";
6
+
7
+ //-------------------------------------
8
+ // 1) c12 config interface
9
+ //-------------------------------------
10
+ type ReinjectUserConfig = {
11
+ // The comment to inject above each error line
12
+ injectComment?: string;
13
+ // The command used to spawn tsc (e.g. "tsc --noEmit --project tsconfig.json")
14
+ tscCommand?: string;
15
+ };
16
+
17
+ //-------------------------------------
18
+ // 2) Load c12 configs
19
+ //-------------------------------------
20
+ async function loadReinjectConfig(): Promise<ReinjectUserConfig> {
21
+ const { config } = await loadConfig<ReinjectUserConfig>({
22
+ name: "reinject", // tries reinject.config.*, .reinjectrc, etc.
23
+ defaults: {},
24
+ overrides: {},
25
+ dotenv: false,
26
+ packageJson: false,
27
+ });
28
+
29
+ return {
30
+ injectComment: config.injectComment ?? "// @ts-expect-error TODO: fix ts",
31
+ tscCommand: config.tscCommand ?? "tsc --noEmit",
32
+ };
33
+ }
34
+
35
+ //-------------------------------------
36
+ // Helper: Parse command string into command and arguments
37
+ //-------------------------------------
38
+ function parseCommand(command: string): { cmd: string; args: string[] } {
39
+ // This parser splits the command by whitespace while handling double or single quotes.
40
+ const regex = /"([^"]+)"|'([^']+)'|(\S+)/g;
41
+ const args: string[] = [];
42
+ let match: RegExpExecArray | null;
43
+ while ((match = regex.exec(command)) !== null) {
44
+ args.push(match[1] || match[2] || match[3]);
45
+ }
46
+ const cmd = args.shift() || "";
47
+ return { cmd, args };
48
+ }
49
+
50
+ //-------------------------------------
51
+ // 3) parseLineRefs from a lines file
52
+ //-------------------------------------
53
+ async function parseLinesFile(linesFile: string) {
54
+ const fileContents = await fs.readFile(linesFile, "utf-8");
55
+ const splitted = fileContents.split(/\r?\n/);
56
+ const results: { filePath: string; lineNumber: number }[] = [];
57
+
58
+ for (const rawLine of splitted) {
59
+ const trimmed = rawLine.trim();
60
+ if (!trimmed) continue;
61
+
62
+ // Could match "N path.ts:line"
63
+ let match = trimmed.match(/^(\d+)\s+(.+?):(\d+)$/);
64
+ if (match) {
65
+ results.push({
66
+ filePath: match[2],
67
+ lineNumber: Number.parseInt(match[3], 10),
68
+ });
69
+ continue;
70
+ }
71
+
72
+ // Or "path.ts:line"
73
+ match = trimmed.match(/^(.+?):(\d+)$/);
74
+ if (match) {
75
+ results.push({
76
+ filePath: match[1],
77
+ lineNumber: Number.parseInt(match[2], 10),
78
+ });
79
+ } else {
80
+ relinka("warn", `Line doesn't match expected format: ${trimmed}`);
81
+ }
82
+ }
83
+ return results;
84
+ }
85
+
86
+ //-------------------------------------
87
+ // 4) runTscAndParseErrors: run tsc with execa, parse error lines
88
+ // Example TSC error line format:
89
+ // src/foo.ts(12,5): error TS2322:
90
+ // We'll capture `src/foo.ts` and `12` as the error line number
91
+ //-------------------------------------
92
+ async function runTscAndParseErrors(
93
+ tscCommand: string,
94
+ tscPaths?: string[],
95
+ ): Promise<{ filePath: string; lineNumber: number }[]> {
96
+ const linesRefs: { filePath: string; lineNumber: number }[] = [];
97
+
98
+ try {
99
+ // Parse the TSC command into the command and its arguments.
100
+ const { cmd, args: cmdArgs } = parseCommand(tscCommand);
101
+ // Append any additional paths (if provided) as extra arguments.
102
+ if (tscPaths && tscPaths.length > 0) {
103
+ cmdArgs.push(...tscPaths);
104
+ }
105
+
106
+ // Run TSC.
107
+ const subprocess = await execa(cmd, cmdArgs, { all: true, reject: false });
108
+ const combinedOutput = subprocess.all || "";
109
+ const splitted = combinedOutput.split(/\r?\n/);
110
+ const regex = /^(.+?)\((\d+),(\d+)\): error TS\d+: /;
111
+
112
+ for (const line of splitted) {
113
+ const trimmed = line.trim();
114
+ const m = trimmed.match(regex);
115
+ if (m) {
116
+ let file = m[1];
117
+ const row = Number.parseInt(m[2], 10);
118
+ if (row > 0) {
119
+ // Normalize Windows paths.
120
+ file = file.replace(/\\/g, "/");
121
+ linesRefs.push({
122
+ filePath: file,
123
+ lineNumber: row,
124
+ });
125
+ }
126
+ }
127
+ }
128
+ } catch (error: any) {
129
+ // In case of error, try to extract the output.
130
+ const combined = (error.all as string) || "";
131
+ if (!combined) {
132
+ relinka("info", `TSC returned no error lines. Possibly no TS errors?`);
133
+ return [];
134
+ }
135
+
136
+ const splitted = combined.split(/\r?\n/);
137
+ const regex = /^(.+?)\((\d+),(\d+)\): error TS\d+: /;
138
+
139
+ for (const line of splitted) {
140
+ const m = line.trim().match(regex);
141
+ if (m) {
142
+ let file = m[1];
143
+ const row = Number.parseInt(m[2], 10);
144
+ if (row > 0) {
145
+ file = file.replace(/\\/g, "/");
146
+ linesRefs.push({ filePath: file, lineNumber: row });
147
+ }
148
+ }
149
+ }
150
+ }
151
+
152
+ return linesRefs;
153
+ }
154
+
155
+ //-------------------------------------
156
+ // Helper: Check if file is within any of the provided directories
157
+ //-------------------------------------
158
+ function isWithin(filePath: string, dirs: string[]): boolean {
159
+ const absFile = path.resolve(filePath);
160
+ for (const dir of dirs) {
161
+ const absDir = path.resolve(dir);
162
+ // Ensure trailing separator for accurate prefix matching
163
+ const normalizedDir = absDir.endsWith(path.sep)
164
+ ? absDir
165
+ : absDir + path.sep;
166
+ if (absFile.startsWith(normalizedDir)) {
167
+ return true;
168
+ }
169
+ }
170
+ return false;
171
+ }
172
+
173
+ //-------------------------------------
174
+ // 5) The injection logic
175
+ //-------------------------------------
176
+ async function injectCommentIntoFiles(
177
+ linesRecords: { filePath: string; lineNumber: number }[],
178
+ commentText: string,
179
+ ) {
180
+ // Group error lines by file
181
+ const byFile = new Map<string, number[]>();
182
+ for (const rec of linesRecords) {
183
+ if (!byFile.has(rec.filePath)) {
184
+ byFile.set(rec.filePath, []);
185
+ }
186
+ byFile.get(rec.filePath)!.push(rec.lineNumber);
187
+ }
188
+
189
+ for (const [filePath, lineNums] of byFile.entries()) {
190
+ // Sort descending so injections don't affect subsequent line numbers
191
+ lineNums.sort((a, b) => b - a);
192
+ const absPath = path.resolve(filePath);
193
+ relinka(
194
+ "info",
195
+ `Injecting into ${absPath} at lines: ${lineNums.join(", ")}`,
196
+ );
197
+
198
+ try {
199
+ const original = await fs.readFile(absPath, "utf-8");
200
+ const splitted = original.split(/\r?\n/);
201
+ for (const ln of lineNums) {
202
+ if (ln <= splitted.length) {
203
+ splitted.splice(ln - 1, 0, commentText);
204
+ } else {
205
+ relinka("warn", `Line ${ln} exceeds file length for ${absPath}`);
206
+ }
207
+ }
208
+ const newContent = splitted.join("\n");
209
+ await fs.writeFile(absPath, newContent, "utf-8");
210
+ } catch (error) {
211
+ relinka("error", `Failed editing ${filePath}: ${error}`);
212
+ }
213
+ }
214
+ }
215
+
216
+ //-------------------------------------
217
+ // 6) The main usage function
218
+ //-------------------------------------
219
+ export async function useTsExpectError(args: {
220
+ files: string[];
221
+ comment?: string;
222
+ tscPaths?: string[];
223
+ }) {
224
+ // 1) load c12 config
225
+ const userConfig = await loadReinjectConfig();
226
+ const finalComment = args.comment || userConfig.injectComment!;
227
+
228
+ // Gather references
229
+ const lines: { filePath: string; lineNumber: number }[] = [];
230
+ let usedAuto = false;
231
+ for (const item of args.files) {
232
+ if (item.toLowerCase() === "auto") {
233
+ usedAuto = true;
234
+ }
235
+ }
236
+
237
+ if (usedAuto) {
238
+ relinka("info", "Running TSC to discover error lines...");
239
+ const tscCommand = userConfig.tscCommand!;
240
+ try {
241
+ const discovered = await runTscAndParseErrors(tscCommand, args.tscPaths);
242
+ // If tscPaths are provided, filter discovered errors to include only files within those paths.
243
+ if (args.tscPaths && args.tscPaths.length > 0) {
244
+ const filtered = discovered.filter((rec) =>
245
+ isWithin(rec.filePath, args.tscPaths!),
246
+ );
247
+ lines.push(...filtered);
248
+ } else {
249
+ lines.push(...discovered);
250
+ }
251
+ } catch (error) {
252
+ relinka("error", `Failed running tsc: ${error}`);
253
+ process.exit(1);
254
+ }
255
+ }
256
+
257
+ // Parse lines from each file that isn't "auto"
258
+ for (const item of args.files) {
259
+ if (item.toLowerCase() === "auto") continue;
260
+
261
+ try {
262
+ const recs = await parseLinesFile(item);
263
+ lines.push(...recs);
264
+ } catch (error) {
265
+ relinka("error", `Failed reading lines file ${item}: ${error}`);
266
+ }
267
+ }
268
+
269
+ if (lines.length === 0) {
270
+ relinka("error", "No references found. Nothing to do.");
271
+ relinka("error", "Lines: ", JSON.stringify(lines));
272
+ process.exit(1);
273
+ }
274
+
275
+ await injectCommentIntoFiles(lines, finalComment);
276
+ relinka("success", "All lines processed successfully.");
277
+ }