@jay-framework/jay-stack-cli 0.16.3 → 0.16.5
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/page-components.md +10 -0
- package/agent-kit-template/plugin/component-structure.md +8 -5
- package/agent-kit-template/plugin/contracts-guide.md +9 -1
- package/agent-kit-template/plugin/plugin-structure.md +81 -0
- package/dist/index.js +99 -15
- package/package.json +10 -10
|
@@ -125,6 +125,16 @@ src/pages/products/[slug]/
|
|
|
125
125
|
|
|
126
126
|
The jay-html template uses unprefixed bindings for page data and key-prefixed bindings for plugin data.
|
|
127
127
|
|
|
128
|
+
## Note on `.withClientDefaults()`
|
|
129
|
+
|
|
130
|
+
`withClientDefaults` is only needed when a headless component is used **inside a `forEach`** and new items can be added on the client (e.g., "Add Item" button). It provides initial ViewState for instances that don't exist during SSR.
|
|
131
|
+
|
|
132
|
+
You do NOT need it for:
|
|
133
|
+
|
|
134
|
+
- Components outside forEach — `withFastRender` provides SSR initial state
|
|
135
|
+
- Components inside a conditional (`if=`) — server data is computed for all discovered instances regardless of the condition's SSR value
|
|
136
|
+
- Static forEach where all items come from the server
|
|
137
|
+
|
|
128
138
|
## Builder API Reference
|
|
129
139
|
|
|
130
140
|
See the plugin [component-structure.md](../plugin/component-structure.md) for the full builder API: `.withProps()`, `.withServices()`, `.withContexts()`, phase rendering, and render results.
|
|
@@ -96,17 +96,20 @@ Runs on each request. Receives props (including `query` for query parameters) an
|
|
|
96
96
|
})
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
-
### `.withClientDefaults(fn)` —
|
|
99
|
+
### `.withClientDefaults(fn)` — Defaults for dynamically created forEach items
|
|
100
100
|
|
|
101
|
-
|
|
101
|
+
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.
|
|
102
102
|
|
|
103
103
|
```typescript
|
|
104
|
-
.withClientDefaults(() => ({
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
.withClientDefaults((props) => ({
|
|
105
|
+
viewState: { label: `Item ${props.itemId}`, value: 0 },
|
|
106
|
+
carryForward: {},
|
|
107
107
|
}))
|
|
108
108
|
```
|
|
109
109
|
|
|
110
|
+
**When to use:** Component inside `forEach` + items can be added client-side.
|
|
111
|
+
**When NOT to use:** Components outside forEach, or forEach with server-only items. Use `withFastRender` instead for SSR initial state.
|
|
112
|
+
|
|
110
113
|
### `.withInteractive(ComponentConstructor)` — Client-side logic
|
|
111
114
|
|
|
112
115
|
The interactive phase runs in the browser. Use hooks here (see component-state.md):
|
|
@@ -83,7 +83,15 @@ Linked (reference another contract file):
|
|
|
83
83
|
```yaml
|
|
84
84
|
- tag: author
|
|
85
85
|
type: sub-contract
|
|
86
|
-
link: ./author.jay-contract
|
|
86
|
+
link: ./author.jay-contract # relative path (same package)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
For dynamic/materialized contracts linking to static contracts in a plugin package, use the package path:
|
|
90
|
+
|
|
91
|
+
```yaml
|
|
92
|
+
- tag: gallery
|
|
93
|
+
type: sub-contract
|
|
94
|
+
link: '@my-org/my-plugin/media-gallery' # package path (cross-directory)
|
|
87
95
|
```
|
|
88
96
|
|
|
89
97
|
### `sub-contract` with `repeated: true` — Arrays
|
|
@@ -19,6 +19,16 @@ contracts:
|
|
|
19
19
|
component: productSearch
|
|
20
20
|
description: Product listing with filters and pagination
|
|
21
21
|
|
|
22
|
+
dynamic_contracts:
|
|
23
|
+
# Single contract: prefix used as the contract name directly
|
|
24
|
+
- prefix: product-page
|
|
25
|
+
component: productPage
|
|
26
|
+
generator: productPageContractGenerator
|
|
27
|
+
# Multiple contracts: prefix/name format (e.g., list/recipes, list/articles)
|
|
28
|
+
- prefix: list
|
|
29
|
+
component: dynamicList
|
|
30
|
+
generator: listContractGenerator
|
|
31
|
+
|
|
22
32
|
actions:
|
|
23
33
|
- name: searchProducts
|
|
24
34
|
action: search-products.jay-action
|
|
@@ -56,6 +66,49 @@ setup:
|
|
|
56
66
|
- `component` — Export name of the component (e.g., `productPage`)
|
|
57
67
|
- `description` — What this component does and when to use it
|
|
58
68
|
|
|
69
|
+
### Dynamic Contract Entry Fields
|
|
70
|
+
|
|
71
|
+
Dynamic contracts are generated at setup time from site-specific data (e.g., CMS collection schemas, extended product fields).
|
|
72
|
+
|
|
73
|
+
- `prefix` — Identifier for this dynamic contract group. Used as the contract name for single contracts, or as `prefix/name` for multiple.
|
|
74
|
+
- `component` — Export name of the headless component that serves these contracts
|
|
75
|
+
- `generator` — Export name of the generator function that produces contract YAML
|
|
76
|
+
|
|
77
|
+
**Single contract** — generator returns one `{ yaml }` without a name:
|
|
78
|
+
|
|
79
|
+
```yaml
|
|
80
|
+
dynamic_contracts:
|
|
81
|
+
- prefix: product-page
|
|
82
|
+
component: productPage
|
|
83
|
+
generator: productPageContractGenerator
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Referenced as `contract="product-page"` in jay-html.
|
|
87
|
+
|
|
88
|
+
**Multiple contracts** — generator yields `{ name, yaml }` for each:
|
|
89
|
+
|
|
90
|
+
```yaml
|
|
91
|
+
dynamic_contracts:
|
|
92
|
+
- prefix: list
|
|
93
|
+
component: dynamicList
|
|
94
|
+
generator: listContractGenerator
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Referenced as `contract="list/recipes"`, `contract="list/articles"` etc.
|
|
98
|
+
|
|
99
|
+
Contracts are materialized by `jay-stack agent-kit` or `jay-stack setup` and stored in `agent-kit/materialized-contracts/`.
|
|
100
|
+
|
|
101
|
+
**Linking to static contracts from generated YAML** — materialized contracts live in a different directory than the plugin source. Use the plugin's package path (not relative paths) for `link:` references to static contracts:
|
|
102
|
+
|
|
103
|
+
```yaml
|
|
104
|
+
# In the generated contract YAML:
|
|
105
|
+
tags:
|
|
106
|
+
- tag: gallery
|
|
107
|
+
type: sub-contract
|
|
108
|
+
link: '@my-org/my-plugin/media-gallery' # package path — works from any directory
|
|
109
|
+
# NOT: link: ./media-gallery # relative path — breaks in materialized location
|
|
110
|
+
```
|
|
111
|
+
|
|
59
112
|
### Action Entry Fields
|
|
60
113
|
|
|
61
114
|
- `name` — Action name (used with `jay-stack action <plugin>/<action>`)
|
|
@@ -271,6 +324,34 @@ my-plugin/
|
|
|
271
324
|
└── my-plugin-extending.md # How to extend the plugin
|
|
272
325
|
```
|
|
273
326
|
|
|
327
|
+
For NPM packages, include `agent-kit` in the `files` array:
|
|
328
|
+
|
|
329
|
+
```json
|
|
330
|
+
{
|
|
331
|
+
"files": ["dist", "plugin.yaml", "agent-kit"]
|
|
332
|
+
}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
No `plugin.yaml` declaration needed — the CLI discovers guides by scanning the `agent-kit/` directory. Files are copied as-is into the project's `agent-kit/{role}/`.
|
|
336
|
+
|
|
337
|
+
**File format convention:** The first line after the `#` heading is used as the description in the INSTRUCTIONS.md index table. Write it as a short sentence explaining when to use this guide:
|
|
338
|
+
|
|
339
|
+
```markdown
|
|
340
|
+
# Scroll Carousel
|
|
341
|
+
|
|
342
|
+
Horizontal slider with prev/next buttons and edge detection. Headless component — requires import.
|
|
343
|
+
|
|
344
|
+
## Import
|
|
345
|
+
|
|
346
|
+
...
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
The INSTRUCTIONS.md table will show:
|
|
350
|
+
|
|
351
|
+
```
|
|
352
|
+
| scroll-carousel.md | my-plugin | Horizontal slider with prev/next buttons and edge detection. Headless component — requires import. |
|
|
353
|
+
```
|
|
354
|
+
|
|
274
355
|
## Reference Declarations
|
|
275
356
|
|
|
276
357
|
Plugins can declare reference data generated by `jay-stack agent-kit`:
|
package/dist/index.js
CHANGED
|
@@ -8,15 +8,19 @@ import path from "path";
|
|
|
8
8
|
import fs, { promises } from "fs";
|
|
9
9
|
import YAML from "yaml";
|
|
10
10
|
import { getLogger, createDevLogger, setDevLogger } from "@jay-framework/logger";
|
|
11
|
-
import { parseJayFile, JAY_IMPORT_RESOLVER, generateElementDefinitionFile, ContractTagType, parseContract, generateElementFile, htmlElementTagNameMap } from "@jay-framework/compiler-jay-html";
|
|
11
|
+
import { parseJayFile, JAY_IMPORT_RESOLVER, generateElementDefinitionFile, ContractTagType, parseContract, generateElementFile, generateServerElementFile, htmlElementTagNameMap } from "@jay-framework/compiler-jay-html";
|
|
12
12
|
import { JAY_CONTRACT_EXTENSION, JAY_EXTENSION, resolvePluginManifest, LOCAL_PLUGIN_PATH, JayAtomicType, JayEnumType, loadPluginManifest, RuntimeMode, GenerateTarget } from "@jay-framework/compiler-shared";
|
|
13
|
-
import { listContracts, materializeContracts } from "@jay-framework/stack-server-runtime";
|
|
13
|
+
import { scanPlugins as scanPlugins$1, listContracts, materializeContracts } from "@jay-framework/stack-server-runtime";
|
|
14
14
|
import { listContracts as listContracts2, materializeContracts as materializeContracts2 } from "@jay-framework/stack-server-runtime";
|
|
15
15
|
import { Command } from "commander";
|
|
16
16
|
import chalk from "chalk";
|
|
17
17
|
import { createRequire } from "module";
|
|
18
18
|
import { glob } from "glob";
|
|
19
19
|
import { parse } from "node-html-parser";
|
|
20
|
+
import path$1 from "node:path";
|
|
21
|
+
import fs$1 from "node:fs/promises";
|
|
22
|
+
import fsSync from "node:fs";
|
|
23
|
+
import { fileURLToPath } from "node:url";
|
|
20
24
|
const DEFAULT_CONFIG = {
|
|
21
25
|
devServer: {
|
|
22
26
|
portRange: [3e3, 3100],
|
|
@@ -4167,6 +4171,16 @@ async function validateJayFiles(options = {}) {
|
|
|
4167
4171
|
} else if (options.verbose) {
|
|
4168
4172
|
getLogger().info(chalk.green(`✓ ${relativePath}`));
|
|
4169
4173
|
}
|
|
4174
|
+
const serverElementFile = generateServerElementFile(parsedFile.val);
|
|
4175
|
+
if (serverElementFile.validations.length > 0) {
|
|
4176
|
+
for (const validation of serverElementFile.validations) {
|
|
4177
|
+
errors.push({
|
|
4178
|
+
file: relativePath,
|
|
4179
|
+
message: `[SSR] ${validation}`,
|
|
4180
|
+
stage: "generate"
|
|
4181
|
+
});
|
|
4182
|
+
}
|
|
4183
|
+
}
|
|
4170
4184
|
} catch (error) {
|
|
4171
4185
|
errors.push({
|
|
4172
4186
|
file: relativePath,
|
|
@@ -4630,32 +4644,101 @@ program.command("validate-plugin [path]").description("Validate a Jay Stack plug
|
|
|
4630
4644
|
});
|
|
4631
4645
|
const ALL_ROLES = ["designer", "developer", "plugin"];
|
|
4632
4646
|
async function ensureAgentKitDocs(projectRoot, _force, mode) {
|
|
4633
|
-
const
|
|
4634
|
-
const
|
|
4635
|
-
const
|
|
4636
|
-
const agentKitDir = path2.join(projectRoot, "agent-kit");
|
|
4637
|
-
const thisDir = path2.dirname(fileURLToPath(import.meta.url));
|
|
4638
|
-
const templateDir = path2.resolve(thisDir, "..", "agent-kit-template");
|
|
4647
|
+
const agentKitDir = path$1.join(projectRoot, "agent-kit");
|
|
4648
|
+
const thisDir = path$1.dirname(fileURLToPath(import.meta.url));
|
|
4649
|
+
const templateDir = path$1.resolve(thisDir, "..", "agent-kit-template");
|
|
4639
4650
|
const roles = mode && ALL_ROLES.includes(mode) ? [mode] : ALL_ROLES;
|
|
4640
4651
|
for (const role of roles) {
|
|
4641
|
-
const roleTemplateDir =
|
|
4642
|
-
const roleOutputDir =
|
|
4652
|
+
const roleTemplateDir = path$1.join(templateDir, role);
|
|
4653
|
+
const roleOutputDir = path$1.join(agentKitDir, role);
|
|
4643
4654
|
let files;
|
|
4644
4655
|
try {
|
|
4645
|
-
files = (await
|
|
4656
|
+
files = (await fs$1.readdir(roleTemplateDir)).filter((f) => f.endsWith(".md"));
|
|
4646
4657
|
} catch {
|
|
4647
4658
|
continue;
|
|
4648
4659
|
}
|
|
4649
|
-
await
|
|
4660
|
+
await fs$1.mkdir(roleOutputDir, { recursive: true });
|
|
4650
4661
|
for (const filename of files) {
|
|
4651
|
-
await
|
|
4652
|
-
|
|
4653
|
-
|
|
4662
|
+
await fs$1.copyFile(
|
|
4663
|
+
path$1.join(roleTemplateDir, filename),
|
|
4664
|
+
path$1.join(roleOutputDir, filename)
|
|
4654
4665
|
);
|
|
4655
4666
|
getLogger().info(chalk.gray(` Created agent-kit/${role}/${filename}`));
|
|
4656
4667
|
}
|
|
4657
4668
|
}
|
|
4658
4669
|
}
|
|
4670
|
+
async function mergePluginAgentKitGuides(projectRoot, mode) {
|
|
4671
|
+
const plugins = await scanPlugins$1({ projectRoot });
|
|
4672
|
+
const agentKitDir = path$1.join(projectRoot, "agent-kit");
|
|
4673
|
+
const roles = mode && ALL_ROLES.includes(mode) ? [mode] : ALL_ROLES;
|
|
4674
|
+
const copiedPerRole = /* @__PURE__ */ new Map();
|
|
4675
|
+
for (const [, plugin] of plugins) {
|
|
4676
|
+
const pluginAgentKitDir = path$1.join(plugin.pluginPath, "agent-kit");
|
|
4677
|
+
if (!fsSync.existsSync(pluginAgentKitDir))
|
|
4678
|
+
continue;
|
|
4679
|
+
for (const role of roles) {
|
|
4680
|
+
const roleSourceDir = path$1.join(pluginAgentKitDir, role);
|
|
4681
|
+
let files;
|
|
4682
|
+
try {
|
|
4683
|
+
files = (await fs$1.readdir(roleSourceDir)).filter(
|
|
4684
|
+
(f) => f.endsWith(".md") && f !== "INSTRUCTIONS.md"
|
|
4685
|
+
);
|
|
4686
|
+
} catch {
|
|
4687
|
+
continue;
|
|
4688
|
+
}
|
|
4689
|
+
if (files.length === 0)
|
|
4690
|
+
continue;
|
|
4691
|
+
const roleOutputDir = path$1.join(agentKitDir, role);
|
|
4692
|
+
await fs$1.mkdir(roleOutputDir, { recursive: true });
|
|
4693
|
+
for (const filename of files) {
|
|
4694
|
+
const sourcePath = path$1.join(roleSourceDir, filename);
|
|
4695
|
+
await fs$1.copyFile(sourcePath, path$1.join(roleOutputDir, filename));
|
|
4696
|
+
let description = "";
|
|
4697
|
+
try {
|
|
4698
|
+
const content = await fs$1.readFile(sourcePath, "utf-8");
|
|
4699
|
+
const lines = content.split("\n");
|
|
4700
|
+
let pastHeading = false;
|
|
4701
|
+
for (const line of lines) {
|
|
4702
|
+
if (line.startsWith("# ")) {
|
|
4703
|
+
pastHeading = true;
|
|
4704
|
+
continue;
|
|
4705
|
+
}
|
|
4706
|
+
if (pastHeading && line.trim()) {
|
|
4707
|
+
description = line.trim();
|
|
4708
|
+
break;
|
|
4709
|
+
}
|
|
4710
|
+
}
|
|
4711
|
+
} catch {
|
|
4712
|
+
}
|
|
4713
|
+
if (!copiedPerRole.has(role))
|
|
4714
|
+
copiedPerRole.set(role, []);
|
|
4715
|
+
copiedPerRole.get(role).push({ filename, pluginName: plugin.name, description });
|
|
4716
|
+
getLogger().info(
|
|
4717
|
+
chalk.gray(
|
|
4718
|
+
` Copied agent-kit/${role}/${filename} from plugin "${plugin.name}"`
|
|
4719
|
+
)
|
|
4720
|
+
);
|
|
4721
|
+
}
|
|
4722
|
+
}
|
|
4723
|
+
}
|
|
4724
|
+
for (const [role, entries] of copiedPerRole) {
|
|
4725
|
+
const instructionsPath = path$1.join(agentKitDir, role, "INSTRUCTIONS.md");
|
|
4726
|
+
if (!fsSync.existsSync(instructionsPath))
|
|
4727
|
+
continue;
|
|
4728
|
+
const lines = [
|
|
4729
|
+
"",
|
|
4730
|
+
"## Plugin-Contributed Guides",
|
|
4731
|
+
"",
|
|
4732
|
+
"| File | Plugin | Description |",
|
|
4733
|
+
"| --- | --- | --- |"
|
|
4734
|
+
];
|
|
4735
|
+
for (const { filename, pluginName, description } of entries) {
|
|
4736
|
+
lines.push(`| [${filename}](${filename}) | ${pluginName} | ${description} |`);
|
|
4737
|
+
}
|
|
4738
|
+
lines.push("");
|
|
4739
|
+
await fs$1.appendFile(instructionsPath, lines.join("\n"));
|
|
4740
|
+
}
|
|
4741
|
+
}
|
|
4659
4742
|
async function generatePluginReferences(projectRoot, options, initErrors, viteServer) {
|
|
4660
4743
|
const { discoverPluginsWithReferences, executePluginReferences } = await import("@jay-framework/stack-server-runtime");
|
|
4661
4744
|
const plugins = await discoverPluginsWithReferences({
|
|
@@ -4790,6 +4873,7 @@ program.command("agent-kit").description(
|
|
|
4790
4873
|
try {
|
|
4791
4874
|
if (!options.list) {
|
|
4792
4875
|
await ensureAgentKitDocs(projectRoot, options.force, options.mode);
|
|
4876
|
+
await mergePluginAgentKitGuides(projectRoot, options.mode);
|
|
4793
4877
|
if (options.references !== false) {
|
|
4794
4878
|
await generatePluginReferences(projectRoot, options, initErrors, viteServer);
|
|
4795
4879
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jay-framework/jay-stack-cli",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.5",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -24,14 +24,14 @@
|
|
|
24
24
|
"test:watch": "vitest"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@jay-framework/compiler-jay-html": "^0.16.
|
|
28
|
-
"@jay-framework/compiler-shared": "^0.16.
|
|
29
|
-
"@jay-framework/dev-server": "^0.16.
|
|
30
|
-
"@jay-framework/editor-server": "^0.16.
|
|
31
|
-
"@jay-framework/fullstack-component": "^0.16.
|
|
32
|
-
"@jay-framework/logger": "^0.16.
|
|
33
|
-
"@jay-framework/plugin-validator": "^0.16.
|
|
34
|
-
"@jay-framework/stack-server-runtime": "^0.16.
|
|
27
|
+
"@jay-framework/compiler-jay-html": "^0.16.5",
|
|
28
|
+
"@jay-framework/compiler-shared": "^0.16.5",
|
|
29
|
+
"@jay-framework/dev-server": "^0.16.5",
|
|
30
|
+
"@jay-framework/editor-server": "^0.16.5",
|
|
31
|
+
"@jay-framework/fullstack-component": "^0.16.5",
|
|
32
|
+
"@jay-framework/logger": "^0.16.5",
|
|
33
|
+
"@jay-framework/plugin-validator": "^0.16.5",
|
|
34
|
+
"@jay-framework/stack-server-runtime": "^0.16.5",
|
|
35
35
|
"chalk": "^4.1.2",
|
|
36
36
|
"commander": "^14.0.0",
|
|
37
37
|
"express": "^5.0.1",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"yaml": "^2.3.4"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"@jay-framework/dev-environment": "^0.16.
|
|
45
|
+
"@jay-framework/dev-environment": "^0.16.5",
|
|
46
46
|
"@types/express": "^5.0.2",
|
|
47
47
|
"@types/node": "^22.15.21",
|
|
48
48
|
"nodemon": "^3.0.3",
|