Support docker compose self hosting

This commit is contained in:
Ben Senescu 2026-03-04 14:44:15 -05:00
parent b1e634166a
commit 4350b12c67
19 changed files with 472 additions and 26 deletions

View File

@ -5,7 +5,24 @@ PORT=3001
VITE_APP_ID=open-seo
VITE_GATEWAY_URL=https://your-gateway-domain.example.com
GATEWAY_URL=https://your-gateway-domain.example.com
GATEWAY_APP_API_TOKEN=your_gateway_app_api_token
# DataForSEO Basic auth value: base64(login:password)
DATAFORSEO_API_KEY=base64_login_colon_password
# --- Docker self-hosting only (see SELF_HOSTING_DOCKER.md) ---
# For Docker, set GATEWAY_URL and VITE_GATEWAY_URL to:
# http://every-app-gateway.localhost:3000
# Optional: pin to a specific Gateway release tag.
# By default the latest release is pulled automatically (recommended).
# GATEWAY_RELEASE_TAG=
# Optional: needed only for Gateway AI proxy routes.
# OPENAI_API_KEY=
# Required for Gateway auth when running Docker self-host.
# Generate with: pnpm run docker:generate-secrets
# Validate with: pnpm run docker:check-env
# BETTER_AUTH_SECRET=
# JWT_PRIVATE_KEY=""
# JWT_PUBLIC_KEY=""

1
.gitignore vendored
View File

@ -28,3 +28,4 @@ dist/
/playwright/.cache/
.tanstack
.logs/
**/.pnpm-store/

View File

@ -12,6 +12,7 @@ OpenSEO is an SEO tool for _the people_. If tools like Semrush or Ahrefs are too
- [Community](#community)
- [Pricing / Costs (Free + API costs)](#pricing--costs)
- [Self Hosting (Deploy on Cloudflare) \[5-10 minutes\]](#self-hosting-deploy-on-cloudflare-5-10-minutes)
- [Docker Self Hosting (Gateway + OpenSEO)](#docker-self-hosting-gateway--openseo)
- [Local Development](#local-development)
- [Contributing](#contributing)
- [SEO API Cost Reference](#seo-api-cost-reference)
@ -42,10 +43,12 @@ Top priorities:
If something important is missing, please join the [Discord](https://discord.gg/c9uGs3cFXr) or email me at ben@everyapp.dev and request it.
## Community
Email me: ben@everyapp.dev
Join discord to chat: [Discord](https://discord.gg/c9uGs3cFXr)
Follow along for updates:
- [r/everyapp](https://www.reddit.com/r/everyapp/)
- On X: https://x.com/bensenescu
@ -65,7 +68,6 @@ For cost estimates, see [DataForSEO API Cost Reference](#seo-api-cost-reference)
> [!TIP]
> If anything in this section is confusing or unfamiliar like running terminal commands, copy this link into ChatGPT or Claude and ask it explain.
OpenSEO is built on [Every App](https://github.com/every-app/every-app), a platform for easily self-hosting open source apps like OpenSEO in your own Cloudflare account. Cloudflare enables much more powerful functionality than is possible running on your own computer or on a VPS.
_Windows Users_
@ -73,6 +75,7 @@ _Windows Users_
This has not been tested on Windows. Please let me know if you run into problems. Using WSL will likely work better. Also, try using [fly.io Sprites](https://sprites.dev/) to get a linux sandbox for free if you get totally stuck.
### Video Walkthrough
This video walks through setting up the Gateway and self hosting OpenSEO. If you run into any problems, reference the [Community](#community) section for how to reach out.
https://github.com/user-attachments/assets/e40d5089-971f-43c9-85ff-1213aea35156
@ -100,22 +103,29 @@ npx everyapp gateway deploy
- Follow the link output by the last command to create an account. You will access OpenSEO through this account.
### Self Host OpenSEO
Deploy the app to cloudflare.
1. Clone the repo to your machine
```sh
git clone https://github.com/every-app/open-seo.git
```
2. Switch to the directory
```sh
cd open-seo
```
3. Self host via the Every App CLI
```sh
npx everyapp app deploy
```
#### DataForSEO API Key Setup [5 minutes]
OpenSEO use DataForSEO to get the SEO info. You need an API key to connect OpenSEO to the service.
1. Go to [DataForSEO API Access](https://app.dataforseo.com/api-access).
@ -127,12 +137,18 @@ printf '%s' 'YOUR_LOGIN:YOUR_PASSWORD' | base64
```
4. Set this as a secret in Cloudflare. Use the value from the previous step when prompted.
```sh
npx wrangler secret put DATAFORSEO_API_KEY
```
Now you're all set! Go back to the gateway, click on the OpenSEO app, and start getting better at SEO!
## Docker Self Hosting (Gateway + OpenSEO)
If you want a single Docker Compose command that runs both Every App Gateway and OpenSEO together, see [`SELF_HOSTING_DOCKER.md`](./SELF_HOSTING_DOCKER.md).
This runtime uses local dev servers to emulate Cloudflare Worker bindings. It is intended for **local use only** — do not expose ports directly to the public internet. For remote access, use [Tailscale](https://tailscale.com/). For internet-facing deployments, use the Cloudflare deployment path above. See the [security and runtime caveats](./SELF_HOSTING_DOCKER.md#security-and-runtime-caveats) in the Docker guide for details.
## Local Development

155
SELF_HOSTING_DOCKER.md Normal file
View File

@ -0,0 +1,155 @@
# Docker Self-Hosting
This guide runs both Every App Gateway and OpenSEO with one Docker Compose command.
OpenSEO is an app built on Every App so things like authentication and user management are delegated to the Gateway. Every App is built with deployment to Cloudflare as its target with the goal of making self hosting more accessible to people not already running home labs. Because of that design principle, self hosting with docker is a bit complicated right now, but will hopefully will get smoother over time.
The stack uses local Cloudflare-compatible runtime behavior (`wrangler` + Vite/worker runtime) so bindings and auth behavior stay close to Workers while running on your own machine or server.
## Prerequisites
- Docker Desktop (or Docker Engine + Docker Compose)
## Runtime model
- OpenSEO runs from this repository source in containerized local runtime mode.
- Gateway is built from `apps/every-app-gateway` source at a release tag in https://github.com/every-app/every-app
Default gateway release policy:
- Source: `every-app/every-app` releases
- Tag: latest release (recommended)
You can optionally pin to a specific release by setting `GATEWAY_RELEASE_TAG` in your env file.
## Security and runtime caveats
This stack runs local dev servers to emulate Cloudflare Worker bindings — this is currently the best way to self-host outside Cloudflare, but it means dev-only surfaces (HMR, verbose errors, broader file-serving) are exposed on the serving ports. Do not expose these ports directly to the public internet. If you need remote access, use [Tailscale](https://tailscale.com/) instead of a public tunnel. For internet-facing deployments, use the [Cloudflare deployment path](./README.md#self-hosting-deploy-on-cloudflare-5-10-minutes).
## 1) Configure env values
From the repository root:
```bash
cp .env.example .env.local
```
Set values as needed in `.env.local`.
Important values:
- `GATEWAY_URL` and `VITE_GATEWAY_URL` should be set to `http://every-app-gateway.localhost:3000` for local Docker networking and JWT issuer consistency.
- Add a host entry for `every-app-gateway.localhost` if needed:
- macOS/Linux: add `127.0.0.1 every-app-gateway.localhost` to `/etc/hosts`
- Windows: add `127.0.0.1 every-app-gateway.localhost` to `C:\Windows\System32\drivers\etc\hosts`
- If you use a different host or port, set both `GATEWAY_URL` and `VITE_GATEWAY_URL` to the same origin.
- `DATAFORSEO_API_KEY` is required for OpenSEO SEO-data workflows.
- See [README: DataForSEO API Key Setup](./README.md#dataforseo-api-key-setup-5-minutes).
- `BETTER_AUTH_SECRET`, `JWT_PRIVATE_KEY`, and `JWT_PUBLIC_KEY` are required for gateway auth (see how to generate them below)
Generate auth values with:
```bash
pnpm run docker:generate-secrets
```
Copy the printed lines into `.env.local`.
Validate env before startup:
```bash
pnpm run docker:check-env
```
## 2) Start both services with one command
```bash
pnpm run docker:up
```
URLs:
- Gateway: `http://every-app-gateway.localhost:3000`
- OpenSEO: `http://localhost:3001`
Gateway boot behavior:
- Resolves the latest gateway release tag (unless explicitly pinned).
- Pulls gateway source for that tag, installs dependencies during image build, and runs in local runtime mode.
- Applies local D1 migrations on start.
- Persists local gateway Wrangler/D1 state in Docker volume `every_app_gateway_wrangler_state`.
OpenSEO boot behavior:
- Uses dependencies installed during image build, then applies local D1 migrations on start.
- Starts local dev runtime (Vite). See [Security and runtime caveats](#security-and-runtime-caveats) above.
## 3) Bootstrap Gateway and app access
1. Open `http://every-app-gateway.localhost:3000/sign-up` and create the owner account.
2. In Gateway admin (`/admin/apps`), add OpenSEO:
- App ID: `open-seo`
- App URL: `http://localhost:3001`
- Or whatever port you have this running at
3. Start using OpenSEO by accessing it through Gateway: `http://every-app-gateway.localhost:3000/`.
## Optional: Run only one service
Run gateway only:
```bash
pnpm run docker:check-env
docker compose -f self-host/docker-compose.yml --env-file .env.local up --build gateway
```
Run OpenSEO only (expects gateway already reachable at `GATEWAY_URL`):
```bash
pnpm run docker:check-env
docker compose -f self-host/docker-compose.yml --env-file .env.local up --build open-seo
```
## Updating gateway version
By default this stack pulls the latest published gateway release (recommended).
To pin to a specific gateway release instead:
1. Set `GATEWAY_RELEASE_TAG` in `.env.local` (for example `gateway-v0.1.11`).
2. Rebuild gateway:
```bash
docker compose -f self-host/docker-compose.yml --env-file .env.local build --no-cache gateway
docker compose -f self-host/docker-compose.yml --env-file .env.local up -d gateway
```
When tracking latest, rebuild gateway with `--no-cache` to pull newer gateway source for the latest tag.
## Troubleshooting
- `Issuer must be provided` or `signature verification failed`: make sure `GATEWAY_URL` and `VITE_GATEWAY_URL` both point to `http://every-app-gateway.localhost:3000`, then clear browser cookies/storage for `localhost` and `every-app-gateway.localhost` and sign in again.
- OpenSEO env values seem stale: restart OpenSEO:
```bash
docker compose -f self-host/docker-compose.yml --env-file .env.local up -d --build open-seo
```
## Stop and cleanup
Stop stack:
```bash
docker compose -f self-host/docker-compose.yml --env-file .env.local down
```
Stop and remove Docker volumes:
```bash
docker compose -f self-host/docker-compose.yml --env-file .env.local down -v
```
To reset only the gateway local DB state explicitly:
```bash
docker volume rm every_app_gateway_wrangler_state
```

View File

@ -8,8 +8,10 @@
"drizzle.config.ts",
// DB index re-exports schema for convenience
"src/db/index.ts",
// Docker self-host runtime entrypoint scripts
"self-host/scripts/*.mjs",
],
"project": ["**/*.{js,ts,tsx}", "!src/routeTree.gen.ts"],
"project": ["**/*.{js,mjs,ts,tsx}", "!src/routeTree.gen.ts"],
"ignore": [
"drizzle-prod.config.ts",
"src/client/features/keywords/utils.ts",

View File

@ -20,11 +20,15 @@
"db:migrate:prod": "npx everyapp app remote-d1-shell -- drizzle-kit migrate --config=drizzle-prod.config.ts",
"db:studio:local": "drizzle-kit studio",
"db:studio:prod": "npx everyapp app remote-d1-shell -- drizzle-kit studio --config=drizzle-prod.config.ts",
"docker:check-env": "node ./self-host/scripts/validate-selfhost-env.mjs",
"docker:up": "pnpm run docker:check-env && docker compose -f self-host/docker-compose.yml --env-file .env.local up --build",
"docker:down": "docker compose -f self-host/docker-compose.yml --env-file .env.local down",
"docker:generate-secrets": "node ./self-host/scripts/generate-selfhost-secrets.mjs",
"knip": "knip",
"ci": "prettier --check . && knip && tsc --noEmit && oxlint ."
},
"dependencies": {
"@every-app/sdk": "^0.1.12",
"@every-app/sdk": "^0.1.13",
"@tanstack/query-core": "^5.90.9",
"@tanstack/query-sync-storage-persister": "^5.90.14",
"@tanstack/react-form": "^1.25.0",

10
pnpm-lock.yaml generated
View File

@ -9,8 +9,8 @@ importers:
.:
dependencies:
'@every-app/sdk':
specifier: ^0.1.12
version: 0.1.12(@tanstack/react-router@1.162.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@tanstack/react-start@1.162.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(jose@6.1.3)(react@19.2.4)
specifier: ^0.1.13
version: 0.1.13(@tanstack/react-router@1.162.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@tanstack/react-start@1.162.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(jose@6.1.3)(react@19.2.4)
'@tanstack/query-core':
specifier: ^5.90.9
version: 5.90.20
@ -760,8 +760,8 @@ packages:
cpu: [x64]
os: [win32]
'@every-app/sdk@0.1.12':
resolution: {integrity: sha512-SmNFEEAskoiPKnqCDZrUKW3XIvHHRDlswbaYCP/2ZVPrK++rJBjlb0erGRxpOBSx70avg9KTDOvGaptfIgZF2g==}
'@every-app/sdk@0.1.13':
resolution: {integrity: sha512-r8NxAydshZVK2Z3H+SzYHuTGvIPHS+y/M38AgolRW6C87eQHa+VRWlZAtuUIq5P/fIaDpZcYgz43qRfTusvDSg==}
peerDependencies:
'@tanstack/react-router': ^1.0.0
'@tanstack/react-start': ^1.0.0
@ -3463,7 +3463,7 @@ snapshots:
'@esbuild/win32-x64@0.27.3':
optional: true
'@every-app/sdk@0.1.12(@tanstack/react-router@1.162.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@tanstack/react-start@1.162.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(jose@6.1.3)(react@19.2.4)':
'@every-app/sdk@0.1.13(@tanstack/react-router@1.162.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@tanstack/react-start@1.162.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)))(jose@6.1.3)(react@19.2.4)':
dependencies:
'@tanstack/react-router': 1.162.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@tanstack/react-start': 1.162.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0))

View File

@ -0,0 +1,22 @@
FROM node:22
ENV PNPM_HOME=/pnpm
ENV PATH=$PNPM_HOME:$PATH
WORKDIR /app
RUN corepack enable
COPY self-host/scripts/resolve-gateway-tag.mjs self-host/scripts/download-archive.mjs /tmp/scripts/
ARG GATEWAY_RELEASE_TAG=
RUN gateway_tag=$(node /tmp/scripts/resolve-gateway-tag.mjs) \
&& echo "Downloading Gateway source for tag: ${gateway_tag}" \
&& node /tmp/scripts/download-archive.mjs "https://github.com/every-app/every-app/archive/refs/tags/${gateway_tag}.tar.gz" /tmp/every-app-source.tar.gz \
&& mkdir -p /tmp/every-app-source \
&& tar -xzf /tmp/every-app-source.tar.gz -C /tmp/every-app-source \
&& source_root=$(find /tmp/every-app-source -mindepth 1 -maxdepth 1 -type d | head -n 1) \
&& cp -R "$source_root/apps/every-app-gateway/." /app \
&& rm -rf /tmp/every-app-source /tmp/every-app-source.tar.gz /tmp/scripts
RUN pnpm install --frozen-lockfile

View File

@ -0,0 +1,12 @@
FROM node:22
ENV PNPM_HOME=/pnpm
ENV PATH=$PNPM_HOME:$PATH
WORKDIR /app
RUN corepack enable
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile

View File

@ -0,0 +1,63 @@
services:
gateway:
build:
context: ..
dockerfile: self-host/Dockerfile.gateway-selfhost
args:
GATEWAY_RELEASE_TAG: ${GATEWAY_RELEASE_TAG:-}
env_file:
- ../.env.local
command:
[
"sh",
"-c",
"pnpm run db:migrate:local && pnpm exec vite dev --host 0.0.0.0 --port 3000",
]
ports:
- "127.0.0.1:3000:3000"
volumes:
- ../.env.local:/app/.env:ro
- gateway_wrangler_state:/app/.wrangler/state
networks:
selfhost:
aliases:
- every-app-gateway.localhost
open-seo:
build:
context: ..
dockerfile: self-host/Dockerfile.selfhost
working_dir: /app
environment:
- PORT=3001
- VITE_APP_ID=${VITE_APP_ID}
- VITE_GATEWAY_URL=${VITE_GATEWAY_URL}
- GATEWAY_URL=${GATEWAY_URL}
- DATAFORSEO_API_KEY=${DATAFORSEO_API_KEY}
- OPENAI_API_KEY=${OPENAI_API_KEY:-}
- VITE_SHOW_DEVTOOLS=false
command:
[
"sh",
"-c",
"pnpm run db:migrate:local && pnpm exec vite dev --host 0.0.0.0 --port 3001",
]
depends_on:
- gateway
ports:
- "127.0.0.1:3001:3001"
volumes:
- ..:/app
- open_seo_node_modules:/app/node_modules
- open_seo_pnpm_store:/pnpm/store
networks:
- selfhost
networks:
selfhost:
volumes:
gateway_wrangler_state:
name: every_app_gateway_wrangler_state
open_seo_node_modules:
open_seo_pnpm_store:

View File

@ -0,0 +1,20 @@
// Downloads a file from a URL and writes it to a local path.
// Usage: node self-host/scripts/download-archive.mjs <url> <output-path>
import { writeFileSync } from "node:fs";
const [url, outputPath] = process.argv.slice(2);
if (!url || !outputPath) {
console.error(
"Usage: node self-host/scripts/download-archive.mjs <url> <output-path>",
);
process.exit(1);
}
const res = await fetch(url);
if (!res.ok) {
throw new Error("Failed to download source archive: " + String(res.status));
}
writeFileSync(outputPath, Buffer.from(await res.arrayBuffer()));

View File

@ -0,0 +1,19 @@
import { generateKeyPairSync, randomBytes } from "node:crypto";
function escapeForEnv(value) {
return value.replace(/\r?\n/g, "\\n");
}
const betterAuthSecret = randomBytes(32).toString("base64");
const { privateKey, publicKey } = generateKeyPairSync("rsa", {
modulusLength: 2048,
publicKeyEncoding: { type: "spki", format: "pem" },
privateKeyEncoding: { type: "pkcs8", format: "pem" },
});
console.log(
"# Copy these lines into .env.local\n# See SELF_HOSTING_DOCKER.md for setup instructions",
);
console.log(`BETTER_AUTH_SECRET=${betterAuthSecret}`);
console.log(`JWT_PRIVATE_KEY="${escapeForEnv(privateKey)}"`);
console.log(`JWT_PUBLIC_KEY="${escapeForEnv(publicKey)}"`);

View File

@ -0,0 +1,23 @@
// Resolves the gateway release tag to use for the selfhost Docker build.
// If GATEWAY_RELEASE_TAG is set, prints it and exits.
// Otherwise fetches the latest release tag from the GitHub API.
const tag = process.env.GATEWAY_RELEASE_TAG;
if (tag) {
process.stdout.write(tag);
process.exit(0);
}
const res = await fetch(
"https://api.github.com/repos/every-app/every-app/releases/latest",
);
if (!res.ok) {
throw new Error("Failed to resolve latest release: " + String(res.status));
}
const parsed = await res.json();
if (!parsed.tag_name) {
throw new Error("Missing tag_name in latest release payload");
}
process.stdout.write(parsed.tag_name);

View File

@ -0,0 +1,92 @@
import { existsSync, readFileSync } from "node:fs";
function parseEnvFile(path) {
const raw = readFileSync(path, "utf8");
const out = {};
for (const line of raw.split(/\r?\n/)) {
const trimmed = line.trim();
if (trimmed.length === 0 || trimmed.startsWith("#")) {
continue;
}
const equalsIndex = trimmed.indexOf("=");
if (equalsIndex <= 0) {
continue;
}
const key = trimmed.slice(0, equalsIndex).trim();
let value = trimmed.slice(equalsIndex + 1).trim();
if (
(value.startsWith('"') && value.endsWith('"')) ||
(value.startsWith("'") && value.endsWith("'"))
) {
value = value.slice(1, -1);
}
out[key] = value.replace(/\\n/g, "\n");
}
return out;
}
function isBlank(value) {
return typeof value !== "string" || value.trim().length === 0;
}
const envPath = process.argv[2] || ".env.local";
if (!existsSync(envPath)) {
console.error(`Missing env file: ${envPath}`);
console.error("Create it with: cp .env.example .env.local");
process.exit(1);
}
const env = parseEnvFile(envPath);
const requiredKeys = [
"GATEWAY_URL",
"VITE_GATEWAY_URL",
"VITE_APP_ID",
"DATAFORSEO_API_KEY",
"BETTER_AUTH_SECRET",
"JWT_PRIVATE_KEY",
"JWT_PUBLIC_KEY",
];
const missingKeys = requiredKeys.filter((key) => isBlank(env[key]));
if (missingKeys.length > 0) {
console.error("Missing required keys in env file:");
for (const key of missingKeys) {
console.error(`- ${key}`);
}
console.error("\nGenerate auth keys with: pnpm run docker:generate-secrets");
process.exit(1);
}
if (env.GATEWAY_URL !== env.VITE_GATEWAY_URL) {
console.error(
"GATEWAY_URL and VITE_GATEWAY_URL must match for auth issuer consistency.",
);
process.exit(1);
}
if (!env.JWT_PRIVATE_KEY.includes("BEGIN PRIVATE KEY")) {
console.error("JWT_PRIVATE_KEY does not look like a PEM private key.");
process.exit(1);
}
if (!env.JWT_PUBLIC_KEY.includes("BEGIN PUBLIC KEY")) {
console.error("JWT_PUBLIC_KEY does not look like a PEM public key.");
process.exit(1);
}
if (env.BETTER_AUTH_SECRET.trim().length < 32) {
console.error(
"BETTER_AUTH_SECRET is too short. Generate a new one with docker:generate-secrets.",
);
process.exit(1);
}
console.log(`Env validation passed: ${envPath}`);

6
src/env.d.ts vendored
View File

@ -8,12 +8,6 @@ declare namespace Cloudflare {
// Gateway URL
GATEWAY_URL: string;
// Optional machine token used for app-to-gateway requests
GATEWAY_APP_API_TOKEN?: string;
// Legacy alias retained for backwards compatibility
APP_TOKEN?: string;
// DataForSEO API Basic auth value (base64 of login:password)
DATAFORSEO_API_KEY: string;
}

View File

@ -184,6 +184,9 @@ function AppLayout() {
}
function RootDocument({ children }: { children: React.ReactNode }) {
const showDevtools =
import.meta.env.DEV && import.meta.env.VITE_SHOW_DEVTOOLS !== "false";
return (
<html>
<head>
@ -199,7 +202,7 @@ function RootDocument({ children }: { children: React.ReactNode }) {
position="bottom-right"
mobileOffset={{ bottom: 100 }}
/>
{import.meta.env.DEV ? (
{showDevtools ? (
<TanStackDevtools
config={{ position: "bottom-right" }}
eventBusConfig={{ connectToServerBus: true }}

View File

@ -8,6 +8,7 @@ interface ViteTypeOptions {
interface ImportMetaEnv {
readonly VITE_GATEWAY_URL: string;
readonly VITE_APP_ID: string;
readonly VITE_SHOW_DEVTOOLS?: string;
// more env variables...
}

View File

@ -9,6 +9,7 @@ import { devtools } from "@tanstack/devtools-vite";
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), "");
const port = env.PORT ? Number(env.PORT) : 3001;
const showDevtools = env.VITE_SHOW_DEVTOOLS !== "false";
return {
envPrefix: ["VITE_", "BYPASS_GATEWAY_LOCAL_ONLY"],
@ -16,12 +17,14 @@ export default defineConfig(({ mode }) => {
port,
},
plugins: [
devtools({
showDevtools
? devtools({
consolePiping: {
enabled: true,
levels: ["log", "warn", "error", "info", "debug"],
},
}),
})
: null,
cloudflare({ viteEnvironment: { name: "ssr" } }),
tsConfigPaths(),
tanstackStart(),

View File

@ -12,7 +12,6 @@ declare namespace Cloudflare {
VITE_APP_ID: string;
VITE_GATEWAY_URL: string;
GATEWAY_URL: string;
GATEWAY_APP_API_TOKEN: string;
EVERY_APP_GATEWAY: Fetcher /* every-app-gateway */;
SITE_AUDIT_WORKFLOW: Workflow<Parameters<import("./src/server").SiteAuditWorkflow['run']>[0]['payload']>;
}
@ -22,7 +21,7 @@ type StringifyValues<EnvType extends Record<string, unknown>> = {
[Binding in keyof EnvType]: EnvType[Binding] extends string ? EnvType[Binding] : string;
};
declare namespace NodeJS {
interface ProcessEnv extends StringifyValues<Pick<Cloudflare.Env, "VITE_APP_ID" | "VITE_GATEWAY_URL" | "GATEWAY_URL" | "GATEWAY_APP_API_TOKEN">> {}
interface ProcessEnv extends StringifyValues<Pick<Cloudflare.Env, "VITE_APP_ID" | "VITE_GATEWAY_URL" | "GATEWAY_URL">> {}
}
// Begin runtime types