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.
- package/LICENSE +21 -0
- package/README.md +86 -0
- package/bin/miniread +17 -0
- package/bin/miniread-evaluate +17 -0
- package/bin/miniread-sample +17 -0
- package/dist/cli/config.d.ts +17 -0
- package/dist/cli/config.js +85 -0
- package/dist/cli/generate-code.d.ts +2 -0
- package/dist/cli/generate-code.js +7 -0
- package/dist/cli/output.d.ts +7 -0
- package/dist/cli/output.js +119 -0
- package/dist/cli/run-transforms.d.ts +16 -0
- package/dist/cli/run-transforms.js +145 -0
- package/dist/cli/runner.d.ts +3 -0
- package/dist/cli/runner.js +2 -0
- package/dist/cli/transform-stdin.d.ts +14 -0
- package/dist/cli/transform-stdin.js +58 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +121 -0
- package/dist/core/paths.d.ts +8 -0
- package/dist/core/paths.js +9 -0
- package/dist/core/project-graph.d.ts +6 -0
- package/dist/core/project-graph.js +40 -0
- package/dist/core/types.d.ts +30 -0
- package/dist/core/types.js +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +7 -0
- package/dist/scripts/evaluate/create-evaluate-command.d.ts +5 -0
- package/dist/scripts/evaluate/create-evaluate-command.js +47 -0
- package/dist/scripts/evaluate/diff-utilities.d.ts +17 -0
- package/dist/scripts/evaluate/diff-utilities.js +114 -0
- package/dist/scripts/evaluate/evaluation-report.d.ts +22 -0
- package/dist/scripts/evaluate/evaluation-report.js +1 -0
- package/dist/scripts/evaluate/evaluation-types.d.ts +16 -0
- package/dist/scripts/evaluate/evaluation-types.js +1 -0
- package/dist/scripts/evaluate/metrics.d.ts +3 -0
- package/dist/scripts/evaluate/metrics.js +11 -0
- package/dist/scripts/evaluate/pair-evaluator.d.ts +21 -0
- package/dist/scripts/evaluate/pair-evaluator.js +104 -0
- package/dist/scripts/evaluate/parse-evaluate-cli-options.d.ts +40 -0
- package/dist/scripts/evaluate/parse-evaluate-cli-options.js +97 -0
- package/dist/scripts/evaluate/parse-transform-manifest.d.ts +22 -0
- package/dist/scripts/evaluate/parse-transform-manifest.js +29 -0
- package/dist/scripts/evaluate/resolve-evaluate-dependencies.d.ts +9 -0
- package/dist/scripts/evaluate/resolve-evaluate-dependencies.js +66 -0
- package/dist/scripts/evaluate/run-evaluate-cli.d.ts +1 -0
- package/dist/scripts/evaluate/run-evaluate-cli.js +112 -0
- package/dist/scripts/evaluate/run-evaluations.d.ts +25 -0
- package/dist/scripts/evaluate/run-evaluations.js +50 -0
- package/dist/scripts/evaluate/run-pair-transformations.d.ts +21 -0
- package/dist/scripts/evaluate/run-pair-transformations.js +57 -0
- package/dist/scripts/evaluate/sanitize.d.ts +2 -0
- package/dist/scripts/evaluate/sanitize.js +6 -0
- package/dist/scripts/evaluate/shell-utilities.d.ts +15 -0
- package/dist/scripts/evaluate/shell-utilities.js +68 -0
- package/dist/scripts/evaluate/transform-id-set.d.ts +3 -0
- package/dist/scripts/evaluate/transform-id-set.js +29 -0
- package/dist/scripts/evaluate/transform-manifest.d.ts +13 -0
- package/dist/scripts/evaluate/transform-manifest.js +102 -0
- package/dist/scripts/evaluate/write-evaluation-stdout.d.ts +7 -0
- package/dist/scripts/evaluate/write-evaluation-stdout.js +20 -0
- package/dist/scripts/evaluate/write-text-file-atomic.d.ts +5 -0
- package/dist/scripts/evaluate/write-text-file-atomic.js +47 -0
- package/dist/scripts/evaluate.d.ts +2 -0
- package/dist/scripts/evaluate.js +13 -0
- package/dist/scripts/sample/choose-line-window.d.ts +11 -0
- package/dist/scripts/sample/choose-line-window.js +19 -0
- package/dist/scripts/sample/clip-text-around-core.d.ts +12 -0
- package/dist/scripts/sample/clip-text-around-core.js +61 -0
- package/dist/scripts/sample/collect-function-candidates.d.ts +20 -0
- package/dist/scripts/sample/collect-function-candidates.js +82 -0
- package/dist/scripts/sample/create-function-excerpt.d.ts +20 -0
- package/dist/scripts/sample/create-function-excerpt.js +37 -0
- package/dist/scripts/sample/create-sample-command.d.ts +17 -0
- package/dist/scripts/sample/create-sample-command.js +50 -0
- package/dist/scripts/sample/extract-function-samples.d.ts +31 -0
- package/dist/scripts/sample/extract-function-samples.js +105 -0
- package/dist/scripts/sample/find-source-files.d.ts +1 -0
- package/dist/scripts/sample/find-source-files.js +49 -0
- package/dist/scripts/sample/format-sample-output.d.ts +22 -0
- package/dist/scripts/sample/format-sample-output.js +29 -0
- package/dist/scripts/sample/line-offsets.d.ts +4 -0
- package/dist/scripts/sample/line-offsets.js +40 -0
- package/dist/scripts/sample/parse-sample-cli-options.d.ts +18 -0
- package/dist/scripts/sample/parse-sample-cli-options.js +36 -0
- package/dist/scripts/sample/random.d.ts +10 -0
- package/dist/scripts/sample/random.js +60 -0
- package/dist/scripts/sample/run-sample-cli.d.ts +1 -0
- package/dist/scripts/sample/run-sample-cli.js +79 -0
- package/dist/scripts/sample.d.ts +2 -0
- package/dist/scripts/sample.js +13 -0
- package/dist/transforms/add-prefix/add-prefix-transform.d.ts +2 -0
- package/dist/transforms/add-prefix/add-prefix-transform.js +40 -0
- package/dist/transforms/add-suffix/add-suffix-transform.d.ts +2 -0
- package/dist/transforms/add-suffix/add-suffix-transform.js +40 -0
- package/dist/transforms/expand-boolean-literals/expand-boolean-literals-transform.d.ts +2 -0
- package/dist/transforms/expand-boolean-literals/expand-boolean-literals-transform.js +39 -0
- package/dist/transforms/expand-undefined-literals/expand-undefined-literals-transform.d.ts +2 -0
- package/dist/transforms/expand-undefined-literals/expand-undefined-literals-transform.js +40 -0
- package/dist/transforms/rename-catch-parameters/rename-catch-parameters-transform.d.ts +2 -0
- package/dist/transforms/rename-catch-parameters/rename-catch-parameters-transform.js +67 -0
- package/dist/transforms/rename-loop-index-variables/rename-loop-index-variables-transform.d.ts +2 -0
- package/dist/transforms/rename-loop-index-variables/rename-loop-index-variables-transform.js +125 -0
- package/dist/transforms/transform-presets.d.ts +3 -0
- package/dist/transforms/transform-presets.js +19 -0
- package/dist/transforms/transform-registry.d.ts +3 -0
- package/dist/transforms/transform-registry.js +15 -0
- package/package.json +84 -0
- package/transform-manifest.json +70 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { transformPresets } from "../../transforms/transform-presets.js";
|
|
3
|
+
import { allTransformIds } from "../../transforms/transform-registry.js";
|
|
4
|
+
import { createTransformSlug } from "./diff-utilities.js";
|
|
5
|
+
import { sanitizeFileComponent } from "./sanitize.js";
|
|
6
|
+
const parseTransformList = (value) => {
|
|
7
|
+
if (!value || value === "none")
|
|
8
|
+
return [];
|
|
9
|
+
const ids = value
|
|
10
|
+
.split(",")
|
|
11
|
+
.map((id) => id.trim())
|
|
12
|
+
.filter(Boolean)
|
|
13
|
+
.filter((id) => id !== "none");
|
|
14
|
+
const expandedIds = [];
|
|
15
|
+
for (const id of ids) {
|
|
16
|
+
if (id === "recommended" || id === "default") {
|
|
17
|
+
expandedIds.push(...transformPresets.recommended);
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
if (id === "all") {
|
|
21
|
+
expandedIds.push(...allTransformIds);
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
expandedIds.push(id);
|
|
25
|
+
}
|
|
26
|
+
const uniqueIds = [];
|
|
27
|
+
const seen = new Set();
|
|
28
|
+
for (const id of expandedIds) {
|
|
29
|
+
if (seen.has(id))
|
|
30
|
+
continue;
|
|
31
|
+
seen.add(id);
|
|
32
|
+
uniqueIds.push(id);
|
|
33
|
+
}
|
|
34
|
+
return uniqueIds;
|
|
35
|
+
};
|
|
36
|
+
const containsPathSeparator = (value) => {
|
|
37
|
+
return value.includes("/") || value.includes("\\");
|
|
38
|
+
};
|
|
39
|
+
const parseComparePair = (value) => {
|
|
40
|
+
const [fromRaw, toRaw] = value.split(":");
|
|
41
|
+
const from = fromRaw?.trim();
|
|
42
|
+
const to = toRaw?.trim();
|
|
43
|
+
if (!from || !to) {
|
|
44
|
+
throw new Error(`invalid compare format '${value}' (expected: from:to)`);
|
|
45
|
+
}
|
|
46
|
+
if (from === "." || from === ".." || containsPathSeparator(from)) {
|
|
47
|
+
throw new Error(`invalid 'from' value '${from}' (expected a directory name under --sources)`);
|
|
48
|
+
}
|
|
49
|
+
if (to === "." || to === ".." || containsPathSeparator(to)) {
|
|
50
|
+
throw new Error(`invalid 'to' value '${to}' (expected a directory name under --sources)`);
|
|
51
|
+
}
|
|
52
|
+
return { from, to };
|
|
53
|
+
};
|
|
54
|
+
export const parseEvaluateCliOptions = (options) => {
|
|
55
|
+
const { cwd, rawOptions } = options;
|
|
56
|
+
if (rawOptions.compare.length === 0) {
|
|
57
|
+
throw new Error("at least one --compare flag is required");
|
|
58
|
+
}
|
|
59
|
+
const pairs = [];
|
|
60
|
+
for (const value of rawOptions.compare) {
|
|
61
|
+
pairs.push(parseComparePair(value));
|
|
62
|
+
}
|
|
63
|
+
const baselineTransforms = parseTransformList(rawOptions.baseline);
|
|
64
|
+
const testTransforms = parseTransformList(rawOptions.test);
|
|
65
|
+
const verbose = rawOptions.verbose ?? false;
|
|
66
|
+
const overwrite = rawOptions.overwrite ?? false;
|
|
67
|
+
const formatCode = rawOptions.formatCode;
|
|
68
|
+
const baselineSlug = sanitizeFileComponent(createTransformSlug(baselineTransforms));
|
|
69
|
+
const testSlug = sanitizeFileComponent(createTransformSlug(testTransforms));
|
|
70
|
+
const sourcesDirectory = path.resolve(cwd, rawOptions.sources);
|
|
71
|
+
const evaluationDirectory = path.resolve(cwd, rawOptions.evaluationDir);
|
|
72
|
+
const manifestPath = path.resolve(cwd, rawOptions.manifestFile);
|
|
73
|
+
const shouldWriteMetrics = Boolean(rawOptions.writeMetrics || rawOptions.metricsFile);
|
|
74
|
+
const shouldWritePatches = Boolean(rawOptions.writePatches);
|
|
75
|
+
const shouldUpdateManifest = Boolean(rawOptions.updateManifest);
|
|
76
|
+
const metricsPath = rawOptions.metricsFile
|
|
77
|
+
? path.resolve(cwd, rawOptions.metricsFile)
|
|
78
|
+
: path.join(evaluationDirectory, `metrics-${baselineSlug}-vs-${testSlug}.json`);
|
|
79
|
+
return {
|
|
80
|
+
verbose,
|
|
81
|
+
pairs,
|
|
82
|
+
baselineTransforms,
|
|
83
|
+
testTransforms,
|
|
84
|
+
sourcesDirectory,
|
|
85
|
+
evaluationDirectory,
|
|
86
|
+
format: rawOptions.format,
|
|
87
|
+
shouldWriteMetrics,
|
|
88
|
+
shouldWritePatches,
|
|
89
|
+
shouldUpdateManifest,
|
|
90
|
+
metricsPath,
|
|
91
|
+
manifestPath,
|
|
92
|
+
overwrite,
|
|
93
|
+
formatCode,
|
|
94
|
+
baselineSlug,
|
|
95
|
+
testSlug,
|
|
96
|
+
};
|
|
97
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Transform } from "../../core/types.js";
|
|
2
|
+
type TransformManifestEntry = {
|
|
3
|
+
id: string;
|
|
4
|
+
description: string;
|
|
5
|
+
iteration: number;
|
|
6
|
+
scope: Transform["scope"];
|
|
7
|
+
parallelizable: boolean;
|
|
8
|
+
diffReductionImpact: number;
|
|
9
|
+
enabledByDefault: boolean;
|
|
10
|
+
notes?: string;
|
|
11
|
+
supersededBy?: string;
|
|
12
|
+
};
|
|
13
|
+
type PresetStatsEntry = {
|
|
14
|
+
diffReductionImpact: number;
|
|
15
|
+
notes?: string;
|
|
16
|
+
};
|
|
17
|
+
type TransformManifest = {
|
|
18
|
+
transforms: TransformManifestEntry[];
|
|
19
|
+
presetStats?: Record<string, PresetStatsEntry>;
|
|
20
|
+
};
|
|
21
|
+
export declare const parseTransformManifest: (raw: string) => TransformManifest;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
const TransformManifestEntrySchema = z
|
|
3
|
+
.object({
|
|
4
|
+
id: z.string().min(1),
|
|
5
|
+
description: z.string(),
|
|
6
|
+
iteration: z.number().int().nonnegative(),
|
|
7
|
+
scope: z.union([z.literal("file"), z.literal("project")]),
|
|
8
|
+
parallelizable: z.boolean(),
|
|
9
|
+
diffReductionImpact: z.number(),
|
|
10
|
+
enabledByDefault: z.boolean(),
|
|
11
|
+
notes: z.string().optional(),
|
|
12
|
+
supersededBy: z.string().min(1).optional(),
|
|
13
|
+
})
|
|
14
|
+
.loose();
|
|
15
|
+
const PresetStatsEntrySchema = z
|
|
16
|
+
.object({
|
|
17
|
+
diffReductionImpact: z.number(),
|
|
18
|
+
notes: z.string().optional(),
|
|
19
|
+
})
|
|
20
|
+
.loose();
|
|
21
|
+
const TransformManifestSchema = z
|
|
22
|
+
.object({
|
|
23
|
+
transforms: z.array(TransformManifestEntrySchema),
|
|
24
|
+
presetStats: z.record(z.string(), PresetStatsEntrySchema).optional(),
|
|
25
|
+
})
|
|
26
|
+
.loose();
|
|
27
|
+
export const parseTransformManifest = (raw) => {
|
|
28
|
+
return TransformManifestSchema.parse(JSON.parse(raw));
|
|
29
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
const checkDependency = (options) => {
|
|
3
|
+
const { name, command, args, envVar, installHint, cwd } = options;
|
|
4
|
+
const result = spawnSync(command, args, { cwd, stdio: "ignore" });
|
|
5
|
+
if (!result.error && result.status === 0)
|
|
6
|
+
return;
|
|
7
|
+
const errorMessage = result.error instanceof Error
|
|
8
|
+
? result.error.message
|
|
9
|
+
: `exit code ${result.status ?? "unknown"}`;
|
|
10
|
+
throw new Error([
|
|
11
|
+
`Error: Required dependency '${name}' not available.`,
|
|
12
|
+
`Looked for: ${command}`,
|
|
13
|
+
`Reason: ${errorMessage}`,
|
|
14
|
+
"",
|
|
15
|
+
"To fix, either:",
|
|
16
|
+
` 1. Install it: ${installHint}`,
|
|
17
|
+
` 2. Set ${envVar}=/path/to/${name}`,
|
|
18
|
+
"",
|
|
19
|
+
"Try 'miniread-evaluate --help' for details.",
|
|
20
|
+
].join("\n"));
|
|
21
|
+
};
|
|
22
|
+
export const resolveEvaluateDependencies = (options) => {
|
|
23
|
+
const { cwd, formatCode } = options;
|
|
24
|
+
const diffPath = process.env.MINIREAD_EVALUATE_DIFF_PATH ?? "diff";
|
|
25
|
+
checkDependency({
|
|
26
|
+
name: "diff",
|
|
27
|
+
command: diffPath,
|
|
28
|
+
args: ["--version"],
|
|
29
|
+
envVar: "MINIREAD_EVALUATE_DIFF_PATH",
|
|
30
|
+
installHint: "install diffutils (provides 'diff')",
|
|
31
|
+
cwd,
|
|
32
|
+
});
|
|
33
|
+
if (!formatCode) {
|
|
34
|
+
return { diffPath, pnpmPath: undefined, prettierPath: undefined };
|
|
35
|
+
}
|
|
36
|
+
const prettierPath = process.env.MINIREAD_EVALUATE_PRETTIER_PATH;
|
|
37
|
+
if (prettierPath) {
|
|
38
|
+
checkDependency({
|
|
39
|
+
name: "prettier",
|
|
40
|
+
command: prettierPath,
|
|
41
|
+
args: ["--version"],
|
|
42
|
+
envVar: "MINIREAD_EVALUATE_PRETTIER_PATH",
|
|
43
|
+
installHint: "install Prettier (e.g. `pnpm install` in this repo)",
|
|
44
|
+
cwd,
|
|
45
|
+
});
|
|
46
|
+
return { diffPath, pnpmPath: undefined, prettierPath };
|
|
47
|
+
}
|
|
48
|
+
const pnpmPath = process.env.MINIREAD_EVALUATE_PNPM_PATH ?? "pnpm";
|
|
49
|
+
checkDependency({
|
|
50
|
+
name: "pnpm",
|
|
51
|
+
command: pnpmPath,
|
|
52
|
+
args: ["--version"],
|
|
53
|
+
envVar: "MINIREAD_EVALUATE_PNPM_PATH",
|
|
54
|
+
installHint: "install pnpm (https://pnpm.io/installation)",
|
|
55
|
+
cwd,
|
|
56
|
+
});
|
|
57
|
+
checkDependency({
|
|
58
|
+
name: "prettier",
|
|
59
|
+
command: pnpmPath,
|
|
60
|
+
args: ["exec", "prettier", "--version"],
|
|
61
|
+
envVar: "MINIREAD_EVALUATE_PNPM_PATH",
|
|
62
|
+
installHint: "run `pnpm install` (prettier is a devDependency in this repo)",
|
|
63
|
+
cwd,
|
|
64
|
+
});
|
|
65
|
+
return { diffPath, pnpmPath, prettierPath: undefined };
|
|
66
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const runEvaluateCli: (argv: string[]) => Promise<number>;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import packageJson from "../../../package.json" with { type: "json" };
|
|
3
|
+
import { transformRegistry } from "../../transforms/transform-registry.js";
|
|
4
|
+
import { createEvaluateCommand } from "./create-evaluate-command.js";
|
|
5
|
+
import { parseEvaluateCliOptions, } from "./parse-evaluate-cli-options.js";
|
|
6
|
+
import { resolveEvaluateDependencies } from "./resolve-evaluate-dependencies.js";
|
|
7
|
+
import { runEvaluations } from "./run-evaluations.js";
|
|
8
|
+
import { writeEvaluationStdout } from "./write-evaluation-stdout.js";
|
|
9
|
+
import { updateTransformManifestFromEvaluation } from "./transform-manifest.js";
|
|
10
|
+
import { writeJsonFileAtomic } from "./write-text-file-atomic.js";
|
|
11
|
+
export const runEvaluateCli = async (argv) => {
|
|
12
|
+
const program = createEvaluateCommand({ version: packageJson.version });
|
|
13
|
+
// Package managers often pass a standalone `--` to separate script args.
|
|
14
|
+
// Commander treats `--` as "end of options", which would cause flags like
|
|
15
|
+
// `--help` to be parsed as positional arguments. Strip it for robustness.
|
|
16
|
+
program.parse(argv.filter((argument) => argument !== "--"));
|
|
17
|
+
const rawOptions = program.opts();
|
|
18
|
+
let parsedOptions;
|
|
19
|
+
try {
|
|
20
|
+
parsedOptions = parseEvaluateCliOptions({
|
|
21
|
+
cwd: process.cwd(),
|
|
22
|
+
rawOptions,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
program.error(error instanceof Error ? error.message : String(error));
|
|
27
|
+
return 1;
|
|
28
|
+
}
|
|
29
|
+
let exitCode = 0;
|
|
30
|
+
const log = (message) => {
|
|
31
|
+
if (!parsedOptions.verbose)
|
|
32
|
+
return;
|
|
33
|
+
console.error(message);
|
|
34
|
+
};
|
|
35
|
+
let dependencies;
|
|
36
|
+
try {
|
|
37
|
+
dependencies = resolveEvaluateDependencies({
|
|
38
|
+
cwd: process.cwd(),
|
|
39
|
+
formatCode: parsedOptions.formatCode,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
44
|
+
console.error(message);
|
|
45
|
+
return 1;
|
|
46
|
+
}
|
|
47
|
+
if (parsedOptions.shouldWriteMetrics || parsedOptions.shouldWritePatches) {
|
|
48
|
+
await fs.mkdir(parsedOptions.evaluationDirectory, { recursive: true });
|
|
49
|
+
}
|
|
50
|
+
const { results, errors, outcomes } = await runEvaluations({
|
|
51
|
+
pairs: parsedOptions.pairs,
|
|
52
|
+
baselineTransforms: parsedOptions.baselineTransforms,
|
|
53
|
+
testTransforms: parsedOptions.testTransforms,
|
|
54
|
+
sourcesDirectory: parsedOptions.sourcesDirectory,
|
|
55
|
+
evaluationDirectory: parsedOptions.evaluationDirectory,
|
|
56
|
+
shouldWritePatches: parsedOptions.shouldWritePatches,
|
|
57
|
+
baselineSlug: parsedOptions.baselineSlug,
|
|
58
|
+
testSlug: parsedOptions.testSlug,
|
|
59
|
+
overwrite: parsedOptions.overwrite,
|
|
60
|
+
formatCode: parsedOptions.formatCode,
|
|
61
|
+
verbose: parsedOptions.verbose,
|
|
62
|
+
log,
|
|
63
|
+
dependencies,
|
|
64
|
+
rootDirectory: process.cwd(),
|
|
65
|
+
});
|
|
66
|
+
const report = {
|
|
67
|
+
version: packageJson.version,
|
|
68
|
+
baselineTransforms: parsedOptions.baselineTransforms,
|
|
69
|
+
testTransforms: parsedOptions.testTransforms,
|
|
70
|
+
pairs: parsedOptions.pairs,
|
|
71
|
+
results,
|
|
72
|
+
errors,
|
|
73
|
+
};
|
|
74
|
+
writeEvaluationStdout({
|
|
75
|
+
format: parsedOptions.format,
|
|
76
|
+
report,
|
|
77
|
+
outcomes,
|
|
78
|
+
});
|
|
79
|
+
if (parsedOptions.shouldWriteMetrics) {
|
|
80
|
+
try {
|
|
81
|
+
await writeJsonFileAtomic(parsedOptions.metricsPath, report, {
|
|
82
|
+
overwrite: parsedOptions.overwrite,
|
|
83
|
+
});
|
|
84
|
+
log(`Wrote ${parsedOptions.metricsPath}`);
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
88
|
+
console.error(`Error writing metrics: ${message}`);
|
|
89
|
+
exitCode = 1;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (parsedOptions.shouldUpdateManifest) {
|
|
93
|
+
if (errors.length > 0) {
|
|
94
|
+
console.error("Refusing to update transform-manifest.json because some evaluations failed.");
|
|
95
|
+
exitCode = 1;
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
const updateResult = await updateTransformManifestFromEvaluation({
|
|
99
|
+
manifestPath: parsedOptions.manifestPath,
|
|
100
|
+
baselineTransforms: parsedOptions.baselineTransforms,
|
|
101
|
+
testTransforms: parsedOptions.testTransforms,
|
|
102
|
+
reductionRatios: results.map((r) => r.reductionRatio),
|
|
103
|
+
transformRegistry,
|
|
104
|
+
});
|
|
105
|
+
console.error(updateResult.message);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (errors.length > 0) {
|
|
109
|
+
exitCode = 1;
|
|
110
|
+
}
|
|
111
|
+
return exitCode;
|
|
112
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ComparisonPair, EvaluationResult } from "./evaluation-types.js";
|
|
2
|
+
import type { EvaluationError, EvaluationOutcome } from "./evaluation-report.js";
|
|
3
|
+
import type { EvaluateDependencies } from "./resolve-evaluate-dependencies.js";
|
|
4
|
+
type RunEvaluationsResult = {
|
|
5
|
+
results: EvaluationResult[];
|
|
6
|
+
errors: EvaluationError[];
|
|
7
|
+
outcomes: EvaluationOutcome[];
|
|
8
|
+
};
|
|
9
|
+
export declare const runEvaluations: (options: {
|
|
10
|
+
pairs: ComparisonPair[];
|
|
11
|
+
baselineTransforms: string[];
|
|
12
|
+
testTransforms: string[];
|
|
13
|
+
sourcesDirectory: string;
|
|
14
|
+
evaluationDirectory: string;
|
|
15
|
+
shouldWritePatches: boolean;
|
|
16
|
+
baselineSlug: string;
|
|
17
|
+
testSlug: string;
|
|
18
|
+
overwrite: boolean;
|
|
19
|
+
formatCode: boolean;
|
|
20
|
+
verbose: boolean;
|
|
21
|
+
log: (message: string) => void;
|
|
22
|
+
dependencies: EvaluateDependencies;
|
|
23
|
+
rootDirectory: string;
|
|
24
|
+
}) => Promise<RunEvaluationsResult>;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { evaluatePair } from "./pair-evaluator.js";
|
|
3
|
+
import { sanitizeFileComponent } from "./sanitize.js";
|
|
4
|
+
export const runEvaluations = async (options) => {
|
|
5
|
+
const { pairs, baselineTransforms, testTransforms, sourcesDirectory, evaluationDirectory, shouldWritePatches, baselineSlug, testSlug, overwrite, formatCode, verbose, log, dependencies, rootDirectory, } = options;
|
|
6
|
+
const results = [];
|
|
7
|
+
const errors = [];
|
|
8
|
+
const outcomes = [];
|
|
9
|
+
for (const pair of pairs) {
|
|
10
|
+
const safeFrom = sanitizeFileComponent(pair.from);
|
|
11
|
+
const safeTo = sanitizeFileComponent(pair.to);
|
|
12
|
+
const filePrefix = `metrics-${baselineSlug}-vs-${testSlug}_${safeFrom}_${safeTo}`;
|
|
13
|
+
const baselinePatchPath = shouldWritePatches
|
|
14
|
+
? path.join(evaluationDirectory, `${filePrefix}_baseline.patch`)
|
|
15
|
+
: undefined;
|
|
16
|
+
const testPatchPath = shouldWritePatches
|
|
17
|
+
? path.join(evaluationDirectory, `${filePrefix}_test.patch`)
|
|
18
|
+
: undefined;
|
|
19
|
+
try {
|
|
20
|
+
const result = await evaluatePair({
|
|
21
|
+
pair,
|
|
22
|
+
baselineTransforms,
|
|
23
|
+
testTransforms,
|
|
24
|
+
sourcesDirectory,
|
|
25
|
+
rootDirectory,
|
|
26
|
+
nodePath: process.execPath,
|
|
27
|
+
minireadCliPath: process.env.MINIREAD_EVALUATE_MINIREAD_PATH ?? "bin/miniread",
|
|
28
|
+
diffPath: dependencies.diffPath,
|
|
29
|
+
pnpmPath: dependencies.pnpmPath,
|
|
30
|
+
prettierPath: dependencies.prettierPath,
|
|
31
|
+
formatCode,
|
|
32
|
+
baselinePatchPath,
|
|
33
|
+
testPatchPath,
|
|
34
|
+
overwrite,
|
|
35
|
+
verbose,
|
|
36
|
+
log,
|
|
37
|
+
});
|
|
38
|
+
results.push(result);
|
|
39
|
+
outcomes.push({ ok: true, pair, result });
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
43
|
+
const entry = { pair, error: message };
|
|
44
|
+
errors.push(entry);
|
|
45
|
+
outcomes.push({ ok: false, pair, error: message });
|
|
46
|
+
console.error(`Error evaluating ${pair.from} -> ${pair.to}: ${message}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return { results, errors, outcomes };
|
|
50
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ComparisonPair } from "./evaluation-types.js";
|
|
2
|
+
type PairTransformOutputs = {
|
|
3
|
+
sourceDirectoryFrom: string;
|
|
4
|
+
sourceDirectoryTo: string;
|
|
5
|
+
baselineOutputFrom: string;
|
|
6
|
+
baselineOutputTo: string;
|
|
7
|
+
testOutputFrom: string;
|
|
8
|
+
testOutputTo: string;
|
|
9
|
+
};
|
|
10
|
+
export declare const runPairTransformations: (options: {
|
|
11
|
+
pair: ComparisonPair;
|
|
12
|
+
baselineTransforms: string[];
|
|
13
|
+
testTransforms: string[];
|
|
14
|
+
sourcesDirectory: string;
|
|
15
|
+
temporaryDirectory: string;
|
|
16
|
+
rootDirectory: string;
|
|
17
|
+
nodePath: string;
|
|
18
|
+
minireadCliPath: string;
|
|
19
|
+
verbose: boolean;
|
|
20
|
+
}) => Promise<PairTransformOutputs>;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { runMiniread } from "./shell-utilities.js";
|
|
3
|
+
export const runPairTransformations = async (options) => {
|
|
4
|
+
const { pair, baselineTransforms, testTransforms, sourcesDirectory, temporaryDirectory, rootDirectory, nodePath, minireadCliPath, verbose, } = options;
|
|
5
|
+
const sourceDirectoryFrom = path.join(sourcesDirectory, pair.from);
|
|
6
|
+
const sourceDirectoryTo = path.join(sourcesDirectory, pair.to);
|
|
7
|
+
const baselineOutputFrom = path.join(temporaryDirectory, `baseline-${pair.from}`);
|
|
8
|
+
const baselineOutputTo = path.join(temporaryDirectory, `baseline-${pair.to}`);
|
|
9
|
+
const testOutputFrom = path.join(temporaryDirectory, `test-${pair.from}`);
|
|
10
|
+
const testOutputTo = path.join(temporaryDirectory, `test-${pair.to}`);
|
|
11
|
+
await Promise.all([
|
|
12
|
+
runMiniread({
|
|
13
|
+
inputDirectory: sourceDirectoryFrom,
|
|
14
|
+
outputDirectory: baselineOutputFrom,
|
|
15
|
+
transforms: baselineTransforms,
|
|
16
|
+
rootDirectory,
|
|
17
|
+
nodePath,
|
|
18
|
+
minireadCliPath,
|
|
19
|
+
verbose,
|
|
20
|
+
}),
|
|
21
|
+
runMiniread({
|
|
22
|
+
inputDirectory: sourceDirectoryTo,
|
|
23
|
+
outputDirectory: baselineOutputTo,
|
|
24
|
+
transforms: baselineTransforms,
|
|
25
|
+
rootDirectory,
|
|
26
|
+
nodePath,
|
|
27
|
+
minireadCliPath,
|
|
28
|
+
verbose,
|
|
29
|
+
}),
|
|
30
|
+
runMiniread({
|
|
31
|
+
inputDirectory: sourceDirectoryFrom,
|
|
32
|
+
outputDirectory: testOutputFrom,
|
|
33
|
+
transforms: testTransforms,
|
|
34
|
+
rootDirectory,
|
|
35
|
+
nodePath,
|
|
36
|
+
minireadCliPath,
|
|
37
|
+
verbose,
|
|
38
|
+
}),
|
|
39
|
+
runMiniread({
|
|
40
|
+
inputDirectory: sourceDirectoryTo,
|
|
41
|
+
outputDirectory: testOutputTo,
|
|
42
|
+
transforms: testTransforms,
|
|
43
|
+
rootDirectory,
|
|
44
|
+
nodePath,
|
|
45
|
+
minireadCliPath,
|
|
46
|
+
verbose,
|
|
47
|
+
}),
|
|
48
|
+
]);
|
|
49
|
+
return {
|
|
50
|
+
sourceDirectoryFrom,
|
|
51
|
+
sourceDirectoryTo,
|
|
52
|
+
baselineOutputFrom,
|
|
53
|
+
baselineOutputTo,
|
|
54
|
+
testOutputFrom,
|
|
55
|
+
testOutputTo,
|
|
56
|
+
};
|
|
57
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare const runMiniread: (options: {
|
|
2
|
+
inputDirectory: string;
|
|
3
|
+
outputDirectory: string;
|
|
4
|
+
transforms: string[];
|
|
5
|
+
rootDirectory: string;
|
|
6
|
+
nodePath: string;
|
|
7
|
+
minireadCliPath: string;
|
|
8
|
+
verbose: boolean;
|
|
9
|
+
}) => Promise<void>;
|
|
10
|
+
export declare const formatDirectoryWithPrettier: (options: {
|
|
11
|
+
directory: string;
|
|
12
|
+
rootDirectory: string;
|
|
13
|
+
pnpmPath: string | undefined;
|
|
14
|
+
prettierPath: string | undefined;
|
|
15
|
+
}) => Promise<void>;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
const runCommand = async (command, commandArguments, options) => {
|
|
4
|
+
const processResult = spawn(command, commandArguments, {
|
|
5
|
+
cwd: options.cwd,
|
|
6
|
+
stdio: ["ignore", "ignore", "pipe"],
|
|
7
|
+
});
|
|
8
|
+
const stderrChunks = [];
|
|
9
|
+
processResult.stderr.setEncoding("utf8");
|
|
10
|
+
processResult.stderr.on("data", (chunk) => {
|
|
11
|
+
if (stderrChunks.join("").length > 200_000)
|
|
12
|
+
return;
|
|
13
|
+
stderrChunks.push(chunk);
|
|
14
|
+
});
|
|
15
|
+
const exitCode = await new Promise((resolve, reject) => {
|
|
16
|
+
processResult.on("error", reject);
|
|
17
|
+
processResult.on("close", (code) => {
|
|
18
|
+
resolve(code);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
if (exitCode === 0)
|
|
22
|
+
return;
|
|
23
|
+
const stderr = stderrChunks.join("").trim();
|
|
24
|
+
const stderrSuffix = stderr ? `\n\nstderr:\n${stderr}` : "";
|
|
25
|
+
throw new Error(`Command failed (${options.label}, exit code ${exitCode}).${stderrSuffix}`);
|
|
26
|
+
};
|
|
27
|
+
export const runMiniread = async (options) => {
|
|
28
|
+
const { inputDirectory, outputDirectory, transforms, rootDirectory, nodePath, minireadCliPath, verbose, } = options;
|
|
29
|
+
const packageRootDirectory = path.resolve(import.meta.dirname, "../../..");
|
|
30
|
+
const resolvedMinireadPath = path.isAbsolute(minireadCliPath)
|
|
31
|
+
? minireadCliPath
|
|
32
|
+
: path.join(packageRootDirectory, minireadCliPath);
|
|
33
|
+
const transformsValue = transforms.length === 0 ? "none" : transforms.join(",");
|
|
34
|
+
const minireadArguments = [
|
|
35
|
+
resolvedMinireadPath,
|
|
36
|
+
"-i",
|
|
37
|
+
inputDirectory,
|
|
38
|
+
"-o",
|
|
39
|
+
outputDirectory,
|
|
40
|
+
"--transforms",
|
|
41
|
+
transformsValue,
|
|
42
|
+
];
|
|
43
|
+
if (verbose)
|
|
44
|
+
minireadArguments.push("--verbose");
|
|
45
|
+
await runCommand(nodePath, minireadArguments, {
|
|
46
|
+
cwd: rootDirectory,
|
|
47
|
+
label: "miniread",
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
export const formatDirectoryWithPrettier = async (options) => {
|
|
51
|
+
const { directory, rootDirectory, pnpmPath, prettierPath } = options;
|
|
52
|
+
if (prettierPath) {
|
|
53
|
+
await runCommand(prettierPath, ["--write", "--ignore-unknown", "--print-width", "9999", directory], { cwd: rootDirectory, label: "prettier" });
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (!pnpmPath) {
|
|
57
|
+
throw new Error("Unable to format: missing pnpmPath and prettierPath (this is a bug).");
|
|
58
|
+
}
|
|
59
|
+
await runCommand(pnpmPath, [
|
|
60
|
+
"exec",
|
|
61
|
+
"prettier",
|
|
62
|
+
"--write",
|
|
63
|
+
"--ignore-unknown",
|
|
64
|
+
"--print-width",
|
|
65
|
+
"9999",
|
|
66
|
+
directory,
|
|
67
|
+
], { cwd: rootDirectory, label: "pnpm exec prettier" });
|
|
68
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export const normalizeTransformIds = (ids) => {
|
|
2
|
+
return ids
|
|
3
|
+
.map((id) => id.trim())
|
|
4
|
+
.filter(Boolean)
|
|
5
|
+
.filter((id) => id !== "none");
|
|
6
|
+
};
|
|
7
|
+
export const uniqueIds = (ids) => {
|
|
8
|
+
const unique = [];
|
|
9
|
+
const seen = new Set();
|
|
10
|
+
for (const id of ids) {
|
|
11
|
+
if (seen.has(id))
|
|
12
|
+
continue;
|
|
13
|
+
seen.add(id);
|
|
14
|
+
unique.push(id);
|
|
15
|
+
}
|
|
16
|
+
return unique;
|
|
17
|
+
};
|
|
18
|
+
export const haveSameIds = (left, right) => {
|
|
19
|
+
if (left.length !== right.length)
|
|
20
|
+
return false;
|
|
21
|
+
const leftSet = new Set(left);
|
|
22
|
+
if (leftSet.size !== left.length)
|
|
23
|
+
return false;
|
|
24
|
+
for (const id of right) {
|
|
25
|
+
if (!leftSet.has(id))
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
return true;
|
|
29
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Transform } from "../../core/types.js";
|
|
2
|
+
type UpdateTransformManifestOptions = {
|
|
3
|
+
manifestPath: string;
|
|
4
|
+
baselineTransforms: string[];
|
|
5
|
+
testTransforms: string[];
|
|
6
|
+
reductionRatios: number[];
|
|
7
|
+
transformRegistry: Record<string, Transform>;
|
|
8
|
+
};
|
|
9
|
+
export declare const updateTransformManifestFromEvaluation: (options: UpdateTransformManifestOptions) => Promise<{
|
|
10
|
+
updated: boolean;
|
|
11
|
+
message: string;
|
|
12
|
+
}>;
|
|
13
|
+
export {};
|