@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.
- package/agent-kit-template/developer/dev-server-service.md +7 -7
- package/agent-kit-template/developer/routing.md +1 -1
- package/agent-kit-template/plugin/actions-guide.md +10 -0
- package/agent-kit-template/plugin/commands-guide.md +15 -0
- package/agent-kit-template/plugin/contracts-guide.md +7 -0
- package/agent-kit-template/plugin/dev-server-service.md +7 -7
- package/agent-kit-template/plugin/plugin-routes.md +43 -2
- package/agent-kit-template/plugin/plugin-structure.md +101 -15
- package/agent-kit-template/plugin/setup-guide.md +7 -4
- package/agent-kit-template/plugin/validation.md +22 -4
- package/dist/{index-CatrpDqC.js → index-B9vCxAZ1.js} +19 -11
- package/dist/index.js +176 -39
- package/package.json +12 -12
|
@@ -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
|
-
###
|
|
99
|
+
### Frozen Page Refresh (Dev)
|
|
100
100
|
|
|
101
|
-
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
|
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
|
-
###
|
|
110
|
+
### Frozen Page Refresh (Dev)
|
|
111
111
|
|
|
112
|
-
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
|
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 | **
|
|
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
|
|
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
|
|
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
|
-
##
|
|
320
|
+
## Entry Points
|
|
267
321
|
|
|
268
|
-
Jay plugins
|
|
322
|
+
Jay plugins run in three contexts. The build produces up to three bundles:
|
|
269
323
|
|
|
270
|
-
- **Server** (`dist/index.js
|
|
271
|
-
|
|
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
|
|
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**
|
|
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
|
-
? {
|
|
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
|
-
|
|
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
|
|
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
|
|
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/
|
|
273
|
+
// lib/tools.ts (./tools) — compiler allowed, toolchain-only
|
|
273
274
|
export { setupMyPlugin } from './setup.js';
|
|
274
275
|
export { generateMyAgentKit } from './agentkit.js';
|
|
275
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
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}: ${
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
1394
|
+
module = await viteServer.ssrLoadModule(toolsEntry);
|
|
1364
1395
|
} else {
|
|
1365
|
-
module = await import(
|
|
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(
|
|
1532
|
+
module = await viteServer.ssrLoadModule(toolsEntry);
|
|
1501
1533
|
} else {
|
|
1502
|
-
module = await import(
|
|
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 "${
|
|
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-
|
|
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
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
3450
|
+
message: `${label} "${value}" is not exported from the "${exportKey}" entry`,
|
|
3334
3451
|
location,
|
|
3335
|
-
suggestion: `Add "export { ${value} } from '...'" to the
|
|
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
|
-
|
|
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 "./
|
|
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 "./
|
|
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
|
-
|
|
4740
|
+
errors.push({
|
|
4605
4741
|
file: relativePath,
|
|
4606
|
-
message: '<script type="application/jay-params"> is
|
|
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.
|
|
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.
|
|
29
|
-
"@jay-framework/compiler-shared": "^0.24.
|
|
30
|
-
"@jay-framework/dev-server": "^0.24.
|
|
31
|
-
"@jay-framework/fullstack-component": "^0.24.
|
|
32
|
-
"@jay-framework/logger": "^0.24.
|
|
33
|
-
"@jay-framework/plugin-validator": "^0.24.
|
|
34
|
-
"@jay-framework/production-build": "^0.24.
|
|
35
|
-
"@jay-framework/production-server": "^0.24.
|
|
36
|
-
"@jay-framework/stack-server-build": "^0.24.
|
|
37
|
-
"@jay-framework/stack-server-runtime": "^0.24.
|
|
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.
|
|
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",
|