Skip to content

Commit 2cdb0e5

Browse files
committed
Add script to dynamically generate wrangler configs in dev mode.
1 parent 1e84f22 commit 2cdb0e5

6 files changed

Lines changed: 135 additions & 41 deletions

File tree

README.md

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ This is a demonstration of an agent harness and vibe coding environment built en
1717
2. Run `pnpm install`.
1818
3. Run `pnpm build`.
1919
4. Start the server: `pnpm run dev-server`
20+
* To enable Workers AI models, use `pnpm run dev-server -- --use-workers-ai-binding`, but this will require Cloudflare login.
2021
5. Start the client: `pnpm run dev-client`
2122
6. Visit `localhost:3000`
2223
7. Create an account and log in.
@@ -38,8 +39,3 @@ To enable support for external APIs, you must do further configuration to regist
3839
* [Google API](packages/gatekeeper-google/README.md)
3940
* [Email Workers](packages/gatekeeper-email/README.md)
4041

41-
# FAQ
42-
43-
Q: Why do I have to log in to Cloudflare to run locally?
44-
45-
A: This is only for Workers AI. If you don't care about using Workers AI models and don't want to log in, edit packages/workshop-backend/wrangler.jsonc and comment out the WORKERS_AI binding.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@
22
"name": "gadgets",
33
"version": "1.0.0",
44
"private": true,
5+
"type": "module",
56
"scripts": {
67
"build": "pnpm run --recursive build",
78
"test": "pnpm run --recursive --if-present test",
89
"dev-client": "cd packages/workshop-frontend && pnpm run dev",
9-
"dev-server": "wrangler dev -c wrangler.jsonc -c packages/workshop-backend/wrangler.jsonc -c packages/gatekeeper-google/wrangler.jsonc -c packages/gatekeeper-github/wrangler.jsonc -c packages/gatekeeper-email/wrangler.jsonc",
10+
"dev-server": "node run-dev-server.js",
1011
"clean": "pnpm run --recursive clean"
1112
},
1213
"devDependencies": {
14+
"jsonc-parser": "^3.3.1",
1315
"typescript": "^5.9.3",
1416
"wrangler": "^4.82.2"
1517
}

packages/workshop-backend/wrangler.jsonc

Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,11 @@
44
"main": "src/server.ts",
55
"compatibility_date": "2026-02-02",
66
"compatibility_flags": ["experimental", "allow_irrevocable_stub_storage"],
7-
"services": [
8-
{
9-
"binding": "GATEKEEPER_GOOGLE",
10-
"service": "gatekeeper-google",
11-
"entrypoint": "GatekeeperVendor"
12-
},
13-
{
14-
"binding": "GATEKEEPER_EMAIL",
15-
"service": "gatekeeper-email",
16-
"entrypoint": "GatekeeperVendor"
17-
},
18-
{
19-
"binding": "GATEKEEPER_GITHUB",
20-
"service": "gatekeeper-github",
21-
"entrypoint": "GatekeeperVendor"
22-
}
23-
],
7+
8+
// Gatekeeper service bindings and the Workers AI binding are dynamically
9+
// added by run-dev-server.js (for dev) and generate-wrangler-prod.js (for
10+
// production).
11+
2412
"migrations": [
2513
{
2614
"tag": "v0",
@@ -49,12 +37,6 @@
4937
}
5038
],
5139

52-
// Comment out this binding if you don't want to log in to Cloudflare and don't care to use
53-
// Workers AI.
54-
"ai": {
55-
"binding": "WORKERS_AI"
56-
},
57-
5840
// When deploying to prod, you may want to bundle the frontend as assets on the backend, like so.
5941
// But it's not the only way to do it.
6042
//

pnpm-lock.yaml

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

run-dev-server.js

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
#!/usr/bin/env node
2+
3+
// Generates wrangler.dev.jsonc files for the dev-router and workshop-backend
4+
// workers (with dynamically-discovered gatekeeper service bindings), then
5+
// launches `wrangler dev` with all discovered workers.
6+
//
7+
// Flags:
8+
// --use-workers-ai-binding Include the Workers AI binding in
9+
// workshop-backend (requires Cloudflare login).
10+
11+
import { readFileSync, writeFileSync, readdirSync, statSync } from "node:fs";
12+
import { execFileSync } from "node:child_process";
13+
import { join, dirname } from "node:path";
14+
import { fileURLToPath } from "node:url";
15+
import { parse } from "jsonc-parser";
16+
17+
const ROOT = dirname(fileURLToPath(import.meta.url));
18+
const PACKAGES_DIR = join(ROOT, "packages");
19+
20+
const useWorkersAi = process.argv.includes("--use-workers-ai-binding");
21+
22+
// ---------------------------------------------------------------------------
23+
// Discover gatekeeper packages.
24+
// ---------------------------------------------------------------------------
25+
function findGatekeepers(parentDir) {
26+
try {
27+
return readdirSync(parentDir)
28+
.filter(name => name.startsWith("gatekeeper-"))
29+
.filter(name => {
30+
try {
31+
return statSync(join(parentDir, name, "wrangler.jsonc")).isFile();
32+
} catch {
33+
return false;
34+
}
35+
});
36+
} catch {
37+
return [];
38+
}
39+
}
40+
41+
const gatekeepers = findGatekeepers(PACKAGES_DIR);
42+
43+
// Helper: "gatekeeper-github" -> "GATEKEEPER_GITHUB"
44+
function bindingName(pkg) {
45+
return pkg.toUpperCase().replaceAll("-", "_");
46+
}
47+
48+
// ---------------------------------------------------------------------------
49+
// Generate wrangler.dev.jsonc (dev-router with gatekeeper service bindings).
50+
// ---------------------------------------------------------------------------
51+
{
52+
const srcPath = join(ROOT, "wrangler.jsonc");
53+
const config = parse(readFileSync(srcPath, "utf8"));
54+
55+
config.services = config.services || [];
56+
for (const gk of gatekeepers) {
57+
config.services.push({ binding: bindingName(gk), service: gk });
58+
}
59+
60+
const outPath = join(ROOT, "wrangler.dev.jsonc");
61+
writeFileSync(outPath, JSON.stringify(config, null, 2) + "\n");
62+
console.log(`generated: ${outPath}`);
63+
}
64+
65+
// ---------------------------------------------------------------------------
66+
// Generate packages/workshop-backend/wrangler.dev.jsonc (with gatekeeper
67+
// service bindings using the GatekeeperVendor entrypoint).
68+
// ---------------------------------------------------------------------------
69+
{
70+
const srcPath = join(ROOT, "packages", "workshop-backend", "wrangler.jsonc");
71+
const config = parse(readFileSync(srcPath, "utf8"));
72+
73+
config.services = config.services || [];
74+
75+
// For local testing, create an account named "admin" to test admin features.
76+
config.vars = config.vars || {};
77+
config.vars.ADMINS = ["admin"];
78+
79+
for (const gk of gatekeepers) {
80+
config.services.push({
81+
binding: bindingName(gk),
82+
service: gk,
83+
entrypoint: "GatekeeperVendor",
84+
});
85+
}
86+
87+
if (useWorkersAi) {
88+
config.ai = { binding: "WORKERS_AI" };
89+
}
90+
91+
const outPath = join(ROOT, "packages", "workshop-backend", "wrangler.dev.jsonc");
92+
writeFileSync(outPath, JSON.stringify(config, null, 2) + "\n");
93+
console.log(`generated: ${outPath}`);
94+
}
95+
96+
// ---------------------------------------------------------------------------
97+
// Build the wrangler dev command and exec it.
98+
// ---------------------------------------------------------------------------
99+
100+
const configs = [
101+
"wrangler.dev.jsonc",
102+
join("packages", "workshop-backend", "wrangler.dev.jsonc"),
103+
...gatekeepers.map(gk => join("packages", gk, "wrangler.jsonc")),
104+
];
105+
106+
const args = configs.flatMap(c => ["-c", c]);
107+
console.log(`\nStarting: wrangler dev ${args.join(" ")}\n`);
108+
109+
try {
110+
execFileSync("pnpm", ["exec", "wrangler", "dev", ...args],
111+
{ stdio: "inherit", cwd: ROOT });
112+
} catch (e) {
113+
// wrangler was killed or exited with an error; the output was already shown
114+
// via stdio: "inherit", so just propagate the exit code.
115+
process.exit(e.status ?? 1);
116+
}

wrangler.jsonc

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,12 @@
33
"main": "dev-router.js",
44
"compatibility_date": "2025-11-01",
55
"compatibility_flags": ["enable_ctx_exports"],
6+
7+
// Gatekeeper service bindings are dynamically added by run-dev-server.js.
68
"services": [
79
{
810
"binding": "WORKSHOP_BACKEND",
911
"service": "workshop-backend"
10-
},
11-
{
12-
"binding": "GATEKEEPER_GOOGLE",
13-
"service": "gatekeeper-google"
14-
},
15-
{
16-
"binding": "GATEKEEPER_GITHUB",
17-
"service": "gatekeeper-github"
18-
},
19-
{
20-
"binding": "GATEKEEPER_EMAIL",
21-
"service": "gatekeeper-email"
2212
}
2313
]
2414
}

0 commit comments

Comments
 (0)