Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion packages/cli/src/lib/formatters/time-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { colorTag } from "./markdown.js";
* - Under 3 days: "2d ago"
* - Otherwise: short date like "Jan 18"
*
* Returns a muted "—" when the input is undefined.
* Returns a muted "—" when the input is undefined or not a valid date.
*
* @param dateString - ISO date string or undefined
* @returns Human-readable relative time string
Expand All @@ -30,6 +30,10 @@ export function formatRelativeTime(dateString: string | undefined): string {
}

const date = new Date(dateString);
// An unparseable timestamp would otherwise render as "Invalid Date".
if (Number.isNaN(date.getTime())) {
return colorTag("muted", "—");
}
const now = Date.now();
// Clamp to >= 0 so clock skew, scheduled/future timestamps, or bad API data
// render as "0m ago" instead of a negative duration like "-5m ago".
Expand Down
12 changes: 11 additions & 1 deletion packages/cli/src/lib/sourcemap/debug-id.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import { createHash } from "node:crypto";
import { readFile, writeFile } from "node:fs/promises";
import { ValidationError } from "../errors.js";
import { logger } from "../logger.js";
import { isLikelyBinary } from "../scan/binary.js";
import {
Expand Down Expand Up @@ -266,7 +267,16 @@ export async function injectDebugId(
return { debugId: jsDebugId, wasInjected: false };
}

const map = JSON.parse(mapContent) as SourcemapJson;
let map: SourcemapJson;
try {
map = JSON.parse(mapContent) as SourcemapJson;
} catch (error) {
log.debug(`Failed to parse sourcemap ${mapPath}`, error);
throw new ValidationError(
`Sourcemap '${mapPath}' is not valid JSON.`,
"sourcemap",
);
}
const existingId = jsDebugId ?? readSourcemapDebugId(map);
if (
existingId &&
Expand Down
4 changes: 4 additions & 0 deletions packages/cli/test/lib/formatters/human.utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ describe("formatRelativeTime", () => {
expect(stripAnsi(result).trim()).toBe("—");
});

test("returns em-dash for an unparseable date", () => {
expect(stripAnsi(formatRelativeTime("not-a-date")).trim()).toBe("—");
});

test("formats minutes ago for recent times", () => {
const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000).toISOString();
const result = stripAnsi(formatRelativeTime(fiveMinutesAgo));
Expand Down
14 changes: 14 additions & 0 deletions packages/cli/test/script/debug-id.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,20 @@ describe("injectDebugId", () => {
expect(mapResult.debug_id).toBe(debugId);
});

test("rejects a malformed sourcemap with a ValidationError", async () => {
const jsPath = join(tmpDir, "bundle.js");
const mapPath = join(tmpDir, "bundle.js.map");

await writeFile(jsPath, 'console.log("hello");\n');
await writeFile(mapPath, "{ not json");

await expect(injectDebugId(jsPath, mapPath)).rejects.toThrow(
`Sourcemap '${mapPath}' is not valid JSON.`,
);
// The JS file must not be touched when the map cannot be parsed.
expect(await readFile(jsPath, "utf-8")).toBe('console.log("hello");\n');
});

test("prepends one ; to sourcemap mappings", async () => {
const jsPath = join(tmpDir, "bundle.js");
const mapPath = join(tmpDir, "bundle.js.map");
Expand Down
Loading