create-basalt 1.11.0 → 1.13.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 +32 -9
- package/dist/bin-template.js +15 -1
- package/dist/cli.js +4 -1
- package/dist/index.js +3 -1
- package/dist/latest-versions.d.ts +2 -0
- package/dist/latest-versions.js +14 -2
- package/dist/project/doctor.d.ts +1 -1
- package/dist/project/doctor.js +53 -1
- package/dist/project/production.d.ts +32 -0
- package/dist/project/production.js +74 -0
- package/dist/project/seed-script.d.ts +18 -0
- package/dist/project/seed-script.js +32 -0
- package/dist/project/update.d.ts +11 -0
- package/dist/project/update.js +70 -1
- package/dist/stubs.d.ts +30 -0
- package/dist/stubs.js +123 -0
- package/dist/templates.d.ts +35 -5
- package/dist/templates.js +144 -42
- package/dist/versions.js +22 -20
- package/package.json +21 -18
package/dist/stubs.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// GENERATED by scripts/sync-stubs.mjs from packages/cli/src/stubs.ts (runs before every build) — do not edit.
|
|
2
|
+
/**
|
|
3
|
+
* The production stubs `basalt publish` copies into an app — the SINGLE source
|
|
4
|
+
* of truth. create-basalt ships the same files in every new scaffold and offers
|
|
5
|
+
* them from `create-basalt update`: its build copies this file verbatim to
|
|
6
|
+
* `packages/create-app/src/stubs.ts` (scripts/sync-stubs.mjs), and a test there
|
|
7
|
+
* fails if the copy drifts. Keep this module free of imports for that reason.
|
|
8
|
+
*
|
|
9
|
+
* The layout these stubs assume is the create-basalt scaffold's (BK-026):
|
|
10
|
+
* `pnpm build` runs `tsc -p tsconfig.build.json` (rootDir `.`, outDir `dist`),
|
|
11
|
+
* so `src/server.ts` compiles to {@link PRODUCTION_ENTRY}; `pnpm start` runs it
|
|
12
|
+
* with plain `node` — no tsx, no TypeScript at runtime. A Prisma client, when
|
|
13
|
+
* there is one, is generated OUTSIDE `src/` (`generated/prisma`) and imported
|
|
14
|
+
* through the package.json `imports` alias `#db/*`, so it resolves the same
|
|
15
|
+
* from `src/` (tsx) and from `dist/src/` (node).
|
|
16
|
+
*/
|
|
17
|
+
/** The compiled server entry the Dockerfile runs and the scaffold's `start` script points at. */
|
|
18
|
+
export const PRODUCTION_ENTRY = 'dist/src/server.js';
|
|
19
|
+
/** Multi-stage production image: build with the dev toolchain, run on plain node as a non-root user. */
|
|
20
|
+
export const DOCKERFILE = `# syntax=docker/dockerfile:1
|
|
21
|
+
# Production image for a Basalt app (the create-basalt layout): \`pnpm build\`
|
|
22
|
+
# compiles src/ to dist/ and the image runs ${PRODUCTION_ENTRY} on plain
|
|
23
|
+
# node — no tsx, no TypeScript at runtime.
|
|
24
|
+
#
|
|
25
|
+
# docker build -t my-app .
|
|
26
|
+
# docker run -p 3000:3000 -e APP_SECRET=… -e DATABASE_URL=… my-app
|
|
27
|
+
#
|
|
28
|
+
# Configuration comes from the environment (nothing loads .env here, and
|
|
29
|
+
# .dockerignore keeps it out of the image). Database migrations are NOT run
|
|
30
|
+
# by the image: apply them before rolling out (\`pnpm db:deploy\` in CI).
|
|
31
|
+
#
|
|
32
|
+
# Installs with pnpm from pnpm-lock.yaml. An app on npm/yarn/bun replaces the
|
|
33
|
+
# pnpm install / exec / run / prune lines with its own package manager's.
|
|
34
|
+
ARG NODE_VERSION=22
|
|
35
|
+
ARG PNPM_VERSION=11
|
|
36
|
+
|
|
37
|
+
FROM node:\${NODE_VERSION}-slim AS build
|
|
38
|
+
ARG PNPM_VERSION
|
|
39
|
+
# Non-interactive pnpm: without it \`pnpm prune\` in a workspace (a --ui app's
|
|
40
|
+
# web/) aborts with ERR_PNPM_ABORTED_REMOVE_MODULES_DIR_NO_TTY.
|
|
41
|
+
ENV CI=true
|
|
42
|
+
WORKDIR /app
|
|
43
|
+
RUN npm install -g pnpm@\${PNPM_VERSION}
|
|
44
|
+
COPY . .
|
|
45
|
+
# Full install: the build needs the devDependencies (typescript, prisma).
|
|
46
|
+
RUN pnpm install --frozen-lockfile
|
|
47
|
+
# The Prisma client (when the app has a schema) is generated into ./generated.
|
|
48
|
+
RUN if [ -f prisma/schema.prisma ]; then pnpm exec prisma generate; fi && mkdir -p generated
|
|
49
|
+
RUN pnpm run build
|
|
50
|
+
# Drop the devDependencies (tsx, typescript, prisma CLI, generators, the AI
|
|
51
|
+
# bridge): none of them may ship. --ignore-scripts keeps the app's postinstall
|
|
52
|
+
# (\`prisma generate\`, a dev tool) from running again.
|
|
53
|
+
RUN pnpm prune --prod --ignore-scripts
|
|
54
|
+
|
|
55
|
+
FROM node:\${NODE_VERSION}-slim AS run
|
|
56
|
+
ENV NODE_ENV=production
|
|
57
|
+
ENV PORT=3000
|
|
58
|
+
# Path the HEALTHCHECK probes: the scaffold's /health route by default; set
|
|
59
|
+
# /readyz when the app registers healthPlugin().
|
|
60
|
+
ENV HEALTHCHECK_PATH=/health
|
|
61
|
+
WORKDIR /app
|
|
62
|
+
COPY --from=build --chown=node:node /app/package.json ./package.json
|
|
63
|
+
COPY --from=build --chown=node:node /app/node_modules ./node_modules
|
|
64
|
+
COPY --from=build --chown=node:node /app/generated ./generated
|
|
65
|
+
COPY --from=build --chown=node:node /app/dist ./dist
|
|
66
|
+
USER node
|
|
67
|
+
EXPOSE 3000
|
|
68
|
+
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \\
|
|
69
|
+
CMD node -e "fetch('http://127.0.0.1:' + (process.env.PORT || 3000) + process.env.HEALTHCHECK_PATH).then((r) => process.exit(r.ok ? 0 : 1), () => process.exit(1))"
|
|
70
|
+
CMD ["node", "--enable-source-maps", "${PRODUCTION_ENTRY}"]
|
|
71
|
+
`;
|
|
72
|
+
/**
|
|
73
|
+
* Published with the Dockerfile: its \`COPY . .\` would otherwise bake \`.env\`,
|
|
74
|
+
* private keys and VCS metadata into an image layer. Every secret rule is
|
|
75
|
+
* \`**\/\`-prefixed: .dockerignore patterns are anchored at the context root, so
|
|
76
|
+
* a bare \`.env\` or \`*.pem\` would still let \`prisma/.env\` or
|
|
77
|
+
* \`certs/server.key\` through. \`dist\` is rebuilt inside the image.
|
|
78
|
+
*/
|
|
79
|
+
export const DOCKERIGNORE = `**/.env
|
|
80
|
+
**/.env.*
|
|
81
|
+
!**/.env.example
|
|
82
|
+
**/.npmrc
|
|
83
|
+
**/.git
|
|
84
|
+
**/node_modules
|
|
85
|
+
**/coverage
|
|
86
|
+
**/*.log
|
|
87
|
+
**/*.pem
|
|
88
|
+
**/*.key
|
|
89
|
+
**/*.p12
|
|
90
|
+
**/*.pfx
|
|
91
|
+
**/.DS_Store
|
|
92
|
+
dist
|
|
93
|
+
`;
|
|
94
|
+
/** GitHub Actions CI: install, typecheck, build (the production path), test. */
|
|
95
|
+
export const CI_WORKFLOW = `name: ci
|
|
96
|
+
on:
|
|
97
|
+
push: { branches: [main] }
|
|
98
|
+
pull_request:
|
|
99
|
+
jobs:
|
|
100
|
+
build:
|
|
101
|
+
runs-on: ubuntu-latest
|
|
102
|
+
steps:
|
|
103
|
+
- uses: actions/checkout@v4
|
|
104
|
+
# Drop \`with: version\` if package.json declares a packageManager.
|
|
105
|
+
- uses: pnpm/action-setup@v4
|
|
106
|
+
with: { version: 11 }
|
|
107
|
+
- uses: actions/setup-node@v4
|
|
108
|
+
with: { node-version: 22, cache: pnpm }
|
|
109
|
+
- run: pnpm install --frozen-lockfile
|
|
110
|
+
- run: pnpm run typecheck
|
|
111
|
+
- run: pnpm run build
|
|
112
|
+
- run: pnpm run test
|
|
113
|
+
`;
|
|
114
|
+
export const EDITORCONFIG = `root = true
|
|
115
|
+
|
|
116
|
+
[*]
|
|
117
|
+
charset = utf-8
|
|
118
|
+
end_of_line = lf
|
|
119
|
+
insert_final_newline = true
|
|
120
|
+
indent_style = space
|
|
121
|
+
indent_size = 2
|
|
122
|
+
trim_trailing_whitespace = true
|
|
123
|
+
`;
|
package/dist/templates.d.ts
CHANGED
|
@@ -20,8 +20,8 @@ export { basaltBin } from './bin-template.js';
|
|
|
20
20
|
export { SCAFFOLD_VERSIONS } from './versions.js';
|
|
21
21
|
export { THIRD_PARTY_VERSIONS } from './latest-versions.js';
|
|
22
22
|
/**
|
|
23
|
-
* The dependency range for a @basaltkit/* package:
|
|
24
|
-
* (`^<major>.<minor
|
|
23
|
+
* The dependency range for a @basaltkit/* package: its workspace version as
|
|
24
|
+
* the floor (`^<major>.<minor>.<patch>`), from the generated
|
|
25
25
|
* {@link SCAFFOLD_VERSIONS} map. Throws for an unknown package rather than
|
|
26
26
|
* guessing a range — a stale guess once pinned new apps to frozen 1.x lines.
|
|
27
27
|
*/
|
|
@@ -33,6 +33,24 @@ export declare const versionOf: (pkg: string) => string;
|
|
|
33
33
|
* unknown package so every emitted range lives in that single table.
|
|
34
34
|
*/
|
|
35
35
|
export declare const thirdPartyVersionOf: (pkg: string) => string;
|
|
36
|
+
/**
|
|
37
|
+
* The production path (BK-026): `build` compiles src/ with tsconfig.build.json
|
|
38
|
+
* (rootDir `.`, outDir `dist`) and `start` runs the result on plain node — the
|
|
39
|
+
* same entry the Dockerfile's CMD runs. `start:dev` keeps the old tsx start.
|
|
40
|
+
*/
|
|
41
|
+
export declare const BUILD_SCRIPT = "tsc -p tsconfig.build.json";
|
|
42
|
+
/** The `db:seed` script of a `--prisma` scaffold with tenancy (prisma.config.ts loads .env, then runs `migrations.seed`). */
|
|
43
|
+
export declare const DB_SEED_SCRIPT = "prisma db seed";
|
|
44
|
+
export declare const START_SCRIPT = "node --enable-source-maps dist/src/server.js";
|
|
45
|
+
export declare const START_DEV_SCRIPT = "tsx src/server.ts";
|
|
46
|
+
/** The `start` script every create-basalt release before the production path wrote. */
|
|
47
|
+
export declare const LEGACY_START_SCRIPT = "tsx src/server.ts";
|
|
48
|
+
/**
|
|
49
|
+
* package.json `imports` alias for the generated Prisma client. It lives in
|
|
50
|
+
* ./generated/prisma — outside src/, so tsc never has to copy its .js files —
|
|
51
|
+
* and `#db/*` resolves to it from src/ (tsx, vitest) and dist/src/ (node) alike.
|
|
52
|
+
*/
|
|
53
|
+
export declare const PRISMA_IMPORTS: Readonly<Record<string, string>>;
|
|
36
54
|
/** The root `basalt` script of an app without `--cli`: create-basalt's project commands. */
|
|
37
55
|
export declare const BASALT_PROJECT_SCRIPT = "create-basalt --project";
|
|
38
56
|
/** The root `dev:web` script of a `--ui` app (web/ is a pnpm workspace member). */
|
|
@@ -41,6 +59,15 @@ export declare const webDevScript: (options: Pick<ProjectOptions, 'name'>) => st
|
|
|
41
59
|
export declare const enforcesMembership: (options: ProjectOptions) => boolean;
|
|
42
60
|
export declare function packageJson(options: ProjectOptions): string;
|
|
43
61
|
export declare function tsconfigJson(options: ProjectOptions): string;
|
|
62
|
+
/**
|
|
63
|
+
* `tsconfig.build.json` — what `pnpm build` compiles: src/ only (tests and
|
|
64
|
+
* bin/ stay dev-only), emitted to dist/ with rootDir `.`, so src/server.ts
|
|
65
|
+
* becomes dist/src/server.js. Extends tsconfig.json, so the compiler options
|
|
66
|
+
* (and `pnpm typecheck`) never diverge.
|
|
67
|
+
*/
|
|
68
|
+
export declare function tsconfigBuildJson(): string;
|
|
69
|
+
/** The production Dockerfile — the same file `basalt publish dockerfile` writes. */
|
|
70
|
+
export declare function dockerfile(): string;
|
|
44
71
|
export declare function envTs(options: ProjectOptions): string;
|
|
45
72
|
/**
|
|
46
73
|
* An app-specific env-var prefix derived from the project name
|
|
@@ -101,8 +128,9 @@ export declare function prismaConfigTs(options: ProjectOptions): string;
|
|
|
101
128
|
export declare function dbTs(options: ProjectOptions): string;
|
|
102
129
|
/**
|
|
103
130
|
* `prisma/seed.ts` — creates the `demo` tenant the scaffolded resolvers expect
|
|
104
|
-
* (`x-tenant-id: demo`, `demo.localhost`). Runs from `pnpm db:seed`
|
|
105
|
-
* `prisma
|
|
131
|
+
* (`x-tenant-id: demo`, `demo.localhost`). Runs from `pnpm db:seed`
|
|
132
|
+
* (`prisma db seed`, the command prisma.config.ts declares) — Prisma 7's
|
|
133
|
+
* `migrate dev` does not run it.
|
|
106
134
|
*/
|
|
107
135
|
export declare function prismaSeedTs(options: ProjectOptions): string;
|
|
108
136
|
export declare function appTest(options: ProjectOptions): string;
|
|
@@ -129,7 +157,9 @@ export declare function mcpJson(_options: ProjectOptions): string;
|
|
|
129
157
|
export declare function dockerignore(): string;
|
|
130
158
|
export declare function gitignore(options: ProjectOptions): string;
|
|
131
159
|
/**
|
|
132
|
-
* pnpm settings: esbuild's build script is required by tsx;
|
|
160
|
+
* pnpm settings: esbuild's build script is required by tsx; with --prisma,
|
|
161
|
+
* the Prisma CLI's build scripts (prisma, @prisma/engines) are approved too — pnpm 11 fails
|
|
162
|
+
* the install on an unapproved build;
|
|
133
163
|
* msgpackr-extract (optional native accelerator via BullMQ) is declined —
|
|
134
164
|
* msgpackr falls back to pure JS. With --ui, `web` is a workspace member so
|
|
135
165
|
* its own dependencies resolve.
|
package/dist/templates.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
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';
|
|
5
6
|
export { THIRD_PARTY_VERSIONS } from './latest-versions.js';
|
|
6
7
|
/**
|
|
7
|
-
* The dependency range for a @basaltkit/* package:
|
|
8
|
-
* (`^<major>.<minor
|
|
8
|
+
* The dependency range for a @basaltkit/* package: its workspace version as
|
|
9
|
+
* the floor (`^<major>.<minor>.<patch>`), from the generated
|
|
9
10
|
* {@link SCAFFOLD_VERSIONS} map. Throws for an unknown package rather than
|
|
10
11
|
* guessing a range — a stale guess once pinned new apps to frozen 1.x lines.
|
|
11
12
|
*/
|
|
@@ -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,12 +94,20 @@ 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
|
-
|
|
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
|
}
|
|
82
105
|
const devDependencies = {
|
|
83
106
|
'@basaltkit/testing': versionOf('@basaltkit/testing'),
|
|
107
|
+
// @basaltkit/testing's peer: fakeMailer() binds the app's own MAILER token,
|
|
108
|
+
// so the copy must be the app's (a package manager that skips peers would
|
|
109
|
+
// otherwise leave `@basaltkit/testing` unable to load).
|
|
110
|
+
'@basaltkit/mailer': versionOf('@basaltkit/mailer'),
|
|
84
111
|
// The project commands (\`pnpm basalt update | add | doctor | info\`) — dev
|
|
85
112
|
// tooling with no dependencies of its own, never imported by the app.
|
|
86
113
|
'create-basalt': versionOf('create-basalt'),
|
|
@@ -108,11 +135,15 @@ export function packageJson(options) {
|
|
|
108
135
|
version: '0.1.0',
|
|
109
136
|
private: true,
|
|
110
137
|
type: 'module',
|
|
138
|
+
...(options.prisma ? { imports: PRISMA_IMPORTS } : {}),
|
|
111
139
|
scripts: {
|
|
112
140
|
// dev opts into NODE_ENV=development (src/dev.ts); start does not, so an
|
|
113
141
|
// unset NODE_ENV counts as production and secrets are required.
|
|
114
142
|
dev: 'tsx watch src/dev.ts',
|
|
115
|
-
|
|
143
|
+
// Production: compile once, run on plain node (no tsx at runtime).
|
|
144
|
+
build: BUILD_SCRIPT,
|
|
145
|
+
start: START_SCRIPT,
|
|
146
|
+
'start:dev': START_DEV_SCRIPT,
|
|
116
147
|
test: 'vitest run',
|
|
117
148
|
typecheck: 'tsc --noEmit',
|
|
118
149
|
// With --cli, bin/basalt.ts serves the generators AND hands the project
|
|
@@ -132,7 +163,11 @@ export function packageJson(options) {
|
|
|
132
163
|
// is deliberately no `db:push` script.
|
|
133
164
|
'db:migrate': 'prisma migrate dev',
|
|
134
165
|
'db:deploy': 'prisma migrate deploy',
|
|
135
|
-
|
|
166
|
+
// Through Prisma, not `tsx prisma/seed.ts`: prisma.config.ts loads
|
|
167
|
+
// .env (the seed imports src/env.ts, which needs the database URL),
|
|
168
|
+
// then runs the `migrations.seed` command it declares. Prisma 7's
|
|
169
|
+
// `migrate dev` no longer seeds on its own.
|
|
170
|
+
...(options.tenancy ? { 'db:seed': DB_SEED_SCRIPT } : {}),
|
|
136
171
|
}
|
|
137
172
|
: {}),
|
|
138
173
|
},
|
|
@@ -153,12 +188,34 @@ export function tsconfigJson(options) {
|
|
|
153
188
|
noEmit: true,
|
|
154
189
|
types: ['node'],
|
|
155
190
|
},
|
|
156
|
-
// The generated Prisma client lives
|
|
157
|
-
// re-created by `prisma generate
|
|
158
|
-
// type-checked too.
|
|
191
|
+
// The generated Prisma client lives in ./generated (git-ignored,
|
|
192
|
+
// re-created by `prisma generate`, reached through the `#db/*` import
|
|
193
|
+
// alias); the seed script under prisma/ is type-checked too.
|
|
159
194
|
include: ['src', 'tests', ...(options.prisma && options.tenancy ? ['prisma/seed.ts'] : [])],
|
|
160
195
|
}, null, 2)}\n`;
|
|
161
196
|
}
|
|
197
|
+
/**
|
|
198
|
+
* `tsconfig.build.json` — what `pnpm build` compiles: src/ only (tests and
|
|
199
|
+
* bin/ stay dev-only), emitted to dist/ with rootDir `.`, so src/server.ts
|
|
200
|
+
* becomes dist/src/server.js. Extends tsconfig.json, so the compiler options
|
|
201
|
+
* (and `pnpm typecheck`) never diverge.
|
|
202
|
+
*/
|
|
203
|
+
export function tsconfigBuildJson() {
|
|
204
|
+
return `${JSON.stringify({
|
|
205
|
+
extends: './tsconfig.json',
|
|
206
|
+
compilerOptions: {
|
|
207
|
+
noEmit: false,
|
|
208
|
+
rootDir: '.',
|
|
209
|
+
outDir: 'dist',
|
|
210
|
+
sourceMap: true,
|
|
211
|
+
},
|
|
212
|
+
include: ['src'],
|
|
213
|
+
}, null, 2)}\n`;
|
|
214
|
+
}
|
|
215
|
+
/** The production Dockerfile — the same file `basalt publish dockerfile` writes. */
|
|
216
|
+
export function dockerfile() {
|
|
217
|
+
return DOCKERFILE;
|
|
218
|
+
}
|
|
162
219
|
export function envTs(options) {
|
|
163
220
|
const prefix = envPrefix(options.name);
|
|
164
221
|
return `import { defineEnv${options.auth ? ', secret' : ''} } from '@basaltkit/env'
|
|
@@ -534,7 +591,15 @@ import { env } from './env.js'
|
|
|
534
591
|
const app = await buildApp({
|
|
535
592
|
logLevel: env.LOG_LEVEL,
|
|
536
593
|
pretty: env.NODE_ENV === 'development',
|
|
537
|
-
})
|
|
594
|
+
})
|
|
595
|
+
.boot()
|
|
596
|
+
.catch((error: unknown) => {
|
|
597
|
+
// A database failure diagnosed by @basaltkit/prisma (permission denied, not
|
|
598
|
+
// migrated, unreachable…) carries its one-line fix: print it above the stack.
|
|
599
|
+
const diagnosis = (error as { details?: { diagnosis?: { cause?: string; fix?: string } } } | null)?.details?.diagnosis
|
|
600
|
+
if (diagnosis) console.error('Cannot start: ' + diagnosis.cause + '\\nFix: ' + diagnosis.fix)
|
|
601
|
+
throw error
|
|
602
|
+
})
|
|
538
603
|
|
|
539
604
|
const server = app.container.get(FASTIFY)
|
|
540
605
|
await server.listen({ port: env.PORT, host: env.HOST })
|
|
@@ -594,9 +659,17 @@ const PRISMA_MODELS = {
|
|
|
594
659
|
}
|
|
595
660
|
|
|
596
661
|
model TenantDomain {
|
|
597
|
-
domain
|
|
598
|
-
tenantId
|
|
599
|
-
|
|
662
|
+
domain String @id
|
|
663
|
+
tenantId String
|
|
664
|
+
// Custom-domain verification (PrismaDomainStore). A row with a NULL token
|
|
665
|
+
// mirrors \`tenant.domains\` and is written by PrismaTenantSource; a row with a
|
|
666
|
+
// token is a CustomDomains claim, which the source never deletes, and which
|
|
667
|
+
// only resolves a request once \`verified\` is true.
|
|
668
|
+
verificationToken String?
|
|
669
|
+
verified Boolean @default(true)
|
|
670
|
+
createdAt DateTime @default(now())
|
|
671
|
+
verifiedAt DateTime?
|
|
672
|
+
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
|
|
600
673
|
|
|
601
674
|
@@index([tenantId])
|
|
602
675
|
@@map("tenant_domains")
|
|
@@ -611,9 +684,11 @@ model TenantDomain {
|
|
|
611
684
|
}
|
|
612
685
|
|
|
613
686
|
model AuthSession {
|
|
614
|
-
id
|
|
615
|
-
userId
|
|
616
|
-
expiresAt
|
|
687
|
+
id String @id
|
|
688
|
+
userId String
|
|
689
|
+
expiresAt DateTime
|
|
690
|
+
/// Last use, for the idle timeout (trackSessionActivity: true).
|
|
691
|
+
lastSeenAt DateTime?
|
|
617
692
|
|
|
618
693
|
@@index([userId])
|
|
619
694
|
@@map("auth_sessions")
|
|
@@ -654,9 +729,17 @@ model AuthApiKey {
|
|
|
654
729
|
expiresAt DateTime?
|
|
655
730
|
lastUsedAt DateTime?
|
|
656
731
|
revokedAt DateTime?
|
|
732
|
+
// 3.0: API key lifecycle (migration \`add_api_key_lifecycle\`).
|
|
733
|
+
serviceId String?
|
|
734
|
+
previousHash String? @unique
|
|
735
|
+
previousHashExpiresAt DateTime?
|
|
736
|
+
rotatedAt DateTime?
|
|
737
|
+
allowedIps String[] @default([])
|
|
738
|
+
meta Json?
|
|
657
739
|
|
|
658
740
|
@@index([tenantId])
|
|
659
741
|
@@index([userId])
|
|
742
|
+
@@index([serviceId])
|
|
660
743
|
@@map("auth_api_keys")
|
|
661
744
|
}
|
|
662
745
|
|
|
@@ -792,7 +875,9 @@ export function prismaSchema(options) {
|
|
|
792
875
|
|
|
793
876
|
generator client {
|
|
794
877
|
provider = "prisma-client-js"
|
|
795
|
-
|
|
878
|
+
// Outside src/ on purpose: src/db.ts imports it as \`#db/client.js\` (the
|
|
879
|
+
// package.json "imports" alias), which resolves from src/ and dist/src/ alike.
|
|
880
|
+
output = "../generated/prisma"
|
|
796
881
|
}
|
|
797
882
|
|
|
798
883
|
datasource db {
|
|
@@ -840,7 +925,7 @@ try {
|
|
|
840
925
|
export default defineConfig({
|
|
841
926
|
schema: 'prisma/schema.prisma',${options.tenancy
|
|
842
927
|
? `
|
|
843
|
-
//
|
|
928
|
+
// Run by \`prisma db seed\` (\`pnpm db:seed\`), with the .env loaded above.
|
|
844
929
|
migrations: { seed: 'tsx prisma/seed.ts' },`
|
|
845
930
|
: ''}
|
|
846
931
|
datasource: {
|
|
@@ -860,7 +945,9 @@ export default defineConfig({
|
|
|
860
945
|
*/
|
|
861
946
|
export function dbTs(options) {
|
|
862
947
|
return `import { PrismaPg } from '@prisma/adapter-pg'${options.tenancy ? `\nimport { tenancyExtension } from '@basaltkit/prisma'` : ''}
|
|
863
|
-
|
|
948
|
+
// The generated client (./generated/prisma), through the package.json
|
|
949
|
+
// "imports" alias — the same specifier works under tsx and from dist/.
|
|
950
|
+
import { PrismaClient } from '#db/client.js'
|
|
864
951
|
import { env } from './env.js'
|
|
865
952
|
|
|
866
953
|
/**
|
|
@@ -896,8 +983,9 @@ export const db = prisma
|
|
|
896
983
|
}
|
|
897
984
|
/**
|
|
898
985
|
* `prisma/seed.ts` — creates the `demo` tenant the scaffolded resolvers expect
|
|
899
|
-
* (`x-tenant-id: demo`, `demo.localhost`). Runs from `pnpm db:seed`
|
|
900
|
-
* `prisma
|
|
986
|
+
* (`x-tenant-id: demo`, `demo.localhost`). Runs from `pnpm db:seed`
|
|
987
|
+
* (`prisma db seed`, the command prisma.config.ts declares) — Prisma 7's
|
|
988
|
+
* `migrate dev` does not run it.
|
|
901
989
|
*/
|
|
902
990
|
export function prismaSeedTs(options) {
|
|
903
991
|
return `import { prismaTenantSource } from '@basaltkit/tenancy-prisma'
|
|
@@ -984,7 +1072,8 @@ Included: ${features.join(' · ')}.
|
|
|
984
1072
|
pnpm install${options.prisma
|
|
985
1073
|
? `
|
|
986
1074
|
# start PostgreSQL — .env points ${envPrefix(options.name)}_DATABASE_URL at localhost:5432 — then
|
|
987
|
-
pnpm db:migrate # create the tables${options.tenancy ? `
|
|
1075
|
+
pnpm db:migrate # create the tables${options.tenancy ? `
|
|
1076
|
+
pnpm db:seed # the 'demo' tenant the resolvers expect` : ''}`
|
|
988
1077
|
: ''}
|
|
989
1078
|
pnpm dev # API on http://localhost:3000
|
|
990
1079
|
pnpm test
|
|
@@ -998,11 +1087,11 @@ the models this app needs — the reference models of the \`@basaltkit/*-prisma\
|
|
|
998
1087
|
packages it uses, plus your own \`Project\` at the end.
|
|
999
1088
|
|
|
1000
1089
|
\`\`\`bash
|
|
1001
|
-
pnpm db:migrate
|
|
1090
|
+
pnpm db:migrate --name <change> # prisma migrate dev — writes AND applies a migration (asks for a name without --name)${options.tenancy
|
|
1002
1091
|
? `
|
|
1003
|
-
pnpm db:seed # the 'demo' tenant the resolvers expect
|
|
1092
|
+
pnpm db:seed # prisma db seed — the 'demo' tenant the resolvers expect`
|
|
1004
1093
|
: ''}
|
|
1005
|
-
pnpm db:generate # prisma generate — refresh
|
|
1094
|
+
pnpm db:generate # prisma generate — refresh generated/prisma after a schema change (also runs on install; migrate dev does not)
|
|
1006
1095
|
pnpm db:deploy # prisma migrate deploy — apply pending migrations in production
|
|
1007
1096
|
\`\`\`
|
|
1008
1097
|
|
|
@@ -1044,7 +1133,7 @@ scaffold with \`--cli\` — \`pnpm basalt prisma:sync\` does the merge for you.)
|
|
|
1044
1133
|
\`src/env.ts\` validates \`process.env\`. For development, ${options.cli ? '`pnpm dev` and `pnpm basalt` load' : '`pnpm dev` loads'} \`.env\`
|
|
1045
1134
|
automatically — create-basalt wrote one for you: git-ignored, with local
|
|
1046
1135
|
values${options.auth ? ' and a generated secret' : ''}. \`.env.example\` is its committed template.
|
|
1047
|
-
\`pnpm start\` (production, \`
|
|
1136
|
+
\`pnpm start\` (production, \`${PRODUCTION_ENTRY}\`) loads **nothing**: there the
|
|
1048
1137
|
variables come from the real environment (or \`node --env-file=.env\`).
|
|
1049
1138
|
|
|
1050
1139
|
The variables are **app-prefixed**: \`${envPrefix(options.name)}_PORT\`,
|
|
@@ -1068,6 +1157,24 @@ To drop the fallback entirely (prefixed names only), write
|
|
|
1068
1157
|
\`prefix: { value: '${envPrefix(options.name)}', fallback: false }\`. When in doubt about what
|
|
1069
1158
|
your shell exports: \`env | grep DATABASE_URL\`.
|
|
1070
1159
|
|
|
1160
|
+
## Build & ship
|
|
1161
|
+
|
|
1162
|
+
\`\`\`bash
|
|
1163
|
+
pnpm build # tsc -p tsconfig.build.json → dist/ (src only)
|
|
1164
|
+
pnpm start # node --enable-source-maps ${PRODUCTION_ENTRY} — no tsx at runtime
|
|
1165
|
+
pnpm start:dev # the server from source with tsx, without building
|
|
1166
|
+
docker build -t ${options.name} . # the Dockerfile: build stage + plain-node runtime
|
|
1167
|
+
\`\`\`
|
|
1168
|
+
|
|
1169
|
+
\`pnpm start\` runs the compiled server, so build first. The \`Dockerfile\` (also
|
|
1170
|
+
what \`basalt publish dockerfile\` writes) installs, ${options.prisma ? 'generates the Prisma client, ' : ''}builds, drops the
|
|
1171
|
+
devDependencies and runs ${PRODUCTION_ENTRY} as the \`node\` user, with a
|
|
1172
|
+
\`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
|
|
1173
|
+
? ` The Prisma client is generated into \`generated/prisma\` (outside \`src/\`) and
|
|
1174
|
+
imported as \`#db/client.js\` through the package.json \`imports\` alias, so the same
|
|
1175
|
+
import works under tsx and from \`dist/\`.`
|
|
1176
|
+
: ''}
|
|
1177
|
+
|
|
1071
1178
|
## Keeping it up to date
|
|
1072
1179
|
|
|
1073
1180
|
\`\`\`bash
|
|
@@ -1177,20 +1284,7 @@ export function mcpJson(_options) {
|
|
|
1177
1284
|
* `certs/server.key` through.
|
|
1178
1285
|
*/
|
|
1179
1286
|
export function dockerignore() {
|
|
1180
|
-
return
|
|
1181
|
-
**/.env.*
|
|
1182
|
-
!**/.env.example
|
|
1183
|
-
**/.npmrc
|
|
1184
|
-
**/.git
|
|
1185
|
-
**/node_modules
|
|
1186
|
-
**/coverage
|
|
1187
|
-
**/*.log
|
|
1188
|
-
**/*.pem
|
|
1189
|
-
**/*.key
|
|
1190
|
-
**/*.p12
|
|
1191
|
-
**/*.pfx
|
|
1192
|
-
**/.DS_Store
|
|
1193
|
-
`;
|
|
1287
|
+
return DOCKERIGNORE;
|
|
1194
1288
|
}
|
|
1195
1289
|
export function gitignore(options) {
|
|
1196
1290
|
return `node_modules/
|
|
@@ -1201,12 +1295,14 @@ dist/
|
|
|
1201
1295
|
*.log
|
|
1202
1296
|
${options.prisma
|
|
1203
1297
|
? `# Generated by \`prisma generate\` (runs on install) — never edit or commit it.
|
|
1204
|
-
|
|
1298
|
+
generated/
|
|
1205
1299
|
`
|
|
1206
1300
|
: ''}`;
|
|
1207
1301
|
}
|
|
1208
1302
|
/**
|
|
1209
|
-
* pnpm settings: esbuild's build script is required by tsx;
|
|
1303
|
+
* pnpm settings: esbuild's build script is required by tsx; with --prisma,
|
|
1304
|
+
* the Prisma CLI's build scripts (prisma, @prisma/engines) are approved too — pnpm 11 fails
|
|
1305
|
+
* the install on an unapproved build;
|
|
1210
1306
|
* msgpackr-extract (optional native accelerator via BullMQ) is declined —
|
|
1211
1307
|
* msgpackr falls back to pure JS. With --ui, `web` is a workspace member so
|
|
1212
1308
|
* its own dependencies resolve.
|
|
@@ -1224,7 +1320,13 @@ export function pnpmWorkspaceYaml(options) {
|
|
|
1224
1320
|
`
|
|
1225
1321
|
: ''}allowBuilds:
|
|
1226
1322
|
esbuild: true
|
|
1227
|
-
msgpackr-extract: false
|
|
1323
|
+
msgpackr-extract: false${options.prisma
|
|
1324
|
+
? `
|
|
1325
|
+
# The Prisma CLI's install scripts fetch its engines; pnpm 11 refuses to finish
|
|
1326
|
+
# an install (ERR_PNPM_IGNORED_BUILDS) while a dependency's build is unapproved.
|
|
1327
|
+
'@prisma/engines': true
|
|
1328
|
+
prisma: true`
|
|
1329
|
+
: ''}
|
|
1228
1330
|
# @basaltkit/* releases in lockstep, often within hours — exclude the scope (and
|
|
1229
1331
|
# create-basalt, which serves \`pnpm basalt update\`) from pnpm's minimumReleaseAge
|
|
1230
1332
|
# policy so an update is never blocked on a fresh release.
|
package/dist/versions.js
CHANGED
|
@@ -1,29 +1,31 @@
|
|
|
1
1
|
// GENERATED by scripts/sync-versions.mjs (runs before every build) — do not edit.
|
|
2
2
|
// The dependency range a new app gets for each @basaltkit/* package: the
|
|
3
|
-
//
|
|
3
|
+
// workspace version as the floor (^<major>.<minor>.<patch>), so a new app never
|
|
4
|
+
// resolves an older patch of the same line.
|
|
4
5
|
export const SCAFFOLD_VERSIONS = {
|
|
5
6
|
'@basaltkit/admin': '^2.0.0',
|
|
6
|
-
'@basaltkit/admin-shadcn': '^1.1.
|
|
7
|
-
'@basaltkit/ai-mcp': '^0.
|
|
8
|
-
'@basaltkit/auth': '^
|
|
9
|
-
'@basaltkit/auth-prisma': '^
|
|
10
|
-
'@basaltkit/cli': '^1.
|
|
11
|
-
'@basaltkit/config': '^1.1.
|
|
12
|
-
'@basaltkit/core': '^1.
|
|
13
|
-
'@basaltkit/env': '^3.1.
|
|
7
|
+
'@basaltkit/admin-shadcn': '^1.1.1',
|
|
8
|
+
'@basaltkit/ai-mcp': '^0.4.0',
|
|
9
|
+
'@basaltkit/auth': '^5.0.0',
|
|
10
|
+
'@basaltkit/auth-prisma': '^3.0.0',
|
|
11
|
+
'@basaltkit/cli': '^1.4.1',
|
|
12
|
+
'@basaltkit/config': '^1.1.2',
|
|
13
|
+
'@basaltkit/core': '^1.6.0',
|
|
14
|
+
'@basaltkit/env': '^3.1.1',
|
|
14
15
|
'@basaltkit/events': '^1.3.0',
|
|
15
|
-
'@basaltkit/fastify': '^
|
|
16
|
-
'@basaltkit/generator': '^1.
|
|
16
|
+
'@basaltkit/fastify': '^3.0.0',
|
|
17
|
+
'@basaltkit/generator': '^1.6.0',
|
|
17
18
|
'@basaltkit/logger': '^1.3.0',
|
|
18
|
-
'@basaltkit/
|
|
19
|
-
'@basaltkit/
|
|
19
|
+
'@basaltkit/mailer': '^2.1.0',
|
|
20
|
+
'@basaltkit/mcp': '^6.0.0',
|
|
21
|
+
'@basaltkit/prisma': '^4.0.0',
|
|
20
22
|
'@basaltkit/sdk': '^2.1.0',
|
|
21
|
-
'@basaltkit/subscriptions': '^
|
|
22
|
-
'@basaltkit/subscriptions-prisma': '^
|
|
23
|
-
'@basaltkit/teams': '^4.
|
|
23
|
+
'@basaltkit/subscriptions': '^6.0.0',
|
|
24
|
+
'@basaltkit/subscriptions-prisma': '^4.0.0',
|
|
25
|
+
'@basaltkit/teams': '^4.1.1',
|
|
24
26
|
'@basaltkit/teams-prisma': '^1.1.0',
|
|
25
|
-
'@basaltkit/tenancy': '^
|
|
26
|
-
'@basaltkit/tenancy-prisma': '^
|
|
27
|
-
'@basaltkit/testing': '^
|
|
28
|
-
'create-basalt': '^1.
|
|
27
|
+
'@basaltkit/tenancy': '^4.0.0',
|
|
28
|
+
'@basaltkit/tenancy-prisma': '^3.0.0',
|
|
29
|
+
'@basaltkit/testing': '^3.0.0',
|
|
30
|
+
'create-basalt': '^1.13.0',
|
|
29
31
|
};
|