@webjsdev/cli 0.10.17 → 0.10.19
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 +3 -3
- package/bin/webjs.js +90 -22
- package/lib/app-tasks.js +62 -0
- package/lib/create.js +234 -82
- package/lib/run-tasks.js +100 -0
- package/lib/saas-template.js +65 -73
- package/package.json +1 -1
- package/templates/.agents/rules/workflow.md +5 -5
- package/templates/.env.example +2 -2
- package/templates/.github/copilot-instructions.md +4 -4
- package/templates/.github/workflows/ci.yml +4 -7
- package/templates/AGENTS.md +88 -72
- package/templates/CONVENTIONS.md +52 -41
- package/templates/Dockerfile +10 -7
- package/templates/compose.yaml +3 -3
- package/templates/test/hello/hello.test.ts +1 -1
- package/lib/prisma-preflight.js +0 -168
package/lib/saas-template.js
CHANGED
|
@@ -14,17 +14,19 @@ const UI_REGISTRY_ROOT = resolve(
|
|
|
14
14
|
);
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
* Read a registry component and rewrite its `'
|
|
18
|
-
* the scaffolded app's `components/ui/<name>.ts` layout (
|
|
19
|
-
* Mirrors the helper in `create.js`, kept private here to avoid coupling.
|
|
17
|
+
* Read a registry component and rewrite its `'#lib/utils.ts'` import for
|
|
18
|
+
* the scaffolded app's `components/ui/<name>.ts` layout (the `#lib/utils/cn.ts`
|
|
19
|
+
* alias). Mirrors the helper in `create.js`, kept private here to avoid coupling.
|
|
20
20
|
*/
|
|
21
21
|
async function readUiComponent(name) {
|
|
22
22
|
const src = join(UI_REGISTRY_ROOT, 'components', `${name}.ts`);
|
|
23
23
|
if (!existsSync(src)) return null;
|
|
24
24
|
const raw = await readFile(src, 'utf8');
|
|
25
|
+
// The registry component imports cn() via a relative `../lib/utils.ts`; rewrite
|
|
26
|
+
// it to the scaffolded app's aliased path (cn lives at lib/utils/cn.ts).
|
|
25
27
|
return raw
|
|
26
|
-
.replaceAll("'../lib/utils.ts'", "'
|
|
27
|
-
.replaceAll('"../lib/utils.ts"', '"
|
|
28
|
+
.replaceAll("'../lib/utils.ts'", "'#lib/utils/cn.ts'")
|
|
29
|
+
.replaceAll('"../lib/utils.ts"', '"#lib/utils/cn.ts"');
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
/** Copy named registry components into `<appDir>/components/ui/`. */
|
|
@@ -50,16 +52,10 @@ export async function writeSaasFiles(appDir) {
|
|
|
50
52
|
// the saas auth pages use raw <form> + label/input class helpers instead.
|
|
51
53
|
await copyUiComponents(appDir, ['dialog', 'switch', 'checkbox']);
|
|
52
54
|
|
|
53
|
-
//
|
|
55
|
+
// The db/ layer (columns/connection) is written by the full-stack scaffold
|
|
56
|
+
// already; this template overwrites db/schema.server.ts below to add the
|
|
57
|
+
// User.passwordHash column auth needs.
|
|
54
58
|
await mkdir(join(appDir, 'lib'), { recursive: true });
|
|
55
|
-
await writeFile(join(appDir, 'lib', 'prisma.server.ts'), [
|
|
56
|
-
"import { PrismaClient } from '@prisma/client';",
|
|
57
|
-
"",
|
|
58
|
-
"const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };",
|
|
59
|
-
"export const prisma = globalForPrisma.prisma || new PrismaClient();",
|
|
60
|
-
"if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;",
|
|
61
|
-
"",
|
|
62
|
-
].join('\n'));
|
|
63
59
|
|
|
64
60
|
// lib/password.server.ts
|
|
65
61
|
await writeFile(join(appDir, 'lib', 'password.server.ts'), [
|
|
@@ -85,14 +81,14 @@ export async function writeSaasFiles(appDir) {
|
|
|
85
81
|
// lib/auth.server.ts
|
|
86
82
|
await writeFile(join(appDir, 'lib', 'auth.server.ts'), [
|
|
87
83
|
"import { createAuth, Credentials } from '@webjsdev/server';",
|
|
88
|
-
"import {
|
|
84
|
+
"import { db } from '#db/connection.server.ts';",
|
|
89
85
|
"import { compare } from './password.server.ts';",
|
|
90
86
|
"",
|
|
91
87
|
"export const { auth, signIn, signOut, handlers } = createAuth({",
|
|
92
88
|
" providers: [",
|
|
93
89
|
" Credentials({",
|
|
94
90
|
" async authorize(credentials: { email: string; password: string }) {",
|
|
95
|
-
" const user = await
|
|
91
|
+
" const user = await db.query.users.findFirst({ where: { email: credentials.email } });",
|
|
96
92
|
" if (!user || !await compare(credentials.password, user.passwordHash)) return null;",
|
|
97
93
|
" return { id: String(user.id), name: user.name, email: user.email };",
|
|
98
94
|
" },",
|
|
@@ -103,26 +99,24 @@ export async function writeSaasFiles(appDir) {
|
|
|
103
99
|
"",
|
|
104
100
|
].join('\n'));
|
|
105
101
|
|
|
106
|
-
//
|
|
107
|
-
|
|
108
|
-
await writeFile(join(appDir, '
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
'
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
'}',
|
|
125
|
-
'',
|
|
102
|
+
// db/schema.server.ts: overwrite the full-stack scaffold's example User to
|
|
103
|
+
// add passwordHash (the column auth needs). Drizzle, dialect-agnostic.
|
|
104
|
+
await writeFile(join(appDir, 'db', 'schema.server.ts'), [
|
|
105
|
+
"import { defineRelations } from 'drizzle-orm';",
|
|
106
|
+
"import { table, pk, text, createdAt } from './columns.server.ts';",
|
|
107
|
+
"",
|
|
108
|
+
"export const users = table('users', {",
|
|
109
|
+
" id: pk(),",
|
|
110
|
+
" email: text().notNull().unique(),",
|
|
111
|
+
" name: text(),",
|
|
112
|
+
" passwordHash: text().notNull(),",
|
|
113
|
+
" createdAt: createdAt(),",
|
|
114
|
+
"});",
|
|
115
|
+
"",
|
|
116
|
+
"export const relations = defineRelations({ users }, () => ({}));",
|
|
117
|
+
"",
|
|
118
|
+
"export type User = typeof users.$inferSelect;",
|
|
119
|
+
"",
|
|
126
120
|
].join('\n'));
|
|
127
121
|
|
|
128
122
|
// modules/auth/actions/signup.server.ts
|
|
@@ -132,15 +126,14 @@ export async function writeSaasFiles(appDir) {
|
|
|
132
126
|
await writeFile(join(appDir, 'modules', 'auth', 'actions', 'signup.server.ts'), [
|
|
133
127
|
"'use server';",
|
|
134
128
|
"",
|
|
135
|
-
"import {
|
|
136
|
-
"import {
|
|
129
|
+
"import { db } from '#db/connection.server.ts';",
|
|
130
|
+
"import { users } from '#db/schema.server.ts';",
|
|
131
|
+
"import { hash } from '#lib/password.server.ts';",
|
|
137
132
|
"",
|
|
138
133
|
"export async function signup(input: { name: string; email: string; password: string }) {",
|
|
139
|
-
" const exists = await
|
|
134
|
+
" const exists = await db.query.users.findFirst({ where: { email: input.email }, columns: { id: true } });",
|
|
140
135
|
" if (exists) return { success: false as const, error: 'Email already registered', status: 409 };",
|
|
141
|
-
" const user = await
|
|
142
|
-
" data: { name: input.name, email: input.email, passwordHash: await hash(input.password) },",
|
|
143
|
-
" });",
|
|
136
|
+
" const [user] = await db.insert(users).values({ name: input.name, email: input.email, passwordHash: await hash(input.password) }).returning();",
|
|
144
137
|
" return { success: true as const, data: { id: user.id, name: user.name, email: user.email } };",
|
|
145
138
|
"}",
|
|
146
139
|
"",
|
|
@@ -150,7 +143,7 @@ export async function writeSaasFiles(appDir) {
|
|
|
150
143
|
await writeFile(join(appDir, 'modules', 'auth', 'queries', 'current-user.server.ts'), [
|
|
151
144
|
"'use server';",
|
|
152
145
|
"",
|
|
153
|
-
"import { auth } from '
|
|
146
|
+
"import { auth } from '#lib/auth.server.ts';",
|
|
154
147
|
"",
|
|
155
148
|
"export async function currentUser() {",
|
|
156
149
|
" const session = await auth();",
|
|
@@ -183,11 +176,10 @@ export async function writeSaasFiles(appDir) {
|
|
|
183
176
|
// - The protected-route gate (unauthenticated /dashboard -> 302 /login) runs
|
|
184
177
|
// ALWAYS once the app modules import: auth() only reads a cookie, no DB
|
|
185
178
|
// query. This is the headline security assertion and it is REAL.
|
|
186
|
-
//
|
|
187
|
-
//
|
|
188
|
-
//
|
|
189
|
-
//
|
|
190
|
-
// message instead of crashing. After you set up the DB it runs for real.
|
|
179
|
+
// The signup, login, and protected-route flow writes + reads a user, so it
|
|
180
|
+
// needs the DB migrated (`npm run db:generate` then `npm run db:migrate`).
|
|
181
|
+
// Until the users table exists those flows error, so the suite skips with a
|
|
182
|
+
// clear message instead of crashing. After DB setup it runs for real.
|
|
191
183
|
await mkdir(join(appDir, 'test', 'auth'), { recursive: true });
|
|
192
184
|
await writeFile(join(appDir, 'test', 'auth', 'auth.test.ts'), [
|
|
193
185
|
"import { test } from 'node:test';",
|
|
@@ -200,20 +192,20 @@ export async function writeSaasFiles(appDir) {
|
|
|
200
192
|
"",
|
|
201
193
|
"const appDir = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');",
|
|
202
194
|
"",
|
|
203
|
-
"// The auth pages + dashboard middleware
|
|
204
|
-
"//
|
|
205
|
-
"//
|
|
206
|
-
"//
|
|
207
|
-
"//
|
|
195
|
+
"// The auth pages + dashboard middleware query the users table via Drizzle.",
|
|
196
|
+
"// Until `npm run db:generate` + `npm run db:migrate` have created it, a",
|
|
197
|
+
"// request hitting those modules 500s. We detect that at the RESPONSE level",
|
|
198
|
+
"// (a 5xx on the dashboard) and SKIP with a clear message rather than report",
|
|
199
|
+
"// a misleading failure. After you run",
|
|
208
200
|
"// npm install && npm run db:generate && npm run db:migrate",
|
|
209
201
|
"// every assertion below runs for real.",
|
|
210
202
|
"process.env.DATABASE_URL ||= 'file:./dev.db';",
|
|
211
203
|
"process.env.AUTH_SECRET ||= 'test-secret-at-least-32-characters-long!!';",
|
|
212
204
|
"",
|
|
213
205
|
"function makeHandler() {",
|
|
214
|
-
" // createRequestHandler builds lazily, so it succeeds even before
|
|
215
|
-
" //
|
|
216
|
-
" //
|
|
206
|
+
" // createRequestHandler builds lazily, so it succeeds even before the DB",
|
|
207
|
+
" // is migrated; the missing table only surfaces when a request reaches a",
|
|
208
|
+
" // module that queries it. That is why readiness is probed per-response.",
|
|
217
209
|
" return createRequestHandler({ appDir, dev: true });",
|
|
218
210
|
"}",
|
|
219
211
|
"",
|
|
@@ -273,7 +265,7 @@ export async function writeSaasFiles(appDir) {
|
|
|
273
265
|
// app/api/auth/[...path]/route.ts
|
|
274
266
|
await mkdir(join(appDir, 'app', 'api', 'auth', '[...path]'), { recursive: true });
|
|
275
267
|
await writeFile(join(appDir, 'app', 'api', 'auth', '[...path]', 'route.ts'), [
|
|
276
|
-
"import { handlers } from '
|
|
268
|
+
"import { handlers } from '#lib/auth.server.ts';",
|
|
277
269
|
"export const GET = handlers.GET;",
|
|
278
270
|
"export const POST = handlers.POST;",
|
|
279
271
|
"",
|
|
@@ -283,10 +275,10 @@ export async function writeSaasFiles(appDir) {
|
|
|
283
275
|
await mkdir(join(appDir, 'app', 'login'), { recursive: true });
|
|
284
276
|
await writeFile(join(appDir, 'app', 'login', 'page.ts'), [
|
|
285
277
|
"import { html } from '@webjsdev/core';",
|
|
286
|
-
"import { cardClass, cardHeaderClass, cardTitleClass, cardDescriptionClass, cardContentClass, cardFooterClass } from '
|
|
287
|
-
"import { buttonClass } from '
|
|
288
|
-
"import { inputClass } from '
|
|
289
|
-
"import { labelClass } from '
|
|
278
|
+
"import { cardClass, cardHeaderClass, cardTitleClass, cardDescriptionClass, cardContentClass, cardFooterClass } from '#components/ui/card.ts';",
|
|
279
|
+
"import { buttonClass } from '#components/ui/button.ts';",
|
|
280
|
+
"import { inputClass } from '#components/ui/input.ts';",
|
|
281
|
+
"import { labelClass } from '#components/ui/label.ts';",
|
|
290
282
|
"",
|
|
291
283
|
"export const metadata = { title: 'Login' };",
|
|
292
284
|
"",
|
|
@@ -325,11 +317,11 @@ export async function writeSaasFiles(appDir) {
|
|
|
325
317
|
await mkdir(join(appDir, 'app', 'signup'), { recursive: true });
|
|
326
318
|
await writeFile(join(appDir, 'app', 'signup', 'page.ts'), [
|
|
327
319
|
"import { html } from '@webjsdev/core';",
|
|
328
|
-
"import { signup } from '
|
|
329
|
-
"import { cardClass, cardHeaderClass, cardTitleClass, cardDescriptionClass, cardContentClass, cardFooterClass } from '
|
|
330
|
-
"import { buttonClass } from '
|
|
331
|
-
"import { inputClass } from '
|
|
332
|
-
"import { labelClass } from '
|
|
320
|
+
"import { signup } from '#modules/auth/actions/signup.server.ts';",
|
|
321
|
+
"import { cardClass, cardHeaderClass, cardTitleClass, cardDescriptionClass, cardContentClass, cardFooterClass } from '#components/ui/card.ts';",
|
|
322
|
+
"import { buttonClass } from '#components/ui/button.ts';",
|
|
323
|
+
"import { inputClass } from '#components/ui/input.ts';",
|
|
324
|
+
"import { labelClass } from '#components/ui/label.ts';",
|
|
333
325
|
"",
|
|
334
326
|
"export const metadata = { title: 'Sign up' };",
|
|
335
327
|
"",
|
|
@@ -397,7 +389,7 @@ export async function writeSaasFiles(appDir) {
|
|
|
397
389
|
// app/dashboard/middleware.ts
|
|
398
390
|
await mkdir(join(appDir, 'app', 'dashboard', 'settings'), { recursive: true });
|
|
399
391
|
await writeFile(join(appDir, 'app', 'dashboard', 'middleware.ts'), [
|
|
400
|
-
"import { auth } from '
|
|
392
|
+
"import { auth } from '#lib/auth.server.ts';",
|
|
401
393
|
"",
|
|
402
394
|
"export default async function requireAuth(req: Request, next: () => Promise<Response>) {",
|
|
403
395
|
" const session = await auth();",
|
|
@@ -412,10 +404,10 @@ export async function writeSaasFiles(appDir) {
|
|
|
412
404
|
// app/dashboard/page.ts
|
|
413
405
|
await writeFile(join(appDir, 'app', 'dashboard', 'page.ts'), [
|
|
414
406
|
"import { html } from '@webjsdev/core';",
|
|
415
|
-
"import { currentUser } from '
|
|
416
|
-
"import { cardClass, cardHeaderClass, cardTitleClass, cardDescriptionClass, cardContentClass } from '
|
|
417
|
-
"import { buttonClass } from '
|
|
418
|
-
"import { badgeClass } from '
|
|
407
|
+
"import { currentUser } from '#modules/auth/queries/current-user.server.ts';",
|
|
408
|
+
"import { cardClass, cardHeaderClass, cardTitleClass, cardDescriptionClass, cardContentClass } from '#components/ui/card.ts';",
|
|
409
|
+
"import { buttonClass } from '#components/ui/button.ts';",
|
|
410
|
+
"import { badgeClass } from '#components/ui/badge.ts';",
|
|
419
411
|
"",
|
|
420
412
|
"export const metadata = { title: 'Dashboard' };",
|
|
421
413
|
"",
|
|
@@ -443,8 +435,8 @@ export async function writeSaasFiles(appDir) {
|
|
|
443
435
|
// app/dashboard/settings/page.ts
|
|
444
436
|
await writeFile(join(appDir, 'app', 'dashboard', 'settings', 'page.ts'), [
|
|
445
437
|
"import { html } from '@webjsdev/core';",
|
|
446
|
-
"import { currentUser } from '
|
|
447
|
-
"import { cardClass, cardHeaderClass, cardTitleClass, cardDescriptionClass, cardContentClass } from '
|
|
438
|
+
"import { currentUser } from '#modules/auth/queries/current-user.server.ts';",
|
|
439
|
+
"import { cardClass, cardHeaderClass, cardTitleClass, cardDescriptionClass, cardContentClass } from '#components/ui/card.ts';",
|
|
448
440
|
"",
|
|
449
441
|
"export const metadata = { title: 'Settings' };",
|
|
450
442
|
"",
|
package/package.json
CHANGED
|
@@ -7,10 +7,10 @@ cover what you need, the full hosted docs are at **https://docs.webjs.com**.
|
|
|
7
7
|
|
|
8
8
|
## Persistence + scaffold rules (non-negotiable)
|
|
9
9
|
|
|
10
|
-
- **Use
|
|
11
|
-
(`
|
|
10
|
+
- **Use Drizzle + SQLite for data, never JSON files.** It is already wired up
|
|
11
|
+
(`db/schema.server.ts`, `db/connection.server.ts`, `npm run db:generate` + `npm run db:migrate`). For
|
|
12
12
|
ANY data the app stores (todos, posts, messages, products, comments), define
|
|
13
|
-
a
|
|
13
|
+
a Drizzle table. NEVER create `data/*.json`, `db.json`, or any JSON file as a
|
|
14
14
|
fake database. NEVER use module-scope arrays / Maps as a substitute. NEVER
|
|
15
15
|
use localStorage for app data. These are project conventions in
|
|
16
16
|
CONVENTIONS.md (a JSON file used as a database resets on reload and
|
|
@@ -131,12 +131,12 @@ self-review loop.
|
|
|
131
131
|
- Custom-element tag names are passed to `.register('tag-name')`. They are NOT
|
|
132
132
|
a static field on the class.
|
|
133
133
|
- One function per server action file (`*.server.ts`).
|
|
134
|
-
- Server-only code (
|
|
134
|
+
- Server-only code (a DB driver like `better-sqlite3`/`pg`, `node:*`, anything that needs Node APIs)
|
|
135
135
|
goes only in `.server.{js,ts}` files, `route.ts` handlers, or
|
|
136
136
|
`middleware.ts`. Never in pages, layouts, or components. Wrap the access in
|
|
137
137
|
a `.server.{js,ts}` file; the framework rewrites that import into an RPC
|
|
138
138
|
stub for the browser. `lib/` holds both server-only infra
|
|
139
|
-
(`
|
|
139
|
+
(the DB in `db/*.server.ts`) and browser-safe utilities (`lib/utils/cn.ts` with
|
|
140
140
|
`cn`); follow the same rule per file.
|
|
141
141
|
- Directives are deliberately minimal: only `unsafeHTML`, `live`, and `repeat`
|
|
142
142
|
ship. Use plain template-literal expressions
|
package/templates/.env.example
CHANGED
|
@@ -23,5 +23,5 @@ AUTH_SECRET=
|
|
|
23
23
|
# REDIS_URL=redis://localhost:6379
|
|
24
24
|
|
|
25
25
|
# ── Database ────────────────────────────────────────────────────────
|
|
26
|
-
# Used by
|
|
27
|
-
DATABASE_URL=file:./dev.db
|
|
26
|
+
# Used by Drizzle. SQLite for dev, PostgreSQL for production (--db postgres).
|
|
27
|
+
DATABASE_URL=file:./db/dev.db
|
|
@@ -7,10 +7,10 @@ the full hosted docs are at **https://docs.webjs.com**.
|
|
|
7
7
|
|
|
8
8
|
## Persistence + scaffold rules (non-negotiable)
|
|
9
9
|
|
|
10
|
-
- **Use
|
|
11
|
-
(`
|
|
10
|
+
- **Use Drizzle + SQLite for data, never JSON files.** It's already wired up
|
|
11
|
+
(`db/schema.server.ts`, `db/connection.server.ts`, `npm run db:generate` + `npm run db:migrate`). For ANY
|
|
12
12
|
data the app stores (todos, posts, messages, products, comments…),
|
|
13
|
-
define a
|
|
13
|
+
define a Drizzle table. NEVER create `data/*.json`, `db.json`, or any
|
|
14
14
|
JSON file as a fake database. NEVER use module-scope arrays / Maps as
|
|
15
15
|
a substitute. NEVER use localStorage for app data. It resets on reload and cannot scale. This is a project convention
|
|
16
16
|
(CONVENTIONS.md).
|
|
@@ -104,7 +104,7 @@ each change must include.
|
|
|
104
104
|
- Components: extend WebComponent, declare `static properties` (and `static styles` for shadow-DOM components), call `Class.register('tag-name')` at the bottom of the file. The tag name is the argument to `.register()`, not a static field.
|
|
105
105
|
- Async data in a component: prefer an `async render()` (`const u = await getUser(this.uid)`), which SSR awaits so the data is in the first paint, over prop-drilling from the page or fetching in `connectedCallback`. `renderFallback()` is the optional re-fetch loading state (never first paint); error isolation is automatic (`renderError()` customizes it); a `Task` is for genuinely client-only data.
|
|
106
106
|
- Server actions: *.server.ts files with one exported async function each.
|
|
107
|
-
- Server-only code (
|
|
107
|
+
- Server-only code (a DB driver like better-sqlite3/pg, node:*, anything needing Node APIs) goes only in .server.{js,ts} files, route.ts handlers, or middleware.ts. Never in pages, layouts, or components. Wrap in a .server.{js,ts} file; the framework rewrites that import to an RPC stub for the browser. The DB lives in db/*.server.ts; lib/ holds other server-only infra and browser-safe utilities (lib/utils/cn.ts with cn); apply the same rule per file.
|
|
108
108
|
- Directives: webjs ships only `unsafeHTML`, `live`, and `repeat`. Lit's `classMap` / `styleMap` / `ref` / `when` / `choose` / `guard` are NOT exported. Use plain template-literal expressions and lifecycle hooks instead.
|
|
109
109
|
- Context: import { createContext, ContextProvider, ContextConsumer } from '@webjsdev/core/context'
|
|
110
110
|
- Task: import { Task, TaskStatus } from '@webjsdev/core/task'
|
|
@@ -45,9 +45,8 @@ jobs:
|
|
|
45
45
|
node-version: '24'
|
|
46
46
|
cache: npm
|
|
47
47
|
- run: npm ci
|
|
48
|
-
-
|
|
49
|
-
|
|
50
|
-
run: npx prisma migrate deploy
|
|
48
|
+
- name: Set up the database (generate + apply migrations)
|
|
49
|
+
run: npm run db:generate && npm run db:migrate
|
|
51
50
|
env:
|
|
52
51
|
DATABASE_URL: file:./ci.db
|
|
53
52
|
# --server keeps this job to node:test (the browser layer is its own
|
|
@@ -66,7 +65,6 @@ jobs:
|
|
|
66
65
|
node-version: '24'
|
|
67
66
|
cache: npm
|
|
68
67
|
- run: npm ci
|
|
69
|
-
- run: npx prisma generate
|
|
70
68
|
- name: Install Playwright Chromium
|
|
71
69
|
run: npx playwright install --with-deps chromium
|
|
72
70
|
- run: npm run test:browser
|
|
@@ -81,9 +79,8 @@ jobs:
|
|
|
81
79
|
node-version: '24'
|
|
82
80
|
cache: npm
|
|
83
81
|
- run: npm ci
|
|
84
|
-
-
|
|
85
|
-
|
|
86
|
-
run: npx prisma migrate deploy
|
|
82
|
+
- name: Set up the database (generate + apply migrations)
|
|
83
|
+
run: npm run db:generate && npm run db:migrate
|
|
87
84
|
env:
|
|
88
85
|
DATABASE_URL: file:./ci.db
|
|
89
86
|
# The scaffold's e2e test (test/hello/e2e/) drives a real browser
|