supabase-worker-previews 0.0.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +143 -1
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +152 -0
  5. package/dist/cloudflare.d.ts +37 -0
  6. package/dist/cloudflare.js +95 -0
  7. package/dist/commands/branches.d.ts +62 -0
  8. package/dist/commands/branches.js +273 -0
  9. package/dist/commands/doctor.d.ts +24 -0
  10. package/dist/commands/doctor.js +289 -0
  11. package/dist/commands/init.d.ts +11 -0
  12. package/dist/commands/init.js +132 -0
  13. package/dist/commands/pr.d.ts +37 -0
  14. package/dist/commands/pr.js +78 -0
  15. package/dist/commands/prune.d.ts +32 -0
  16. package/dist/commands/prune.js +110 -0
  17. package/dist/config.d.ts +50 -0
  18. package/dist/config.js +105 -0
  19. package/dist/feedback.d.ts +64 -0
  20. package/dist/feedback.js +238 -0
  21. package/dist/github.d.ts +33 -0
  22. package/dist/github.js +78 -0
  23. package/dist/http.d.ts +7 -0
  24. package/dist/http.js +44 -0
  25. package/dist/jsonc.d.ts +2 -0
  26. package/dist/jsonc.js +56 -0
  27. package/dist/preview-name.d.ts +3 -0
  28. package/dist/preview-name.js +15 -0
  29. package/dist/run.d.ts +21 -0
  30. package/dist/run.js +59 -0
  31. package/dist/runtime.d.ts +56 -0
  32. package/dist/runtime.js +121 -0
  33. package/dist/style.d.ts +8 -0
  34. package/dist/style.js +21 -0
  35. package/dist/suggest.d.ts +2 -0
  36. package/dist/suggest.js +22 -0
  37. package/dist/supabase.d.ts +78 -0
  38. package/dist/supabase.js +168 -0
  39. package/dist/usage.d.ts +46 -0
  40. package/dist/usage.js +297 -0
  41. package/package.json +84 -9
  42. package/templates/default-privileges.sql +9 -0
  43. package/templates/supabase-previews-action.yml +29 -0
  44. package/templates/supabase-previews.yml +38 -0
package/dist/usage.js ADDED
@@ -0,0 +1,297 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { DOCS_URL, TOKENS_URL } from "./http.js";
5
+ import { parseArgs } from "./run.js";
6
+ import { didYouMean } from "./suggest.js";
7
+ import { makeStyle } from "./style.js";
8
+ const TOKENS = ["SUPABASE_ACCESS_TOKEN", "CLOUDFLARE_API_TOKEN", "CLOUDFLARE_ACCOUNT_ID"];
9
+ const WITH_GITHUB = [...TOKENS, "GITHUB_TOKEN"];
10
+ const DRY_RUN = { name: "dry-run", help: "Print the plan; change nothing" };
11
+ const OVERRIDES = [
12
+ { name: "dotenv", value: "<path>", help: "Load tokens from this file (default: .env.swp when present)" },
13
+ { name: "worker", value: "<name>", help: 'Worker name (default: "name" in the wrangler config)' },
14
+ { name: "project-ref", value: "<ref>", help: "Production project ref (default: swp.config.json)" },
15
+ { name: "trunk", value: "<branch>", help: "Branch the shared database tracks (default: swp.config.json)" },
16
+ ];
17
+ const BRANCH = [
18
+ { name: "branch", value: "<b>", help: "Git branch (default: the current one, or the CI branch)" },
19
+ { name: "pr", value: "<n>", help: 'Pull request number; needed when previewName is "pr"' },
20
+ ];
21
+ const HELP = [
22
+ { name: "help", help: "Show help" },
23
+ { name: "version", help: "Print the version" },
24
+ ];
25
+ export const COMMANDS = [
26
+ {
27
+ name: "init",
28
+ group: "Set up",
29
+ args: "[--project-ref <ref>] [--trunk <branch>] [--action]",
30
+ summary: "Scaffold swp.config.json, the grants migration and the PR workflow",
31
+ details: "Writes swp.config.json, a first migration that grants the API roles default privileges, and\n" +
32
+ ".github/workflows/supabase-previews.yml. Never overwrites a file; run it again safely.",
33
+ flags: [
34
+ { name: "project-ref", value: "<ref>", help: "Production project ref (default: from `supabase link`)" },
35
+ {
36
+ name: "trunk",
37
+ value: "<branch>",
38
+ help: "Branch the shared database tracks (default: origin/HEAD, else main)",
39
+ },
40
+ {
41
+ name: "action",
42
+ help: "Write a workflow that uses the published GitHub Action instead of npx supabase-worker-previews",
43
+ },
44
+ { ...DRY_RUN, help: "Print what it would write; write nothing" },
45
+ ],
46
+ env: [],
47
+ dryRunEnv: [],
48
+ examples: ["swp init --project-ref abcdefghijklmnopqrst", "swp init --action"],
49
+ },
50
+ {
51
+ name: "doctor",
52
+ group: "Set up",
53
+ args: "",
54
+ summary: "Check the setup: local files, and the Supabase project when a token is set",
55
+ details: "Checks wrangler, the previews block, bindings, static HTML, the grants migration and auth\n" +
56
+ "redirects. With SUPABASE_ACCESS_TOKEN it also checks branching, the shared Preview database\n" +
57
+ "and the GitHub integration. Exits 1 when any check fails.",
58
+ flags: OVERRIDES,
59
+ env: [],
60
+ dryRunEnv: [],
61
+ examples: ["swp doctor"],
62
+ },
63
+ {
64
+ name: "shared",
65
+ group: "Set up",
66
+ args: "",
67
+ summary: "Create or repair the shared Preview database; prints its previews.vars",
68
+ details: "Makes a persistent Supabase branch that tracks the trunk, waits for its migrations, sets its\n" +
69
+ "auth redirects, stores its secret key in the Preview base config, and prints the values to\n" +
70
+ "paste under previews.vars in the wrangler config.",
71
+ flags: [DRY_RUN, ...OVERRIDES],
72
+ env: TOKENS,
73
+ dryRunEnv: ["SUPABASE_ACCESS_TOKEN"],
74
+ examples: ["swp shared --dry-run", "swp shared"],
75
+ },
76
+ {
77
+ name: "up",
78
+ group: "Per branch",
79
+ args: "[--branch <b>] [--pr <n>]",
80
+ summary: "Give a branch's Preview its own database",
81
+ details: "Finds or creates the Supabase branch for the git branch, waits for its migrations, and points\n" +
82
+ "the branch's Preview at it with one SUPABASE_OVERRIDE secret.",
83
+ flags: [...BRANCH, DRY_RUN, ...OVERRIDES],
84
+ env: TOKENS,
85
+ dryRunEnv: [],
86
+ examples: ["swp up --branch feat/notes --dry-run"],
87
+ },
88
+ {
89
+ name: "check",
90
+ group: "Per branch",
91
+ args: "[--branch <b>] [--pr <n>] [--isolated]",
92
+ summary: "Fail unless a branch's Preview serves the right database",
93
+ details: "Reads which database the deployed Preview serves and retries for up to 15 minutes while it\n" +
94
+ "deploys. Fails at once if it serves production.",
95
+ flags: [
96
+ ...BRANCH,
97
+ { name: "isolated", help: "Expect the branch's own database, not the shared one" },
98
+ DRY_RUN,
99
+ ...OVERRIDES,
100
+ ],
101
+ env: TOKENS,
102
+ dryRunEnv: [],
103
+ examples: ["swp check --branch feat/notes --isolated"],
104
+ },
105
+ {
106
+ name: "down",
107
+ group: "Per branch",
108
+ args: "[--branch <b>] [--pr <n>]",
109
+ summary: "Delete a branch's Preview and its own database",
110
+ details: "Never deletes the production project, the shared database or a persistent branch.",
111
+ flags: [...BRANCH, DRY_RUN, ...OVERRIDES],
112
+ env: TOKENS,
113
+ dryRunEnv: TOKENS,
114
+ examples: ["swp down --branch feat/notes --dry-run"],
115
+ },
116
+ {
117
+ name: "pr",
118
+ group: "In CI",
119
+ args: "[--no-comment] [--no-deployments]",
120
+ summary: "Run up, check and down for a pull_request workflow, and report on the PR",
121
+ details: "Reads the pull_request event from GITHUB_EVENT_PATH. On close it runs down; otherwise it gives\n" +
122
+ "the Preview its own database when the PR changes supabase/ (or has the isolated-db label),\n" +
123
+ "then checks it. Keeps one status comment and a GitHub deployment on the PR.",
124
+ flags: [
125
+ { name: "no-comment", help: "Skip the PR status comment" },
126
+ { name: "no-deployments", help: "Skip the GitHub deployment" },
127
+ DRY_RUN,
128
+ ...OVERRIDES,
129
+ ],
130
+ env: WITH_GITHUB,
131
+ dryRunEnv: WITH_GITHUB,
132
+ examples: ["swp pr"],
133
+ },
134
+ {
135
+ name: "prune",
136
+ group: "In CI",
137
+ args: "[--repo <owner/name>] [--yes]",
138
+ summary: "List leftovers of deleted branches and closed PRs; --yes deletes them",
139
+ details: "Lists Supabase branches and Previews whose git branch is gone or whose PR is closed.",
140
+ flags: [
141
+ {
142
+ name: "repo",
143
+ value: "<owner/name>",
144
+ help: "GitHub repo (default: GITHUB_REPOSITORY, else the origin remote)",
145
+ },
146
+ { name: "yes", help: "Delete what it lists" },
147
+ DRY_RUN,
148
+ ...OVERRIDES,
149
+ ],
150
+ env: WITH_GITHUB,
151
+ dryRunEnv: WITH_GITHUB,
152
+ examples: ["swp prune", "swp prune --yes"],
153
+ },
154
+ ];
155
+ const ALL_FLAGS = [...COMMANDS.flatMap((c) => c.flags), ...HELP];
156
+ export const BOOLEAN_FLAGS = new Set(ALL_FLAGS.filter((f) => !f.value).map((f) => f.name));
157
+ export function command(name) {
158
+ return COMMANDS.find((c) => c.name === name);
159
+ }
160
+ export function version() {
161
+ const path = join(dirname(fileURLToPath(import.meta.url)), "..", "package.json");
162
+ return JSON.parse(readFileSync(path, "utf8")).version;
163
+ }
164
+ function flagTable(flags, s) {
165
+ const label = (f) => (f.name === "help" ? "-h, " : f.name === "version" ? "-v, " : "") +
166
+ `--${f.name}${f.value ? ` ${f.value}` : ""}`;
167
+ const width = Math.max(...flags.map((f) => label(f).length));
168
+ return flags.map((f) => ` ${s.cyan(label(f).padEnd(width))} ${f.help}`);
169
+ }
170
+ export function mainHelp(s = makeStyle(false)) {
171
+ const width = Math.max(...COMMANDS.map((c) => c.name.length));
172
+ const groups = [...new Set(COMMANDS.map((c) => c.group))].flatMap((group) => [
173
+ "",
174
+ s.bold(group),
175
+ ...COMMANDS.filter((c) => c.group === group).map((c) => ` ${s.cyan(c.name.padEnd(width))} ${c.summary}`),
176
+ ]);
177
+ return [
178
+ `${s.bold("swp")} ${version()}: branch previews for Cloudflare Workers on Supabase branching`,
179
+ "",
180
+ `${s.bold("Usage:")} swp <command> [flags]`,
181
+ ...groups,
182
+ "",
183
+ s.bold("Common flags"),
184
+ ...flagTable([DRY_RUN, ...OVERRIDES, ...HELP], s),
185
+ "",
186
+ s.bold("Environment"),
187
+ ` ${TOKENS.join(", ")}, and GITHUB_TOKEN for pr and prune.`,
188
+ ` Where to get them: ${TOKENS_URL}`,
189
+ "",
190
+ s.bold("Examples"),
191
+ " npx supabase-worker-previews init --project-ref abcdefghijklmnopqrst",
192
+ " npx supabase-worker-previews doctor",
193
+ " npx supabase-worker-previews shared --dry-run",
194
+ "",
195
+ `Run ${s.cyan("swp <command> --help")} for one command. Docs: ${DOCS_URL}`,
196
+ ].join("\n");
197
+ }
198
+ export function commandHelp(c, s = makeStyle(false)) {
199
+ return [
200
+ `${s.bold(`swp ${c.name}`)}: ${c.summary.charAt(0).toLowerCase()}${c.summary.slice(1)}`,
201
+ "",
202
+ `${s.bold("Usage:")} swp ${c.name}${c.args ? ` ${c.args}` : ""}`,
203
+ "",
204
+ c.details,
205
+ "",
206
+ s.bold("Flags"),
207
+ ...flagTable([...c.flags, HELP[0]], s),
208
+ ...(c.env.length ? ["", `${s.bold("Needs:")} ${c.env.join(", ")} (${TOKENS_URL})`] : []),
209
+ "",
210
+ s.bold("Examples"),
211
+ ...c.examples.map((e) => ` ${e}`),
212
+ "",
213
+ `Docs: ${DOCS_URL}`,
214
+ ].join("\n");
215
+ }
216
+ /** What the arguments ask for, or a usage error that says how to fix them. */
217
+ export function resolveInvocation(argv, s = makeStyle(false)) {
218
+ const { positional, flags } = parseArgs(argv, BOOLEAN_FLAGS);
219
+ if (flags.version)
220
+ return { kind: "version" };
221
+ const [name, ...rest] = positional;
222
+ if (name === "help") {
223
+ if (!rest[0])
224
+ return { kind: "help", text: mainHelp(s), code: 0 };
225
+ const c = command(rest[0]);
226
+ return c ? { kind: "help", text: commandHelp(c, s), code: 0 } : unknownCommand(rest[0]);
227
+ }
228
+ if (!name)
229
+ return { kind: "help", text: mainHelp(s), code: flags.help ? 0 : 2 };
230
+ const c = command(name);
231
+ if (!c)
232
+ return unknownCommand(name);
233
+ if (flags.help)
234
+ return { kind: "help", text: commandHelp(c, s), code: 0 };
235
+ if (rest.length) {
236
+ const hint = BRANCH.every((f) => c.flags.includes(f))
237
+ ? ` To name a branch, use --branch ${rest[0]}.`
238
+ : "";
239
+ return usageError(`${c.name} takes no arguments, but got "${rest.join(" ")}".${hint}`, c.name);
240
+ }
241
+ const known = [...c.flags, ...HELP].map((f) => f.name);
242
+ for (const [key, value] of Object.entries(flags)) {
243
+ const flag = c.flags.find((f) => f.name === key);
244
+ if (!flag) {
245
+ const elsewhere = ALL_FLAGS.some((f) => f.name === key);
246
+ const guess = key === "env-file" ? "dotenv" : didYouMean(key, known);
247
+ return usageError(elsewhere
248
+ ? `${c.name} does not take --${key}.`
249
+ : `Unknown flag --${key}.${guess ? ` Did you mean --${guess}?` : ""}`, c.name);
250
+ }
251
+ if (flag.value && (value === true || value === ""))
252
+ return usageError(`--${key} needs a value: --${key} ${flag.value}`, c.name);
253
+ if (!flag.value && value !== true)
254
+ return usageError(`--${key} takes no value.`, c.name);
255
+ }
256
+ return { kind: "run", command: c.name, flags };
257
+ }
258
+ function unknownCommand(name) {
259
+ const guess = didYouMean(name, COMMANDS.map((c) => c.name));
260
+ return {
261
+ kind: "error",
262
+ message: `Unknown command "${name}".${guess ? ` Did you mean "swp ${guess}"?` : ""}\nRun swp --help to see every command.`,
263
+ };
264
+ }
265
+ function usageError(message, name) {
266
+ return { kind: "error", message: `${message}\nRun swp ${name} --help for its usage.` };
267
+ }
268
+ const ENV_SOURCES = {
269
+ SUPABASE_ACCESS_TOKEN: "https://supabase.com/dashboard/account/tokens (organization-scoped)",
270
+ CLOUDFLARE_API_TOKEN: "https://dash.cloudflare.com/profile/api-tokens (Workers Scripts: Edit)",
271
+ CLOUDFLARE_ACCOUNT_ID: "Cloudflare dashboard, Workers and Pages, Account details",
272
+ GITHUB_TOKEN: "`gh auth token` locally; ${{ github.token }} in Actions",
273
+ };
274
+ /** Names every variable the command needs and lacks, with where to get each; undefined when none is missing. */
275
+ export function missingEnvMessage(name, dryRun, env = process.env) {
276
+ const c = command(name);
277
+ const missing = (dryRun ? c.dryRunEnv : c.env).filter((v) => !env[v]);
278
+ if (!missing.length)
279
+ return undefined;
280
+ const width = Math.max(...missing.map((v) => v.length));
281
+ return [
282
+ `${name}${dryRun ? " --dry-run" : ""} needs ${missing.length === 1 ? "this variable" : "these variables"}, not set:`,
283
+ ...missing.map((v) => ` ${v.padEnd(width)} ${ENV_SOURCES[v]}`),
284
+ `Put ${missing.length === 1 ? "it" : "them"} in .env.swp (git-ignore it) or the environment. Scopes: ${TOKENS_URL}`,
285
+ ].join("\n");
286
+ }
287
+ /** One line for the user: the message, and for a network failure the host and the system error. */
288
+ export function formatError(err, debug = !!process.env.SWP_DEBUG) {
289
+ if (!(err instanceof Error))
290
+ return String(err);
291
+ if (debug && err.stack)
292
+ return err.stack;
293
+ const cause = err.cause;
294
+ if (err.message === "fetch failed" && cause)
295
+ return `network error${cause.hostname ? ` reaching ${cause.hostname}` : ""}: ${cause.code ?? cause.message ?? "unknown"}. Check the connection and try again.`;
296
+ return err.message;
297
+ }
package/package.json CHANGED
@@ -1,17 +1,92 @@
1
1
  {
2
2
  "name": "supabase-worker-previews",
3
- "version": "0.0.0",
4
- "description": "Placeholder. Install 0.1.0 or later.",
5
- "main": "index.js",
6
- "scripts": {
7
- "test": "echo \"Error: no test specified\" && exit 1"
8
- },
9
- "keywords": [],
10
- "author": "",
3
+ "version": "0.1.1",
4
+ "description": "Branch previews for Cloudflare Workers on Supabase branching: every git branch gets a Worker Preview, and every PR that changes the schema gets its own Supabase database.",
11
5
  "license": "MIT",
12
- "type": "commonjs",
6
+ "author": "Matt Ruby",
13
7
  "repository": {
14
8
  "type": "git",
15
9
  "url": "git+https://github.com/mattruby/supabase-worker-previews.git"
10
+ },
11
+ "homepage": "https://github.com/mattruby/supabase-worker-previews#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/mattruby/supabase-worker-previews/issues"
14
+ },
15
+ "type": "module",
16
+ "keywords": [
17
+ "cloudflare",
18
+ "cloudflare-workers",
19
+ "workers",
20
+ "wrangler",
21
+ "supabase",
22
+ "supabase-branching",
23
+ "branching",
24
+ "preview",
25
+ "preview-deployments",
26
+ "branch-preview",
27
+ "database",
28
+ "postgres",
29
+ "ci",
30
+ "github-actions",
31
+ "cli"
32
+ ],
33
+ "engines": {
34
+ "node": ">=22.0.0"
35
+ },
36
+ "bin": {
37
+ "swp": "dist/cli.js",
38
+ "supabase-worker-previews": "dist/cli.js"
39
+ },
40
+ "main": "./dist/runtime.js",
41
+ "types": "./dist/runtime.d.ts",
42
+ "exports": {
43
+ ".": {
44
+ "types": "./dist/runtime.d.ts",
45
+ "default": "./dist/runtime.js"
46
+ },
47
+ "./package.json": "./package.json"
48
+ },
49
+ "files": [
50
+ "dist",
51
+ "templates"
52
+ ],
53
+ "sideEffects": false,
54
+ "publishConfig": {
55
+ "access": "public",
56
+ "provenance": true
57
+ },
58
+ "scripts": {
59
+ "build": "tsc -p tsconfig.build.json",
60
+ "typecheck": "tsc --noEmit",
61
+ "lint": "eslint .",
62
+ "format": "prettier --write .",
63
+ "format:check": "prettier --check .",
64
+ "test": "vitest run",
65
+ "test:package": "node scripts/check-package.mjs",
66
+ "changeset": "changeset",
67
+ "version-packages": "changeset version && node scripts/sync-plugin-version.mjs && npm install --package-lock-only --ignore-scripts --no-audit --no-fund",
68
+ "prepare": "npm run build"
69
+ },
70
+ "peerDependencies": {
71
+ "wrangler": ">=4.135.0"
72
+ },
73
+ "peerDependenciesMeta": {
74
+ "wrangler": {
75
+ "optional": true
76
+ }
77
+ },
78
+ "devDependencies": {
79
+ "@changesets/cli": "^3.0.3",
80
+ "@cloudflare/workers-types": "^5.20261001.1",
81
+ "@eslint/js": "^10.0.1",
82
+ "@types/node": "^22.20.4",
83
+ "eslint": "^10.11.0",
84
+ "prettier": "^3.9.9",
85
+ "typescript": "^5.9.0",
86
+ "typescript-eslint": "^8.71.0",
87
+ "vitest": "^4.0.0"
88
+ },
89
+ "dependencies": {
90
+ "smol-toml": "^1.9.0"
16
91
  }
17
92
  }
@@ -0,0 +1,9 @@
1
+ -- Supabase branches (and newer projects) start without the API roles in the
2
+ -- default privileges; only migrations can grant them back.
3
+ -- This must run before any table exists. Later migrations revoke what they must.
4
+ alter default privileges for role postgres in schema public
5
+ grant all on tables to anon, authenticated, service_role;
6
+ alter default privileges for role postgres in schema public
7
+ grant all on sequences to anon, authenticated, service_role;
8
+ alter default privileges for role postgres in schema public
9
+ grant execute on functions to anon, authenticated, service_role;
@@ -0,0 +1,29 @@
1
+ name: Preview database
2
+
3
+ # The same job as supabase-previews.yml, through the published action. It runs
4
+ # the project's installed swp when there is one, else downloads the package.
5
+
6
+ on:
7
+ pull_request:
8
+ types: [opened, synchronize, reopened, labeled, unlabeled, closed]
9
+
10
+ concurrency:
11
+ group: supabase-previews-${{ github.head_ref }}
12
+ cancel-in-progress: false
13
+
14
+ permissions:
15
+ contents: read
16
+ pull-requests: write
17
+ deployments: write
18
+
19
+ jobs:
20
+ preview-db:
21
+ runs-on: ubuntu-latest
22
+ timeout-minutes: 45
23
+ steps:
24
+ - uses: actions/checkout@v4
25
+ - uses: mattruby/supabase-worker-previews@v0
26
+ with:
27
+ supabase-access-token: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
28
+ cloudflare-api-token: ${{ secrets.CLOUDFLARE_API_TOKEN }}
29
+ cloudflare-account-id: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
@@ -0,0 +1,38 @@
1
+ name: Preview database
2
+
3
+ # Workers Builds makes a Worker Preview of every branch; the Supabase GitHub
4
+ # integration makes a database for every PR that changes supabase/. This
5
+ # points each PR's Preview at the right database, proves it never serves
6
+ # production, and cleans up when the PR closes. It reports on the PR with one
7
+ # status comment and a GitHub deployment (the "View deployment" button).
8
+
9
+ on:
10
+ pull_request:
11
+ types: [opened, synchronize, reopened, labeled, unlabeled, closed]
12
+
13
+ concurrency:
14
+ group: supabase-previews-${{ github.head_ref }}
15
+ cancel-in-progress: false
16
+
17
+ permissions:
18
+ contents: read
19
+ pull-requests: write
20
+ deployments: write
21
+
22
+ jobs:
23
+ preview-db:
24
+ runs-on: ubuntu-latest
25
+ timeout-minutes: 45
26
+ steps:
27
+ - uses: actions/checkout@v4
28
+ - uses: actions/setup-node@v4
29
+ with:
30
+ node-version: 24
31
+ cache: npm
32
+ - run: npm ci --no-audit --no-fund
33
+ - run: npx supabase-worker-previews pr
34
+ env:
35
+ GITHUB_TOKEN: ${{ github.token }}
36
+ SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
37
+ CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
38
+ CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}