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
5 changes: 5 additions & 0 deletions .changeset/free-numbers-relate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"counterfact": patch
---

Warn and skip loading context modules when they fail to import, including syntax errors in \_.context files.
2 changes: 2 additions & 0 deletions docs/features/state.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
10 changes: 10 additions & 0 deletions src/server/module-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`;
Expand Down
40 changes: 40 additions & 0 deletions test/server/module-loader.test.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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", () => {
Expand Down
Loading