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
14 changes: 14 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,18 @@ jobs:
# so it passes here without a .dev.vars (which is gitignored and never in CI).
- run: pnpm -r test

# The web build hard-fails without VITE_API_BASE (see packages/web/vite.config.ts).
# Supply a throwaway value — CI only verifies the build compiles; admin/worker ignore it.
- run: pnpm -r build
env:
VITE_API_BASE: https://example.invalid

# Regression guard: the web build MUST refuse to build without VITE_API_BASE, so a fork
# can never ship an upload page that white-screens at load (no VITE_API_BASE in this step).
- name: web build must fail without VITE_API_BASE
run: |
if pnpm --filter @chippot/web build; then
echo "::error::web build succeeded without VITE_API_BASE — the fail-fast guard is broken"
exit 1
fi
echo "OK: web build correctly refused to build without VITE_API_BASE"
16 changes: 13 additions & 3 deletions packages/web/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
import { defineConfig } from "vite";
import { defineConfig, loadEnv } from "vite";
import react from "@vitejs/plugin-react";

export default defineConfig({
plugins: [react()],
// VITE_API_BASE is statically inlined at build time and points the upload page at the user's
// own worker. If it's missing, `vite build` still succeeds and emits a bundle that throws at
// load (white screen) — invisible to CI's `pnpm -r build`. Fail the BUILD instead so a fork
// finds out immediately. (dev is exempt: `vite dev` can run against a proxy/placeholder.)
export default defineConfig(({ command, mode }) => {
if (command === "build" && !loadEnv(mode, process.cwd(), "VITE_").VITE_API_BASE) {
throw new Error(
"VITE_API_BASE is required for the web build. Point it at your worker, e.g.\n" +
" VITE_API_BASE=https://chippot.<your-subdomain>.workers.dev pnpm --filter @chippot/web build"
);
}
return { plugins: [react()] };
});
6 changes: 6 additions & 0 deletions packages/worker/test/apply-migrations.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
import { applyD1Migrations, env } from "cloudflare:test";

// Scrub external secrets that a local `.dev.vars` would otherwise inject (CI has none), so the
// test baseline is identical on every machine. Tests that exercise outbound calls set their own
// dummy token + stub fetch; leaving a real token here would make those paths hit Discord for real
// locally while CI takes the no-send branch. Keep in sync with outbound-gated secrets.
delete (env as { DISCORD_BOT_TOKEN?: string }).DISCORD_BOT_TOKEN;

await applyD1Migrations(env.DB, env.TEST_MIGRATIONS);
9 changes: 9 additions & 0 deletions packages/worker/test/smoke.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,13 @@ describe("test harness", () => {
expect(env.DB).toBeDefined();
expect(env.BUCKET).toBeDefined();
});

// A local `.dev.vars` (gitignored) supplies a REAL DISCORD_BOT_TOKEN, but CI has none.
// If that token leaked into the test baseline, any code path gated on it (e.g.
// billing/initiate → sendBillingOpened) would make a REAL Discord fetch locally while
// CI silently takes the no-send branch — tests would behave differently per machine.
// The setup file scrubs it; tests that exercise sending set their own dummy token.
it("does not leak external secrets from .dev.vars into the test baseline", () => {
expect(env.DISCORD_BOT_TOKEN).toBeUndefined();
});
});