fix: validate sandbox plugin exports and fix plugin packaging (#363)

* fix(webhook-notifier): add build step and export built files

The webhook-notifier plugin exported raw TypeScript source from its
package.json exports (./sandbox pointed to src/sandbox-entry.ts).
When the Vite plugin resolved this at site build time, it embedded
unbuilt TypeScript into the sandbox module, causing "Unexpected token
'{'" errors at runtime.

Add a tsdown build step (matching sandboxed-test's pattern) and update
the exports map to point to dist/*.mjs.

Fixes #150

* fix(core): reject unbuilt source in sandbox module generator and bundle validator

Add two validation checks to prevent plugins with misconfigured exports
from silently breaking site builds:

1. generateSandboxedPluginsModule() now throws a clear error if a
   sandbox entrypoint resolves to a TypeScript/JSX source file instead
   of pre-built JavaScript. This catches the problem at site build time
   with an actionable message.

2. The `emdash bundle` command now validates that all package.json
   exports point to built files (.js/.mjs), not source (.ts/.tsx/.jsx).
   This catches the misconfiguration at plugin publish time, before
   consumers are affected.

Fixes #150

* chore: add changeset for sandbox source validation

* fix: use slash syntax for e18e rule override in oxlintrc

The test file override for e18e/prefer-static-regex used parenthesis
syntax ("e18e(prefer-static-regex)") which is the diagnostic display
format, not the config format. Changed to slash syntax to match the
top-level rule declarations so the override actually takes effect.

* test: add tests for sandbox source validation

Add tests for both validation checks:

- generateSandboxedPluginsModule: verifies it embeds pre-built JS,
  rejects .ts/.tsx/.mts source files, and includes the plugin ID in
  error messages.

- findSourceExports: verifies it flags .ts/.tsx/.mts/.cts/.jsx exports,
  accepts .mjs/.js exports, and handles conditional export maps.

Also extracts findSourceExports() from the inline bundle.ts validation
into bundle-utils.ts so it can be tested without the CLI harness.

* fix(atproto, audit-log): add build step and export built files

Same issue as webhook-notifier — both plugins exported raw TypeScript
source from their package.json sandbox exports. Add tsdown build steps
and update exports to point to dist/*.mjs.

* refactor(smoke): replace sequential per-site astro builds with recursive pnpm build

The build verification section was running `astro build` individually
and sequentially for every demo and template (~12 sites). Replace with
a single `pnpm run --recursive --filter {./demos/*} --filter
{./templates/*} build` which pnpm parallelizes automatically.
This commit is contained in:
Matt Kane
2026-04-07 22:34:35 +01:00
committed by GitHub
parent 885df3b3a8
commit 91e31fb2ca
12 changed files with 321 additions and 48 deletions

View File

@@ -181,39 +181,37 @@ async function fetchWithRetry(url: string, retries = 10, delayMs = 1500): Promis
}
// ---------------------------------------------------------------------------
// Build verification — runs `astro build` for every site to catch adapter
// and bundling errors that dev mode doesn't surface.
// Build verification — runs a single recursive `pnpm build` across all demos
// and templates in parallel, then verifies each site produced output.
// ---------------------------------------------------------------------------
describe.sequential("Site build verification", () => {
const BUILD_TIMEOUT = 120_000;
describe("Site build verification", () => {
it("all demos and templates build successfully", { timeout: 300_000 }, async () => {
await ensureBuilt();
for (const site of SITE_MATRIX) {
if (site.mode === "typecheck") continue;
it(`${site.name} builds successfully`, { timeout: BUILD_TIMEOUT + 30_000 }, async () => {
await ensureBuilt();
try {
await execAsync("pnpm", ["exec", "astro", "build"], {
cwd: site.dir,
timeout: BUILD_TIMEOUT,
try {
await execAsync(
"pnpm",
["run", "--recursive", "--filter", "{./demos/*}", "--filter", "{./templates/*}", "build"],
{
cwd: WORKSPACE_ROOT,
timeout: 240_000,
env: {
...process.env,
CI: "true",
},
});
} catch (error) {
const stderr =
error instanceof Error && "stderr" in error ? (error as { stderr: string }).stderr : "";
const stdout =
error instanceof Error && "stdout" in error ? (error as { stdout: string }).stdout : "";
throw new Error(`${site.name} build failed:\n\n${stderr || stdout}`.slice(0, 5000), {
cause: error,
});
}
});
}
},
);
} catch (error) {
const stderr =
error instanceof Error && "stderr" in error ? (error as { stderr: string }).stderr : "";
const stdout =
error instanceof Error && "stdout" in error ? (error as { stdout: string }).stdout : "";
throw new Error(`Site builds failed:\n\n${stderr || stdout}`.slice(0, 5000), {
cause: error,
});
}
});
});
// ---------------------------------------------------------------------------