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/dist/templates.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { THIRD_PARTY_VERSIONS } from './latest-versions.js';
2
+ import { DOCKERFILE, DOCKERIGNORE, PRODUCTION_ENTRY } from './stubs.js';
2
3
  import { SCAFFOLD_VERSIONS } from './versions.js';
3
4
  export { basaltBin } from './bin-template.js';
4
5
  export { SCAFFOLD_VERSIONS } from './versions.js';
@@ -29,6 +30,24 @@ export const thirdPartyVersionOf = (pkg) => {
29
30
  }
30
31
  return range;
31
32
  };
33
+ /**
34
+ * The production path (BK-026): `build` compiles src/ with tsconfig.build.json
35
+ * (rootDir `.`, outDir `dist`) and `start` runs the result on plain node — the
36
+ * same entry the Dockerfile's CMD runs. `start:dev` keeps the old tsx start.
37
+ */
38
+ export const BUILD_SCRIPT = 'tsc -p tsconfig.build.json';
39
+ /** The `db:seed` script of a `--prisma` scaffold with tenancy (prisma.config.ts loads .env, then runs `migrations.seed`). */
40
+ export const DB_SEED_SCRIPT = 'prisma db seed';
41
+ export const START_SCRIPT = `node --enable-source-maps ${PRODUCTION_ENTRY}`;
42
+ export const START_DEV_SCRIPT = 'tsx src/server.ts';
43
+ /** The `start` script every create-basalt release before the production path wrote. */
44
+ export const LEGACY_START_SCRIPT = 'tsx src/server.ts';
45
+ /**
46
+ * package.json `imports` alias for the generated Prisma client. It lives in
47
+ * ./generated/prisma — outside src/, so tsc never has to copy its .js files —
48
+ * and `#db/*` resolves to it from src/ (tsx, vitest) and dist/src/ (node) alike.
49
+ */
50
+ export const PRISMA_IMPORTS = { '#db/*': './generated/prisma/*' };
32
51
  /** The root `basalt` script of an app without `--cli`: create-basalt's project commands. */
33
52
  export const BASALT_PROJECT_SCRIPT = 'create-basalt --project';
34
53
  /** The root `dev:web` script of a `--ui` app (web/ is a pnpm workspace member). */
@@ -75,7 +94,11 @@ export function packageJson(options) {
75
94
  if (options.prisma) {
76
95
  // Prisma 7 talks to PostgreSQL through a driver adapter, so `pg` is a real
77
96
  // runtime dependency (see src/db.ts); the `prisma` CLI is dev-only.
78
- for (const pkg of ['@prisma/adapter-pg', '@prisma/client', 'pg']) {
97
+ // @prisma/client-runtime-utils is required BY NAME from the generated
98
+ // client's runtime/ (./generated/prisma/runtime/client.js). Under pnpm it is
99
+ // only a transitive dependency of @prisma/client, out of reach of plain
100
+ // `node` — so the app declares it directly (BK-026).
101
+ for (const pkg of ['@prisma/adapter-pg', '@prisma/client', '@prisma/client-runtime-utils', 'pg']) {
79
102
  dependencies[pkg] = thirdPartyVersionOf(pkg);
80
103
  }
81
104
  }
@@ -108,11 +131,15 @@ export function packageJson(options) {
108
131
  version: '0.1.0',
109
132
  private: true,
110
133
  type: 'module',
134
+ ...(options.prisma ? { imports: PRISMA_IMPORTS } : {}),
111
135
  scripts: {
112
136
  // dev opts into NODE_ENV=development (src/dev.ts); start does not, so an
113
137
  // unset NODE_ENV counts as production and secrets are required.
114
138
  dev: 'tsx watch src/dev.ts',
115
- start: 'tsx src/server.ts',
139
+ // Production: compile once, run on plain node (no tsx at runtime).
140
+ build: BUILD_SCRIPT,
141
+ start: START_SCRIPT,
142
+ 'start:dev': START_DEV_SCRIPT,
116
143
  test: 'vitest run',
117
144
  typecheck: 'tsc --noEmit',
118
145
  // With --cli, bin/basalt.ts serves the generators AND hands the project
@@ -132,7 +159,11 @@ export function packageJson(options) {
132
159
  // is deliberately no `db:push` script.
133
160
  'db:migrate': 'prisma migrate dev',
134
161
  'db:deploy': 'prisma migrate deploy',
135
- ...(options.tenancy ? { 'db:seed': 'tsx prisma/seed.ts' } : {}),
162
+ // Through Prisma, not `tsx prisma/seed.ts`: prisma.config.ts loads
163
+ // .env (the seed imports src/env.ts, which needs the database URL),
164
+ // then runs the `migrations.seed` command it declares. Prisma 7's
165
+ // `migrate dev` no longer seeds on its own.
166
+ ...(options.tenancy ? { 'db:seed': DB_SEED_SCRIPT } : {}),
136
167
  }
137
168
  : {}),
138
169
  },
@@ -153,12 +184,34 @@ export function tsconfigJson(options) {
153
184
  noEmit: true,
154
185
  types: ['node'],
155
186
  },
156
- // The generated Prisma client lives under src/generated (git-ignored, and
157
- // re-created by `prisma generate`); the seed script under prisma/ is
158
- // type-checked too.
187
+ // The generated Prisma client lives in ./generated (git-ignored,
188
+ // re-created by `prisma generate`, reached through the `#db/*` import
189
+ // alias); the seed script under prisma/ is type-checked too.
159
190
  include: ['src', 'tests', ...(options.prisma && options.tenancy ? ['prisma/seed.ts'] : [])],
160
191
  }, null, 2)}\n`;
161
192
  }
193
+ /**
194
+ * `tsconfig.build.json` — what `pnpm build` compiles: src/ only (tests and
195
+ * bin/ stay dev-only), emitted to dist/ with rootDir `.`, so src/server.ts
196
+ * becomes dist/src/server.js. Extends tsconfig.json, so the compiler options
197
+ * (and `pnpm typecheck`) never diverge.
198
+ */
199
+ export function tsconfigBuildJson() {
200
+ return `${JSON.stringify({
201
+ extends: './tsconfig.json',
202
+ compilerOptions: {
203
+ noEmit: false,
204
+ rootDir: '.',
205
+ outDir: 'dist',
206
+ sourceMap: true,
207
+ },
208
+ include: ['src'],
209
+ }, null, 2)}\n`;
210
+ }
211
+ /** The production Dockerfile — the same file `basalt publish dockerfile` writes. */
212
+ export function dockerfile() {
213
+ return DOCKERFILE;
214
+ }
162
215
  export function envTs(options) {
163
216
  const prefix = envPrefix(options.name);
164
217
  return `import { defineEnv${options.auth ? ', secret' : ''} } from '@basaltkit/env'
@@ -222,10 +275,19 @@ export function envPrefix(name) {
222
275
  base = base.slice(start, end);
223
276
  return base === '' || /^\d/.test(base) ? `APP_${base}` : base;
224
277
  }
278
+ /**
279
+ * The opening lines of `.env.example`. Up to create-basalt 1.10 they read
280
+ * {@link LEGACY_ENV_EXAMPLE_LEAD} ("nothing loads this file"); `update`
281
+ * swaps that for this one in existing apps.
282
+ */
283
+ export const ENV_EXAMPLE_LEAD = `# Development: copy this file to .env — \`pnpm dev\` and \`pnpm basalt\` load it
284
+ # automatically (a new app already has one). \`pnpm start\` (production) does
285
+ # NOT: there the variables come from the environment (or \`node --env-file\`).`;
286
+ export const LEGACY_ENV_EXAMPLE_LEAD = `# Nothing loads this file for you: copy it to .env and start with
287
+ # \`node --env-file=.env\` / \`tsx --env-file=.env\`, or export the variables.`;
225
288
  export function envExample(options) {
226
289
  const prefix = envPrefix(options.name);
227
- return `# Nothing loads this file for you: copy it to .env and start with
228
- # \`node --env-file=.env\` / \`tsx --env-file=.env\`, or export the variables.
290
+ return `${ENV_EXAMPLE_LEAD}
229
291
  # --env-file NEVER overrides a variable already exported in your shell: if
230
292
  # another project exported PORT or DATABASE_URL, an app reading the generic
231
293
  # name would silently use THAT one. That is why src/env.ts reads app-prefixed
@@ -252,6 +314,20 @@ ${prefix}_DATABASE_URL=postgres://postgres:postgres@localhost:5432/${databaseNam
252
314
  # ${prefix}_DATABASE_URL=postgres://user:pass@localhost:5432/${options.name.replace(/^@[^/]+\//, '')}
253
315
  `}`;
254
316
  }
317
+ /**
318
+ * The `.env` a new app starts with (git-ignored, never in the manifest): the
319
+ * values of `.env.example` for local development plus — with auth — a freshly
320
+ * generated APP_SECRET, so `pnpm dev` works without a copy step.
321
+ */
322
+ export function devEnvFile(options, appSecret) {
323
+ const prefix = envPrefix(options.name);
324
+ const example = envExample(options);
325
+ const body = example.slice(example.indexOf(`${prefix}_PORT=`));
326
+ return `# Local development settings, generated by create-basalt from .env.example.
327
+ # Git-ignored: never commit it. \`pnpm dev\` and \`pnpm basalt\` load it (a
328
+ # variable exported in your shell wins); \`pnpm start\` (production) does not.
329
+ ${body.replace(`# ${prefix}_APP_SECRET=\n`, `${prefix}_APP_SECRET=${appSecret}\n`)}`;
330
+ }
255
331
  /** A PostgreSQL-safe database name derived from the project name. */
256
332
  export function databaseName(name) {
257
333
  return name.replace(/^@[^/]+\//, '').replace(/[^a-zA-Z0-9]+/g, '_').toLowerCase();
@@ -511,7 +587,15 @@ import { env } from './env.js'
511
587
  const app = await buildApp({
512
588
  logLevel: env.LOG_LEVEL,
513
589
  pretty: env.NODE_ENV === 'development',
514
- }).boot()
590
+ })
591
+ .boot()
592
+ .catch((error: unknown) => {
593
+ // A database failure diagnosed by @basaltkit/prisma (permission denied, not
594
+ // migrated, unreachable…) carries its one-line fix: print it above the stack.
595
+ const diagnosis = (error as { details?: { diagnosis?: { cause?: string; fix?: string } } } | null)?.details?.diagnosis
596
+ if (diagnosis) console.error('Cannot start: ' + diagnosis.cause + '\\nFix: ' + diagnosis.fix)
597
+ throw error
598
+ })
515
599
 
516
600
  const server = app.container.get(FASTIFY)
517
601
  await server.listen({ port: env.PORT, host: env.HOST })
@@ -526,14 +610,27 @@ for (const signal of ['SIGINT', 'SIGTERM'] as const) {
526
610
  `;
527
611
  }
528
612
  /**
529
- * `pnpm dev` entrypoint. Opts into NODE_ENV=development (dev secret defaults,
530
- * pretty logs) unless NODE_ENV is already set, then starts the server. Kept out
531
- * of server.ts on purpose: `pnpm start` runs server.ts directly, where an unset
532
- * NODE_ENV counts as production and secrets are required (fail-closed).
613
+ * `pnpm dev` entrypoint. Loads `.env` (when present) with `node --env-file`
614
+ * semantics — an exported variable wins, the file only fills in what is
615
+ * missing — and opts into NODE_ENV=development (dev secret defaults, pretty
616
+ * logs) unless NODE_ENV is already set, then starts the server. Kept out of
617
+ * server.ts on purpose: `pnpm start` runs server.ts directly, where nothing
618
+ * loads `.env` (production configuration comes from the real environment) and
619
+ * an unset NODE_ENV counts as production, so secrets are required (fail-closed).
533
620
  */
534
621
  export function devTs() {
535
622
  return `// Development entrypoint (\`pnpm dev\`). \`pnpm start\` runs server.ts directly,
536
623
  // where an unset NODE_ENV counts as production: secrets are then required.
624
+ import { existsSync } from 'node:fs'
625
+ import { fileURLToPath } from 'node:url'
626
+
627
+ // Development loads .env (when present) like \`node --env-file=.env\`: a variable
628
+ // already exported in your shell still wins, the file only fills in what is
629
+ // missing. \`pnpm start\` (production) never loads it — there the configuration
630
+ // comes from the real environment.
631
+ const envFile = fileURLToPath(new URL('../.env', import.meta.url))
632
+ if (existsSync(envFile)) process.loadEnvFile(envFile)
633
+
537
634
  process.env['NODE_ENV'] ??= 'development'
538
635
  await import('./server.js')
539
636
  `;
@@ -558,9 +655,17 @@ const PRISMA_MODELS = {
558
655
  }
559
656
 
560
657
  model TenantDomain {
561
- domain String @id
562
- tenantId String
563
- tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
658
+ domain String @id
659
+ tenantId String
660
+ // Custom-domain verification (PrismaDomainStore). A row with a NULL token
661
+ // mirrors \`tenant.domains\` and is written by PrismaTenantSource; a row with a
662
+ // token is a CustomDomains claim, which the source never deletes, and which
663
+ // only resolves a request once \`verified\` is true.
664
+ verificationToken String?
665
+ verified Boolean @default(true)
666
+ createdAt DateTime @default(now())
667
+ verifiedAt DateTime?
668
+ tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
564
669
 
565
670
  @@index([tenantId])
566
671
  @@map("tenant_domains")
@@ -575,9 +680,11 @@ model TenantDomain {
575
680
  }
576
681
 
577
682
  model AuthSession {
578
- id String @id
579
- userId String
580
- expiresAt DateTime
683
+ id String @id
684
+ userId String
685
+ expiresAt DateTime
686
+ /// Last use, for the idle timeout (trackSessionActivity: true).
687
+ lastSeenAt DateTime?
581
688
 
582
689
  @@index([userId])
583
690
  @@map("auth_sessions")
@@ -756,7 +863,9 @@ export function prismaSchema(options) {
756
863
 
757
864
  generator client {
758
865
  provider = "prisma-client-js"
759
- output = "../src/generated/prisma"
866
+ // Outside src/ on purpose: src/db.ts imports it as \`#db/client.js\` (the
867
+ // package.json "imports" alias), which resolves from src/ and dist/src/ alike.
868
+ output = "../generated/prisma"
760
869
  }
761
870
 
762
871
  datasource db {
@@ -792,9 +901,9 @@ export function prismaConfigTs(options) {
792
901
  return `import { loadEnvFile } from 'node:process'
793
902
  import { defineConfig } from 'prisma/config'
794
903
 
795
- // Prisma loads no .env for you (neither does the app — see src/env.ts). This
796
- // mirrors \`node --env-file=.env\`: a variable already exported in your shell
797
- // still wins, the file only fills in what is missing.
904
+ // Prisma loads no .env for you. Like the app's dev entrypoints (src/dev.ts,
905
+ // bin/basalt.ts) this mirrors \`node --env-file=.env\`: a variable already
906
+ // exported in your shell still wins, the file only fills in what is missing.
798
907
  try {
799
908
  loadEnvFile('.env')
800
909
  } catch {
@@ -804,7 +913,7 @@ try {
804
913
  export default defineConfig({
805
914
  schema: 'prisma/schema.prisma',${options.tenancy
806
915
  ? `
807
- // Runs after \`prisma migrate dev\` / \`prisma migrate reset\`.
916
+ // Run by \`prisma db seed\` (\`pnpm db:seed\`), with the .env loaded above.
808
917
  migrations: { seed: 'tsx prisma/seed.ts' },`
809
918
  : ''}
810
919
  datasource: {
@@ -824,7 +933,9 @@ export default defineConfig({
824
933
  */
825
934
  export function dbTs(options) {
826
935
  return `import { PrismaPg } from '@prisma/adapter-pg'${options.tenancy ? `\nimport { tenancyExtension } from '@basaltkit/prisma'` : ''}
827
- import { PrismaClient } from './generated/prisma/client.js'
936
+ // The generated client (./generated/prisma), through the package.json
937
+ // "imports" alias — the same specifier works under tsx and from dist/.
938
+ import { PrismaClient } from '#db/client.js'
828
939
  import { env } from './env.js'
829
940
 
830
941
  /**
@@ -860,8 +971,9 @@ export const db = prisma
860
971
  }
861
972
  /**
862
973
  * `prisma/seed.ts` — creates the `demo` tenant the scaffolded resolvers expect
863
- * (`x-tenant-id: demo`, `demo.localhost`). Runs from `pnpm db:seed` and after
864
- * `prisma migrate dev` (prisma.config.ts declares it).
974
+ * (`x-tenant-id: demo`, `demo.localhost`). Runs from `pnpm db:seed`
975
+ * (`prisma db seed`, the command prisma.config.ts declares) — Prisma 7's
976
+ * `migrate dev` does not run it.
865
977
  */
866
978
  export function prismaSeedTs(options) {
867
979
  return `import { prismaTenantSource } from '@basaltkit/tenancy-prisma'
@@ -947,8 +1059,9 @@ Included: ${features.join(' · ')}.
947
1059
  \`\`\`bash
948
1060
  pnpm install${options.prisma
949
1061
  ? `
950
- # point ${envPrefix(options.name)}_DATABASE_URL at a PostgreSQL database (.env.example), then
951
- pnpm db:migrate # create the tables${options.tenancy ? ` (runs the seed too)` : ''}`
1062
+ # start PostgreSQL — .env points ${envPrefix(options.name)}_DATABASE_URL at localhost:5432 — then
1063
+ pnpm db:migrate # create the tables${options.tenancy ? `
1064
+ pnpm db:seed # the 'demo' tenant the resolvers expect` : ''}`
952
1065
  : ''}
953
1066
  pnpm dev # API on http://localhost:3000
954
1067
  pnpm test
@@ -962,11 +1075,11 @@ the models this app needs — the reference models of the \`@basaltkit/*-prisma\
962
1075
  packages it uses, plus your own \`Project\` at the end.
963
1076
 
964
1077
  \`\`\`bash
965
- pnpm db:migrate # prisma migrate dev — change the schema, write a migration${options.tenancy
1078
+ pnpm db:migrate --name <change> # prisma migrate dev — writes AND applies a migration (asks for a name without --name)${options.tenancy
966
1079
  ? `
967
- pnpm db:seed # the 'demo' tenant the resolvers expect (also run by db:migrate)`
1080
+ pnpm db:seed # prisma db seed — the 'demo' tenant the resolvers expect`
968
1081
  : ''}
969
- pnpm db:generate # prisma generate — refresh src/generated/prisma (also runs on install)
1082
+ pnpm db:generate # prisma generate — refresh generated/prisma after a schema change (also runs on install; migrate dev does not)
970
1083
  pnpm db:deploy # prisma migrate deploy — apply pending migrations in production
971
1084
  \`\`\`
972
1085
 
@@ -1005,16 +1118,18 @@ scaffold with \`--cli\` — \`pnpm basalt prisma:sync\` does the merge for you.)
1005
1118
  : ''}
1006
1119
  ## Environment
1007
1120
 
1008
- \`src/env.ts\` validates \`process.env\` — nothing loads \`.env\` for you. Copy
1009
- \`.env.example\` to \`.env\` and launch with \`--env-file=.env\` (Node/tsx), or
1010
- export the variables.
1121
+ \`src/env.ts\` validates \`process.env\`. For development, ${options.cli ? '`pnpm dev` and `pnpm basalt` load' : '`pnpm dev` loads'} \`.env\`
1122
+ automatically — create-basalt wrote one for you: git-ignored, with local
1123
+ values${options.auth ? ' and a generated secret' : ''}. \`.env.example\` is its committed template.
1124
+ \`pnpm start\` (production, \`${PRODUCTION_ENTRY}\`) loads **nothing**: there the
1125
+ variables come from the real environment (or \`node --env-file=.env\`).
1011
1126
 
1012
1127
  The variables are **app-prefixed**: \`${envPrefix(options.name)}_PORT\`,
1013
1128
  \`${envPrefix(options.name)}_HOST\`, \`${envPrefix(options.name)}_LOG_LEVEL\`${options.auth ? `, \`${envPrefix(options.name)}_APP_SECRET\`` : ''}${options.prisma ? `, \`${envPrefix(options.name)}_DATABASE_URL\`` : ''}. \`NODE_ENV\` is never prefixed.
1014
1129
 
1015
- Why: **\`--env-file\` never overrides a variable that is already exported.** In a
1016
- shell where another project exported \`DATABASE_URL\` (or \`PORT\`), an app reading
1017
- the generic name boots against THAT value and only fails on the first request
1130
+ Why: **loading \`.env\` (like \`--env-file\`) never overrides a variable that is
1131
+ already exported.** In a shell where another project exported \`DATABASE_URL\`
1132
+ (or \`PORT\`), an app reading the generic name boots against THAT value and only fails on the first request
1018
1133
  that touches it. \`src/env.ts\` therefore passes \`prefix: '${envPrefix(options.name)}'\` to
1019
1134
  \`defineEnv\`: each variable is read as \`${envPrefix(options.name)}_<NAME>\` first and
1020
1135
  **falls back** to the bare \`<NAME>\`, so a deployment that already exports the
@@ -1030,6 +1145,24 @@ To drop the fallback entirely (prefixed names only), write
1030
1145
  \`prefix: { value: '${envPrefix(options.name)}', fallback: false }\`. When in doubt about what
1031
1146
  your shell exports: \`env | grep DATABASE_URL\`.
1032
1147
 
1148
+ ## Build & ship
1149
+
1150
+ \`\`\`bash
1151
+ pnpm build # tsc -p tsconfig.build.json → dist/ (src only)
1152
+ pnpm start # node --enable-source-maps ${PRODUCTION_ENTRY} — no tsx at runtime
1153
+ pnpm start:dev # the server from source with tsx, without building
1154
+ docker build -t ${options.name} . # the Dockerfile: build stage + plain-node runtime
1155
+ \`\`\`
1156
+
1157
+ \`pnpm start\` runs the compiled server, so build first. The \`Dockerfile\` (also
1158
+ what \`basalt publish dockerfile\` writes) installs, ${options.prisma ? 'generates the Prisma client, ' : ''}builds, drops the
1159
+ devDependencies and runs ${PRODUCTION_ENTRY} as the \`node\` user, with a
1160
+ \`HEALTHCHECK\` on \`/health\`. Pass the configuration as environment variables${options.prisma ? ', and apply migrations (\`pnpm db:deploy\`) before rolling out — the image does not run them' : ''}.${options.prisma
1161
+ ? ` The Prisma client is generated into \`generated/prisma\` (outside \`src/\`) and
1162
+ imported as \`#db/client.js\` through the package.json \`imports\` alias, so the same
1163
+ import works under tsx and from \`dist/\`.`
1164
+ : ''}
1165
+
1033
1166
  ## Keeping it up to date
1034
1167
 
1035
1168
  \`\`\`bash
@@ -1139,20 +1272,7 @@ export function mcpJson(_options) {
1139
1272
  * `certs/server.key` through.
1140
1273
  */
1141
1274
  export function dockerignore() {
1142
- return `**/.env
1143
- **/.env.*
1144
- !**/.env.example
1145
- **/.npmrc
1146
- **/.git
1147
- **/node_modules
1148
- **/coverage
1149
- **/*.log
1150
- **/*.pem
1151
- **/*.key
1152
- **/*.p12
1153
- **/*.pfx
1154
- **/.DS_Store
1155
- `;
1275
+ return DOCKERIGNORE;
1156
1276
  }
1157
1277
  export function gitignore(options) {
1158
1278
  return `node_modules/
@@ -1163,12 +1283,14 @@ dist/
1163
1283
  *.log
1164
1284
  ${options.prisma
1165
1285
  ? `# Generated by \`prisma generate\` (runs on install) — never edit or commit it.
1166
- src/generated/
1286
+ generated/
1167
1287
  `
1168
1288
  : ''}`;
1169
1289
  }
1170
1290
  /**
1171
- * pnpm settings: esbuild's build script is required by tsx;
1291
+ * pnpm settings: esbuild's build script is required by tsx; with --prisma,
1292
+ * the Prisma CLI's build scripts (prisma, @prisma/engines) are approved too — pnpm 11 fails
1293
+ * the install on an unapproved build;
1172
1294
  * msgpackr-extract (optional native accelerator via BullMQ) is declined —
1173
1295
  * msgpackr falls back to pure JS. With --ui, `web` is a workspace member so
1174
1296
  * its own dependencies resolve.
@@ -1186,7 +1308,13 @@ export function pnpmWorkspaceYaml(options) {
1186
1308
  `
1187
1309
  : ''}allowBuilds:
1188
1310
  esbuild: true
1189
- msgpackr-extract: false
1311
+ msgpackr-extract: false${options.prisma
1312
+ ? `
1313
+ # The Prisma CLI's install scripts fetch its engines; pnpm 11 refuses to finish
1314
+ # an install (ERR_PNPM_IGNORED_BUILDS) while a dependency's build is unapproved.
1315
+ '@prisma/engines': true
1316
+ prisma: true`
1317
+ : ''}
1190
1318
  # @basaltkit/* releases in lockstep, often within hours — exclude the scope (and
1191
1319
  # create-basalt, which serves \`pnpm basalt update\`) from pnpm's minimumReleaseAge
1192
1320
  # policy so an update is never blocked on a fresh release.
package/dist/versions.js CHANGED
@@ -4,26 +4,26 @@
4
4
  export const SCAFFOLD_VERSIONS = {
5
5
  '@basaltkit/admin': '^2.0.0',
6
6
  '@basaltkit/admin-shadcn': '^1.1.0',
7
- '@basaltkit/ai-mcp': '^0.3.0',
8
- '@basaltkit/auth': '^4.1.0',
9
- '@basaltkit/auth-prisma': '^2.0.0',
10
- '@basaltkit/cli': '^1.3.0',
7
+ '@basaltkit/ai-mcp': '^0.4.0',
8
+ '@basaltkit/auth': '^4.2.0',
9
+ '@basaltkit/auth-prisma': '^2.1.0',
10
+ '@basaltkit/cli': '^1.4.0',
11
11
  '@basaltkit/config': '^1.1.0',
12
- '@basaltkit/core': '^1.5.0',
12
+ '@basaltkit/core': '^1.6.0',
13
13
  '@basaltkit/env': '^3.1.0',
14
14
  '@basaltkit/events': '^1.3.0',
15
- '@basaltkit/fastify': '^2.5.0',
16
- '@basaltkit/generator': '^1.5.0',
15
+ '@basaltkit/fastify': '^2.6.0',
16
+ '@basaltkit/generator': '^1.6.0',
17
17
  '@basaltkit/logger': '^1.3.0',
18
- '@basaltkit/mcp': '^5.0.0',
19
- '@basaltkit/prisma': '^3.0.0',
18
+ '@basaltkit/mcp': '^5.1.0',
19
+ '@basaltkit/prisma': '^3.1.0',
20
20
  '@basaltkit/sdk': '^2.1.0',
21
21
  '@basaltkit/subscriptions': '^5.0.0',
22
22
  '@basaltkit/subscriptions-prisma': '^3.0.0',
23
- '@basaltkit/teams': '^4.0.0',
23
+ '@basaltkit/teams': '^4.1.0',
24
24
  '@basaltkit/teams-prisma': '^1.1.0',
25
- '@basaltkit/tenancy': '^3.1.0',
26
- '@basaltkit/tenancy-prisma': '^2.0.0',
25
+ '@basaltkit/tenancy': '^3.2.0',
26
+ '@basaltkit/tenancy-prisma': '^2.1.0',
27
27
  '@basaltkit/testing': '^2.0.0',
28
- 'create-basalt': '^1.10.0',
28
+ 'create-basalt': '^1.12.0',
29
29
  };
package/dist/wizard.js CHANGED
@@ -11,9 +11,9 @@ export const FEATURES = [
11
11
  ];
12
12
  export const PRESETS = [
13
13
  { value: 'saas', label: 'SaaS starter', hint: 'tenancy + auth + billing + database + CLI', features: ['tenancy', 'auth', 'billing', 'prisma', 'cli'] },
14
- { value: 'api', label: 'API only', hint: 'auth + MCP, no tenancy or UI', features: ['auth', 'mcp'] },
14
+ { value: 'api', label: 'API only', hint: 'auth + MCP + CLI, no tenancy or UI', features: ['auth', 'mcp', 'cli'] },
15
15
  { value: 'full', label: 'Full stack', hint: 'everything + web UI', features: ['tenancy', 'auth', 'billing', 'prisma', 'ui', 'cli', 'mcp'] },
16
- { value: 'minimal', label: 'Minimal', hint: 'no batteries, add them later', features: [] },
16
+ { value: 'minimal', label: 'Minimal', hint: 'just the CLI, add the rest later', features: ['cli'] },
17
17
  { value: 'custom', label: 'Custom', hint: 'pick features yourself', features: null },
18
18
  ];
19
19
  const PACKAGE_MANAGERS = [
@@ -59,7 +59,7 @@ export async function runWizard(prompter, options = {}) {
59
59
  (await prompter.multiselect({
60
60
  message: 'Select features',
61
61
  choices: FEATURES,
62
- initial: ['tenancy', 'auth'],
62
+ initial: ['tenancy', 'auth', 'cli'],
63
63
  })));
64
64
  let pm = await prompter.select({
65
65
  message: 'Package manager',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-basalt",
3
- "version": "1.10.0",
3
+ "version": "1.12.0",
4
4
  "engines": {
5
5
  "node": ">=22.5.0"
6
6
  },
@@ -23,33 +23,35 @@
23
23
  "devDependencies": {
24
24
  "@prisma/adapter-pg": "^7.10.0",
25
25
  "@prisma/client": "^7.10.0",
26
+ "@prisma/client-runtime-utils": "^7.10.0",
26
27
  "@types/node": "^26.6.1",
27
28
  "@types/pg": "^8.23.1",
28
29
  "pg": "^8.23.0",
29
30
  "prisma": "^7.10.0",
31
+ "tsx": "^4.23.13",
30
32
  "typescript": "^7.0.2",
31
33
  "vitest": "^5.0.1",
32
34
  "zod": "^4.6.5",
33
- "@basaltkit/core": "^1.5.0",
35
+ "@basaltkit/auth": "^4.2.0",
36
+ "@basaltkit/auth-prisma": "^2.1.0",
37
+ "@basaltkit/cli": "^1.4.0",
38
+ "@basaltkit/core": "^1.6.0",
34
39
  "@basaltkit/events": "^1.3.0",
35
- "@basaltkit/env": "^3.1.1",
36
- "@basaltkit/fastify": "^2.5.0",
37
- "@basaltkit/auth": "^4.1.0",
38
- "@basaltkit/auth-prisma": "^2.0.0",
40
+ "@basaltkit/fastify": "^2.6.0",
41
+ "@basaltkit/config": "^1.1.2",
42
+ "@basaltkit/generator": "^1.6.0",
39
43
  "@basaltkit/logger": "^1.3.0",
40
- "@basaltkit/mcp": "^5.0.0",
41
- "@basaltkit/prisma": "^3.0.0",
44
+ "@basaltkit/env": "^3.1.1",
45
+ "@basaltkit/mcp": "^5.1.0",
46
+ "@basaltkit/prisma": "^3.1.0",
47
+ "@basaltkit/subscriptions": "^5.0.0",
42
48
  "@basaltkit/subscriptions-prisma": "^3.0.0",
43
- "@basaltkit/teams": "^4.0.0",
44
- "@basaltkit/generator": "^1.5.0",
45
- "@basaltkit/cli": "^1.3.0",
46
- "@basaltkit/tenancy": "^3.1.0",
49
+ "@basaltkit/teams": "^4.1.0",
50
+ "@basaltkit/tenancy": "^3.2.0",
47
51
  "@basaltkit/teams-prisma": "^1.1.0",
48
- "@basaltkit/tenancy-prisma": "^2.0.0",
49
- "@basaltkit/testing": "^2.0.3",
50
- "@basaltkit/subscriptions": "^5.0.0",
52
+ "@basaltkit/tenancy-prisma": "^2.1.0",
51
53
  "@basaltkit/tsconfig": "^0.24.0",
52
- "@basaltkit/config": "^1.1.2"
54
+ "@basaltkit/testing": "^2.0.3"
53
55
  },
54
56
  "publishConfig": {
55
57
  "access": "public"
@@ -71,8 +73,9 @@
71
73
  "boilerplate"
72
74
  ],
73
75
  "scripts": {
74
- "build": "node scripts/sync-versions.mjs && tsc -p tsconfig.build.json",
76
+ "build": "node scripts/sync-versions.mjs && node scripts/sync-stubs.mjs && tsc -p tsconfig.build.json",
75
77
  "sync-versions": "node scripts/sync-versions.mjs",
78
+ "sync-stubs": "node scripts/sync-stubs.mjs",
76
79
  "test": "vitest run",
77
80
  "typecheck": "tsc --noEmit"
78
81
  }