@jay-framework/jay-stack-cli 0.17.4 → 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/developer/render-results.md +37 -0
- package/agent-kit-template/developer/routing.md +25 -0
- package/agent-kit-template/devops/INSTRUCTIONS.md +8 -7
- package/agent-kit-template/devops/fetch-handler.md +66 -3
- package/agent-kit-template/devops/serving-modes.md +27 -0
- package/agent-kit-template/plugin/INSTRUCTIONS.md +1 -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-structure.md +26 -2
- package/agent-kit-template/plugin/plugin-structure.md +15 -0
- package/agent-kit-template/plugin/render-results.md +37 -0
- package/dist/index.js +172 -10
- package/package.json +11 -11
|
@@ -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 page 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
|
+
{ 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:
|
|
@@ -160,6 +160,31 @@ URL query parameters (`?page=2&sort=price`) are available in the **fast render p
|
|
|
160
160
|
- Not available in the slow phase (compile error) — slow results are cached by path params only
|
|
161
161
|
- In the interactive phase, use `new URLSearchParams(window.location.search)` directly
|
|
162
162
|
|
|
163
|
+
## Cookies
|
|
164
|
+
|
|
165
|
+
HTTP cookies are available in the **fast render phase only** via `props.cookies`:
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
.withFastRender(async (props, carryForward, memberService) => {
|
|
169
|
+
const token = props.cookies['session-token'];
|
|
170
|
+
if (!token) return redirect3xx(302, '/login');
|
|
171
|
+
|
|
172
|
+
const member = await memberService.validate(token);
|
|
173
|
+
if (!member) return redirect3xx(302, '/login');
|
|
174
|
+
|
|
175
|
+
return phaseOutput(
|
|
176
|
+
{ memberName: member.name },
|
|
177
|
+
{},
|
|
178
|
+
{ responseHeaders: { 'Cache-Control': 'no-store' } },
|
|
179
|
+
);
|
|
180
|
+
})
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
- `props.cookies` is `Record<string, string>` — empty `{}` when no cookies
|
|
184
|
+
- Not available in the slow phase (compile error) — same as query params
|
|
185
|
+
- In the interactive phase, use `document.cookie` directly
|
|
186
|
+
- To set response headers (e.g. `Cache-Control: no-store`), use `responseHeaders` in `phaseOutput()` options
|
|
187
|
+
|
|
163
188
|
## Plugin Routes
|
|
164
189
|
|
|
165
190
|
Plugins can provide their own pages via `routes` in `plugin.yaml`. These are backoffice tools, admin dashboards, or editors with boxed designs that don't need per-site customization.
|
|
@@ -9,15 +9,16 @@ The devops role handles the production lifecycle: building artifacts, configurin
|
|
|
9
9
|
## Workflow
|
|
10
10
|
|
|
11
11
|
1. **Build** — `jay-stack build` to compile all pages into production artifacts
|
|
12
|
-
2. **Deploy** — upload `frontend/` to CDN, deploy `backend/` to server container
|
|
12
|
+
2. **Deploy** — upload `frontend/` to CDN, deploy `backend/` to server container. Plugins can provide deploy commands via `jay-stack run <plugin>/deploy`
|
|
13
13
|
3. **Serve** — start the production server with environment-appropriate flags
|
|
14
14
|
4. **Invalidate** — rebuild specific pages when data changes
|
|
15
|
+
5. **Admin** — run plugin CLI commands via `jay-stack run <plugin>/<command>` (media upload, data sync, cache purge)
|
|
15
16
|
|
|
16
17
|
## Guides
|
|
17
18
|
|
|
18
|
-
| File | Topic
|
|
19
|
-
| ------------------------------------------ |
|
|
20
|
-
| [production-build.md](production-build.md) | Build pipeline, output structure, frontend/backend split
|
|
21
|
-
| [serving-modes.md](serving-modes.md) | Self-hosted, CDN, BaaS (fetch handler), CLI flags
|
|
22
|
-
| [fetch-handler.md](fetch-handler.md) |
|
|
23
|
-
| [invalidation.md](invalidation.md) | Rebuild, renderer server, cleanup
|
|
19
|
+
| File | Topic |
|
|
20
|
+
| ------------------------------------------ | ---------------------------------------------------------- |
|
|
21
|
+
| [production-build.md](production-build.md) | Build pipeline, output structure, frontend/backend split |
|
|
22
|
+
| [serving-modes.md](serving-modes.md) | Self-hosted, CDN, BaaS (fetch handler), CLI flags |
|
|
23
|
+
| [fetch-handler.md](fetch-handler.md) | Fetch handler, ArtifactStore interface, BaaS custom stores |
|
|
24
|
+
| [invalidation.md](invalidation.md) | Rebuild, renderer server, cleanup |
|
|
@@ -23,23 +23,45 @@ const handler = createJayFetchHandler(options);
|
|
|
23
23
|
|
|
24
24
|
```typescript
|
|
25
25
|
interface JayFetchHandlerOptions {
|
|
26
|
-
|
|
26
|
+
// Artifact source (one required)
|
|
27
|
+
backendDir?: string; // Path to build/v{n}/backend/ (creates FilesystemArtifactStore)
|
|
28
|
+
artifactStore?: ArtifactStore; // Custom store for non-filesystem backends (DL#143)
|
|
29
|
+
|
|
27
30
|
staticBaseUrl?: string; // Base URL for browser assets (default: '/')
|
|
28
31
|
frontendDir?: string; // When set, serves static files from this directory
|
|
32
|
+
|
|
33
|
+
// Pre-imported modules — for bundled entry.mjs (DL#143)
|
|
34
|
+
plugins?: PreImportedPlugin[];
|
|
35
|
+
actionModules?: Array<{ module: Record<string, unknown>; name: string }>;
|
|
29
36
|
}
|
|
30
37
|
```
|
|
31
38
|
|
|
32
39
|
| Option | Required | Description |
|
|
33
40
|
| --------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------ |
|
|
34
|
-
| `backendDir` |
|
|
41
|
+
| `backendDir` | \* | Path to the backend build directory. Creates a `FilesystemArtifactStore` internally |
|
|
42
|
+
| `artifactStore` | \* | Custom `ArtifactStore` implementation (e.g., cloud storage). Use instead of `backendDir` |
|
|
35
43
|
| `staticBaseUrl` | No | URL prefix for import maps, CSS links, and client bundles. Set to your CDN URL for external hosting. Default: `/` |
|
|
36
44
|
| `frontendDir` | No | When provided, the handler serves static files from this directory. Omit for CDN deployments where static files are hosted elsewhere |
|
|
45
|
+
| `plugins` | No | Pre-imported plugin init modules. Bypasses filesystem discovery — use for bundled deployments |
|
|
46
|
+
| `actionModules` | No | Pre-imported action modules. Bypasses filesystem discovery — use for bundled deployments |
|
|
37
47
|
|
|
38
|
-
|
|
48
|
+
\* One of `backendDir` or `artifactStore` is required.
|
|
49
|
+
|
|
50
|
+
## Usage — Self-Hosted
|
|
39
51
|
|
|
40
52
|
```typescript
|
|
41
53
|
import { createJayFetchHandler } from '@jay-framework/jay-fetch-handler';
|
|
42
54
|
|
|
55
|
+
const handler = createJayFetchHandler({
|
|
56
|
+
backendDir: './build/v1/backend',
|
|
57
|
+
staticBaseUrl: '/',
|
|
58
|
+
frontendDir: './build/v1/frontend',
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Usage — CDN Mode
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
43
65
|
const handler = createJayFetchHandler({
|
|
44
66
|
backendDir: './build/v1/backend',
|
|
45
67
|
staticBaseUrl: 'https://static.parastorage.com/services/my-app/1.0.0/',
|
|
@@ -50,6 +72,47 @@ export default { fetch: handler };
|
|
|
50
72
|
|
|
51
73
|
The BaaS runtime calls `handler(request)` for each incoming HTTP request.
|
|
52
74
|
|
|
75
|
+
## Usage — BaaS with Custom Artifact Store
|
|
76
|
+
|
|
77
|
+
For deployments where backend files are not on the local filesystem (e.g., stored in a cloud database), provide a custom `ArtifactStore` and pre-imported modules:
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import { createJayFetchHandler } from '@jay-framework/jay-fetch-handler';
|
|
81
|
+
import { WixDataArtifactStore } from '@jay-framework/wix-baas-adapter';
|
|
82
|
+
import { init as wixStoresInit } from '@jay-framework/wix-stores';
|
|
83
|
+
import * as wixStoresModule from '@jay-framework/wix-stores';
|
|
84
|
+
|
|
85
|
+
const handler = createJayFetchHandler({
|
|
86
|
+
artifactStore: new WixDataArtifactStore({
|
|
87
|
+
collectionId: 'jay-backend-files',
|
|
88
|
+
cacheDir: '/tmp/jay-backend',
|
|
89
|
+
}),
|
|
90
|
+
staticBaseUrl: 'https://static.parastorage.com/services/my-app/1.0.0/',
|
|
91
|
+
plugins: [{ name: 'wix-stores', init: wixStoresInit }],
|
|
92
|
+
actionModules: [{ module: wixStoresModule, name: 'wix-stores' }],
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
export default { fetch: handler };
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The `ArtifactStore` interface:
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
interface ArtifactStore {
|
|
102
|
+
readManifest(): Promise<RouteManifest>;
|
|
103
|
+
readCacheData(relativePath: string): Promise<CacheEntry>;
|
|
104
|
+
readPagePartsConfig(relativePath: string): Promise<any>;
|
|
105
|
+
loadServerElement(relativePath: string): Promise<ServerElementModule>;
|
|
106
|
+
loadModule(modulePath: string, local?: boolean): Promise<any>;
|
|
107
|
+
getAssetPath(relativePath: string): string;
|
|
108
|
+
getBuildDir(): string;
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
`loadModule` handles all module loading — server elements, page components, headless components. The `local` flag indicates whether the path is relative to the build directory (`true`) or an npm package (`false`). For filesystem deployments, local modules resolve from `basePath` and npm modules use bare `import()`. BaaS implementations resolve all modules from their pre-bundled registry, ignoring the `local` flag.
|
|
113
|
+
|
|
114
|
+
For serve-only imports (no build-time dependencies), use `@jay-framework/production-server/serve`.
|
|
115
|
+
|
|
53
116
|
## Usage — Cloudflare Workers
|
|
54
117
|
|
|
55
118
|
```typescript
|
|
@@ -43,6 +43,33 @@ jay-stack serve --port 4000 \
|
|
|
43
43
|
|
|
44
44
|
The server generates import maps, CSS links, and client bundle URLs prefixed with `--static-base-url`. It does not serve static files itself.
|
|
45
45
|
|
|
46
|
+
## BaaS Mode (Custom Artifact Store)
|
|
47
|
+
|
|
48
|
+
For platforms where backend files are not on the local filesystem (e.g., stored in a cloud database), use `createJayFetchHandler` with a custom `ArtifactStore` and pre-imported modules:
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import { createJayFetchHandler } from '@jay-framework/jay-fetch-handler';
|
|
52
|
+
|
|
53
|
+
const handler = createJayFetchHandler({
|
|
54
|
+
artifactStore: customStore, // Custom ArtifactStore implementation
|
|
55
|
+
staticBaseUrl: 'https://cdn.example.com/app/1.0.0/',
|
|
56
|
+
plugins: [
|
|
57
|
+
// Pre-imported plugin init modules
|
|
58
|
+
{ name: 'my-plugin', init: myPluginInit },
|
|
59
|
+
],
|
|
60
|
+
actionModules: [
|
|
61
|
+
// Pre-imported action modules
|
|
62
|
+
{ module: myPluginModule, name: 'my-plugin' },
|
|
63
|
+
],
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
export default { fetch: handler };
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Pre-imported modules bypass filesystem discovery — the entry file bundles everything with esbuild. See [fetch-handler.md](fetch-handler.md) for the `ArtifactStore` interface and full BaaS example.
|
|
70
|
+
|
|
71
|
+
For serve-only imports without build-time dependencies, use `@jay-framework/production-server/serve`.
|
|
72
|
+
|
|
46
73
|
## CLI Flags
|
|
47
74
|
|
|
48
75
|
### jay-stack serve
|
|
@@ -33,6 +33,7 @@ A plugin provides headless components (data + interactions, no UI) that project
|
|
|
33
33
|
| [services-guide.md](services-guide.md) | createJayService, makeJayInit |
|
|
34
34
|
| [plugin-routes.md](plugin-routes.md) | Plugin-provided pages: routes, jay-html templates, page components |
|
|
35
35
|
| [seo-guide.md](seo-guide.md) | SEO head tags: title, meta, OG, canonical via phaseOutput |
|
|
36
|
+
| [commands-guide.md](commands-guide.md) | makeCliCommand, .jay-command files, CONSOLE_CONTEXT, jay-stack run |
|
|
36
37
|
| [validation.md](validation.md) | jay-stack validate-plugin usage |
|
|
37
38
|
| [dev-server-service.md](dev-server-service.md) | Dev server service API: routes, params, freeze management |
|
|
38
39
|
| `../references/<plugin>/` | Plugin reference data |
|
|
@@ -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`.
|
|
@@ -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
|
|
@@ -55,6 +55,12 @@ routes:
|
|
|
55
55
|
component: ./pages/admin/page.ts
|
|
56
56
|
description: Admin dashboard with product stats
|
|
57
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
|
+
|
|
58
64
|
setup:
|
|
59
65
|
handler: setup-handler
|
|
60
66
|
references: references-handler
|
|
@@ -172,6 +178,13 @@ services:
|
|
|
172
178
|
|
|
173
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.
|
|
174
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
|
+
|
|
175
188
|
### Setup Fields
|
|
176
189
|
|
|
177
190
|
- `handler` — Setup handler for `jay-stack setup` (handles config, credentials)
|
|
@@ -193,6 +206,8 @@ my-plugin/
|
|
|
193
206
|
│ ├── actions/
|
|
194
207
|
│ │ ├── search-products.jay-action
|
|
195
208
|
│ │ └── add-to-cart.jay-action
|
|
209
|
+
│ ├── commands/
|
|
210
|
+
│ │ └── upload-public.jay-command
|
|
196
211
|
│ ├── webhooks/
|
|
197
212
|
│ │ └── on-product-change.ts
|
|
198
213
|
│ ├── components/
|
|
@@ -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:
|
package/dist/index.js
CHANGED
|
@@ -2530,6 +2530,11 @@ async function startDevServer(options = {}) {
|
|
|
2530
2530
|
routes.forEach((route) => {
|
|
2531
2531
|
app.get(route.path, route.handler);
|
|
2532
2532
|
});
|
|
2533
|
+
service.attachRouteRegistrar((added) => {
|
|
2534
|
+
added.forEach((route) => {
|
|
2535
|
+
app.get(route.path, route.handler);
|
|
2536
|
+
});
|
|
2537
|
+
});
|
|
2533
2538
|
generatePageDefinitionFiles(routes, jayOptions.tsConfigFilePath, process.cwd());
|
|
2534
2539
|
httpServer.listen(devServerPort, () => {
|
|
2535
2540
|
log.important(`🚀 Jay Stack dev server started successfully!`);
|
|
@@ -2643,7 +2648,7 @@ async function resolveProductionContext(projectPath, versionOverride) {
|
|
|
2643
2648
|
pagesBase = jayConfig?.devServer?.pagesBase || pagesBase;
|
|
2644
2649
|
} catch {
|
|
2645
2650
|
}
|
|
2646
|
-
const version = versionOverride
|
|
2651
|
+
const version = versionOverride || await resolveVersionFromPackageJson(resolvedPath);
|
|
2647
2652
|
return {
|
|
2648
2653
|
resolvedPath,
|
|
2649
2654
|
pagesRoot: path$1.resolve(resolvedPath, pagesBase),
|
|
@@ -2658,14 +2663,11 @@ async function resolveVersionFromPackageJson(projectRoot) {
|
|
|
2658
2663
|
await fs$1.readFile(path$1.join(projectRoot, "package.json"), "utf-8")
|
|
2659
2664
|
);
|
|
2660
2665
|
if (pkgJson.version) {
|
|
2661
|
-
|
|
2662
|
-
const minor = parseInt(pkgJson.version.split(".")[1] || "0", 10);
|
|
2663
|
-
const patch = parseInt(pkgJson.version.split(".")[2] || "0", 10);
|
|
2664
|
-
return major * 1e4 + minor * 100 + patch;
|
|
2666
|
+
return pkgJson.version;
|
|
2665
2667
|
}
|
|
2666
2668
|
} catch {
|
|
2667
2669
|
}
|
|
2668
|
-
return 1;
|
|
2670
|
+
return "1";
|
|
2669
2671
|
}
|
|
2670
2672
|
function initLogger(verbose) {
|
|
2671
2673
|
const logLevel = verbose ? "verbose" : "info";
|
|
@@ -2750,6 +2752,7 @@ const runProduction = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defin
|
|
|
2750
2752
|
__proto__: null,
|
|
2751
2753
|
initLogger,
|
|
2752
2754
|
resolveProductionContext,
|
|
2755
|
+
resolveVersionFromPackageJson,
|
|
2753
2756
|
runBuild,
|
|
2754
2757
|
runRebuild,
|
|
2755
2758
|
runServe
|
|
@@ -3776,9 +3779,6 @@ const SKIP_ATTRS = /* @__PURE__ */ new Set([
|
|
|
3776
3779
|
"if",
|
|
3777
3780
|
"ref",
|
|
3778
3781
|
"trackBy",
|
|
3779
|
-
"slowForEach",
|
|
3780
|
-
"jayIndex",
|
|
3781
|
-
"jayTrackBy",
|
|
3782
3782
|
"when-resolved",
|
|
3783
3783
|
"when-loading",
|
|
3784
3784
|
"when-rejected",
|
|
@@ -4502,7 +4502,7 @@ function printValidationResult(result, verbose) {
|
|
|
4502
4502
|
logger.important(chalk.red(`${result.errors.length} errors found.`));
|
|
4503
4503
|
}
|
|
4504
4504
|
}
|
|
4505
|
-
const ALL_ROLES = ["designer", "developer", "plugin"];
|
|
4505
|
+
const ALL_ROLES = ["designer", "developer", "plugin", "devops"];
|
|
4506
4506
|
async function runAgentKit(options) {
|
|
4507
4507
|
const projectRoot = process.cwd();
|
|
4508
4508
|
const { initErrors, viteServer } = await runMaterialize(
|
|
@@ -5029,6 +5029,161 @@ async function runSetup(pluginFilter, options, projectRoot, initializeServices)
|
|
|
5029
5029
|
}
|
|
5030
5030
|
}
|
|
5031
5031
|
}
|
|
5032
|
+
async function runCommand(commandRef, rawArgs, options, projectRoot, initializeServices) {
|
|
5033
|
+
let viteServer;
|
|
5034
|
+
try {
|
|
5035
|
+
const {
|
|
5036
|
+
discoverPluginCommands,
|
|
5037
|
+
commandSchemaToFlags,
|
|
5038
|
+
parseInputFromFlags,
|
|
5039
|
+
executePluginCommand
|
|
5040
|
+
} = await import("@jay-framework/stack-server-runtime");
|
|
5041
|
+
const commands = await discoverPluginCommands({
|
|
5042
|
+
projectRoot,
|
|
5043
|
+
verbose: options.verbose
|
|
5044
|
+
});
|
|
5045
|
+
if (options.list || !commandRef) {
|
|
5046
|
+
printCommandList(commands);
|
|
5047
|
+
return;
|
|
5048
|
+
}
|
|
5049
|
+
const slashIndex = commandRef.indexOf("/");
|
|
5050
|
+
if (slashIndex === -1) {
|
|
5051
|
+
getLogger().error(
|
|
5052
|
+
chalk.red("Invalid command reference. Use format: <plugin>/<command>")
|
|
5053
|
+
);
|
|
5054
|
+
getLogger().error(chalk.gray("Example: jay-stack run media/upload-public"));
|
|
5055
|
+
process.exit(1);
|
|
5056
|
+
}
|
|
5057
|
+
const pluginName = commandRef.substring(0, slashIndex);
|
|
5058
|
+
const commandName = commandRef.substring(slashIndex + 1);
|
|
5059
|
+
const command = commands.find(
|
|
5060
|
+
(c) => (c.pluginName === pluginName || c.packageName === pluginName) && c.commandName === commandName
|
|
5061
|
+
);
|
|
5062
|
+
if (!command) {
|
|
5063
|
+
getLogger().error(chalk.red(`Command "${commandRef}" not found.`));
|
|
5064
|
+
const pluginCommands = commands.filter(
|
|
5065
|
+
(c) => c.pluginName === pluginName || c.packageName === pluginName
|
|
5066
|
+
);
|
|
5067
|
+
if (pluginCommands.length > 0) {
|
|
5068
|
+
getLogger().error(`Available commands for ${pluginName}:`);
|
|
5069
|
+
for (const c of pluginCommands) {
|
|
5070
|
+
getLogger().error(
|
|
5071
|
+
` ${c.commandName}${c.metadata?.description ? " " + c.metadata.description : ""}`
|
|
5072
|
+
);
|
|
5073
|
+
}
|
|
5074
|
+
} else if (commands.length > 0) {
|
|
5075
|
+
getLogger().error("Available plugins with commands:");
|
|
5076
|
+
const plugins = [...new Set(commands.map((c) => c.pluginName))];
|
|
5077
|
+
for (const p of plugins) {
|
|
5078
|
+
getLogger().error(` ${p}`);
|
|
5079
|
+
}
|
|
5080
|
+
} else {
|
|
5081
|
+
getLogger().error("No plugins with CLI commands found.");
|
|
5082
|
+
}
|
|
5083
|
+
process.exit(1);
|
|
5084
|
+
}
|
|
5085
|
+
let input = {};
|
|
5086
|
+
if (command.metadata?.inputSchema) {
|
|
5087
|
+
const flagDefs = commandSchemaToFlags(command.metadata.inputSchema);
|
|
5088
|
+
const rawOptions = parseRawFlags(rawArgs, flagDefs);
|
|
5089
|
+
input = parseInputFromFlags(rawOptions, command.metadata.inputSchema);
|
|
5090
|
+
}
|
|
5091
|
+
if (options.verbose) {
|
|
5092
|
+
getLogger().info("Starting Vite for TypeScript support...");
|
|
5093
|
+
}
|
|
5094
|
+
viteServer = await createViteForCli({ projectRoot });
|
|
5095
|
+
await initializeServices(projectRoot, viteServer);
|
|
5096
|
+
const { registerService } = await import("@jay-framework/stack-server-runtime");
|
|
5097
|
+
const { CONSOLE_CONTEXT } = await import("@jay-framework/fullstack-component");
|
|
5098
|
+
const jayConfig = loadConfig();
|
|
5099
|
+
const publicFolder = path$1.resolve(
|
|
5100
|
+
projectRoot,
|
|
5101
|
+
jayConfig.devServer?.publicFolder || "public"
|
|
5102
|
+
);
|
|
5103
|
+
const version = await resolveVersionFromPackageJson(projectRoot);
|
|
5104
|
+
const buildRoot = path$1.resolve(projectRoot, `build/v${version}`);
|
|
5105
|
+
registerService(CONSOLE_CONTEXT, {
|
|
5106
|
+
projectRoot,
|
|
5107
|
+
publicFolder,
|
|
5108
|
+
build: {
|
|
5109
|
+
frontend: path$1.join(buildRoot, "frontend"),
|
|
5110
|
+
backend: path$1.join(buildRoot, "backend")
|
|
5111
|
+
},
|
|
5112
|
+
verbose: options.verbose ?? false,
|
|
5113
|
+
log: (msg) => getLogger().important(msg),
|
|
5114
|
+
warn: (msg) => getLogger().warn(chalk.yellow(msg)),
|
|
5115
|
+
error: (msg) => getLogger().error(chalk.red(msg))
|
|
5116
|
+
});
|
|
5117
|
+
if (options.verbose) {
|
|
5118
|
+
getLogger().info(`Executing ${command.pluginName}/${command.commandName}...`);
|
|
5119
|
+
}
|
|
5120
|
+
const result = await executePluginCommand(command, input, viteServer);
|
|
5121
|
+
if (!result.success) {
|
|
5122
|
+
process.exit(1);
|
|
5123
|
+
}
|
|
5124
|
+
} catch (error) {
|
|
5125
|
+
getLogger().error(chalk.red("Command failed:") + " " + error.message);
|
|
5126
|
+
if (options.verbose) {
|
|
5127
|
+
getLogger().error(error.stack);
|
|
5128
|
+
}
|
|
5129
|
+
process.exit(1);
|
|
5130
|
+
} finally {
|
|
5131
|
+
if (viteServer) {
|
|
5132
|
+
await viteServer.close();
|
|
5133
|
+
}
|
|
5134
|
+
}
|
|
5135
|
+
}
|
|
5136
|
+
function printCommandList(commands) {
|
|
5137
|
+
const logger = getLogger();
|
|
5138
|
+
if (commands.length === 0) {
|
|
5139
|
+
logger.important(chalk.gray("No plugins with CLI commands found."));
|
|
5140
|
+
return;
|
|
5141
|
+
}
|
|
5142
|
+
logger.important("\nAvailable plugin commands:\n");
|
|
5143
|
+
const byPlugin = /* @__PURE__ */ new Map();
|
|
5144
|
+
for (const cmd of commands) {
|
|
5145
|
+
if (!byPlugin.has(cmd.pluginName))
|
|
5146
|
+
byPlugin.set(cmd.pluginName, []);
|
|
5147
|
+
byPlugin.get(cmd.pluginName).push(cmd);
|
|
5148
|
+
}
|
|
5149
|
+
for (const [pluginName, cmds] of byPlugin) {
|
|
5150
|
+
logger.important(chalk.bold(` ${pluginName}`));
|
|
5151
|
+
for (const cmd of cmds) {
|
|
5152
|
+
const desc = cmd.metadata?.description ? ` ${cmd.metadata.description}` : "";
|
|
5153
|
+
logger.important(` ${cmd.commandName}${desc}`);
|
|
5154
|
+
}
|
|
5155
|
+
logger.important("");
|
|
5156
|
+
}
|
|
5157
|
+
}
|
|
5158
|
+
function parseRawFlags(args, flagDefs) {
|
|
5159
|
+
const result = {};
|
|
5160
|
+
const booleanFlags = new Set(
|
|
5161
|
+
flagDefs.filter((f) => f.type === "boolean").map((f) => {
|
|
5162
|
+
const match = f.flag.match(/^--(\S+)/);
|
|
5163
|
+
return match ? match[1] : "";
|
|
5164
|
+
})
|
|
5165
|
+
);
|
|
5166
|
+
let i = 0;
|
|
5167
|
+
while (i < args.length) {
|
|
5168
|
+
const arg = args[i];
|
|
5169
|
+
if (arg.startsWith("--")) {
|
|
5170
|
+
const name = arg.slice(2);
|
|
5171
|
+
if (booleanFlags.has(name)) {
|
|
5172
|
+
result[name] = true;
|
|
5173
|
+
i++;
|
|
5174
|
+
} else if (i + 1 < args.length && !args[i + 1].startsWith("--")) {
|
|
5175
|
+
result[name] = args[i + 1];
|
|
5176
|
+
i += 2;
|
|
5177
|
+
} else {
|
|
5178
|
+
result[name] = true;
|
|
5179
|
+
i++;
|
|
5180
|
+
}
|
|
5181
|
+
} else {
|
|
5182
|
+
i++;
|
|
5183
|
+
}
|
|
5184
|
+
}
|
|
5185
|
+
return result;
|
|
5186
|
+
}
|
|
5032
5187
|
const program = new Command();
|
|
5033
5188
|
program.name("jay-stack").description("Jay Stack CLI - Development server and plugin validation").version("0.9.0");
|
|
5034
5189
|
program.command("dev").description("Start the Jay Stack development server").option("-p, --path <path>", "Project root (default: cwd)").option("-v, --verbose", "Enable verbose logging output").option("-q, --quiet", "Suppress all non-error output").option("--test-mode", "Enable test endpoints (/_jay/health, /_jay/shutdown)").option("--timeout <seconds>", "Auto-shutdown after N seconds (implies --test-mode)", parseInt).action(async (options) => {
|
|
@@ -5115,6 +5270,13 @@ program.command("agent-kit").description("Prepare agent kit: materialize contrac
|
|
|
5115
5270
|
program.command("action <plugin/action>").description("Run a plugin action (e.g., jay-stack action wix-stores/searchProducts)").option("--input <json>", "JSON input for the action (default: {})").option("--yaml", "Output result as YAML instead of JSON").option("-v, --verbose", "Show detailed output").action(async (actionRef, options) => {
|
|
5116
5271
|
await runAction(actionRef, options, process.cwd(), initializeServicesForCli);
|
|
5117
5272
|
});
|
|
5273
|
+
program.command("run [plugin/command]").description("Run a plugin CLI command (e.g., jay-stack run media/upload-public)").option("--list", "List all available plugin commands").option("-v, --verbose", "Show detailed output").allowUnknownOption().allowExcessArguments().action(async (commandRef, options, command) => {
|
|
5274
|
+
const rawArgs = command.parent?.rawArgs.slice(3) ?? [];
|
|
5275
|
+
const extraArgs = rawArgs.filter(
|
|
5276
|
+
(a) => a !== commandRef && a !== "-v" && a !== "--verbose" && a !== "--list"
|
|
5277
|
+
);
|
|
5278
|
+
await runCommand(commandRef, extraArgs, options, process.cwd(), initializeServicesForCli);
|
|
5279
|
+
});
|
|
5118
5280
|
program.command("params <plugin/contract>").description("Discover load param values for a contract (NDJSON or YAML)").option("--yaml", "Output as YAML instead of NDJSON").option("-v, --verbose", "Show detailed output").action(async (contractRef, options) => {
|
|
5119
5281
|
await runParams(contractRef, options, process.cwd(), initializeServicesForCli);
|
|
5120
5282
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jay-framework/jay-stack-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -24,15 +24,15 @@
|
|
|
24
24
|
"test:watch": "vitest"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@jay-framework/compiler-jay-html": "^0.
|
|
28
|
-
"@jay-framework/compiler-shared": "^0.
|
|
29
|
-
"@jay-framework/dev-server": "^0.
|
|
30
|
-
"@jay-framework/editor-server": "^0.
|
|
31
|
-
"@jay-framework/fullstack-component": "^0.
|
|
32
|
-
"@jay-framework/logger": "^0.
|
|
33
|
-
"@jay-framework/plugin-validator": "^0.
|
|
34
|
-
"@jay-framework/production-server": "^0.
|
|
35
|
-
"@jay-framework/stack-server-runtime": "^0.
|
|
27
|
+
"@jay-framework/compiler-jay-html": "^0.18.0",
|
|
28
|
+
"@jay-framework/compiler-shared": "^0.18.0",
|
|
29
|
+
"@jay-framework/dev-server": "^0.18.0",
|
|
30
|
+
"@jay-framework/editor-server": "^0.18.0",
|
|
31
|
+
"@jay-framework/fullstack-component": "^0.18.0",
|
|
32
|
+
"@jay-framework/logger": "^0.18.0",
|
|
33
|
+
"@jay-framework/plugin-validator": "^0.18.0",
|
|
34
|
+
"@jay-framework/production-server": "^0.18.0",
|
|
35
|
+
"@jay-framework/stack-server-runtime": "^0.18.0",
|
|
36
36
|
"chalk": "^4.1.2",
|
|
37
37
|
"commander": "^14.0.0",
|
|
38
38
|
"express": "^5.0.1",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"yaml": "^2.3.4"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@jay-framework/dev-environment": "^0.
|
|
46
|
+
"@jay-framework/dev-environment": "^0.18.0",
|
|
47
47
|
"@types/express": "^5.0.2",
|
|
48
48
|
"@types/node": "^22.15.21",
|
|
49
49
|
"nodemon": "^3.0.3",
|