create-basalt 1.6.0 → 1.8.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 +85 -8
- package/dist/args.d.ts +2 -1
- package/dist/args.js +8 -0
- package/dist/cli.js +9 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +13 -3
- package/dist/latest-versions.js +5 -0
- package/dist/templates.d.ts +38 -2
- package/dist/templates.js +593 -41
- package/dist/versions.js +10 -6
- package/dist/wizard.d.ts +2 -1
- package/dist/wizard.js +4 -2
- package/package.json +17 -8
package/README.md
CHANGED
|
@@ -48,6 +48,7 @@ Subscriptions / billing? (y/N) n
|
|
|
48
48
|
Web UI (React + shadcn)? (y/N) n
|
|
49
49
|
MCP server (routes as AI-agent tools)? (y/N) n
|
|
50
50
|
'basalt' CLI (code generators)? (y/N) n
|
|
51
|
+
Database (PostgreSQL via Prisma)? (y/N) n
|
|
51
52
|
Install dependencies now? (y/N) y
|
|
52
53
|
Initialize a git repository? (y/N) y
|
|
53
54
|
```
|
|
@@ -91,6 +92,7 @@ Usage: npm create basalt <name> [options]
|
|
|
91
92
|
| `--ui` | off | Generates the `web/` frontend (React + shadcn via `@basaltkit/admin-shadcn` + `@basaltkit/sdk`). **Forces pnpm** — see note below |
|
|
92
93
|
| `--cli` | off | Generates the `basalt` CLI (`bin/basalt.ts`, `pnpm basalt` script, `make:*` generators from `@basaltkit/generator`) |
|
|
93
94
|
| `--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
|
+
| `--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 |
|
|
94
96
|
| `--install` | off | Installs dependencies at the end (with the detected/chosen manager) |
|
|
95
97
|
| `--git` | off | Runs `git init` + first commit ("Initial commit from create-basalt") |
|
|
96
98
|
| `--offline` | off | Don't query the npm registry for the latest versions; use the ranges bundled with this create-basalt release |
|
|
@@ -115,7 +117,7 @@ Behavior notes (faithful to the code):
|
|
|
115
117
|
pnpm create basalt
|
|
116
118
|
|
|
117
119
|
# Full project, no questions, with everything:
|
|
118
|
-
pnpm create basalt my-app --billing --ui --cli --install --git
|
|
120
|
+
pnpm create basalt my-app --billing --ui --cli --prisma --install --git
|
|
119
121
|
|
|
120
122
|
# Minimal API (no tenancy or auth), in another folder:
|
|
121
123
|
npm create basalt service-api --no-tenancy --no-auth --dir=./apps/service-api
|
|
@@ -135,19 +137,19 @@ Always:
|
|
|
135
137
|
my-app/
|
|
136
138
|
├── package.json # scripts: dev, start, test, typecheck (+ basalt with --cli)
|
|
137
139
|
├── tsconfig.json # strict TypeScript, ESM
|
|
138
|
-
├── .env.example #
|
|
140
|
+
├── .env.example # MY_APP_PORT, MY_APP_HOST, MY_APP_LOG_LEVEL, NODE_ENV (+ MY_APP_APP_SECRET with auth) + the --env-file precedence warning
|
|
139
141
|
├── .gitignore
|
|
140
142
|
├── README.md # instructions adapted to the chosen options
|
|
141
143
|
├── pnpm-workspace.yaml # esbuild allowBuilds, @basaltkit/* release-age exclusion (+ "web" member with --ui) + pnpm 11 notes
|
|
142
144
|
├── src/
|
|
143
|
-
│ ├── env.ts # environment variables validated with Zod (@basaltkit/env)
|
|
145
|
+
│ ├── env.ts # environment variables validated with Zod (@basaltkit/env), app-prefixed
|
|
144
146
|
│ ├── app.ts # buildApp() with the chosen plugins
|
|
145
147
|
│ ├── routes.ts # GET / (friendly index) and GET /health
|
|
146
148
|
│ └── server.ts # startup + clean shutdown on SIGINT/SIGTERM
|
|
147
149
|
└── tests/app.test.ts # smoke test adapted to the options
|
|
148
150
|
```
|
|
149
151
|
|
|
150
|
-
With `--cli`, adds `bin/basalt.ts` and the `"basalt": "tsx bin/basalt.ts"` script. 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).
|
|
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. With `--cli`, adds `bin/basalt.ts` and the `"basalt": "tsx bin/basalt.ts"` script. 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).
|
|
151
153
|
|
|
152
154
|
### With `--ui`: running the API and frontend
|
|
153
155
|
|
|
@@ -159,6 +161,44 @@ pnpm --filter my-app-web dev # terminal 2 — UI at http://localhost:5180
|
|
|
159
161
|
|
|
160
162
|
Vite's dev server proxies `/api` to the API — there's no CORS to configure.
|
|
161
163
|
|
|
164
|
+
### With `--prisma`: a PostgreSQL-backed app
|
|
165
|
+
|
|
166
|
+
Without the flag the generated app has **no database**: it boots on
|
|
167
|
+
`MemoryUserSource` / `MemoryTenantSource` and the default in-memory team and
|
|
168
|
+
subscription stores, which is ideal for a first run and for CI and is gone on
|
|
169
|
+
the next restart.
|
|
170
|
+
|
|
171
|
+
`--prisma` (or `--db`) generates the database-backed shape instead:
|
|
172
|
+
|
|
173
|
+
| File | What it holds |
|
|
174
|
+
| --- | --- |
|
|
175
|
+
| `prisma/schema.prisma` | The reference models of every `@basaltkit/*-prisma` package the project uses — the same blocks `basalt prisma:sync` merges — plus a `Project` model of your own |
|
|
176
|
+
| `prisma.config.ts` | Prisma 7 keeps the connection URL here. It reads `MY_APP_DATABASE_URL` first and the bare `DATABASE_URL` only as a fallback — the same precedence as `src/env.ts`, so the CLI and the app can never mean two different databases |
|
|
177
|
+
| `src/db.ts` | `prisma` (unscoped — the framework stores use it, they run before a tenant is known) and, with tenancy, `db = prisma.$extends(tenancyExtension())` |
|
|
178
|
+
| `src/app.ts` | `prismaPlugin({ client: db, assertMigrated: true })`, `prismaTenantSource`, `prismaAuthStores`, `prismaTeamsStores`, `prismaSubscriptionsStores` |
|
|
179
|
+
| `prisma/seed.ts` | The `demo` tenant the header and subdomain resolvers expect |
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
pnpm create basalt my-app --prisma
|
|
183
|
+
cd my-app && pnpm install # postinstall runs `prisma generate`
|
|
184
|
+
cp .env.example .env # point MY_APP_DATABASE_URL at your database
|
|
185
|
+
pnpm db:migrate # prisma migrate dev — creates the tables and seeds `demo`
|
|
186
|
+
pnpm dev
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**Migrations, never `prisma db push`.** The generated app boots with
|
|
190
|
+
`assertMigrated: true`, which refuses to start unless the database it reached has
|
|
191
|
+
the `_prisma_migrations` table — written by `prisma migrate dev` /
|
|
192
|
+
`prisma migrate deploy`, and *not* by `db push`. That is what turns a wrong
|
|
193
|
+
`DATABASE_URL` (a shell that exported another project's) into a boot error naming
|
|
194
|
+
the database and host instead of a `500` on the first request. In production:
|
|
195
|
+
`pnpm db:deploy`.
|
|
196
|
+
|
|
197
|
+
The generated `tests/app.test.ts` skips itself when no database is configured, so
|
|
198
|
+
`pnpm test` stays green on a machine without PostgreSQL. To add another Basalt
|
|
199
|
+
domain later, install its `@basaltkit/<domain>-prisma` package, run
|
|
200
|
+
`pnpm basalt prisma:sync` (with `--cli`) and then `pnpm db:migrate`.
|
|
201
|
+
|
|
162
202
|
### With `--cli`: the `basalt` command line
|
|
163
203
|
|
|
164
204
|
```bash
|
|
@@ -176,7 +216,28 @@ With pnpm 11, every `pnpm <script>` and `pnpm exec` first verifies dependencies
|
|
|
176
216
|
|
|
177
217
|
### Environment variables and `--env-file`
|
|
178
218
|
|
|
179
|
-
Nothing loads `.env` for you. `node --env-file=.env` / `tsx --env-file=.env` **never override a variable already exported in the shell** — in a terminal where another project exported `DATABASE_URL` or `PORT`,
|
|
219
|
+
Nothing loads `.env` for you. `node --env-file=.env` / `tsx --env-file=.env` **never override 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
|
+
|
|
221
|
+
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
|
+
|
|
223
|
+
```ts
|
|
224
|
+
// src/env.ts (generated)
|
|
225
|
+
export const env = defineEnv(
|
|
226
|
+
{ PORT: z.coerce.number().default(3000), /* … */ },
|
|
227
|
+
{ prefix: 'MY_SAAS' },
|
|
228
|
+
)
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
# .env.example (generated)
|
|
233
|
+
MY_SAAS_PORT=3000
|
|
234
|
+
MY_SAAS_HOST=0.0.0.0
|
|
235
|
+
MY_SAAS_LOG_LEVEL=info
|
|
236
|
+
NODE_ENV=development # never prefixed — a Node-wide convention
|
|
237
|
+
# MY_SAAS_APP_SECRET= # with auth
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
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).
|
|
180
241
|
|
|
181
242
|
### Programmatic usage (Advanced)
|
|
182
243
|
|
|
@@ -219,6 +280,7 @@ Exported from `create-basalt` (in addition to the `create-basalt` executable):
|
|
|
219
280
|
| `ui` | `boolean` | No | `false` | Generate the `web/` frontend |
|
|
220
281
|
| `cli` | `boolean` | No | `false` | Generate the `basalt` CLI |
|
|
221
282
|
| `mcp` | `boolean` | No | `false` | Expose read-only routes as MCP tools at `/mcp` |
|
|
283
|
+
| `prisma` | `boolean` | No | `false` | Back the app with PostgreSQL through Prisma (schema, migrations, Prisma-backed stores, `assertMigrated`) |
|
|
222
284
|
| `resolveLatest` | `boolean` | No | `false` | Resolve every dependency to `^<latest>` from the npm registry before writing (falls back to the bundled ranges on any registry failure) |
|
|
223
285
|
| `registry` | `ResolveLatestOptions` | No | — | `{ fetch?, registry?, timeoutMs?, overallTimeoutMs?, concurrency?, minimumReleaseAge?, now? }` — injectable fetch (tests), registry URL (default `npm_config_registry` or `https://registry.npmjs.org`), timeouts (5 s per request, 15 s overall), release-age window in minutes (default `pnpm_config_minimum_release_age` / `npm_config_minimum_release_age`, else 1440; `0` disables the probe), clock (tests) |
|
|
224
286
|
|
|
@@ -246,7 +308,7 @@ Error (extends `Error`) with the message `Target directory "<dir>" already exist
|
|
|
246
308
|
| Type | Description |
|
|
247
309
|
| --- | --- |
|
|
248
310
|
| `PackageManager` | `'pnpm' \| 'npm' \| 'yarn' \| 'bun'` |
|
|
249
|
-
| `ProjectOptions` | `{ name, tenancy, auth, billing, ui, cli }` — all resolved (no optionals) |
|
|
311
|
+
| `ProjectOptions` | `{ name, tenancy, auth, billing, ui, cli, mcp, prisma }` — all resolved (no optionals) |
|
|
250
312
|
| `CreateProjectInput`, `CreateProjectResult` | Described above |
|
|
251
313
|
|
|
252
314
|
## Common errors and solutions (FAQ)
|
|
@@ -267,10 +329,24 @@ Outside an interactive terminal (no TTY) the questions are skipped. Always pass
|
|
|
267
329
|
Automatic installation failed (network, Node version, etc.). Go into the folder and run `pnpm install` (or the indicated manager) to see the real error.
|
|
268
330
|
|
|
269
331
|
**I started the app and `GET /auth/login` gives a secret error.**
|
|
270
|
-
With auth on, `src/env.ts` requires `APP_SECRET` with at least
|
|
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`). Copy `.env.example` to `.env` and set your own secret before going to production.
|
|
333
|
+
|
|
334
|
+
**With `--prisma`, the app refuses to boot: `PRISMA_NOT_MIGRATED`.**
|
|
335
|
+
`assertMigrated` reached a database without the `_prisma_migrations` table. The
|
|
336
|
+
message names the database and host it actually reached (never the
|
|
337
|
+
credentials): either it is not the database you meant — check
|
|
338
|
+
`env | grep DATABASE_URL`, a shell may have exported another project's — or it
|
|
339
|
+
was never migrated: run `pnpm db:migrate` (development) or `pnpm db:deploy`
|
|
340
|
+
(production). A database set up with `prisma db push` has no
|
|
341
|
+
`_prisma_migrations` at all; this scaffold is migration-based on purpose.
|
|
342
|
+
|
|
343
|
+
**With `--prisma`, `pnpm typecheck` can't find `./generated/prisma/client.js`.**
|
|
344
|
+
The Prisma client is generated code, not a checked-in file. `pnpm install` runs
|
|
345
|
+
`prisma generate` for you (`postinstall`); after a `git clone` without install,
|
|
346
|
+
or after editing the schema, run `pnpm db:generate`.
|
|
271
347
|
|
|
272
348
|
**My app connects to the wrong database / port.**
|
|
273
|
-
A variable exported in your shell beats `--env-file`.
|
|
349
|
+
A variable exported in your shell beats `--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`*.
|
|
274
350
|
|
|
275
351
|
**`pnpm basalt …` starts by running `pnpm install` (or fails offline).**
|
|
276
352
|
pnpm 11's `verifyDepsBeforeRun` (default `install`). Run `node_modules/.bin/tsx bin/basalt.ts …` instead, or set `verifyDepsBeforeRun: warn` in `pnpm-workspace.yaml`.
|
|
@@ -286,6 +362,7 @@ There's no "re-scaffold" command. Either generate a new project with the right f
|
|
|
286
362
|
- **`@basaltkit/tenancy`** — included by default (remove with `--no-tenancy`): header and subdomain resolvers, with a demo `MemoryTenantSource`.
|
|
287
363
|
- **`@basaltkit/auth`** — included by default (remove with `--no-auth`): `/auth/*` routes and `APP_SECRET` validated in `env.ts`.
|
|
288
364
|
- **`@basaltkit/subscriptions`** — with `--billing`: example `free`/`pro` plans with trial and feature limits.
|
|
365
|
+
- **`@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.
|
|
289
366
|
- **`@basaltkit/cli` + `@basaltkit/generator`** — with `--cli`: `bin/basalt.ts` calls `runCli`, and `commandsPlugin(generatorCommands())` registers the `make:*` generators.
|
|
290
367
|
- **`@basaltkit/sdk` + `@basaltkit/admin-shadcn` + `@basaltkit/admin`** — with `--ui`: the `web/` frontend calls the API through a typed client and uses the shadcn components.
|
|
291
368
|
- **`@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 Scaffold the 'basalt' CLI (code generators + commands)\n --mcp Expose read-only routes as MCP tools at /mcp\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";
|
|
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 Scaffold the 'basalt' CLI (code generators + commands)\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";
|
|
3
3
|
export interface Flags {
|
|
4
4
|
name?: string;
|
|
5
5
|
dir?: string;
|
|
@@ -9,6 +9,7 @@ export interface Flags {
|
|
|
9
9
|
ui: boolean;
|
|
10
10
|
cli: boolean;
|
|
11
11
|
mcp: boolean;
|
|
12
|
+
prisma: boolean;
|
|
12
13
|
/** Tri-state: undefined = decide from the environment (TTY yes, CI no). */
|
|
13
14
|
install?: boolean;
|
|
14
15
|
git?: boolean;
|
package/dist/args.js
CHANGED
|
@@ -9,6 +9,9 @@ Options:
|
|
|
9
9
|
--ui Scaffold a web/ frontend (React + shadcn + SDK)
|
|
10
10
|
--cli Scaffold the 'basalt' CLI (code generators + commands)
|
|
11
11
|
--mcp Expose read-only routes as MCP tools at /mcp
|
|
12
|
+
--prisma, --db Back the app with PostgreSQL through Prisma: schema,
|
|
13
|
+
migrations, prisma-backed stores and a boot-time check
|
|
14
|
+
that the database is the migrated one
|
|
12
15
|
--install Install dependencies (default: yes in an interactive
|
|
13
16
|
terminal, skipped in CI/non-TTY; --no-install to opt out)
|
|
14
17
|
--no-install Never install dependencies
|
|
@@ -31,6 +34,7 @@ export function parseArgs(argv) {
|
|
|
31
34
|
ui: false,
|
|
32
35
|
cli: false,
|
|
33
36
|
mcp: false,
|
|
37
|
+
prisma: false,
|
|
34
38
|
yes: false,
|
|
35
39
|
offline: false,
|
|
36
40
|
};
|
|
@@ -47,6 +51,10 @@ export function parseArgs(argv) {
|
|
|
47
51
|
flags.cli = true;
|
|
48
52
|
else if (token === '--mcp')
|
|
49
53
|
flags.mcp = true;
|
|
54
|
+
// `--db` reads naturally in a sentence ("scaffold it with a db") and is the
|
|
55
|
+
// same switch; the canonical spelling stays the package name, like --mcp.
|
|
56
|
+
else if (token === '--prisma' || token === '--db')
|
|
57
|
+
flags.prisma = true;
|
|
50
58
|
else if (token === '--install')
|
|
51
59
|
flags.install = true;
|
|
52
60
|
else if (token === '--no-install')
|
package/dist/cli.js
CHANGED
|
@@ -3,6 +3,7 @@ import { spawn } from 'node:child_process';
|
|
|
3
3
|
import { stdin, stdout } from 'node:process';
|
|
4
4
|
import { createProject, describeResolution, detectPackageManager, resolveRunDefaults, runWizard, ttyPrompter, TargetNotEmptyError, WizardCancelledError, } from './index.js';
|
|
5
5
|
import { parseArgs, resolvesLatest, USAGE } from './args.js';
|
|
6
|
+
import { envPrefix } from './templates.js';
|
|
6
7
|
/** Runs a command inheriting stdio; resolves false on non-zero exit (never throws). */
|
|
7
8
|
function run(command, args, cwd) {
|
|
8
9
|
return new Promise((resolve) => {
|
|
@@ -35,6 +36,7 @@ if (!flags.yes && flags.name === undefined && stdin.isTTY) {
|
|
|
35
36
|
flags.ui = result.ui;
|
|
36
37
|
flags.cli = result.cli;
|
|
37
38
|
flags.mcp = result.mcp;
|
|
39
|
+
flags.prisma = result.prisma;
|
|
38
40
|
flags.install = result.install;
|
|
39
41
|
flags.git = result.git;
|
|
40
42
|
flags.pm = result.pm;
|
|
@@ -68,6 +70,7 @@ try {
|
|
|
68
70
|
ui: flags.ui,
|
|
69
71
|
cli: flags.cli,
|
|
70
72
|
mcp: flags.mcp,
|
|
73
|
+
prisma: flags.prisma,
|
|
71
74
|
// New apps get the latest published version of every dependency (with the
|
|
72
75
|
// bundled ranges as an offline fallback); --offline skips the registry.
|
|
73
76
|
resolveLatest: resolvesLatest(flags),
|
|
@@ -108,6 +111,12 @@ try {
|
|
|
108
111
|
const steps = [`cd ${result.dir}`];
|
|
109
112
|
if (!run_.install)
|
|
110
113
|
steps.push(`${pm} install`);
|
|
114
|
+
if (flags.prisma) {
|
|
115
|
+
// Nothing boots before the database exists: env.DATABASE_URL is required and
|
|
116
|
+
// the app asserts at boot that it reached the MIGRATED database.
|
|
117
|
+
steps.push(`cp .env.example .env # then point ${envPrefix(result.options.name)}_DATABASE_URL at your database`);
|
|
118
|
+
steps.push(`${pm} run db:migrate${flags.tenancy ? ' # creates the tables and seeds the demo tenant' : ' # creates the tables'}`);
|
|
119
|
+
}
|
|
111
120
|
steps.push(`${pm} run dev${flags.ui ? ' # API on :3000' : ''}`);
|
|
112
121
|
// `web` is wired as a pnpm workspace member (pnpm-workspace.yaml), so its dev
|
|
113
122
|
// server is launched with a pnpm filter regardless of the root package manager.
|
package/dist/index.d.ts
CHANGED
|
@@ -14,6 +14,12 @@ export interface CreateProjectInput {
|
|
|
14
14
|
cli?: boolean;
|
|
15
15
|
/** Expose opted-in routes as MCP tools over HTTP at `/mcp`. Default: false. */
|
|
16
16
|
mcp?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Back the app with PostgreSQL through Prisma: `prisma/schema.prisma`,
|
|
19
|
+
* `src/db.ts`, `prismaPlugin({ assertMigrated: true })` and the Prisma-backed
|
|
20
|
+
* stores instead of the in-memory ones. Default: false (no database at all).
|
|
21
|
+
*/
|
|
22
|
+
prisma?: boolean;
|
|
17
23
|
/**
|
|
18
24
|
* Resolve every dependency to its latest published version (npm registry)
|
|
19
25
|
* before writing files. Default: false — the embedded fallback ranges are
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { mkdir, readdir, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { dirname, join, resolve } from 'node:path';
|
|
3
|
-
import { appTest, appTs, devTs, dockerignore, envExample, envTs, gitignore, mcpJson, basaltBin, packageJson, pnpmWorkspaceYaml, readme, routesTs, serverTs, tsconfigJson, } from './templates.js';
|
|
3
|
+
import { appTest, appTs, dbTs, devTs, dockerignore, envExample, envTs, gitignore, mcpJson, basaltBin, packageJson, pnpmWorkspaceYaml, prismaConfigTs, prismaSchema, prismaSeedTs, readme, routesTs, serverTs, tsconfigJson, } from './templates.js';
|
|
4
4
|
import { uiFiles } from './templates-ui.js';
|
|
5
5
|
import { applyVersions, collectDependencies, resolveLatestVersions, } from './latest-versions.js';
|
|
6
6
|
export class TargetNotEmptyError extends Error {
|
|
@@ -45,6 +45,7 @@ export async function createProject(input) {
|
|
|
45
45
|
ui: input.ui ?? false,
|
|
46
46
|
cli: input.cli ?? false,
|
|
47
47
|
mcp: input.mcp ?? false,
|
|
48
|
+
prisma: input.prisma ?? false,
|
|
48
49
|
};
|
|
49
50
|
const dir = resolve(input.dir ?? input.name);
|
|
50
51
|
const existing = await readdir(dir).catch(() => null);
|
|
@@ -52,9 +53,9 @@ export async function createProject(input) {
|
|
|
52
53
|
throw new TargetNotEmptyError(dir);
|
|
53
54
|
const files = {
|
|
54
55
|
'package.json': packageJson(options),
|
|
55
|
-
'tsconfig.json': tsconfigJson(),
|
|
56
|
+
'tsconfig.json': tsconfigJson(options),
|
|
56
57
|
'.env.example': envExample(options),
|
|
57
|
-
'.gitignore': gitignore(),
|
|
58
|
+
'.gitignore': gitignore(options),
|
|
58
59
|
'.dockerignore': dockerignore(),
|
|
59
60
|
'README.md': readme(options),
|
|
60
61
|
'pnpm-workspace.yaml': pnpmWorkspaceYaml(options),
|
|
@@ -64,6 +65,15 @@ export async function createProject(input) {
|
|
|
64
65
|
'src/server.ts': serverTs(),
|
|
65
66
|
'src/dev.ts': devTs(),
|
|
66
67
|
'tests/app.test.ts': appTest(options),
|
|
68
|
+
...(options.prisma
|
|
69
|
+
? {
|
|
70
|
+
'prisma/schema.prisma': prismaSchema(options),
|
|
71
|
+
'prisma.config.ts': prismaConfigTs(options),
|
|
72
|
+
'src/db.ts': dbTs(options),
|
|
73
|
+
// The demo tenant only exists where tenancy does.
|
|
74
|
+
...(options.tenancy ? { 'prisma/seed.ts': prismaSeedTs(options) } : {}),
|
|
75
|
+
}
|
|
76
|
+
: {}),
|
|
67
77
|
...(options.cli ? { 'bin/basalt.ts': basaltBin() } : {}),
|
|
68
78
|
...(options.mcp ? { '.mcp.json': mcpJson(options) } : {}),
|
|
69
79
|
...(options.ui ? uiFiles(options) : {}),
|
package/dist/latest-versions.js
CHANGED
|
@@ -31,12 +31,17 @@
|
|
|
31
31
|
* major. Bump a range to a new major only together with the templates.
|
|
32
32
|
*/
|
|
33
33
|
export const THIRD_PARTY_VERSIONS = {
|
|
34
|
+
'@prisma/adapter-pg': '^7.10.0',
|
|
35
|
+
'@prisma/client': '^7.10.0',
|
|
34
36
|
'@tailwindcss/vite': '^4.3.3',
|
|
35
37
|
'@types/node': '^26.6.2',
|
|
38
|
+
'@types/pg': '^8.15.6',
|
|
36
39
|
'@types/react': '^19.3.0',
|
|
37
40
|
'@types/react-dom': '^19.3.0',
|
|
38
41
|
'@vitejs/plugin-react': '^6.1.1',
|
|
42
|
+
pg: '^8.16.3',
|
|
39
43
|
'pino-pretty': '^13.1.3',
|
|
44
|
+
prisma: '^7.10.0',
|
|
40
45
|
react: '^19.3.0',
|
|
41
46
|
'react-dom': '^19.3.0',
|
|
42
47
|
tailwindcss: '^4.3.3',
|
package/dist/templates.d.ts
CHANGED
|
@@ -9,6 +9,12 @@ export interface ProjectOptions {
|
|
|
9
9
|
cli: boolean;
|
|
10
10
|
/** Expose opted-in routes as MCP tools (@basaltkit/mcp) over HTTP at `/mcp`. */
|
|
11
11
|
mcp: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Back the app with a real PostgreSQL database: a `prisma/schema.prisma`,
|
|
14
|
+
* `src/db.ts`, `prismaPlugin({ assertMigrated: true })` and the Prisma-backed
|
|
15
|
+
* stores instead of the in-memory ones.
|
|
16
|
+
*/
|
|
17
|
+
prisma: boolean;
|
|
12
18
|
}
|
|
13
19
|
export { SCAFFOLD_VERSIONS } from './versions.js';
|
|
14
20
|
export { THIRD_PARTY_VERSIONS } from './latest-versions.js';
|
|
@@ -29,7 +35,7 @@ export declare const thirdPartyVersionOf: (pkg: string) => string;
|
|
|
29
35
|
/** True when the scaffold binds users to the tenant they act on (tenancy + auth). */
|
|
30
36
|
export declare const enforcesMembership: (options: ProjectOptions) => boolean;
|
|
31
37
|
export declare function packageJson(options: ProjectOptions): string;
|
|
32
|
-
export declare function tsconfigJson(): string;
|
|
38
|
+
export declare function tsconfigJson(options: ProjectOptions): string;
|
|
33
39
|
export declare function envTs(options: ProjectOptions): string;
|
|
34
40
|
/**
|
|
35
41
|
* An app-specific env-var prefix derived from the project name
|
|
@@ -38,6 +44,8 @@ export declare function envTs(options: ProjectOptions): string;
|
|
|
38
44
|
*/
|
|
39
45
|
export declare function envPrefix(name: string): string;
|
|
40
46
|
export declare function envExample(options: ProjectOptions): string;
|
|
47
|
+
/** A PostgreSQL-safe database name derived from the project name. */
|
|
48
|
+
export declare function databaseName(name: string): string;
|
|
41
49
|
export declare function appTs(options: ProjectOptions): string;
|
|
42
50
|
export declare function routesTs(options: ProjectOptions): string;
|
|
43
51
|
export declare function serverTs(): string;
|
|
@@ -49,6 +57,34 @@ export declare function serverTs(): string;
|
|
|
49
57
|
*/
|
|
50
58
|
export declare function devTs(): string;
|
|
51
59
|
export declare function basaltBin(): string;
|
|
60
|
+
/**
|
|
61
|
+
* `prisma/schema.prisma` — the datasource, the client generator, the models of
|
|
62
|
+
* every Basalt domain this app enables, and the app's own first model.
|
|
63
|
+
*
|
|
64
|
+
* Prisma 7 no longer takes the connection URL here (prisma.config.ts does), and
|
|
65
|
+
* the client needs an explicit `output`; src/db.ts imports it from there.
|
|
66
|
+
*/
|
|
67
|
+
export declare function prismaSchema(options: ProjectOptions): string;
|
|
68
|
+
/**
|
|
69
|
+
* `prisma.config.ts` — Prisma 7 reads the connection URL (and the seed command)
|
|
70
|
+
* from here instead of from schema.prisma. It applies the SAME precedence as
|
|
71
|
+
* src/env.ts: the app-prefixed name first, the bare `DATABASE_URL` only as a
|
|
72
|
+
* fallback, so the CLI and the running app can never mean different databases.
|
|
73
|
+
*/
|
|
74
|
+
export declare function prismaConfigTs(options: ProjectOptions): string;
|
|
75
|
+
/**
|
|
76
|
+
* `src/db.ts` — the Prisma client(s). Two bindings on purpose: the unscoped
|
|
77
|
+
* `prisma` for the framework stores (they key their own rows and run outside a
|
|
78
|
+
* tenant context) and, with tenancy, the `db` client the whole application uses,
|
|
79
|
+
* where every query is filtered by the request's tenant.
|
|
80
|
+
*/
|
|
81
|
+
export declare function dbTs(options: ProjectOptions): string;
|
|
82
|
+
/**
|
|
83
|
+
* `prisma/seed.ts` — creates the `demo` tenant the scaffolded resolvers expect
|
|
84
|
+
* (`x-tenant-id: demo`, `demo.localhost`). Runs from `pnpm db:seed` and after
|
|
85
|
+
* `prisma migrate dev` (prisma.config.ts declares it).
|
|
86
|
+
*/
|
|
87
|
+
export declare function prismaSeedTs(options: ProjectOptions): string;
|
|
52
88
|
export declare function appTest(options: ProjectOptions): string;
|
|
53
89
|
export declare function readme(options: ProjectOptions): string;
|
|
54
90
|
/**
|
|
@@ -65,7 +101,7 @@ export declare function mcpJson(_options: ProjectOptions): string;
|
|
|
65
101
|
* `certs/server.key` through.
|
|
66
102
|
*/
|
|
67
103
|
export declare function dockerignore(): string;
|
|
68
|
-
export declare function gitignore(): string;
|
|
104
|
+
export declare function gitignore(options: ProjectOptions): string;
|
|
69
105
|
/**
|
|
70
106
|
* pnpm settings: esbuild's build script is required by tsx;
|
|
71
107
|
* msgpackr-extract (optional native accelerator via BullMQ) is declined —
|