From da78f72a8dd993c8a47102e33d08b0870dbf9226 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 16 Jul 2026 18:58:50 +0000 Subject: [PATCH 1/4] fix(action): improve ensure-json-report error messages - Add specific error messages for different failure scenarios - Distinguish between empty reports, invalid JSON, missing schema version, unsupported schema version, and missing ok field - Guide users to update the action when schema version mismatch occurs - Provide actionable error messages instead of generic 'exited before producing' message Fixes #1331 Co-authored-by: Skosh --- scripts/ensure-json-report.mjs | 63 +++++++++++++++++++++++++++------- 1 file changed, 51 insertions(+), 12 deletions(-) diff --git a/scripts/ensure-json-report.mjs b/scripts/ensure-json-report.mjs index 27948cb86d..481708b571 100644 --- a/scripts/ensure-json-report.mjs +++ b/scripts/ensure-json-report.mjs @@ -7,7 +7,7 @@ if (!reportPath) { process.exit(0); } -const fallbackReport = { +const buildFallbackReport = (errorMessage) => ({ schemaVersion: 3, version: "unknown", ok: false, @@ -27,24 +27,63 @@ const fallbackReport = { elapsedMilliseconds: 0, error: { name: "ReactDoctorActionError", - message: `react-doctor exited with status ${Number.isFinite(status) ? status : 1} before producing a JSON report.`, + message: errorMessage, chain: [], }, -}; +}); -// Known JsonReport schema versions remain valid CLI output; only an -// unparseable or unrecognized payload is treated as a failed scan. const KNOWN_SCHEMA_VERSIONS = new Set([1, 2, 3]); try { const raw = fs.readFileSync(reportPath, "utf8").trim(); + + if (!raw) { + const fallbackReport = buildFallbackReport( + `react-doctor exited with status ${Number.isFinite(status) ? status : 1} but produced an empty report. This may occur when no files were scanned or an unexpected error occurred.`, + ); + fs.writeFileSync(reportPath, `${JSON.stringify(fallbackReport)}\n`); + process.exit(1); + } + const parsed = JSON.parse(raw); - if (parsed && KNOWN_SCHEMA_VERSIONS.has(parsed.schemaVersion) && typeof parsed.ok === "boolean") { - process.exit(0); + + if (!parsed || typeof parsed !== "object") { + const fallbackReport = buildFallbackReport( + `react-doctor produced invalid JSON output. This may indicate a crash or unexpected error during the scan.`, + ); + fs.writeFileSync(reportPath, `${JSON.stringify(fallbackReport)}\n`); + process.exit(1); + } + + if (typeof parsed.schemaVersion !== "number") { + const fallbackReport = buildFallbackReport( + `react-doctor produced a JSON report without a schemaVersion field. The installed react-doctor version may be incompatible with this GitHub Action version.`, + ); + fs.writeFileSync(reportPath, `${JSON.stringify(fallbackReport)}\n`); + process.exit(1); + } + + if (!KNOWN_SCHEMA_VERSIONS.has(parsed.schemaVersion)) { + const fallbackReport = buildFallbackReport( + `react-doctor produced a JSON report with schema version ${parsed.schemaVersion}, which is not supported by this GitHub Action version (supports: ${Array.from(KNOWN_SCHEMA_VERSIONS).sort().join(", ")}). Please update the GitHub Action to the latest version (millionco/react-doctor@v2) or pin the react-doctor version to match this Action release.`, + ); + fs.writeFileSync(reportPath, `${JSON.stringify(fallbackReport)}\n`); + process.exit(1); } -} catch { - // Fall through to the fallback report. + + if (typeof parsed.ok !== "boolean") { + const fallbackReport = buildFallbackReport( + `react-doctor produced a JSON report with schema version ${parsed.schemaVersion} but is missing the required 'ok' field. The report may be corrupted.`, + ); + fs.writeFileSync(reportPath, `${JSON.stringify(fallbackReport)}\n`); + process.exit(1); + } + + process.exit(0); +} catch (parseError) { + const fallbackReport = buildFallbackReport( + `react-doctor produced unparseable JSON output. Parse error: ${parseError.message}`, + ); + fs.writeFileSync(reportPath, `${JSON.stringify(fallbackReport)}\n`); + process.exit(1); } - -fs.writeFileSync(reportPath, `${JSON.stringify(fallbackReport)}\n`); -process.exit(1); From 3501ed03a6bf1693389fb3ca1e081ffc0694d0c0 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 16 Jul 2026 18:58:58 +0000 Subject: [PATCH 2/4] chore: add changeset for action error message improvements Co-authored-by: Skosh --- .changeset/fix-action-error-messages.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fix-action-error-messages.md diff --git a/.changeset/fix-action-error-messages.md b/.changeset/fix-action-error-messages.md new file mode 100644 index 0000000000..f2392cbc35 --- /dev/null +++ b/.changeset/fix-action-error-messages.md @@ -0,0 +1,5 @@ +--- +"react-doctor": patch +--- + +fix(action): improve ensure-json-report error messages with specific diagnostics for different failure scenarios From 47a6a74e33193c65201a3add2e753700fb54bf6e Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 16 Jul 2026 19:00:44 +0000 Subject: [PATCH 3/4] style: format ensure-json-report.mjs Co-authored-by: Skosh --- scripts/ensure-json-report.mjs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/scripts/ensure-json-report.mjs b/scripts/ensure-json-report.mjs index 481708b571..f429e1d1c5 100644 --- a/scripts/ensure-json-report.mjs +++ b/scripts/ensure-json-report.mjs @@ -36,7 +36,7 @@ const KNOWN_SCHEMA_VERSIONS = new Set([1, 2, 3]); try { const raw = fs.readFileSync(reportPath, "utf8").trim(); - + if (!raw) { const fallbackReport = buildFallbackReport( `react-doctor exited with status ${Number.isFinite(status) ? status : 1} but produced an empty report. This may occur when no files were scanned or an unexpected error occurred.`, @@ -44,9 +44,9 @@ try { fs.writeFileSync(reportPath, `${JSON.stringify(fallbackReport)}\n`); process.exit(1); } - + const parsed = JSON.parse(raw); - + if (!parsed || typeof parsed !== "object") { const fallbackReport = buildFallbackReport( `react-doctor produced invalid JSON output. This may indicate a crash or unexpected error during the scan.`, @@ -54,7 +54,7 @@ try { fs.writeFileSync(reportPath, `${JSON.stringify(fallbackReport)}\n`); process.exit(1); } - + if (typeof parsed.schemaVersion !== "number") { const fallbackReport = buildFallbackReport( `react-doctor produced a JSON report without a schemaVersion field. The installed react-doctor version may be incompatible with this GitHub Action version.`, @@ -62,7 +62,7 @@ try { fs.writeFileSync(reportPath, `${JSON.stringify(fallbackReport)}\n`); process.exit(1); } - + if (!KNOWN_SCHEMA_VERSIONS.has(parsed.schemaVersion)) { const fallbackReport = buildFallbackReport( `react-doctor produced a JSON report with schema version ${parsed.schemaVersion}, which is not supported by this GitHub Action version (supports: ${Array.from(KNOWN_SCHEMA_VERSIONS).sort().join(", ")}). Please update the GitHub Action to the latest version (millionco/react-doctor@v2) or pin the react-doctor version to match this Action release.`, @@ -70,7 +70,7 @@ try { fs.writeFileSync(reportPath, `${JSON.stringify(fallbackReport)}\n`); process.exit(1); } - + if (typeof parsed.ok !== "boolean") { const fallbackReport = buildFallbackReport( `react-doctor produced a JSON report with schema version ${parsed.schemaVersion} but is missing the required 'ok' field. The report may be corrupted.`, @@ -78,7 +78,7 @@ try { fs.writeFileSync(reportPath, `${JSON.stringify(fallbackReport)}\n`); process.exit(1); } - + process.exit(0); } catch (parseError) { const fallbackReport = buildFallbackReport( From 0d59eb9b1dfee840758fcbdb552bf5660f5af2ef Mon Sep 17 00:00:00 2001 From: Aiden Bai Date: Mon, 27 Jul 2026 03:48:51 +0000 Subject: [PATCH 4/4] fix(action): harden JSON report fallback --- .changeset/fix-action-error-messages.md | 5 - .../tests/ensure-json-report.test.ts | 94 +++++++++++++++++++ scripts/ensure-json-report.mjs | 85 +++++++++-------- 3 files changed, 136 insertions(+), 48 deletions(-) delete mode 100644 .changeset/fix-action-error-messages.md create mode 100644 packages/react-doctor/tests/ensure-json-report.test.ts diff --git a/.changeset/fix-action-error-messages.md b/.changeset/fix-action-error-messages.md deleted file mode 100644 index f2392cbc35..0000000000 --- a/.changeset/fix-action-error-messages.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"react-doctor": patch ---- - -fix(action): improve ensure-json-report error messages with specific diagnostics for different failure scenarios diff --git a/packages/react-doctor/tests/ensure-json-report.test.ts b/packages/react-doctor/tests/ensure-json-report.test.ts new file mode 100644 index 0000000000..8abc882387 --- /dev/null +++ b/packages/react-doctor/tests/ensure-json-report.test.ts @@ -0,0 +1,94 @@ +import { spawnSync } from "node:child_process"; +import * as fs from "node:fs"; +import os from "node:os"; +import * as path from "node:path"; +import { afterEach, describe, expect, it } from "vite-plus/test"; + +const REPOSITORY_ROOT = path.resolve(import.meta.dirname, "..", "..", ".."); +const ENSURE_REPORT_SCRIPT_PATH = path.join(REPOSITORY_ROOT, "scripts/ensure-json-report.mjs"); +const tempDirectories: string[] = []; + +const runEnsureJsonReport = (contents?: string) => { + const tempDirectory = fs.mkdtempSync(path.join(os.tmpdir(), "react-doctor-report-")); + const reportPath = path.join(tempDirectory, "report.json"); + tempDirectories.push(tempDirectory); + + if (contents !== undefined) { + fs.writeFileSync(reportPath, contents); + } + + const result = spawnSync(process.execPath, [ENSURE_REPORT_SCRIPT_PATH, reportPath, "7"], { + encoding: "utf8", + }); + + return { + report: JSON.parse(fs.readFileSync(reportPath, "utf8")), + status: result.status, + }; +}; + +describe("ensure-json-report", () => { + afterEach(() => { + for (const directory of tempDirectories.splice(0)) { + fs.rmSync(directory, { recursive: true, force: true }); + } + }); + + it.each([1, 2, 3])("accepts schema version %s reports", (schemaVersion) => { + const contents = JSON.stringify({ schemaVersion, ok: true }); + const { report, status } = runEnsureJsonReport(contents); + + expect(status).toBe(0); + expect(report).toEqual({ schemaVersion, ok: true }); + }); + + it.each([ + { + contents: "", + message: "react-doctor exited with status 7 but produced an empty report.", + }, + { + contents: "{", + message: "react-doctor produced output that is not valid JSON.", + }, + { + contents: "null", + message: "react-doctor produced JSON that is not a report object.", + }, + { + contents: "{}", + message: + "react-doctor produced a JSON report without a schemaVersion. The installed CLI may be incompatible with this GitHub Action version.", + }, + { + contents: '{"schemaVersion":999,"ok":true}', + message: + "react-doctor produced schema version 999, but this GitHub Action supports 1, 2, 3. Update millionco/react-doctor@v2 or pin the CLI version to match the Action release.", + }, + { + contents: '{"schemaVersion":3}', + message: "react-doctor produced a schema version 3 report without the required ok field.", + }, + ])("writes a valid fallback report for malformed output", ({ contents, message }) => { + const { report, status } = runEnsureJsonReport(contents); + + expect(status).toBe(1); + expect(report).toMatchObject({ + schemaVersion: 3, + ok: false, + error: { + name: "ReactDoctorActionError", + message, + }, + }); + }); + + it("reports when the CLI did not create a readable report", () => { + const { report, status } = runEnsureJsonReport(); + + expect(status).toBe(1); + expect(report.error.message).toBe( + "react-doctor exited with status 7 without producing a readable JSON report.", + ); + }); +}); diff --git a/scripts/ensure-json-report.mjs b/scripts/ensure-json-report.mjs index f429e1d1c5..da351508c7 100644 --- a/scripts/ensure-json-report.mjs +++ b/scripts/ensure-json-report.mjs @@ -32,58 +32,57 @@ const buildFallbackReport = (errorMessage) => ({ }, }); +const failWithReport = (errorMessage) => { + fs.writeFileSync(reportPath, `${JSON.stringify(buildFallbackReport(errorMessage))}\n`); + process.exit(1); +}; + const KNOWN_SCHEMA_VERSIONS = new Set([1, 2, 3]); +let raw; + try { - const raw = fs.readFileSync(reportPath, "utf8").trim(); + raw = fs.readFileSync(reportPath, "utf8").trim(); +} catch { + failWithReport( + `react-doctor exited with status ${Number.isFinite(status) ? status : 1} without producing a readable JSON report.`, + ); +} - if (!raw) { - const fallbackReport = buildFallbackReport( - `react-doctor exited with status ${Number.isFinite(status) ? status : 1} but produced an empty report. This may occur when no files were scanned or an unexpected error occurred.`, - ); - fs.writeFileSync(reportPath, `${JSON.stringify(fallbackReport)}\n`); - process.exit(1); - } +if (!raw) { + failWithReport( + `react-doctor exited with status ${Number.isFinite(status) ? status : 1} but produced an empty report.`, + ); +} - const parsed = JSON.parse(raw); +let parsed; - if (!parsed || typeof parsed !== "object") { - const fallbackReport = buildFallbackReport( - `react-doctor produced invalid JSON output. This may indicate a crash or unexpected error during the scan.`, - ); - fs.writeFileSync(reportPath, `${JSON.stringify(fallbackReport)}\n`); - process.exit(1); - } +try { + parsed = JSON.parse(raw); +} catch { + failWithReport("react-doctor produced output that is not valid JSON."); +} - if (typeof parsed.schemaVersion !== "number") { - const fallbackReport = buildFallbackReport( - `react-doctor produced a JSON report without a schemaVersion field. The installed react-doctor version may be incompatible with this GitHub Action version.`, - ); - fs.writeFileSync(reportPath, `${JSON.stringify(fallbackReport)}\n`); - process.exit(1); - } +if (!parsed || typeof parsed !== "object") { + failWithReport("react-doctor produced JSON that is not a report object."); +} - if (!KNOWN_SCHEMA_VERSIONS.has(parsed.schemaVersion)) { - const fallbackReport = buildFallbackReport( - `react-doctor produced a JSON report with schema version ${parsed.schemaVersion}, which is not supported by this GitHub Action version (supports: ${Array.from(KNOWN_SCHEMA_VERSIONS).sort().join(", ")}). Please update the GitHub Action to the latest version (millionco/react-doctor@v2) or pin the react-doctor version to match this Action release.`, - ); - fs.writeFileSync(reportPath, `${JSON.stringify(fallbackReport)}\n`); - process.exit(1); - } +if (typeof parsed.schemaVersion !== "number") { + failWithReport( + "react-doctor produced a JSON report without a schemaVersion. The installed CLI may be incompatible with this GitHub Action version.", + ); +} - if (typeof parsed.ok !== "boolean") { - const fallbackReport = buildFallbackReport( - `react-doctor produced a JSON report with schema version ${parsed.schemaVersion} but is missing the required 'ok' field. The report may be corrupted.`, - ); - fs.writeFileSync(reportPath, `${JSON.stringify(fallbackReport)}\n`); - process.exit(1); - } +if (!KNOWN_SCHEMA_VERSIONS.has(parsed.schemaVersion)) { + failWithReport( + `react-doctor produced schema version ${parsed.schemaVersion}, but this GitHub Action supports ${Array.from(KNOWN_SCHEMA_VERSIONS).sort().join(", ")}. Update millionco/react-doctor@v2 or pin the CLI version to match the Action release.`, + ); +} - process.exit(0); -} catch (parseError) { - const fallbackReport = buildFallbackReport( - `react-doctor produced unparseable JSON output. Parse error: ${parseError.message}`, +if (typeof parsed.ok !== "boolean") { + failWithReport( + `react-doctor produced a schema version ${parsed.schemaVersion} report without the required ok field.`, ); - fs.writeFileSync(reportPath, `${JSON.stringify(fallbackReport)}\n`); - process.exit(1); } + +process.exit(0);