miniread 1.7.0 → 1.8.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/bin/miniread-snapshot +17 -0
- package/dist/scripts/snapshot/create-snapshot-command.d.ts +9 -0
- package/dist/scripts/snapshot/create-snapshot-command.js +32 -0
- package/dist/scripts/snapshot/run-snapshot-cli.d.ts +1 -0
- package/dist/scripts/snapshot/run-snapshot-cli.js +162 -0
- package/dist/scripts/snapshot.d.ts +2 -0
- package/dist/scripts/snapshot.js +13 -0
- package/package.json +4 -2
|
@@ -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/snapshot.js");
|
|
11
|
+
} catch (error) {
|
|
12
|
+
console.error(
|
|
13
|
+
"Failed to start miniread-snapshot:",
|
|
14
|
+
error instanceof Error ? error.message : error,
|
|
15
|
+
);
|
|
16
|
+
process.exitCode = 1;
|
|
17
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Command } from "@commander-js/extra-typings";
|
|
2
|
+
export const createSnapshotCommand = (options) => {
|
|
3
|
+
const program = new Command()
|
|
4
|
+
.name("miniread-snapshot")
|
|
5
|
+
.description("Generate test case snapshots for transform development workflow.")
|
|
6
|
+
.version(options.version)
|
|
7
|
+
.showHelpAfterError("(add --help for additional information)")
|
|
8
|
+
.showSuggestionAfterError()
|
|
9
|
+
.requiredOption("--testcase <name>", "Test case directory name (e.g., boolean-literals)")
|
|
10
|
+
.requiredOption("--transform <id>", "Transform ID to run (e.g., expand-boolean-literals)")
|
|
11
|
+
.option("--expected", "Write {transform}-expected.js, compare with actual, delete expected on success", false);
|
|
12
|
+
program.addHelpText("after", `
|
|
13
|
+
Examples:
|
|
14
|
+
# Generate actual snapshot only (for updating existing snapshots)
|
|
15
|
+
pnpm run snapshot -- --testcase boolean-literals --transform expand-boolean-literals
|
|
16
|
+
|
|
17
|
+
# Full expected-file workflow (Design → Test phase)
|
|
18
|
+
pnpm run snapshot -- --testcase boolean-literals --transform expand-boolean-literals --expected
|
|
19
|
+
1. Writes {transform}-expected.js (copy of base.js for manual editing)
|
|
20
|
+
2. Writes {transform}.js (actual transform output)
|
|
21
|
+
3. Runs diff between expected and actual
|
|
22
|
+
4. Deletes expected.js on success (no diff)
|
|
23
|
+
|
|
24
|
+
Workflow:
|
|
25
|
+
1. Create test-cases/{testcase}/base.js with your minified snippet
|
|
26
|
+
2. Run with --expected to generate the expected file template
|
|
27
|
+
3. Manually edit {transform}-expected.js to match your intent
|
|
28
|
+
4. Re-run with --expected to compare actual output
|
|
29
|
+
5. On success (no diff), expected file is auto-deleted
|
|
30
|
+
`);
|
|
31
|
+
return program;
|
|
32
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const runSnapshotCli: (argv: string[]) => Promise<number>;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import * as prettier from "prettier";
|
|
5
|
+
import packageJson from "../../../package.json" with { type: "json" };
|
|
6
|
+
import { buildProjectGraph } from "../../core/project-graph.js";
|
|
7
|
+
import { transformPresets } from "../../transforms/transform-presets.js";
|
|
8
|
+
import { transformRegistry } from "../../transforms/transform-registry.js";
|
|
9
|
+
import { createSnapshotCommand, } from "./create-snapshot-command.js";
|
|
10
|
+
const require = createRequire(import.meta.url);
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
12
|
+
const generate = require("@babel/generator").default;
|
|
13
|
+
const TEST_CASES_DIR = path.resolve(import.meta.dirname, "../../../test-cases");
|
|
14
|
+
const formatCode = async (code) => prettier.format(code, { parser: "babel" });
|
|
15
|
+
const runTransform = async (inputPath, transformId) => {
|
|
16
|
+
const content = await fs.readFile(inputPath, "utf8");
|
|
17
|
+
// Handle "recommended" as a special case that runs all recommended transforms
|
|
18
|
+
const transformIds = transformId === "recommended"
|
|
19
|
+
? transformPresets.recommended
|
|
20
|
+
: [transformId];
|
|
21
|
+
const transforms = transformIds.map((id) => {
|
|
22
|
+
const transform = transformRegistry[id];
|
|
23
|
+
if (!transform) {
|
|
24
|
+
throw new Error(`Unknown transform: ${id}. Run 'miniread --list-transforms' to see available transforms.`);
|
|
25
|
+
}
|
|
26
|
+
return transform;
|
|
27
|
+
});
|
|
28
|
+
const projectGraph = buildProjectGraph([{ path: inputPath, content }]);
|
|
29
|
+
for (const transform of transforms) {
|
|
30
|
+
await transform.transform({
|
|
31
|
+
projectGraph,
|
|
32
|
+
currentFile: undefined,
|
|
33
|
+
options: {},
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
const fileInfo = projectGraph.files.get(inputPath);
|
|
37
|
+
if (!fileInfo) {
|
|
38
|
+
return content;
|
|
39
|
+
}
|
|
40
|
+
return generate(fileInfo.ast).code;
|
|
41
|
+
};
|
|
42
|
+
export const runSnapshotCli = async (argv) => {
|
|
43
|
+
const program = createSnapshotCommand({ version: packageJson.version });
|
|
44
|
+
program.parse(argv.filter((argument) => argument !== "--"));
|
|
45
|
+
const rawOptions = program.opts();
|
|
46
|
+
const { testcase, transform, expected } = rawOptions;
|
|
47
|
+
// Validate test case directory exists
|
|
48
|
+
const testCaseDirectory = path.join(TEST_CASES_DIR, testcase);
|
|
49
|
+
try {
|
|
50
|
+
const stats = await fs.stat(testCaseDirectory);
|
|
51
|
+
if (!stats.isDirectory()) {
|
|
52
|
+
console.error(`Error: ${testcase} is not a directory`);
|
|
53
|
+
return 1;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
console.error(`Error: Test case directory not found: test-cases/${testcase}`);
|
|
58
|
+
console.error(`Create it first: mkdir -p test-cases/${testcase}`);
|
|
59
|
+
return 1;
|
|
60
|
+
}
|
|
61
|
+
// Validate base.js exists
|
|
62
|
+
const basePath = path.join(testCaseDirectory, "base.js");
|
|
63
|
+
try {
|
|
64
|
+
await fs.access(basePath);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
console.error(`Error: base.js not found in test-cases/${testcase}/`);
|
|
68
|
+
console.error(`Create test-cases/${testcase}/base.js with your minified snippet first.`);
|
|
69
|
+
return 1;
|
|
70
|
+
}
|
|
71
|
+
const expectedPath = path.join(testCaseDirectory, `${transform}-expected.js`);
|
|
72
|
+
const actualPath = path.join(testCaseDirectory, `${transform}.js`);
|
|
73
|
+
// Run the transform and format
|
|
74
|
+
let actualOutput;
|
|
75
|
+
try {
|
|
76
|
+
const rawOutput = await runTransform(basePath, transform);
|
|
77
|
+
actualOutput = await formatCode(rawOutput);
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
81
|
+
console.error(`Error running transform: ${message}`);
|
|
82
|
+
return 1;
|
|
83
|
+
}
|
|
84
|
+
if (expected) {
|
|
85
|
+
// Expected-file workflow
|
|
86
|
+
// Step 1: Check if expected file exists, create from base.js if not
|
|
87
|
+
let expectedExists = false;
|
|
88
|
+
try {
|
|
89
|
+
await fs.access(expectedPath);
|
|
90
|
+
expectedExists = true;
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// Expected file doesn't exist, create it from base.js
|
|
94
|
+
const baseContent = await fs.readFile(basePath, "utf8");
|
|
95
|
+
const formattedBase = await formatCode(baseContent);
|
|
96
|
+
await fs.writeFile(expectedPath, formattedBase);
|
|
97
|
+
console.log(`Created: test-cases/${testcase}/${transform}-expected.js`);
|
|
98
|
+
console.log("Edit this file to match your expected output, then re-run.");
|
|
99
|
+
}
|
|
100
|
+
// Step 2: Write actual output
|
|
101
|
+
await fs.writeFile(actualPath, actualOutput);
|
|
102
|
+
console.log(`Created: test-cases/${testcase}/${transform}.js`);
|
|
103
|
+
if (!expectedExists) {
|
|
104
|
+
// First run - just created expected file, user needs to edit it
|
|
105
|
+
return 0;
|
|
106
|
+
}
|
|
107
|
+
// Step 3: Compare expected vs actual
|
|
108
|
+
const expectedContent = await fs.readFile(expectedPath, "utf8");
|
|
109
|
+
if (expectedContent === actualOutput) {
|
|
110
|
+
// Success! Delete expected file
|
|
111
|
+
await fs.unlink(expectedPath);
|
|
112
|
+
console.log(`\nSuccess: Output matches expected.`);
|
|
113
|
+
console.log(`Deleted: test-cases/${testcase}/${transform}-expected.js`);
|
|
114
|
+
console.log(`\nSnapshot ready: test-cases/${testcase}/${transform}.js`);
|
|
115
|
+
return 0;
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
// Diff - show the difference
|
|
119
|
+
console.log(`\nDiff: expected vs actual`);
|
|
120
|
+
console.log("---");
|
|
121
|
+
// Simple line-by-line diff output
|
|
122
|
+
const expectedLines = expectedContent.split("\n");
|
|
123
|
+
const actualLines = actualOutput.split("\n");
|
|
124
|
+
const maxLines = Math.max(expectedLines.length, actualLines.length);
|
|
125
|
+
let hasDiff = false;
|
|
126
|
+
for (let lineIndex = 0; lineIndex < maxLines; lineIndex++) {
|
|
127
|
+
const exp = expectedLines[lineIndex];
|
|
128
|
+
const act = actualLines[lineIndex];
|
|
129
|
+
if (exp !== act) {
|
|
130
|
+
hasDiff = true;
|
|
131
|
+
if (exp !== undefined && act === undefined) {
|
|
132
|
+
console.log(`Line ${lineIndex + 1}:`);
|
|
133
|
+
console.log(` - ${exp}`);
|
|
134
|
+
}
|
|
135
|
+
else if (exp === undefined && act !== undefined) {
|
|
136
|
+
console.log(`Line ${lineIndex + 1}:`);
|
|
137
|
+
console.log(` + ${act}`);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
console.log(`Line ${lineIndex + 1}:`);
|
|
141
|
+
console.log(` - ${exp}`);
|
|
142
|
+
console.log(` + ${act}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (!hasDiff) {
|
|
147
|
+
// Shouldn't happen since we already checked equality, but just in case
|
|
148
|
+
console.log("(no visible differences)");
|
|
149
|
+
}
|
|
150
|
+
console.log("---");
|
|
151
|
+
console.log(`\nExpected file preserved: test-cases/${testcase}/${transform}-expected.js`);
|
|
152
|
+
console.log("Fix the implementation or update the expected file, then re-run.");
|
|
153
|
+
return 1;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
// Simple mode: just write actual output
|
|
158
|
+
await fs.writeFile(actualPath, actualOutput);
|
|
159
|
+
console.log(`Created: test-cases/${testcase}/${transform}.js`);
|
|
160
|
+
return 0;
|
|
161
|
+
}
|
|
162
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { runSnapshotCli } from "./snapshot/run-snapshot-cli.js";
|
|
3
|
+
try {
|
|
4
|
+
const exitCode = await runSnapshotCli(process.argv);
|
|
5
|
+
if (exitCode !== 0) {
|
|
6
|
+
process.exitCode = exitCode;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
catch (error) {
|
|
10
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
11
|
+
console.error(`Error: ${message}`);
|
|
12
|
+
process.exitCode = 1;
|
|
13
|
+
}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "miniread",
|
|
3
3
|
"author": "Łukasz Jerciński",
|
|
4
4
|
"license": "MIT",
|
|
5
|
-
"version": "1.
|
|
5
|
+
"version": "1.8.0",
|
|
6
6
|
"description": "Transform minified JavaScript/TypeScript into a more readable form using deterministic AST-based transforms.",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
"bin": {
|
|
25
25
|
"miniread": "bin/miniread",
|
|
26
26
|
"miniread-evaluate": "bin/miniread-evaluate",
|
|
27
|
-
"miniread-sample": "bin/miniread-sample"
|
|
27
|
+
"miniread-sample": "bin/miniread-sample",
|
|
28
|
+
"miniread-snapshot": "bin/miniread-snapshot"
|
|
28
29
|
},
|
|
29
30
|
"files": [
|
|
30
31
|
"bin/",
|
|
@@ -47,6 +48,7 @@
|
|
|
47
48
|
"miniread": "pnpm -s run rebuild && node bin/miniread",
|
|
48
49
|
"miniread-evaluate": "pnpm -s run rebuild && node bin/miniread-evaluate",
|
|
49
50
|
"miniread-sample": "pnpm -s run rebuild && node bin/miniread-sample",
|
|
51
|
+
"snapshot": "pnpm -s run rebuild && node bin/miniread-snapshot",
|
|
50
52
|
"rebuild": "pnpm run clean && pnpm run build",
|
|
51
53
|
"test": "vitest run",
|
|
52
54
|
"test:coverage": "vitest run --coverage",
|