miniread 1.0.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 (109) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +86 -0
  3. package/bin/miniread +17 -0
  4. package/bin/miniread-evaluate +17 -0
  5. package/bin/miniread-sample +17 -0
  6. package/dist/cli/config.d.ts +17 -0
  7. package/dist/cli/config.js +85 -0
  8. package/dist/cli/generate-code.d.ts +2 -0
  9. package/dist/cli/generate-code.js +7 -0
  10. package/dist/cli/output.d.ts +7 -0
  11. package/dist/cli/output.js +119 -0
  12. package/dist/cli/run-transforms.d.ts +16 -0
  13. package/dist/cli/run-transforms.js +145 -0
  14. package/dist/cli/runner.d.ts +3 -0
  15. package/dist/cli/runner.js +2 -0
  16. package/dist/cli/transform-stdin.d.ts +14 -0
  17. package/dist/cli/transform-stdin.js +58 -0
  18. package/dist/cli.d.ts +2 -0
  19. package/dist/cli.js +121 -0
  20. package/dist/core/paths.d.ts +8 -0
  21. package/dist/core/paths.js +9 -0
  22. package/dist/core/project-graph.d.ts +6 -0
  23. package/dist/core/project-graph.js +40 -0
  24. package/dist/core/types.d.ts +30 -0
  25. package/dist/core/types.js +1 -0
  26. package/dist/index.d.ts +7 -0
  27. package/dist/index.js +7 -0
  28. package/dist/scripts/evaluate/create-evaluate-command.d.ts +5 -0
  29. package/dist/scripts/evaluate/create-evaluate-command.js +47 -0
  30. package/dist/scripts/evaluate/diff-utilities.d.ts +17 -0
  31. package/dist/scripts/evaluate/diff-utilities.js +114 -0
  32. package/dist/scripts/evaluate/evaluation-report.d.ts +22 -0
  33. package/dist/scripts/evaluate/evaluation-report.js +1 -0
  34. package/dist/scripts/evaluate/evaluation-types.d.ts +16 -0
  35. package/dist/scripts/evaluate/evaluation-types.js +1 -0
  36. package/dist/scripts/evaluate/metrics.d.ts +3 -0
  37. package/dist/scripts/evaluate/metrics.js +11 -0
  38. package/dist/scripts/evaluate/pair-evaluator.d.ts +21 -0
  39. package/dist/scripts/evaluate/pair-evaluator.js +104 -0
  40. package/dist/scripts/evaluate/parse-evaluate-cli-options.d.ts +40 -0
  41. package/dist/scripts/evaluate/parse-evaluate-cli-options.js +97 -0
  42. package/dist/scripts/evaluate/parse-transform-manifest.d.ts +22 -0
  43. package/dist/scripts/evaluate/parse-transform-manifest.js +29 -0
  44. package/dist/scripts/evaluate/resolve-evaluate-dependencies.d.ts +9 -0
  45. package/dist/scripts/evaluate/resolve-evaluate-dependencies.js +66 -0
  46. package/dist/scripts/evaluate/run-evaluate-cli.d.ts +1 -0
  47. package/dist/scripts/evaluate/run-evaluate-cli.js +112 -0
  48. package/dist/scripts/evaluate/run-evaluations.d.ts +25 -0
  49. package/dist/scripts/evaluate/run-evaluations.js +50 -0
  50. package/dist/scripts/evaluate/run-pair-transformations.d.ts +21 -0
  51. package/dist/scripts/evaluate/run-pair-transformations.js +57 -0
  52. package/dist/scripts/evaluate/sanitize.d.ts +2 -0
  53. package/dist/scripts/evaluate/sanitize.js +6 -0
  54. package/dist/scripts/evaluate/shell-utilities.d.ts +15 -0
  55. package/dist/scripts/evaluate/shell-utilities.js +68 -0
  56. package/dist/scripts/evaluate/transform-id-set.d.ts +3 -0
  57. package/dist/scripts/evaluate/transform-id-set.js +29 -0
  58. package/dist/scripts/evaluate/transform-manifest.d.ts +13 -0
  59. package/dist/scripts/evaluate/transform-manifest.js +102 -0
  60. package/dist/scripts/evaluate/write-evaluation-stdout.d.ts +7 -0
  61. package/dist/scripts/evaluate/write-evaluation-stdout.js +20 -0
  62. package/dist/scripts/evaluate/write-text-file-atomic.d.ts +5 -0
  63. package/dist/scripts/evaluate/write-text-file-atomic.js +47 -0
  64. package/dist/scripts/evaluate.d.ts +2 -0
  65. package/dist/scripts/evaluate.js +13 -0
  66. package/dist/scripts/sample/choose-line-window.d.ts +11 -0
  67. package/dist/scripts/sample/choose-line-window.js +19 -0
  68. package/dist/scripts/sample/clip-text-around-core.d.ts +12 -0
  69. package/dist/scripts/sample/clip-text-around-core.js +61 -0
  70. package/dist/scripts/sample/collect-function-candidates.d.ts +20 -0
  71. package/dist/scripts/sample/collect-function-candidates.js +82 -0
  72. package/dist/scripts/sample/create-function-excerpt.d.ts +20 -0
  73. package/dist/scripts/sample/create-function-excerpt.js +37 -0
  74. package/dist/scripts/sample/create-sample-command.d.ts +17 -0
  75. package/dist/scripts/sample/create-sample-command.js +50 -0
  76. package/dist/scripts/sample/extract-function-samples.d.ts +31 -0
  77. package/dist/scripts/sample/extract-function-samples.js +105 -0
  78. package/dist/scripts/sample/find-source-files.d.ts +1 -0
  79. package/dist/scripts/sample/find-source-files.js +49 -0
  80. package/dist/scripts/sample/format-sample-output.d.ts +22 -0
  81. package/dist/scripts/sample/format-sample-output.js +29 -0
  82. package/dist/scripts/sample/line-offsets.d.ts +4 -0
  83. package/dist/scripts/sample/line-offsets.js +40 -0
  84. package/dist/scripts/sample/parse-sample-cli-options.d.ts +18 -0
  85. package/dist/scripts/sample/parse-sample-cli-options.js +36 -0
  86. package/dist/scripts/sample/random.d.ts +10 -0
  87. package/dist/scripts/sample/random.js +60 -0
  88. package/dist/scripts/sample/run-sample-cli.d.ts +1 -0
  89. package/dist/scripts/sample/run-sample-cli.js +79 -0
  90. package/dist/scripts/sample.d.ts +2 -0
  91. package/dist/scripts/sample.js +13 -0
  92. package/dist/transforms/add-prefix/add-prefix-transform.d.ts +2 -0
  93. package/dist/transforms/add-prefix/add-prefix-transform.js +40 -0
  94. package/dist/transforms/add-suffix/add-suffix-transform.d.ts +2 -0
  95. package/dist/transforms/add-suffix/add-suffix-transform.js +40 -0
  96. package/dist/transforms/expand-boolean-literals/expand-boolean-literals-transform.d.ts +2 -0
  97. package/dist/transforms/expand-boolean-literals/expand-boolean-literals-transform.js +39 -0
  98. package/dist/transforms/expand-undefined-literals/expand-undefined-literals-transform.d.ts +2 -0
  99. package/dist/transforms/expand-undefined-literals/expand-undefined-literals-transform.js +40 -0
  100. package/dist/transforms/rename-catch-parameters/rename-catch-parameters-transform.d.ts +2 -0
  101. package/dist/transforms/rename-catch-parameters/rename-catch-parameters-transform.js +67 -0
  102. package/dist/transforms/rename-loop-index-variables/rename-loop-index-variables-transform.d.ts +2 -0
  103. package/dist/transforms/rename-loop-index-variables/rename-loop-index-variables-transform.js +125 -0
  104. package/dist/transforms/transform-presets.d.ts +3 -0
  105. package/dist/transforms/transform-presets.js +19 -0
  106. package/dist/transforms/transform-registry.d.ts +3 -0
  107. package/dist/transforms/transform-registry.js +15 -0
  108. package/package.json +84 -0
  109. package/transform-manifest.json +70 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Łukasz Jerciński
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,86 @@
1
+ # miniread
2
+
3
+ Transform minified JavaScript/TypeScript into a more readable form using deterministic AST-based transforms.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install --global miniread
9
+ ```
10
+
11
+ ## Quick start
12
+
13
+ ```bash
14
+ miniread --input ./minified --output ./readable
15
+ cat bundle.min.js | miniread > bundle.js
16
+ miniread --list-transforms
17
+ miniread --input ./minified --output ./readable --transforms recommended
18
+ miniread --input ./minified --output ./readable --dry-run
19
+ miniread --input ./minified --output ./readable --workers 8
20
+ ```
21
+
22
+ ## Examples
23
+
24
+ ### Find the most common identifiers in a minified bundle
25
+
26
+ ```bash
27
+ cat bundle.min.js | miniread | grep -oE '[A-Za-z_][A-Za-z0-9_]+' | sort | uniq -c | sort -rn | head -20
28
+ ```
29
+
30
+ ### Locate error-throwing code paths quickly
31
+
32
+ ```bash
33
+ cat bundle.min.js | miniread | grep -n "throw new" | head -20
34
+ ```
35
+
36
+ ### Use transform metadata in scripts (TSV)
37
+
38
+ ```bash
39
+ miniread --list-transforms --format tsv | tail -n +2 | cut -f1
40
+ ```
41
+
42
+ ## Agent Rule
43
+
44
+ Add to your `CLAUDE.md` or `AGENTS.md`:
45
+
46
+ ```markdown
47
+ # Rule: `miniread` Usage
48
+
49
+ Run `npx -y miniread --help` to learn available options.
50
+
51
+ Use `miniread` when you need readable JavaScript/TypeScript from minified input for review, diffing, or grepping; it works as a stdin/stdout filter so it composes well with Unix pipelines.
52
+ ```
53
+
54
+ ## Development
55
+
56
+ ```bash
57
+ pnpm install
58
+ pnpm run build
59
+ node bin/miniread --help
60
+ ```
61
+
62
+ ## miniread-evaluate (developer tool)
63
+
64
+ The `miniread-evaluate` CLI compares transform configurations by measuring diff reduction across version pairs under `./sources/`. It prints TSV to stdout by default (or JSON with `--format json`) and sends logs/errors to stderr.
65
+
66
+ ### Requirements
67
+
68
+ - `diff` (override with `MINIREAD_EVALUATE_DIFF_PATH`)
69
+ - `pnpm` + `prettier` (used unless `--no-format-code`; overrides: `MINIREAD_EVALUATE_PNPM_PATH`, `MINIREAD_EVALUATE_PRETTIER_PATH`)
70
+
71
+ ### Usage
72
+
73
+ ```bash
74
+ pnpm run miniread-evaluate -- --help
75
+
76
+ # Compare baseline none vs a transform
77
+ pnpm run miniread-evaluate -- --compare v1.0:v1.1 --baseline none --test add-prefix
78
+
79
+ # Pipeline example: sort by reduction ratio (best first)
80
+ pnpm run miniread-evaluate -- --compare v1.0:v1.1 --compare v2.0:v2.1 --baseline none --test add-prefix \
81
+ | tail -n +2 | sort -k5,5nr | head -10
82
+ ```
83
+
84
+ ## License
85
+
86
+ MIT
package/bin/miniread ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CLI entry point that dynamically imports the compiled TypeScript.
4
+ *
5
+ * Uses top-level await to ensure module evaluation errors are handled
6
+ * properly. Without await, errors during import would surface as unhandled
7
+ * rejections instead of clean CLI failures with appropriate exit codes.
8
+ */
9
+ try {
10
+ await import("../dist/cli.js");
11
+ } catch (error) {
12
+ console.error(
13
+ "Failed to start miniread:",
14
+ error instanceof Error ? error.message : error,
15
+ );
16
+ process.exitCode = 1;
17
+ }
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CLI entry point that dynamically imports the compiled TypeScript.
4
+ *
5
+ * Uses top-level await to ensure module evaluation errors are handled
6
+ * properly. Without await, errors during import would surface as unhandled
7
+ * rejections instead of clean CLI failures with appropriate exit codes.
8
+ */
9
+ try {
10
+ await import("../dist/scripts/evaluate.js");
11
+ } catch (error) {
12
+ console.error(
13
+ "Failed to start miniread-evaluate:",
14
+ error instanceof Error ? error.message : error,
15
+ );
16
+ process.exitCode = 1;
17
+ }
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CLI entry point that dynamically imports the compiled TypeScript.
4
+ *
5
+ * Uses top-level await to ensure module evaluation errors are handled
6
+ * properly. Without await, errors during import would surface as unhandled
7
+ * rejections instead of clean CLI failures with appropriate exit codes.
8
+ */
9
+ try {
10
+ await import("../dist/scripts/sample.js");
11
+ } catch (error) {
12
+ console.error(
13
+ "Failed to start miniread-sample:",
14
+ error instanceof Error ? error.message : error,
15
+ );
16
+ process.exitCode = 1;
17
+ }
@@ -0,0 +1,17 @@
1
+ import type { Transform } from "../core/types.js";
2
+ type TransformSummary = {
3
+ id: string;
4
+ description: string;
5
+ scope: Transform["scope"];
6
+ parallelizable: boolean;
7
+ };
8
+ export declare const getTransformSummaries: () => TransformSummary[];
9
+ type GetTransformsResult = {
10
+ ok: true;
11
+ transforms: Transform[];
12
+ } | {
13
+ ok: false;
14
+ error: string;
15
+ };
16
+ export declare const getTransformsToRun: (transformList?: string) => GetTransformsResult;
17
+ export {};
@@ -0,0 +1,85 @@
1
+ import { transformPresets } from "../transforms/transform-presets.js";
2
+ import { allTransformIds, transformRegistry, } from "../transforms/transform-registry.js";
3
+ export const getTransformSummaries = () => {
4
+ const summaries = [];
5
+ for (const id of allTransformIds) {
6
+ const transform = transformRegistry[id];
7
+ if (!transform)
8
+ continue;
9
+ summaries.push({
10
+ id,
11
+ description: transform.description,
12
+ scope: transform.scope,
13
+ parallelizable: transform.parallelizable,
14
+ });
15
+ }
16
+ return summaries;
17
+ };
18
+ const getTransformsForPreset = (preset) => {
19
+ if (preset === "recommended" || preset === "default") {
20
+ return [...transformPresets.recommended];
21
+ }
22
+ if (preset === "all") {
23
+ return [...allTransformIds];
24
+ }
25
+ return;
26
+ };
27
+ export const getTransformsToRun = (transformList) => {
28
+ // undefined = run recommended transforms (default behavior)
29
+ if (transformList === undefined) {
30
+ const requestedIds = getTransformsForPreset("recommended");
31
+ if (!requestedIds || requestedIds.length === 0) {
32
+ return { ok: false, error: "No recommended transforms are configured." };
33
+ }
34
+ const transforms = [];
35
+ for (const id of requestedIds) {
36
+ const transform = transformRegistry[id];
37
+ if (!transform)
38
+ return { ok: false, error: `Unknown transform: ${id}` };
39
+ transforms.push(transform);
40
+ }
41
+ return { ok: true, transforms };
42
+ }
43
+ // Empty string = run no transforms (explicit empty list)
44
+ if (transformList === "" || transformList === "none") {
45
+ return { ok: true, transforms: [] };
46
+ }
47
+ // Parse comma-separated list
48
+ const requestedIds = transformList.split(",").map((id) => id.trim());
49
+ const expandedIds = [];
50
+ const unknownIds = [];
51
+ for (const id of requestedIds) {
52
+ if (!id || id === "none")
53
+ continue;
54
+ const presetTransforms = getTransformsForPreset(id);
55
+ if (presetTransforms) {
56
+ expandedIds.push(...presetTransforms);
57
+ continue;
58
+ }
59
+ expandedIds.push(id);
60
+ }
61
+ const uniqueIds = [];
62
+ const seen = new Set();
63
+ for (const id of expandedIds) {
64
+ if (seen.has(id))
65
+ continue;
66
+ seen.add(id);
67
+ uniqueIds.push(id);
68
+ }
69
+ const transforms = [];
70
+ for (const id of uniqueIds) {
71
+ const transform = transformRegistry[id];
72
+ if (!transform) {
73
+ unknownIds.push(id);
74
+ continue;
75
+ }
76
+ transforms.push(transform);
77
+ }
78
+ if (unknownIds.length > 0) {
79
+ return {
80
+ ok: false,
81
+ error: `Unknown transform(s): ${unknownIds.join(", ")}. Use --list-transforms to see available transforms.`,
82
+ };
83
+ }
84
+ return { ok: true, transforms };
85
+ };
@@ -0,0 +1,2 @@
1
+ import type { File } from "@babel/types";
2
+ export declare const generateCode: (ast: File) => string;
@@ -0,0 +1,7 @@
1
+ import { createRequire } from "node:module";
2
+ const require = createRequire(import.meta.url);
3
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
4
+ const generate = require("@babel/generator").default;
5
+ export const generateCode = (ast) => {
6
+ return generate(ast).code;
7
+ };
@@ -0,0 +1,7 @@
1
+ import type PQueue from "p-queue";
2
+ export declare const printDryRunSummary: (finalFiles: Map<string, string>, otherFilePaths: string[], inputBase: string) => void;
3
+ type WriteOutputOptions = {
4
+ overwrite: boolean;
5
+ };
6
+ export declare const writeOutputFiles: (finalFiles: Map<string, string>, otherFilePaths: string[], inputBase: string, output: string, queue: PQueue, options: WriteOutputOptions) => Promise<void>;
7
+ export {};
@@ -0,0 +1,119 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { constants as fsConstants } from "node:fs";
3
+ import * as fs from "node:fs/promises";
4
+ import path from "node:path";
5
+ export const printDryRunSummary = (finalFiles, otherFilePaths, inputBase) => {
6
+ process.stdout.write("ACTION\tPATH\tBYTES\n");
7
+ for (const [filePath, content] of finalFiles) {
8
+ const relativePath = path.relative(inputBase, filePath);
9
+ process.stdout.write(`TRANSFORM\t${relativePath}\t${content.length}\n`);
10
+ }
11
+ for (const filePath of otherFilePaths) {
12
+ const relativePath = path.relative(inputBase, filePath);
13
+ process.stdout.write(`COPY\t${relativePath}\t\n`);
14
+ }
15
+ };
16
+ const isErrnoException = (error) => {
17
+ return error instanceof Error;
18
+ };
19
+ const commitTemporaryFile = async (temporaryPath, outputPath, options) => {
20
+ if (options.overwrite) {
21
+ try {
22
+ await fs.rename(temporaryPath, outputPath);
23
+ return;
24
+ }
25
+ catch (error) {
26
+ if (!isErrnoException(error))
27
+ throw error;
28
+ if (process.platform !== "win32")
29
+ throw error;
30
+ if (error.code !== "EEXIST" && error.code !== "EPERM")
31
+ throw error;
32
+ await fs.unlink(outputPath);
33
+ await fs.rename(temporaryPath, outputPath);
34
+ return;
35
+ }
36
+ }
37
+ if (process.platform === "win32") {
38
+ await fs.rename(temporaryPath, outputPath);
39
+ return;
40
+ }
41
+ await fs.link(temporaryPath, outputPath);
42
+ await fs.unlink(temporaryPath);
43
+ };
44
+ const writeTextFileAtomic = async (outputPath, content, options) => {
45
+ const outputDirectory = path.dirname(outputPath);
46
+ const temporaryPath = path.join(outputDirectory, `.miniread-tmp-${randomUUID()}`);
47
+ await fs.writeFile(temporaryPath, content, { encoding: "utf8", flag: "wx" });
48
+ try {
49
+ await commitTemporaryFile(temporaryPath, outputPath, options);
50
+ }
51
+ catch (error) {
52
+ await fs.unlink(temporaryPath).catch(() => { });
53
+ throw error;
54
+ }
55
+ };
56
+ const copyFileAtomic = async (sourcePath, outputPath, options) => {
57
+ const outputDirectory = path.dirname(outputPath);
58
+ const temporaryPath = path.join(outputDirectory, `.miniread-tmp-${randomUUID()}`);
59
+ await fs.copyFile(sourcePath, temporaryPath, fsConstants.COPYFILE_EXCL);
60
+ try {
61
+ await commitTemporaryFile(temporaryPath, outputPath, options);
62
+ }
63
+ catch (error) {
64
+ await fs.unlink(temporaryPath).catch(() => { });
65
+ throw error;
66
+ }
67
+ };
68
+ export const writeOutputFiles = async (finalFiles, otherFilePaths, inputBase, output, queue, options) => {
69
+ const conflicts = [];
70
+ if (!options.overwrite) {
71
+ for (const filePath of finalFiles.keys()) {
72
+ const relativePath = path.relative(inputBase, filePath);
73
+ const outputPath = path.join(output, relativePath);
74
+ try {
75
+ await fs.access(outputPath);
76
+ conflicts.push(relativePath);
77
+ }
78
+ catch {
79
+ // ok
80
+ }
81
+ }
82
+ for (const filePath of otherFilePaths) {
83
+ const relativePath = path.relative(inputBase, filePath);
84
+ const outputPath = path.join(output, relativePath);
85
+ try {
86
+ await fs.access(outputPath);
87
+ conflicts.push(relativePath);
88
+ }
89
+ catch {
90
+ // ok
91
+ }
92
+ }
93
+ }
94
+ if (conflicts.length > 0) {
95
+ const sample = conflicts.slice(0, 5).join(", ");
96
+ throw new Error(`Refusing to overwrite existing output file(s): ${sample}. Re-run with --overwrite to replace.`);
97
+ }
98
+ await fs.mkdir(output, { recursive: true });
99
+ const writePromises = [];
100
+ for (const [filePath, content] of finalFiles) {
101
+ const relativePath = path.relative(inputBase, filePath);
102
+ const outputPath = path.join(output, relativePath);
103
+ writePromises.push(queue.add(async () => {
104
+ const outputDirectory = path.dirname(outputPath);
105
+ await fs.mkdir(outputDirectory, { recursive: true });
106
+ await writeTextFileAtomic(outputPath, content, options);
107
+ }));
108
+ }
109
+ for (const filePath of otherFilePaths) {
110
+ const relativePath = path.relative(inputBase, filePath);
111
+ const outputPath = path.join(output, relativePath);
112
+ writePromises.push(queue.add(async () => {
113
+ const outputDirectory = path.dirname(outputPath);
114
+ await fs.mkdir(outputDirectory, { recursive: true });
115
+ await copyFileAtomic(filePath, outputPath, options);
116
+ }));
117
+ }
118
+ await Promise.all(writePromises);
119
+ };
@@ -0,0 +1,16 @@
1
+ import type { Transform } from "../core/types.js";
2
+ export type RunnerOptions = {
3
+ input: string;
4
+ output: string;
5
+ transforms: Transform[];
6
+ dryRun: boolean;
7
+ workers: number;
8
+ overwrite: boolean;
9
+ verbose: boolean;
10
+ };
11
+ export type RunnerResult = {
12
+ filesProcessed: number;
13
+ totalTransformations: number;
14
+ errors: string[];
15
+ };
16
+ export declare const runTransforms: (options: RunnerOptions) => Promise<RunnerResult>;
@@ -0,0 +1,145 @@
1
+ import * as fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import PQueue from "p-queue";
4
+ import { buildProjectGraph } from "../core/project-graph.js";
5
+ import { printDryRunSummary, writeOutputFiles } from "./output.js";
6
+ import { generateCode } from "./generate-code.js";
7
+ const logVerbose = (verbose, message) => {
8
+ if (!verbose)
9
+ return;
10
+ console.error(message);
11
+ };
12
+ const isSourceFile = (fileName) => {
13
+ return (!fileName.endsWith(".d.ts") &&
14
+ (fileName.endsWith(".ts") ||
15
+ fileName.endsWith(".tsx") ||
16
+ fileName.endsWith(".js") ||
17
+ fileName.endsWith(".jsx")));
18
+ };
19
+ const findAllFiles = async (directory) => {
20
+ const sourceFiles = [];
21
+ const otherFiles = [];
22
+ const entries = await fs.readdir(directory, { withFileTypes: true });
23
+ for (const entry of entries) {
24
+ const fullPath = path.join(directory, entry.name);
25
+ if (entry.isDirectory()) {
26
+ const subResult = await findAllFiles(fullPath);
27
+ sourceFiles.push(...subResult.sourceFiles);
28
+ otherFiles.push(...subResult.otherFiles);
29
+ }
30
+ else if (entry.isFile()) {
31
+ if (isSourceFile(entry.name)) {
32
+ sourceFiles.push(fullPath);
33
+ }
34
+ else {
35
+ otherFiles.push(fullPath);
36
+ }
37
+ }
38
+ }
39
+ return { sourceFiles, otherFiles };
40
+ };
41
+ export const runTransforms = async (options) => {
42
+ const { input, output, transforms, dryRun, workers, overwrite, verbose } = options;
43
+ // Check if input exists
44
+ try {
45
+ await fs.access(input);
46
+ }
47
+ catch {
48
+ return {
49
+ filesProcessed: 0,
50
+ totalTransformations: 0,
51
+ errors: [`Input path does not exist: ${input}`],
52
+ };
53
+ }
54
+ // Find all files
55
+ const inputStat = await fs.stat(input);
56
+ let sourceFilePaths;
57
+ let otherFilePaths;
58
+ if (inputStat.isDirectory()) {
59
+ const allFiles = await findAllFiles(input);
60
+ sourceFilePaths = allFiles.sourceFiles;
61
+ otherFilePaths = allFiles.otherFiles;
62
+ }
63
+ else {
64
+ sourceFilePaths = isSourceFile(input) ? [input] : [];
65
+ otherFilePaths = isSourceFile(input) ? [] : [input];
66
+ }
67
+ if (sourceFilePaths.length === 0) {
68
+ return {
69
+ filesProcessed: 0,
70
+ totalTransformations: 0,
71
+ errors: ["No source files found in input path"],
72
+ };
73
+ }
74
+ logVerbose(verbose, `Found ${sourceFilePaths.length} source file(s)`);
75
+ if (otherFilePaths.length > 0) {
76
+ logVerbose(verbose, `Found ${otherFilePaths.length} other file(s) to copy`);
77
+ }
78
+ // Read all files
79
+ logVerbose(verbose, "Reading files...");
80
+ const parsedFiles = [];
81
+ for (const filePath of sourceFilePaths) {
82
+ const content = await fs.readFile(filePath, "utf8");
83
+ parsedFiles.push({ path: filePath, content });
84
+ }
85
+ // Build project graph (includes parsing)
86
+ logVerbose(verbose, "Parsing files with Babel...");
87
+ const projectGraph = buildProjectGraph(parsedFiles);
88
+ logVerbose(verbose, `Running ${transforms.length} transform(s) with ${workers} worker(s)...`);
89
+ // Create a queue for parallel processing
90
+ const queue = new PQueue({ concurrency: workers });
91
+ // Track results
92
+ let totalTransformations = 0;
93
+ // Run transforms sequentially - each mutates the AST in place
94
+ for (const transform of transforms) {
95
+ logVerbose(verbose, `Running transform: ${transform.id}`);
96
+ try {
97
+ const stats = await transform.transform({
98
+ projectGraph,
99
+ currentFile: undefined,
100
+ options: {},
101
+ });
102
+ totalTransformations += stats.transformationsApplied;
103
+ logVerbose(verbose, `Applied ${stats.transformationsApplied} transformation(s)`);
104
+ }
105
+ catch (error) {
106
+ const message = error instanceof Error ? error.message : String(error);
107
+ return {
108
+ filesProcessed: 0,
109
+ totalTransformations,
110
+ errors: [`Transform '${transform.id}' failed: ${message}`],
111
+ };
112
+ }
113
+ }
114
+ // Generate final code from ASTs using @babel/generator
115
+ const finalFiles = new Map();
116
+ for (const [filePath, fileInfo] of projectGraph.files) {
117
+ const generated = generateCode(fileInfo.ast);
118
+ finalFiles.set(filePath, generated);
119
+ }
120
+ // Output results
121
+ const inputBase = inputStat.isDirectory() ? input : path.dirname(input);
122
+ if (dryRun) {
123
+ printDryRunSummary(finalFiles, otherFilePaths, inputBase);
124
+ }
125
+ else {
126
+ try {
127
+ await writeOutputFiles(finalFiles, otherFilePaths, inputBase, output, queue, { overwrite });
128
+ }
129
+ catch (error) {
130
+ const message = error instanceof Error ? error.message : String(error);
131
+ return {
132
+ filesProcessed: 0,
133
+ totalTransformations,
134
+ errors: [message],
135
+ };
136
+ }
137
+ }
138
+ logVerbose(verbose, `Processed ${finalFiles.size + otherFilePaths.length} file(s)`);
139
+ logVerbose(verbose, `Total transformations applied: ${totalTransformations}`);
140
+ return {
141
+ filesProcessed: finalFiles.size + otherFilePaths.length,
142
+ totalTransformations,
143
+ errors: [],
144
+ };
145
+ };
@@ -0,0 +1,3 @@
1
+ export type { RunnerOptions, RunnerResult } from "./run-transforms.js";
2
+ export { runTransforms } from "./run-transforms.js";
3
+ export { transformStdin } from "./transform-stdin.js";
@@ -0,0 +1,2 @@
1
+ export { runTransforms } from "./run-transforms.js";
2
+ export { transformStdin } from "./transform-stdin.js";
@@ -0,0 +1,14 @@
1
+ import type { Transform } from "../core/types.js";
2
+ type StdinTransformOptions = {
3
+ transforms: Transform[];
4
+ verbose: boolean;
5
+ };
6
+ type StdinTransformResult = {
7
+ ok: true;
8
+ output: string;
9
+ } | {
10
+ ok: false;
11
+ error: string;
12
+ };
13
+ export declare const transformStdin: (options: StdinTransformOptions) => Promise<StdinTransformResult>;
14
+ export {};
@@ -0,0 +1,58 @@
1
+ import { buildProjectGraph } from "../core/project-graph.js";
2
+ import { generateCode } from "./generate-code.js";
3
+ const logVerbose = (verbose, message) => {
4
+ if (!verbose)
5
+ return;
6
+ console.error(message);
7
+ };
8
+ const readStdin = async () => {
9
+ return new Promise((resolve, reject) => {
10
+ let text = "";
11
+ process.stdin.setEncoding("utf8");
12
+ process.stdin.on("data", (chunk) => {
13
+ text += chunk;
14
+ });
15
+ process.stdin.on("end", () => {
16
+ resolve(text);
17
+ });
18
+ process.stdin.on("error", reject);
19
+ });
20
+ };
21
+ export const transformStdin = async (options) => {
22
+ const { transforms, verbose } = options;
23
+ const inputText = await readStdin();
24
+ const stdinPath = "stdin.tsx";
25
+ const parsedFiles = [{ path: stdinPath, content: inputText }];
26
+ const projectGraph = buildProjectGraph(parsedFiles);
27
+ const stdinFile = projectGraph.files.get(stdinPath);
28
+ if (!stdinFile) {
29
+ return { ok: false, error: "Failed to parse input" };
30
+ }
31
+ let totalTransformations = 0;
32
+ for (const transform of transforms) {
33
+ try {
34
+ const stats = await transform.transform({
35
+ projectGraph,
36
+ currentFile: undefined,
37
+ options: {},
38
+ });
39
+ totalTransformations += stats.transformationsApplied;
40
+ logVerbose(verbose, `Applied ${stats.transformationsApplied} transformation(s) from ${transform.id}`);
41
+ }
42
+ catch (error) {
43
+ const message = error instanceof Error ? error.message : String(error);
44
+ return {
45
+ ok: false,
46
+ error: `Transform '${transform.id}' failed: ${message}`,
47
+ };
48
+ }
49
+ }
50
+ const generated = generateCode(stdinFile.ast);
51
+ const output = generated === ""
52
+ ? ""
53
+ : generated.endsWith("\n")
54
+ ? generated
55
+ : `${generated}\n`;
56
+ logVerbose(verbose, `Total transformations applied: ${totalTransformations}`);
57
+ return { ok: true, output };
58
+ };
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};