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
@@ -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,9 @@
1
+ export type EvaluateDependencies = {
2
+ diffPath: string;
3
+ pnpmPath: string | undefined;
4
+ prettierPath: string | undefined;
5
+ };
6
+ export declare const resolveEvaluateDependencies: (options: {
7
+ cwd: string;
8
+ formatCode: boolean;
9
+ }) => EvaluateDependencies;
@@ -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,2 @@
1
+ export declare const sanitizeFileComponent: (value: string) => string;
2
+ export declare const sanitizeTsvField: (value: string) => string;
@@ -0,0 +1,6 @@
1
+ export const sanitizeFileComponent = (value) => {
2
+ return value.replaceAll(/[^\w.-]/gu, "_");
3
+ };
4
+ export const sanitizeTsvField = (value) => {
5
+ return value.replaceAll(/\r?\n/gu, " ").replaceAll("\t", " ").trim();
6
+ };
@@ -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,3 @@
1
+ export declare const normalizeTransformIds: (ids: string[]) => string[];
2
+ export declare const uniqueIds: (ids: string[]) => string[];
3
+ export declare const haveSameIds: (left: string[], right: string[]) => boolean;
@@ -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 {};