create-basalt 1.9.0 → 1.11.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 +192 -21
- package/dist/args.d.ts +1 -1
- package/dist/args.js +16 -2
- package/dist/bin-template.d.ts +28 -0
- package/dist/bin-template.js +291 -0
- package/dist/cli.js +46 -6
- package/dist/dev-entry.d.ts +23 -0
- package/dist/dev-entry.js +49 -0
- package/dist/index.d.ts +15 -1
- package/dist/index.js +53 -28
- package/dist/latest-versions.d.ts +31 -2
- package/dist/latest-versions.js +91 -51
- package/dist/project/add.d.ts +66 -0
- package/dist/project/add.js +322 -0
- package/dist/project/context.d.ts +53 -0
- package/dist/project/context.js +112 -0
- package/dist/project/doctor.d.ts +50 -0
- package/dist/project/doctor.js +460 -0
- package/dist/project/manifest.d.ts +36 -0
- package/dist/project/manifest.js +81 -0
- package/dist/project/package-json.d.ts +57 -0
- package/dist/project/package-json.js +161 -0
- package/dist/project/run.d.ts +55 -0
- package/dist/project/run.js +477 -0
- package/dist/project/semver.d.ts +29 -0
- package/dist/project/semver.js +132 -0
- package/dist/project/term.d.ts +24 -0
- package/dist/project/term.js +39 -0
- package/dist/project/update.d.ts +101 -0
- package/dist/project/update.js +325 -0
- package/dist/templates.d.ts +31 -5
- package/dist/templates.js +102 -58
- package/dist/versions.js +6 -5
- package/dist/wizard.js +3 -3
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -16,6 +16,8 @@ Starting a backend project from scratch involves dozens of decisions and files b
|
|
|
16
16
|
|
|
17
17
|
It works in two ways: interactive mode (answers questions in the terminal) or direct mode with flags (ideal for scripts). It doesn't install dependencies or touch git unless you ask it to (`--install`, `--git`).
|
|
18
18
|
|
|
19
|
+
It also stays useful **after** the project exists: `create-basalt update` moves an app to the latest dependency versions, `create-basalt add ui|cli|mcp` adds a feature you didn't pick at creation time, and `create-basalt doctor` / `info` check the project — see [Project commands](#project-commands-update-add-doctor-info). In a scaffolded app they are simply `pnpm basalt update`, `pnpm basalt add ui`, …
|
|
20
|
+
|
|
19
21
|
## Installation
|
|
20
22
|
|
|
21
23
|
You don't need to install anything — your package manager's `create` command downloads and runs the package on the spot:
|
|
@@ -84,13 +86,14 @@ Usage: npm create basalt <name> [options]
|
|
|
84
86
|
|
|
85
87
|
| Flag | Default | What it does |
|
|
86
88
|
| --- | --- | --- |
|
|
87
|
-
| `<name>` | — (asked in interactive mode) | Project name (and folder name, unless `--dir`) |
|
|
89
|
+
| `<name>` | — (asked in interactive mode) | Project name (and folder name, unless `--dir`). `update`, `add`, `doctor` and `info` are reserved for the [project commands](#project-commands-update-add-doctor-info) |
|
|
90
|
+
| `--name=<name>` | — | Project name as a flag — the way to create a project literally called `update`/`add`/`doctor`/`info` |
|
|
88
91
|
| `--dir=<path>` | `./<name>` | Destination folder |
|
|
89
92
|
| `--no-tenancy` | tenancy **on** | Removes multi-tenancy (`@basaltkit/tenancy`) |
|
|
90
93
|
| `--no-auth` | auth **on** | Removes authentication (`@basaltkit/auth`, `APP_SECRET`, `/auth/*` routes) |
|
|
91
94
|
| `--billing` | off | Includes subscriptions/plans (`@basaltkit/subscriptions`, example `free` and `pro` plans) |
|
|
92
95
|
| `--ui` | off | Generates the `web/` frontend (React + shadcn via `@basaltkit/admin-shadcn` + `@basaltkit/sdk`). **Forces pnpm** — see note below |
|
|
93
|
-
| `--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`) |
|
|
94
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` |
|
|
95
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 |
|
|
96
99
|
| `--install` | off | Installs dependencies at the end (with the detected/chosen manager) |
|
|
@@ -117,7 +120,7 @@ Behavior notes (faithful to the code):
|
|
|
117
120
|
pnpm create basalt
|
|
118
121
|
|
|
119
122
|
# Full project, no questions, with everything:
|
|
120
|
-
pnpm create basalt my-app --billing --ui --
|
|
123
|
+
pnpm create basalt my-app --billing --ui --prisma --install --git
|
|
121
124
|
|
|
122
125
|
# Minimal API (no tenancy or auth), in another folder:
|
|
123
126
|
npm create basalt service-api --no-tenancy --no-auth --dir=./apps/service-api
|
|
@@ -135,28 +138,31 @@ Always:
|
|
|
135
138
|
|
|
136
139
|
```
|
|
137
140
|
my-app/
|
|
138
|
-
├── package.json # scripts: dev, start, test, typecheck (+
|
|
141
|
+
├── package.json # scripts: dev, start, test, typecheck, basalt (+ dev:web with --ui); create-basalt as a devDependency
|
|
142
|
+
├── .basalt/project.json # scaffold manifest: create-basalt version, options, a hash per generated file — commit it
|
|
139
143
|
├── tsconfig.json # strict TypeScript, ESM
|
|
140
|
-
├── .env.example # MY_APP_PORT, MY_APP_HOST, MY_APP_LOG_LEVEL, NODE_ENV (+ MY_APP_APP_SECRET with auth) + the
|
|
144
|
+
├── .env.example # MY_APP_PORT, MY_APP_HOST, MY_APP_LOG_LEVEL, NODE_ENV (+ MY_APP_APP_SECRET with auth) + the precedence warning — committed
|
|
145
|
+
├── .env # the same values for local development (+ a generated MY_APP_APP_SECRET with auth) — git-ignored, loaded by dev/basalt
|
|
141
146
|
├── .gitignore
|
|
142
147
|
├── README.md # instructions adapted to the chosen options
|
|
143
|
-
├── pnpm-workspace.yaml # esbuild allowBuilds, @basaltkit/* release-age exclusion (+ "web" member with --ui) + pnpm 11 notes
|
|
148
|
+
├── pnpm-workspace.yaml # esbuild allowBuilds, @basaltkit/* + create-basalt release-age exclusion (+ "web" member with --ui) + pnpm 11 notes
|
|
144
149
|
├── src/
|
|
145
150
|
│ ├── env.ts # environment variables validated with Zod (@basaltkit/env), app-prefixed
|
|
146
151
|
│ ├── app.ts # buildApp() with the chosen plugins
|
|
147
152
|
│ ├── routes.ts # GET / (friendly index) and GET /health
|
|
148
|
-
│
|
|
153
|
+
│ ├── server.ts # startup + clean shutdown on SIGINT/SIGTERM (`pnpm start` — loads no .env)
|
|
154
|
+
│ └── dev.ts # `pnpm dev`: loads .env, defaults NODE_ENV=development, starts server.ts
|
|
149
155
|
└── tests/app.test.ts # smoke test adapted to the options
|
|
150
156
|
```
|
|
151
157
|
|
|
152
|
-
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.
|
|
158
|
+
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` 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).
|
|
153
159
|
|
|
154
160
|
### With `--ui`: running the API and frontend
|
|
155
161
|
|
|
156
162
|
```bash
|
|
157
163
|
pnpm install
|
|
158
|
-
pnpm dev
|
|
159
|
-
pnpm
|
|
164
|
+
pnpm dev # terminal 1 — API on :3000
|
|
165
|
+
pnpm dev:web # terminal 2 — UI at http://localhost:5180 (= pnpm --filter my-app-web dev)
|
|
160
166
|
```
|
|
161
167
|
|
|
162
168
|
Vite's dev server proxies `/api` to the API — there's no CORS to configure.
|
|
@@ -181,7 +187,7 @@ the next restart.
|
|
|
181
187
|
```bash
|
|
182
188
|
pnpm create basalt my-app --prisma
|
|
183
189
|
cd my-app && pnpm install # postinstall runs `prisma generate`
|
|
184
|
-
|
|
190
|
+
# .env already points MY_APP_DATABASE_URL at postgres://…@localhost:5432/my_app — start PostgreSQL or edit it
|
|
185
191
|
pnpm db:migrate # prisma migrate dev — creates the tables and seeds `demo`
|
|
186
192
|
pnpm dev
|
|
187
193
|
```
|
|
@@ -197,9 +203,9 @@ the database and host instead of a `500` on the first request. In production:
|
|
|
197
203
|
The generated `tests/app.test.ts` skips itself when no database is configured, so
|
|
198
204
|
`pnpm test` stays green on a machine without PostgreSQL. To add another Basalt
|
|
199
205
|
domain later, install its `@basaltkit/<domain>-prisma` package, run
|
|
200
|
-
`pnpm basalt prisma:sync` (
|
|
206
|
+
`pnpm basalt prisma:sync` (unless `--no-cli`) and then `pnpm db:migrate`.
|
|
201
207
|
|
|
202
|
-
###
|
|
208
|
+
### The `basalt` command line (default; `--no-cli` skips it)
|
|
203
209
|
|
|
204
210
|
```bash
|
|
205
211
|
pnpm basalt list # available commands
|
|
@@ -209,6 +215,138 @@ pnpm basalt make:resource Project # generates schema → repository → servic
|
|
|
209
215
|
|
|
210
216
|
With pnpm 11, every `pnpm <script>` and `pnpm exec` first verifies dependencies (`verifyDepsBeforeRun`, default `install`) and runs `pnpm install` when any workspace project is out of sync — so `pnpm basalt …` can need the network. `node_modules/.bin/tsx bin/basalt.ts …` skips that check; `verifyDepsBeforeRun: warn` (commented out in the generated `pnpm-workspace.yaml`) turns it into a warning for the whole project.
|
|
211
217
|
|
|
218
|
+
## Project commands: update, add, doctor, info
|
|
219
|
+
|
|
220
|
+
Run inside an existing Basalt app — any app, created with or without the CLI (`--no-cli`),
|
|
221
|
+
by any create-basalt release:
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
npx create-basalt@latest update # or, in a scaffolded app: pnpm basalt update
|
|
225
|
+
npx create-basalt@latest add ui # pnpm basalt add ui
|
|
226
|
+
npx create-basalt@latest doctor # pnpm basalt doctor
|
|
227
|
+
npx create-basalt@latest info # pnpm basalt info
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
The first positional argument picks the mode: `update`, `add`, `doctor` and
|
|
231
|
+
`info` are project commands; anything else is a new project's name (use
|
|
232
|
+
`--name=update` to create a project with one of those names). Outside a Basalt
|
|
233
|
+
app (no `package.json`, or no `@basaltkit/*` dependency) they stop with an
|
|
234
|
+
actionable error. Every command has `--help`; `--cwd=<dir>` targets another
|
|
235
|
+
directory, `--pm=` overrides the detected package manager (`packageManager`
|
|
236
|
+
field, then the lockfile), and output is colored unless `NO_COLOR` is set,
|
|
237
|
+
`--no-color` is passed or stdout is not a terminal.
|
|
238
|
+
|
|
239
|
+
**How `pnpm basalt <command>` reaches them.** New apps list `create-basalt` as a
|
|
240
|
+
devDependency. With `--no-cli` the `basalt` script is `create-basalt --project`;
|
|
241
|
+
with `--cli`, `bin/basalt.ts` handles `update`/`add`/`doctor`/`info` **before**
|
|
242
|
+
importing anything of the app (they must work while the app is broken
|
|
243
|
+
mid-upgrade) and runs the installed create-basalt — or `pnpm dlx` /
|
|
244
|
+
`bunx` / `npx create-basalt@latest` when it isn't installed.
|
|
245
|
+
|
|
246
|
+
### Updating an app
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
pnpm basalt update --dry # the table, nothing written
|
|
250
|
+
pnpm basalt update # asks, writes, installs, runs the codemods
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
| Flag | What it does |
|
|
254
|
+
| --- | --- |
|
|
255
|
+
| `--dry` | Prints the plan; writes nothing |
|
|
256
|
+
| `-y`, `--yes` | Applies without asking (required when stdin is not a terminal) |
|
|
257
|
+
| `--major` | Lets third-party packages cross a major too |
|
|
258
|
+
| `--only=@basaltkit` | Only the framework packages (and `create-basalt`) |
|
|
259
|
+
| `--no-install` | Writes `package.json`, skips the install and the codemods |
|
|
260
|
+
| `--no-tooling` | Leaves `bin/basalt.ts`, `src/dev.ts`, `.env.example` and the `create-basalt` devDependency alone |
|
|
261
|
+
|
|
262
|
+
The policy is the scaffold's own (the same registry code): `@basaltkit/*` and
|
|
263
|
+
`create-basalt` go to `latest` across majors — each framework major prints a
|
|
264
|
+
link to its CHANGELOG and to the upgrade notes; third-party packages go to the
|
|
265
|
+
newest release **on the app's current major** (a newer major is listed as
|
|
266
|
+
"kept … pass --major"); a version younger than the release-age window (pnpm
|
|
267
|
+
`minimumReleaseAge`, read from the project's `pnpm-workspace.yaml` when set) is
|
|
268
|
+
left for later so the install cannot refuse it. When a framework release
|
|
269
|
+
declares a peer range the app would not meet (say `zod ^5` while zod is held on
|
|
270
|
+
4), the plan warns before anything is written.
|
|
271
|
+
|
|
272
|
+
Both `package.json` and `web/package.json` are updated, **in place**: only the
|
|
273
|
+
version strings change — order, indentation and range style (`^`, `~`, exact)
|
|
274
|
+
are kept, and ranges `update` does not manage (`workspace:`, tags, git, `>=`) are
|
|
275
|
+
left alone. The lockfile is never edited by hand: the detected package manager
|
|
276
|
+
installs, then the installed `@basaltkit/cli` upgrade codemods run (no app
|
|
277
|
+
boot), then it suggests `pnpm typecheck && pnpm test`. If the install fails the
|
|
278
|
+
edits stay and the command says how to revert (`git checkout -- package.json …`).
|
|
279
|
+
`--offline` is refused: update needs the registry.
|
|
280
|
+
|
|
281
|
+
Project tooling rides along (skip with `--no-tooling`): an app without the
|
|
282
|
+
`create-basalt` devDependency gets it plus a `basalt` script (and a
|
|
283
|
+
`create-basalt` entry in `minimumReleaseAgeExclude`); a `bin/basalt.ts`
|
|
284
|
+
generated by an earlier release gains the project commands, `.env` loading, a
|
|
285
|
+
pre-boot `upgrade` and the readable env error; a `src/dev.ts` from 1.9/1.10 gains
|
|
286
|
+
`.env` loading; and the `.env.example` header stops saying "nothing loads this
|
|
287
|
+
file". Files are patched **only** when they are byte-for-byte a known template
|
|
288
|
+
(or the manifest records them untouched); a customised one is left alone and the
|
|
289
|
+
exact snippet to paste is printed.
|
|
290
|
+
|
|
291
|
+
### Adding features later
|
|
292
|
+
|
|
293
|
+
```bash
|
|
294
|
+
pnpm basalt add ui --dry # what would be created/merged/skipped
|
|
295
|
+
pnpm basalt add ui # web/ exactly as --ui scaffolds it
|
|
296
|
+
pnpm basalt add cli # bin/basalt.ts + @basaltkit/cli, generator, prisma
|
|
297
|
+
pnpm basalt add mcp # @basaltkit/mcp at POST /mcp + the dev-only ai-mcp bridge + .mcp.json
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
`add` adapts the templates to the project as it is now (name, auth/tenancy from
|
|
301
|
+
its dependencies) and plans every change before applying any:
|
|
302
|
+
|
|
303
|
+
- **Existing files are never overwritten** — skipped with a notice; `--force`
|
|
304
|
+
overwrites generated files.
|
|
305
|
+
- **Merged, not replaced:** `package.json` (new dependencies in sorted
|
|
306
|
+
position, scripts such as `dev:web`; an existing entry is never changed),
|
|
307
|
+
`pnpm-workspace.yaml` (`web` added to `packages:`), `.gitignore`, `README.md`.
|
|
308
|
+
- **Your code** (`src/app.ts`, `src/routes.ts`) is regenerated only when
|
|
309
|
+
`.basalt/project.json` proves it untouched, patched where the template's
|
|
310
|
+
anchors are still unambiguous (works with `fastifyPlugin`, `expressPlugin` or
|
|
311
|
+
`honoPlugin`), and otherwise left alone with the exact manual steps printed.
|
|
312
|
+
- `add ui` needs a pnpm project (`web/` is a pnpm workspace member) and touches
|
|
313
|
+
no API code: the Vite dev server proxies `/api`, so there is no CORS to set up.
|
|
314
|
+
- Dependencies are installed afterwards unless `--no-install`; `--offline` uses
|
|
315
|
+
the ranges bundled with this create-basalt instead of the registry.
|
|
316
|
+
|
|
317
|
+
A project created without `--ui` followed by `add ui` ends up with the same
|
|
318
|
+
files as one created with `--ui` (the test suite checks exactly that).
|
|
319
|
+
|
|
320
|
+
### doctor and info
|
|
321
|
+
|
|
322
|
+
`doctor` is read-only and exits non-zero only on errors (warnings exit 0). It
|
|
323
|
+
checks Node against `engines` and Basalt's `>=22.5.0`; the package manager and
|
|
324
|
+
lockfiles; installed versus declared versions, duplicated `@basaltkit/*`
|
|
325
|
+
versions and unmet peer ranges; framework packages behind `latest` (skip with
|
|
326
|
+
`--offline`); the auth secret (`<PREFIX>_APP_SECRET`, from the environment or
|
|
327
|
+
`.env`, against the `minLength` in `src/env.ts` and the placeholder rules of
|
|
328
|
+
`secret()`); every variable `src/env.ts` **requires** (declared without a
|
|
329
|
+
default — `DATABASE_URL` with `--prisma`) that is set neither in the environment
|
|
330
|
+
nor in `.env` — an error, with the fix (`cp .env.example .env`, set it, start
|
|
331
|
+
PostgreSQL); a missing `.env` next to a `.env.example` (warning); whether the Prisma client is generated and
|
|
332
|
+
migrations exist (whether they are *applied* needs a database —
|
|
333
|
+
`prisma migrate status`); `.mcp.json` when `@basaltkit/ai-mcp` is installed; dev
|
|
334
|
+
tooling declared as a runtime dependency; and an outdated `bin/basalt.ts` or
|
|
335
|
+
`src/dev.ts`.
|
|
336
|
+
|
|
337
|
+
`info` prints create-basalt, Node, OS, package manager, the app's features and
|
|
338
|
+
the declared/installed versions of the framework and key tools — paste it into
|
|
339
|
+
bug reports.
|
|
340
|
+
|
|
341
|
+
### The scaffold manifest
|
|
342
|
+
|
|
343
|
+
`.basalt/project.json` records the create-basalt version, the options and a
|
|
344
|
+
`sha256` of every file the scaffold wrote (`package.json` excluded — installs
|
|
345
|
+
rewrite it). Commit it: it is how `add` and `update` tell an untouched template
|
|
346
|
+
file from one you edited. Projects created before it existed are handled too —
|
|
347
|
+
features are inferred from `package.json` and the tree, and every file counts as
|
|
348
|
+
yours; the first `add` creates the manifest.
|
|
349
|
+
|
|
212
350
|
### `pnpm-workspace.yaml` and pnpm 11
|
|
213
351
|
|
|
214
352
|
- `minimumReleaseAgeExclude` is evaluated **first-match-wins by package name**. To exclude several versions of one package, write ONE entry with a union — `'@types/node@22.20.4 || 26.6.2'` — never two `@types/node@…` entries (only the first would apply). The generated file carries this example as a comment.
|
|
@@ -216,7 +354,9 @@ With pnpm 11, every `pnpm <script>` and `pnpm exec` first verifies dependencies
|
|
|
216
354
|
|
|
217
355
|
### Environment variables and `--env-file`
|
|
218
356
|
|
|
219
|
-
|
|
357
|
+
**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.
|
|
358
|
+
|
|
359
|
+
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.
|
|
220
360
|
|
|
221
361
|
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.
|
|
222
362
|
|
|
@@ -237,6 +377,8 @@ NODE_ENV=development # never prefixed — a Node-wide convention
|
|
|
237
377
|
# MY_SAAS_APP_SECRET= # with auth
|
|
238
378
|
```
|
|
239
379
|
|
|
380
|
+
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.
|
|
381
|
+
|
|
240
382
|
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).
|
|
241
383
|
|
|
242
384
|
### Programmatic usage (Advanced)
|
|
@@ -289,7 +431,7 @@ Exported from `create-basalt` (in addition to the `create-basalt` executable):
|
|
|
289
431
|
| Field | Type | Description |
|
|
290
432
|
| --- | --- | --- |
|
|
291
433
|
| `dir` | `string` | Absolute path of the created folder |
|
|
292
|
-
| `files` | `string[]` | Files written (relative, sorted) |
|
|
434
|
+
| `files` | `string[]` | Files written (relative, sorted) — includes the development `.env` |
|
|
293
435
|
| `options` | `ProjectOptions` | The options actually applied (with defaults resolved) |
|
|
294
436
|
| `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' }`) |
|
|
295
437
|
|
|
@@ -299,6 +441,17 @@ Throws `TargetNotEmptyError` if the destination folder exists and isn't empty.
|
|
|
299
441
|
|
|
300
442
|
Detects the manager that invoked the command from `npm_config_user_agent` (or the string passed in). Returns `'pnpm' | 'yarn' | 'bun'` when recognized; otherwise `'npm'`.
|
|
301
443
|
|
|
444
|
+
### Project commands (programmatic)
|
|
445
|
+
|
|
446
|
+
| Export | What it does |
|
|
447
|
+
| --- | --- |
|
|
448
|
+
| `runProjectCommand(argv, deps)` | Runs `update`/`add`/`doctor`/`info` and returns the exit code; every side effect (output, prompts, package-manager runs, registry fetch, codemods) is injected through `deps` |
|
|
449
|
+
| `loadProject(dir, pm?)` | Reads an app: package.json (+ `web/`), manifest, detected features and package manager. Throws `NotABasaltAppError` |
|
|
450
|
+
| `planUpdate(ctx, { major?, only?, tooling?, registry? })` | The update plan — entries, the full before/after of every file, tooling, warnings, framework majors. Writes nothing |
|
|
451
|
+
| `planAdd(ctx, feature, { force?, resolveLatest?, registry? })` | The add plan — created/merged/skipped files and manual steps. Writes nothing |
|
|
452
|
+
| `runDoctor(ctx, options)` | Doctor findings (`{ level: 'ok' \| 'info' \| 'warn' \| 'error', area, message }`) |
|
|
453
|
+
| `MANIFEST_PATH`, `readManifest(dir)`, `hashContent(text)` | The scaffold manifest |
|
|
454
|
+
|
|
302
455
|
### `TargetNotEmptyError`
|
|
303
456
|
|
|
304
457
|
Error (extends `Error`) with the message `Target directory "<dir>" already exists and is not empty.`
|
|
@@ -329,9 +482,12 @@ Outside an interactive terminal (no TTY) the questions are skipped. Always pass
|
|
|
329
482
|
Automatic installation failed (network, Node version, etc.). Go into the folder and run `pnpm install` (or the indicated manager) to see the real error.
|
|
330
483
|
|
|
331
484
|
**I started the app and `GET /auth/login` gives a secret error.**
|
|
332
|
-
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`).
|
|
485
|
+
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`.
|
|
486
|
+
|
|
487
|
+
**`The app cannot start — the database did not answer`.**
|
|
488
|
+
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.
|
|
333
489
|
|
|
334
|
-
**With `--prisma`, the app refuses to boot: `PRISMA_NOT_MIGRATED
|
|
490
|
+
**With `--prisma`, the app refuses to boot: `PRISMA_NOT_MIGRATED`** (from `pnpm basalt`: `The app cannot start — the database is not migrated`).
|
|
335
491
|
`assertMigrated` reached a database without the `_prisma_migrations` table. The
|
|
336
492
|
message names the database and host it actually reached (never the
|
|
337
493
|
credentials): either it is not the database you meant — check
|
|
@@ -346,13 +502,28 @@ The Prisma client is generated code, not a checked-in file. `pnpm install` runs
|
|
|
346
502
|
or after editing the schema, run `pnpm db:generate`.
|
|
347
503
|
|
|
348
504
|
**My app connects to the wrong database / port.**
|
|
349
|
-
A variable exported in your shell beats `--env-file
|
|
505
|
+
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`*.
|
|
506
|
+
|
|
507
|
+
**`The app cannot start — invalid environment variables` (or `EnvValidationError: … expected string, received undefined`).**
|
|
508
|
+
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.
|
|
350
509
|
|
|
351
510
|
**`pnpm basalt …` starts by running `pnpm install` (or fails offline).**
|
|
352
511
|
pnpm 11's `verifyDepsBeforeRun` (default `install`). Run `node_modules/.bin/tsx bin/basalt.ts …` instead, or set `verifyDepsBeforeRun: warn` in `pnpm-workspace.yaml`.
|
|
353
512
|
|
|
354
|
-
**I want to change my mind after generating (e.g. add
|
|
355
|
-
|
|
513
|
+
**I want to change my mind after generating (e.g. add the frontend).**
|
|
514
|
+
`pnpm basalt add ui` (or `npx create-basalt@latest add ui`) — likewise `cli` and `mcp`; see [Adding features later](#adding-features-later). Billing, tenancy and auth are not `add`-able (they reshape `src/app.ts` and the schema): install `@basaltkit/subscriptions` and add the `subscriptionsPlugin` to `src/app.ts` by hand (the generated README and the templates serve as reference).
|
|
515
|
+
|
|
516
|
+
**`npm create basalt update` did not create a project called "update".**
|
|
517
|
+
`update`, `add`, `doctor` and `info` are project commands. Use `npm create basalt -- --name=update`.
|
|
518
|
+
|
|
519
|
+
**`update cannot run with --offline` / `Could not reach https://registry.npmjs.org for any package`.**
|
|
520
|
+
`update` resolves versions from the registry. Drop `--offline`, check the connection, or point `npm_config_registry` at your mirror.
|
|
521
|
+
|
|
522
|
+
**`Not a terminal — nothing written. Re-run with --yes`.**
|
|
523
|
+
`update` and `add` only apply after a confirmation; in CI or a pipe, pass `--yes`.
|
|
524
|
+
|
|
525
|
+
**`pnpm basalt update` says `Unknown command "update"`.**
|
|
526
|
+
The app's `bin/basalt.ts` predates the project commands. Run `npx create-basalt@latest update` once — it patches an unmodified `bin/basalt.ts` (or prints the snippet for a customised one).
|
|
356
527
|
|
|
357
528
|
## How it connects to other modules
|
|
358
529
|
|
|
@@ -363,6 +534,6 @@ There's no "re-scaffold" command. Either generate a new project with the right f
|
|
|
363
534
|
- **`@basaltkit/auth`** — included by default (remove with `--no-auth`): `/auth/*` routes and `APP_SECRET` validated in `env.ts`.
|
|
364
535
|
- **`@basaltkit/subscriptions`** — with `--billing`: example `free`/`pro` plans with trial and feature limits.
|
|
365
536
|
- **`@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.
|
|
366
|
-
- **`@basaltkit/cli` + `@basaltkit/generator`** — with `--cli
|
|
537
|
+
- **`@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.
|
|
367
538
|
- **`@basaltkit/sdk` + `@basaltkit/admin-shadcn` + `@basaltkit/admin`** — with `--ui`: the `web/` frontend calls the API through a typed client and uses the shadcn components.
|
|
368
539
|
- **`@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 --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
|
@@ -2,12 +2,15 @@ import { stdout } from 'node:process';
|
|
|
2
2
|
export const USAGE = `Usage: npm create basalt <name> [options]
|
|
3
3
|
|
|
4
4
|
Options:
|
|
5
|
+
--name=<name> Project name (the way to create a project named like a
|
|
6
|
+
project command: update, add, doctor, info)
|
|
5
7
|
--dir=<path> Target directory (default: ./<name>)
|
|
6
8
|
--no-tenancy Skip multi-tenancy
|
|
7
9
|
--no-auth Skip authentication
|
|
8
10
|
--billing Include subscriptions/billing
|
|
9
11
|
--ui Scaffold a web/ frontend (React + shadcn + SDK)
|
|
10
|
-
--cli
|
|
12
|
+
--no-cli Skip the 'basalt' CLI (code generators + commands;
|
|
13
|
+
on by default)
|
|
11
14
|
--mcp Expose read-only routes as MCP tools at /mcp
|
|
12
15
|
--prisma, --db Back the app with PostgreSQL through Prisma: schema,
|
|
13
16
|
migrations, prisma-backed stores and a boot-time check
|
|
@@ -25,6 +28,13 @@ Options:
|
|
|
25
28
|
-h, --help Show this help
|
|
26
29
|
|
|
27
30
|
Run with no name in a terminal to be prompted interactively.
|
|
31
|
+
|
|
32
|
+
Project commands — inside an existing app (in a scaffolded app: pnpm basalt <command>):
|
|
33
|
+
create-basalt update Update dependencies to the latest versions (--dry, --major, …)
|
|
34
|
+
create-basalt add <feature> Add ui | cli | mcp to the app
|
|
35
|
+
create-basalt doctor Check the project's health
|
|
36
|
+
create-basalt info Versions summary for bug reports
|
|
37
|
+
create-basalt <command> --help Options of one command
|
|
28
38
|
`;
|
|
29
39
|
export function parseArgs(argv) {
|
|
30
40
|
const flags = {
|
|
@@ -32,7 +42,7 @@ export function parseArgs(argv) {
|
|
|
32
42
|
auth: true,
|
|
33
43
|
billing: false,
|
|
34
44
|
ui: false,
|
|
35
|
-
cli:
|
|
45
|
+
cli: true,
|
|
36
46
|
mcp: false,
|
|
37
47
|
prisma: false,
|
|
38
48
|
yes: false,
|
|
@@ -49,6 +59,8 @@ export function parseArgs(argv) {
|
|
|
49
59
|
flags.ui = true;
|
|
50
60
|
else if (token === '--cli')
|
|
51
61
|
flags.cli = true;
|
|
62
|
+
else if (token === '--no-cli')
|
|
63
|
+
flags.cli = false;
|
|
52
64
|
else if (token === '--mcp')
|
|
53
65
|
flags.mcp = true;
|
|
54
66
|
// `--db` reads naturally in a sentence ("scaffold it with a db") and is the
|
|
@@ -69,6 +81,8 @@ export function parseArgs(argv) {
|
|
|
69
81
|
flags.offline = true;
|
|
70
82
|
else if (token.startsWith('--dir='))
|
|
71
83
|
flags.dir = token.slice('--dir='.length);
|
|
84
|
+
else if (token.startsWith('--name='))
|
|
85
|
+
flags.name = token.slice('--name='.length);
|
|
72
86
|
else if (token.startsWith('--pm='))
|
|
73
87
|
flags.pm = token.slice('--pm='.length);
|
|
74
88
|
else if (token === '--help' || token === '-h') {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** Marker proving a bin/basalt.ts already delegates the project commands. */
|
|
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)";
|
|
5
|
+
export declare function basaltBin(): string;
|
|
6
|
+
/**
|
|
7
|
+
* Hashes of every bin/basalt.ts an earlier create-basalt release generated
|
|
8
|
+
* (CRLF-insensitive). Only these — or a file the project's manifest records as
|
|
9
|
+
* untouched — are patched automatically.
|
|
10
|
+
*/
|
|
11
|
+
export declare const LEGACY_BIN_HASHES: ReadonlySet<string>;
|
|
12
|
+
export declare const hasProjectCommands: (content: string) => boolean;
|
|
13
|
+
export declare const hasDevPrelude: (content: string) => boolean;
|
|
14
|
+
/**
|
|
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}.
|
|
22
|
+
*/
|
|
23
|
+
export declare function patchBasaltBin(content: string): string;
|
|
24
|
+
export type BinStatus = 'current' | 'patchable' | 'modified' | 'absent';
|
|
25
|
+
/** Where a bin/basalt.ts stands: already current, an untouched old template, or user code. */
|
|
26
|
+
export declare function binStatus(content: string | undefined, manifestHash?: string): BinStatus;
|
|
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;
|