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
23 changes: 13 additions & 10 deletions .agents/skills/codflow-setup/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,20 +135,23 @@ grep -rn "00000000-0000\|00000000000000000000000000000000" \
# expected: no matches — fix any hit before continuing
```

**Propagate renamed D1 database through scripts:** If the database name differs
from `codflow-os-db`, it is hardcoded in multiple locations that must be updated:
- `cod-server/package.json`: `db:migrate:local` and `db:migrate:remote` scripts
- `cod-server/scripts/seed-local.mjs`: both `--local` and `--remote` wrangler
d1 execute calls
- `cod-client-astro/scripts/seed-admin.mjs`: both `--local` and `--remote`
wrangler d1 execute calls
**Set the D1 database name once:** the scripts are not hardcoded — the seeders,
the migration wrapper (`cod-server/scripts/d1.mjs`) and the R2 setup all read
`COD_DB_NAME` from `<repo-root>/.env` via `cod-server/scripts/cloud-env.mjs`
(precedence: `process.env` > `.env` > default). Set it there:

After updating, grep the repo to confirm only README/doc mentions of the old
name remain:
```bash
cp .env.example .env # at the repo root
# COD_DB_NAME=<your database name>
```

The `database_name` / `database_id` in each `wrangler.toml` still has to match
the database you created — those are wrangler's own config, not script input.
Confirm no script or config still pins the sample name:

```bash
grep -r "codflow-os-db" --exclude-dir=node_modules --exclude-dir=.git
# expected: only documentation files, zero script/config hits
# expected: only .env.example defaults and documentation, zero script hits
```

Confirm the filled `wrangler.toml` files are not tracked by git:
Expand Down
23 changes: 23 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Unified CodFlow Cloud resource values — single source of truth for the
# seeder scripts, the D1 migration wrapper, the R2 CORS setup, and the
# storefront deploy helper.
#
# Copy this file to .env at the repo root and fill in YOUR values.
# .env is gitignored — never commit real values.
#
# Precedence for every key: process.env > .env > built-in default.
#
# COD_ACCOUNT_ID — Cloudflare account ID (dashboard → account menu)
# COD_DB_NAME — D1 database name (wrangler d1 create <name>)
# COD_R2_BUCKET_NAME — R2 bucket name (wrangler r2 bucket create <name>)
# COD_SERVER_URL — deployed cod-server origin (storefront fetches this).
# Defaults to http://localhost:8787 for local dev;
# the storefront deploy refuses to ship a localhost
# value unless you pass --force-local.
# COD_MEDIA_DOMAIN — R2 custom domain, e.g. media.yourdomain.com

# COD_ACCOUNT_ID=
# COD_DB_NAME=codflow-os-db
# COD_R2_BUCKET_NAME=codflow-images
# COD_SERVER_URL=https://api.yourdomain.com
# COD_MEDIA_DOMAIN=media.yourdomain.com
23 changes: 13 additions & 10 deletions .opencode/skills/codflow-setup/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,20 +135,23 @@ grep -rn "00000000-0000\|00000000000000000000000000000000" \
# expected: no matches — fix any hit before continuing
```

**Propagate renamed D1 database through scripts:** If the database name differs
from `codflow-os-db`, it is hardcoded in multiple locations that must be updated:
- `cod-server/package.json`: `db:migrate:local` and `db:migrate:remote` scripts
- `cod-server/scripts/seed-local.mjs`: both `--local` and `--remote` wrangler
d1 execute calls
- `cod-client-astro/scripts/seed-admin.mjs`: both `--local` and `--remote`
wrangler d1 execute calls
**Set the D1 database name once:** the scripts are not hardcoded — the seeders,
the migration wrapper (`cod-server/scripts/d1.mjs`) and the R2 setup all read
`COD_DB_NAME` from `<repo-root>/.env` via `cod-server/scripts/cloud-env.mjs`
(precedence: `process.env` > `.env` > default). Set it there:

After updating, grep the repo to confirm only README/doc mentions of the old
name remain:
```bash
cp .env.example .env # at the repo root
# COD_DB_NAME=<your database name>
```

The `database_name` / `database_id` in each `wrangler.toml` still has to match
the database you created — those are wrangler's own config, not script input.
Confirm no script or config still pins the sample name:

```bash
grep -r "codflow-os-db" --exclude-dir=node_modules --exclude-dir=.git
# expected: only documentation files, zero script/config hits
# expected: only .env.example defaults and documentation, zero script hits
```

Confirm the filled `wrangler.toml` files are not tracked by git:
Expand Down
19 changes: 19 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,20 @@ npm run seed:admin` (sign-up is disabled by design).
`cod-astro/theme01` has extra validators — see
`cod-astro/theme01/AGENTS.md` for its commands.

## Cloud resource configuration

Resource names and URLs are **not** hardcoded in scripts. The seeders, the
D1 migration wrapper (`cod-server/scripts/d1.mjs`), the R2 CORS setup and
the storefront deploy helper all read `<repo-root>/.env` through
`cod-server/scripts/cloud-env.mjs`. Precedence is
**`process.env` > `.env` > built-in default**; the committed template is
`<repo-root>/.env.example`.

Keys: `COD_ACCOUNT_ID`, `COD_DB_NAME`, `COD_R2_BUCKET_NAME`,
`COD_SERVER_URL`, `COD_MEDIA_DOMAIN`. Add a key to `DEFAULTS` in
`cloud-env.mjs` and to `.env.example` together — a key in one and not the
other is how these drift.

## Verification

- After changing TypeScript: run `npm run typecheck` in the affected package.
Expand Down Expand Up @@ -84,6 +98,11 @@ npm run seed:admin` (sign-up is disabled by design).
workspace members. It keeps a single Vite major across astro/vitest;
removing it reintroduces the dual-Vite boot crash
(`Missing field 'moduleType'`). Keep it in sync when astro bumps Vite.
- `COD_SERVER_URL` defaults to `http://localhost:8787` so local dev works
untouched. A deployed Worker can never reach that, so
`cod-astro/theme01/scripts/deploy.mjs` refuses to deploy a loopback value
unless `--force-local` is passed. Set the real origin in the root `.env`
before deploying the storefront.
- Local D1 state is **shared** through `<repo-root>/.wrangler-shared`:
cod-server's dev/migrate scripts write there via `--persist-to`, and
cod-client-astro's astro dev reads the same files via the Cloudflare
Expand Down
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,16 +164,24 @@ npm ci
```

### 2. Create Cloudflare Resources
Names are yours to choose — scripts read them from the root `.env`
(see step 3), so nothing is hardcoded.

```bash
wrangler login
wrangler d1 create codflow-os-db
wrangler r2 bucket create codflow-images
wrangler d1 create my-codflow-db
wrangler r2 bucket create my-codflow-images
wrangler kv namespace create RATE_LIMIT
wrangler kv namespace create OAUTH_KV
```

### 3. Configure Environment
```bash
# Repo root — resource names for the seeders, the D1 wrapper, the R2 CORS
# setup and the storefront deploy helper. Precedence: process.env > .env > default.
cp .env.example .env
# Set COD_ACCOUNT_ID, COD_DB_NAME, COD_R2_BUCKET_NAME, COD_SERVER_URL, COD_MEDIA_DOMAIN

# Backend
cd cod-server
cp .dev.vars.example .dev.vars
Expand Down
2 changes: 1 addition & 1 deletion cod-astro/theme01/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"dev": "astro dev",
"build": "astro build",
"preview": "astro build && wrangler dev",
"deploy": "astro build && wrangler deploy --var COD_SERVER_URL:https://api.codflow.store",
"deploy": "npm run build && node scripts/deploy.mjs",
"cf-typegen": "wrangler types --env-interface CloudflareEnv src/env.d.ts",
"test": "vitest --run",
"test:watch": "vitest",
Expand Down
67 changes: 67 additions & 0 deletions cod-astro/theme01/scripts/deploy.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#!/usr/bin/env node
/**
* Deploy the storefront worker with COD_SERVER_URL injected from the unified
* root .env (COD_SERVER_URL) — see cod-server/scripts/cloud-env.mjs.
* STORE_API_KEY is a worker secret, set separately via `wrangler secret put`.
*
* COD_SERVER_URL defaults to http://localhost:8787 so `npm run dev` works out
* of the box. A deployed Worker can never reach that address, so shipping it
* produces a storefront whose every API call fails. Deployment is refused when
* the value resolves to localhost unless --force-local is passed.
*
* Usage:
* npm run deploy
* npm run deploy -- --force-local # intentionally deploy the local value
*/

import { execSync } from "node:child_process";
import { getCloudEnv } from "../../../cod-server/scripts/cloud-env.mjs";

const forceLocal = process.argv.includes("--force-local");
const { serverUrl } = getCloudEnv();

/** Loopback hosts a deployed Worker can never reach. */
function isLoopbackUrl(value) {
let hostname;
try {
({ hostname } = new URL(value));
} catch {
return false; // not a URL — let wrangler report it
}
return (
hostname === "localhost" ||
hostname === "127.0.0.1" ||
hostname === "0.0.0.0" ||
hostname === "[::1]" ||
hostname === "::1" ||
hostname.endsWith(".localhost")
);
}

if (isLoopbackUrl(serverUrl) && !forceLocal) {
console.error(`
Error: COD_SERVER_URL resolves to a local address (${serverUrl}).

A deployed Worker cannot reach your machine, so this would ship a storefront
whose every API call fails.

Set the deployed cod-server origin in <repo-root>/.env:

COD_SERVER_URL=https://api.yourdomain.com

See <repo-root>/.env.example for the full template, then re-run:

npm run deploy

To deploy the local value anyway (rarely what you want):

npm run deploy -- --force-local
`);
process.exit(1);
}

if (forceLocal && isLoopbackUrl(serverUrl)) {
console.warn(`Warning: deploying with a local COD_SERVER_URL (${serverUrl}) — --force-local was passed.`);
}

execSync(`npx wrangler deploy --var COD_SERVER_URL:${serverUrl}`, { stdio: "inherit" });
19 changes: 6 additions & 13 deletions cod-astro/theme01/wrangler.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,11 @@
},
"observability": {
"enabled": true
},
// Replace with your own values before deploying:
// COD_SERVER_URL → your cod-server Worker's public URL. The committed
// default targets local dev; for production deploy pass
// `wrangler deploy --var COD_SERVER_URL:https://api.yourdomain.com`
// STORE_API_KEY → a secret — set it via `wrangler secret put STORE_API_KEY`
// (do NOT put the real key here). Local dev reads both from .dev.vars.
"vars": {
// LOCAL DEV DEFAULT. Production deploys MUST override:
// npx wrangler deploy --var COD_SERVER_URL:https://api.yourdomain.com
// Forgetting the override breaks every storefront API fetch (products,
// landing pages — everything 302s home). Deploy scripts must pass it.
"COD_SERVER_URL": "http://localhost:8787"
}
// COD_SERVER_URL is injected at deploy time from the unified root .env
// by `npm run deploy` → scripts/deploy.mjs — never hardcode it here.
// Local dev reads it from this package's own .dev.vars via astro:env;
// cod-server/scripts/cloud-env.mjs only reads the root .env, not .dev.vars.
// STORE_API_KEY is a secret — set it via `wrangler secret put STORE_API_KEY`
// (do NOT put the real key here).
}
12 changes: 8 additions & 4 deletions cod-client-astro/scripts/seed-admin.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
* admin@example.com / Admin). Set them when you run the script:
* ADMIN_EMAIL=you@example.com ADMIN_NAME=You node scripts/seed-admin.mjs
*
* The database name must match the D1 binding in cod-server/wrangler.toml
* (and this package's wrangler.toml) — default `codflow-os-db`.
* The D1 database name comes from the unified root .env (COD_DB_NAME, via
* ../../cod-server/scripts/cloud-env.mjs) and must match the binding in
* cod-server/wrangler.toml.
*
* Local state is the repo-shared ../.wrangler-shared (same file cod-server
* migrates to — persist-to is resolved relative to this package's cwd — and
Expand All @@ -27,6 +28,9 @@ import { writeFileSync, unlinkSync } from "node:fs";
import { execSync } from "node:child_process";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { getCloudEnv } from "../../cod-server/scripts/cloud-env.mjs";

const { dbName } = getCloudEnv();

const scryptAsync = promisify(scrypt);

Expand Down Expand Up @@ -104,11 +108,11 @@ WHERE user_id = (SELECT id FROM users WHERE email = '${email}') AND provider_id

try {
console.log("\n=== Seeding admin (local) ===");
run(`npx wrangler d1 execute codflow-os-db --local --persist-to ../.wrangler-shared --file "${tmpFile}"`);
run(`npx wrangler d1 execute ${dbName} --local --persist-to ../.wrangler-shared --file "${tmpFile}"`);

if (remote) {
console.log("\n=== Seeding admin (remote) ===");
run(`npx wrangler d1 execute codflow-os-db --remote --file "${tmpFile}"`);
run(`npx wrangler d1 execute ${dbName} --remote --file "${tmpFile}"`);
}
} finally {
unlinkSync(tmpFile);
Expand Down
4 changes: 2 additions & 2 deletions cod-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
"deploy": "wrangler deploy",
"build:ci": "wrangler deploy --dry-run --outdir dist",
"db:generate": "drizzle-kit generate",
"db:migrate:local": "wrangler d1 migrations apply codflow-os-db --local --persist-to ../.wrangler-shared",
"db:migrate:remote": "wrangler d1 migrations apply codflow-os-db --remote",
"db:migrate:local": "node scripts/d1.mjs migrations apply --local --persist-to ../.wrangler-shared",
"db:migrate:remote": "node scripts/d1.mjs migrations apply --remote",
"db:seed:local": "node scripts/seed-local.mjs",
"db:setup:local": "npm run db:migrate:local && npm run db:seed:local",
"db:seed:remote": "node scripts/seed-local.mjs --remote",
Expand Down
63 changes: 63 additions & 0 deletions cod-server/scripts/cloud-env.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/**
* Unified Cloud resource values — single source of truth for CodFlow scripts.
*
* Reads <repo-root>/.env (gitignored) for the D1 database name, R2 bucket and
* worker URLs. Every workspace imports this helper instead of hardcoding
* resource values. Precedence: process.env > .env > default.
*
* Template keys live in <repo-root>/.env.example.
*/

import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";

const ROOT = fileURLToPath(new URL("../../", import.meta.url));

const DEFAULTS = {
COD_ACCOUNT_ID: "",
COD_DB_NAME: "codflow-os-db",
COD_R2_BUCKET_NAME: "codflow-images",
COD_SERVER_URL: "http://localhost:8787",
COD_MEDIA_DOMAIN: "media.example.com",
};

function parseEnv(text) {
const out = {};
for (const line of text.split(/\r?\n/)) {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith("#")) continue;
const eq = trimmed.indexOf("=");
if (eq === -1) continue;
const key = trimmed.slice(0, eq).trim();
let value = trimmed.slice(eq + 1).trim();
if (
(value.startsWith('"') && value.endsWith('"')) ||
(value.startsWith("'") && value.endsWith("'"))
) {
value = value.slice(1, -1);
}
out[key] = value;
}
return out;
}

export function getCloudEnv() {
let fileEnv = {};
try {
fileEnv = parseEnv(readFileSync(ROOT + ".env", "utf8"));
} catch {
// No .env file present — fall back to defaults below.
}
const merged = {};
for (const key of Object.keys(DEFAULTS)) {
const fromProcess = process.env[key];
merged[key] = fromProcess ? fromProcess : fileEnv[key] ?? DEFAULTS[key];
}
return {
accountId: merged.COD_ACCOUNT_ID,
dbName: merged.COD_DB_NAME,
bucketName: merged.COD_R2_BUCKET_NAME,
serverUrl: merged.COD_SERVER_URL,
mediaDomain: merged.COD_MEDIA_DOMAIN,
};
}
Loading