@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/prisma-preflight.js
DELETED
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Prisma-client preflight for `webjs dev` (#452).
|
|
3
|
-
*
|
|
4
|
-
* The scaffold's `dev` npm script is `webjs dev`, and `npm run dev` runs the
|
|
5
|
-
* `predev` hook (`prisma generate`) FIRST. Invoking the `webjs dev` binary
|
|
6
|
-
* directly (easy to do, and tempting for an AI/CLI) skips `predev`, so the dev
|
|
7
|
-
* server boots against an ungenerated `@prisma/client` and crashes with a raw
|
|
8
|
-
* "did not initialize yet" error and no hint that the canonical command is
|
|
9
|
-
* `npm run dev`. This turns that crash into a one-line, actionable message.
|
|
10
|
-
*
|
|
11
|
-
* Scope is deliberately narrow: it only fires for an app that actually uses
|
|
12
|
-
* Prisma (a `prisma/schema.prisma` OR an `@prisma/client` dependency), and it
|
|
13
|
-
* only HINTS. It never auto-runs an arbitrary `predev` script and never shells
|
|
14
|
-
* out to `prisma generate` on its own, keeping the no-build promise intact.
|
|
15
|
-
*
|
|
16
|
-
* Detection (verified against a real Prisma 6 install): the GENERATED
|
|
17
|
-
* `.prisma/client` target is resolved through standard Node resolution from the
|
|
18
|
-
* app (so a hoisted monorepo, where the client lives at a PARENT `node_modules`,
|
|
19
|
-
* resolves correctly), then read. An ABSENT target, or a present-but-stub target
|
|
20
|
-
* (the ungenerated client whose `PrismaClient` constructor throws the init
|
|
21
|
-
* error), is "ungenerated". A real generated target older than the schema is
|
|
22
|
-
* "stale". We do NOT grep the static `@prisma/client` re-export shim: it is
|
|
23
|
-
* present in both states and never carries the init-error string itself.
|
|
24
|
-
*/
|
|
25
|
-
import { existsSync, statSync, readFileSync } from 'node:fs';
|
|
26
|
-
import { join, dirname } from 'node:path';
|
|
27
|
-
import { createRequire } from 'node:module';
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Does this app use Prisma? True if a schema is checked in OR `@prisma/client`
|
|
31
|
-
* is a declared dependency. Either alone is enough; a non-Prisma app has
|
|
32
|
-
* neither and gets no warning.
|
|
33
|
-
*
|
|
34
|
-
* @param {string} cwd
|
|
35
|
-
* @returns {boolean}
|
|
36
|
-
*/
|
|
37
|
-
export function usesPrisma(cwd) {
|
|
38
|
-
if (existsSync(join(cwd, 'prisma', 'schema.prisma'))) return true;
|
|
39
|
-
try {
|
|
40
|
-
const pkg = JSON.parse(readFileSync(join(cwd, 'package.json'), 'utf8'));
|
|
41
|
-
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
42
|
-
return Boolean(deps && deps['@prisma/client']);
|
|
43
|
-
} catch {
|
|
44
|
-
return false;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Marker the ungenerated `prisma-client-js` stub embeds in its generated target
|
|
49
|
-
// (`node_modules/.prisma/client/index.js`). Verified against a real Prisma 6
|
|
50
|
-
// install: after `npm i @prisma/client` but before `prisma generate`, the
|
|
51
|
-
// generated `.prisma/client` entry IS present but its `PrismaClient` constructor
|
|
52
|
-
// throws `@prisma/client did not initialize yet. Please run "prisma generate"`.
|
|
53
|
-
// A real `prisma generate` replaces that stub with the generated client, which
|
|
54
|
-
// does NOT contain this string. So the marker, read from the GENERATED target
|
|
55
|
-
// (not the static `@prisma/client` shim), is the reliable ungenerated signal.
|
|
56
|
-
const UNGENERATED_MARKER = 'did not initialize yet';
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Resolve the GENERATED Prisma client entry (`.prisma/client/index.js`) for an
|
|
60
|
-
* app, following standard Node resolution so a hoisted monorepo layout (the
|
|
61
|
-
* generated client at a PARENT `node_modules`, the app under `apps/<x>`) still
|
|
62
|
-
* resolves. Returns a discriminated result so the caller can tell the three
|
|
63
|
-
* cases apart:
|
|
64
|
-
* - `{ kind: 'unresolved' }` - `@prisma/client` itself is not resolvable.
|
|
65
|
-
* - `{ kind: 'no-target' }` - the package resolves but `.prisma/client`
|
|
66
|
-
* does not (a custom `output`, ambiguous).
|
|
67
|
-
* - `{ kind: 'target', path }` - the generated target resolves.
|
|
68
|
-
*
|
|
69
|
-
* @param {string} cwd
|
|
70
|
-
* @returns {{ kind: 'unresolved' } | { kind: 'no-target' } | { kind: 'target', path: string }}
|
|
71
|
-
*/
|
|
72
|
-
function resolveGeneratedClient(cwd) {
|
|
73
|
-
let clientDir;
|
|
74
|
-
try {
|
|
75
|
-
// Resolve @prisma/client AS THE APP would (hoisting-aware), then locate its
|
|
76
|
-
// package dir. The shim itself loads `.prisma/client/default` relative to
|
|
77
|
-
// here, so resolving from this dir follows the same (possibly hoisted) path.
|
|
78
|
-
const appRequire = createRequire(join(cwd, 'noop.js'));
|
|
79
|
-
clientDir = dirname(appRequire.resolve('@prisma/client'));
|
|
80
|
-
} catch {
|
|
81
|
-
return { kind: 'unresolved' };
|
|
82
|
-
}
|
|
83
|
-
const shimRequire = createRequire(join(clientDir, 'noop.js'));
|
|
84
|
-
for (const entry of ['.prisma/client/index.js', '.prisma/client/default.js']) {
|
|
85
|
-
try {
|
|
86
|
-
return { kind: 'target', path: shimRequire.resolve(entry) };
|
|
87
|
-
} catch { /* try the next entry */ }
|
|
88
|
-
}
|
|
89
|
-
return { kind: 'no-target' };
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Inspect the generated Prisma client state for a Prisma app.
|
|
94
|
-
*
|
|
95
|
-
* Returns one of:
|
|
96
|
-
* - `{ status: 'ok' }` - client generated and not older than the schema.
|
|
97
|
-
* - `{ status: 'missing' }` - schema/dep present but no generated client.
|
|
98
|
-
* - `{ status: 'stale' }` - client exists but the schema is newer than it.
|
|
99
|
-
*
|
|
100
|
-
* Detection resolves the GENERATED `.prisma/client` target through standard Node
|
|
101
|
-
* resolution (so hoisted monorepos are handled) and reads it: an absent target,
|
|
102
|
-
* or a present-but-stub target (the ungenerated `PrismaClient` that throws on
|
|
103
|
-
* construction), is `missing`. A real generated client that is older than the
|
|
104
|
-
* schema is `stale`. A custom-`output` generator whose target Node cannot
|
|
105
|
-
* resolve falls back to `ok` rather than nag a working app (false positives are
|
|
106
|
-
* worse than a missed hint here).
|
|
107
|
-
*
|
|
108
|
-
* @param {string} cwd
|
|
109
|
-
* @returns {{ status: 'ok' | 'missing' | 'stale' }}
|
|
110
|
-
*/
|
|
111
|
-
export function prismaClientState(cwd) {
|
|
112
|
-
const resolved = resolveGeneratedClient(cwd);
|
|
113
|
-
|
|
114
|
-
// @prisma/client not resolvable: the app declared the dep (usesPrisma gated
|
|
115
|
-
// us here) but it is not installed/generated. That is the boot-crash case.
|
|
116
|
-
if (resolved.kind === 'unresolved') return { status: 'missing' };
|
|
117
|
-
|
|
118
|
-
// The package resolves but the default `.prisma/client` target does not: a
|
|
119
|
-
// custom `output` whose location we cannot cheaply verify. Fall back to `ok`
|
|
120
|
-
// rather than nag a working app (false positives are worse than a missed hint).
|
|
121
|
-
if (resolved.kind === 'no-target') return { status: 'ok' };
|
|
122
|
-
|
|
123
|
-
const generatedIndex = resolved.path;
|
|
124
|
-
|
|
125
|
-
// The generated target exists. Is it still the ungenerated stub (its
|
|
126
|
-
// PrismaClient constructor throws the init error)?
|
|
127
|
-
try {
|
|
128
|
-
const body = readFileSync(generatedIndex, 'utf8');
|
|
129
|
-
if (body.includes(UNGENERATED_MARKER)) return { status: 'missing' };
|
|
130
|
-
} catch { /* unreadable: fall through to the stale check, then ok */ }
|
|
131
|
-
|
|
132
|
-
// Generated for real. Is it older than the schema (a stale client)?
|
|
133
|
-
const schema = join(cwd, 'prisma', 'schema.prisma');
|
|
134
|
-
try {
|
|
135
|
-
if (existsSync(schema)) {
|
|
136
|
-
const schemaMtime = statSync(schema).mtimeMs;
|
|
137
|
-
const clientMtime = statSync(generatedIndex).mtimeMs;
|
|
138
|
-
if (schemaMtime > clientMtime) return { status: 'stale' };
|
|
139
|
-
}
|
|
140
|
-
} catch { /* if we can't stat, treat as ok */ }
|
|
141
|
-
|
|
142
|
-
return { status: 'ok' };
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Build the actionable hint for an ungenerated/stale client, or `null` when the
|
|
147
|
-
* app is fine or does not use Prisma. The caller prints it (a warning, not a
|
|
148
|
-
* hard exit) before booting the dev server.
|
|
149
|
-
*
|
|
150
|
-
* @param {string} cwd
|
|
151
|
-
* @returns {string | null}
|
|
152
|
-
*/
|
|
153
|
-
export function prismaDevHint(cwd) {
|
|
154
|
-
if (!usesPrisma(cwd)) return null;
|
|
155
|
-
const { status } = prismaClientState(cwd);
|
|
156
|
-
if (status === 'ok') return null;
|
|
157
|
-
|
|
158
|
-
const reason =
|
|
159
|
-
status === 'stale'
|
|
160
|
-
? 'Your Prisma client looks stale (the schema changed since it was generated).'
|
|
161
|
-
: 'Your Prisma client is not generated yet.';
|
|
162
|
-
return (
|
|
163
|
-
`webjs: ${reason}\n` +
|
|
164
|
-
` The dev server will crash on an ungenerated client. Fix it with either:\n` +
|
|
165
|
-
` npm run dev # canonical: runs \`prisma generate\` (predev) first\n` +
|
|
166
|
-
` webjs db generate # just regenerate the client, then re-run\n`
|
|
167
|
-
);
|
|
168
|
-
}
|