miniread 1.94.0 → 1.95.1

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/README.md CHANGED
@@ -18,8 +18,11 @@ miniread --input ./minified --output ./readable --transforms recommended
18
18
  miniread --input ./minified --output ./readable --dry-run
19
19
  miniread --input ./minified --output ./readable --workers 8
20
20
  miniread --input ./minified --output ./readable --safe-stabilize-top-level-bindings
21
+ miniread --input ./repo --output ./readable --ignore-dirs none
21
22
  ```
22
23
 
24
+ When scanning an input directory, miniread ignores `.git`, `node_modules`, `coverage`, `.turbo`, and `.next` by default. `--ignore-dirs` replaces that default list (for example: `--ignore-dirs build,dist`), and `--ignore-dirs none` disables ignores entirely.
25
+
23
26
  ## Output stability
24
27
 
25
28
  The recommended preset includes `stabilize-top-level-bindings`, which renames program-scope bindings to stable hash-based names (`$h_<hash>`). This produces deterministic output across minifier versions, making diffs more useful.
@@ -14,6 +14,7 @@ export declare const createProgram: (packageMetadata: PackageMetadata) => Comman
14
14
  format: ListFormat;
15
15
  dryRun?: true | undefined;
16
16
  workers: number;
17
+ ignoreDirs?: ReadonlySet<string> | undefined;
17
18
  overwrite?: true | undefined;
18
19
  safeStabilizeTopLevelBindings?: true | undefined;
19
20
  }, {}>;
@@ -1,4 +1,5 @@
1
1
  import { Command, InvalidArgumentError } from "@commander-js/extra-typings";
2
+ import { parseIgnoredDirectories } from "./parse-ignored-directories.js";
2
3
  const parseListFormat = (value) => {
3
4
  if (value === "text" || value === "tsv" || value === "json")
4
5
  return value;
@@ -26,6 +27,7 @@ export const createProgram = (packageMetadata) => {
26
27
  .option("--format <format>", "Output format for --list-transforms: text, tsv, json", parseListFormat, "text")
27
28
  .option("--dry-run", "Show what would be changed without writing files")
28
29
  .option("-w, --workers <n>", "Number of parallel workers", parsePositiveInt, 4)
30
+ .option("--ignore-dirs <list>", "Comma-separated directory names to skip during --input directory scans. Replaces default ignores (.git,node_modules,coverage,.turbo,.next). Use 'none' to disable.", parseIgnoredDirectories)
29
31
  .option("-f, --overwrite", "Overwrite existing files in output directory")
30
32
  .option("--safe-stabilize-top-level-bindings", "Make stabilize-top-level-bindings bail out on dynamic-name hazards (safer; output more likely runnable)");
31
33
  program.addHelpText("after", `
@@ -9,5 +9,8 @@ type FindInputFilesError = {
9
9
  errors: string[];
10
10
  };
11
11
  type FindInputFilesResult = FindInputFilesOk | FindInputFilesError;
12
- export declare const findInputFiles: (input: string) => Promise<FindInputFilesResult>;
12
+ type FindInputFilesOptions = {
13
+ ignoredDirectories?: ReadonlySet<string>;
14
+ };
15
+ export declare const findInputFiles: (input: string, options?: FindInputFilesOptions) => Promise<FindInputFilesResult>;
13
16
  export {};
@@ -7,14 +7,34 @@ const isSourceFile = (fileName) => {
7
7
  fileName.endsWith(".js") ||
8
8
  fileName.endsWith(".jsx")));
9
9
  };
10
- const findAllFiles = async (directory) => {
10
+ const ignoredDirectoryNames = new Set([
11
+ ".git",
12
+ "node_modules",
13
+ "coverage",
14
+ ".turbo",
15
+ ".next",
16
+ ]);
17
+ const normalizeIgnoredDirectoryNames = (directoryNames) => {
18
+ const normalized = new Set();
19
+ for (const directoryName of directoryNames) {
20
+ const normalizedDirectoryName = directoryName.trim().toLowerCase();
21
+ if (normalizedDirectoryName === "")
22
+ continue;
23
+ normalized.add(normalizedDirectoryName);
24
+ }
25
+ return normalized;
26
+ };
27
+ const findAllFiles = async (directory, ignoredDirectories) => {
11
28
  const sourceFiles = [];
12
29
  const otherFiles = [];
13
30
  const entries = await fs.readdir(directory, { withFileTypes: true });
14
31
  for (const entry of entries) {
15
32
  const fullPath = path.join(directory, entry.name);
16
33
  if (entry.isDirectory()) {
17
- const subResult = await findAllFiles(fullPath);
34
+ // Directory matching should be case-insensitive across filesystems.
35
+ if (ignoredDirectories.has(entry.name.toLowerCase()))
36
+ continue;
37
+ const subResult = await findAllFiles(fullPath, ignoredDirectories);
18
38
  sourceFiles.push(...subResult.sourceFiles);
19
39
  otherFiles.push(...subResult.otherFiles);
20
40
  continue;
@@ -29,7 +49,7 @@ const findAllFiles = async (directory) => {
29
49
  }
30
50
  return { sourceFiles, otherFiles };
31
51
  };
32
- export const findInputFiles = async (input) => {
52
+ export const findInputFiles = async (input, options = {}) => {
33
53
  let inputStat;
34
54
  try {
35
55
  inputStat = await fs.stat(input);
@@ -40,9 +60,10 @@ export const findInputFiles = async (input) => {
40
60
  }
41
61
  let sourceFilePaths;
42
62
  let otherFilePaths;
63
+ const normalizedIgnoredDirectories = normalizeIgnoredDirectoryNames(options.ignoredDirectories ?? ignoredDirectoryNames);
43
64
  try {
44
65
  if (inputStat.isDirectory()) {
45
- const allFiles = await findAllFiles(input);
66
+ const allFiles = await findAllFiles(input, normalizedIgnoredDirectories);
46
67
  sourceFilePaths = allFiles.sourceFiles;
47
68
  otherFilePaths = allFiles.otherFiles;
48
69
  }
@@ -0,0 +1 @@
1
+ export declare const parseIgnoredDirectories: (value: string) => ReadonlySet<string>;
@@ -0,0 +1,18 @@
1
+ import { InvalidArgumentError } from "@commander-js/extra-typings";
2
+ export const parseIgnoredDirectories = (value) => {
3
+ const normalizedValue = value.trim();
4
+ if (normalizedValue.toLowerCase() === "none") {
5
+ return new Set();
6
+ }
7
+ if (normalizedValue === "") {
8
+ throw new InvalidArgumentError("must not be empty; use 'none' to disable ignored directories");
9
+ }
10
+ const directories = normalizedValue
11
+ .split(",")
12
+ .map((directoryName) => directoryName.trim())
13
+ .filter((directoryName) => directoryName !== "");
14
+ if (directories.length === 0) {
15
+ throw new InvalidArgumentError("must contain at least one directory name or 'none'");
16
+ }
17
+ return new Set(directories);
18
+ };
@@ -25,6 +25,7 @@ export const runInputMode = async (options) => {
25
25
  input: options.input,
26
26
  output: options.output,
27
27
  transforms: transformsResult.transforms,
28
+ ignoredDirectories: options.ignoreDirs,
28
29
  transformOptions: {
29
30
  ...getDefaultTransformOptions(),
30
31
  unsafeStabilizeTopLevelBindings: shouldUnsafeStabilize(options.safeStabilizeTopLevelBindings),
@@ -3,6 +3,7 @@ export type RunnerOptions = {
3
3
  input: string;
4
4
  output: string;
5
5
  transforms: Transform[];
6
+ ignoredDirectories?: ReadonlySet<string>;
6
7
  transformOptions?: Record<string, unknown>;
7
8
  dryRun: boolean;
8
9
  workers: number;
@@ -11,8 +11,8 @@ const logVerbose = (verbose, message) => {
11
11
  console.error(message);
12
12
  };
13
13
  export const runTransforms = async (options) => {
14
- const { input, output, transforms, transformOptions = {}, dryRun, workers, overwrite, verbose, } = options;
15
- const inputFilesResult = await findInputFiles(input);
14
+ const { input, output, transforms, ignoredDirectories, transformOptions = {}, dryRun, workers, overwrite, verbose, } = options;
15
+ const inputFilesResult = await findInputFiles(input, { ignoredDirectories });
16
16
  if (!inputFilesResult.ok) {
17
17
  return {
18
18
  filesProcessed: 0,
@@ -162,7 +162,7 @@ const manifestData = {
162
162
  },
163
163
  "rename-execfile-arguments": {
164
164
  recommended: true,
165
- evaluations: { "claude-code-2.1.10:claude-code-2.1.11": { "diffSizePercent": 100, "evaluatedAt": "2026-02-04T12:56:34.513Z", "changedLines": 4, "durationSeconds": 163.50355117599997, "stableNames": 1359 } },
165
+ evaluations: { "claude-code-2.1.10:claude-code-2.1.11": { "diffSizePercent": 100, "evaluatedAt": "2026-02-07T09:44:20.481Z", "changedLines": 4, "durationSeconds": 27.924432042, "stableNames": 1359 } },
166
166
  },
167
167
  "rename-file-extension-variables": {
168
168
  recommended: true,
@@ -1,5 +1,6 @@
1
1
  import { isIdentifier } from "@babel/types";
2
2
  const CHILD_PROCESS_MODULES = new Set(["child_process", "node:child_process"]);
3
+ const childProcessExecFileBindingCache = new WeakMap();
3
4
  export const getExecFileBindings = (pattern) => {
4
5
  if (pattern.elements.length !== 2)
5
6
  return undefined;
@@ -35,48 +36,57 @@ const isExecFileCallee = (call) => {
35
36
  return (callee.property.type === "Identifier" && callee.property.name === "execFile");
36
37
  };
37
38
  const isChildProcessExecFileBinding = (binding) => {
39
+ const cached = childProcessExecFileBindingCache.get(binding);
40
+ if (cached !== undefined)
41
+ return cached;
38
42
  const bindingPath = binding.path;
43
+ let isMatch = false;
39
44
  // ESM: import { execFile } from 'child_process'
40
45
  if (bindingPath.isImportSpecifier()) {
41
46
  const importDeclaration = bindingPath.parentPath;
42
- if (!importDeclaration.isImportDeclaration())
43
- return false;
44
- if (!CHILD_PROCESS_MODULES.has(importDeclaration.node.source.value))
45
- return false;
46
- const imported = bindingPath.node.imported;
47
- return imported.type === "Identifier" && imported.name === "execFile";
47
+ if (importDeclaration.isImportDeclaration() &&
48
+ CHILD_PROCESS_MODULES.has(importDeclaration.node.source.value)) {
49
+ const imported = bindingPath.node.imported;
50
+ isMatch = imported.type === "Identifier" && imported.name === "execFile";
51
+ }
52
+ childProcessExecFileBindingCache.set(binding, isMatch);
53
+ return isMatch;
48
54
  }
49
55
  // CJS: const { execFile } = require('child_process')
50
56
  // For CJS destructuring, Babel's binding path is the VariableDeclarator,
51
57
  // not an Identifier inside ObjectProperty.
52
58
  if (bindingPath.isVariableDeclarator()) {
53
59
  const declarator = bindingPath.node;
54
- if (declarator.id.type !== "ObjectPattern")
55
- return false;
56
- const init = declarator.init;
57
- if (init?.type !== "CallExpression")
58
- return false;
59
- if (init.callee.type !== "Identifier" || init.callee.name !== "require")
60
- return false;
61
- const source = init.arguments[0];
62
- if (source?.type !== "StringLiteral")
63
- return false;
64
- if (!CHILD_PROCESS_MODULES.has(source.value))
65
- return false;
66
- // Find the property that matches the binding identifier
67
- const bindingName = binding.identifier.name;
68
- for (const property of declarator.id.properties) {
69
- if (property.type !== "ObjectProperty")
70
- continue;
71
- if (property.value.type !== "Identifier")
72
- continue;
73
- if (property.value.name !== bindingName)
74
- continue;
75
- // Check the key is 'execFile' (handles aliases like { execFile: exec })
76
- return (property.key.type === "Identifier" && property.key.name === "execFile");
60
+ if (declarator.id.type === "ObjectPattern") {
61
+ const init = declarator.init;
62
+ if (init?.type === "CallExpression" &&
63
+ init.callee.type === "Identifier" &&
64
+ init.callee.name === "require") {
65
+ const source = init.arguments[0];
66
+ if (source?.type === "StringLiteral" &&
67
+ CHILD_PROCESS_MODULES.has(source.value)) {
68
+ // Find the property that matches the binding identifier
69
+ const bindingName = binding.identifier.name;
70
+ for (const property of declarator.id.properties) {
71
+ if (property.type !== "ObjectProperty")
72
+ continue;
73
+ if (property.value.type !== "Identifier")
74
+ continue;
75
+ if (property.value.name !== bindingName)
76
+ continue;
77
+ // Check the key is 'execFile' (handles aliases like { execFile: exec })
78
+ isMatch =
79
+ property.key.type === "Identifier" &&
80
+ property.key.name === "execFile";
81
+ break;
82
+ }
83
+ }
84
+ }
77
85
  }
78
- return false;
86
+ childProcessExecFileBindingCache.set(binding, isMatch);
87
+ return isMatch;
79
88
  }
89
+ childProcessExecFileBindingCache.set(binding, false);
80
90
  return false;
81
91
  };
82
92
  export const isExecFileCallArgument = (referencePath, commandBinding, argumentsBinding) => {
@@ -3,9 +3,9 @@
3
3
  "evaluations": {
4
4
  "claude-code-2.1.10:claude-code-2.1.11": {
5
5
  "diffSizePercent": 100,
6
- "evaluatedAt": "2026-02-04T12:56:34.513Z",
6
+ "evaluatedAt": "2026-02-07T09:44:20.481Z",
7
7
  "changedLines": 4,
8
- "durationSeconds": 163.50355117599997,
8
+ "durationSeconds": 27.924432042,
9
9
  "stableNames": 1359
10
10
  }
11
11
  }
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.94.0",
5
+ "version": "1.95.1",
6
6
  "description": "Transform minified JavaScript/TypeScript into a more readable form using deterministic AST-based transforms.",
7
7
  "repository": {
8
8
  "type": "git",