@jay-framework/jay-stack-cli 0.17.3 → 0.18.0
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/designer/jay-html-components.md +2 -0
- package/agent-kit-template/designer/jay-html-syntax.md +1 -1
- package/agent-kit-template/designer/project-structure.md +5 -0
- package/agent-kit-template/developer/cli-commands.md +5 -1
- package/agent-kit-template/developer/component-refs.md +7 -3
- package/agent-kit-template/developer/project-structure.md +5 -0
- package/agent-kit-template/developer/render-results.md +37 -0
- package/agent-kit-template/developer/routing.md +25 -0
- package/agent-kit-template/devops/INSTRUCTIONS.md +24 -0
- package/agent-kit-template/devops/fetch-handler.md +185 -0
- package/agent-kit-template/devops/invalidation.md +77 -0
- package/agent-kit-template/devops/production-build.md +80 -0
- package/agent-kit-template/devops/serving-modes.md +118 -0
- package/agent-kit-template/plugin/INSTRUCTIONS.md +2 -0
- package/agent-kit-template/plugin/actions-guide.md +2 -10
- package/agent-kit-template/plugin/commands-guide.md +121 -0
- package/agent-kit-template/plugin/component-refs.md +7 -3
- package/agent-kit-template/plugin/component-structure.md +26 -2
- package/agent-kit-template/plugin/plugin-structure.md +27 -0
- package/agent-kit-template/plugin/render-results.md +37 -0
- package/agent-kit-template/plugin/webhooks-guide.md +124 -0
- package/dist/index.js +732 -456
- package/package.json +11 -11
|
@@ -38,7 +38,7 @@ makeJayAction('name')
|
|
|
38
38
|
.withServices(SERVICE1, SERVICE2) // Inject services
|
|
39
39
|
.withMethod('PUT') // Override HTTP method (default: POST for actions)
|
|
40
40
|
.withCaching({ maxAge: 60 }) // Enable caching (queries only)
|
|
41
|
-
.withFiles(
|
|
41
|
+
.withFiles() // Accept file uploads (multipart/form-data)
|
|
42
42
|
.withHandler(async (input, svc1, svc2) => {
|
|
43
43
|
// Define handler
|
|
44
44
|
return result;
|
|
@@ -55,7 +55,7 @@ import { makeJayAction, type JayFile } from '@jay-framework/fullstack-component'
|
|
|
55
55
|
import fs from 'fs';
|
|
56
56
|
|
|
57
57
|
export const uploadPhoto = makeJayAction('photos.upload')
|
|
58
|
-
.withFiles(
|
|
58
|
+
.withFiles()
|
|
59
59
|
.withHandler(async (input: { caption: string; photo: JayFile }) => {
|
|
60
60
|
// JayFile provides: name, type, size, path (temp file on disk)
|
|
61
61
|
const data = fs.readFileSync(input.photo.path);
|
|
@@ -123,14 +123,6 @@ refs.uploadBtn.onClick(async () => {
|
|
|
123
123
|
});
|
|
124
124
|
```
|
|
125
125
|
|
|
126
|
-
### FileUploadOptions
|
|
127
|
-
|
|
128
|
-
```typescript
|
|
129
|
-
.withFiles() // Defaults: 10MB per file, 10 files max
|
|
130
|
-
.withFiles({ maxFileSize: 2 * 1024 * 1024 }) // 2MB limit
|
|
131
|
-
.withFiles({ maxFileSize: 20_000_000, maxFiles: 5 }) // 20MB, 5 files
|
|
132
|
-
```
|
|
133
|
-
|
|
134
126
|
## ActionError
|
|
135
127
|
|
|
136
128
|
Throw typed errors from action handlers:
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# CLI Commands
|
|
2
|
+
|
|
3
|
+
Plugins can expose CLI commands for admin and batch operations. Run via `jay-stack run <plugin>/<command>`.
|
|
4
|
+
|
|
5
|
+
## When to Use
|
|
6
|
+
|
|
7
|
+
- Media upload (public folder → cloud storage)
|
|
8
|
+
- Data sync (external CMS → local references)
|
|
9
|
+
- Deployment (build artifacts → cloud provider)
|
|
10
|
+
- Cache management (CDN purge, rebuild triggers)
|
|
11
|
+
|
|
12
|
+
For request-response operations, use [actions](actions-guide.md) instead.
|
|
13
|
+
|
|
14
|
+
## Creating a Command
|
|
15
|
+
|
|
16
|
+
### 1. Build the handler
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
import { makeCliCommand, CONSOLE_CONTEXT } from '@jay-framework/fullstack-component';
|
|
20
|
+
import { MEDIA_SERVICE } from './services';
|
|
21
|
+
|
|
22
|
+
export const uploadPublic = makeCliCommand('upload-public')
|
|
23
|
+
.withServices(MEDIA_SERVICE, CONSOLE_CONTEXT)
|
|
24
|
+
.withHandler(async (input: { folder?: string; dryRun?: boolean }, mediaService, console) => {
|
|
25
|
+
const path = await import('node:path');
|
|
26
|
+
const fs = await import('node:fs/promises');
|
|
27
|
+
|
|
28
|
+
const dir = path.resolve(console.publicFolder, input.folder || '');
|
|
29
|
+
const files = await fs.readdir(dir, { recursive: true });
|
|
30
|
+
|
|
31
|
+
for (const file of files) {
|
|
32
|
+
const filePath = path.join(dir, String(file));
|
|
33
|
+
const stat = await fs.stat(filePath);
|
|
34
|
+
if (!stat.isFile()) continue;
|
|
35
|
+
|
|
36
|
+
if (input.dryRun) {
|
|
37
|
+
console.log(`[dry-run] Would upload ${file}`);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const url = await mediaService.upload(filePath);
|
|
42
|
+
console.log(`Uploaded ${file} → ${url}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return { success: true };
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 2. Create the metadata file
|
|
50
|
+
|
|
51
|
+
Place `.jay-command` files in a `commands/` folder (alongside `contracts/` and `actions/`):
|
|
52
|
+
|
|
53
|
+
```yaml
|
|
54
|
+
# commands/upload-public.jay-command
|
|
55
|
+
name: upload-public
|
|
56
|
+
description: Upload public folder files to cloud storage
|
|
57
|
+
|
|
58
|
+
inputSchema:
|
|
59
|
+
folder?: string
|
|
60
|
+
dryRun?: boolean
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The `inputSchema` auto-generates CLI flags:
|
|
64
|
+
|
|
65
|
+
- `folder?: string` → `--folder <value>` (optional)
|
|
66
|
+
- `dryRun?: boolean` → `--dry-run` (optional flag)
|
|
67
|
+
|
|
68
|
+
Required fields (no `?`) are validated before the handler runs.
|
|
69
|
+
|
|
70
|
+
### 3. Declare in plugin.yaml
|
|
71
|
+
|
|
72
|
+
```yaml
|
|
73
|
+
commands:
|
|
74
|
+
- name: upload-public
|
|
75
|
+
command: commands/upload-public.jay-command
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## `CONSOLE_CONTEXT` Service
|
|
79
|
+
|
|
80
|
+
A framework-provided service with project info and a logger:
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
interface ConsoleContext {
|
|
84
|
+
projectRoot: string;
|
|
85
|
+
publicFolder: string;
|
|
86
|
+
build: {
|
|
87
|
+
frontend: string; // Build output: JS, CSS, public assets
|
|
88
|
+
backend: string; // Build output: server modules, pre-rendered HTML
|
|
89
|
+
};
|
|
90
|
+
verbose: boolean;
|
|
91
|
+
log: (message: string) => void;
|
|
92
|
+
warn: (message: string) => void;
|
|
93
|
+
error: (message: string) => void;
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Request it via `.withServices(CONSOLE_CONTEXT)`. Commands that don't need it simply don't request it.
|
|
98
|
+
|
|
99
|
+
## Running Commands
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
# Run a command
|
|
103
|
+
jay-stack run media/upload-public --folder images --dry-run
|
|
104
|
+
|
|
105
|
+
# List all available commands
|
|
106
|
+
jay-stack run --list
|
|
107
|
+
|
|
108
|
+
# Verbose output
|
|
109
|
+
jay-stack run media/upload-public -v
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Input Type Mapping
|
|
113
|
+
|
|
114
|
+
| Schema type | CLI flag | Example |
|
|
115
|
+
| ----------------- | ----------------------------------- | ------------------ |
|
|
116
|
+
| `field: string` | `--field <value>` (required) | `--env production` |
|
|
117
|
+
| `field?: string` | `--field <value>` (optional) | `--folder images` |
|
|
118
|
+
| `field?: boolean` | `--field` (optional flag) | `--dry-run` |
|
|
119
|
+
| `field: number` | `--field <value>` (required number) | `--concurrency 4` |
|
|
120
|
+
|
|
121
|
+
camelCase names become kebab-case flags: `dryRun` → `--dry-run`.
|
|
@@ -31,9 +31,13 @@ refs.submitButton.onClick(() => {
|
|
|
31
31
|
/* ... */
|
|
32
32
|
});
|
|
33
33
|
|
|
34
|
-
// exec$ gives direct access to the element and current ViewState
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
// exec$ gives direct access to the element and current ViewState.
|
|
35
|
+
// Only use exec$ inside event handlers — never at top-level component
|
|
36
|
+
// creation or in effects, because elements don't exist yet at that point.
|
|
37
|
+
refs.submitButton.onclick(() => {
|
|
38
|
+
refs.submitButton.exec$((element, viewState) => {
|
|
39
|
+
element.disabled = viewState.isSubmitting;
|
|
40
|
+
});
|
|
37
41
|
});
|
|
38
42
|
```
|
|
39
43
|
|
|
@@ -87,7 +87,7 @@ CarryForward data is passed to the fast phase but not included in the ViewState.
|
|
|
87
87
|
|
|
88
88
|
### `.withFastRender(fn)` — Request-time rendering
|
|
89
89
|
|
|
90
|
-
Runs on each request. Receives props (including `query` for query parameters) and carry-forward from slow phase.
|
|
90
|
+
Runs on each request. Receives props (including `query` for query parameters and `cookies` for HTTP cookies) and carry-forward from slow phase. Can set HTTP response headers via `phaseOutput()` options.
|
|
91
91
|
|
|
92
92
|
```typescript
|
|
93
93
|
.withFastRender(async (props, db) => {
|
|
@@ -96,6 +96,30 @@ Runs on each request. Receives props (including `query` for query parameters) an
|
|
|
96
96
|
})
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
+
#### Cookies
|
|
100
|
+
|
|
101
|
+
`props.cookies` is a `Record<string, string>` parsed from the HTTP `Cookie` header. Use for auth checks:
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
.withFastRender(async (props, memberService) => {
|
|
105
|
+
const token = props.cookies['session-token'];
|
|
106
|
+
if (!token) return redirect3xx(302, '/login');
|
|
107
|
+
|
|
108
|
+
const member = await memberService.validate(token);
|
|
109
|
+
if (!member) return redirect3xx(302, '/login');
|
|
110
|
+
|
|
111
|
+
return phaseOutput(
|
|
112
|
+
{ isLoggedIn: true, memberName: member.name },
|
|
113
|
+
{},
|
|
114
|
+
{ responseHeaders: { 'Cache-Control': 'no-store' } },
|
|
115
|
+
);
|
|
116
|
+
})
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
- Empty `{}` when no cookies are present
|
|
120
|
+
- Not available in the slow phase (compile error)
|
|
121
|
+
- `responseHeaders` in `phaseOutput()` options sets HTTP headers on the response (e.g. `Cache-Control: no-store` for per-user pages)
|
|
122
|
+
|
|
99
123
|
### `.withClientDefaults(fn)` — Defaults for dynamically created forEach items
|
|
100
124
|
|
|
101
125
|
Required only when the component is used inside a `forEach` where new items can be added on the client. When a user adds a new item to a forEach array, the new instance has no server data — `withClientDefaults` provides the initial ViewState.
|
|
@@ -134,7 +158,7 @@ The interactive phase runs in the browser. Use hooks here (see component-state.m
|
|
|
134
158
|
|
|
135
159
|
Each phase can return:
|
|
136
160
|
|
|
137
|
-
- `phaseOutput(viewState, carryForward)` — success
|
|
161
|
+
- `phaseOutput(viewState, carryForward, options?)` — success (options: `{ headTags?, responseHeaders? }`)
|
|
138
162
|
- `notFound()`, `badRequest()`, `unauthorized()`, `forbidden()` — client errors
|
|
139
163
|
- `serverError5xx(status, message)` — server errors
|
|
140
164
|
- `redirect3xx(status, location)` — redirects
|
|
@@ -35,6 +35,10 @@ actions:
|
|
|
35
35
|
- name: addToCart
|
|
36
36
|
action: add-to-cart.jay-action
|
|
37
37
|
|
|
38
|
+
webhooks:
|
|
39
|
+
- name: onProductChange
|
|
40
|
+
- name: onInventoryUpdate
|
|
41
|
+
|
|
38
42
|
services:
|
|
39
43
|
- name: my-store
|
|
40
44
|
marker: MY_STORE_SERVICE_MARKER
|
|
@@ -51,6 +55,12 @@ routes:
|
|
|
51
55
|
component: ./pages/admin/page.ts
|
|
52
56
|
description: Admin dashboard with product stats
|
|
53
57
|
|
|
58
|
+
commands:
|
|
59
|
+
- name: upload-public
|
|
60
|
+
command: commands/upload-public.jay-command
|
|
61
|
+
- name: sync-catalog
|
|
62
|
+
command: commands/sync-catalog.jay-command
|
|
63
|
+
|
|
54
64
|
setup:
|
|
55
65
|
handler: setup-handler
|
|
56
66
|
references: references-handler
|
|
@@ -114,6 +124,12 @@ tags:
|
|
|
114
124
|
- `name` — Action name (used with `jay-stack action <plugin>/<action>`)
|
|
115
125
|
- `action` — Path to `.jay-action` metadata file
|
|
116
126
|
|
|
127
|
+
### Webhook Entry Fields
|
|
128
|
+
|
|
129
|
+
- `name` — Export name of the `makeWebhook()` constant (e.g., `onProductChange`)
|
|
130
|
+
|
|
131
|
+
Webhooks are exposed at `POST /_jay/webhooks/{webhookName}` on the renderer server. The `webhookName` comes from the `makeWebhook('plugin.event-name')` call, not from the export name. The export name in plugin.yaml tells the framework which export to load from the plugin module.
|
|
132
|
+
|
|
117
133
|
### Service Entry Fields
|
|
118
134
|
|
|
119
135
|
- `name` — Service name (for identification in plugins-index)
|
|
@@ -162,6 +178,13 @@ services:
|
|
|
162
178
|
|
|
163
179
|
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.
|
|
164
180
|
|
|
181
|
+
### Command Entry Fields
|
|
182
|
+
|
|
183
|
+
- `name` — Command name (used with `jay-stack run <plugin>/<command>`)
|
|
184
|
+
- `command` — (optional) Path to `.jay-command` metadata file (declares description and input schema)
|
|
185
|
+
|
|
186
|
+
Commands are CLI operations run via `jay-stack run`. Use `makeCliCommand()` to create handlers with service injection. See [commands-guide.md](commands-guide.md).
|
|
187
|
+
|
|
165
188
|
### Setup Fields
|
|
166
189
|
|
|
167
190
|
- `handler` — Setup handler for `jay-stack setup` (handles config, credentials)
|
|
@@ -183,6 +206,10 @@ my-plugin/
|
|
|
183
206
|
│ ├── actions/
|
|
184
207
|
│ │ ├── search-products.jay-action
|
|
185
208
|
│ │ └── add-to-cart.jay-action
|
|
209
|
+
│ ├── commands/
|
|
210
|
+
│ │ └── upload-public.jay-command
|
|
211
|
+
│ ├── webhooks/
|
|
212
|
+
│ │ └── on-product-change.ts
|
|
186
213
|
│ ├── components/
|
|
187
214
|
│ │ ├── product-page.ts
|
|
188
215
|
│ │ └── product-search.ts
|
|
@@ -17,6 +17,43 @@ return phaseOutput(
|
|
|
17
17
|
|
|
18
18
|
CarryForward is available in the next phase via `props.carryForward` but is not part of the ViewState.
|
|
19
19
|
|
|
20
|
+
### Response Headers (fast phase only)
|
|
21
|
+
|
|
22
|
+
The third parameter accepts `responseHeaders` to set HTTP headers on the page response:
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
return phaseOutput(
|
|
26
|
+
{ memberName: member.name },
|
|
27
|
+
{},
|
|
28
|
+
{ responseHeaders: { 'Cache-Control': 'no-store' } },
|
|
29
|
+
);
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Use this when the component renders per-user data that must not be cached by CDN or browser. Can be combined with `headTags` in the same options object.
|
|
33
|
+
|
|
34
|
+
### Cookies (fast phase only)
|
|
35
|
+
|
|
36
|
+
The fast phase receives `props.cookies` — a `Record<string, string>` parsed from the HTTP `Cookie` header:
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
.withFastRender(async (props, memberService) => {
|
|
40
|
+
const token = props.cookies['session-token'];
|
|
41
|
+
if (!token) return redirect3xx(302, '/login');
|
|
42
|
+
|
|
43
|
+
const member = await memberService.validate(token);
|
|
44
|
+
if (!member) return redirect3xx(302, '/login');
|
|
45
|
+
|
|
46
|
+
return phaseOutput(
|
|
47
|
+
{ isLoggedIn: true, memberName: member.name },
|
|
48
|
+
{},
|
|
49
|
+
{ responseHeaders: { 'Cache-Control': 'no-store' } },
|
|
50
|
+
);
|
|
51
|
+
})
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
- `props.cookies` is `Record<string, string>` — empty `{}` when no cookies
|
|
55
|
+
- Not available in the slow phase (compile error) — same as `props.query`
|
|
56
|
+
|
|
20
57
|
## Error Results
|
|
21
58
|
|
|
22
59
|
Return errors to stop rendering and show an error page:
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Webhooks & Data Change Invalidation
|
|
2
|
+
|
|
3
|
+
Webhooks let external systems (CMS, database, etc.) notify the renderer server when data changes, triggering targeted rebuilds of affected pages.
|
|
4
|
+
|
|
5
|
+
## makeWebhook
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { makeWebhook } from '@jay-framework/fullstack-component';
|
|
9
|
+
|
|
10
|
+
export const onProductChange = makeWebhook('wix-stores.product-change')
|
|
11
|
+
.withServices(PRODUCTS_SERVICE)
|
|
12
|
+
.withHandler(async (event, invalidate, productsService) => {
|
|
13
|
+
const slug = await productsService.resolveSlug(event.payload.itemId);
|
|
14
|
+
await invalidate('product-page', { slug });
|
|
15
|
+
});
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Builder API
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
makeWebhook('plugin-name.event-name')
|
|
22
|
+
.withServices(SERVICE1, SERVICE2) // Inject services
|
|
23
|
+
.withHandler(async (event, invalidate, svc1, svc2) => {
|
|
24
|
+
// event.type — webhook name
|
|
25
|
+
// event.payload — parsed JSON body from the HTTP request
|
|
26
|
+
// event.headers — HTTP headers
|
|
27
|
+
// invalidate(contractName, params?) — trigger rebuild
|
|
28
|
+
});
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## The invalidate Function
|
|
32
|
+
|
|
33
|
+
The `invalidate` callback resolves contract names to routes and rebuilds affected instances:
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
// Rebuild a specific instance — finds routes using 'product-page' contract,
|
|
37
|
+
// rebuilds the instance matching { slug: 'blue-widget' }
|
|
38
|
+
await invalidate('product-page', { slug: 'blue-widget' });
|
|
39
|
+
|
|
40
|
+
// Rebuild ALL instances of routes using 'search-results' contract
|
|
41
|
+
await invalidate('search-results');
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**Contract-based, not route-based.** A plugin knows its own contract names but not the project's route structure. The framework resolves which routes use each contract via the `contracts` field in the route manifest.
|
|
45
|
+
|
|
46
|
+
## Declaring in plugin.yaml
|
|
47
|
+
|
|
48
|
+
```yaml
|
|
49
|
+
webhooks:
|
|
50
|
+
- name: onProductChange
|
|
51
|
+
- name: onInventoryUpdate
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The `name` field is the **export name** from the plugin module. The webhook URL is derived from the `makeWebhook()` name argument, not the export name.
|
|
55
|
+
|
|
56
|
+
## URL Mapping
|
|
57
|
+
|
|
58
|
+
Webhooks are exposed on the renderer server at:
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
POST /_jay/webhooks/{webhookName}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Where `{webhookName}` is the first argument to `makeWebhook()`. Example:
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
makeWebhook('wix-stores.product-change')
|
|
68
|
+
→ POST /_jay/webhooks/wix-stores.product-change
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Optimistic Skip
|
|
72
|
+
|
|
73
|
+
After re-running the slow render, the framework compares the new pre-rendered jay-html with the existing one. If the template structure is unchanged (e.g., a price change updates ViewState but doesn't toggle a slow conditional), the server element compilation and Vite client build are skipped.
|
|
74
|
+
|
|
75
|
+
## Project Webhooks
|
|
76
|
+
|
|
77
|
+
Projects can define webhooks in `src/webhooks/`:
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
// src/webhooks/on-content-update.ts
|
|
81
|
+
import { makeWebhook } from '@jay-framework/fullstack-component';
|
|
82
|
+
|
|
83
|
+
export const onContentUpdate = makeWebhook('cms.content-update').withHandler(
|
|
84
|
+
async (event, invalidate) => {
|
|
85
|
+
const { pageSlug } = event.payload as { pageSlug: string };
|
|
86
|
+
await invalidate('content-page', { slug: pageSlug });
|
|
87
|
+
},
|
|
88
|
+
);
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Project webhooks don't need plugin.yaml — they are discovered by scanning compiled files in `server/webhooks/`.
|
|
92
|
+
|
|
93
|
+
## CLI Rebuild
|
|
94
|
+
|
|
95
|
+
For manual or CI-triggered rebuilds without a webhook:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
# By contract — rebuild all routes using this contract
|
|
99
|
+
jay-stack rebuild --contract product-page
|
|
100
|
+
|
|
101
|
+
# By contract + params — rebuild specific instance
|
|
102
|
+
jay-stack rebuild --contract product-page --params '{"slug":"blue-widget"}'
|
|
103
|
+
|
|
104
|
+
# By route — rebuild all instances of a route (useful for pages with page.ts, no contract)
|
|
105
|
+
jay-stack rebuild --route /products/[slug]
|
|
106
|
+
|
|
107
|
+
# By route + params — rebuild specific instance
|
|
108
|
+
jay-stack rebuild --route /products/[slug] --params '{"slug":"blue-widget"}'
|
|
109
|
+
|
|
110
|
+
# By URL — resolve URL to route+params, rebuild that instance
|
|
111
|
+
jay-stack rebuild --url /products/blue-widget
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Renderer Server
|
|
115
|
+
|
|
116
|
+
The renderer server (`jay-stack serve --role=renderer`) hosts webhook endpoints and a rebuild API:
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
POST /_jay/webhooks/:name — Webhook handler (plugin-defined)
|
|
120
|
+
POST /_jay/rebuild — Programmatic rebuild { contract, route, url, params }
|
|
121
|
+
GET /_jay/status — Health check + build info
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The `POST /_jay/rebuild` endpoint accepts one of `contract`, `route`, or `url` in the JSON body.
|