create-basalt 1.6.0 → 1.7.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 +26 -5
- package/dist/templates.js +59 -24
- package/dist/versions.js +4 -4
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -135,12 +135,12 @@ Always:
|
|
|
135
135
|
my-app/
|
|
136
136
|
├── package.json # scripts: dev, start, test, typecheck (+ basalt with --cli)
|
|
137
137
|
├── tsconfig.json # strict TypeScript, ESM
|
|
138
|
-
├── .env.example #
|
|
138
|
+
├── .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
139
|
├── .gitignore
|
|
140
140
|
├── README.md # instructions adapted to the chosen options
|
|
141
141
|
├── pnpm-workspace.yaml # esbuild allowBuilds, @basaltkit/* release-age exclusion (+ "web" member with --ui) + pnpm 11 notes
|
|
142
142
|
├── src/
|
|
143
|
-
│ ├── env.ts # environment variables validated with Zod (@basaltkit/env)
|
|
143
|
+
│ ├── env.ts # environment variables validated with Zod (@basaltkit/env), app-prefixed
|
|
144
144
|
│ ├── app.ts # buildApp() with the chosen plugins
|
|
145
145
|
│ ├── routes.ts # GET / (friendly index) and GET /health
|
|
146
146
|
│ └── server.ts # startup + clean shutdown on SIGINT/SIGTERM
|
|
@@ -176,7 +176,28 @@ With pnpm 11, every `pnpm <script>` and `pnpm exec` first verifies dependencies
|
|
|
176
176
|
|
|
177
177
|
### Environment variables and `--env-file`
|
|
178
178
|
|
|
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`,
|
|
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`, an app reading the generic name boots against that value and only fails on the first request that touches it.
|
|
180
|
+
|
|
181
|
+
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.
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
// src/env.ts (generated)
|
|
185
|
+
export const env = defineEnv(
|
|
186
|
+
{ PORT: z.coerce.number().default(3000), /* … */ },
|
|
187
|
+
{ prefix: 'MY_SAAS' },
|
|
188
|
+
)
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
# .env.example (generated)
|
|
193
|
+
MY_SAAS_PORT=3000
|
|
194
|
+
MY_SAAS_HOST=0.0.0.0
|
|
195
|
+
MY_SAAS_LOG_LEVEL=info
|
|
196
|
+
NODE_ENV=development # never prefixed — a Node-wide convention
|
|
197
|
+
# MY_SAAS_APP_SECRET= # with auth
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
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
201
|
|
|
181
202
|
### Programmatic usage (Advanced)
|
|
182
203
|
|
|
@@ -267,10 +288,10 @@ Outside an interactive terminal (no TTY) the questions are skipped. Always pass
|
|
|
267
288
|
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
289
|
|
|
269
290
|
**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
|
|
291
|
+
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.
|
|
271
292
|
|
|
272
293
|
**My app connects to the wrong database / port.**
|
|
273
|
-
A variable exported in your shell beats `--env-file`.
|
|
294
|
+
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
295
|
|
|
275
296
|
**`pnpm basalt …` starts by running `pnpm install` (or fails offline).**
|
|
276
297
|
pnpm 11's `verifyDepsBeforeRun` (default `install`). Run `node_modules/.bin/tsx bin/basalt.ts …` instead, or set `verifyDepsBeforeRun: warn` in `pnpm-workspace.yaml`.
|
package/dist/templates.js
CHANGED
|
@@ -105,25 +105,39 @@ export function tsconfigJson() {
|
|
|
105
105
|
}, null, 2)}\n`;
|
|
106
106
|
}
|
|
107
107
|
export function envTs(options) {
|
|
108
|
+
const prefix = envPrefix(options.name);
|
|
108
109
|
return `import { defineEnv${options.auth ? ', secret' : ''} } from '@basaltkit/env'
|
|
109
110
|
import { LOG_LEVELS } from '@basaltkit/logger'
|
|
110
111
|
import { z } from 'zod'
|
|
111
112
|
|
|
112
|
-
export const env = defineEnv(
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
113
|
+
export const env = defineEnv(
|
|
114
|
+
{
|
|
115
|
+
PORT: z.coerce.number().default(3000),
|
|
116
|
+
HOST: z.string().default('0.0.0.0'),
|
|
117
|
+
// Typed against the logger's LogLevel union — a free-form string here fails
|
|
118
|
+
// \`pnpm typecheck\` where loggerPlugin({ level }) consumes it.
|
|
119
|
+
LOG_LEVEL: z.enum(LOG_LEVELS).default('info'),
|
|
120
|
+
// Unset counts as production (fail-closed); \`pnpm dev\` sets development.
|
|
121
|
+
// NODE_ENV is a Node-wide convention and is never prefixed.
|
|
122
|
+
NODE_ENV: z.enum(['development', 'production', 'test']).default('production'),${options.auth
|
|
120
123
|
? `
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
124
|
+
// Signs JWTs and sessions. secret() is fail-closed: required unless NODE_ENV
|
|
125
|
+
// is explicitly development/test (no fallback when NODE_ENV is unset),
|
|
126
|
+
// rejected if it looks like a placeholder. \`pnpm dev\` uses devDefault.
|
|
127
|
+
APP_SECRET: secret({ minLength: 32, devDefault: 'dev-only-insecure-secret-please-change-me' }),`
|
|
125
128
|
: ''}
|
|
126
|
-
}
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
// Every variable is read as ${prefix}_<NAME> first (${prefix}_PORT before PORT),
|
|
132
|
+
// falling back to the bare name. \`--env-file\` never overrides a variable
|
|
133
|
+
// already exported in your shell, so without the prefix another project's
|
|
134
|
+
// exported PORT / DATABASE_URL would silently win. The keys above stay bare:
|
|
135
|
+
// the rest of the app still reads env.PORT.
|
|
136
|
+
// To require the prefixed names (no bare fallback at all), write:
|
|
137
|
+
// prefix: { value: '${prefix}', fallback: false }
|
|
138
|
+
prefix: '${prefix}',
|
|
139
|
+
},
|
|
140
|
+
)
|
|
127
141
|
`;
|
|
128
142
|
}
|
|
129
143
|
/**
|
|
@@ -151,13 +165,24 @@ export function envExample(options) {
|
|
|
151
165
|
return `# Nothing loads this file for you: copy it to .env and start with
|
|
152
166
|
# \`node --env-file=.env\` / \`tsx --env-file=.env\`, or export the variables.
|
|
153
167
|
# --env-file NEVER overrides a variable already exported in your shell: if
|
|
154
|
-
# another project exported DATABASE_URL,
|
|
155
|
-
#
|
|
156
|
-
PORT
|
|
157
|
-
|
|
158
|
-
|
|
168
|
+
# another project exported PORT or DATABASE_URL, an app reading the generic
|
|
169
|
+
# name would silently use THAT one. That is why src/env.ts reads app-prefixed
|
|
170
|
+
# names — ${prefix}_PORT wins over a stray PORT. The bare names still work as a
|
|
171
|
+
# fallback, so an existing deployment exporting them keeps booting.
|
|
172
|
+
${prefix}_PORT=3000
|
|
173
|
+
${prefix}_HOST=0.0.0.0
|
|
174
|
+
${prefix}_LOG_LEVEL=info
|
|
175
|
+
# NODE_ENV is a Node-wide convention and is never prefixed.
|
|
159
176
|
NODE_ENV=development
|
|
160
|
-
${options.auth
|
|
177
|
+
${options.auth
|
|
178
|
+
? `# Required unless NODE_ENV is development/test — \`pnpm start\` refuses to boot
|
|
179
|
+
# without it. Generate a strong one: openssl rand -base64 48
|
|
180
|
+
# ${prefix}_APP_SECRET=
|
|
181
|
+
`
|
|
182
|
+
: ''}# When you add a database, declare DATABASE_URL in src/env.ts and set it here
|
|
183
|
+
# under the prefixed name:
|
|
184
|
+
# ${prefix}_DATABASE_URL=postgres://user:pass@localhost:5432/${options.name.replace(/^@[^/]+\//, '')}
|
|
185
|
+
`;
|
|
161
186
|
}
|
|
162
187
|
export function appTs(options) {
|
|
163
188
|
const imports = [
|
|
@@ -461,11 +486,21 @@ pnpm test
|
|
|
461
486
|
\`.env.example\` to \`.env\` and launch with \`--env-file=.env\` (Node/tsx), or
|
|
462
487
|
export the variables.
|
|
463
488
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
489
|
+
The variables are **app-prefixed**: \`${envPrefix(options.name)}_PORT\`,
|
|
490
|
+
\`${envPrefix(options.name)}_HOST\`, \`${envPrefix(options.name)}_LOG_LEVEL\`${options.auth ? `, \`${envPrefix(options.name)}_APP_SECRET\`` : ''}. \`NODE_ENV\` is never prefixed.
|
|
491
|
+
|
|
492
|
+
Why: **\`--env-file\` never overrides a variable that is already exported.** In a
|
|
493
|
+
shell where another project exported \`DATABASE_URL\` (or \`PORT\`), an app reading
|
|
494
|
+
the generic name boots against THAT value and only fails on the first request
|
|
495
|
+
that touches it. \`src/env.ts\` therefore passes \`prefix: '${envPrefix(options.name)}'\` to
|
|
496
|
+
\`defineEnv\`: each variable is read as \`${envPrefix(options.name)}_<NAME>\` first and
|
|
497
|
+
**falls back** to the bare \`<NAME>\`, so a deployment that already exports the
|
|
498
|
+
generic names keeps booting while a stray one in your shell loses. Add a
|
|
499
|
+
database and \`DATABASE_URL\` is read as \`${envPrefix(options.name)}_DATABASE_URL\`.
|
|
500
|
+
|
|
501
|
+
To drop the fallback entirely (prefixed names only), write
|
|
502
|
+
\`prefix: { value: '${envPrefix(options.name)}', fallback: false }\`. When in doubt about what
|
|
503
|
+
your shell exports: \`env | grep DATABASE_URL\`.
|
|
469
504
|
${options.cli
|
|
470
505
|
? `
|
|
471
506
|
## The \`basalt\` CLI
|
package/dist/versions.js
CHANGED
|
@@ -8,15 +8,15 @@ export const SCAFFOLD_VERSIONS = {
|
|
|
8
8
|
'@basaltkit/auth': '^3.1.0',
|
|
9
9
|
'@basaltkit/cli': '^1.2.0',
|
|
10
10
|
'@basaltkit/config': '^1.1.0',
|
|
11
|
-
'@basaltkit/core': '^1.
|
|
12
|
-
'@basaltkit/env': '^3.
|
|
11
|
+
'@basaltkit/core': '^1.4.0',
|
|
12
|
+
'@basaltkit/env': '^3.1.0',
|
|
13
13
|
'@basaltkit/events': '^1.3.0',
|
|
14
14
|
'@basaltkit/fastify': '^2.2.0',
|
|
15
|
-
'@basaltkit/generator': '^1.
|
|
15
|
+
'@basaltkit/generator': '^1.5.0',
|
|
16
16
|
'@basaltkit/logger': '^1.3.0',
|
|
17
17
|
'@basaltkit/mcp': '^3.0.0',
|
|
18
18
|
'@basaltkit/prisma': '^2.1.0',
|
|
19
|
-
'@basaltkit/sdk': '^2.
|
|
19
|
+
'@basaltkit/sdk': '^2.1.0',
|
|
20
20
|
'@basaltkit/subscriptions': '^4.0.0',
|
|
21
21
|
'@basaltkit/teams': '^3.0.0',
|
|
22
22
|
'@basaltkit/tenancy': '^2.0.0',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-basalt",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"engines": {
|
|
5
5
|
"node": ">=22.5.0"
|
|
6
6
|
},
|
|
@@ -25,22 +25,22 @@
|
|
|
25
25
|
"typescript": "^7.0.2",
|
|
26
26
|
"vitest": "^5.0.1",
|
|
27
27
|
"zod": "^4.6.5",
|
|
28
|
-
"@basaltkit/auth": "^3.1.0",
|
|
29
28
|
"@basaltkit/config": "^1.1.2",
|
|
30
|
-
"@basaltkit/
|
|
31
|
-
"@basaltkit/env": "^3.0.0",
|
|
29
|
+
"@basaltkit/auth": "^3.1.0",
|
|
32
30
|
"@basaltkit/cli": "^1.2.5",
|
|
31
|
+
"@basaltkit/env": "^3.1.0",
|
|
32
|
+
"@basaltkit/core": "^1.4.0",
|
|
33
33
|
"@basaltkit/events": "^1.3.0",
|
|
34
|
-
"@basaltkit/
|
|
34
|
+
"@basaltkit/fastify": "^2.2.0",
|
|
35
|
+
"@basaltkit/generator": "^1.5.0",
|
|
35
36
|
"@basaltkit/logger": "^1.3.0",
|
|
36
37
|
"@basaltkit/mcp": "^3.0.0",
|
|
37
|
-
"@basaltkit/
|
|
38
|
+
"@basaltkit/subscriptions": "^4.0.0",
|
|
38
39
|
"@basaltkit/teams": "^3.0.1",
|
|
39
|
-
"@basaltkit/fastify": "^2.2.0",
|
|
40
40
|
"@basaltkit/tenancy": "^2.0.0",
|
|
41
|
+
"@basaltkit/tsconfig": "^0.24.0",
|
|
41
42
|
"@basaltkit/testing": "^2.0.2",
|
|
42
|
-
"@basaltkit/
|
|
43
|
-
"@basaltkit/tsconfig": "^0.24.0"
|
|
43
|
+
"@basaltkit/prisma": "^2.1.0"
|
|
44
44
|
},
|
|
45
45
|
"publishConfig": {
|
|
46
46
|
"access": "public"
|