miniread 1.17.0 → 1.19.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 (58) hide show
  1. package/dist/scripts/evaluate/check-expected-evaluations.d.ts +1 -1
  2. package/dist/scripts/evaluate/check-expected-evaluations.js +8 -7
  3. package/dist/scripts/evaluate/create-evaluate-command.js +1 -2
  4. package/dist/scripts/evaluate/parse-evaluate-cli-options.d.ts +2 -2
  5. package/dist/scripts/evaluate/parse-evaluate-cli-options.js +4 -2
  6. package/dist/scripts/evaluate/run-check-mode.d.ts +1 -1
  7. package/dist/scripts/evaluate/run-check-mode.js +9 -4
  8. package/dist/scripts/evaluate/run-evaluate-cli.js +6 -3
  9. package/dist/scripts/evaluate/transform-manifest.d.ts +3 -1
  10. package/dist/scripts/evaluate/transform-manifest.js +43 -36
  11. package/dist/scripts/snapshot/format-code.d.ts +1 -0
  12. package/dist/scripts/snapshot/format-code.js +4 -0
  13. package/dist/scripts/snapshot/print-snapshot-diff.d.ts +6 -0
  14. package/dist/scripts/snapshot/print-snapshot-diff.js +26 -0
  15. package/dist/scripts/snapshot/resolve-testcase-input.d.ts +16 -0
  16. package/dist/scripts/snapshot/resolve-testcase-input.js +30 -0
  17. package/dist/scripts/snapshot/run-snapshot-cli.js +67 -119
  18. package/dist/scripts/snapshot/run-testcase-transform.d.ts +13 -0
  19. package/dist/scripts/snapshot/run-testcase-transform.js +46 -0
  20. package/dist/transforms/_generated/manifest.d.ts +14 -0
  21. package/dist/transforms/_generated/manifest.js +173 -0
  22. package/dist/transforms/_generated/registry.d.ts +3 -0
  23. package/dist/transforms/_generated/registry.js +55 -0
  24. package/dist/transforms/expand-boolean-literals/manifest.json +6 -0
  25. package/dist/transforms/expand-sequence-expressions-v4/manifest.json +7 -0
  26. package/dist/transforms/expand-sequence-expressions-v5/manifest.json +5 -0
  27. package/dist/transforms/expand-special-number-literals/manifest.json +6 -0
  28. package/dist/transforms/expand-undefined-literals/manifest.json +6 -0
  29. package/dist/transforms/preset-stats.json +6 -0
  30. package/dist/transforms/remove-redundant-else/manifest.json +6 -0
  31. package/dist/transforms/rename-catch-parameters/manifest.json +6 -0
  32. package/dist/transforms/rename-char-code-at/manifest.json +6 -0
  33. package/dist/transforms/rename-charcode-variables/manifest.json +7 -0
  34. package/dist/transforms/rename-charcode-variables-v2/manifest.json +6 -0
  35. package/dist/transforms/rename-comparison-flags/manifest.json +6 -0
  36. package/dist/transforms/rename-destructured-aliases/manifest.json +6 -0
  37. package/dist/transforms/rename-event-parameters/manifest.json +5 -0
  38. package/dist/transforms/rename-loop-index-variables/manifest.json +7 -0
  39. package/dist/transforms/rename-loop-index-variables-v2/manifest.json +7 -0
  40. package/dist/transforms/rename-loop-index-variables-v3/manifest.json +6 -0
  41. package/dist/transforms/rename-parameters-to-match-properties/manifest.json +5 -0
  42. package/dist/transforms/rename-promise-executor-parameters/manifest.json +6 -0
  43. package/dist/transforms/rename-replace-child-parameters/manifest.json +6 -0
  44. package/dist/transforms/rename-this-aliases/manifest.json +6 -0
  45. package/dist/transforms/rename-timeout-ids/manifest.json +6 -0
  46. package/dist/transforms/rename-use-reference-guards/manifest.json +7 -0
  47. package/dist/transforms/rename-use-reference-guards-v2/manifest.json +5 -0
  48. package/dist/transforms/simplify-boolean-negations/manifest.json +5 -0
  49. package/dist/transforms/simplify-boolean-negations/simplify-boolean-negations-transform.d.ts +2 -0
  50. package/dist/transforms/simplify-boolean-negations/simplify-boolean-negations-transform.js +90 -0
  51. package/dist/transforms/split-variable-declarations/manifest.json +6 -0
  52. package/dist/transforms/transform-presets.js +2 -17
  53. package/dist/transforms/transform-registry.d.ts +1 -3
  54. package/dist/transforms/transform-registry.js +1 -51
  55. package/package.json +5 -6
  56. package/dist/scripts/evaluate/parse-transform-manifest.d.ts +0 -22
  57. package/dist/scripts/evaluate/parse-transform-manifest.js +0 -29
  58. package/transform-manifest.json +0 -251
@@ -10,7 +10,7 @@ type CheckExpectedEvaluationsResult = {
10
10
  allPresent: boolean;
11
11
  };
12
12
  type CheckExpectedEvaluationsOptions = {
13
- manifestPath: string;
13
+ transformsDirectory: string;
14
14
  testCasesDirectory: string;
15
15
  };
16
16
  export declare const checkExpectedEvaluations: (options: CheckExpectedEvaluationsOptions) => Promise<CheckExpectedEvaluationsResult>;
@@ -1,19 +1,20 @@
1
1
  import * as fs from "node:fs/promises";
2
+ import path from "node:path";
2
3
  import { allTransformIds } from "../../transforms/transform-registry.js";
3
4
  import { transformRegistry } from "../../transforms/transform-registry.js";
4
5
  import { transformPresets } from "../../transforms/transform-presets.js";
5
- import { parseTransformManifest } from "./parse-transform-manifest.js";
6
6
  import { checkAllSnapshots } from "./check-snapshots.js";
7
7
  export const checkExpectedEvaluations = async (options) => {
8
- const { manifestPath, testCasesDirectory } = options;
8
+ const { transformsDirectory, testCasesDirectory } = options;
9
9
  const transformIds = allTransformIds.toSorted();
10
10
  const missingManifestEntries = [];
11
- // Check manifest entries
12
- const manifestContent = await fs.readFile(manifestPath, "utf8");
13
- const manifest = parseTransformManifest(manifestContent);
14
- const manifestIds = new Set(manifest.transforms.map((t) => t.id));
11
+ // Check that each transform has a manifest.json file
15
12
  for (const transformId of transformIds) {
16
- if (!manifestIds.has(transformId)) {
13
+ const manifestPath = path.join(transformsDirectory, transformId, "manifest.json");
14
+ try {
15
+ await fs.access(manifestPath);
16
+ }
17
+ catch {
17
18
  missingManifestEntries.push({
18
19
  transformId,
19
20
  path: manifestPath,
@@ -22,8 +22,7 @@ export const createEvaluateCommand = (options) => {
22
22
  .option("--write-metrics", "Write metrics JSON to the evaluation directory")
23
23
  .option("--metrics-file <path>", "Write metrics JSON to a specific path")
24
24
  .option("--write-patches", "Write baseline/test diff patches to the evaluation directory")
25
- .option("-u, --update-manifest", "Update transform-manifest.json metrics (does not add new entries or edit notes)")
26
- .option("--manifest-file <path>", "Path to transform-manifest.json", "transform-manifest.json")
25
+ .option("-u, --update-manifest", "Update manifest.json metrics for transforms (does not add new entries or edit notes)")
27
26
  .option("-f, --overwrite", "Overwrite existing metrics/patch files")
28
27
  .option("--no-format-code", "Skip formatting temp directories with Prettier before diffing")
29
28
  .option("--check", "Check that all expected evaluation metrics files exist (does not run evaluations)");
@@ -12,7 +12,6 @@ export type EvaluateCliRawOptions = {
12
12
  metricsFile?: string;
13
13
  writePatches?: boolean;
14
14
  updateManifest?: boolean;
15
- manifestFile: string;
16
15
  overwrite?: boolean;
17
16
  formatCode: boolean;
18
17
  check?: boolean;
@@ -30,7 +29,8 @@ export type EvaluateCliOptions = {
30
29
  shouldWritePatches: boolean;
31
30
  shouldUpdateManifest: boolean;
32
31
  metricsPath: string;
33
- manifestPath: string;
32
+ transformsDirectory: string;
33
+ presetStatsPath: string;
34
34
  overwrite: boolean;
35
35
  formatCode: boolean;
36
36
  baselineSlug: string;
@@ -77,7 +77,8 @@ export const parseEvaluateCliOptions = (options) => {
77
77
  });
78
78
  const sourcesDirectory = path.resolve(cwd, rawOptions.sources);
79
79
  const evaluationDirectory = path.resolve(cwd, rawOptions.evaluationDir);
80
- const manifestPath = path.resolve(cwd, rawOptions.manifestFile);
80
+ const transformsDirectory = path.resolve(cwd, "src/transforms");
81
+ const presetStatsPath = path.resolve(cwd, "src/transforms/preset-stats.json");
81
82
  const shouldWriteMetrics = Boolean(rawOptions.writeMetrics || rawOptions.metricsFile);
82
83
  const shouldWritePatches = Boolean(rawOptions.writePatches);
83
84
  const shouldUpdateManifest = Boolean(rawOptions.updateManifest);
@@ -97,7 +98,8 @@ export const parseEvaluateCliOptions = (options) => {
97
98
  shouldWritePatches,
98
99
  shouldUpdateManifest,
99
100
  metricsPath,
100
- manifestPath,
101
+ transformsDirectory,
102
+ presetStatsPath,
101
103
  overwrite,
102
104
  formatCode,
103
105
  baselineSlug,
@@ -1,6 +1,6 @@
1
1
  import type { OutputFormat } from "./create-evaluate-command.js";
2
2
  type RunCheckModeOptions = {
3
- manifestPath: string;
3
+ transformsDirectory: string;
4
4
  rootDirectory: string;
5
5
  format: OutputFormat;
6
6
  };
@@ -1,9 +1,9 @@
1
1
  import path from "node:path";
2
2
  import { checkExpectedEvaluations } from "./check-expected-evaluations.js";
3
3
  export const runCheckMode = async (options) => {
4
- const { manifestPath, rootDirectory, format } = options;
4
+ const { transformsDirectory, rootDirectory, format } = options;
5
5
  const checkResult = await checkExpectedEvaluations({
6
- manifestPath,
6
+ transformsDirectory,
7
7
  testCasesDirectory: path.join(rootDirectory, "test-cases"),
8
8
  });
9
9
  if (format === "json") {
@@ -33,11 +33,16 @@ export const runCheckMode = async (options) => {
33
33
  }
34
34
  let exitCode = 0;
35
35
  if (checkResult.missingManifestEntries.length > 0) {
36
- console.error(`\n[MISSING MANIFEST ENTRIES] ${checkResult.missingManifestEntries.length} transform(s) not in transform-manifest.json:`);
36
+ console.error(`\n[MISSING MANIFEST FILES] ${checkResult.missingManifestEntries.length} transform(s) missing manifest.json:`);
37
37
  for (const item of checkResult.missingManifestEntries) {
38
38
  console.error(` - ${item.transformId}`);
39
39
  }
40
- console.error(`\n To fix: Add entries manually to transform-manifest.json, or run evaluation with --update-manifest`);
40
+ console.error(`\n To fix: Create a manifest.json file in each transform directory with the following structure:`);
41
+ console.error(` {`);
42
+ console.error(` "diffReductionImpact": 0,`);
43
+ console.error(` "recommended": true,`);
44
+ console.error(` "notes": "Description of the transform's impact"`);
45
+ console.error(` }`);
41
46
  exitCode = 1;
42
47
  }
43
48
  const missingSnapshots = checkResult.snapshotIssues.filter((issue) => issue.issue === "missing-required");
@@ -1,5 +1,6 @@
1
1
  import * as fs from "node:fs/promises";
2
2
  import packageJson from "../../../package.json" with { type: "json" };
3
+ import { transformPresets } from "../../transforms/transform-presets.js";
3
4
  import { createEvaluateCommand } from "./create-evaluate-command.js";
4
5
  import { parseEvaluateCliOptions, } from "./parse-evaluate-cli-options.js";
5
6
  import { resolveEvaluateDependencies } from "./resolve-evaluate-dependencies.js";
@@ -35,7 +36,7 @@ export const runEvaluateCli = async (argv) => {
35
36
  };
36
37
  if (parsedOptions.checkMode) {
37
38
  return runCheckMode({
38
- manifestPath: parsedOptions.manifestPath,
39
+ transformsDirectory: parsedOptions.transformsDirectory,
39
40
  rootDirectory: process.cwd(),
40
41
  format: parsedOptions.format,
41
42
  });
@@ -100,15 +101,17 @@ export const runEvaluateCli = async (argv) => {
100
101
  }
101
102
  if (parsedOptions.shouldUpdateManifest) {
102
103
  if (errors.length > 0) {
103
- console.error("Refusing to update transform-manifest.json because some evaluations failed.");
104
+ console.error("Refusing to update manifests because some evaluations failed.");
104
105
  exitCode = 1;
105
106
  }
106
107
  else {
107
108
  const updateResult = await updateTransformManifestFromEvaluation({
108
- manifestPath: parsedOptions.manifestPath,
109
+ transformsDirectory: parsedOptions.transformsDirectory,
110
+ presetStatsPath: parsedOptions.presetStatsPath,
109
111
  baselineTransforms: parsedOptions.baselineTransforms,
110
112
  testTransforms: parsedOptions.testTransforms,
111
113
  reductionRatios: results.map((r) => r.reductionRatio),
114
+ recommendedTransformIds: transformPresets.recommended,
112
115
  });
113
116
  console.error(updateResult.message);
114
117
  }
@@ -1,8 +1,10 @@
1
1
  type UpdateTransformManifestOptions = {
2
- manifestPath: string;
2
+ transformsDirectory: string;
3
+ presetStatsPath: string;
3
4
  baselineTransforms: string[];
4
5
  testTransforms: string[];
5
6
  reductionRatios: number[];
7
+ recommendedTransformIds: string[];
6
8
  };
7
9
  export declare const updateTransformManifestFromEvaluation: (options: UpdateTransformManifestOptions) => Promise<{
8
10
  updated: boolean;
@@ -1,7 +1,8 @@
1
1
  import * as fs from "node:fs/promises";
2
+ import path from "node:path";
2
3
  import { haveSameIds, normalizeTransformIds, uniqueIds, } from "./transform-id-set.js";
3
- import { parseTransformManifest } from "./parse-transform-manifest.js";
4
4
  import { writeJsonFileAtomic } from "./write-text-file-atomic.js";
5
+ import presetStats from "../../transforms/preset-stats.json" with { type: "json" };
5
6
  const average = (values) => {
6
7
  if (values.length === 0)
7
8
  return 0;
@@ -11,73 +12,79 @@ const average = (values) => {
11
12
  }
12
13
  return total / values.length;
13
14
  };
14
- export const updateTransformManifestFromEvaluation = async (options) => {
15
- const { manifestPath, baselineTransforms, testTransforms, reductionRatios } = options;
15
+ const loadManifest = async (manifestPath) => {
16
16
  try {
17
- await fs.access(manifestPath);
17
+ const content = await fs.readFile(manifestPath, "utf8");
18
+ return JSON.parse(content);
18
19
  }
19
- catch {
20
- return {
21
- updated: false,
22
- message: `Skipped updating transform-manifest.json (not found at ${manifestPath})`,
23
- };
20
+ catch (error) {
21
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
22
+ return undefined;
23
+ }
24
+ throw error;
24
25
  }
25
- const manifest = parseTransformManifest(await fs.readFile(manifestPath, "utf8"));
26
- const existingIds = new Set(manifest.transforms.map((t) => t.id));
26
+ };
27
+ export const updateTransformManifestFromEvaluation = async (options) => {
28
+ const { transformsDirectory, presetStatsPath, baselineTransforms, testTransforms, reductionRatios, recommendedTransformIds, } = options;
27
29
  const baselineIds = normalizeTransformIds(baselineTransforms);
28
30
  const testIds = normalizeTransformIds(testTransforms);
29
- const idsToUpdate = new Set([...baselineIds, ...testIds]);
30
- idsToUpdate.delete("none");
31
- const missingIds = [...idsToUpdate].filter((id) => !existingIds.has(id));
32
- let updated = false;
33
31
  const isBaselineNone = baselineIds.length === 0 ||
34
32
  (baselineTransforms.length === 1 && baselineTransforms[0] === "none");
33
+ let updated = false;
34
+ const messages = [];
35
+ // Update individual transform manifest if baseline is none and we have a single test transform
35
36
  if (isBaselineNone && testIds.length === 1) {
36
37
  const transformId = testIds[0];
37
38
  if (transformId) {
38
- const measuredImpact = average(reductionRatios);
39
- const entry = manifest.transforms.find((t) => t.id === transformId);
40
- if (entry && entry.diffReductionImpact !== measuredImpact) {
41
- entry.diffReductionImpact = measuredImpact;
42
- updated = true;
39
+ const manifestPath = path.join(transformsDirectory, transformId, "manifest.json");
40
+ const manifest = await loadManifest(manifestPath);
41
+ if (manifest) {
42
+ const measuredImpact = average(reductionRatios);
43
+ if (manifest.diffReductionImpact !== measuredImpact) {
44
+ manifest.diffReductionImpact = measuredImpact;
45
+ manifest.evaluatedAt = new Date().toISOString();
46
+ await writeJsonFileAtomic(manifestPath, manifest, {
47
+ overwrite: true,
48
+ });
49
+ messages.push(`Updated ${transformId}/manifest.json`);
50
+ updated = true;
51
+ }
52
+ }
53
+ else {
54
+ messages.push(`Skipping update for ${transformId}: manifest.json not found at ${manifestPath}`);
43
55
  }
44
56
  }
45
57
  }
58
+ // Update preset stats if testing the recommended preset
46
59
  if (isBaselineNone) {
47
- const recommendedIds = manifest.transforms
48
- .filter((entry) => entry.recommended)
49
- .map((entry) => entry.id);
50
60
  const testUniqueIds = uniqueIds(testIds);
51
- const recommendedUniqueIds = uniqueIds(recommendedIds);
61
+ const recommendedUniqueIds = uniqueIds(recommendedTransformIds);
52
62
  if (testUniqueIds.length > 0 &&
53
63
  haveSameIds(testUniqueIds, recommendedUniqueIds)) {
54
64
  const measuredImpact = average(reductionRatios);
55
- manifest.presetStats ??= {};
56
- const existing = manifest.presetStats.recommended;
65
+ const currentStats = { ...presetStats };
66
+ const existing = currentStats.recommended;
57
67
  if (existing?.diffReductionImpact !== measuredImpact) {
58
- manifest.presetStats.recommended = {
68
+ currentStats.recommended = {
59
69
  diffReductionImpact: measuredImpact,
60
70
  notes: `Measured with baseline none: ${(measuredImpact * 100).toFixed(2)}%.`,
61
71
  };
72
+ await writeJsonFileAtomic(presetStatsPath, currentStats, {
73
+ overwrite: true,
74
+ });
75
+ messages.push("Updated preset-stats.json");
62
76
  updated = true;
63
77
  }
64
78
  }
65
79
  }
66
80
  if (!updated) {
67
- const missingMessage = missingIds.length > 0
68
- ? ` Missing transform entries: ${missingIds.join(", ")}. Add them to transform-manifest.json manually.`
69
- : "";
70
81
  return {
71
82
  updated: false,
72
- message: `transform-manifest.json already up to date.${missingMessage}`,
83
+ message: messages.length > 0 ? messages.join("\n") : "No updates needed.",
73
84
  };
74
85
  }
75
- await writeJsonFileAtomic(manifestPath, manifest, { overwrite: true });
76
- const missingMessage = missingIds.length > 0
77
- ? ` Missing transform entries: ${missingIds.join(", ")}. Add them to transform-manifest.json manually.`
78
- : "";
79
86
  return {
80
87
  updated: true,
81
- message: `Updated transform-manifest.json.${missingMessage}`,
88
+ message: messages.join("\n"),
82
89
  };
83
90
  };
@@ -0,0 +1 @@
1
+ export declare const formatCode: (code: string) => Promise<string>;
@@ -0,0 +1,4 @@
1
+ import * as prettier from "prettier";
2
+ export const formatCode = async (code) => {
3
+ return prettier.format(code, { parser: "babel" });
4
+ };
@@ -0,0 +1,6 @@
1
+ type PrintSnapshotDiffOptions = {
2
+ expectedContent: string;
3
+ actualOutput: string;
4
+ };
5
+ export declare const printSnapshotDiff: (options: PrintSnapshotDiffOptions) => void;
6
+ export {};
@@ -0,0 +1,26 @@
1
+ export const printSnapshotDiff = (options) => {
2
+ const { expectedContent, actualOutput } = options;
3
+ console.log(`\nDiff: expected vs actual`);
4
+ console.log("---");
5
+ const expectedLines = expectedContent.split("\n");
6
+ const actualLines = actualOutput.split("\n");
7
+ const maxLines = Math.max(expectedLines.length, actualLines.length);
8
+ for (let lineIndex = 0; lineIndex < maxLines; lineIndex++) {
9
+ const expectedLine = expectedLines[lineIndex];
10
+ const actualLine = actualLines[lineIndex];
11
+ if (expectedLine === actualLine)
12
+ continue;
13
+ console.log(`Line ${lineIndex + 1}:`);
14
+ if (expectedLine !== undefined && actualLine === undefined) {
15
+ console.log(` - ${expectedLine}`);
16
+ continue;
17
+ }
18
+ if (expectedLine === undefined && actualLine !== undefined) {
19
+ console.log(` + ${actualLine}`);
20
+ continue;
21
+ }
22
+ console.log(` - ${expectedLine}`);
23
+ console.log(` + ${actualLine}`);
24
+ }
25
+ console.log("---");
26
+ };
@@ -0,0 +1,16 @@
1
+ type ResolveTestcaseInputOptions = {
2
+ testcase: string;
3
+ };
4
+ type ResolveTestcaseInputValue = {
5
+ testcaseDirectory: string;
6
+ basePath: string;
7
+ };
8
+ type Result<T> = {
9
+ ok: true;
10
+ value: T;
11
+ } | {
12
+ ok: false;
13
+ error: Error;
14
+ };
15
+ export declare const resolveTestcaseInput: (options: ResolveTestcaseInputOptions) => Promise<Result<ResolveTestcaseInputValue>>;
16
+ export {};
@@ -0,0 +1,30 @@
1
+ import * as fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ const TEST_CASES_DIR = path.resolve(import.meta.dirname, "../../../test-cases");
4
+ export const resolveTestcaseInput = async (options) => {
5
+ const { testcase } = options;
6
+ const testcaseDirectory = path.join(TEST_CASES_DIR, testcase);
7
+ try {
8
+ const stats = await fs.stat(testcaseDirectory);
9
+ if (!stats.isDirectory()) {
10
+ return { ok: false, error: new Error(`${testcase} is not a directory`) };
11
+ }
12
+ }
13
+ catch {
14
+ return {
15
+ ok: false,
16
+ error: new Error(`Test case directory not found: test-cases/${testcase}`),
17
+ };
18
+ }
19
+ const basePath = path.join(testcaseDirectory, "base.js");
20
+ try {
21
+ await fs.access(basePath);
22
+ }
23
+ catch {
24
+ return {
25
+ ok: false,
26
+ error: new Error(`base.js not found in test-cases/${testcase}/`),
27
+ };
28
+ }
29
+ return { ok: true, value: { testcaseDirectory, basePath } };
30
+ };
@@ -1,75 +1,39 @@
1
1
  import * as fs from "node:fs/promises";
2
- import { createRequire } from "node:module";
3
2
  import path from "node:path";
4
- import * as prettier from "prettier";
5
3
  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";
4
+ import { formatCode } from "./format-code.js";
9
5
  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
- };
6
+ import { printSnapshotDiff } from "./print-snapshot-diff.js";
7
+ import { resolveTestcaseInput } from "./resolve-testcase-input.js";
8
+ import { runTestcaseTransform } from "./run-testcase-transform.js";
42
9
  export const runSnapshotCli = async (argv) => {
43
10
  const program = createSnapshotCommand({ version: packageJson.version });
44
11
  program.parse(argv.filter((argument) => argument !== "--"));
45
12
  const rawOptions = program.opts();
46
13
  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`);
14
+ const resolved = await resolveTestcaseInput({ testcase });
15
+ if (!resolved.ok) {
16
+ const message = resolved.error.message;
17
+ if (message === `${testcase} is not a directory`) {
18
+ console.error(`Error: ${message}`);
53
19
  return 1;
54
20
  }
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.`);
21
+ if (message.startsWith("Test case directory not found:")) {
22
+ console.error(`Error: ${message}`);
23
+ console.error(`Create it first: mkdir -p test-cases/${testcase}`);
24
+ return 1;
25
+ }
26
+ if (message.startsWith("base.js not found in test-cases/")) {
27
+ console.error(`Error: ${message}`);
28
+ console.error(`Create test-cases/${testcase}/base.js with your minified snippet first.`);
29
+ return 1;
30
+ }
31
+ console.error(`Error: ${message}`);
69
32
  return 1;
70
33
  }
71
- const expectedPath = path.join(testCaseDirectory, `${transform}-expected.js`);
72
- const actualPath = path.join(testCaseDirectory, `${transform}.js`);
34
+ const { testcaseDirectory, basePath } = resolved.value;
35
+ const expectedPath = path.join(testcaseDirectory, `${transform}-expected.js`);
36
+ const actualPath = path.join(testcaseDirectory, `${transform}.js`);
73
37
  if (expected) {
74
38
  // Expected-file workflow
75
39
  // Step 1: Check if expected file exists, create from base.js if not
@@ -83,69 +47,53 @@ export const runSnapshotCli = async (argv) => {
83
47
  await fs.writeFile(expectedPath, formattedBase);
84
48
  console.log(`Created: test-cases/${testcase}/${transform}-expected.js`);
85
49
  console.log("Edit this file to match your expected output, then re-run.");
50
+ // Design phase: allow creating the expected file before the transform exists.
51
+ // The next run (after implementing the transform) will write the actual output
52
+ // and compare it against this expected snapshot.
86
53
  return 0;
87
54
  }
88
- }
89
- // Common path: Run the transform, format, and write actual output
90
- let actualOutput;
91
- try {
92
- const rawOutput = await runTransform(basePath, transform);
93
- actualOutput = await formatCode(rawOutput);
94
- }
95
- catch (error) {
96
- const message = error instanceof Error ? error.message : String(error);
97
- console.error(`Error running transform: ${message}`);
98
- return 1;
99
- }
100
- await fs.writeFile(actualPath, actualOutput);
101
- console.log(`Created: test-cases/${testcase}/${transform}.js`);
102
- if (!expected) {
103
- return 0;
104
- }
105
- // Step 3: Compare expected vs actual
106
- const expectedContent = await fs.readFile(expectedPath, "utf8");
107
- if (expectedContent === actualOutput) {
108
- // Success! Delete expected file
109
- await fs.unlink(expectedPath);
110
- console.log(`\nSuccess: Output matches expected.`);
111
- console.log(`Deleted: test-cases/${testcase}/${transform}-expected.js`);
112
- console.log(`\nSnapshot ready: test-cases/${testcase}/${transform}.js`);
113
- return 0;
114
- }
115
- // Diff - show the difference
116
- console.log(`\nDiff: expected vs actual`);
117
- console.log("---");
118
- // Simple line-by-line diff output
119
- const expectedLines = expectedContent.split("\n");
120
- const actualLines = actualOutput.split("\n");
121
- const maxLines = Math.max(expectedLines.length, actualLines.length);
122
- let hasDiff = false;
123
- for (let lineIndex = 0; lineIndex < maxLines; lineIndex++) {
124
- const exp = expectedLines[lineIndex];
125
- const act = actualLines[lineIndex];
126
- if (exp !== act) {
127
- hasDiff = true;
128
- if (exp !== undefined && act === undefined) {
129
- console.log(`Line ${lineIndex + 1}:`);
130
- console.log(` - ${exp}`);
131
- }
132
- else if (exp === undefined && act !== undefined) {
133
- console.log(`Line ${lineIndex + 1}:`);
134
- console.log(` + ${act}`);
135
- }
136
- else {
137
- console.log(`Line ${lineIndex + 1}:`);
138
- console.log(` - ${exp}`);
139
- console.log(` + ${act}`);
140
- }
55
+ const outputResult = await runTestcaseTransform({
56
+ inputPath: basePath,
57
+ transformId: transform,
58
+ });
59
+ if (!outputResult.ok) {
60
+ console.error(`Error running transform: ${outputResult.error.message}`);
61
+ return 1;
62
+ }
63
+ const actualOutput = outputResult.value;
64
+ // Step 2: Write actual output
65
+ await fs.writeFile(actualPath, actualOutput);
66
+ console.log(`Created: test-cases/${testcase}/${transform}.js`);
67
+ // Step 3: Compare expected vs actual
68
+ const expectedContent = await fs.readFile(expectedPath, "utf8");
69
+ if (expectedContent === actualOutput) {
70
+ // Success! Delete expected file
71
+ await fs.unlink(expectedPath);
72
+ console.log(`\nSuccess: Output matches expected.`);
73
+ console.log(`Deleted: test-cases/${testcase}/${transform}-expected.js`);
74
+ console.log(`\nSnapshot ready: test-cases/${testcase}/${transform}.js`);
75
+ return 0;
76
+ }
77
+ else {
78
+ printSnapshotDiff({ expectedContent, actualOutput });
79
+ console.log(`\nExpected file preserved: test-cases/${testcase}/${transform}-expected.js`);
80
+ console.log("Fix the implementation or update the expected file, then re-run.");
81
+ return 1;
141
82
  }
142
83
  }
143
- if (!hasDiff) {
144
- // Shouldn't happen since we already checked equality, but just in case
145
- console.log("(no visible differences)");
84
+ else {
85
+ const outputResult = await runTestcaseTransform({
86
+ inputPath: basePath,
87
+ transformId: transform,
88
+ });
89
+ if (!outputResult.ok) {
90
+ console.error(`Error running transform: ${outputResult.error.message}`);
91
+ return 1;
92
+ }
93
+ const actualOutput = outputResult.value;
94
+ // Simple mode: just write actual output
95
+ await fs.writeFile(actualPath, actualOutput);
96
+ console.log(`Created: test-cases/${testcase}/${transform}.js`);
97
+ return 0;
146
98
  }
147
- console.log("---");
148
- console.log(`\nExpected file preserved: test-cases/${testcase}/${transform}-expected.js`);
149
- console.log("Fix the implementation or update the expected file, then re-run.");
150
- return 1;
151
99
  };
@@ -0,0 +1,13 @@
1
+ type Result<T> = {
2
+ ok: true;
3
+ value: T;
4
+ } | {
5
+ ok: false;
6
+ error: Error;
7
+ };
8
+ type RunTestcaseTransformOptions = {
9
+ inputPath: string;
10
+ transformId: string;
11
+ };
12
+ export declare const runTestcaseTransform: (options: RunTestcaseTransformOptions) => Promise<Result<string>>;
13
+ export {};