@webjsdev/cli 0.10.12 → 0.10.14

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.
@@ -0,0 +1,168 @@
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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webjsdev/cli",
3
- "version": "0.10.12",
3
+ "version": "0.10.14",
4
4
  "type": "module",
5
5
  "description": "webjs CLI - dev, start, create, db",
6
6
  "bin": {
@@ -10,10 +10,10 @@
10
10
  "bin",
11
11
  "lib",
12
12
  "templates",
13
- "README.md",
14
- "resources"
13
+ "README.md"
15
14
  ],
16
15
  "dependencies": {
16
+ "@webjsdev/mcp": "^0.1.0",
17
17
  "@webjsdev/server": "^0.8.0",
18
18
  "@webjsdev/ui": "^0.3.1"
19
19
  },
@@ -36,9 +36,5 @@
36
36
  ],
37
37
  "engines": {
38
38
  "node": ">=24.0.0"
39
- },
40
- "scripts": {
41
- "prepack": "node scripts/copy-mcp-resources.js",
42
- "postpack": "node scripts/clean-mcp-resources.js"
43
39
  }
44
40
  }
@@ -8,7 +8,7 @@
8
8
  "webjs": {
9
9
  "type": "stdio",
10
10
  "command": "npx",
11
- "args": ["@webjsdev/cli", "mcp"]
11
+ "args": ["@webjsdev/mcp"]
12
12
  }
13
13
  }
14
14
  }
@@ -89,14 +89,60 @@ node_modules/@webjsdev/
89
89
  src/actions.js ← .server.ts scanner, RPC, expose()
90
90
  src/auth.js, session.js, cache.js, rate-limit.js, csrf.js
91
91
  cli/ webjs CLI (dev / start / build / test / check / create / db)
92
- ts-plugin/ tsserver plugin: go-to-definition + diagnostic suppression
92
+ intellisense/ tsserver plugin: go-to-definition + diagnostic suppression
93
93
  + attribute auto-complete for Class.register('tag') elements
94
94
  ```
95
95
 
96
96
  Reaching straight for the source is the fastest way to resolve "why
97
97
  doesn't X work?" with no documentation guesswork and no stale blog posts.
98
98
 
99
- ## Editor TS plugin: `@webjsdev/ts-plugin`
99
+ ## Use the webjs MCP server (introspection + framework knowledge)
100
+
101
+ This project ships a **read-only Model Context Protocol server** that gives
102
+ you (the AI agent) live, version-accurate facts about THIS app and the
103
+ framework. Prefer it over guessing or recalling webjs from training data,
104
+ which drifts. It mutates nothing.
105
+
106
+ **It is already available, no install needed:** the webjs CLI (a project
107
+ dependency) has it built in as `webjs mcp`. It is an MCP STDIO server (JSON-RPC
108
+ over stdout), so you do not run it in a terminal and read its output. Your MCP
109
+ host (Claude Code, Cursor, etc.) launches it and surfaces its tools, then you
110
+ invoke those tools through the MCP protocol.
111
+
112
+ Claude Code is pre-wired (see `.claude.json`). For another host, register the
113
+ server by pointing it at the CLI (or the equivalent standalone package):
114
+
115
+ ```jsonc
116
+ // Cursor: .cursor/mcp.json (or your host's MCP config)
117
+ { "mcpServers": { "webjs": {
118
+ "command": "npx", "args": ["@webjsdev/cli", "mcp"] // the built-in CLI route
119
+ // equivalent: "command": "npx", "args": ["@webjsdev/mcp"]
120
+ } } }
121
+ ```
122
+
123
+ What it serves:
124
+
125
+ - **Introspection of this app** (read-only, no module load, no DB side
126
+ effects): `list_routes` (the route table), `list_actions` (server actions
127
+ with their `/__webjs/action/<hash>/<fn>` RPC endpoints), `list_components`
128
+ (registered custom-element tags), `check` (the structured `webjs check`
129
+ violations). Use these to learn the real route/action/component surface
130
+ before editing, instead of grepping or assuming.
131
+ - **Framework knowledge**: an `init` primer (the read-first mental model +
132
+ invariants), a `docs` tool (retrieve a topic or search the `agent-docs`
133
+ corpus), MCP `resources` (the docs corpus + this AGENTS.md), recipe
134
+ `prompts` (guided page/route/action/component workflows), and a `source`
135
+ tool that reads the framework's OWN no-build source from
136
+ `node_modules/@webjsdev/*/src` (what actually runs).
137
+
138
+ You have TWO complementary ways to understand the framework, use whichever
139
+ helps (or both): (1) **grep the full framework source** under
140
+ `node_modules/@webjsdev/*/src`, which is the real no-build code that runs (no
141
+ sourcemaps, no guessing), and (2) **the MCP** for live app introspection plus
142
+ the curated `init` / `docs` / `source` knowledge tools. Reach for either before
143
+ guessing from training data or asking the user.
144
+
145
+ ## Editor TS plugin: `@webjsdev/intellisense`
100
146
 
101
147
  This scaffold's `tsconfig.json` lists a single tsserver plugin. It is
102
148
  editor-only, not required for the framework to run.
@@ -104,22 +150,24 @@ editor-only, not required for the framework to run.
104
150
  ```jsonc
105
151
  // tsconfig.json (already wired by the scaffold)
106
152
  "plugins": [
107
- { "name": "@webjsdev/ts-plugin" }
153
+ { "name": "@webjsdev/intellisense" }
108
154
  ]
109
155
  ```
110
156
 
111
- `@webjsdev/ts-plugin` bundles `ts-lit-plugin` internally (it's a runtime
112
- dependency of the plugin) and loads it programmatically, so users
113
- list one entry, not two. You get the full stack of template-literal
114
- intelligence (type-checking, diagnostics, go-to-def inside
115
- `` html`…` `` and `` css`…` `` templates) **plus** webjs-aware behaviour
116
- layered on top:
157
+ `@webjsdev/intellisense` is **standalone** (no Lit dependency): one plugin
158
+ entry, its own template parser. Inside `` html`…` `` templates you get:
159
+
160
+ - Go-to-definition on custom-element tags, attribute / property / event
161
+ names, and CSS classes in `class="…"`.
162
+ - Binding-aware completions: reachable tag names after `<`, and
163
+ prefix-keyed attributes (`.prop` property names, `?bool` / plain
164
+ hyphenated attribute names).
165
+ - Diagnostics: value type-checks against `declare propName: T`, unquoted
166
+ `@`/`.`/`?` bindings, and expressionless `.prop` bindings.
167
+ - Hover showing the component class / declared member type.
117
168
 
118
- - "Unknown tag/attribute" diagnostics are silenced for elements
119
- registered via `Class.register('tag-name')`.
120
- - Attribute auto-complete sourced from each component's
121
- `static properties`.
122
- - Attribute-value type-check against `declare propName: T` annotations.
169
+ In VS Code / Cursor / Windsurf, the **`webjs` extension** bundles this
170
+ automatically (no `tsconfig.json` edit, no separate Lit extension).
123
171
 
124
172
  See [docs.webjs.com → Editor setup](https://docs.webjs.com/docs/editor-setup)
125
173
  for the full walkthrough.
@@ -472,9 +520,10 @@ Production then has no importmap.json and the server falls back to
472
520
  calling api.jspm.io on every cold start. The `**/` prefix matters too:
473
521
  it ignores `.webjs/` at any depth, so an app nested below its repo root
474
522
  (a monorepo package) does not leak its generated `.webjs/routes.d.ts`
475
- into `git status`. The `gitignore-vendor-not-ignored` lint rule
476
- (`webjs check`) verifies the pattern with `git check-ignore` and will
477
- fail CI if it regresses.
523
+ into `git status`. The `vendor-gitignore` check (`webjs doctor`)
524
+ verifies the pattern with `git check-ignore` and warns if it regresses
525
+ (it is a project-config / setup concern, not a source-code-correctness
526
+ CI gate).
478
527
 
479
528
  ## Imports
480
529
 
@@ -66,7 +66,10 @@ even if the user doesn't explicitly ask.**
66
66
  Run `npm run doctor` (which runs `webjs doctor`) once after cloning to assert
67
67
  the project is set up correctly: the Node major (the strip-types floor), the
68
68
  tsconfig `erasableSyntaxOnly` flag, `.env` drift vs `.env.example`, vendor-pin
69
- freshness, `@webjsdev/*` version coherence, and the git pre-commit hook. It
69
+ freshness, the `.gitignore` keeping `.webjs/vendor/` committable
70
+ (`vendor-gitignore`), importmap-coherence (the resolved client deps agree on a
71
+ shared transitive version), `@webjsdev/*` version coherence, and the git
72
+ pre-commit hook. It
70
73
  prints `[pass]` / `[warn]` / `[fail]` per check with an actionable fix line and
71
74
  exits non-zero only on a hard fail (a broken toolchain), so a green run means
72
75
  `npm run dev` will boot. It is a local onboarding/setup-verify tool, not a CI
@@ -348,7 +351,7 @@ variables control infrastructure (no config files needed):
348
351
  | `AUTH_SECRET` | Required for auth JWT signing (32+ random chars) |
349
352
  | `AUTH_GOOGLE_ID` | Google OAuth client ID (optional) |
350
353
  | `AUTH_GITHUB_ID` | GitHub OAuth client ID (optional) |
351
- | `PORT` | Server port (default: 8080) |
354
+ | `PORT` | Server port. Precedence: `--port` flag > `PORT` (a real exported env var or a `PORT` in `.env`) > 8080. |
352
355
  | `WEBJS_PUBLIC_*` | Any env var starting with this prefix is exposed to the browser as `process.env.WEBJS_PUBLIC_X`. Components can read it directly. No build step, no transform. Use for API base URLs, Stripe publishable keys, analytics IDs, anything that is intended to be visible client-side. |
353
356
 
354
357
  **Server-only by default.** Any env var without the `WEBJS_PUBLIC_` prefix never reaches the browser. Reading `process.env.DATABASE_URL` from a component returns `undefined`, the same as a typo. The prefix is fail-closed: secrets cannot accidentally leak.
@@ -654,11 +657,11 @@ attribute coercion, reflection). `declare` types the field for
654
657
  TypeScript without emitting a class-field initializer that would
655
658
  clobber the reactive accessor at construction time. The two
656
659
  declarations together give you full intelligence in any tsserver-backed
657
- editor. See the Editor Setup docs for the `ts-lit-plugin` +
658
- `@webjsdev/ts-plugin` setup that extends this to tag / attribute
659
- intelligence inside `html\`…\`` templates (go-to-definition, attribute
660
- auto-complete from `static properties`, no "Unknown tag" red-squiggle on
661
- registered webjs elements).
660
+ editor. See the Editor Setup docs for the standalone `@webjsdev/intellisense`
661
+ (no Lit dependency) that extends this to tag / attribute intelligence
662
+ inside `html\`…\`` templates (go-to-definition, binding-aware completions,
663
+ value/binding diagnostics, hover); in VS Code / Cursor / Windsurf the
664
+ `webjs` extension bundles it automatically.
662
665
 
663
666
  **Rules:**
664
667
  - One component per file
package/lib/check-json.js DELETED
@@ -1,47 +0,0 @@
1
- /**
2
- * Shared JSON projector for `webjs check` violations (#262).
3
- *
4
- * `webjs check --json` and the `webjs mcp` server's `check` tool BOTH return
5
- * the identical shape, so the projection lives here once. The input is the raw
6
- * `Violation[]` from `checkConventions(appDir)` (each `{ rule, file, message,
7
- * fix }`); the output adds a `summary` count plus a per-rule breakdown so an
8
- * agent consuming the structured output never has to regex-scrape stdout.
9
- *
10
- * Pure and side-effect-free: it neither reads files nor prints. The caller owns
11
- * running `checkConventions` and (for the CLI) the non-zero exit when there are
12
- * violations.
13
- *
14
- * @module check-json
15
- */
16
-
17
- /**
18
- * @typedef {{ rule: string, file: string, message: string, fix: string }} Violation
19
- */
20
-
21
- /**
22
- * @typedef {{
23
- * violations: Violation[],
24
- * summary: { count: number, byRule: Record<string, number> },
25
- * }} CheckReport
26
- */
27
-
28
- /**
29
- * Project a raw `Violation[]` into the structured `{ violations, summary }`
30
- * report shared by `check --json` and the MCP `check` tool. `violations` is
31
- * passed through verbatim (the `{ rule, file, message, fix }` shape), and
32
- * `summary.byRule` tallies how many violations each rule produced.
33
- *
34
- * @param {Violation[]} violations
35
- * @returns {CheckReport}
36
- */
37
- export function projectCheck(violations) {
38
- /** @type {Record<string, number>} */
39
- const byRule = {};
40
- for (const v of violations) {
41
- byRule[v.rule] = (byRule[v.rule] || 0) + 1;
42
- }
43
- return {
44
- violations,
45
- summary: { count: violations.length, byRule },
46
- };
47
- }