Skip to content

Commit 8f7604d

Browse files
marcusbellamyshaw-cellclaudeascorbicemdashbot[bot]
authored
fix(plugins): hoist id/version regexes into defineNativePlugin to avoid TDZ on Workers (#1370) (#1542)
* fix(plugins): hoist id/version regexes into defineNativePlugin to avoid TDZ on Workers Module-scope regex consts (SIMPLE_ID/SCOPED_ID/SEMVER_PATTERN) could be read before initialization during the worker's circular module init under ssr.noExternal, throwing "Cannot access 'SIMPLE_ID' before initialization" and 500-ing every route on Cloudflare Workers. Moving them function-local makes them evaluate at call time, eliminating the temporal dead zone regardless of bundle ordering. Closes #1370 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Update .changeset/fix-define-plugin-tdz.md Co-authored-by: emdashbot[bot] <273199577+emdashbot[bot]@users.noreply.github.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Matt Kane <m@mk.gg> Co-authored-by: emdashbot[bot] <273199577+emdashbot[bot]@users.noreply.github.com>
1 parent 971c627 commit 8f7604d

3 files changed

Lines changed: 37 additions & 5 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"emdash": patch
3+
---
4+
5+
Fixes a crash that returned HTTP 500 on every route on Cloudflare Workers when native plugins are registered with `definePlugin` (`Cannot access 'SIMPLE_ID' before initialization`). Plugin registration is now safe regardless of bundle ordering.

packages/core/src/plugins/define-plugin.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,6 @@ import type {
2222
PluginStorageConfig,
2323
} from "./types.js";
2424

25-
// Plugin ID validation patterns
26-
const SIMPLE_ID = /^[a-z0-9-]+$/;
27-
const SCOPED_ID = /^@[a-z0-9-]+\/[a-z0-9-]+$/;
28-
const SEMVER_PATTERN = /^\d+\.\d+\.\d+/;
29-
3025
/**
3126
* Define a native EmDash plugin.
3227
*
@@ -94,6 +89,20 @@ export function definePlugin<TStorage extends PluginStorageConfig>(
9489
function defineNativePlugin<TStorage extends PluginStorageConfig>(
9590
definition: PluginDefinition<TStorage>,
9691
): ResolvedPlugin<TStorage> {
92+
// Declared function-local (not module scope) on purpose. Under
93+
// `ssr.noExternal` the worker entry can instantiate native plugins during a
94+
// circular module init, reaching this function before module-scope consts
95+
// initialize -> "Cannot access 'SIMPLE_ID' before initialization" -> every
96+
// route 500s on Cloudflare Workers. Call-time consts evaluate after the
97+
// literals are parsed, so the temporal dead zone cannot occur regardless of
98+
// bundle ordering. See #1370.
99+
// oxlint-disable-next-line e18e/prefer-static-regex -- call-time on purpose (see #1370)
100+
const SIMPLE_ID = /^[a-z0-9-]+$/;
101+
// oxlint-disable-next-line e18e/prefer-static-regex -- call-time on purpose (see #1370)
102+
const SCOPED_ID = /^@[a-z0-9-]+\/[a-z0-9-]+$/;
103+
// oxlint-disable-next-line e18e/prefer-static-regex -- call-time on purpose (see #1370)
104+
const SEMVER_PATTERN = /^\d+\.\d+\.\d+/;
105+
97106
const {
98107
id,
99108
version,

packages/core/tests/unit/plugins/define-plugin.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,24 @@ describe("definePlugin", () => {
161161
});
162162
});
163163

164+
// Regression: #1370 — the id/version validation patterns must stay
165+
// function-local (evaluated at call time). As module-scope consts, a
166+
// circular module init on Cloudflare Workers could reach defineNativePlugin
167+
// before they initialized, throwing "Cannot access 'SIMPLE_ID' before
168+
// initialization" and 500-ing every route. Validation must keep working.
169+
describe("#1370 — call-time validation regexes", () => {
170+
it("validates id and version on every call", () => {
171+
expect(definePlugin({ id: "first", version: "1.0.0" }).id).toBe("first");
172+
expect(definePlugin({ id: "@scope/second", version: "2.3.4" }).version).toBe("2.3.4");
173+
expect(() => definePlugin({ id: "Bad_Id", version: "1.0.0" })).toThrow(
174+
INVALID_PLUGIN_ID_PATTERN,
175+
);
176+
expect(() => definePlugin({ id: "ok", version: "nope" })).toThrow(
177+
INVALID_PLUGIN_VERSION_PATTERN,
178+
);
179+
});
180+
});
181+
164182
describe("capability validation", () => {
165183
it("accepts valid capabilities", () => {
166184
const plugin = definePlugin({

0 commit comments

Comments
 (0)