@jay-framework/jay-stack-cli 0.24.2 → 0.24.3

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.
@@ -96,15 +96,15 @@ These APIs are also exposed via the editor protocol (Socket.IO) for design board
96
96
  // Server emits: { type: 'routeParamsBatch', route: '...', params: [], hasMore: false }
97
97
  ```
98
98
 
99
- ### Freeze Changed Event
99
+ ### Frozen Page Refresh (Dev)
100
100
 
101
- The `freezeChanged` socket event is emitted when jay-html or CSS files change. Design board applications should listen for this to refresh their frozen views:
101
+ In development, **full-page** frozen views (`?_jay_freeze=<id>` in iframe or tab) self-reload when jay-html or CSS changes — same `jay:page-reload` Hot Module Replacement (HMR) path as live pages. No host application wiring required. Saved ViewState is preserved across reload.
102
102
 
103
- ```typescript
104
- socket.on('freezeChanged', () => {
105
- // Re-fetch frozen page fragments
106
- });
107
- ```
103
+ Fragment format (`format=fragment` for shadow DOM embedders) does **not** auto-reload; those hosts must re-fetch the fragment.
104
+
105
+ ### Freeze Changed Event (Fragment Hosts)
106
+
107
+ The `freezeChanged` socket event was described for design board fragment refresh but is **not** emitted by the current dev server. Fragment embedders should re-fetch on file changes themselves, or wait for a future fetch-and-swap protocol. Dev full-page frozen views do not need this event.
108
108
 
109
109
  ## Iframe / Embed Mode
110
110
 
@@ -87,7 +87,7 @@ The script body is YAML. Values are passed to the component as props alongside r
87
87
  </script>
88
88
  ```
89
89
 
90
- > **Note:** `<script type="application/jay-params">` is deprecated. Move param values into the headless component's script tag body.
90
+ > **Note:** `<script type="application/jay-params">` is no longer supported — it is silently ignored by the route scanner and reported as an error by `jay-stack validate`. Move param values into the headless component's script tag body.
91
91
 
92
92
  ## Page Files
93
93
 
@@ -2,6 +2,16 @@
2
2
 
3
3
  Actions provide RPC-style server endpoints for client-to-server communication.
4
4
 
5
+ > **Actions are compiler-free and live on the `.` entry.** They run in production, so their handlers
6
+ > must not import the compiler (`@jay-framework/compiler-*`). A handler that needs the compiler is
7
+ > either a [CLI command](commands-guide.md) or a **`devOnly` action** (see below) — not a regular
8
+ > action. `validate-plugin`'s leak scan fails a compiler import in `dist/index.js`.
9
+ >
10
+ > **`devOnly` actions** (`actions[].devOnly: true`) — browser-callable handlers for settings/admin
11
+ > pages that may use the compiler and are **excluded from production**. Their handlers live in the
12
+ > `./tools` entry (`lib/tools.ts`), and the dev server registers them normally. See the settings-page
13
+ > pattern in [plugin-routes.md](plugin-routes.md).
14
+
5
15
  ## makeJayAction — Mutations (POST)
6
16
 
7
17
  ```typescript
@@ -75,6 +75,21 @@ commands:
75
75
  command: commands/upload-public.jay-command
76
76
  ```
77
77
 
78
+ ### 4. Export from `./tools`
79
+
80
+ Commands are the **tools** primitive — they load only from the `./tools` entry (`lib/tools.ts`), which
81
+ may use the compiler. Re-export each command handler there, and add the `./tools` export to
82
+ `package.json`:
83
+
84
+ ```typescript
85
+ // lib/tools.ts (./tools) — toolchain-only, compiler allowed
86
+ export { uploadPublic } from './commands/upload-public.js';
87
+ ```
88
+
89
+ Do **not** re-export commands from `lib/index.ts` — the serve entry must stay compiler-free. (An
90
+ operation that must be callable from a browser page is an [action](actions-guide.md), not a command;
91
+ if it also needs the compiler, mark the action `devOnly`.)
92
+
78
93
  ## `CONSOLE_CONTEXT` Service
79
94
 
80
95
  A framework-provided service with project info and a logger:
@@ -4,6 +4,13 @@ For the full contract syntax, decision tree, and examples, see the shared [Contr
4
4
 
5
5
  This file covers plugin-specific contract concerns. Contracts (`.jay-contract` files) are the source of truth for a component's data shape. Define the contract before implementing the component.
6
6
 
7
+ > **Interactive components need a `./client` export.** If a component built from this contract declares
8
+ > an interactive phase (`.withInteractive(...)`) — or the plugin declares `contexts` — the package
9
+ > must expose a `./client` export (`./dist/index.client.js`) for browser hydration. `validate-plugin`
10
+ > detects interactivity by scanning the built server bundle and errors if `./client` is missing.
11
+ > Server-only (slow/fast) component plugins need no `./client`. See
12
+ > [plugin-structure.md](plugin-structure.md).
13
+
7
14
  ## Basic Structure
8
15
 
9
16
  ```yaml
@@ -107,15 +107,15 @@ These APIs are also exposed via the editor protocol (Socket.IO) for design board
107
107
  // Server emits: { type: 'routeParamsBatch', route: '...', params: [], hasMore: false }
108
108
  ```
109
109
 
110
- ### Freeze Changed Event
110
+ ### Frozen Page Refresh (Dev)
111
111
 
112
- The `freezeChanged` socket event is emitted when jay-html or CSS files change. Design board applications should listen for this to refresh their frozen views:
112
+ In development, **full-page** frozen views (`?_jay_freeze=<id>` in iframe or tab) self-reload when jay-html or CSS changes — same `jay:page-reload` Hot Module Replacement (HMR) path as live pages. No host application wiring required. Saved ViewState is preserved across reload.
113
113
 
114
- ```typescript
115
- socket.on('freezeChanged', () => {
116
- // Re-fetch frozen page fragments
117
- });
118
- ```
114
+ Fragment format (`format=fragment` for shadow DOM embedders) does **not** auto-reload; those hosts must re-fetch the fragment.
115
+
116
+ ### Freeze Changed Event (Fragment Hosts)
117
+
118
+ The `freezeChanged` socket event was described for design board fragment refresh but is **not** emitted by the current dev server. Fragment embedders should re-fetch on file changes themselves, or wait for a future fetch-and-swap protocol. Dev full-page frozen views do not need this event.
119
119
 
120
120
  ## Iframe / Embed Mode
121
121
 
@@ -149,7 +149,7 @@ There is no enforced convention — just pick a prefix that's unique and descrip
149
149
 
150
150
  ## Dev-only routes
151
151
 
152
- Some plugin pages are **dev-server tooling** — internal dashboards, QA fixtures, builder settings UIs. Mark them with `devOnly: true` so consumers of `listRoutes()` can distinguish them from public site pages. Production builds do not yet exclude `devOnly` routes that is planned for a future framework release.
152
+ Some plugin pages are **dev-server tooling** — internal dashboards, QA fixtures, builder settings UIs. Mark them with `devOnly: true` so they are served by the dev server, distinguishable via `listRoutes()`, and **excluded from production builds** (not compiled, not in the route manifest).
153
153
 
154
154
  ```yaml
155
155
  routes:
@@ -168,7 +168,48 @@ routes:
168
168
  | `listRoutes()` / `RouteInfo` | Includes route with `devOnly: true` |
169
169
  | Page navigation UIs | **Consumer choice** — tools may filter `devOnly` routes from pickers |
170
170
  | Routes loaded by explicit path | **Unaffected** — embed/host tools pass a known route URL |
171
- | Production build | **Deferred** — future task excludes dev-only routes |
171
+ | Production build | **Excluded** — the route is not compiled or bundled |
172
+ | Component entry (compiler) | Resolved from **`./tools`** when the page uses the compiler |
173
+
174
+ ## Settings pages (devOnly route + devOnly actions)
175
+
176
+ A common pattern: a `devOnly` route whose interactive form calls plugin server handlers to run
177
+ analysis, rebuild a catalog, etc. Because a settings page runs in the browser it invokes handlers via
178
+ the **action RPC** — so these handlers must be **actions**, not CLI commands. When the handler uses the
179
+ compiler (e.g. it parses jay-html), mark the action **`devOnly: true`**: its handler then lives in
180
+ `./tools` (compiler allowed) and is excluded from production alongside the route.
181
+
182
+ ```yaml
183
+ routes:
184
+ - path: /my-plugin/settings
185
+ jayHtml: ./dist/pages/settings/page.jay-html
186
+ component: mySettingsPage
187
+ devOnly: true
188
+ actions:
189
+ - name: runAnalysis
190
+ action: run-analysis.jay-action
191
+ devOnly: true # handler in ./tools, may use the compiler, excluded from production
192
+ - name: fontFallback
193
+ action: font-fallback.jay-action # normal production action — compiler-free, stays on `.`
194
+ ```
195
+
196
+ ```typescript
197
+ // lib/tools.ts (./tools) — compiler allowed, never in the production serve bundle
198
+ export { runAnalysis } from './actions/run-analysis.js'; // uses the compiler
199
+ export { mySettingsPage } from './pages/settings/page.js';
200
+
201
+ // lib/index.ts (.) — compiler-free serve entry
202
+ export { fontFallback } from './actions/font-fallback.js';
203
+ ```
204
+
205
+ - The dev server registers **all** actions (devOnly from `./tools`, regular from `.`) and serves the
206
+ route end-to-end with the compiler present.
207
+ - Production build **excludes** the `devOnly` route (not compiled) and **skips** `devOnly` actions
208
+ (not registered/dispatchable). A compiler-using action left **without** `devOnly` would leak the
209
+ compiler into `dist/index.js` and fail the leak scan — that is the signal to mark it `devOnly` (or
210
+ reclassify it as a command).
211
+ - `devOnly` is orthogonal to compiler use: a compiler-free admin action can still be `devOnly` purely
212
+ to keep it out of production.
172
213
 
173
214
  ### Standalone access
174
215
 
@@ -2,6 +2,53 @@
2
2
 
3
3
  A plugin provides headless components, contracts, and actions. It can be a standalone npm package or inline within a project.
4
4
 
5
+ ## Capabilities & the runtime/tools split
6
+
7
+ A plugin package runs code in two very different phases, and they must **not** share one module graph:
8
+
9
+ - **Serve-time (runtime)** — headless components, production routes, **server actions**, the client
10
+ bundle, global `init`. Loaded on the production request path. **Must stay compiler-free** — the
11
+ serve bundle ships to production.
12
+ - **Tools-time** — validators, **CLI commands**, agent-kit generators, `setup` handlers, and
13
+ `devOnly` route components/actions. Run only under the Jay toolchain (`jay-stack
14
+ validate`/`agent-kit`/`setup`/`run`, dev server). These **may** use the compiler.
15
+
16
+ **Actions vs commands** — the load-bearing distinction:
17
+
18
+ - **Actions** are the _serving_ primitive: request-time handlers that run in production. They live on
19
+ the `.` entry and **must be compiler-free**.
20
+ - **CLI commands** are the _tools_ primitive: invoked under the toolchain. They live on `./tools` and
21
+ **may use the compiler**.
22
+
23
+ Rule of thumb: **if a handler needs the compiler, it is a command (or a `devOnly` action), not a
24
+ regular action.**
25
+
26
+ ### Capability → required export
27
+
28
+ Derive a plugin's required `package.json` exports from the capabilities it declares:
29
+
30
+ | Capability (plugin.yaml) | Needs `./client`? | Handler/export loaded from |
31
+ | --------------------------- | -------------------------- | ------------------------------- |
32
+ | `contracts` | only if interactive phase | `.` (+ `./<contract>` per item) |
33
+ | `dynamic_contracts` | only if interactive phase | `.` |
34
+ | `routes` | only if interactive phase | `.` (or `./tools` if `devOnly`) |
35
+ | `contexts` | ✅ always (client by def.) | `.`, `./client` |
36
+ | `actions` | ❌ | `.` (or `./tools` if `devOnly`) |
37
+ | `services`, `init` / global | ❌ | `.` |
38
+ | `validators` | ❌ | **`./tools`** |
39
+ | `commands` | ❌ | **`./tools`** |
40
+ | `agentkit` / `setup` | ❌ | **`./tools`** |
41
+
42
+ Rules `jay-stack validate-plugin` enforces:
43
+
44
+ - **≥1 capability.** A plugin declaring none is flagged (a `global: true` plugin counts iff it exports
45
+ a resolvable `init`/`setup` handler).
46
+ - **`./tools` required** iff any tools capability (`validators`, `commands`, `agentkit`, `setup`) or a
47
+ `devOnly` action is declared.
48
+ - **`./client` required** iff a provided component has an interactive phase, or `contexts` is declared.
49
+ Server-only and tools-only plugins need no `./client`.
50
+ - **Leak scan:** `dist/index.js` (the `.` entry) must contain no `@jay-framework/compiler-` import.
51
+
5
52
  ## plugin.yaml
6
53
 
7
54
  The plugin manifest declares all contracts, actions, services, contexts, and configuration:
@@ -125,6 +172,10 @@ tags:
125
172
 
126
173
  - `name` — Action name (used with `jay-stack action <plugin>/<action>`)
127
174
  - `action` — Path to `.jay-action` metadata file
175
+ - `devOnly` — (optional, boolean) When `true`, the action's handler lives in `./tools` (compiler
176
+ allowed), is served by the dev server, and is **excluded from production builds**. Use for
177
+ settings-page / admin handlers. A non-`devOnly` action's handler must be compiler-free on `.`. See
178
+ [plugin-routes.md](plugin-routes.md) for the settings-page pattern.
128
179
 
129
180
  ### Webhook Entry Fields
130
181
 
@@ -177,6 +228,9 @@ services:
177
228
  - `css` — (optional) Path to the page's CSS file
178
229
  - `component` — Path to the page component (relative to plugin root, or exported member name for NPM)
179
230
  - `description` — What this page does
231
+ - `devOnly` — (optional, boolean) When `true`, the route is dev-server tooling (e.g. a settings UI):
232
+ served by the dev server, **excluded from production builds**. A `devOnly` route whose page
233
+ component uses the compiler resolves its component from `./tools`.
180
234
 
181
235
  Plugin routes are served by the dev server alongside project routes. If a project defines the same route path, the project's page takes precedence.
182
236
 
@@ -193,7 +247,7 @@ Commands are CLI operations run via `jay-stack run`. Use `makeCliCommand()` to c
193
247
  - `handler` — Export name (NPM plugins) or relative path (local plugins) to the validator function
194
248
  - `description` — (optional) What this validator checks
195
249
 
196
- **NPM plugins:** `handler` is the export name from the package entry point (e.g., `validateMediaOptimization`). The function must be exported from `lib/index.ts`.
250
+ **NPM plugins:** `handler` is the export name from the **`./tools`** entry (e.g., `validateMediaOptimization`). The function must be exported from `lib/tools.ts` — **never re-export a validator (or any compiler-using handler) from `lib/index.ts`**, or the compiler leaks into the serve bundle.
197
251
  **Local plugins:** `handler` is a relative path to the module (e.g., `./validators/media-validator`). The module must export a `validate` function.
198
252
 
199
253
  Validators run during `jay-stack validate` against every parsed jay-html file in the project. See [validation.md](validation.md) for implementation details.
@@ -204,7 +258,7 @@ Validators run during `jay-stack validate` against every parsed jay-html file in
204
258
  - `agentkit` — Export name (NPM) or relative path (local) for `jay-stack agent-kit`. Generates discovery data: add-menu catalogs, reference files, skills, thumbnails.
205
259
  - `description` — (optional, top-level) Human-readable description of what setup validates
206
260
 
207
- **NPM plugins:** `setup` and `agentkit` are export names from the package entry point.
261
+ **NPM plugins:** `setup` and `agentkit` are export names from the **`./tools`** entry (`lib/tools.ts`) — they are tools-time handlers and may use the compiler.
208
262
  **Local plugins:** relative paths to the handler modules.
209
263
 
210
264
  `jay-stack validate-plugin` checks that declared handlers exist and are correctly exported.
@@ -263,21 +317,29 @@ my-project/
263
317
 
264
318
  See `examples/jay-stack/fake-shop` for a working example.
265
319
 
266
- ## Dual Entry Points
320
+ ## Entry Points
267
321
 
268
- Jay plugins are fullstack — they run on both server and client. The build produces two bundles:
322
+ Jay plugins run in three contexts. The build produces up to three bundles:
269
323
 
270
- - **Server** (`dist/index.js`) — actions, services, SSR rendering, `init()`. Built with `vite build --ssr`.
271
- - **Client** (`dist/index.client.js`) components for hydration, context tokens, `init()`. Built with `vite build`.
324
+ - **Server / serve** (`dist/index.js`, `.`) — actions, services, SSR rendering, `init()`. Loaded on
325
+ the production request path. **Compiler-free.** Built with `vite build --ssr`.
326
+ - **Client** (`dist/index.client.js`, `./client`) — components for hydration, context tokens,
327
+ `init()`. Built with `vite build`.
328
+ - **Tools** (`dist/tools.js`, `./tools`) — validators, commands, agent-kit/setup handlers, and any
329
+ `devOnly` route component/action. **Compiler-allowed** (toolchain-only, never in a serve bundle).
330
+ Built alongside the server bundle (`vite build --ssr`).
272
331
 
273
- Create two entry files:
332
+ Create the entry files:
274
333
 
275
- | File | Exports |
276
- | --------------------- | ---------------------------------------------------------- |
277
- | `lib/index.ts` | Actions, services, components (SSR), init, service markers |
278
- | `lib/index.client.ts` | Components (hydration), context markers, init |
334
+ | File | Exports |
335
+ | --------------------- | ------------------------------------------------------------------------------------------------------ |
336
+ | `lib/index.ts` | Actions, services, components (SSR), init, service markers — **compiler-free** |
337
+ | `lib/index.client.ts` | Components (hydration), context markers, init |
338
+ | `lib/tools.ts` | Validators, commands, agent-kit/setup handlers, `devOnly` route components + actions — **compiler OK** |
279
339
 
280
- Actions and service providers are server-only. Components appear in **both** entries.
340
+ Actions and service providers are server-only. Components appear in **both** `index.ts` and
341
+ `index.client.ts`. **`index.ts` must never import `tools.ts`** — that is what keeps the compiler out of
342
+ the serve bundle. A validator-only or tools-only plugin may have a near-empty `index.ts`.
281
343
 
282
344
  ## Build Scripts
283
345
 
@@ -289,7 +351,7 @@ Actions and service providers are server-only. Components appear in **both** ent
289
351
  "build:client": "vite build",
290
352
  "build:server": "vite build --ssr",
291
353
  "build:copy-assets": "cp lib/*.jay-contract* dist/",
292
- "build:types": "tsup lib/index.ts lib/index.client.ts --dts-only --format esm",
354
+ "build:types": "tsup lib/index.ts lib/index.client.ts lib/tools.ts --dts-only --format esm",
293
355
  "validate": "jay-stack-cli validate-plugin",
294
356
  "clean": "rimraf dist"
295
357
  }
@@ -313,7 +375,12 @@ export default defineConfig(({ isSsrBuild }) => ({
313
375
  emptyOutDir: false,
314
376
  lib: {
315
377
  entry: isSsrBuild
316
- ? { index: resolve(__dirname, 'lib/index.ts') }
378
+ ? {
379
+ index: resolve(__dirname, 'lib/index.ts'),
380
+ // Tools entry (compiler-allowed, toolchain-only). Omit if the plugin has no
381
+ // validators/commands/agentkit/setup/devOnly surfaces.
382
+ tools: resolve(__dirname, 'lib/tools.ts'),
383
+ }
317
384
  : { 'index.client': resolve(__dirname, 'lib/index.client.ts') },
318
385
  formats: ['es'],
319
386
  },
@@ -325,6 +392,9 @@ export default defineConfig(({ isSsrBuild }) => ({
325
392
  '@jay-framework/stack-server-runtime',
326
393
  '@jay-framework/reactive',
327
394
  '@jay-framework/runtime',
395
+ // Externalize the compiler namespace: any leak into `.` then shows up as a literal
396
+ // import string in dist/index.js, which validate-plugin's leak scan catches.
397
+ /^@jay-framework\/compiler-/,
328
398
  ],
329
399
  },
330
400
  },
@@ -349,6 +419,10 @@ For NPM packages, declare exports for both server and client entry points:
349
419
  "types": "./dist/index.client.d.ts",
350
420
  "default": "./dist/index.client.js"
351
421
  },
422
+ "./tools": {
423
+ "types": "./dist/tools.d.ts",
424
+ "default": "./dist/tools.js"
425
+ },
352
426
  "./plugin.yaml": "./plugin.yaml",
353
427
  "./my-contract.jay-contract": "./dist/my-contract.jay-contract"
354
428
  },
@@ -356,7 +430,19 @@ For NPM packages, declare exports for both server and client entry points:
356
430
  }
357
431
  ```
358
432
 
359
- The `./client` export is required — the framework uses it for browser-side hydration code. The `.` export handles server-side rendering and action execution.
433
+ - The `.` export handles server-side rendering and action execution — it must be **compiler-free**.
434
+ - The `./client` export is required **only** when a component has an interactive phase or the plugin
435
+ declares `contexts` (browser-side hydration / client contexts).
436
+ - The `./tools` export is required **only** when the plugin declares a tools capability (`validators`,
437
+ `commands`, `agentkit`, `setup`) or a `devOnly` action — those handlers load exclusively from
438
+ `./tools`.
439
+
440
+ ### compiler-\* dependencies
441
+
442
+ If `./tools` uses the compiler (`@jay-framework/compiler-jay-html`, `compiler-shared`), declare those
443
+ packages as **`peerDependencies`** (provided by the toolchain at tools time) plus **`devDependencies`**
444
+ (so the plugin's own build/test resolve them). Never put them in `dependencies` — that would pull the
445
+ compiler into runtime installs.
360
446
 
361
447
  ## Plugin-Contributed Agent-Kit Guides
362
448
 
@@ -23,7 +23,7 @@ agentkit: generateMyAgentKit # export name (NPM) or ./path (local) — optional
23
23
  description: Validate credentials and install config # optional, top-level
24
24
  ```
25
25
 
26
- **NPM plugins:** `setup` and `agentkit` are export names from the package entry point (`lib/index.ts`).
26
+ **NPM plugins:** `setup` and `agentkit` are export names from the **`./tools`** entry (`lib/tools.ts`). They are tools-time handlers (may use the compiler) and load only from `./tools` — do not re-export them from `lib/index.ts`, or the compiler can leak into the serve bundle.
27
27
  **Local plugins:** relative paths to handler modules (e.g. `agentkit: ./agentkit` — export `agentkit` or `default` from that module).
28
28
 
29
29
  `jay-stack validate-plugin` checks that declared handlers exist and are correctly exported.
@@ -266,13 +266,16 @@ See [aiditor-settings-guide.md](aiditor-settings-guide.md) for the full checklis
266
266
 
267
267
  ## Exporting Handlers
268
268
 
269
- For NPM plugins, export handlers from the package entry point:
269
+ For NPM plugins, export setup/agent-kit handlers from the **`./tools`** entry (they may use the
270
+ compiler and must stay out of the serve bundle):
270
271
 
271
272
  ```typescript
272
- // lib/index.ts
273
+ // lib/tools.ts (./tools) — compiler allowed, toolchain-only
273
274
  export { setupMyPlugin } from './setup.js';
274
275
  export { generateMyAgentKit } from './agentkit.js';
275
- // ... other exports (components, actions, services)
276
+
277
+ // lib/index.ts (.) — serve entry, compiler-free
278
+ // ... components, actions, services, init (NOT setup/agentkit)
276
279
  ```
277
280
 
278
281
  For local plugins, use relative paths in `plugin.yaml` and export `agentkit` or `default` from the handler module.
@@ -43,6 +43,24 @@ jay-stack validate-plugin -v
43
43
  - Component export names are valid strings (not file paths)
44
44
  - Action metadata files (`.jay-action`) exist
45
45
 
46
+ ### Capability-aware structure
47
+
48
+ `validate-plugin` derives required exports from the capabilities the plugin declares (see the
49
+ capability matrix in [plugin-structure.md](plugin-structure.md)):
50
+
51
+ - **≥1 capability** — a plugin declaring none is warned (a `global: true` plugin counts iff it exports
52
+ a resolvable `init`/`setup` handler).
53
+ - **`./tools` required** iff any tools capability (`validators`, `commands`, `agentkit`, `setup`) or a
54
+ `devOnly` action is declared — those handlers load only from `./tools`. Validator/setup/agentkit
55
+ handler names are checked against the **`./tools`** entry, not `.`.
56
+ - **`./client` required** iff a component has an interactive phase (detected by scanning the built
57
+ server `.` bundle for the interactive mark) or `contexts` is declared. Server-only and tools-only
58
+ plugins need no `./client`.
59
+ - **Leak scan** — the serve entry `dist/index.js` must contain no `@jay-framework/compiler-` import.
60
+ A compiler-using handler re-exported from `index.ts` fails this check; move it to `lib/tools.ts`.
61
+ - **`devOnly`** — `actions[].devOnly` / `routes[].devOnly` must be booleans; a `devOnly` action
62
+ requires the `./tools` export.
63
+
46
64
  ### Type Generation
47
65
 
48
66
  - Contracts compile to valid TypeScript types
@@ -133,20 +151,20 @@ Plugins can provide custom jay-html validation rules that run during `jay-stack
133
151
  ```yaml
134
152
  validators:
135
153
  - name: media-optimization
136
- handler: validateMediaOptimization # export name from package entry point
154
+ handler: validateMediaOptimization # export name from the ./tools entry
137
155
  description: Ensures media URLs use resize parameters
138
156
  ```
139
157
 
140
158
  **Handler format:**
141
159
 
142
- - **NPM plugins** — `handler` is an export name from the package entry point (e.g., `validateMediaOptimization`). The function must be exported from `lib/index.ts`.
160
+ - **NPM plugins** — `handler` is an export name from the **`./tools`** entry (e.g., `validateMediaOptimization`). The function must be exported from `lib/tools.ts`. **Never re-export a validator from `lib/index.ts`** — validators use compiler APIs (`walkElements`, `parseTemplateParts`, …), and re-exporting from the serve entry pulls the compiler into the production bundle (caught by the leak scan).
143
161
  - **Local plugins** (`src/plugins/`) — `handler` is a relative path to the module (e.g., `./validators/media-validator`). The module must export a `validate` function.
144
162
 
145
- `jay-stack validate-plugin` checks that the handler exists and is correctly exported.
163
+ `jay-stack validate-plugin` checks that the handler exists and is correctly exported from `./tools`.
146
164
 
147
165
  ### Writing a Validator
148
166
 
149
- Export the validator function from the package entry point (for NPM) or from the handler module (for local):
167
+ Export the validator function from the **`./tools`** entry (`lib/tools.ts`, for NPM) or from the handler module (for local):
150
168
 
151
169
  ```typescript
152
170
  import type { JayHtmlValidatorFn, JayHtmlValidationFinding } from '@jay-framework/compiler-shared';
@@ -8,7 +8,7 @@ import fs from "node:fs/promises";
8
8
  import { createRequire } from "node:module";
9
9
  import { runLoadParams, scanPlugins, DevSlowlyChangingPhase, slowRenderInstances } from "@jay-framework/stack-server-runtime";
10
10
  import { parseJayFile, JAY_IMPORT_RESOLVER, injectHeadfullFSTemplates, assignCoordinatesToJayHtml, discoverHeadlessInstances, generateElementHydrateFile, generateServerElementFile } from "@jay-framework/compiler-jay-html";
11
- import { checkValidationErrors, RuntimeMode } from "@jay-framework/compiler-shared";
11
+ import { normalizeActionEntry, checkValidationErrors, RuntimeMode } from "@jay-framework/compiler-shared";
12
12
  import crypto, { createHash } from "node:crypto";
13
13
  import * as productionServer from "@jay-framework/production-server";
14
14
  import { generateSitemap } from "@jay-framework/production-server";
@@ -599,16 +599,18 @@ async function discoverActions(actionPaths, serverOutputDir, buildDir, projectRo
599
599
  plugins.push({ name: plugin.manifest.name, packageName });
600
600
  const pluginActions = plugin.manifest.actions;
601
601
  if (pluginActions && pluginActions.length > 0) {
602
- actions.push({
603
- serverModule: "",
604
- packageName,
605
- isPlugin: true,
606
- actionNames: pluginActions.map(
607
- (a) => typeof a === "string" ? a : a.name
608
- )
609
- });
602
+ const productionActions = pluginActions.map(normalizeActionEntry).filter((a) => !a.devOnly);
603
+ const devOnlyCount = pluginActions.length - productionActions.length;
604
+ if (productionActions.length > 0) {
605
+ actions.push({
606
+ serverModule: "",
607
+ packageName,
608
+ isPlugin: true,
609
+ actionNames: productionActions.map((a) => a.name)
610
+ });
611
+ }
610
612
  getLogger().info(
611
- `[Build] Plugin actions from ${packageName}: ${pluginActions.length}`
613
+ `[Build] Plugin actions from ${packageName}: ${productionActions.length}` + (devOnlyCount > 0 ? ` (excluded ${devOnlyCount} devOnly)` : "")
612
614
  );
613
615
  }
614
616
  }
@@ -1256,7 +1258,13 @@ async function buildVersion(options) {
1256
1258
  clientInits
1257
1259
  };
1258
1260
  const pluginRoutes = await scanPluginRoutes(options.projectRoot, routes);
1259
- const allRoutes = [...routes, ...pluginRoutes];
1261
+ const excludedDevOnly = [...routes, ...pluginRoutes].filter((route) => route.devOnly);
1262
+ if (excludedDevOnly.length > 0) {
1263
+ logger.info(
1264
+ `[Build] Excluding ${excludedDevOnly.length} devOnly route(s) from production: ` + excludedDevOnly.map((r) => r.rawRoute).join(", ")
1265
+ );
1266
+ }
1267
+ const allRoutes = [...routes, ...pluginRoutes].filter((route) => !route.devOnly);
1260
1268
  const routeEntries = allRoutes.map((route) => {
1261
1269
  let serverModule = "";
1262
1270
  if (route.compPath) {
package/dist/index.js CHANGED
@@ -21,7 +21,7 @@ import { createRequire as createRequire$1 } from "node:module";
21
21
  import { isJayAction, isJayStreamAction, isJayCliCommand, CONSOLE_CONTEXT } from "@jay-framework/fullstack-component";
22
22
  import { Command } from "commander";
23
23
  import chalk from "chalk";
24
- import { loadPluginManifest as loadPluginManifest$1, JAY_EXTENSION, RuntimeMode as RuntimeMode$1, GenerateTarget, JAY_CONTRACT_EXTENSION, findDynamicContract } from "@jay-framework/compiler-shared";
24
+ import { loadPluginManifest as loadPluginManifest$1, normalizeActionEntry as normalizeActionEntry$1, JAY_EXTENSION, RuntimeMode as RuntimeMode$1, GenerateTarget, JAY_CONTRACT_EXTENSION, findDynamicContract } from "@jay-framework/compiler-shared";
25
25
  import { glob } from "glob";
26
26
  import { fileURLToPath } from "node:url";
27
27
  import { select, confirm, input } from "@inquirer/prompts";
@@ -393,7 +393,7 @@ function normalizeActionEntry(entry) {
393
393
  if (typeof entry === "string") {
394
394
  return { name: entry };
395
395
  }
396
- return { name: entry.name, action: entry.action };
396
+ return { name: entry.name, action: entry.action, devOnly: entry.devOnly };
397
397
  }
398
398
  function loadPluginManifest(pluginDir) {
399
399
  const pluginYamlPath = path$1.join(pluginDir, "plugin.yaml");
@@ -650,9 +650,18 @@ async function registerNpmPluginActions(packageName, pluginConfig, pluginDir, re
650
650
  } else {
651
651
  pluginModule = await import(packageName);
652
652
  }
653
+ let toolsModule;
654
+ const loadToolsModule = async () => {
655
+ if (!toolsModule) {
656
+ const toolsEntry = `${packageName}/tools`;
657
+ toolsModule = viteServer ? await viteServer.ssrLoadModule(toolsEntry) : await import(toolsEntry);
658
+ }
659
+ return toolsModule;
660
+ };
653
661
  for (const entry of pluginConfig.actions) {
654
- const { name: actionName, action: actionPath } = normalizeActionEntry(entry);
655
- const actionExport = pluginModule[actionName];
662
+ const { name: actionName, action: actionPath, devOnly } = normalizeActionEntry(entry);
663
+ const sourceModule = devOnly ? await loadToolsModule() : pluginModule;
664
+ const actionExport = sourceModule[actionName];
656
665
  if (actionExport && isJayAction(actionExport)) {
657
666
  registry.register(actionExport);
658
667
  const registeredName = actionExport.actionName;
@@ -724,6 +733,13 @@ async function discoverPluginActions(pluginPath, projectRoot, registry = actionR
724
733
  return [];
725
734
  }
726
735
  }
736
+ const resolveLocalToolsModulePath = () => {
737
+ for (const candidate of ["tools.ts", "tools.js"]) {
738
+ const candidatePath = path.join(pluginPath, candidate);
739
+ if (fs.existsSync(candidatePath)) return candidatePath;
740
+ }
741
+ return null;
742
+ };
727
743
  try {
728
744
  let pluginModule;
729
745
  if (viteServer) {
@@ -731,9 +747,23 @@ async function discoverPluginActions(pluginPath, projectRoot, registry = actionR
731
747
  } else {
732
748
  pluginModule = await import(modulePath);
733
749
  }
750
+ let toolsModule;
751
+ const loadToolsModule = async () => {
752
+ if (toolsModule) return toolsModule;
753
+ const toolsPath = resolveLocalToolsModulePath();
754
+ if (!toolsPath) {
755
+ getLogger().warn(
756
+ `[Actions] Plugin "${pluginName}" declares a devOnly action but has no tools.ts/tools.js module`
757
+ );
758
+ return void 0;
759
+ }
760
+ toolsModule = viteServer ? await viteServer.ssrLoadModule(toolsPath) : await import(toolsPath);
761
+ return toolsModule;
762
+ };
734
763
  for (const entry of pluginConfig.actions) {
735
- const { name: actionName, action: actionPath } = normalizeActionEntry(entry);
736
- const actionExport = pluginModule[actionName];
764
+ const { name: actionName, action: actionPath, devOnly } = normalizeActionEntry(entry);
765
+ const sourceModule = devOnly ? await loadToolsModule() : pluginModule;
766
+ const actionExport = sourceModule?.[actionName];
737
767
  if (actionExport && isJayAction(actionExport)) {
738
768
  registry.register(actionExport);
739
769
  const registeredName = actionExport.actionName;
@@ -1359,10 +1389,11 @@ async function loadCommandHandler(command, viteServer) {
1359
1389
  module = await import(modulePath);
1360
1390
  }
1361
1391
  } else {
1392
+ const toolsEntry = `${command.packageName}/tools`;
1362
1393
  if (viteServer) {
1363
- module = await viteServer.ssrLoadModule(command.packageName);
1394
+ module = await viteServer.ssrLoadModule(toolsEntry);
1364
1395
  } else {
1365
- module = await import(command.packageName);
1396
+ module = await import(toolsEntry);
1366
1397
  }
1367
1398
  }
1368
1399
  for (const [, exported] of Object.entries(module)) {
@@ -1496,14 +1527,15 @@ async function loadHandler(plugin, handlerName, viteServer) {
1496
1527
  `Handler "${handlerName}" not found in "${plugin.pluginPath}". Available exports: ${Object.keys(module).join(", ")}`
1497
1528
  );
1498
1529
  } else {
1530
+ const toolsEntry = `${plugin.packageName}/tools`;
1499
1531
  if (viteServer) {
1500
- module = await viteServer.ssrLoadModule(plugin.packageName);
1532
+ module = await viteServer.ssrLoadModule(toolsEntry);
1501
1533
  } else {
1502
- module = await import(plugin.packageName);
1534
+ module = await import(toolsEntry);
1503
1535
  }
1504
1536
  if (typeof module[handlerName] !== "function") {
1505
1537
  throw new Error(
1506
- `Handler "${handlerName}" not found as export in "${plugin.packageName}". Available exports: ${Object.keys(module).join(", ")}`
1538
+ `Handler "${handlerName}" not found as export in "${toolsEntry}". Available exports: ${Object.keys(module).join(", ")}`
1507
1539
  );
1508
1540
  }
1509
1541
  return module[handlerName];
@@ -1600,7 +1632,7 @@ function initLogger(verbose) {
1600
1632
  async function runBuild(projectPath, options) {
1601
1633
  initLogger(options.verbose);
1602
1634
  const ctx = await resolveProductionContext(projectPath, options.version);
1603
- const { buildVersion } = await import("./index-CatrpDqC.js");
1635
+ const { buildVersion } = await import("./index-B9vCxAZ1.js");
1604
1636
  await buildVersion({
1605
1637
  version: ctx.version,
1606
1638
  projectRoot: ctx.resolvedPath,
@@ -2902,6 +2934,7 @@ async function validatePluginPackage(pluginPath, options) {
2902
2934
  }
2903
2935
  await validateAddMenuCatalog(context, result);
2904
2936
  await validateAiditorSettings(context, result);
2937
+ validateNoCompilerLeak(context, result);
2905
2938
  result.valid = result.errors.length === 0;
2906
2939
  return result;
2907
2940
  }
@@ -3075,13 +3108,28 @@ async function validateSchema(context, result) {
3075
3108
  }
3076
3109
  }
3077
3110
  }
3078
- if (!manifest.contracts && !manifest.dynamic_contracts) {
3079
- result.warnings.push({
3080
- type: "schema",
3081
- message: "Plugin has no contracts or dynamic_contracts defined",
3082
- location: "plugin.yaml",
3083
- suggestion: 'Add either "contracts" or "dynamic_contracts" to expose functionality'
3084
- });
3111
+ const hasCapability = Boolean(
3112
+ manifest.contracts || manifest.dynamic_contracts || manifest.actions || manifest.validators || manifest.routes || manifest.services || manifest.contexts || manifest.init || manifest.setup || manifest.agentkit || manifest.commands
3113
+ );
3114
+ if (!hasCapability) {
3115
+ if (manifest.global === true) {
3116
+ const hasGlobalEntry = !context.isNpmPackage || checkExportExists("init", context, ".") || checkExportExists("setup", context, ".");
3117
+ if (!hasGlobalEntry) {
3118
+ result.errors.push({
3119
+ type: "export-mismatch",
3120
+ message: 'Plugin declares "global: true" but exports no init/setup handler to run on each page',
3121
+ location: "plugin.yaml",
3122
+ suggestion: 'Export an "init" (or "setup") handler from the package entry, or declare a capability'
3123
+ });
3124
+ }
3125
+ } else {
3126
+ result.warnings.push({
3127
+ type: "schema",
3128
+ message: "Plugin declares no capabilities (contracts, dynamic_contracts, actions, validators, routes, services, contexts, init, setup, agentkit, commands)",
3129
+ location: "plugin.yaml",
3130
+ suggestion: "Declare at least one capability. See agent-kit/plugin/plugin-structure.md"
3131
+ });
3132
+ }
3085
3133
  }
3086
3134
  if (manifest.services) {
3087
3135
  if (!Array.isArray(manifest.services)) {
@@ -3145,14 +3193,22 @@ async function validateSchema(context, result) {
3145
3193
  }
3146
3194
  if (manifest.actions) {
3147
3195
  for (const entry of manifest.actions) {
3148
- const exportName = typeof entry === "string" ? entry : entry.name;
3196
+ if (typeof entry === "object" && entry.devOnly !== void 0 && typeof entry.devOnly !== "boolean") {
3197
+ result.errors.push({
3198
+ type: "schema",
3199
+ message: `Action "${entry.name}" devOnly must be a boolean`,
3200
+ location: "plugin.yaml actions"
3201
+ });
3202
+ }
3203
+ const { name: exportName, devOnly } = normalizeActionEntry$1(entry);
3149
3204
  if (exportName) {
3150
3205
  validateHandlerRef(
3151
3206
  exportName,
3152
3207
  `Action "${exportName}"`,
3153
3208
  "plugin.yaml actions",
3154
3209
  context,
3155
- result
3210
+ result,
3211
+ devOnly ? "./tools" : "."
3156
3212
  );
3157
3213
  }
3158
3214
  }
@@ -3197,7 +3253,8 @@ async function validateSchema(context, result) {
3197
3253
  `Route "${route.path}" component`,
3198
3254
  `plugin.yaml routes`,
3199
3255
  context,
3200
- result
3256
+ result,
3257
+ route.devOnly ? "./tools" : "."
3201
3258
  );
3202
3259
  }
3203
3260
  if (route.jayHtml) {
@@ -3251,7 +3308,8 @@ async function validateSchema(context, result) {
3251
3308
  `Validator "${validator.name}" handler`,
3252
3309
  "plugin.yaml validators",
3253
3310
  context,
3254
- result
3311
+ result,
3312
+ "./tools"
3255
3313
  );
3256
3314
  }
3257
3315
  });
@@ -3271,7 +3329,8 @@ async function validateSchema(context, result) {
3271
3329
  "Setup handler",
3272
3330
  "plugin.yaml setup",
3273
3331
  context,
3274
- result
3332
+ result,
3333
+ "./tools"
3275
3334
  );
3276
3335
  }
3277
3336
  }
@@ -3281,22 +3340,23 @@ async function validateSchema(context, result) {
3281
3340
  "Agent-kit handler",
3282
3341
  "plugin.yaml agentkit",
3283
3342
  context,
3284
- result
3343
+ result,
3344
+ "./tools"
3285
3345
  );
3286
3346
  }
3287
3347
  }
3288
- function checkExportExists(exportName, context) {
3348
+ function checkExportExists(exportName, context, exportKey = ".") {
3289
3349
  const packageJsonPath = path$1.join(context.pluginPath, "package.json");
3290
3350
  if (!fs$1.existsSync(packageJsonPath)) return true;
3291
3351
  let mainPath;
3292
3352
  try {
3293
3353
  const packageJson = JSON.parse(fs$1.readFileSync(packageJsonPath, "utf-8"));
3294
- if (packageJson.exports?.["."]) {
3295
- const entry = packageJson.exports["."];
3354
+ if (packageJson.exports?.[exportKey]) {
3355
+ const entry = packageJson.exports[exportKey];
3296
3356
  const entryPath = typeof entry === "string" ? entry : entry.default || entry.import;
3297
3357
  if (entryPath) mainPath = path$1.join(context.pluginPath, entryPath);
3298
3358
  }
3299
- if (!mainPath && packageJson.main) {
3359
+ if (!mainPath && exportKey === "." && packageJson.main) {
3300
3360
  mainPath = path$1.join(context.pluginPath, packageJson.main);
3301
3361
  }
3302
3362
  } catch {
@@ -3318,7 +3378,63 @@ function checkExportExists(exportName, context) {
3318
3378
  function isRelativePath(value) {
3319
3379
  return value.startsWith("./") || value.startsWith("../");
3320
3380
  }
3321
- function validateHandlerRef(value, label, location, context, result) {
3381
+ function resolveEntryFile(context, exportKey) {
3382
+ const packageJsonPath = path$1.join(context.pluginPath, "package.json");
3383
+ if (!fs$1.existsSync(packageJsonPath)) return void 0;
3384
+ try {
3385
+ const packageJson = JSON.parse(fs$1.readFileSync(packageJsonPath, "utf-8"));
3386
+ const entry = packageJson.exports?.[exportKey];
3387
+ const entryPath = typeof entry === "string" ? entry : entry?.default || entry?.import || void 0;
3388
+ const resolved = entryPath || (exportKey === "." ? packageJson.main : void 0) ? path$1.join(context.pluginPath, entryPath || packageJson.main) : void 0;
3389
+ return resolved && fs$1.existsSync(resolved) ? resolved : void 0;
3390
+ } catch {
3391
+ return void 0;
3392
+ }
3393
+ }
3394
+ function hasComponentCapability(context) {
3395
+ const m = context.manifest;
3396
+ return Boolean(m.contracts || m.dynamic_contracts || m.routes);
3397
+ }
3398
+ function needsToolsEntry(manifest) {
3399
+ if (manifest.validators || manifest.commands || manifest.agentkit || manifest.setup) {
3400
+ return true;
3401
+ }
3402
+ if (manifest.actions) {
3403
+ return manifest.actions.some((entry) => normalizeActionEntry$1(entry).devOnly === true);
3404
+ }
3405
+ return false;
3406
+ }
3407
+ function detectInteractivePhase(context) {
3408
+ if (!context.isNpmPackage) return "unknown";
3409
+ const entryFile = resolveEntryFile(context, ".");
3410
+ if (!entryFile) return "unknown";
3411
+ try {
3412
+ const content = fs$1.readFileSync(entryFile, "utf-8");
3413
+ return content.includes("withInteractiveMark(");
3414
+ } catch {
3415
+ return "unknown";
3416
+ }
3417
+ }
3418
+ function validateNoCompilerLeak(context, result) {
3419
+ if (!context.isNpmPackage) return;
3420
+ const entryFile = resolveEntryFile(context, ".");
3421
+ if (!entryFile) return;
3422
+ let content;
3423
+ try {
3424
+ content = fs$1.readFileSync(entryFile, "utf-8");
3425
+ } catch {
3426
+ return;
3427
+ }
3428
+ if (content.includes("@jay-framework/compiler-")) {
3429
+ result.errors.push({
3430
+ type: "compiler-leak",
3431
+ message: 'Serve entry "." (dist/index.js) imports "@jay-framework/compiler-…" — the compiler must not reach the production serve bundle',
3432
+ location: entryFile,
3433
+ suggestion: 'Move the compiler-using handler (validator, agentkit, setup, or a devOnly action) to lib/tools.ts (the "./tools" export) and remove its re-export from lib/index.ts. A compiler-using action is really a command or a devOnly action (DL#179/#180).'
3434
+ });
3435
+ }
3436
+ }
3437
+ function validateHandlerRef(value, label, location, context, result, exportKey = ".") {
3322
3438
  if (context.isNpmPackage) {
3323
3439
  if (isRelativePath(value)) {
3324
3440
  result.errors.push({
@@ -3327,12 +3443,13 @@ function validateHandlerRef(value, label, location, context, result) {
3327
3443
  location,
3328
3444
  suggestion: `Export the function from the package entry point and use the export name instead of a path`
3329
3445
  });
3330
- } else if (!checkExportExists(value, context)) {
3446
+ } else if (!checkExportExists(value, context, exportKey)) {
3447
+ const entryFile = exportKey === "./tools" ? "lib/tools.ts" : exportKey === "./client" ? "lib/index.client.ts" : "lib/index.ts";
3331
3448
  result.errors.push({
3332
3449
  type: "export-mismatch",
3333
- message: `${label} "${value}" is not exported from the package`,
3450
+ message: `${label} "${value}" is not exported from the "${exportKey}" entry`,
3334
3451
  location,
3335
- suggestion: `Add "export { ${value} } from '...'" to the package entry point`
3452
+ suggestion: `Add "export { ${value} } from '...'" to ${entryFile} (the "${exportKey}" export)`
3336
3453
  });
3337
3454
  }
3338
3455
  } else if (isRelativePath(value)) {
@@ -3606,11 +3723,30 @@ async function validatePackageJson(context, result) {
3606
3723
  });
3607
3724
  }
3608
3725
  if (!packageJson.exports["./client"]) {
3609
- result.warnings.push({
3726
+ const interactivity = detectInteractivePhase(context);
3727
+ const needsClient = context.manifest.contexts !== void 0 || interactivity === true;
3728
+ if (needsClient) {
3729
+ result.errors.push({
3730
+ type: "export-mismatch",
3731
+ message: 'package.json exports missing "./client" entry point, but the plugin ' + (context.manifest.contexts !== void 0 ? "declares contexts (client-side by definition)" : "provides an interactive component"),
3732
+ location: packageJsonPath,
3733
+ suggestion: 'Add "./client": "./dist/index.client.js" to exports. The client bundle provides components for hydration and client-side contexts. Build with: vite build (client) + vite build --ssr (server)'
3734
+ });
3735
+ } else if (interactivity === "unknown" && hasComponentCapability(context)) {
3736
+ result.warnings.push({
3737
+ type: "export-mismatch",
3738
+ message: 'package.json exports missing "./client" entry point; could not determine whether any component is interactive (build the plugin before validating)',
3739
+ location: packageJsonPath,
3740
+ suggestion: 'If any component declares an interactive phase, add "./client": "./dist/index.client.js"'
3741
+ });
3742
+ }
3743
+ }
3744
+ if (!packageJson.exports["./tools"] && needsToolsEntry(context.manifest)) {
3745
+ result.errors.push({
3610
3746
  type: "export-mismatch",
3611
- message: 'package.json exports missing "./client" entry point',
3747
+ message: 'package.json exports missing "./tools" entry point, but the plugin declares tools capabilities (validators, commands, agentkit, setup, or devOnly actions)',
3612
3748
  location: packageJsonPath,
3613
- suggestion: 'Add "./client": "./dist/index.client.js" to exports. The client bundle provides components for hydration and client-side contexts. Build with: vite build (client) + vite build --ssr (server)'
3749
+ suggestion: 'Add "./tools": "./dist/tools.js" to exports and re-export those handlers from lib/tools.ts'
3614
3750
  });
3615
3751
  }
3616
3752
  if (context.manifest.contracts) {
@@ -4413,7 +4549,7 @@ async function runPluginValidators(projectRoot, parsedFiles, errors, warnings) {
4413
4549
  const handlerPath = path$1.resolve(plugin.pluginPath, validatorDef.handler);
4414
4550
  handlerModule = await import(handlerPath);
4415
4551
  } else {
4416
- handlerModule = await import(plugin.packageName);
4552
+ handlerModule = await import(`${plugin.packageName}/tools`);
4417
4553
  }
4418
4554
  validatorFn = plugin.isLocal ? handlerModule.validate ?? handlerModule.default : handlerModule[validatorDef.handler];
4419
4555
  if (typeof validatorFn !== "function") {
@@ -4601,9 +4737,10 @@ async function validateJayFiles(options = {}) {
4601
4737
  }
4602
4738
  parsedFiles.push({ relativePath, parsed: parsedFile.val });
4603
4739
  if (content.includes("application/jay-params")) {
4604
- warnings.push({
4740
+ errors.push({
4605
4741
  file: relativePath,
4606
- message: '<script type="application/jay-params"> is deprecated. Move the values into the YAML body of the headless component that uses them. See agent-kit/developer/routing.md for details.'
4742
+ message: '<script type="application/jay-params"> is no longer supported and is ignored. Move the values into the YAML body of the headless component that uses them. See agent-kit/developer/routing.md for details.',
4743
+ stage: "parse"
4607
4744
  });
4608
4745
  }
4609
4746
  const pageExportError = checkPageComponentExport(jayFile);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jay-framework/jay-stack-cli",
3
- "version": "0.24.2",
3
+ "version": "0.24.3",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -25,16 +25,16 @@
25
25
  },
26
26
  "dependencies": {
27
27
  "@inquirer/prompts": "^8.5.2",
28
- "@jay-framework/compiler-jay-html": "^0.24.2",
29
- "@jay-framework/compiler-shared": "^0.24.2",
30
- "@jay-framework/dev-server": "^0.24.2",
31
- "@jay-framework/fullstack-component": "^0.24.2",
32
- "@jay-framework/logger": "^0.24.2",
33
- "@jay-framework/plugin-validator": "^0.24.2",
34
- "@jay-framework/production-build": "^0.24.2",
35
- "@jay-framework/production-server": "^0.24.2",
36
- "@jay-framework/stack-server-build": "^0.24.2",
37
- "@jay-framework/stack-server-runtime": "^0.24.2",
28
+ "@jay-framework/compiler-jay-html": "^0.24.3",
29
+ "@jay-framework/compiler-shared": "^0.24.3",
30
+ "@jay-framework/dev-server": "^0.24.3",
31
+ "@jay-framework/fullstack-component": "^0.24.3",
32
+ "@jay-framework/logger": "^0.24.3",
33
+ "@jay-framework/plugin-validator": "^0.24.3",
34
+ "@jay-framework/production-build": "^0.24.3",
35
+ "@jay-framework/production-server": "^0.24.3",
36
+ "@jay-framework/stack-server-build": "^0.24.3",
37
+ "@jay-framework/stack-server-runtime": "^0.24.3",
38
38
  "chalk": "^4.1.2",
39
39
  "commander": "^14.0.0",
40
40
  "express": "^5.0.1",
@@ -45,7 +45,7 @@
45
45
  "yaml": "^2.3.4"
46
46
  },
47
47
  "devDependencies": {
48
- "@jay-framework/dev-environment": "^0.24.2",
48
+ "@jay-framework/dev-environment": "^0.24.3",
49
49
  "@types/express": "^5.0.2",
50
50
  "@types/node": "^22.15.21",
51
51
  "nodemon": "^3.0.3",