@clubedge/create-clubedge-app 0.43.0 → 0.44.0
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.
- package/CHANGELOG.md +23 -0
- package/README.md +43 -24
- package/dist/cli/ui.js +2 -1
- package/dist/core/customize.js +34 -8
- package/dist/core/deploy.js +39 -12
- package/dist/core/manifest-fields.js +5 -1
- package/dist/core/manifest-packs.js +107 -14
- package/dist/core/manifest.js +5 -1
- package/dist/core/plan.js +95 -24
- package/dist/core/project.js +5 -2
- package/dist/core/railway.js +19 -3
- package/dist/deploy-docker.js +8 -4
- package/dist/deploy-preview.js +6 -1
- package/dist/deploy.js +95 -46
- package/dist/index.js +1 -1
- package/dist/mcp-server.js +4 -2
- package/dist/modify.js +1 -1
- package/package.json +3 -3
- package/skills/clubedge-starter/SKILL.md +1 -1
- package/template/.github/workflows/ci.yml +106 -5
- package/template/.github/workflows/preview.yml +13 -5
- package/template/.prettierignore +10 -0
- package/template/AGENTS.md +9 -3
- package/template/README.md +124 -25
- package/template/SETUP.md +237 -51
- package/template/_gitignore +13 -0
- package/template/apps/api/.env.example +18 -8
- package/template/apps/api/clubedge.stack.json +3 -1
- package/template/apps/api/package.json +3 -2
- package/template/apps/api/src/app.ts +23 -0
- package/template/apps/api/src/env/production.test.ts +28 -6
- package/template/apps/api/src/env/production.ts +17 -2
- package/template/apps/api/src/rate-limit.ts +1 -1
- package/template/apps/api/src/server/auth.ts +8 -0
- package/template/apps/api/src/server/index.ts +2 -0
- package/template/apps/api/src/server/variants/auth.better-auth.ts +60 -3
- package/template/apps/api-fastapi/.env.example +5 -3
- package/template/apps/api-fastapi/clubedge.stack.json +1 -0
- package/template/apps/api-fastapi/package.json +1 -1
- package/template/apps/api-fastapi/pyproject.toml +1 -1
- package/template/apps/api-fastapi/src/clubedge_api/production_env.py +2 -0
- package/template/apps/api-fastapi/tests/test_production_env.py +9 -2
- package/template/apps/api-fastapi/uv.lock +1 -1
- package/template/apps/capacitor/package.json +1 -1
- package/template/apps/expo/.env.example +24 -0
- package/template/apps/expo/app.config.ts +48 -0
- package/template/apps/expo/assets/adaptive-icon.png +0 -0
- package/template/apps/expo/assets/icon.png +0 -0
- package/template/apps/expo/assets/splash-icon.png +0 -0
- package/template/apps/expo/clubedge.stack.json +46 -0
- package/template/apps/expo/package.json +42 -0
- package/template/apps/expo/src/app/(app)/_layout.tsx +31 -0
- package/template/apps/expo/src/app/(app)/index.tsx +73 -0
- package/template/apps/expo/src/app/(app)/settings.tsx +168 -0
- package/template/apps/expo/src/app/_layout.tsx +17 -0
- package/template/apps/expo/src/app/forgot-password.tsx +72 -0
- package/template/apps/expo/src/app/reset-password.tsx +80 -0
- package/template/apps/expo/src/app/sign-in.tsx +67 -0
- package/template/apps/expo/src/app/sign-up.tsx +78 -0
- package/template/apps/expo/src/components/ui.tsx +206 -0
- package/template/apps/expo/src/components/update-gate.tsx +54 -0
- package/template/apps/expo/src/config/site.json +11 -0
- package/template/apps/expo/src/lib/api.ts +6 -0
- package/template/apps/expo/src/lib/auth-client.ts +72 -0
- package/template/apps/expo/src/lib/auth-enabled.ts +1 -0
- package/template/apps/expo/src/lib/auth.ts +2 -0
- package/template/apps/expo/src/lib/config.ts +21 -0
- package/template/apps/expo/src/lib/session.tsx +44 -0
- package/template/apps/expo/src/lib/theme.ts +39 -0
- package/template/apps/expo/src/lib/update.test.ts +34 -0
- package/template/apps/expo/src/lib/update.ts +40 -0
- package/template/apps/expo/src/types/expo.d.ts +3 -0
- package/template/apps/expo/src/variants/api.auth.ts +18 -0
- package/template/apps/expo/src/variants/auth-enabled.auth.ts +1 -0
- package/template/apps/expo/src/variants/auth.better-auth.ts +98 -0
- package/template/apps/expo/src/variants/auth.supabase.ts +146 -0
- package/template/apps/expo/src/variants/layout.auth.tsx +56 -0
- package/template/apps/expo/tsconfig.json +12 -0
- package/template/apps/start/package.json +1 -1
- package/template/apps/vite/auth.server.mjs +34 -10
- package/template/apps/vite/package.json +3 -2
- package/template/apps/web/package.json +1 -1
- package/template/clubedge.template.json +42 -3
- package/template/docker-compose.yml +15 -12
- package/template/eslint.config.mjs +9 -0
- package/template/package.json +7 -1
- package/template/packages/auth/package.json +1 -1
- package/template/packages/auth-appwrite/package.json +1 -1
- package/template/packages/auth-better-auth/package.json +2 -2
- package/template/packages/auth-better-auth/src/index.ts +26 -5
- package/template/packages/auth-firebase/package.json +1 -1
- package/template/packages/auth-supabase/package.json +1 -1
- package/template/packages/auth-supabase/src/browser.ts +3 -3
- package/template/packages/cache/package.json +1 -1
- package/template/packages/cache-redis/package.json +1 -1
- package/template/packages/contract/package.json +1 -1
- package/template/packages/core/package.json +1 -1
- package/template/packages/db/package.json +1 -1
- package/template/packages/email/package.json +1 -1
- package/template/packages/email-resend/package.json +1 -1
- package/template/packages/email-smtp/package.json +1 -1
- package/template/packages/jobs/package.json +1 -1
- package/template/packages/jobs-bullmq/package.json +1 -1
- package/template/packages/jobs-inngest/package.json +1 -1
- package/template/packages/jobs-pg-boss/package.json +1 -1
- package/template/packages/local-db/package.json +1 -1
- package/template/packages/mobile/package.json +1 -1
- package/template/packages/observability/package.json +1 -1
- package/template/packages/observability-otel/package.json +1 -1
- package/template/packages/observability-sentry/package.json +1 -1
- package/template/packages/payments/package.json +1 -1
- package/template/packages/payments-stripe/package.json +1 -1
- package/template/packages/storage/package.json +1 -1
- package/template/packages/storage-s3/package.json +1 -1
- package/template/packages/storage-supabase/package.json +1 -1
- package/template/packages/ui/package.json +1 -1
- package/template/pnpm-lock.yaml +5919 -485
- package/template.lock.json +36 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `create-clubedge-app` are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.44.0
|
|
6
|
+
|
|
7
|
+
- **The native app with Expo**: default to Starter `v0.28.0` (#151). `--mobile expo` adds `apps/mobile`, an Android and iOS app with its own native screens, which calls the project's API (#147):
|
|
8
|
+
- Template schema 6 adds the `mobile` role: an app with its own screens that calls an API. Like a shell, it runs no server, so it has no Dockerfile or deployment, and `deploy` leaves it out. Older CLIs refuse a schema 6 Starter and ask for an update.
|
|
9
|
+
- It needs an API, `--api hono` or `--api fastapi`: with React + Vite, or without a website. Next.js and TanStack Start have no separate API for it to call, so they refuse `--mobile expo`. It can sit next to the Capacitor app (`--shell capacitor`).
|
|
10
|
+
- A mobile stack lists the module options it implements, and the others are refused with it: Expo signs in with Better Auth, Supabase, or nothing.
|
|
11
|
+
- Interactive runs ask about it after the framework and the API.
|
|
12
|
+
- The [Starter's release notes](https://github.com/Clubedge/clubedge-starter/releases/tag/v0.28.0) cover the app's screens, sign-in, update notice, and builds.
|
|
13
|
+
- **Projects without a website** (#148): `--framework none --api hono` (or `fastapi`), with or without `--mobile expo`, creates an API, and the native app that calls it directly:
|
|
14
|
+
- Schema 6 slots can be optional. The project records `framework: none`, so `add`, `remove`, and `upgrade` keep it without a web app.
|
|
15
|
+
- A slot names the files and root scripts only its apps use, left out when it is empty, such as the browser tests, and the slot whose package the root scripts use instead, so `pnpm dev` starts the API.
|
|
16
|
+
- Templates list the selections they refuse, with the reason (`rules`). Prompts and `add` offer only what the rules allow, and a refused option fails with the reason. Without a website, the Starter refuses payments, Inngest, FastAPI with Better Auth, and deploy targets other than Docker and Railway.
|
|
17
|
+
- `deploy` publishes the API itself: as the public app behind Caddy with Docker Compose, or as one public service on Railway, with Better Auth's URL and secret. Vercel, Netlify, Fly.io, and Render deploy a web app, so they refuse a project without one, and `deploy --preview` refuses it with the Railway commands for a preview by hand.
|
|
18
|
+
- **Audit exceptions per stack** (#150): a stack lists the advisories `pnpm audit` skips because of its dependencies (`auditIgnores`), and projects without the stack audit them again. The Starter's Expo stack skips two in Expo's development CLI that have no usable fix.
|
|
19
|
+
- The showcase waits for the billing portal's subscription before recording it, so its video no longer ends on a spinner (#146).
|
|
20
|
+
|
|
21
|
+
## 0.43.1
|
|
22
|
+
|
|
23
|
+
- **`try` shows Railway's reason when Railway refuses a VM** (#144):
|
|
24
|
+
- Before, it said Railway may have changed its free VM and told you to update the CLI, which no update fixes.
|
|
25
|
+
- It now prints Railway's own explanation, then Railway's limits, the usual cause: 3 free VMs per network per day, and no anonymous VMs while Railway is busy.
|
|
26
|
+
- Still on Starter `v0.27.1`.
|
|
27
|
+
|
|
5
28
|
## 0.43.0
|
|
6
29
|
|
|
7
30
|
- **Upgrades keep each app's own version** (#134, by @ahmed-husssain):
|
package/README.md
CHANGED
|
@@ -102,7 +102,7 @@ pnpm dev
|
|
|
102
102
|
|
|
103
103
|
Or start it with npm: `npx @clubedge/create-clubedge-app my-app`. The generated project is a pnpm workspace, so dependencies are installed with pnpm either way; the CLI falls back to `corepack pnpm` when `pnpm` is not on your path. Without a destination, the CLI asks where to create the project.
|
|
104
104
|
|
|
105
|
-
The generated project includes `apps/web/.env.local` (and `apps/api/.env.local` with a separate API, `apps/shell/.env.local` with a mobile app), created from the example and pointing at the local services you chose. Its `SETUP.md` describes every variable and provider. The root route is a landing page named after your project, and the dashboard is at `/dashboard`.
|
|
105
|
+
The generated project includes `apps/web/.env.local` (and `apps/api/.env.local` with a separate API, `apps/shell/.env.local` with a mobile shell, `apps/mobile/.env.local` with a native app), created from the example and pointing at the local services you chose. Its `SETUP.md` describes every variable and provider. The root route is a landing page named after your project, and the dashboard is at `/dashboard`.
|
|
106
106
|
|
|
107
107
|
The default Starter release ships inside the CLI package, so GitHub is only contacted when you pass `--ref`. Internet access is needed to install dependencies.
|
|
108
108
|
|
|
@@ -145,18 +145,35 @@ With the Hono API, the decoupled stacks support every module option the full-sta
|
|
|
145
145
|
|
|
146
146
|
### A mobile app
|
|
147
147
|
|
|
148
|
-
|
|
148
|
+
A project can have a mobile app in two ways, and both at once:
|
|
149
|
+
|
|
150
|
+
- **A shell** packages the Vite frontend as an Android and iOS app with [Capacitor](https://capacitorjs.com), so the app shows the same pages as the website (since CLI 0.42.0, with Starter v0.27.0).
|
|
151
|
+
- **A native app** has its own screens, built with [Expo](https://expo.dev) and React Native, and calls the same API (since CLI 0.44.0, with Starter v0.28.0).
|
|
152
|
+
|
|
153
|
+
Earlier Starters have neither, and the CLI refuses `--shell` and `--mobile` with them.
|
|
149
154
|
|
|
150
155
|
```sh
|
|
151
156
|
pnpm dlx @clubedge/create-clubedge-app my-app --framework vite --api hono --shell capacitor
|
|
157
|
+
pnpm dlx @clubedge/create-clubedge-app my-app --framework vite --api hono --mobile expo
|
|
152
158
|
```
|
|
153
159
|
|
|
154
|
-
| Flag
|
|
155
|
-
|
|
|
156
|
-
| `--shell capacitor`
|
|
157
|
-
| `--
|
|
160
|
+
| Flag | App | Where |
|
|
161
|
+
| ------------------------------- | ------------------------------------------------------------ | ------------- |
|
|
162
|
+
| `--shell capacitor` | The Vite app, packaged for Android and iOS with Capacitor | `apps/shell` |
|
|
163
|
+
| `--mobile expo` | A native Android and iOS app with Expo, calling the same API | `apps/mobile` |
|
|
164
|
+
| `--shell none`, `--mobile none` | No mobile app (the default) | |
|
|
165
|
+
|
|
166
|
+
A shell packages the frontend's static files, so it needs `--framework vite`, and the frontend still needs its API (`--api hono` or `--api fastapi`). A native app calls the API through the website, as the shell does, so it needs one too. Next.js and TanStack Start render their pages on their own server and have no separate API for an app to call: the CLI refuses `--shell` and `--mobile` with them, and interactive runs only ask about a mobile app after a frontend and its API. A shell and a native app run no server, so `deploy` deploys the frontend and the API as above and leaves them out. The native app's Starter lists the sign-in options it supports, so the CLI refuses an `--auth` option the app does not have yet.
|
|
167
|
+
|
|
168
|
+
#### Without a website
|
|
169
|
+
|
|
170
|
+
A project can also be only an API and its native app: `--framework none` leaves the web app out (since CLI 0.44.0, with Starter v0.28.0).
|
|
171
|
+
|
|
172
|
+
```sh
|
|
173
|
+
pnpm dlx @clubedge/create-clubedge-app my-app --framework none --api hono --mobile expo
|
|
174
|
+
```
|
|
158
175
|
|
|
159
|
-
|
|
176
|
+
The app then calls the API at the API's own address. A project needs a web app or an API, so `--framework none` needs `--api`. The project records `framework: none`, so `add`, `remove`, and `upgrade` keep it without a web app, and the commands that manage env files leave `apps/web/.env.local` out. `deploy` publishes the API itself on Railway or with Docker Compose (see their sections below); Fly.io, Vercel, Netlify, and Render deploy a web app, so it refuses them for a project without one.
|
|
160
177
|
|
|
161
178
|
## Choosing modules
|
|
162
179
|
|
|
@@ -194,9 +211,10 @@ Usage:
|
|
|
194
211
|
pnpm dlx @clubedge/create-clubedge-app upgrade [--ref <tag>] (inside a project)
|
|
195
212
|
|
|
196
213
|
Options:
|
|
197
|
-
--framework <id> App framework, such as next or tanstack-start (or vite, a frontend)
|
|
214
|
+
--framework <id> App framework, such as next or tanstack-start (or vite, a frontend, or none)
|
|
198
215
|
--api <id> A separate API for a frontend, such as hono or fastapi
|
|
199
216
|
--shell <id> A mobile app that packages a frontend, such as capacitor
|
|
217
|
+
--mobile <id> A native mobile app that calls the API, such as expo
|
|
200
218
|
--<module> <option> A module option, such as --auth none; the Starter lists its modules
|
|
201
219
|
--ref <ref> Download this Starter tag, branch, or commit from GitHub instead
|
|
202
220
|
(upgrade: the Starter to upgrade to)
|
|
@@ -223,7 +241,7 @@ pnpm dlx @clubedge/create-clubedge-app my-app --yes --no-git --framework tanstac
|
|
|
223
241
|
pnpm dlx @clubedge/create-clubedge-app my-app --ref main
|
|
224
242
|
|
|
225
243
|
# Reproduce a specific Starter release or commit
|
|
226
|
-
pnpm dlx @clubedge/create-clubedge-app my-app --ref v0.
|
|
244
|
+
pnpm dlx @clubedge/create-clubedge-app my-app --ref v0.28.0
|
|
227
245
|
pnpm dlx @clubedge/create-clubedge-app my-app --ref <commit-sha>
|
|
228
246
|
|
|
229
247
|
# Test local Starter changes before releasing them
|
|
@@ -242,7 +260,7 @@ pnpm clubedge add email resend payments stripe # several modules as
|
|
|
242
260
|
pnpm clubedge remove storage payments
|
|
243
261
|
pnpm dlx @clubedge/create-clubedge-app add infra local --dry-run
|
|
244
262
|
pnpm dlx @clubedge/create-clubedge-app@latest upgrade # to the newest CLI's Starter release
|
|
245
|
-
pnpm dlx @clubedge/create-clubedge-app upgrade --ref v0.
|
|
263
|
+
pnpm dlx @clubedge/create-clubedge-app upgrade --ref v0.28.0 # or a specific tag, branch, or commit
|
|
246
264
|
```
|
|
247
265
|
|
|
248
266
|
The CLI renders the project twice, from the Starter commit and modules recorded in your `package.json` and from the new state, and merges the difference into your project. The new state has other modules for `add` and `remove`, and the newer Starter with the same modules for `upgrade`. Modules a newer Starter introduces start as `none` where possible, so an upgrade never adds a service on its own.
|
|
@@ -250,7 +268,7 @@ The CLI renders the project twice, from the Starter commit and modules recorded
|
|
|
250
268
|
- Files you never changed are updated, added, or deleted.
|
|
251
269
|
- Files you changed keep your edits. Where the module change touches the same lines, the file gets conflict markers (`<<<<<<< yours` ... `>>>>>>> clubedge`) for you to resolve. The Starter's whole new version of each such file is saved in `.clubedge/incoming/` to compare with yours (`git diff --no-index <file> .clubedge/incoming/<file>`); Git ignores that folder, so delete it when you are done.
|
|
252
270
|
- Your own files are never touched.
|
|
253
|
-
- Each app's local env file (`apps/web/.env.local`, and `apps/api/.env.local` or `apps/
|
|
271
|
+
- Each app's local env file (`apps/web/.env.local`, and `apps/api/.env.local`, `apps/shell/.env.local`, or `apps/mobile/.env.local` when the project has them) keeps your values. Values still at their generated defaults follow the new selection, and new variables are added.
|
|
254
272
|
- `pnpm-lock.yaml` is replaced if you never changed your dependencies; otherwise `pnpm install` reconciles it.
|
|
255
273
|
|
|
256
274
|
Several modules in one command are applied as one change, and their requirements are checked together, so `add auth supabase storage supabase` works even though Supabase Storage needs Supabase Auth. When lines conflict, the CLI lists each file with its number of conflict blocks. Before an upgrade, it lists the Starter releases it crosses with the sections of their release notes, such as `v0.13.0 Observability, Tests, Also`.
|
|
@@ -322,7 +340,7 @@ pnpm clubedge deploy --env-file .env.staging # with another file of product
|
|
|
322
340
|
pnpm clubedge deploy --dry-run # show the plan, create nothing
|
|
323
341
|
```
|
|
324
342
|
|
|
325
|
-
With [Railway's CLI](https://docs.railway.com/cli) (the installed `railway`, or `npx @railway/cli`) and your Railway login, it creates or reuses a project named after the package and the databases selected by the modules. Full-stack projects get one public app service. A Vite project gets two services: the frontend has the public `*.up.railway.app` domain, and the API stays private; the frontend's Node server (`node server.mjs`) forwards the API routes to it over Railway's private network. API variables, including `FRONTEND_URL`, go on the API service; `VITE_*` values go on the frontend service for its build. With Better Auth, the frontend's Node server runs auth: it gets the same `DATABASE_URL` as the API, a generated `BETTER_AUTH_SECRET`, the email settings, and `BETTER_AUTH_URL`, the public frontend URL both services use. Each service's unset variables are reported from its own env example. The API's migration command runs before the API release, and the frontend and API deployments are both awaited. Database variables are references to Railway's Postgres and Redis by default. Values from `.env.production` (or `--env-file`) take precedence; external database URLs stay external, and localhost URLs are ignored. `.railway/railway.ts` describes the selected service topology and health checks; `deploy` applies it and adds Railway's TypeScript SDK (`railway`) as a dev dependency. Commit the generated file and package changes. It needs Railway CLI 5.42.1 or newer. Running `deploy` again reuses the services, so it also adds variables and ships new releases. Railway bills usage on your plan.
|
|
343
|
+
With [Railway's CLI](https://docs.railway.com/cli) (the installed `railway`, or `npx @railway/cli`) and your Railway login, it creates or reuses a project named after the package and the databases selected by the modules. Full-stack projects get one public app service. A project without a website (`--framework none`) also gets one, the API, with the public `*.up.railway.app` domain its mobile app calls, its migrations before each release, and, with Better Auth, which then runs in the API, a generated `BETTER_AUTH_SECRET` and its own public URL as `BETTER_AUTH_URL`. A Vite project gets two services: the frontend has the public `*.up.railway.app` domain, and the API stays private; the frontend's Node server (`node server.mjs`) forwards the API routes to it over Railway's private network. API variables, including `FRONTEND_URL`, go on the API service; `VITE_*` values go on the frontend service for its build. With Better Auth, the frontend's Node server runs auth: it gets the same `DATABASE_URL` as the API, a generated `BETTER_AUTH_SECRET`, the email settings, and `BETTER_AUTH_URL`, the public frontend URL both services use. Each service's unset variables are reported from its own env example. The API's migration command runs before the API release, and the frontend and API deployments are both awaited. Database variables are references to Railway's Postgres and Redis by default. Values from `.env.production` (or `--env-file`) take precedence; external database URLs stay external, and localhost URLs are ignored. `.railway/railway.ts` describes the selected service topology and health checks; `deploy` applies it and adds Railway's TypeScript SDK (`railway`) as a dev dependency. Commit the generated file and package changes. It needs Railway CLI 5.42.1 or newer. Running `deploy` again reuses the services, so it also adds variables and ships new releases. Railway bills usage on your plan.
|
|
326
344
|
|
|
327
345
|
### Deploy to Fly.io (experimental)
|
|
328
346
|
|
|
@@ -344,7 +362,7 @@ In a project with `--deploy render`, `pnpm clubedge deploy` uses Render's API. R
|
|
|
344
362
|
|
|
345
363
|
In a project with `--deploy docker` (the default), `pnpm clubedge deploy` prepares the project for a server of your own and creates nothing anywhere else. It writes three files to commit:
|
|
346
364
|
|
|
347
|
-
- `compose.yaml` runs each app's image behind [Caddy](https://caddyserver.com), the only service that publishes ports (80, and 443 over TCP and UDP): it serves `DOMAIN` over HTTPS, with certificates it obtains and renews by itself. A full-stack app (Next.js or TanStack Start) is one service, `web`, built from the root `Dockerfile`; a frontend and its API are two, the frontend `web`, which Caddy serves, and `api`, which stays on the Compose network behind the frontend's `/api` proxy
|
|
365
|
+
- `compose.yaml` runs each app's image behind [Caddy](https://caddyserver.com), the only service that publishes ports (80, and 443 over TCP and UDP): it serves `DOMAIN` over HTTPS, with certificates it obtains and renews by itself. A full-stack app (Next.js or TanStack Start) is one service, `web`, built from the root `Dockerfile`; a frontend and its API are two, the frontend `web`, which Caddy serves, and `api`, which stays on the Compose network behind the frontend's `/api` proxy; without a website, `api` is the one service, which Caddy serves, and Better Auth in it gets `BETTER_AUTH_URL`. Each service has an image, `${IMAGE_PREFIX:-<project>}-<service>:${TAG:-latest}`, and its build: `docker compose up -d` pulls the released images when `IMAGE_PREFIX` points at GitHub's registry, and `--build` builds them on the server. The public URL defaults to `https://$DOMAIN`: Next.js also gets it as a build argument, and Better Auth on a Vite frontend as `BETTER_AUTH_URL`.
|
|
348
366
|
- `Caddyfile`: every request for `DOMAIN` goes to the public app.
|
|
349
367
|
- `.github/workflows/release-images.yml`: each published GitHub release builds every app's image for `linux/amd64` and `linux/arm64` and pushes it to `ghcr.io/<owner>/<repository>-<service>`, tagged with its version (`1.2.3`), `1.2`, and `latest`; run by hand on the default branch, it pushes `edge`. It never runs for tag pushes, so a deleted tag leaves no images behind. The values an image compiles in come from repository variables: `NEXT_PUBLIC_APP_URL` for Next.js, the `VITE_*` values for a Vite frontend.
|
|
350
368
|
|
|
@@ -354,7 +372,7 @@ Then point the domain's DNS A and AAAA records at the server, publish a release,
|
|
|
354
372
|
# .env.production: DOMAIN=example.com, the app's variables, and, to run the released images,
|
|
355
373
|
# IMAGE_PREFIX=ghcr.io/<owner>/<repository> (TAG=1.2.3 pins a release; latest otherwise)
|
|
356
374
|
docker login ghcr.io # while the packages are private: a token with read:packages
|
|
357
|
-
docker compose --env-file .env.production run --rm web node /app/migrate.mjs # api
|
|
375
|
+
docker compose --env-file .env.production run --rm web node /app/migrate.mjs # api when the project has one
|
|
358
376
|
docker compose --env-file .env.production up -d # --build builds the images on the server
|
|
359
377
|
```
|
|
360
378
|
|
|
@@ -372,7 +390,7 @@ In a project that deploys to Vercel or Railway, `pnpm clubedge deploy --preview`
|
|
|
372
390
|
NEON_API_KEY=... NEON_PROJECT_ID=... pnpm clubedge deploy --preview
|
|
373
391
|
```
|
|
374
392
|
|
|
375
|
-
With `NEON_API_KEY` and `NEON_PROJECT_ID` (in the shell, or in the env file), the preview gets its own [Neon branch](https://neon.com/docs/introduction/branching), `preview/<git branch>`: a copy of the production database made in seconds that shares its storage until it changes. `deploy` creates it, or reuses it on later runs, applies this branch's migrations to it, and points the preview's `DATABASE_URL` and `DATABASE_URL_DIRECT` at it. On Vercel, the preview is a preview deployment with the project's Preview variables, and the branch's URLs are set as Preview variables of your Git branch, which needs the Vercel project connected to its Git repository; without the Neon settings, it uses the Preview `DATABASE_URL`. On Railway, it is an environment named `preview-<git branch>`, copied from production with its own domain; without the Neon settings, it gets its own empty copy of Railway's Postgres, and when production's database comes from elsewhere, `deploy --preview` refuses, since the copy would use production's data. Production is not changed. It deploys a single app for now: a Vite frontend and its API are two services, so `deploy --preview` refuses them and explains how to copy production's Railway environment yourself.
|
|
393
|
+
With `NEON_API_KEY` and `NEON_PROJECT_ID` (in the shell, or in the env file), the preview gets its own [Neon branch](https://neon.com/docs/introduction/branching), `preview/<git branch>`: a copy of the production database made in seconds that shares its storage until it changes. `deploy` creates it, or reuses it on later runs, applies this branch's migrations to it, and points the preview's `DATABASE_URL` and `DATABASE_URL_DIRECT` at it. On Vercel, the preview is a preview deployment with the project's Preview variables, and the branch's URLs are set as Preview variables of your Git branch, which needs the Vercel project connected to its Git repository; without the Neon settings, it uses the Preview `DATABASE_URL`. On Railway, it is an environment named `preview-<git branch>`, copied from production with its own domain; without the Neon settings, it gets its own empty copy of Railway's Postgres, and when production's database comes from elsewhere, `deploy --preview` refuses, since the copy would use production's data. Production is not changed. It deploys a single web app for now: a Vite frontend and its API are two services, and a project without a website runs only its API, so `deploy --preview` refuses them and explains how to copy production's Railway environment yourself.
|
|
376
394
|
|
|
377
395
|
Projects created with `--infra neon` also have `.github/workflows/preview.yml`, which does this for every pull request in CI: a Neon branch per pull request with its migrations, the schema diff as a comment, a Vercel or Railway preview, and cleanup when the pull request closes. The Starter's SETUP.md lists the secrets it needs.
|
|
378
396
|
|
|
@@ -381,27 +399,27 @@ Projects created with `--infra neon` also have `.github/workflows/preview.yml`,
|
|
|
381
399
|
Each CLI release is tested against one specific Starter release:
|
|
382
400
|
|
|
383
401
|
```text
|
|
384
|
-
create-clubedge-app v0.
|
|
402
|
+
create-clubedge-app v0.44.0
|
|
385
403
|
|
|
|
386
404
|
v
|
|
387
|
-
Clubedge Starter v0.
|
|
388
|
-
commit
|
|
405
|
+
Clubedge Starter v0.28.0
|
|
406
|
+
commit 090b05a9561f6eb7bb3bc0ab0b15ada7702aef8c
|
|
389
407
|
```
|
|
390
408
|
|
|
391
409
|
The current relationship is:
|
|
392
410
|
|
|
393
411
|
| CLI release | Default Starter release | Meaning |
|
|
394
412
|
| --- | --- | --- |
|
|
395
|
-
| `create-clubedge-app@0.
|
|
413
|
+
| `create-clubedge-app@0.44.0` | `clubedge-starter@v0.28.0` | The CLI's required, tested default |
|
|
396
414
|
|
|
397
415
|
This means the same CLI version always scaffolds the same Starter revision by default. The CLI does not silently follow Starter `main`.
|
|
398
416
|
|
|
399
|
-
The `v0.
|
|
400
|
-
`
|
|
417
|
+
The `v0.28.0` tag is expected to resolve to commit
|
|
418
|
+
`090b05a9561f6eb7bb3bc0ab0b15ada7702aef8c`. The CLI verifies this before
|
|
401
419
|
scaffolding with the default reference and fails rather than silently using a
|
|
402
420
|
moved tag.
|
|
403
421
|
|
|
404
|
-
When the Starter changes, its maintainers first publish a new versioned release, such as `v0.
|
|
422
|
+
When the Starter changes, its maintainers first publish a new versioned release, such as `v0.28.0`. The CLI is then updated to use that release, all generated-project checks must pass, and a new CLI version is published. This keeps Starter and CLI releases independently versioned while preserving a tested compatibility relationship.
|
|
405
423
|
|
|
406
424
|
Every generated project records its CLI version, Starter repository, Starter ref, Starter commit, framework, and modules in `package.json` and `README.md`.
|
|
407
425
|
|
|
@@ -409,7 +427,7 @@ Every generated project records its CLI version, Starter repository, Starter ref
|
|
|
409
427
|
|
|
410
428
|
Each CLI release bundles one Starter release. When the release is built, `scripts/bundle-template.mjs` clones the pinned Starter tag, verifies that it resolves to the pinned commit, and copies its tracked files into the package (`template/` and `template.lock.json`). Running the CLI then needs no GitHub access for the default Starter.
|
|
411
429
|
|
|
412
|
-
The Starter describes itself in `clubedge.template.json`: its app directory, site config, environment files, Docker image name, and files that stay out of generated projects. Schema 2 manifests also list each framework's app directory, environment example, and Dockerfile. The CLI moves the selected framework's app to `apps/web` (renaming its package to `@clubedge/web`), moves its environment example and Dockerfile to the project root, leaves the other frameworks out, and removes their entries from `pnpm-lock.yaml` so `pnpm install --frozen-lockfile` keeps working. Schema 3 adds `modules`: for each option, the workspace packages it uses, the files it owns, the variant files that replace defaults, the root `package.json` scripts it sets, and the options it requires. Files listed under `conditional` contain `clubedge:if <condition>` / `clubedge:end` blocks (in `#`, `//`, `<!-- -->`, or JSX comments) that the CLI keeps or drops for the selection, always removing the markers; `clubedge:if starter-repository` marks sections for Starter maintainers only. Schema 4 moves each app's description into its own stack pack, placed in a slot (`--framework`, `--api`),
|
|
430
|
+
The Starter describes itself in `clubedge.template.json`: its app directory, site config, environment files, Docker image name, and files that stay out of generated projects. Schema 2 manifests also list each framework's app directory, environment example, and Dockerfile. The CLI moves the selected framework's app to `apps/web` (renaming its package to `@clubedge/web`), moves its environment example and Dockerfile to the project root, leaves the other frameworks out, and removes their entries from `pnpm-lock.yaml` so `pnpm install --frozen-lockfile` keeps working. Schema 3 adds `modules`: for each option, the workspace packages it uses, the files it owns, the variant files that replace defaults, the root `package.json` scripts it sets, and the options it requires. Files listed under `conditional` contain `clubedge:if <condition>` / `clubedge:end` blocks (in `#`, `//`, `<!-- -->`, or JSX comments) that the CLI keeps or drops for the selection, always removing the markers; `clubedge:if starter-repository` marks sections for Starter maintainers only. Schema 4 moves each app's description into its own stack pack, placed in a slot (`--framework`, `--api`), schema 5 adds the shell role for a mobile app that packages the frontend (`--shell`), and schema 6 the mobile role for a native app (`--mobile`); see [docs/stack-packs.md](docs/stack-packs.md). The CLI refuses a Starter whose manifest schema is newer than it understands and asks you to update. Starters before v0.3.0 have no manifest and use the previous layout.
|
|
413
431
|
|
|
414
432
|
The CLI first builds a plan (the files to copy and the edits to make), which `--dry-run` prints, and then applies it. The source is organized the same way:
|
|
415
433
|
|
|
@@ -456,7 +474,7 @@ CI packs the CLI with `npm pack`, scaffolds clean projects from the packed tarba
|
|
|
456
474
|
1. Make and validate changes in `clubedge-starter`.
|
|
457
475
|
2. Run lint, typecheck, unit tests, browser tests, and production build.
|
|
458
476
|
3. Update the Starter package versions.
|
|
459
|
-
4. Commit `main`, create a release tag such as `v0.
|
|
477
|
+
4. Commit `main`, create a release tag such as `v0.28.0`, and publish the Starter release.
|
|
460
478
|
|
|
461
479
|
### CLI maintainers
|
|
462
480
|
|
|
@@ -510,6 +528,7 @@ Each phase ends with a Starter release and a CLI release pinned to it. The full
|
|
|
510
528
|
| 0.41.0 | v0.26.0 | A conflicted package.json no longer breaks later commands (`--detailed-exit-code`); doctor finds conflict markers; projects own their version and README |
|
|
511
529
|
| 0.42.0 | v0.27.0 | Starter v0.27.0: mobile apps with Capacitor (`--shell capacitor`, template schema 5); client addresses per platform; Docker Compose behind Caddy, with images on GHCR |
|
|
512
530
|
| 0.43.0 | v0.27.1 | Upgrades keep each app's own version; add, remove, and upgrade that change nothing skip the Starter download |
|
|
531
|
+
| 0.44.0 | v0.28.0 | Starter v0.28.0: the native app with Expo (`--mobile expo`, template schema 6); projects without a website (`--framework none`), with template rules; audit exceptions per stack |
|
|
513
532
|
|
|
514
533
|
## Community
|
|
515
534
|
|
package/dist/cli/ui.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import process from "node:process";
|
|
2
|
+
import { isOptionalSlot } from "../core/manifest.js";
|
|
2
3
|
import { NO_STACK, stacksIn } from "../core/plan.js";
|
|
3
4
|
import { cliUrl, cliVersion, siteUrl, starterPin } from "../package-info.js";
|
|
4
5
|
// Each glyph is 6 rows tall. Rows are padded to a common width when rendered.
|
|
@@ -72,7 +73,7 @@ export function selectionHelp(manifest) {
|
|
|
72
73
|
}
|
|
73
74
|
return [
|
|
74
75
|
...Object.entries(manifest.slots).flatMap(([id, slot]) => {
|
|
75
|
-
const stacks = [...stacksIn(manifest, id), ...(slot
|
|
76
|
+
const stacks = [...stacksIn(manifest, id), ...(isOptionalSlot(slot) ? [NO_STACK] : [])];
|
|
76
77
|
return stacks.length > 1 ? [line(`--${id} <id>`, `${slot.name}: ${choices(stacks, slot.default ?? NO_STACK)}`)] : [];
|
|
77
78
|
}),
|
|
78
79
|
...Object.entries(manifest.modules).map(([id, module]) => line(`--${id} <id>`, `${module.name}: ${choices(Object.keys(module.options), module.default)}`)),
|
package/dist/core/customize.js
CHANGED
|
@@ -21,7 +21,12 @@ function recordStacks(stacks) {
|
|
|
21
21
|
...(Object.keys(apps).length ? { apps } : {}),
|
|
22
22
|
};
|
|
23
23
|
}
|
|
24
|
-
|
|
24
|
+
/** A command with one package name replaced where it stands on its own, as in `--filter=<name>`. */
|
|
25
|
+
function renamePackage(command, from, to) {
|
|
26
|
+
const escaped = from.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
27
|
+
return command.replace(new RegExp(`(^|[\\s='"])${escaped}(?=$|[\\s'"])`, "g"), `$1${to}`);
|
|
28
|
+
}
|
|
29
|
+
export function customizeRootManifest(source, { packageName, version = "0.1.0" }, provenance, dockerImage, scripts = { set: {}, remove: [] }, droppedAdvisories = []) {
|
|
25
30
|
const manifest = JSON.parse(source);
|
|
26
31
|
manifest.name = packageName;
|
|
27
32
|
manifest.version = version;
|
|
@@ -31,6 +36,23 @@ export function customizeRootManifest(source, { packageName, version = "0.1.0" }
|
|
|
31
36
|
}
|
|
32
37
|
if (Object.keys(scripts.set).length)
|
|
33
38
|
manifest.scripts = { ...manifest.scripts, ...scripts.set };
|
|
39
|
+
for (const [from, to] of Object.entries(scripts.rename ?? {})) {
|
|
40
|
+
for (const [name, command] of Object.entries(manifest.scripts ?? {})) {
|
|
41
|
+
if (typeof command === "string")
|
|
42
|
+
manifest.scripts[name] = renamePackage(command, from, to);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// Advisories that only left-out stacks need are audited again; settings left empty go too.
|
|
46
|
+
const auditConfig = manifest.pnpm?.auditConfig;
|
|
47
|
+
if (droppedAdvisories.length && Array.isArray(auditConfig?.ignoreGhsas)) {
|
|
48
|
+
auditConfig.ignoreGhsas = auditConfig.ignoreGhsas.filter((id) => !droppedAdvisories.includes(id));
|
|
49
|
+
if (!auditConfig.ignoreGhsas.length)
|
|
50
|
+
delete auditConfig.ignoreGhsas;
|
|
51
|
+
if (!Object.keys(auditConfig).length)
|
|
52
|
+
delete manifest.pnpm.auditConfig;
|
|
53
|
+
if (!Object.keys(manifest.pnpm).length)
|
|
54
|
+
delete manifest.pnpm;
|
|
55
|
+
}
|
|
34
56
|
manifest.clubedge = {
|
|
35
57
|
cliVersion: provenance.cliVersion,
|
|
36
58
|
starterRepository: provenance.starterRepository,
|
|
@@ -173,20 +195,24 @@ export class ConditionalError extends Error {
|
|
|
173
195
|
const MARKER = /^\s*(?:\/\/|#|%%|<!--|\{?\/\*)\s*clubedge:(if|end)\b\s*(.*?)\s*(?:-->|\*\/\}?)?\s*$/;
|
|
174
196
|
// A single line kept only when its condition holds: "- Item <!-- clubedge:only auth=supabase -->".
|
|
175
197
|
const LINE_MARKER = /\s*(?:\/\/|#|<!--|\{?\/\*)\s*clubedge:only\s+(.+?)\s*(?:-->|\*\/\}?)?\s*$/;
|
|
198
|
+
/** The parts of a condition such as `framework=none && payments!=none`, as markers and rules write them. */
|
|
199
|
+
export function conditionParts(expression, location) {
|
|
200
|
+
return expression.split("&&").map((part) => {
|
|
201
|
+
const match = /^\s*([a-z][a-z0-9-]*)\s*(!?=)\s*([a-z0-9|-]+)\s*$/.exec(part);
|
|
202
|
+
if (!match)
|
|
203
|
+
throw new ConditionalError(`${location}: cannot read the condition "${expression}".`);
|
|
204
|
+
return { key: match[1], equals: match[2] === "=", values: match[3].split("|") };
|
|
205
|
+
});
|
|
206
|
+
}
|
|
176
207
|
function evaluateCondition(expression, selection, location) {
|
|
177
208
|
// Maintainer-only sections of the Starter repository never reach a generated project.
|
|
178
209
|
if (expression.trim() === "starter-repository")
|
|
179
210
|
return false;
|
|
180
|
-
return expression
|
|
181
|
-
const match = /^\s*([a-z][a-z0-9-]*)\s*(!?=)\s*([a-z0-9|-]+)\s*$/.exec(part);
|
|
182
|
-
if (!match)
|
|
183
|
-
throw new ConditionalError(`${location}: cannot read the condition "${expression}".`);
|
|
184
|
-
const [, key, operator, values] = match;
|
|
211
|
+
return conditionParts(expression, location).every(({ key, equals, values }) => {
|
|
185
212
|
const selected = selection[key];
|
|
186
213
|
if (selected === undefined)
|
|
187
214
|
throw new ConditionalError(`${location}: unknown key "${key}".`);
|
|
188
|
-
|
|
189
|
-
return operator === "=" ? matches : !matches;
|
|
215
|
+
return values.includes(selected) === equals;
|
|
190
216
|
});
|
|
191
217
|
}
|
|
192
218
|
/**
|
package/dist/core/deploy.js
CHANGED
|
@@ -188,6 +188,21 @@ export function planServicePair(input) {
|
|
|
188
188
|
const unset = frontendKeys.filter((key) => set[key] === undefined && !existing.frontend[key] && !optional.includes(key));
|
|
189
189
|
return { frontend: { set, unset }, api };
|
|
190
190
|
}
|
|
191
|
+
/**
|
|
192
|
+
* The variables of an API deployed without a web app, as the public service its mobile app calls.
|
|
193
|
+
* It gets what planVariables provides, with the platform's client-address settings. With Better
|
|
194
|
+
* Auth, which then runs in the API, BETTER_AUTH_URL is the API's public URL, unless the env file
|
|
195
|
+
* names another public one, and the secret is generated unless the service already has one.
|
|
196
|
+
*/
|
|
197
|
+
export function planApiAlone(input) {
|
|
198
|
+
const plan = planVariables(input);
|
|
199
|
+
const configured = input.envFile.BETTER_AUTH_URL;
|
|
200
|
+
if (input.modules.auth === "better-auth")
|
|
201
|
+
plan.set.BETTER_AUTH_URL = configured && !isLocalUrl(configured) ? configured : input.url;
|
|
202
|
+
// The image listens on every interface: its Dockerfile sets HOST.
|
|
203
|
+
plan.unset = plan.unset.filter((key) => plan.set[key] === undefined && key !== "HOST");
|
|
204
|
+
return plan;
|
|
205
|
+
}
|
|
191
206
|
/**
|
|
192
207
|
* DATABASE_URL_DIRECT for a database hosted elsewhere: the env file's, or for a pooled Neon URL the
|
|
193
208
|
* same URL without -pooler. Never next to the platform's own database (no external URL), where it
|
|
@@ -307,7 +322,8 @@ export const railwayIacPath = ".railway/railway.ts";
|
|
|
307
322
|
* the variables stay managed by `deploy` and the dashboard, and the region is Railway's default.
|
|
308
323
|
*/
|
|
309
324
|
export function railwayIacSource(service, services) {
|
|
310
|
-
|
|
325
|
+
// A frontend and its API, or an API without a web app: each app builds its own Dockerfile.
|
|
326
|
+
if (services && Object.values(services).some(({ role }) => role !== "fullstack")) {
|
|
311
327
|
const identifier = (slot) => slot.replace(/[^a-zA-Z0-9_$]/g, "_");
|
|
312
328
|
const apiEntry = Object.entries(services).find(([, item]) => item.role === "api");
|
|
313
329
|
const api = apiEntry?.[1];
|
|
@@ -651,10 +667,16 @@ export const fullstackService = {
|
|
|
651
667
|
healthPath: "/api/health",
|
|
652
668
|
platforms: ["docker"],
|
|
653
669
|
};
|
|
654
|
-
/**
|
|
670
|
+
/**
|
|
671
|
+
* The service the public reaches: a frontend, whose API stays private, else the full-stack app,
|
|
672
|
+
* else an API without a web app, which its mobile app calls directly.
|
|
673
|
+
*/
|
|
655
674
|
export function publicService(services) {
|
|
656
675
|
const apps = Object.values(services);
|
|
657
|
-
return apps.find(({ role }) => role === "frontend") ??
|
|
676
|
+
return (apps.find(({ role }) => role === "frontend") ??
|
|
677
|
+
apps.find(({ role }) => role === "fullstack") ??
|
|
678
|
+
apps.find(({ role }) => role === "api") ??
|
|
679
|
+
fullstackService);
|
|
658
680
|
}
|
|
659
681
|
/**
|
|
660
682
|
* The values an app's image compiles in, which its Dockerfile takes as build arguments, from the
|
|
@@ -671,8 +693,8 @@ export function buildArgumentKeys(service, keys) {
|
|
|
671
693
|
* ports: it serves DOMAIN over HTTPS, with certificates it obtains and renews. `frontendKeys` are
|
|
672
694
|
* the variables the public app's env example lists: the values its image compiles in become build
|
|
673
695
|
* arguments, and a frontend's others runtime variables, such as Better Auth's when the frontend's
|
|
674
|
-
* server runs auth.
|
|
675
|
-
* https://DOMAIN.
|
|
696
|
+
* server runs auth. For an API without a web app, the public app, they are the API's. Compose reads
|
|
697
|
+
* their values from its --env-file; the public URL defaults to https://DOMAIN.
|
|
676
698
|
*/
|
|
677
699
|
export function dockerComposeSource(project, services, frontendKeys = []) {
|
|
678
700
|
const apps = Object.values(services);
|
|
@@ -686,22 +708,27 @@ export function dockerComposeSource(project, services, frontendKeys = []) {
|
|
|
686
708
|
// client-address settings default to Caddy's.
|
|
687
709
|
const runtimeKeys = frontendKeys.filter((key) => !key.startsWith("VITE_") && !["API_URL", "PORT", ...clientAddressKeys].includes(key));
|
|
688
710
|
// Better Auth runs in the frontend's server: the API asks it for the session over the Compose network.
|
|
689
|
-
const authUrl = api && frontendKeys.includes("BETTER_AUTH_URL") ? `http://${web.id}:${web.port}` : null;
|
|
711
|
+
const authUrl = api && api !== web && frontendKeys.includes("BETTER_AUTH_URL") ? `http://${web.id}:${web.port}` : null;
|
|
690
712
|
const environment = (service) => {
|
|
713
|
+
// A frontend has no env_file: CLIENT_IP_HEADER from the env file needs a line of its own.
|
|
714
|
+
const address = [
|
|
715
|
+
" # Caddy, the one proxy in front, sets X-Forwarded-For to the client's address.",
|
|
716
|
+
...(service.role === "frontend" ? clientAddressKeys : Object.keys(behindCaddy)).map((key) => ` ${key}: ${fromEnv(key, behindCaddy[key])}`),
|
|
717
|
+
];
|
|
691
718
|
if (service.role === "api") {
|
|
719
|
+
// The API listens on every interface, whatever HOST the env file has from development.
|
|
720
|
+
if (service === web) {
|
|
721
|
+
// Without a web app, the API is the public app, and runs Better Auth itself.
|
|
722
|
+
const auth = frontendKeys.includes("BETTER_AUTH_URL") ? [` BETTER_AUTH_URL: ${fromEnv("BETTER_AUTH_URL", publicUrl)}`] : [];
|
|
723
|
+
return [" HOST: 0.0.0.0", ...address, ...auth];
|
|
724
|
+
}
|
|
692
725
|
return [
|
|
693
|
-
// The API listens on every interface, whatever HOST the env file has from development.
|
|
694
726
|
" HOST: 0.0.0.0",
|
|
695
727
|
` # Reached only through ${web.id}, which passes on the client's address as the one X-Forwarded-For entry.`,
|
|
696
728
|
...clientAddressKeys.map((key) => ` ${key}: ${JSON.stringify(behindWeb[key] ?? "")}`),
|
|
697
729
|
...(authUrl ? [` BETTER_AUTH_URL: ${authUrl}`] : []),
|
|
698
730
|
];
|
|
699
731
|
}
|
|
700
|
-
// A frontend has no env_file: CLIENT_IP_HEADER from the env file needs a line of its own.
|
|
701
|
-
const address = [
|
|
702
|
-
" # Caddy, the one proxy in front, sets X-Forwarded-For to the client's address.",
|
|
703
|
-
...(service.role === "frontend" ? clientAddressKeys : Object.keys(behindCaddy)).map((key) => ` ${key}: ${fromEnv(key, behindCaddy[key])}`),
|
|
704
|
-
];
|
|
705
732
|
if (service.role !== "frontend")
|
|
706
733
|
return [...appUrlKeys.map((key) => ` ${key}: ${fromEnv(key, publicUrl)}`), ...address];
|
|
707
734
|
return [
|
|
@@ -4,7 +4,11 @@ export const MANIFEST_FILE = "clubedge.template.json";
|
|
|
4
4
|
export class ManifestError extends Error {
|
|
5
5
|
name = "ManifestError";
|
|
6
6
|
}
|
|
7
|
-
export const stackRoles = ["fullstack", "frontend", "api", "shell"];
|
|
7
|
+
export const stackRoles = ["fullstack", "frontend", "api", "shell", "mobile"];
|
|
8
|
+
/** The template schema each role came with: a Starter on an older schema is read without it. */
|
|
9
|
+
export const roleSchema = { fullstack: 4, frontend: 4, api: 4, shell: 5, mobile: 6 };
|
|
10
|
+
/** Whether a role's apps run a server: a shell and a mobile app are installed on devices instead. */
|
|
11
|
+
export const runsServer = (role) => role !== "shell" && role !== "mobile";
|
|
8
12
|
/**
|
|
9
13
|
* The slot for the app itself. Starters before schema 4 call it the framework, and projects
|
|
10
14
|
* record their choice for it as `framework`.
|