diff --git a/.changeset/free-numbers-relate.md b/.changeset/free-numbers-relate.md new file mode 100644 index 000000000..edc2cce94 --- /dev/null +++ b/.changeset/free-numbers-relate.md @@ -0,0 +1,5 @@ +--- +"counterfact": patch +--- + +Warn and skip loading context modules when they fail to import, including syntax errors in \_.context files. diff --git a/docs/features/state.md b/docs/features/state.md index 29b06507c..69abdd7e1 100644 --- a/docs/features/state.md +++ b/docs/features/state.md @@ -38,6 +38,8 @@ export class Context { > [!IMPORTANT] > Keep context in memory. Counterfact is a development tool — starting fresh each time is a feature, not a bug. In-memory state also makes the server very fast. +> +> If a `_.context.ts` file has a syntax/import error, Counterfact prints a warning and skips loading that context file so the app keeps running. ## Nested contexts diff --git a/src/server/module-loader.ts b/src/server/module-loader.ts index 36280e22b..31d2bb3ef 100644 --- a/src/server/module-loader.ts +++ b/src/server/module-loader.ts @@ -302,6 +302,16 @@ export class ModuleLoader extends EventTarget { unescapePathForWindows(pathName), ); + if (this.isContextFile(pathName)) { + const warning = isSyntaxError + ? `Warning: There is a syntax error in the context file: ${displayPath}` + : `Warning: There was an error loading the context file: ${displayPath}`; + + process.stdout.write(`\n${warning}\n`); + + return; + } + const message = isSyntaxError ? `There is a syntax error in the route file: ${displayPath}` : `There was an error loading the route file: ${displayPath}`; diff --git a/test/server/module-loader.test.ts b/test/server/module-loader.test.ts index 5e8ff1155..5c9494d2a 100644 --- a/test/server/module-loader.test.ts +++ b/test/server/module-loader.test.ts @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { once } from "node:events"; +import { jest } from "@jest/globals"; import { usingTemporaryFiles } from "using-temporary-files"; import { ContextRegistry } from "../../src/server/context-registry.js"; @@ -365,6 +366,45 @@ describe("a module loader", () => { expect(response?.body).toContain("syntax error"); }); }); + + it("prints a warning and skips loading a context file with a syntax error", async () => { + await usingTemporaryFiles(async ($) => { + await $.add("_.context.js", "this is not valid javascript @@@"); + await $.add( + "hello.js", + 'export function GET() { return { body: "hello" }; }', + ); + await $.add("package.json", '{ "type": "module" }'); + + const registry: Registry = new Registry(); + const contextRegistry: ContextRegistry = new ContextRegistry(); + const loader: ModuleLoader = new ModuleLoader( + $.path(""), + registry, + contextRegistry, + ); + + const stdoutSpy = jest + .spyOn(process.stdout, "write") + .mockImplementation((() => true) as any); + + await expect(loader.load()).resolves.toBeUndefined(); + + expect(registry.exists("GET", "/hello")).toBe(true); + expect(contextRegistry.getAllPaths()).toEqual(["/"]); + expect(stdoutSpy).toHaveBeenCalledWith( + expect.stringContaining( + "Warning: There was an error loading the context file:", + ), + ); + expect(stdoutSpy).toHaveBeenCalledWith( + expect.stringContaining("_.context.js"), + ); + expect(stdoutSpy).toHaveBeenCalledTimes(1); + + stdoutSpy.mockRestore(); + }); + }); }); describe("ModuleLoader scenario loading", () => {