@clubedge/create-clubedge-app 0.29.0 → 0.30.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 +17 -0
- package/README.md +49 -13
- package/dist/cli/args.js +14 -3
- package/dist/cli/ui.js +16 -4
- package/dist/core/deploy.js +43 -2
- package/dist/core/health.js +51 -0
- package/dist/core/neon.js +314 -0
- package/dist/core/railway.js +21 -7
- package/dist/deploy-preview.js +213 -0
- package/dist/deploy-vercel.js +3 -3
- package/dist/deploy.js +19 -2
- package/dist/index.js +25 -2
- package/dist/inspect.js +15 -1
- package/dist/modify.js +29 -0
- package/dist/neon.js +187 -0
- package/dist/try.js +36 -3
- package/package.json +3 -3
- package/template/.env.example +7 -0
- package/template/.github/workflows/preview.yml +166 -0
- package/template/README.md +14 -1
- package/template/SETUP.md +37 -0
- package/template/apps/start/.env.example +7 -0
- package/template/apps/start/package.json +1 -1
- package/template/apps/start/scripts/check-services.ts +19 -0
- package/template/apps/start/src/env/production.test.ts +17 -0
- package/template/apps/start/src/env/production.ts +12 -0
- package/template/apps/start/src/env/server.ts +2 -0
- package/template/apps/start/src/server/jobs.ts +6 -2
- package/template/apps/web/package.json +1 -1
- package/template/apps/web/scripts/check-services.ts +19 -0
- package/template/apps/web/src/env/production.test.ts +17 -0
- package/template/apps/web/src/env/production.ts +12 -0
- package/template/apps/web/src/env/server.ts +2 -0
- package/template/apps/web/src/server/jobs.ts +6 -2
- package/template/clubedge.template.json +9 -0
- package/template/package.json +1 -1
- package/template/packages/auth/package.json +1 -1
- package/template/packages/auth-better-auth/drizzle.config.ts +2 -2
- package/template/packages/auth-better-auth/package.json +1 -1
- package/template/packages/auth-better-auth/scripts/migrate.ts +2 -2
- package/template/packages/auth-supabase/package.json +1 -1
- package/template/packages/cache/package.json +1 -1
- package/template/packages/cache-redis/package.json +1 -1
- package/template/packages/core/package.json +1 -1
- package/template/packages/db/drizzle.config.ts +2 -2
- package/template/packages/db/package.json +2 -1
- package/template/packages/db/scripts/load-env.ts +9 -0
- package/template/packages/db/scripts/migrate.ts +2 -2
- package/template/packages/db/src/index.ts +1 -0
- package/template/packages/db/src/urls.test.ts +32 -0
- package/template/packages/db/src/urls.ts +32 -0
- 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-pg-boss/package.json +1 -1
- package/template/packages/local-db/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/drizzle.config.ts +2 -2
- package/template/packages/payments/package.json +1 -1
- package/template/packages/payments/scripts/migrate.ts +2 -2
- 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/scripts/migrate.mjs +4 -2
- package/template.lock.json +5 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `create-clubedge-app` are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.30.0
|
|
6
|
+
|
|
7
|
+
- **`--infra neon`** gives a project a hosted Neon database with nothing to install and no account (#84):
|
|
8
|
+
- The CLI creates a free database through Neon's claimable Postgres. It writes the pooled `DATABASE_URL` and the direct `DATABASE_URL_DIRECT` into `apps/web/.env.local`, and the next steps include `pnpm db:migrate`.
|
|
9
|
+
- Neon keeps an unclaimed database for 72 hours. `--no-neon` creates nothing, and a project is still created when Neon can't be reached.
|
|
10
|
+
- `pnpm clubedge add infra neon` switches an existing project and creates a database when `DATABASE_URL` still held the old infrastructure's example value.
|
|
11
|
+
- **`pnpm clubedge neon`** shows the database's status and hours left (#84):
|
|
12
|
+
- `neon claim` opens Neon's page that moves it into your free Neon account. Claiming resets the password, and the CLI says where to copy the new connection strings from.
|
|
13
|
+
- `neon create` makes a new database, for example after one expired.
|
|
14
|
+
- **`doctor`** checks the database URLs (#84): pooled for the app, direct without `-pooler`, both for the same database. It also warns when a free database has less than a day left.
|
|
15
|
+
- **`deploy`** sends `DATABASE_URL_DIRECT` with a `DATABASE_URL` from the env file, and derives it from a pooled Neon URL when the file has none. It never sends one next to the platform's own database, repoints one left from an earlier database, and warns before deploying a database that is still unclaimed (#84).
|
|
16
|
+
- **`deploy --preview`** deploys the current Git branch next to production on Vercel (a preview deployment) or Railway (an environment copied from production) (#84):
|
|
17
|
+
- With `NEON_API_KEY` and `NEON_PROJECT_ID`, the preview gets its own Neon branch, `preview/<git branch>`, with this branch's migrations applied.
|
|
18
|
+
- On Vercel, the branch's URLs are set as Preview variables of the Git branch, which needs the Vercel project connected to its Git repository.
|
|
19
|
+
- **`try --infra neon`** creates the database on your machine and migrates it on the VM. An uploaded Neon project sends its two URLs (#84).
|
|
20
|
+
- Default to Starter `v0.19.0` (#85). It adds `infra neon`, the optional `DATABASE_URL_DIRECT` that migrations and the jobs worker use, and a preview environment with its own Neon branch for each pull request.
|
|
21
|
+
|
|
5
22
|
## 0.29.0
|
|
6
23
|
|
|
7
24
|
- `pnpm clubedge deploy` deploys to Netlify and Render (experimental) (#82), so it now covers Railway, Fly.io, Vercel, Netlify, and Render:
|
package/README.md
CHANGED
|
@@ -80,7 +80,7 @@ Authentication, file storage, caching, email, payments, background jobs, observa
|
|
|
80
80
|
| `--jobs` | `pg-boss`, `none` | `none` |
|
|
81
81
|
| `--observability` | `otel` (OpenTelemetry, to any OTLP collector), `sentry`, `none` | `none` |
|
|
82
82
|
| `--deploy` | `docker` (any container host), `fly`, `railway`, `render`, `vercel`, `netlify` | `docker` |
|
|
83
|
-
| `--infra` | `docker` (Docker Compose), `supabase` (Supabase CLI), `local` (no Docker: PGlite and a local folder), `none` | `docker` |
|
|
83
|
+
| `--infra` | `docker` (Docker Compose), `supabase` (Supabase CLI), `local` (no Docker: PGlite and a local folder), `neon` (a free hosted Neon database), `none` | `docker` |
|
|
84
84
|
|
|
85
85
|
```sh
|
|
86
86
|
# Only the monorepo, UI, database, and tooling
|
|
@@ -129,7 +129,7 @@ pnpm dlx @clubedge/create-clubedge-app my-app --yes --no-git --framework tanstac
|
|
|
129
129
|
pnpm dlx @clubedge/create-clubedge-app my-app --ref main
|
|
130
130
|
|
|
131
131
|
# Reproduce a specific Starter release or commit
|
|
132
|
-
pnpm dlx @clubedge/create-clubedge-app my-app --ref v0.
|
|
132
|
+
pnpm dlx @clubedge/create-clubedge-app my-app --ref v0.19.0
|
|
133
133
|
pnpm dlx @clubedge/create-clubedge-app my-app --ref <commit-sha>
|
|
134
134
|
|
|
135
135
|
# Test local Starter changes before releasing them
|
|
@@ -148,7 +148,7 @@ pnpm clubedge add email resend payments stripe # several modules as
|
|
|
148
148
|
pnpm clubedge remove storage payments
|
|
149
149
|
pnpm dlx @clubedge/create-clubedge-app add infra local --dry-run
|
|
150
150
|
pnpm dlx @clubedge/create-clubedge-app@latest upgrade # to the newest CLI's Starter release
|
|
151
|
-
pnpm dlx @clubedge/create-clubedge-app upgrade --ref v0.
|
|
151
|
+
pnpm dlx @clubedge/create-clubedge-app upgrade --ref v0.19.0 # or a specific tag, branch, or commit
|
|
152
152
|
```
|
|
153
153
|
|
|
154
154
|
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.
|
|
@@ -163,6 +163,29 @@ Several modules in one command are applied as one change, and their requirements
|
|
|
163
163
|
|
|
164
164
|
The project must be a Git repository without uncommitted changes, so you can review the result with `git diff` and undo it. Pass `--force` to skip this check. Add `--no-install` to skip `pnpm install`. Afterward, run `pnpm db:migrate` and `pnpm check:services` if the change added tables or services.
|
|
165
165
|
|
|
166
|
+
## A hosted database with Neon
|
|
167
|
+
|
|
168
|
+
`--infra neon` gives the project a [Neon](https://neon.com) PostgreSQL database with nothing to install and no account:
|
|
169
|
+
|
|
170
|
+
```sh
|
|
171
|
+
pnpm dlx @clubedge/create-clubedge-app my-app --infra neon
|
|
172
|
+
cd my-app
|
|
173
|
+
pnpm db:migrate
|
|
174
|
+
pnpm dev
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
The CLI creates a free database through Neon's [claimable Postgres](https://neon.com/docs/reference/claimable-postgres) and writes two URLs into `apps/web/.env.local`: `DATABASE_URL`, the pooled connection the app uses, and `DATABASE_URL_DIRECT`, the direct one that migrations and the background jobs worker use, since a transaction pooler can break them. Neon keeps an unclaimed database for 72 hours, with up to 100 MB:
|
|
178
|
+
|
|
179
|
+
```sh
|
|
180
|
+
pnpm clubedge neon # where it stands and how long it has left
|
|
181
|
+
pnpm clubedge neon claim # opens Neon's page that moves it into your (free) Neon account
|
|
182
|
+
pnpm clubedge neon create # a new database, for example after one expired
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Claiming keeps the data but resets the password: copy the pooled and direct connection strings from the Neon console into the two variables afterward. To use a Neon project you already have, paste its URLs instead, or pass `--no-neon` to create nothing. `pnpm clubedge add infra neon` switches an existing project and creates a database when `DATABASE_URL` held the old infrastructure's example value. The CLI keeps what it needs to claim each database in `neon.json` in its config directory (`~/.config/clubedge`, or `%APPDATA%\clubedge` on Windows); treat it as a secret.
|
|
186
|
+
|
|
187
|
+
`doctor` checks the two URLs (pooled for the app, direct without `-pooler`, both from the same database) and warns when a free database has less than a day left. `deploy` sends `DATABASE_URL_DIRECT` with a `DATABASE_URL` from the env file, derives it from a pooled Neon URL when the file has none, and warns before deploying a database that is still unclaimed.
|
|
188
|
+
|
|
166
189
|
## Check a project
|
|
167
190
|
|
|
168
191
|
Two read-only commands help you keep a project healthy:
|
|
@@ -185,9 +208,9 @@ pnpm dlx @clubedge/create-clubedge-app try --framework tanstack-start --auth non
|
|
|
185
208
|
|
|
186
209
|
The VM needs no Railway account and lasts 60 minutes; to keep it, open the claim link the CLI prints within 24 hours after, which moves the VM, its files, and its URL into a Railway account. Keep that link private. Until the VM is claimed, the preview opens only from the IP address that created it (not from another device on mobile data, or through a VPN); claiming makes the URL shareable. Railway allows 3 free VMs per network per day.
|
|
187
210
|
|
|
188
|
-
The VM has no Docker, so the project uses `--infra local` (PGlite) and Better Auth unless you choose `--auth none`, with email off; other module flags apply as usual.
|
|
211
|
+
The VM has no Docker, so the project uses `--infra local` (PGlite) and Better Auth unless you choose `--auth none`, with email off; other module flags apply as usual. With `--infra neon`, the CLI creates a free Neon database first, the VM migrates it, and `neon claim` (run outside a project) keeps it after the VM is gone.
|
|
189
212
|
|
|
190
|
-
Inside a project, `pnpm clubedge try` uploads that project instead, to share work in progress without deploying it. It sends what Git would commit (tracked files, plus untracked ones that are not ignored) and never `.env` files other than examples, `node_modules`, or build output; on the VM, the env file is made from `.env.example` as `create` does. The project must use `infra local` or `none`, and not Supabase Auth; `pnpm clubedge add infra local` switches a Docker project. Add `--dry-run` to list the files without uploading anything.
|
|
213
|
+
Inside a project, `pnpm clubedge try` uploads that project instead, to share work in progress without deploying it. It sends what Git would commit (tracked files, plus untracked ones that are not ignored) and never `.env` files other than examples, `node_modules`, or build output; on the VM, the env file is made from `.env.example` as `create` does. The project must use `infra local`, `neon`, or `none`, and not Supabase Auth; with `neon`, the VM gets `DATABASE_URL` and `DATABASE_URL_DIRECT` from your env file; `pnpm clubedge add infra local` switches a Docker project. Add `--dry-run` to list the files without uploading anything.
|
|
191
214
|
|
|
192
215
|
When the app is ready, the CLI opens the preview in your browser (`--no-open` turns that off) and asks whether to share it: claiming the VM, a free Railway sign-up, is what makes the URL work for anyone. `--claim` opens the claim page without asking. `try --status` shows the last VM's preview, time left, and claim link; it connects only while the VM is running, so it never creates a new one. To open the app from another network yourself, forward it: `ssh -i <key> -N -L 3000:127.0.0.1:3000 railway.new`, then http://localhost:3000 (the CLI prints the exact command). The CLI asks before creating anything (`--yes` agrees), keeps its own SSH key in your config directory (`~/.config/clubedge`, or `%APPDATA%\clubedge` on Windows) apart from `~/.ssh`, and connects only to Railway's known host key. Railway's [terms](https://railway.com/legal/terms) apply, and Railway can change or pause its free VM.
|
|
193
216
|
|
|
@@ -219,32 +242,44 @@ In a project with `--deploy netlify`, `pnpm clubedge deploy` uses [Netlify's CLI
|
|
|
219
242
|
|
|
220
243
|
In a project with `--deploy render`, `pnpm clubedge deploy` uses Render's API. Render builds the Dockerfile from your Git repository, so push the project to GitHub, GitLab, or Bitbucket first, with Render's access to it granted in the dashboard; `deploy` warns about commits that aren't pushed. It signs in with an API key: `RENDER_API_KEY`, or else it opens the dashboard's API keys page and asks you to paste one. It creates or reuses a web service named after the package, on the Docker runtime and a starter instance (pre-deploy commands need a paid type), which runs `node /app/migrate.mjs` before each release, checks `/api/health`, and deploys again on each push, as `render.yaml` describes. `DATABASE_URL` comes from the env file (Render Postgres, Neon, Supabase, ...). It sets the production variables: the env file's values, the service's `*.onrender.com` URL, and a generated `BETTER_AUTH_SECRET` that later runs keep. Then it starts a deploy and waits until it is live. When Render names the app something other than `https://<service>.onrender.com`, `deploy` sets that URL and deploys again, since the build compiles it in. Running it again reuses the service. Render bills the instance on your plan.
|
|
221
244
|
|
|
245
|
+
### Preview deployments
|
|
246
|
+
|
|
247
|
+
In a project that deploys to Vercel or Railway, `pnpm clubedge deploy --preview` deploys the current Git branch next to production, after a first `deploy`:
|
|
248
|
+
|
|
249
|
+
```sh
|
|
250
|
+
NEON_API_KEY=... NEON_PROJECT_ID=... pnpm clubedge deploy --preview
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
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.
|
|
254
|
+
|
|
255
|
+
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.
|
|
256
|
+
|
|
222
257
|
## Version relationship
|
|
223
258
|
|
|
224
259
|
Each CLI release is tested against one specific Starter release:
|
|
225
260
|
|
|
226
261
|
```text
|
|
227
|
-
create-clubedge-app v0.
|
|
262
|
+
create-clubedge-app v0.30.0
|
|
228
263
|
|
|
|
229
264
|
v
|
|
230
|
-
Clubedge Starter v0.
|
|
231
|
-
commit
|
|
265
|
+
Clubedge Starter v0.19.0
|
|
266
|
+
commit 1fa6afcefb99f26332f08cd08ac63f6b229cafda
|
|
232
267
|
```
|
|
233
268
|
|
|
234
269
|
The current relationship is:
|
|
235
270
|
|
|
236
271
|
| CLI release | Default Starter release | Meaning |
|
|
237
272
|
| --- | --- | --- |
|
|
238
|
-
| `create-clubedge-app@0.
|
|
273
|
+
| `create-clubedge-app@0.30.0` | `clubedge-starter@v0.19.0` | The CLI's required, tested default |
|
|
239
274
|
|
|
240
275
|
This means the same CLI version always scaffolds the same Starter revision by default. The CLI does not silently follow Starter `main`.
|
|
241
276
|
|
|
242
|
-
The `v0.
|
|
243
|
-
`
|
|
277
|
+
The `v0.19.0` tag is expected to resolve to commit
|
|
278
|
+
`1fa6afcefb99f26332f08cd08ac63f6b229cafda`. The CLI verifies this before
|
|
244
279
|
scaffolding with the default reference and fails rather than silently using a
|
|
245
280
|
moved tag.
|
|
246
281
|
|
|
247
|
-
When the Starter changes, its maintainers first publish a new versioned release, such as `v0.
|
|
282
|
+
When the Starter changes, its maintainers first publish a new versioned release, such as `v0.19.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.
|
|
248
283
|
|
|
249
284
|
Every generated project records its CLI version, Starter repository, Starter ref, Starter commit, framework, and modules in `package.json` and `README.md`.
|
|
250
285
|
|
|
@@ -299,7 +334,7 @@ CI packs the CLI with `npm pack`, scaffolds clean projects from the packed tarba
|
|
|
299
334
|
1. Make and validate changes in `clubedge-starter`.
|
|
300
335
|
2. Run lint, typecheck, unit tests, browser tests, and production build.
|
|
301
336
|
3. Update the Starter package versions.
|
|
302
|
-
4. Commit `main`, create a release tag such as `v0.
|
|
337
|
+
4. Commit `main`, create a release tag such as `v0.19.0`, and publish the Starter release.
|
|
303
338
|
|
|
304
339
|
### CLI maintainers
|
|
305
340
|
|
|
@@ -338,6 +373,7 @@ Each phase ends with a Starter release and a CLI release pinned to it. The full
|
|
|
338
373
|
| 0.27.0 | v0.18.0 | Starter v0.18.0: Vercel and Netlify builds stop when a production variable is missing (`pnpm check:env`) |
|
|
339
374
|
| 0.28.0 | v0.18.0 | `deploy` to Vercel (experimental) |
|
|
340
375
|
| 0.29.0 | v0.18.0 | `deploy` to Netlify and Render (experimental) |
|
|
376
|
+
| 0.30.0 | v0.19.0 | `--infra neon` with a free Neon database, the `neon` command, `deploy --preview` with a Neon branch per preview |
|
|
341
377
|
| next | | Show the Starter's version of a conflicted file next to yours |
|
|
342
378
|
|
|
343
379
|
## Community
|
package/dist/cli/args.js
CHANGED
|
@@ -16,6 +16,8 @@ const OPTIONS = {
|
|
|
16
16
|
open: { type: "boolean", default: true },
|
|
17
17
|
claim: { type: "boolean", default: false },
|
|
18
18
|
status: { type: "boolean", default: false },
|
|
19
|
+
neon: { type: "boolean", default: true },
|
|
20
|
+
preview: { type: "boolean", default: false },
|
|
19
21
|
"env-file": { type: "string" },
|
|
20
22
|
help: { type: "boolean", short: "h", default: false },
|
|
21
23
|
version: { type: "boolean", short: "v", default: false },
|
|
@@ -90,8 +92,8 @@ export function parseCliArgs(argv) {
|
|
|
90
92
|
if (!creates && command.name !== "upgrade" && values.ref !== undefined) {
|
|
91
93
|
throw new UsageError("--ref only applies when creating or upgrading a project.");
|
|
92
94
|
}
|
|
93
|
-
if (command.name !== "deploy" && values["env-file"] !== undefined) {
|
|
94
|
-
throw new UsageError("--env-file only
|
|
95
|
+
if (command.name !== "deploy" && (values["env-file"] !== undefined || values.preview)) {
|
|
96
|
+
throw new UsageError("--env-file and --preview only apply to deploy.");
|
|
95
97
|
}
|
|
96
98
|
if (command.name !== "try" && (values.claim || values.status)) {
|
|
97
99
|
throw new UsageError("--claim and --status only apply to try.");
|
|
@@ -124,6 +126,8 @@ export function parseCliArgs(argv) {
|
|
|
124
126
|
open: values.open,
|
|
125
127
|
claim: values.claim,
|
|
126
128
|
status: values.status,
|
|
129
|
+
neon: values.neon,
|
|
130
|
+
preview: values.preview,
|
|
127
131
|
envFile: values["env-file"],
|
|
128
132
|
help: values.help,
|
|
129
133
|
version: values.version,
|
|
@@ -163,9 +167,16 @@ function parseCommand(positionals) {
|
|
|
163
167
|
}
|
|
164
168
|
if (name === "deploy") {
|
|
165
169
|
if (module !== undefined)
|
|
166
|
-
throw new UsageError("Usage: deploy [--env-file <path>], run inside a project.");
|
|
170
|
+
throw new UsageError("Usage: deploy [--preview] [--env-file <path>], run inside a project.");
|
|
167
171
|
return { name };
|
|
168
172
|
}
|
|
173
|
+
if (name === "neon") {
|
|
174
|
+
const action = module ?? "status";
|
|
175
|
+
if (rest.length > 1 || (action !== "status" && action !== "claim" && action !== "create")) {
|
|
176
|
+
throw new UsageError("Usage: neon [status|claim|create], run inside a project with --infra neon.");
|
|
177
|
+
}
|
|
178
|
+
return { name, action };
|
|
179
|
+
}
|
|
169
180
|
if (name === "try") {
|
|
170
181
|
if (module !== undefined) {
|
|
171
182
|
throw new UsageError("Usage: try [--framework <id>] [module flags] (a new project), or try inside a project to upload it.");
|
package/dist/cli/ui.js
CHANGED
|
@@ -93,9 +93,10 @@ Usage:
|
|
|
93
93
|
pnpm dlx @clubedge/create-clubedge-app doctor (inside a project)
|
|
94
94
|
pnpm dlx @clubedge/create-clubedge-app try [options] (experimental)
|
|
95
95
|
pnpm dlx @clubedge/create-clubedge-app deploy [options] (inside a project)
|
|
96
|
+
pnpm dlx @clubedge/create-clubedge-app neon [claim|create] (--infra neon)
|
|
96
97
|
|
|
97
98
|
Inside a project with the clubedge script, use pnpm clubedge add, remove,
|
|
98
|
-
upgrade, status, doctor, try, or
|
|
99
|
+
upgrade, status, doctor, try, deploy, or neon. Without a module or option, add and remove ask
|
|
99
100
|
for them.
|
|
100
101
|
|
|
101
102
|
add and remove change modules of an existing project, several at once as one
|
|
@@ -108,11 +109,16 @@ Usage:
|
|
|
108
109
|
newer Starter is available. doctor checks Node.js, pnpm, the dependencies, the
|
|
109
110
|
lockfile, and the env file, then runs pnpm check:services; it changes nothing.
|
|
110
111
|
|
|
112
|
+
--infra neon creates a free Neon database (no account; 72 hours unless claimed) and
|
|
113
|
+
writes its pooled and direct URLs into the env file. neon shows how long it has left,
|
|
114
|
+
neon claim opens the page that keeps it in a Neon account, and neon create makes a
|
|
115
|
+
new one.
|
|
116
|
+
|
|
111
117
|
try creates a new project on a free, temporary Railway VM (no account, 60 minutes)
|
|
112
118
|
and prints its preview URL and the link to claim the VM. The VM has no Docker, so
|
|
113
|
-
it uses --infra local (PGlite) and Better Auth, with email off.
|
|
114
|
-
try uploads that project instead (what Git would commit, never .env
|
|
115
|
-
node_modules); it needs infra local or none. It asks first, keeps its own SSH key
|
|
119
|
+
it uses --infra local (PGlite) or --infra neon, and Better Auth, with email off.
|
|
120
|
+
Inside a project, try uploads that project instead (what Git would commit, never .env
|
|
121
|
+
files or node_modules); it needs infra local, neon, or none. It asks first, keeps its own SSH key
|
|
116
122
|
apart from ~/.ssh, and opens the preview when ready. Experimental: Railway can
|
|
117
123
|
change or pause its free VM.
|
|
118
124
|
|
|
@@ -127,6 +133,9 @@ ${selectionHelp(manifest)}
|
|
|
127
133
|
--no-open try, deploy (Vercel, Netlify, Render): do not open the URL in the browser
|
|
128
134
|
--claim try: open the page that claims the VM, to share the preview
|
|
129
135
|
--status try: the last VM's preview, time left, and claim link
|
|
136
|
+
--no-neon --infra neon: don't create a Neon database (set DATABASE_URL yourself)
|
|
137
|
+
--preview deploy (Vercel, Railway): a preview of the current Git branch, with its
|
|
138
|
+
own Neon branch when NEON_API_KEY and NEON_PROJECT_ID are set
|
|
130
139
|
--env-file <path> deploy: production values (default: .env.production when there is one)
|
|
131
140
|
-y, --yes Accept defaults and never prompt (default directory: my-app)
|
|
132
141
|
--no-install Skip dependency installation
|
|
@@ -147,6 +156,9 @@ Examples:
|
|
|
147
156
|
pnpm dlx @clubedge/create-clubedge-app@latest upgrade
|
|
148
157
|
pnpm clubedge status
|
|
149
158
|
pnpm clubedge doctor --no-services
|
|
159
|
+
pnpm dlx @clubedge/create-clubedge-app my-app --infra neon
|
|
160
|
+
pnpm clubedge neon claim
|
|
161
|
+
pnpm clubedge deploy --preview
|
|
150
162
|
pnpm dlx @clubedge/create-clubedge-app try --framework tanstack-start
|
|
151
163
|
|
|
152
164
|
Learn more:
|
package/dist/core/deploy.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// What `deploy` sets up on Railway for a project, as pure functions: the service name, the
|
|
2
2
|
// production variables, and reading the Railway CLI's output. src/deploy.ts runs the CLI.
|
|
3
|
+
import { isNeonPooledUrl, neonDirectUrl } from "./neon.js";
|
|
3
4
|
/** The port the production image listens on (the Starter's Dockerfile sets PORT=3000). */
|
|
4
5
|
export const appPort = 3000;
|
|
5
6
|
/** Railway's names for the databases `railway add --database` creates, used in references. */
|
|
@@ -83,7 +84,7 @@ export function planVariables(input) {
|
|
|
83
84
|
const set = { ...provided, APP_URL: url, NEXT_PUBLIC_APP_URL: url };
|
|
84
85
|
if (modules.auth === "better-auth" && !existing.BETTER_AUTH_SECRET)
|
|
85
86
|
set.BETTER_AUTH_SECRET = secret();
|
|
86
|
-
const databaseKeys = Object.values(databaseVariables);
|
|
87
|
+
const databaseKeys = [...Object.values(databaseVariables), "DATABASE_URL_DIRECT"];
|
|
87
88
|
for (const [key, value] of Object.entries(envFile)) {
|
|
88
89
|
if (value === "" || databaseKeys.includes(key))
|
|
89
90
|
continue;
|
|
@@ -93,9 +94,49 @@ export function planVariables(input) {
|
|
|
93
94
|
set[key] = value;
|
|
94
95
|
}
|
|
95
96
|
Object.assign(set, external);
|
|
96
|
-
const
|
|
97
|
+
const direct = directDatabaseUrl(external.DATABASE_URL, envFile.DATABASE_URL_DIRECT);
|
|
98
|
+
if (direct)
|
|
99
|
+
set.DATABASE_URL_DIRECT = direct;
|
|
100
|
+
// A direct URL left from an earlier database would send migrations there: point it at the
|
|
101
|
+
// database the app now uses, or blank it (blank counts as unset).
|
|
102
|
+
else if (existing.DATABASE_URL_DIRECT)
|
|
103
|
+
set.DATABASE_URL_DIRECT = set.DATABASE_URL ?? "";
|
|
104
|
+
// Optional: without it, migrations and the jobs worker use DATABASE_URL.
|
|
105
|
+
const unset = keys.filter((key) => set[key] === undefined && !existing[key] && key !== "DATABASE_URL_DIRECT");
|
|
97
106
|
return { set, unset };
|
|
98
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* DATABASE_URL_DIRECT for a database hosted elsewhere: the env file's, or for a pooled Neon URL the
|
|
110
|
+
* same URL without -pooler. Never next to the platform's own database (no external URL), where it
|
|
111
|
+
* would send migrations to another database.
|
|
112
|
+
*/
|
|
113
|
+
export function directDatabaseUrl(external, direct) {
|
|
114
|
+
if (!external)
|
|
115
|
+
return null;
|
|
116
|
+
if (direct?.trim() && !isLocalUrl(direct))
|
|
117
|
+
return direct.trim();
|
|
118
|
+
return isNeonPooledUrl(external) ? neonDirectUrl(external) : null;
|
|
119
|
+
}
|
|
120
|
+
/** `railway environment list --json`: an array of environments, or an object holding them. */
|
|
121
|
+
export function railwayEnvironmentNames(output) {
|
|
122
|
+
let value;
|
|
123
|
+
try {
|
|
124
|
+
value = JSON.parse(output);
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
throw new Error("Railway listed its environments in a form deploy doesn't read. Check `railway environment list --json`.");
|
|
128
|
+
}
|
|
129
|
+
const list = Array.isArray(value) ? value : (value?.environments ?? []);
|
|
130
|
+
const items = Array.isArray(list) ? list : (list?.edges ?? []);
|
|
131
|
+
return (Array.isArray(items) ? items : [])
|
|
132
|
+
.map((item) => item?.node ?? item)
|
|
133
|
+
.map((item) => item?.name)
|
|
134
|
+
.filter((name) => typeof name === "string");
|
|
135
|
+
}
|
|
136
|
+
/** The body of `POST /v10/projects/:id/env?upsert=true`: encrypted Preview variables of one Git branch. */
|
|
137
|
+
export function vercelPreviewEnvBody(values, gitBranch) {
|
|
138
|
+
return Object.entries(values).map(([key, value]) => ({ key, value, type: "encrypted", target: ["preview"], gitBranch }));
|
|
139
|
+
}
|
|
99
140
|
/** The public domain in `railway domain` output (JSON or text). */
|
|
100
141
|
export function parseRailwayDomain(output) {
|
|
101
142
|
const domain = /([a-z0-9-]+(?:\.[a-z0-9-]+)*\.up\.railway\.app)/i.exec(output)?.[1];
|
package/dist/core/health.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Pure checks behind `status` and `doctor`: they take what the commands read from the project,
|
|
2
2
|
// the system, and npm, and return what to show, so each can be tested without a real project.
|
|
3
3
|
import { envKeys } from "./env.js";
|
|
4
|
+
import { isNeonPooledUrl, isNeonUrl, neonEndpoint } from "./neon.js";
|
|
4
5
|
/** Compares dotted versions such as "v0.13.1" and "0.12.0"; null when either is not one. */
|
|
5
6
|
export function compareVersions(a, b) {
|
|
6
7
|
const parse = (value) => {
|
|
@@ -159,3 +160,53 @@ export function checkLockfile(error) {
|
|
|
159
160
|
detail: `could not check: ${error.split("\n").find((line) => line.trim()) ?? error}`,
|
|
160
161
|
};
|
|
161
162
|
}
|
|
163
|
+
/** The database a connection URL names (its path). */
|
|
164
|
+
function databaseName(url) {
|
|
165
|
+
try {
|
|
166
|
+
return decodeURIComponent(new URL(url.trim()).pathname.slice(1));
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
return "";
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* The database URLs in the local env file: Neon's pooled URL for the app and its direct URL for
|
|
174
|
+
* migrations and the jobs worker, and how long a free Neon database the CLI created has left.
|
|
175
|
+
* Null when there is nothing to say (no Neon, no direct URL, and infra isn't neon).
|
|
176
|
+
*/
|
|
177
|
+
export function checkDatabaseUrls(input) {
|
|
178
|
+
const { env, infra, claimable, cli, envPath } = input;
|
|
179
|
+
const name = "Database URLs";
|
|
180
|
+
const url = env.DATABASE_URL ?? "";
|
|
181
|
+
const direct = env.DATABASE_URL_DIRECT ?? "";
|
|
182
|
+
if (!url) {
|
|
183
|
+
return infra === "neon"
|
|
184
|
+
? { name, status: "fail", detail: `DATABASE_URL in ${envPath} is empty`, fix: `Run \`${cli} neon create\` for a free Neon database, or paste your Neon project's pooled URL.` }
|
|
185
|
+
: null;
|
|
186
|
+
}
|
|
187
|
+
if (direct && isNeonPooledUrl(direct)) {
|
|
188
|
+
return { name, status: "fail", detail: "DATABASE_URL_DIRECT points at Neon's pooler", fix: "Use the same URL without -pooler in the host: migrations and the jobs worker need a direct session." };
|
|
189
|
+
}
|
|
190
|
+
if (claimable?.kind === "expired") {
|
|
191
|
+
return { name, status: "fail", detail: "the free Neon database in DATABASE_URL expired, and Neon deleted it", fix: `Run \`${cli} neon create\`, then \`pnpm db:migrate\`.` };
|
|
192
|
+
}
|
|
193
|
+
if (!isNeonUrl(url)) {
|
|
194
|
+
return direct ? { name, status: "ok", detail: "DATABASE_URL for the app, DATABASE_URL_DIRECT for migrations and the jobs worker" } : null;
|
|
195
|
+
}
|
|
196
|
+
if (!isNeonPooledUrl(url)) {
|
|
197
|
+
return { name, status: "warn", detail: "DATABASE_URL is Neon's direct host, which has fewer connections", fix: "Put the pooled URL (the host with -pooler) in DATABASE_URL, and this one in DATABASE_URL_DIRECT." };
|
|
198
|
+
}
|
|
199
|
+
if (!direct) {
|
|
200
|
+
return { name, status: "warn", detail: "DATABASE_URL is Neon's pooler, and DATABASE_URL_DIRECT is not set", fix: "Set DATABASE_URL_DIRECT to the same URL without -pooler in the host, for migrations and the jobs worker." };
|
|
201
|
+
}
|
|
202
|
+
if (neonEndpoint(direct) !== neonEndpoint(url) || databaseName(direct) !== databaseName(url)) {
|
|
203
|
+
return { name, status: "warn", detail: "DATABASE_URL and DATABASE_URL_DIRECT point at different Neon databases", fix: "Copy both from the same branch in the Neon console: pooled and direct." };
|
|
204
|
+
}
|
|
205
|
+
if (claimable?.kind === "unclaimed") {
|
|
206
|
+
const detail = `a free Neon database, unclaimed: ${claimable.hoursLeft} hours left`;
|
|
207
|
+
return claimable.hoursLeft < 24
|
|
208
|
+
? { name, status: "warn", detail, fix: `Keep it with \`${cli} neon claim\`, or Neon deletes it with its data.` }
|
|
209
|
+
: { name, status: "ok", detail: `${detail} (\`${cli} neon claim\` keeps it)` };
|
|
210
|
+
}
|
|
211
|
+
return { name, status: "ok", detail: "Neon, pooled for the app and direct for migrations and the jobs worker" };
|
|
212
|
+
}
|