create-basalt 1.10.0 → 1.12.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/README.md +64 -26
- package/dist/args.d.ts +1 -1
- package/dist/args.js +5 -2
- package/dist/bin-template.d.ts +13 -8
- package/dist/bin-template.js +178 -13
- package/dist/cli.js +6 -2
- package/dist/dev-entry.d.ts +23 -0
- package/dist/dev-entry.js +49 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +23 -2
- package/dist/latest-versions.js +1 -0
- package/dist/project/doctor.d.ts +11 -1
- package/dist/project/doctor.js +176 -14
- package/dist/project/production.d.ts +32 -0
- package/dist/project/production.js +74 -0
- package/dist/project/run.js +8 -4
- package/dist/project/seed-script.d.ts +18 -0
- package/dist/project/seed-script.js +32 -0
- package/dist/project/update.js +53 -11
- package/dist/stubs.d.ts +30 -0
- package/dist/stubs.js +123 -0
- package/dist/templates.d.ts +53 -7
- package/dist/templates.js +183 -55
- package/dist/versions.js +13 -13
- package/dist/wizard.js +3 -3
- package/package.json +20 -17
package/README.md
CHANGED
|
@@ -93,7 +93,7 @@ Usage: npm create basalt <name> [options]
|
|
|
93
93
|
| `--no-auth` | auth **on** | Removes authentication (`@basaltkit/auth`, `APP_SECRET`, `/auth/*` routes) |
|
|
94
94
|
| `--billing` | off | Includes subscriptions/plans (`@basaltkit/subscriptions`, example `free` and `pro` plans) |
|
|
95
95
|
| `--ui` | off | Generates the `web/` frontend (React + shadcn via `@basaltkit/admin-shadcn` + `@basaltkit/sdk`). **Forces pnpm** — see note below |
|
|
96
|
-
| `--cli` |
|
|
96
|
+
| `--no-cli` | CLI on | Skips the `basalt` CLI (by default every app gets it: `bin/basalt.ts`, the `pnpm basalt` script, `make:*` generators from `@basaltkit/generator`) |
|
|
97
97
|
| `--mcp` | off | Exposes read-only routes as MCP tools (`@basaltkit/mcp`) over HTTP at `POST /mcp` — the overview and health endpoints are opted in via `meta.mcp` |
|
|
98
98
|
| `--prisma` (alias `--db`) | off | Backs the app with PostgreSQL through Prisma: `prisma/schema.prisma`, `prisma.config.ts`, `src/db.ts`, the `@basaltkit/*-prisma` stores instead of the in-memory ones, a required `<APP>_DATABASE_URL`, the `db:*` scripts and `prismaPlugin({ assertMigrated: true })` — see *With `--prisma`* below |
|
|
99
99
|
| `--install` | off | Installs dependencies at the end (with the detected/chosen manager) |
|
|
@@ -120,7 +120,7 @@ Behavior notes (faithful to the code):
|
|
|
120
120
|
pnpm create basalt
|
|
121
121
|
|
|
122
122
|
# Full project, no questions, with everything:
|
|
123
|
-
pnpm create basalt my-app --billing --ui --
|
|
123
|
+
pnpm create basalt my-app --billing --ui --prisma --install --git
|
|
124
124
|
|
|
125
125
|
# Minimal API (no tenancy or auth), in another folder:
|
|
126
126
|
npm create basalt service-api --no-tenancy --no-auth --dir=./apps/service-api
|
|
@@ -138,10 +138,14 @@ Always:
|
|
|
138
138
|
|
|
139
139
|
```
|
|
140
140
|
my-app/
|
|
141
|
-
├── package.json # scripts: dev, start, test, typecheck, basalt (+ dev:web with --ui); create-basalt as a devDependency
|
|
141
|
+
├── package.json # scripts: dev, build, start (node dist/src/server.js), start:dev (tsx), test, typecheck, basalt (+ dev:web with --ui); create-basalt as a devDependency
|
|
142
142
|
├── .basalt/project.json # scaffold manifest: create-basalt version, options, a hash per generated file — commit it
|
|
143
143
|
├── tsconfig.json # strict TypeScript, ESM
|
|
144
|
-
├── .
|
|
144
|
+
├── tsconfig.build.json # what `pnpm build` compiles: src/ → dist/ (rootDir ".", so dist/src/server.js)
|
|
145
|
+
├── Dockerfile # multi-stage: build with the dev toolchain, run dist/src/server.js on plain node as USER node
|
|
146
|
+
├── .dockerignore # keeps .env, keys, node_modules and dist out of the build context
|
|
147
|
+
├── .env.example # MY_APP_PORT, MY_APP_HOST, MY_APP_LOG_LEVEL, NODE_ENV (+ MY_APP_APP_SECRET with auth) + the precedence warning — committed
|
|
148
|
+
├── .env # the same values for local development (+ a generated MY_APP_APP_SECRET with auth) — git-ignored, loaded by dev/basalt
|
|
145
149
|
├── .gitignore
|
|
146
150
|
├── README.md # instructions adapted to the chosen options
|
|
147
151
|
├── pnpm-workspace.yaml # esbuild allowBuilds, @basaltkit/* + create-basalt release-age exclusion (+ "web" member with --ui) + pnpm 11 notes
|
|
@@ -149,11 +153,12 @@ my-app/
|
|
|
149
153
|
│ ├── env.ts # environment variables validated with Zod (@basaltkit/env), app-prefixed
|
|
150
154
|
│ ├── app.ts # buildApp() with the chosen plugins
|
|
151
155
|
│ ├── routes.ts # GET / (friendly index) and GET /health
|
|
152
|
-
│
|
|
156
|
+
│ ├── server.ts # startup + clean shutdown on SIGINT/SIGTERM (`pnpm start` runs it compiled — loads no .env)
|
|
157
|
+
│ └── dev.ts # `pnpm dev`: loads .env, defaults NODE_ENV=development, starts server.ts
|
|
153
158
|
└── tests/app.test.ts # smoke test adapted to the options
|
|
154
159
|
```
|
|
155
160
|
|
|
156
|
-
With `--prisma`, adds `prisma/schema.prisma`, `prisma.config.ts`, `src/db.ts`, `prisma/seed.ts` (with tenancy) and the `db:generate` / `db:migrate` / `db:deploy` / `db:seed` scripts. The `basalt` script is `create-basalt --project`
|
|
161
|
+
With `--prisma`, adds `prisma/schema.prisma`, `prisma.config.ts`, `src/db.ts`, `prisma/seed.ts` (with tenancy) and the `db:generate` / `db:migrate` / `db:deploy` / `db:seed` scripts. The client is generated into `generated/prisma` (outside `src/`, so the build never copies it) and imported as `#db/client.js` through a package.json `imports` alias; `@prisma/client-runtime-utils` is a direct dependency (the generated runtime requires it by name), and `pnpm-workspace.yaml` approves the `prisma` / `@prisma/engines` build scripts. The `basalt` script is `create-basalt --project` with `--no-cli` (so `pnpm basalt update|add|doctor|info` works in every app); with `--cli` it is `tsx bin/basalt.ts`, and `bin/basalt.ts` forwards those four commands to create-basalt before booting the app. With `--ui`, adds the `web/` folder (Vite 8 + React 19 + Tailwind CSS 4 via `@tailwindcss/vite` + shadcn — Tailwind is configured in `web/src/index.css`, no `tailwind.config.js`/PostCSS — with `web/src/api.ts` built on top of `@basaltkit/sdk`; with auth on it includes a login/register screen).
|
|
157
162
|
|
|
158
163
|
### With `--ui`: running the API and frontend
|
|
159
164
|
|
|
@@ -185,11 +190,14 @@ the next restart.
|
|
|
185
190
|
```bash
|
|
186
191
|
pnpm create basalt my-app --prisma
|
|
187
192
|
cd my-app && pnpm install # postinstall runs `prisma generate`
|
|
188
|
-
|
|
189
|
-
pnpm db:migrate # prisma migrate dev — creates the tables
|
|
193
|
+
# .env already points MY_APP_DATABASE_URL at postgres://…@localhost:5432/my_app — start PostgreSQL or edit it
|
|
194
|
+
pnpm db:migrate # prisma migrate dev — creates the tables
|
|
195
|
+
pnpm db:seed # prisma db seed — the `demo` tenant
|
|
190
196
|
pnpm dev
|
|
191
197
|
```
|
|
192
198
|
|
|
199
|
+
After a schema change: `pnpm db:migrate --name <change>` writes and applies the migration (without `--name` Prisma prompts for one), then `pnpm db:generate` — Prisma 7's `migrate dev` does not regenerate the client.
|
|
200
|
+
|
|
193
201
|
**Migrations, never `prisma db push`.** The generated app boots with
|
|
194
202
|
`assertMigrated: true`, which refuses to start unless the database it reached has
|
|
195
203
|
the `_prisma_migrations` table — written by `prisma migrate dev` /
|
|
@@ -201,9 +209,9 @@ the database and host instead of a `500` on the first request. In production:
|
|
|
201
209
|
The generated `tests/app.test.ts` skips itself when no database is configured, so
|
|
202
210
|
`pnpm test` stays green on a machine without PostgreSQL. To add another Basalt
|
|
203
211
|
domain later, install its `@basaltkit/<domain>-prisma` package, run
|
|
204
|
-
`pnpm basalt prisma:sync` (
|
|
212
|
+
`pnpm basalt prisma:sync` (unless `--no-cli`) and then `pnpm db:migrate`.
|
|
205
213
|
|
|
206
|
-
###
|
|
214
|
+
### The `basalt` command line (default; `--no-cli` skips it)
|
|
207
215
|
|
|
208
216
|
```bash
|
|
209
217
|
pnpm basalt list # available commands
|
|
@@ -215,7 +223,7 @@ With pnpm 11, every `pnpm <script>` and `pnpm exec` first verifies dependencies
|
|
|
215
223
|
|
|
216
224
|
## Project commands: update, add, doctor, info
|
|
217
225
|
|
|
218
|
-
Run inside an existing Basalt app — any app, created with or without `--cli
|
|
226
|
+
Run inside an existing Basalt app — any app, created with or without the CLI (`--no-cli`),
|
|
219
227
|
by any create-basalt release:
|
|
220
228
|
|
|
221
229
|
```bash
|
|
@@ -235,7 +243,7 @@ field, then the lockfile), and output is colored unless `NO_COLOR` is set,
|
|
|
235
243
|
`--no-color` is passed or stdout is not a terminal.
|
|
236
244
|
|
|
237
245
|
**How `pnpm basalt <command>` reaches them.** New apps list `create-basalt` as a
|
|
238
|
-
devDependency.
|
|
246
|
+
devDependency. With `--no-cli` the `basalt` script is `create-basalt --project`;
|
|
239
247
|
with `--cli`, `bin/basalt.ts` handles `update`/`add`/`doctor`/`info` **before**
|
|
240
248
|
importing anything of the app (they must work while the app is broken
|
|
241
249
|
mid-upgrade) and runs the installed create-basalt — or `pnpm dlx` /
|
|
@@ -255,7 +263,7 @@ pnpm basalt update # asks, writes, installs, runs the codemods
|
|
|
255
263
|
| `--major` | Lets third-party packages cross a major too |
|
|
256
264
|
| `--only=@basaltkit` | Only the framework packages (and `create-basalt`) |
|
|
257
265
|
| `--no-install` | Writes `package.json`, skips the install and the codemods |
|
|
258
|
-
| `--no-tooling` | Leaves `bin/basalt.ts` and the `create-basalt` devDependency alone |
|
|
266
|
+
| `--no-tooling` | Leaves `bin/basalt.ts`, `src/dev.ts`, `.env.example` and the `create-basalt` devDependency alone |
|
|
259
267
|
|
|
260
268
|
The policy is the scaffold's own (the same registry code): `@basaltkit/*` and
|
|
261
269
|
`create-basalt` go to `latest` across majors — each framework major prints a
|
|
@@ -278,10 +286,20 @@ edits stay and the command says how to revert (`git checkout -- package.json …
|
|
|
278
286
|
|
|
279
287
|
Project tooling rides along (skip with `--no-tooling`): an app without the
|
|
280
288
|
`create-basalt` devDependency gets it plus a `basalt` script (and a
|
|
281
|
-
`create-basalt` entry in `minimumReleaseAgeExclude`)
|
|
282
|
-
generated by an earlier release gains the project commands
|
|
283
|
-
|
|
284
|
-
|
|
289
|
+
`create-basalt` entry in `minimumReleaseAgeExclude`); a `bin/basalt.ts`
|
|
290
|
+
generated by an earlier release gains the project commands, `.env` loading, a
|
|
291
|
+
pre-boot `upgrade` and the readable env error; a `src/dev.ts` from 1.9/1.10 gains
|
|
292
|
+
`.env` loading; and the `.env.example` header stops saying "nothing loads this
|
|
293
|
+
file". Files are patched **only** when they are byte-for-byte a known template
|
|
294
|
+
(or the manifest records them untouched); a customised one is left alone and the
|
|
295
|
+
exact snippet to paste is printed.
|
|
296
|
+
|
|
297
|
+
An app from before the production path is **offered** what it lacks:
|
|
298
|
+
`tsconfig.build.json`, a `build` script, the `Dockerfile` (pnpm apps) and
|
|
299
|
+
`.dockerignore`, and with Prisma `@prisma/client-runtime-utils`. Existing files
|
|
300
|
+
are never touched and an existing `start` script is never rewritten — the two
|
|
301
|
+
script lines to paste are printed instead. A Prisma client still generated under
|
|
302
|
+
`src/generated` gets the move instructions and no Dockerfile until it is moved.
|
|
285
303
|
|
|
286
304
|
### Adding features later
|
|
287
305
|
|
|
@@ -320,10 +338,20 @@ lockfiles; installed versus declared versions, duplicated `@basaltkit/*`
|
|
|
320
338
|
versions and unmet peer ranges; framework packages behind `latest` (skip with
|
|
321
339
|
`--offline`); the auth secret (`<PREFIX>_APP_SECRET`, from the environment or
|
|
322
340
|
`.env`, against the `minLength` in `src/env.ts` and the placeholder rules of
|
|
323
|
-
`secret()`)
|
|
341
|
+
`secret()`); every variable `src/env.ts` **requires** (declared without a
|
|
342
|
+
default — `DATABASE_URL` with `--prisma`) that is set neither in the environment
|
|
343
|
+
nor in `.env` — an error, with the fix (`cp .env.example .env`, set it, start
|
|
344
|
+
PostgreSQL); a missing `.env` next to a `.env.example` (warning); whether the Prisma client is generated and
|
|
324
345
|
migrations exist (whether they are *applied* needs a database —
|
|
325
|
-
`prisma migrate status`);
|
|
326
|
-
|
|
346
|
+
`prisma migrate status`); the `db:seed` script of create-basalt 1.8–1.11
|
|
347
|
+
(`tsx prisma/seed.ts`, which loads no `.env` — a warning with the exact
|
|
348
|
+
replacement, `"db:seed": "prisma db seed"`; `update` prints the same line and
|
|
349
|
+
never rewrites the script); `.mcp.json` when `@basaltkit/ai-mcp` is installed; dev
|
|
350
|
+
tooling declared as a runtime dependency; an outdated `bin/basalt.ts` or
|
|
351
|
+
`src/dev.ts`; and, statically, the production path (a `start` running tsx while
|
|
352
|
+
tsx is a devDependency, no `build` script, `dist/src/server.js` older than
|
|
353
|
+
`src/`, a Prisma client generated under `src/`, a missing
|
|
354
|
+
`@prisma/client-runtime-utils`). It never builds or starts the app.
|
|
327
355
|
|
|
328
356
|
`info` prints create-basalt, Node, OS, package manager, the app's features and
|
|
329
357
|
the declared/installed versions of the framework and key tools — paste it into
|
|
@@ -345,7 +373,9 @@ yours; the first `add` creates the manifest.
|
|
|
345
373
|
|
|
346
374
|
### Environment variables and `--env-file`
|
|
347
375
|
|
|
348
|
-
|
|
376
|
+
**Development loads `.env`; production does not.** `pnpm dev` (`src/dev.ts`) and `pnpm basalt` (`bin/basalt.ts`, scaffolded unless `--no-cli`) load `.env` from the project root when it exists, with `node --env-file` semantics. `pnpm start` (`src/server.ts`) loads **nothing**: in production the configuration comes from the real environment (or start it yourself with `node --env-file=…`). A new app gets a ready-made `.env` — a copy of `.env.example` with local values and, with auth, a generated `APP_SECRET` — git-ignored (`.gitignore` and `.dockerignore`) and written with mode `0600`; `.env.example` stays the committed template. `prisma.config.ts` loads the same file for the Prisma CLI.
|
|
377
|
+
|
|
378
|
+
Loading `.env` — like `node --env-file=.env` / `tsx --env-file=.env` — **never overrides a variable already exported in the shell**: in a terminal where another project exported `DATABASE_URL` or `PORT`, an app reading the generic name boots against that value and only fails on the first request that touches it.
|
|
349
379
|
|
|
350
380
|
The scaffold closes that trap instead of only warning about it: `src/env.ts` passes an **app-specific prefix** derived from the project name (`my-saas` → `MY_SAAS`) to `defineEnv`, and `.env.example` uses the prefixed names.
|
|
351
381
|
|
|
@@ -366,6 +396,8 @@ NODE_ENV=development # never prefixed — a Node-wide convention
|
|
|
366
396
|
# MY_SAAS_APP_SECRET= # with auth
|
|
367
397
|
```
|
|
368
398
|
|
|
399
|
+
When a variable is invalid or missing, `pnpm basalt <command>` does not print a stack trace: it lists the variables (`EnvValidationError.report`), says where they were read from (the environment, and `.env` when it exists) and how to fix it — `cp .env.example .env`, fill them in, and for `DATABASE_URL` start PostgreSQL — then exits 1. `BASALT_DEBUG=1` (or `--debug`) shows the stack. The database gets the same treatment: PostgreSQL not answering (`ECONNREFUSED`, Prisma `P1001`, or an `assertMigrated` that could not query it) and an unmigrated database (`PRISMA_NOT_MIGRATED`) print what failed, the database in use — `postgres://host:port/name` and the variable it came from, never the credentials — and the fix (start PostgreSQL, e.g. `docker compose up -d`; check `MY_SAAS_DATABASE_URL`; `pnpm db:migrate`). Any other boot error keeps its stack trace. `pnpm basalt upgrade` (the `@basaltkit/cli` codemods) never boots the app, so it runs even when the environment is incomplete.
|
|
400
|
+
|
|
369
401
|
Each variable is read as `MY_SAAS_<NAME>` first and **falls back** to the bare `<NAME>`, so a deployment that already exports the generic names keeps booting — while a stray `PORT` in your shell no longer wins. The rest of the app is unchanged: the keys stay bare (`env.PORT`). To require the prefixed names only, edit the generated file to `prefix: { value: 'MY_SAAS', fallback: false }`. Full rules: [`@basaltkit/env`](https://github.com/basaltkit/basalt/tree/main/packages/env#app-specific-prefix-prefix).
|
|
370
402
|
|
|
371
403
|
### Programmatic usage (Advanced)
|
|
@@ -418,7 +450,7 @@ Exported from `create-basalt` (in addition to the `create-basalt` executable):
|
|
|
418
450
|
| Field | Type | Description |
|
|
419
451
|
| --- | --- | --- |
|
|
420
452
|
| `dir` | `string` | Absolute path of the created folder |
|
|
421
|
-
| `files` | `string[]` | Files written (relative, sorted) |
|
|
453
|
+
| `files` | `string[]` | Files written (relative, sorted) — includes the development `.env` |
|
|
422
454
|
| `options` | `ProjectOptions` | The options actually applied (with defaults resolved) |
|
|
423
455
|
| `versions` | `VersionResolution \| undefined` | With `resolveLatest`: `{ versions, resolved, failed, heldBack, tooFresh, registry }` — the final ranges, which packages got `^<latest>`, which kept the fallback after a registry failure, third-party packages held back because their latest is a new major, and third-party packages whose latest is not provably older than the release-age window (`{ name, latest, range, reason: 'recent' \| 'unknown' }`) |
|
|
424
456
|
|
|
@@ -469,9 +501,12 @@ Outside an interactive terminal (no TTY) the questions are skipped. Always pass
|
|
|
469
501
|
Automatic installation failed (network, Node version, etc.). Go into the folder and run `pnpm install` (or the indicated manager) to see the real error.
|
|
470
502
|
|
|
471
503
|
**I started the app and `GET /auth/login` gives a secret error.**
|
|
472
|
-
With auth on, `src/env.ts` requires `APP_SECRET` — read as `MY_SAAS_APP_SECRET`, with at least 32 characters (`secret()` supplies a development default only when `NODE_ENV` is explicitly `development`/`test`).
|
|
504
|
+
With auth on, `src/env.ts` requires `APP_SECRET` — read as `MY_SAAS_APP_SECRET`, with at least 32 characters (`secret()` supplies a development default only when `NODE_ENV` is explicitly `development`/`test`). For development the scaffold's `.env` carries a generated one; in production export your own (`openssl rand -base64 48`) — `pnpm start` does not read `.env`.
|
|
473
505
|
|
|
474
|
-
|
|
506
|
+
**`The app cannot start — the database did not answer`.**
|
|
507
|
+
PostgreSQL is not running, or `MY_SAAS_DATABASE_URL` points at the wrong host/port (the message shows which, without credentials). Start it (`docker compose up -d`, or your local service) or fix the URL in `.env`; on a fresh database, `pnpm db:migrate`. `pnpm dev` still prints the raw error (the explainer lives in `bin/basalt.ts` only, so `src/dev.ts` stays a few lines); `pnpm basalt routes` gives the explained version.
|
|
508
|
+
|
|
509
|
+
**With `--prisma`, the app refuses to boot: `PRISMA_NOT_MIGRATED`** (from `pnpm basalt`: `The app cannot start — the database is not migrated`).
|
|
475
510
|
`assertMigrated` reached a database without the `_prisma_migrations` table. The
|
|
476
511
|
message names the database and host it actually reached (never the
|
|
477
512
|
credentials): either it is not the database you meant — check
|
|
@@ -486,7 +521,10 @@ The Prisma client is generated code, not a checked-in file. `pnpm install` runs
|
|
|
486
521
|
or after editing the schema, run `pnpm db:generate`.
|
|
487
522
|
|
|
488
523
|
**My app connects to the wrong database / port.**
|
|
489
|
-
A variable exported in your shell beats `--env-file
|
|
524
|
+
A variable exported in your shell beats `.env` (as with `--env-file`). The generated `src/env.ts` already reads app-prefixed names (`MY_SAAS_PORT`), so set those rather than the generic ones — the bare names are only a fallback. Check `env | grep DATABASE_URL`, and see *Environment variables and `--env-file`*.
|
|
525
|
+
|
|
526
|
+
**`The app cannot start — invalid environment variables` (or `EnvValidationError: … expected string, received undefined`).**
|
|
527
|
+
A required variable is set neither in your shell nor in `.env`. Apps from create-basalt ≤ 1.10 did not load `.env` at all in `pnpm dev` / `pnpm basalt`: run `npx create-basalt@latest update` once — it patches an unmodified `bin/basalt.ts` and `src/dev.ts`. Then `cp .env.example .env` if there is no `.env`, fill the variables listed, and run `pnpm basalt doctor` to check. `pnpm start` never reads `.env`: export the variables there.
|
|
490
528
|
|
|
491
529
|
**`pnpm basalt …` starts by running `pnpm install` (or fails offline).**
|
|
492
530
|
pnpm 11's `verifyDepsBeforeRun` (default `install`). Run `node_modules/.bin/tsx bin/basalt.ts …` instead, or set `verifyDepsBeforeRun: warn` in `pnpm-workspace.yaml`.
|
|
@@ -515,6 +553,6 @@ The app's `bin/basalt.ts` predates the project commands. Run `npx create-basalt@
|
|
|
515
553
|
- **`@basaltkit/auth`** — included by default (remove with `--no-auth`): `/auth/*` routes and `APP_SECRET` validated in `env.ts`.
|
|
516
554
|
- **`@basaltkit/subscriptions`** — with `--billing`: example `free`/`pro` plans with trial and feature limits.
|
|
517
555
|
- **`@basaltkit/prisma` + `@basaltkit/{tenancy,auth,teams,subscriptions}-prisma`** — with `--prisma`: the PostgreSQL layer — `prismaPlugin` (tenant-scoped client, boot-time `assertMigrated`), the composed `prisma/schema.prisma` and the Prisma-backed stores for whichever domains are on.
|
|
518
|
-
- **`@basaltkit/cli` + `@basaltkit/generator`** — with `--cli
|
|
556
|
+
- **`@basaltkit/cli` + `@basaltkit/generator`** — by default (not with `--no-cli`): `bin/basalt.ts` calls `runCli`, and `commandsPlugin(generatorCommands())` registers the `make:*` generators. `update` runs the installed `@basaltkit/cli` upgrade codemods after an install.
|
|
519
557
|
- **`@basaltkit/sdk` + `@basaltkit/admin-shadcn` + `@basaltkit/admin`** — with `--ui`: the `web/` frontend calls the API through a typed client and uses the shadcn components.
|
|
520
558
|
- **`@basaltkit/testing`** — always present in `devDependencies`, with a generated smoke test in `tests/app.test.ts`.
|
package/dist/args.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { PackageManager } from './index.js';
|
|
2
|
-
export declare const USAGE = "Usage: npm create basalt <name> [options]\n\nOptions:\n --name=<name> Project name (the way to create a project named like a\n project command: update, add, doctor, info)\n --dir=<path> Target directory (default: ./<name>)\n --no-tenancy Skip multi-tenancy\n --no-auth Skip authentication\n --billing Include subscriptions/billing\n --ui Scaffold a web/ frontend (React + shadcn + SDK)\n --cli
|
|
2
|
+
export declare const USAGE = "Usage: npm create basalt <name> [options]\n\nOptions:\n --name=<name> Project name (the way to create a project named like a\n project command: update, add, doctor, info)\n --dir=<path> Target directory (default: ./<name>)\n --no-tenancy Skip multi-tenancy\n --no-auth Skip authentication\n --billing Include subscriptions/billing\n --ui Scaffold a web/ frontend (React + shadcn + SDK)\n --no-cli Skip the 'basalt' CLI (code generators + commands;\n on by default)\n --mcp Expose read-only routes as MCP tools at /mcp\n --prisma, --db Back the app with PostgreSQL through Prisma: schema,\n migrations, prisma-backed stores and a boot-time check\n that the database is the migrated one\n --install Install dependencies (default: yes in an interactive\n terminal, skipped in CI/non-TTY; --no-install to opt out)\n --no-install Never install dependencies\n --git Initialize a git repository with a first commit\n (same default rule as --install; --no-git to opt out)\n --no-git Never initialize a git repository\n --offline Don't query the npm registry for the latest versions;\n use the ranges bundled with this create-basalt release\n --pm=<manager> Package manager: pnpm | npm | yarn | bun (default: auto-detect)\n -y, --yes Skip prompts and accept defaults\n -h, --help Show this help\n\nRun with no name in a terminal to be prompted interactively.\n\nProject commands \u2014 inside an existing app (in a scaffolded app: pnpm basalt <command>):\n create-basalt update Update dependencies to the latest versions (--dry, --major, \u2026)\n create-basalt add <feature> Add ui | cli | mcp to the app\n create-basalt doctor Check the project's health\n create-basalt info Versions summary for bug reports\n create-basalt <command> --help Options of one command\n";
|
|
3
3
|
export interface Flags {
|
|
4
4
|
name?: string;
|
|
5
5
|
dir?: string;
|
package/dist/args.js
CHANGED
|
@@ -9,7 +9,8 @@ Options:
|
|
|
9
9
|
--no-auth Skip authentication
|
|
10
10
|
--billing Include subscriptions/billing
|
|
11
11
|
--ui Scaffold a web/ frontend (React + shadcn + SDK)
|
|
12
|
-
--cli
|
|
12
|
+
--no-cli Skip the 'basalt' CLI (code generators + commands;
|
|
13
|
+
on by default)
|
|
13
14
|
--mcp Expose read-only routes as MCP tools at /mcp
|
|
14
15
|
--prisma, --db Back the app with PostgreSQL through Prisma: schema,
|
|
15
16
|
migrations, prisma-backed stores and a boot-time check
|
|
@@ -41,7 +42,7 @@ export function parseArgs(argv) {
|
|
|
41
42
|
auth: true,
|
|
42
43
|
billing: false,
|
|
43
44
|
ui: false,
|
|
44
|
-
cli:
|
|
45
|
+
cli: true,
|
|
45
46
|
mcp: false,
|
|
46
47
|
prisma: false,
|
|
47
48
|
yes: false,
|
|
@@ -58,6 +59,8 @@ export function parseArgs(argv) {
|
|
|
58
59
|
flags.ui = true;
|
|
59
60
|
else if (token === '--cli')
|
|
60
61
|
flags.cli = true;
|
|
62
|
+
else if (token === '--no-cli')
|
|
63
|
+
flags.cli = false;
|
|
61
64
|
else if (token === '--mcp')
|
|
62
65
|
flags.mcp = true;
|
|
63
66
|
// `--db` reads naturally in a sentence ("scaffold it with a db") and is the
|
package/dist/bin-template.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/** Marker proving a bin/basalt.ts already delegates the project commands. */
|
|
2
2
|
export declare const PROJECT_COMMANDS_MARKER = "['update', 'add', 'doctor', 'info'].includes(process.argv[2]";
|
|
3
|
+
/** Marker proving a bin/basalt.ts already has the dev prelude (.env loading, `upgrade`, env errors). */
|
|
4
|
+
export declare const DEV_PRELUDE_MARKER = "process.loadEnvFile(envFile)";
|
|
3
5
|
export declare function basaltBin(): string;
|
|
4
6
|
/**
|
|
5
7
|
* Hashes of every bin/basalt.ts an earlier create-basalt release generated
|
|
@@ -8,16 +10,19 @@ export declare function basaltBin(): string;
|
|
|
8
10
|
*/
|
|
9
11
|
export declare const LEGACY_BIN_HASHES: ReadonlySet<string>;
|
|
10
12
|
export declare const hasProjectCommands: (content: string) => boolean;
|
|
13
|
+
export declare const hasDevPrelude: (content: string) => boolean;
|
|
11
14
|
/**
|
|
12
|
-
* Upgrades a template-generated bin/basalt.ts to
|
|
13
|
-
*
|
|
14
|
-
* static imports into dynamic ones (so they no
|
|
15
|
-
*
|
|
16
|
-
*
|
|
15
|
+
* Upgrades a template-generated bin/basalt.ts to the current template, step by
|
|
16
|
+
* step: (1) without the project commands, inserts their prelude after the
|
|
17
|
+
* shebang and turns the remaining static imports into dynamic ones (so they no
|
|
18
|
+
* longer run before it); (2) without the dev prelude, inserts it right after
|
|
19
|
+
* the project commands and routes the app import through `explainBootFailure`.
|
|
20
|
+
* Deterministic for the known templates — for the latest ones (1.9, 1.10) the
|
|
21
|
+
* result is exactly {@link basaltBin}.
|
|
17
22
|
*/
|
|
18
23
|
export declare function patchBasaltBin(content: string): string;
|
|
19
24
|
export type BinStatus = 'current' | 'patchable' | 'modified' | 'absent';
|
|
20
|
-
/** Where a bin/basalt.ts stands: already
|
|
25
|
+
/** Where a bin/basalt.ts stands: already current, an untouched old template, or user code. */
|
|
21
26
|
export declare function binStatus(content: string | undefined, manifestHash?: string): BinStatus;
|
|
22
|
-
/** What to paste by hand into a customised bin/basalt.ts. */
|
|
23
|
-
export declare
|
|
27
|
+
/** What to paste by hand into a customised bin/basalt.ts (only the parts it lacks). */
|
|
28
|
+
export declare function manualBinSnippet(content: string): string;
|
package/dist/bin-template.js
CHANGED
|
@@ -55,11 +55,136 @@ function createBasalt(args: string[]): number {
|
|
|
55
55
|
return result.status ?? 1
|
|
56
56
|
}
|
|
57
57
|
`;
|
|
58
|
+
/**
|
|
59
|
+
* The DEV PRELUDE, right after the project commands and still before anything
|
|
60
|
+
* of the app is imported:
|
|
61
|
+
*
|
|
62
|
+
* - `upgrade` (the @basaltkit/cli codemods) only rewrites source files, so it
|
|
63
|
+
* runs without booting the app — it must keep working while the app cannot
|
|
64
|
+
* start (that is usually why you are upgrading);
|
|
65
|
+
* - `.env` is loaded for development with `node --env-file` semantics: a
|
|
66
|
+
* variable already exported in the shell wins, the file only fills in what
|
|
67
|
+
* is missing (the app-prefixed names in src/env.ts exist because of exactly
|
|
68
|
+
* that rule). `pnpm start` / src/server.ts never loads it;
|
|
69
|
+
* - a boot-time `EnvValidationError` becomes a fix (which variables, where they
|
|
70
|
+
* are read from, what to do) instead of a stack trace.
|
|
71
|
+
*/
|
|
72
|
+
const DEV_PRELUDE = `// \`upgrade\` (framework codemods) only rewrites source files: it runs without
|
|
73
|
+
// booting the app, so it works even while the app cannot start.
|
|
74
|
+
if (process.argv[2] === 'upgrade') {
|
|
75
|
+
const { consoleIo, parseArgv, upgradeCommand } = await import('@basaltkit/cli')
|
|
76
|
+
const { args, flags } = parseArgv(process.argv.slice(2))
|
|
77
|
+
// upgradeCommand reads only io/args/flags — there is no app to hand it.
|
|
78
|
+
const context = { io: consoleIo(), args, flags } as unknown as Parameters<typeof upgradeCommand.handle>[0]
|
|
79
|
+
process.exit((await upgradeCommand.handle(context)) ?? 0)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Development loads .env (when present) like \`node --env-file=.env\`: a variable
|
|
83
|
+
// already exported in your shell still wins, the file only fills in what is
|
|
84
|
+
// missing. \`pnpm start\` (production) never loads it — there the configuration
|
|
85
|
+
// comes from the real environment.
|
|
86
|
+
const envFile = join(root, '.env')
|
|
87
|
+
if (existsSync(envFile)) process.loadEnvFile(envFile)
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* A boot failure as a fix instead of a stack trace (stack: BASALT_DEBUG=1 or --debug):
|
|
91
|
+
* invalid environment variables, a database that does not answer, one that is not
|
|
92
|
+
* migrated. Anything else is rethrown untouched.
|
|
93
|
+
*/
|
|
94
|
+
async function explainBootFailure(error: unknown): Promise<never> {
|
|
95
|
+
const failure = (error ?? {}) as {
|
|
96
|
+
code?: unknown; report?: unknown; message?: unknown; cause?: unknown; errors?: unknown; details?: unknown
|
|
97
|
+
}
|
|
98
|
+
// @basaltkit/prisma's describeDbError diagnosis (assertMigrated attaches it): the cause and its one-line fix.
|
|
99
|
+
const diagnosis = (failure.details as { diagnosis?: { code?: unknown; cause?: unknown; fix?: unknown } } | undefined)?.diagnosis
|
|
100
|
+
const message = String(failure.message ?? '')
|
|
101
|
+
const codes = [failure, failure.cause, ...(Array.isArray(failure.errors) ? failure.errors : [])].map((item) =>
|
|
102
|
+
String((item as { code?: unknown } | null | undefined)?.code ?? ''),
|
|
103
|
+
)
|
|
104
|
+
const unreachable =
|
|
105
|
+
codes.some((code) => /^(ECONNREFUSED|ENOTFOUND|EHOSTUNREACH|ETIMEDOUT|EAI_AGAIN|P1001|P1002)$/.test(code)) ||
|
|
106
|
+
/ECONNREFUSED|ENOTFOUND|EHOSTUNREACH|Can't reach database server/.test(message) ||
|
|
107
|
+
// assertMigrated could not even query the database (down, wrong host/port or credentials).
|
|
108
|
+
(failure.code === 'PRISMA_NOT_MIGRATED' && message.startsWith('Could not verify'))
|
|
109
|
+
const debugHint = 'BASALT_DEBUG=1 shows the stack trace.'
|
|
110
|
+
let explanation: string | undefined
|
|
111
|
+
if (failure.code === 'ENV_INVALID' && Array.isArray(failure.report)) {
|
|
112
|
+
const hasEnvFile = existsSync(envFile)
|
|
113
|
+
explanation = [
|
|
114
|
+
'The app cannot start — invalid environment variables:',
|
|
115
|
+
...failure.report.map((line) => ' - ' + String(line)),
|
|
116
|
+
'',
|
|
117
|
+
"Read from: your shell's environment" +
|
|
118
|
+
(hasEnvFile ? ' and ' + envFile + ' (exported variables win).' : ' only — there is no ' + envFile + '.'),
|
|
119
|
+
'Fix: ' + (hasEnvFile ? 'set them in .env' : 'cp .env.example .env, then fill them in') + ' (or export them)' +
|
|
120
|
+
(existsSync(join(root, 'prisma')) ? ' — for DATABASE_URL, start PostgreSQL and point the URL at it' : '') +
|
|
121
|
+
'. \`pnpm basalt doctor\` checks the environment; ' + debugHint,
|
|
122
|
+
].join('\\n')
|
|
123
|
+
} else if (diagnosis && (diagnosis.code === 'DB_PERMISSION_DENIED' || diagnosis.code === 'DB_NOT_EMPTY_BASELINE')) {
|
|
124
|
+
const database = await databaseInUse()
|
|
125
|
+
explanation = [
|
|
126
|
+
diagnosis.code === 'DB_PERMISSION_DENIED'
|
|
127
|
+
? 'The app cannot start — the database refused the app database role (permission denied):'
|
|
128
|
+
: 'The app cannot start — the database has tables but no migration history:',
|
|
129
|
+
' ' + String(diagnosis.cause),
|
|
130
|
+
'Database: ' + database.target + ' (from ' + database.name + '; credentials never shown).',
|
|
131
|
+
'Fix: ' + String(diagnosis.fix) + ' ' + debugHint,
|
|
132
|
+
].join('\\n')
|
|
133
|
+
} else if (unreachable || failure.code === 'PRISMA_NOT_MIGRATED') {
|
|
134
|
+
const database = await databaseInUse()
|
|
135
|
+
const where = 'Database: ' + database.target + ' (from ' + database.name + '; credentials never shown).'
|
|
136
|
+
explanation = unreachable
|
|
137
|
+
? [
|
|
138
|
+
'The app cannot start — the database did not answer.',
|
|
139
|
+
where,
|
|
140
|
+
'Fix: start PostgreSQL (e.g. \`docker compose up -d\`, or your local service) and check that ' +
|
|
141
|
+
database.name + ' in .env points at it (host, port, user, password); on a new database, then run \`pnpm db:migrate\`. ' + debugHint,
|
|
142
|
+
].join('\\n')
|
|
143
|
+
: [
|
|
144
|
+
'The app cannot start — the database is not migrated:',
|
|
145
|
+
' ' + message,
|
|
146
|
+
where,
|
|
147
|
+
'Fix: run \`pnpm db:migrate\` (development; \`pnpm db:deploy\` in production) — or, if this is not the database you meant, check ' +
|
|
148
|
+
database.name + ' (a shell may have exported another project\\'s). ' + debugHint,
|
|
149
|
+
].join('\\n')
|
|
150
|
+
}
|
|
151
|
+
if (explanation === undefined || process.env['BASALT_DEBUG'] === '1' || process.argv.includes('--debug')) throw error
|
|
152
|
+
console.error(explanation)
|
|
153
|
+
process.exit(1)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** The database URL the app booted with — protocol, host, port and database only, never the credentials. */
|
|
157
|
+
async function databaseInUse(): Promise<{ name: string; target: string }> {
|
|
158
|
+
let url: unknown
|
|
159
|
+
try {
|
|
160
|
+
url = ((await import('../src/env.js')).env as unknown as Record<string, unknown>)['DATABASE_URL']
|
|
161
|
+
} catch {
|
|
162
|
+
// env.ts itself failed: there is no URL to show.
|
|
163
|
+
}
|
|
164
|
+
// The variable it came from: the app-prefixed name first (it wins in src/env.ts).
|
|
165
|
+
const name =
|
|
166
|
+
Object.keys(process.env)
|
|
167
|
+
.filter((key) => key.endsWith('DATABASE_URL') && process.env[key] === url)
|
|
168
|
+
.sort((a, b) => b.length - a.length)[0] ?? 'DATABASE_URL'
|
|
169
|
+
try {
|
|
170
|
+
const parsed = new URL(String(url))
|
|
171
|
+
return { name, target: parsed.protocol + '//' + parsed.hostname + ':' + (parsed.port || '5432') + parsed.pathname }
|
|
172
|
+
} catch {
|
|
173
|
+
return { name, target: '(no valid URL)' }
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
`;
|
|
58
177
|
/** Marker proving a bin/basalt.ts already delegates the project commands. */
|
|
59
178
|
export const PROJECT_COMMANDS_MARKER = "['update', 'add', 'doctor', 'info'].includes(process.argv[2]";
|
|
179
|
+
/** Marker proving a bin/basalt.ts already has the dev prelude (.env loading, `upgrade`, env errors). */
|
|
180
|
+
export const DEV_PRELUDE_MARKER = 'process.loadEnvFile(envFile)';
|
|
181
|
+
const APP_IMPORT = "await import('../src/app.js')";
|
|
182
|
+
const APP_IMPORT_EXPLAINED = `${APP_IMPORT}.catch(explainBootFailure)`;
|
|
183
|
+
const RUN_CLI = 'process.exit(await runCli({ app })';
|
|
60
184
|
export function basaltBin() {
|
|
61
185
|
return `#!/usr/bin/env node
|
|
62
186
|
${PRELUDE}
|
|
187
|
+
${DEV_PRELUDE}
|
|
63
188
|
// The dev tools are imported dynamically, after the project commands above.
|
|
64
189
|
const { runCli } = await import('@basaltkit/cli')
|
|
65
190
|
const { generatorCommands } = await import('@basaltkit/generator')
|
|
@@ -68,7 +193,7 @@ const { prismaSyncCommand } = await import('@basaltkit/prisma')
|
|
|
68
193
|
// Dev tooling: opt into development defaults unless NODE_ENV is already set
|
|
69
194
|
// (imported dynamically below so env.ts is evaluated after this line).
|
|
70
195
|
process.env['NODE_ENV'] ??= 'development'
|
|
71
|
-
const { buildApp } =
|
|
196
|
+
const { buildApp } = ${APP_IMPORT_EXPLAINED}
|
|
72
197
|
|
|
73
198
|
// The 'basalt' CLI: boots the app WITH the dev/CLI commands, runs one, shuts down.
|
|
74
199
|
// The dev tools (@basaltkit/generator; add @basaltkit/ai for ai:*) are imported
|
|
@@ -83,7 +208,7 @@ const app = buildApp({
|
|
|
83
208
|
logLevel: 'silent',
|
|
84
209
|
commands: [...generatorCommands(), prismaSyncCommand()],
|
|
85
210
|
})
|
|
86
|
-
process.exit(await runCli({ app }))
|
|
211
|
+
process.exit(await runCli({ app }).catch(explainBootFailure))
|
|
87
212
|
`;
|
|
88
213
|
}
|
|
89
214
|
/**
|
|
@@ -100,17 +225,31 @@ export const LEGACY_BIN_HASHES = new Set([
|
|
|
100
225
|
'sha256-d09d583f1a39bffcb72f302c5f6c3e2651badea358ba38019238a4aa4b842545',
|
|
101
226
|
// 2026-09-19 (create-basalt ≤ 1.9): NODE_ENV defaulted before the app import.
|
|
102
227
|
'sha256-a0df89dee04091a9470f67968ff72cfc3bf64076c1bf3d141ae657c7438a94c8',
|
|
228
|
+
// 2026-10-01 (create-basalt 1.10): project commands (update/add/doctor/info)
|
|
229
|
+
// delegated before boot — but no .env loading and `upgrade` booted the app.
|
|
230
|
+
'sha256-9091e137a5de39929d0e70b2fd973386b9cc1c2f996e130989a22c795b9d0893',
|
|
103
231
|
]);
|
|
104
232
|
export const hasProjectCommands = (content) => content.includes(PROJECT_COMMANDS_MARKER);
|
|
233
|
+
export const hasDevPrelude = (content) => content.includes(DEV_PRELUDE_MARKER);
|
|
105
234
|
/**
|
|
106
|
-
* Upgrades a template-generated bin/basalt.ts to
|
|
107
|
-
*
|
|
108
|
-
* static imports into dynamic ones (so they no
|
|
109
|
-
*
|
|
110
|
-
*
|
|
235
|
+
* Upgrades a template-generated bin/basalt.ts to the current template, step by
|
|
236
|
+
* step: (1) without the project commands, inserts their prelude after the
|
|
237
|
+
* shebang and turns the remaining static imports into dynamic ones (so they no
|
|
238
|
+
* longer run before it); (2) without the dev prelude, inserts it right after
|
|
239
|
+
* the project commands and routes the app import through `explainBootFailure`.
|
|
240
|
+
* Deterministic for the known templates — for the latest ones (1.9, 1.10) the
|
|
241
|
+
* result is exactly {@link basaltBin}.
|
|
111
242
|
*/
|
|
112
243
|
export function patchBasaltBin(content) {
|
|
113
|
-
|
|
244
|
+
let text = content.replace(/\r\n/g, '\n');
|
|
245
|
+
if (!hasProjectCommands(text))
|
|
246
|
+
text = addProjectCommands(text);
|
|
247
|
+
if (!hasDevPrelude(text))
|
|
248
|
+
text = addDevPrelude(text);
|
|
249
|
+
return text;
|
|
250
|
+
}
|
|
251
|
+
function addProjectCommands(content) {
|
|
252
|
+
const lines = content.split('\n');
|
|
114
253
|
const shebang = lines[0]?.startsWith('#!') ? `${lines.shift()}\n` : '';
|
|
115
254
|
const body = lines.map((line) => {
|
|
116
255
|
const match = /^import \{ ([^}]+) \} from '([^']+)'$/.exec(line);
|
|
@@ -122,19 +261,45 @@ export function patchBasaltBin(content) {
|
|
|
122
261
|
}
|
|
123
262
|
return `${shebang}${PRELUDE}\n${body.join('\n')}`;
|
|
124
263
|
}
|
|
125
|
-
|
|
264
|
+
function addDevPrelude(content) {
|
|
265
|
+
const at = content.indexOf(PRELUDE);
|
|
266
|
+
// A file without the template's prelude block is not ours to rearrange.
|
|
267
|
+
if (at < 0)
|
|
268
|
+
return content;
|
|
269
|
+
const end = at + PRELUDE.length + (content.startsWith('\n', at + PRELUDE.length) ? 1 : 0);
|
|
270
|
+
const rest = content
|
|
271
|
+
.slice(end)
|
|
272
|
+
.replace(`= ${APP_IMPORT}\n`, `= ${APP_IMPORT_EXPLAINED}\n`)
|
|
273
|
+
.replace(`${RUN_CLI})\n`, `${RUN_CLI}.catch(explainBootFailure))\n`);
|
|
274
|
+
return `${content.slice(0, end)}${DEV_PRELUDE}\n${rest}`;
|
|
275
|
+
}
|
|
276
|
+
/** Where a bin/basalt.ts stands: already current, an untouched old template, or user code. */
|
|
126
277
|
export function binStatus(content, manifestHash) {
|
|
127
278
|
if (content === undefined)
|
|
128
279
|
return 'absent';
|
|
129
|
-
if (hasProjectCommands(content))
|
|
280
|
+
if (hasProjectCommands(content) && hasDevPrelude(content))
|
|
130
281
|
return 'current';
|
|
131
282
|
const hash = hashContent(content);
|
|
132
283
|
return LEGACY_BIN_HASHES.has(hash) || hash === manifestHash ? 'patchable' : 'modified';
|
|
133
284
|
}
|
|
134
|
-
/** What to paste by hand into a customised bin/basalt.ts. */
|
|
135
|
-
export
|
|
285
|
+
/** What to paste by hand into a customised bin/basalt.ts (only the parts it lacks). */
|
|
286
|
+
export function manualBinSnippet(content) {
|
|
287
|
+
const steps = [];
|
|
288
|
+
if (!hasProjectCommands(content)) {
|
|
289
|
+
steps.push(`Paste this right after the shebang line of bin/basalt.ts, then turn its
|
|
136
290
|
static imports of @basaltkit/* and ../src/app.js into dynamic ones
|
|
137
291
|
(\`import { runCli } from '@basaltkit/cli'\` → \`const { runCli } = await import('@basaltkit/cli')\`)
|
|
138
292
|
so they no longer run before it:
|
|
139
293
|
|
|
140
|
-
${PRELUDE}
|
|
294
|
+
${PRELUDE}`);
|
|
295
|
+
}
|
|
296
|
+
if (!hasDevPrelude(content)) {
|
|
297
|
+
steps.push(`${steps.length > 0 ? 'Then paste' : 'Paste'} this after the project-commands block (before anything of the app is
|
|
298
|
+
imported) — it runs \`upgrade\` without booting the app, loads .env for development
|
|
299
|
+
and explains env errors — and change the app import to
|
|
300
|
+
\`const { buildApp } = ${APP_IMPORT_EXPLAINED}\`:
|
|
301
|
+
|
|
302
|
+
${DEV_PRELUDE}`);
|
|
303
|
+
}
|
|
304
|
+
return steps.join('\n');
|
|
305
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -152,8 +152,12 @@ try {
|
|
|
152
152
|
if (flags.prisma) {
|
|
153
153
|
// Nothing boots before the database exists: env.DATABASE_URL is required and
|
|
154
154
|
// the app asserts at boot that it reached the MIGRATED database.
|
|
155
|
-
|
|
156
|
-
steps.push(
|
|
155
|
+
// .env is already written with a local URL (pnpm dev / pnpm basalt load it).
|
|
156
|
+
steps.push(`# start PostgreSQL, or edit ${envPrefix(result.options.name)}_DATABASE_URL in .env`);
|
|
157
|
+
steps.push(`${pm} run db:migrate # creates the tables`);
|
|
158
|
+
// Prisma 7's `migrate dev` no longer seeds: the demo tenant is its own step.
|
|
159
|
+
if (flags.tenancy)
|
|
160
|
+
steps.push(`${pm} run db:seed # the demo tenant the resolvers expect`);
|
|
157
161
|
}
|
|
158
162
|
steps.push(`${pm} run dev${flags.ui ? ' # API on :3000' : ''}`);
|
|
159
163
|
// `web` is a pnpm workspace member (pnpm-workspace.yaml); `dev:web` runs its
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `src/dev.ts` (the `pnpm dev` entrypoint) and the `.env.example` header across
|
|
3
|
+
* create-basalt releases. Up to 1.10, nothing loaded `.env`: `pnpm dev` booted
|
|
4
|
+
* without the variables the example told you to set. `update` brings untouched
|
|
5
|
+
* copies to the current templates; customised ones get the snippet instead.
|
|
6
|
+
*/
|
|
7
|
+
/** Hashes of every src/dev.ts an earlier create-basalt release generated (CRLF-insensitive). */
|
|
8
|
+
export declare const LEGACY_DEV_HASHES: ReadonlySet<string>;
|
|
9
|
+
/** Marker proving a src/dev.ts already loads .env. */
|
|
10
|
+
export declare const DEV_ENV_MARKER = "process.loadEnvFile(";
|
|
11
|
+
export type DevEntryStatus = 'current' | 'patchable' | 'modified' | 'absent';
|
|
12
|
+
/** Where a src/dev.ts stands: loads .env already, an untouched old template, or user code. */
|
|
13
|
+
export declare function devEntryStatus(content: string | undefined, manifestHash?: string): DevEntryStatus;
|
|
14
|
+
/** The current src/dev.ts — what a patchable one becomes. */
|
|
15
|
+
export declare const patchDevEntry: () => string;
|
|
16
|
+
/** What to paste by hand into a customised src/dev.ts. */
|
|
17
|
+
export declare const MANUAL_DEV_SNIPPET = "Paste this at the top of src/dev.ts, BEFORE the server is imported (keep that\nimport dynamic \u2014 `await import('./server.js')` \u2014 so the variables are set first):\n\nimport { existsSync } from 'node:fs'\nimport { fileURLToPath } from 'node:url'\n\n// Development loads .env (when present) like `node --env-file=.env`: a variable\n// already exported in your shell still wins, the file only fills in what is missing.\nconst envFile = fileURLToPath(new URL('../.env', import.meta.url))\nif (existsSync(envFile)) process.loadEnvFile(envFile)\n";
|
|
18
|
+
/**
|
|
19
|
+
* `.env.example` with the old "nothing loads this file" lead swapped for the
|
|
20
|
+
* current one; undefined when the file does not start with the old lead (a
|
|
21
|
+
* newer or hand-written file is left alone — it is only comments).
|
|
22
|
+
*/
|
|
23
|
+
export declare function patchEnvExample(content: string): string | undefined;
|