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
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,10 @@
"@types/cors": "^2.8.19",
"@types/express": "^5.0.6",
"@types/morgan": "^1.9.10",
"@types/node": "^22.9.0",
"@types/node": "^24.12.2",
"@types/split2": "^4.2.3",
"jasmine": "^6.2.0",
"simple-git-hooks": "^2.13.1",
"typescript": "^5.6.3"
"typescript": "^6.0.3"
}
}
35 changes: 20 additions & 15 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions routes/caniuse/feature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@ export default async function route(req: IRequest, res: Response) {
}
res.json({ result });
} catch (error) {
const errorCode = error.message === "INTERNAL_ERROR" ? 500 : 404;
const message = error instanceof Error ? error.message : String(error);
const errorCode = message === "INTERNAL_ERROR" ? 500 : 404;
console.error("caniuse feature route error", error);
res.status(errorCode);
res.setHeader("Content-Type", "text/plain");
res.send(error.message);
res.send(errorCode === 500 ? "Internal Server Error" : "Not Found");
}
}

Expand Down
2 changes: 1 addition & 1 deletion routes/caniuse/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { cache } from "./lib/index.js";
import { Request, Response } from "express";

const workerFile = path.join(import.meta.dirname, "update.worker.js");
const taskQueue = new BackgroundTaskQueue<typeof import("./update.worker")>(
const taskQueue = new BackgroundTaskQueue<typeof import("./update.worker.ts")>(
workerFile,
"caniuse_update",
);
Expand Down
3 changes: 2 additions & 1 deletion routes/docs/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ export default async function route(_req: Request, res: Response) {
console.log(`Successfully regenerated docs in ${Date.now() - start}ms.`);
res.sendStatus(200); // ok
} catch (error) {
const { message = "", statusCode = 500 } = error;
const message = error instanceof Error ? error.message : String(error);
const statusCode = error instanceof HTTPError ? error.statusCode : 500;
console.error(`Failed to regenerate docs: ${message.slice(0, 400)}...`);
res.status(statusCode);
res.send(message);
Expand Down
3 changes: 2 additions & 1 deletion routes/github/commits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ export default async function route(req: IRequest, res: Response) {
}
res.json(commits);
} catch (error) {
console.error("Failed to fetch commits", error);
res.set("Content-Type", "text/plain");
res.status(404).send(error.message);
res.status(404).send("Unable to fetch commits");
}
}
2 changes: 1 addition & 1 deletion routes/github/contributors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export default async function route(req: IRequest, res: Response) {
contributors.push(contributor);
}
} catch (err) {
if (err.message && err.message.includes("404 Not Found")) {
if (err instanceof Error && err.message.includes("404 Not Found")) {
await cache.set(cacheKey, null);
return res.sendStatus(404);
} else {
Expand Down
6 changes: 4 additions & 2 deletions routes/github/files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ export default async function route(req: IRequest, res: Response) {
res.set("Cache-Control", `max-age=${seconds("30m")}`);
res.json({ entries });
} catch (error) {
const errorCode = error.message === "INTERNAL_ERROR" ? 500 : 404;
const message = error instanceof Error ? error.message : String(error);
const errorCode = message === "INTERNAL_ERROR" ? 500 : 404;
console.error("Failed to get repository files", error);
res.status(errorCode);
res.setHeader("Content-Type", "text/plain");
res.send(error.message);
res.send(errorCode === 500 ? "Internal server error" : "Not found");
}
}

Expand Down
5 changes: 3 additions & 2 deletions routes/respec/builds/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { mkdir, readFile } from "node:fs/promises";

import type { Request, Response } from "express";

import { env } from "../../../utils/misc.js";
import { env, HTTPError } from "../../../utils/misc.js";
import sh from "../../../utils/sh.js";

export const PKG_DIR = path.join(env("DATA_DIR"), "respec", "package");
Expand All @@ -25,7 +25,8 @@ export default async function route(req: Request, res: Response) {
await pullRelease();
res.sendStatus(200); // ok
} catch (error) {
const { message = "", statusCode = 500 } = error;
const message = error instanceof Error ? error.message : String(error);
Comment thread
marcoscaceres marked this conversation as resolved.
const statusCode = error instanceof HTTPError ? error.statusCode : 500;
console.error(`Failed to pull respec release: ${message.slice(0, 400)}...`);
res.status(statusCode);
res.send(message);
Expand Down
8 changes: 5 additions & 3 deletions routes/w3c/group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ export default async function route(req: IRequest, res: Response) {
res.set("Cache-Control", `max-age=${seconds("24h")}`);
res.json(groupInfo);
} catch (error) {
const { statusCode = 500, message } = error;
const statusCode = error instanceof HTTPError ? error.statusCode : 500;
const message = error instanceof Error ? error.message : String(error);
res.set("Content-Type", "text/plain");
res.status(statusCode).send(message);
}
Expand Down Expand Up @@ -117,9 +118,10 @@ async function fetchGroupInfo(
}
var json = (await res.json()) as APIResponse;
} catch (error) {
if (error instanceof HTTPError) throw error;
throw new HTTPError(
error.statusCode || 500,
error.message
500,
error instanceof Error ? error.message : String(error),
Comment thread
sidvishnoi marked this conversation as resolved.
);
}

Expand Down
2 changes: 1 addition & 1 deletion routes/xref/lib/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export function searchOne(

function normalizeQuery(query: Query, options: Options) {
if (Array.isArray(query.specs) && !Array.isArray(query.specs[0])) {
// @ts-ignore
// @ts-expect-error - backward compatibility: wrapping flat specs array
query.specs = [query.specs]; // for backward compatibility
}
if (!Array.isArray(query.types) || !query.types.length) {
Expand Down
6 changes: 3 additions & 3 deletions routes/xref/lib/store.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import path from "path";
import { readFileSync } from "fs";

import { env } from "../../../utils/misc.js";
import { env, getErrnoCode } from "../../../utils/misc.js";
import { DataEntry } from "./search.js";
import { HeadingEntry, HeadingsBySpec } from "./scraper.js";

Expand Down Expand Up @@ -94,8 +94,8 @@ function readJson(filename: string) {
function readJsonOptional(filename: string) {
try {
return readJson(filename);
} catch (err: any) {
if (err?.code === "ENOENT") {
} catch (err: unknown) {
if (getErrnoCode(err) === "ENOENT") {
console.warn(`Optional data file not found: ${filename}`);
return {};
}
Comment thread
marcoscaceres marked this conversation as resolved.
Expand Down
2 changes: 1 addition & 1 deletion routes/xref/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { cache as searchCache } from "./lib/search.js";
import { store } from "./lib/store-init.js";

const workerFile = path.join(import.meta.dirname, "update.worker.js");
const taskQueue = new BackgroundTaskQueue<typeof import("./update.worker")>(
const taskQueue = new BackgroundTaskQueue<typeof import("./update.worker.ts")>(
workerFile,
"xref_update",
);
Expand Down
38 changes: 38 additions & 0 deletions tests/utils/misc.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { getErrnoCode } from "../../build/utils/misc.js";

describe("utils/misc", () => {
describe("getErrnoCode", () => {
it("returns the string code from an Error with a code property", () => {
const err = Object.assign(new Error("ENOENT"), { code: "ENOENT" });
expect(getErrnoCode(err)).toBe("ENOENT");
});

it("returns the string code from a plain object with a code property", () => {
expect(getErrnoCode({ code: "EACCES" })).toBe("EACCES");
});

it("returns undefined when the code property is not a string", () => {
expect(getErrnoCode({ code: 42 })).toBeUndefined();
});

it("returns undefined for an Error without a code property", () => {
expect(getErrnoCode(new Error("oops"))).toBeUndefined();
});

it("returns undefined for null", () => {
expect(getErrnoCode(null)).toBeUndefined();
});

it("returns undefined for undefined", () => {
expect(getErrnoCode(undefined)).toBeUndefined();
});

it("returns undefined for a string", () => {
expect(getErrnoCode("ENOENT")).toBeUndefined();
});

it("returns undefined for a number", () => {
expect(getErrnoCode(404)).toBeUndefined();
});
});
});
4 changes: 2 additions & 2 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
"target": "ESNext",
"removeComments": false,
"allowSyntheticDefaultImports": true,
"moduleResolution": "node",
"moduleResolution": "nodenext",
"noImplicitThis": true,
"strictNullChecks": true,
"noImplicitAny": true,
"module": "esnext",
"module": "nodenext",
"outDir": "build",
"sourceMap": true,
"resolveJsonModule": true
Expand Down
6 changes: 4 additions & 2 deletions utils/background-task-queue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ class Logger {
id: string;
input: unknown;
timings: Record<string, Date> = {};
result: { type: "success" | "failure"; value: unknown };
result!: { type: "success" | "failure"; value: unknown };

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this do?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ! is TypeScript's definite assignment assertion. It tells the compiler this property will be assigned before it's read, even though the constructor doesn't assign it. Without it, TS6 with strictPropertyInitialization (implied by strict) reports an error because result has no initializer and isn't set in the constructor. It's safe here because setResult() (line 99) is always called before result is read.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIL!

stdout: [date: Date, line: string][] = [];
stderr: [date: Date, line: string][] = [];

Expand Down Expand Up @@ -118,7 +118,9 @@ class Logger {
}
}

type TaskModule = { default: (input?: unknown) => unknown };
// Method shorthand syntax enables bivariant parameter checking so concrete
// worker input types (e.g. `{ webhookId: string }`) satisfy this constraint.
type TaskModule = { default(input?: unknown): unknown };
/**
* Create a worker thread to run a task in background. A task/job added to the
* queue with `add()`, and run serially in order of calling `run()` on the job
Expand Down
4 changes: 2 additions & 2 deletions utils/disk-cache.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import path from "path";
import { mkdir, readFile, unlink, writeFile } from "fs/promises";

import { env } from "./misc.js";
import { env, getErrnoCode } from "./misc.js";
import { MemCache } from "./mem-cache.js";

interface CacheEntry<V> {
Expand Down Expand Up @@ -86,7 +86,7 @@ export class DiskCache<ValueType> {
const text = await readFile(fileName, "utf-8");
return JSON.parse(text) as CacheEntry<ValueType>;
} catch (error) {
if (error.code !== "ENOENT") {
if (getErrnoCode(error) !== "ENOENT") {
console.error(error);
}
}
Expand Down
12 changes: 12 additions & 0 deletions utils/misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,18 @@ export function ms(duration: string) {
return seconds(duration) * 1000;
}

/**
* Extract the `code` property from a thrown value, or return `undefined`.
* Safe to call with any `unknown` catch value (strings, plain objects, etc.).
*/
export function getErrnoCode(err: unknown): string | undefined {
if (typeof err === "object" && err !== null && "code" in err) {
const { code } = err as NodeJS.ErrnoException;
return typeof code === "string" ? code : undefined;
}
return undefined;
}

export class HTTPError extends Error {
constructor(public statusCode: number, message: string, public url?: string) {
super(message);
Expand Down
Loading
Loading