@jay-framework/jay-stack-cli 0.15.5 → 0.15.6
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/{INSTRUCTIONS.md → designer/INSTRUCTIONS.md} +11 -8
- package/agent-kit-template/{jay-html-syntax.md → designer/jay-html-components.md} +89 -158
- package/agent-kit-template/designer/jay-html-styling.md +97 -0
- package/agent-kit-template/designer/jay-html-syntax.md +44 -0
- package/agent-kit-template/designer/jay-html-template-syntax.md +203 -0
- package/agent-kit-template/developer/INSTRUCTIONS.md +34 -0
- package/agent-kit-template/developer/cli-commands.md +228 -0
- package/agent-kit-template/developer/component-data.md +109 -0
- package/agent-kit-template/developer/component-refs.md +117 -0
- package/agent-kit-template/developer/component-state.md +140 -0
- package/agent-kit-template/developer/configuration.md +76 -0
- package/agent-kit-template/developer/page-components.md +103 -0
- package/agent-kit-template/developer/page-contracts.md +114 -0
- package/agent-kit-template/developer/project-structure.md +242 -0
- package/agent-kit-template/developer/render-results.md +112 -0
- package/agent-kit-template/developer/routing.md +161 -0
- package/agent-kit-template/developer/seo-guide.md +93 -0
- package/agent-kit-template/plugin/INSTRUCTIONS.md +40 -0
- package/agent-kit-template/plugin/actions-guide.md +125 -0
- package/agent-kit-template/plugin/component-context.md +103 -0
- package/agent-kit-template/plugin/component-data.md +109 -0
- package/agent-kit-template/plugin/component-refs.md +117 -0
- package/agent-kit-template/plugin/component-state.md +140 -0
- package/agent-kit-template/plugin/component-structure.md +174 -0
- package/agent-kit-template/plugin/contracts-guide.md +193 -0
- package/agent-kit-template/plugin/plugin-structure.md +194 -0
- package/agent-kit-template/plugin/render-results.md +112 -0
- package/agent-kit-template/plugin/seo-guide.md +93 -0
- package/agent-kit-template/plugin/services-guide.md +116 -0
- package/agent-kit-template/plugin/validation.md +101 -0
- package/dist/index.js +678 -60
- package/package.json +10 -10
- /package/agent-kit-template/{cli-commands.md → designer/cli-commands.md} +0 -0
- /package/agent-kit-template/{contracts-and-plugins.md → designer/contracts-and-plugins.md} +0 -0
- /package/agent-kit-template/{project-structure.md → designer/project-structure.md} +0 -0
- /package/agent-kit-template/{routing.md → designer/routing.md} +0 -0
package/dist/index.js
CHANGED
|
@@ -14,6 +14,7 @@ import { listContracts, materializeContracts } from "@jay-framework/stack-server
|
|
|
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
|
+
import { createRequire } from "module";
|
|
17
18
|
import { glob } from "glob";
|
|
18
19
|
import { parse } from "node-html-parser";
|
|
19
20
|
const DEFAULT_CONFIG = {
|
|
@@ -244,7 +245,7 @@ function getCommonStyles(node) {
|
|
|
244
245
|
transformStyles.push(`rotate(${node.rotation}deg)`);
|
|
245
246
|
}
|
|
246
247
|
if (node.effects && Array.isArray(node.effects) && node.effects.length > 0) {
|
|
247
|
-
const visibleEffects = node.effects.filter((
|
|
248
|
+
const visibleEffects = node.effects.filter((e2) => e2.visible !== false).reverse();
|
|
248
249
|
const filterFunctions = [];
|
|
249
250
|
const boxShadows = [];
|
|
250
251
|
for (const effect of visibleEffects) {
|
|
@@ -410,7 +411,7 @@ function getStrokeStyles(node) {
|
|
|
410
411
|
if (!node.strokes || node.strokes.length === 0) {
|
|
411
412
|
return "";
|
|
412
413
|
}
|
|
413
|
-
const visibleStrokes = node.strokes.filter((
|
|
414
|
+
const visibleStrokes = node.strokes.filter((s2) => s2.visible !== false);
|
|
414
415
|
if (visibleStrokes.length === 0) {
|
|
415
416
|
return "";
|
|
416
417
|
}
|
|
@@ -1588,9 +1589,9 @@ function convertContractToProtocol(contract) {
|
|
|
1588
1589
|
}
|
|
1589
1590
|
async function isPageDirectory(dirPath) {
|
|
1590
1591
|
const entries = await fs.promises.readdir(dirPath, { withFileTypes: true });
|
|
1591
|
-
const hasPageHtml = entries.some((
|
|
1592
|
-
const hasPageContract = entries.some((
|
|
1593
|
-
const hasPageConfig = entries.some((
|
|
1592
|
+
const hasPageHtml = entries.some((e2) => e2.name === PAGE_FILENAME);
|
|
1593
|
+
const hasPageContract = entries.some((e2) => e2.name === PAGE_CONTRACT_FILENAME);
|
|
1594
|
+
const hasPageConfig = entries.some((e2) => e2.name === PAGE_CONFIG_FILENAME);
|
|
1594
1595
|
const isPage = hasPageHtml || hasPageContract || hasPageConfig;
|
|
1595
1596
|
return { isPage, hasPageHtml, hasPageContract, hasPageConfig };
|
|
1596
1597
|
}
|
|
@@ -2155,7 +2156,7 @@ function createEditorHandlers(config, tsConfigPath, projectRoot) {
|
|
|
2155
2156
|
}
|
|
2156
2157
|
return {
|
|
2157
2158
|
type: "publish",
|
|
2158
|
-
success: status.every((
|
|
2159
|
+
success: status.every((s2) => s2.success),
|
|
2159
2160
|
status
|
|
2160
2161
|
};
|
|
2161
2162
|
};
|
|
@@ -2511,6 +2512,280 @@ async function startDevServer(options = {}) {
|
|
|
2511
2512
|
process.on("SIGTERM", shutdown);
|
|
2512
2513
|
process.on("SIGINT", shutdown);
|
|
2513
2514
|
}
|
|
2515
|
+
const s = createRequire(import.meta.url), e = s("typescript"), u = e;
|
|
2516
|
+
new Proxy(e, {
|
|
2517
|
+
get(t, r) {
|
|
2518
|
+
return t[r];
|
|
2519
|
+
}
|
|
2520
|
+
});
|
|
2521
|
+
const FRAMEWORK_PROP_TYPES = /* @__PURE__ */ new Set(["PageProps", "RequestQuery"]);
|
|
2522
|
+
function checkComponentPropsAndParams(sourceCode, contract, contractName, contractPath, sourcePath) {
|
|
2523
|
+
const errors = [];
|
|
2524
|
+
const warnings = [];
|
|
2525
|
+
const sourceFile = u.createSourceFile(
|
|
2526
|
+
sourcePath,
|
|
2527
|
+
sourceCode,
|
|
2528
|
+
u.ScriptTarget.Latest,
|
|
2529
|
+
true,
|
|
2530
|
+
u.ScriptKind.TS
|
|
2531
|
+
);
|
|
2532
|
+
const localInterfaces = collectLocalInterfaces(sourceFile);
|
|
2533
|
+
const contractImportedTypes = collectContractImportedTypes(sourceFile);
|
|
2534
|
+
const builderInfo = analyzeBuilderChains(sourceFile);
|
|
2535
|
+
for (const propsTypeName of builderInfo.propsTypeNames) {
|
|
2536
|
+
checkPropsConsistency(
|
|
2537
|
+
propsTypeName,
|
|
2538
|
+
localInterfaces,
|
|
2539
|
+
contractImportedTypes,
|
|
2540
|
+
contract,
|
|
2541
|
+
contractName,
|
|
2542
|
+
contractPath,
|
|
2543
|
+
sourcePath,
|
|
2544
|
+
errors,
|
|
2545
|
+
warnings
|
|
2546
|
+
);
|
|
2547
|
+
}
|
|
2548
|
+
if (builderInfo.hasLoadParams) {
|
|
2549
|
+
checkParamsConsistency(
|
|
2550
|
+
builderInfo.paramsTypeNames,
|
|
2551
|
+
localInterfaces,
|
|
2552
|
+
contractImportedTypes,
|
|
2553
|
+
contract,
|
|
2554
|
+
contractName,
|
|
2555
|
+
contractPath,
|
|
2556
|
+
sourcePath,
|
|
2557
|
+
errors,
|
|
2558
|
+
warnings
|
|
2559
|
+
);
|
|
2560
|
+
}
|
|
2561
|
+
return { errors, warnings };
|
|
2562
|
+
}
|
|
2563
|
+
function collectLocalInterfaces(sourceFile) {
|
|
2564
|
+
const interfaces = /* @__PURE__ */ new Map();
|
|
2565
|
+
for (const statement of sourceFile.statements) {
|
|
2566
|
+
if (u.isInterfaceDeclaration(statement)) {
|
|
2567
|
+
const name = statement.name.text;
|
|
2568
|
+
const properties = [];
|
|
2569
|
+
for (const member of statement.members) {
|
|
2570
|
+
if (u.isPropertySignature(member) && member.name) {
|
|
2571
|
+
if (u.isIdentifier(member.name)) {
|
|
2572
|
+
properties.push(member.name.text);
|
|
2573
|
+
} else if (u.isStringLiteral(member.name)) {
|
|
2574
|
+
properties.push(member.name.text);
|
|
2575
|
+
}
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2578
|
+
const extendsTypes = [];
|
|
2579
|
+
if (statement.heritageClauses) {
|
|
2580
|
+
for (const clause of statement.heritageClauses) {
|
|
2581
|
+
for (const type of clause.types) {
|
|
2582
|
+
if (u.isIdentifier(type.expression)) {
|
|
2583
|
+
extendsTypes.push(type.expression.text);
|
|
2584
|
+
}
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2587
|
+
}
|
|
2588
|
+
interfaces.set(name, { name, properties, extendsTypes });
|
|
2589
|
+
}
|
|
2590
|
+
if (u.isTypeAliasDeclaration(statement)) {
|
|
2591
|
+
const name = statement.name.text;
|
|
2592
|
+
if (u.isTypeLiteralNode(statement.type)) {
|
|
2593
|
+
const properties = [];
|
|
2594
|
+
for (const member of statement.type.members) {
|
|
2595
|
+
if (u.isPropertySignature(member) && member.name) {
|
|
2596
|
+
if (u.isIdentifier(member.name)) {
|
|
2597
|
+
properties.push(member.name.text);
|
|
2598
|
+
}
|
|
2599
|
+
}
|
|
2600
|
+
}
|
|
2601
|
+
interfaces.set(name, { name, properties, extendsTypes: [] });
|
|
2602
|
+
}
|
|
2603
|
+
}
|
|
2604
|
+
}
|
|
2605
|
+
return interfaces;
|
|
2606
|
+
}
|
|
2607
|
+
function collectContractImportedTypes(sourceFile) {
|
|
2608
|
+
const contractTypes = /* @__PURE__ */ new Set();
|
|
2609
|
+
for (const statement of sourceFile.statements) {
|
|
2610
|
+
if (!u.isImportDeclaration(statement))
|
|
2611
|
+
continue;
|
|
2612
|
+
const moduleSpecifier = statement.moduleSpecifier;
|
|
2613
|
+
if (!u.isStringLiteral(moduleSpecifier))
|
|
2614
|
+
continue;
|
|
2615
|
+
const modulePath = moduleSpecifier.text;
|
|
2616
|
+
if (!modulePath.includes(".jay-contract"))
|
|
2617
|
+
continue;
|
|
2618
|
+
const importClause = statement.importClause;
|
|
2619
|
+
if (!importClause)
|
|
2620
|
+
continue;
|
|
2621
|
+
const namedBindings = importClause.namedBindings;
|
|
2622
|
+
if (namedBindings && u.isNamedImports(namedBindings)) {
|
|
2623
|
+
for (const element of namedBindings.elements) {
|
|
2624
|
+
contractTypes.add(element.name.text);
|
|
2625
|
+
}
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
return contractTypes;
|
|
2629
|
+
}
|
|
2630
|
+
function analyzeBuilderChains(sourceFile) {
|
|
2631
|
+
const result = {
|
|
2632
|
+
propsTypeNames: [],
|
|
2633
|
+
hasLoadParams: false,
|
|
2634
|
+
paramsTypeNames: []
|
|
2635
|
+
};
|
|
2636
|
+
visitNode(sourceFile, result);
|
|
2637
|
+
return result;
|
|
2638
|
+
}
|
|
2639
|
+
function visitNode(node, result) {
|
|
2640
|
+
if (u.isCallExpression(node)) {
|
|
2641
|
+
const expr = node.expression;
|
|
2642
|
+
if (u.isPropertyAccessExpression(expr) && expr.name.text === "withProps") {
|
|
2643
|
+
if (node.typeArguments && node.typeArguments.length > 0) {
|
|
2644
|
+
const typeNames = extractTypeNames(node.typeArguments[0]);
|
|
2645
|
+
result.propsTypeNames.push(...typeNames);
|
|
2646
|
+
}
|
|
2647
|
+
}
|
|
2648
|
+
if (u.isPropertyAccessExpression(expr) && expr.name.text === "withLoadParams") {
|
|
2649
|
+
result.hasLoadParams = true;
|
|
2650
|
+
if (node.typeArguments && node.typeArguments.length > 0) {
|
|
2651
|
+
const typeNames = extractTypeNames(node.typeArguments[0]);
|
|
2652
|
+
result.paramsTypeNames.push(...typeNames);
|
|
2653
|
+
}
|
|
2654
|
+
if (node.arguments.length > 0) {
|
|
2655
|
+
const arg = node.arguments[0];
|
|
2656
|
+
if (u.isIdentifier(arg))
|
|
2657
|
+
;
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
2660
|
+
}
|
|
2661
|
+
u.forEachChild(node, (child) => visitNode(child, result));
|
|
2662
|
+
}
|
|
2663
|
+
function extractTypeNames(typeNode) {
|
|
2664
|
+
if (u.isTypeReferenceNode(typeNode)) {
|
|
2665
|
+
if (u.isIdentifier(typeNode.typeName)) {
|
|
2666
|
+
return [typeNode.typeName.text];
|
|
2667
|
+
}
|
|
2668
|
+
}
|
|
2669
|
+
if (u.isIntersectionTypeNode(typeNode)) {
|
|
2670
|
+
const names = [];
|
|
2671
|
+
for (const member of typeNode.types) {
|
|
2672
|
+
names.push(...extractTypeNames(member));
|
|
2673
|
+
}
|
|
2674
|
+
return names;
|
|
2675
|
+
}
|
|
2676
|
+
return [];
|
|
2677
|
+
}
|
|
2678
|
+
function checkPropsConsistency(propsTypeName, localInterfaces, contractImportedTypes, contract, contractName, contractPath, sourcePath, errors, warnings) {
|
|
2679
|
+
if (FRAMEWORK_PROP_TYPES.has(propsTypeName))
|
|
2680
|
+
return;
|
|
2681
|
+
if (contractImportedTypes.has(propsTypeName))
|
|
2682
|
+
return;
|
|
2683
|
+
const prefix = `[${contractName}]`;
|
|
2684
|
+
const iface = localInterfaces.get(propsTypeName);
|
|
2685
|
+
if (!iface) {
|
|
2686
|
+
if (!contract.props || contract.props.length === 0) {
|
|
2687
|
+
errors.push({
|
|
2688
|
+
type: "contract-invalid",
|
|
2689
|
+
message: `${prefix} component uses .withProps<${propsTypeName}>() but the contract does not declare any props`,
|
|
2690
|
+
location: contractPath,
|
|
2691
|
+
suggestion: `Add a props section to the contract`
|
|
2692
|
+
});
|
|
2693
|
+
}
|
|
2694
|
+
return;
|
|
2695
|
+
}
|
|
2696
|
+
const ownProperties = iface.properties;
|
|
2697
|
+
if (ownProperties.length === 0)
|
|
2698
|
+
return;
|
|
2699
|
+
if (!contract.props || contract.props.length === 0) {
|
|
2700
|
+
errors.push({
|
|
2701
|
+
type: "contract-invalid",
|
|
2702
|
+
message: `${prefix} component uses .withProps<${propsTypeName}>() with properties [${ownProperties.join(", ")}] but the contract does not declare any props`,
|
|
2703
|
+
location: contractPath,
|
|
2704
|
+
suggestion: `Add to contract: props:
|
|
2705
|
+
` + ownProperties.map((p) => ` - name: ${p}
|
|
2706
|
+
type: string`).join("\n")
|
|
2707
|
+
});
|
|
2708
|
+
return;
|
|
2709
|
+
}
|
|
2710
|
+
const contractPropNames = new Set(contract.props.map((p) => p.name));
|
|
2711
|
+
for (const prop of ownProperties) {
|
|
2712
|
+
if (!contractPropNames.has(prop)) {
|
|
2713
|
+
errors.push({
|
|
2714
|
+
type: "contract-invalid",
|
|
2715
|
+
message: `${prefix} component prop "${prop}" (from ${propsTypeName}) is not declared in the contract`,
|
|
2716
|
+
location: contractPath,
|
|
2717
|
+
suggestion: `Add to contract props: - name: ${prop}`
|
|
2718
|
+
});
|
|
2719
|
+
}
|
|
2720
|
+
}
|
|
2721
|
+
for (const contractProp of contract.props) {
|
|
2722
|
+
if (!ownProperties.includes(contractProp.name)) {
|
|
2723
|
+
warnings.push({
|
|
2724
|
+
type: "contract-invalid",
|
|
2725
|
+
message: `${prefix} contract declares prop "${contractProp.name}" but the component's ${propsTypeName} interface does not include it`,
|
|
2726
|
+
location: sourcePath,
|
|
2727
|
+
suggestion: `Add "${contractProp.name}" to the ${propsTypeName} interface, or remove it from the contract`
|
|
2728
|
+
});
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
2731
|
+
}
|
|
2732
|
+
function checkParamsConsistency(paramsTypeNames, localInterfaces, contractImportedTypes, contract, contractName, contractPath, sourcePath, errors, warnings) {
|
|
2733
|
+
const prefix = `[${contractName}]`;
|
|
2734
|
+
if (!contract.params || contract.params.length === 0) {
|
|
2735
|
+
errors.push({
|
|
2736
|
+
type: "contract-invalid",
|
|
2737
|
+
message: `${prefix} component uses .withLoadParams() but the contract does not declare any params`,
|
|
2738
|
+
location: contractPath,
|
|
2739
|
+
suggestion: `Add a params section to the contract (e.g., params: { slug: string })`
|
|
2740
|
+
});
|
|
2741
|
+
for (const typeName of paramsTypeNames) {
|
|
2742
|
+
if (FRAMEWORK_PROP_TYPES.has(typeName))
|
|
2743
|
+
continue;
|
|
2744
|
+
if (contractImportedTypes.has(typeName))
|
|
2745
|
+
continue;
|
|
2746
|
+
const iface = localInterfaces.get(typeName);
|
|
2747
|
+
if (iface) {
|
|
2748
|
+
const ownProps = iface.properties;
|
|
2749
|
+
if (ownProps.length > 0) {
|
|
2750
|
+
errors[errors.length - 1].suggestion = `Add to contract: params:
|
|
2751
|
+
` + ownProps.map((p) => ` ${p}: string`).join("\n");
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
return;
|
|
2756
|
+
}
|
|
2757
|
+
for (const typeName of paramsTypeNames) {
|
|
2758
|
+
if (FRAMEWORK_PROP_TYPES.has(typeName))
|
|
2759
|
+
continue;
|
|
2760
|
+
if (contractImportedTypes.has(typeName))
|
|
2761
|
+
continue;
|
|
2762
|
+
const iface = localInterfaces.get(typeName);
|
|
2763
|
+
if (!iface)
|
|
2764
|
+
continue;
|
|
2765
|
+
const ownProperties = iface.properties;
|
|
2766
|
+
const contractParamNames = new Set(contract.params.map((p) => p.name));
|
|
2767
|
+
for (const prop of ownProperties) {
|
|
2768
|
+
if (!contractParamNames.has(prop)) {
|
|
2769
|
+
errors.push({
|
|
2770
|
+
type: "contract-invalid",
|
|
2771
|
+
message: `${prefix} component param "${prop}" (from ${typeName}) is not declared in the contract`,
|
|
2772
|
+
location: contractPath,
|
|
2773
|
+
suggestion: `Add to contract params: ${prop}: string`
|
|
2774
|
+
});
|
|
2775
|
+
}
|
|
2776
|
+
}
|
|
2777
|
+
for (const contractParam of contract.params) {
|
|
2778
|
+
if (!ownProperties.includes(contractParam.name)) {
|
|
2779
|
+
warnings.push({
|
|
2780
|
+
type: "contract-invalid",
|
|
2781
|
+
message: `${prefix} contract declares param "${contractParam.name}" but the component's ${typeName} interface does not include it`,
|
|
2782
|
+
location: sourcePath,
|
|
2783
|
+
suggestion: `Add "${contractParam.name}" to the ${typeName} interface, or remove it from the contract`
|
|
2784
|
+
});
|
|
2785
|
+
}
|
|
2786
|
+
}
|
|
2787
|
+
}
|
|
2788
|
+
}
|
|
2514
2789
|
async function validatePlugin(options = {}) {
|
|
2515
2790
|
const pluginPath = options.pluginPath || process.cwd();
|
|
2516
2791
|
if (options.local) {
|
|
@@ -2612,6 +2887,36 @@ async function validateLocalPlugins(projectPath, options) {
|
|
|
2612
2887
|
typesGenerated: allResults.reduce((sum, r) => sum + (r.typesGenerated || 0), 0)
|
|
2613
2888
|
};
|
|
2614
2889
|
}
|
|
2890
|
+
function validateDocFile(docPath, label, context, result) {
|
|
2891
|
+
const resolvedPath = path.join(context.pluginPath, docPath);
|
|
2892
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
2893
|
+
result.errors.push({
|
|
2894
|
+
type: "file-missing",
|
|
2895
|
+
message: `Doc file for ${label} not found: ${docPath}`,
|
|
2896
|
+
location: "plugin.yaml",
|
|
2897
|
+
suggestion: `Create the documentation file at ${resolvedPath}`
|
|
2898
|
+
});
|
|
2899
|
+
return;
|
|
2900
|
+
}
|
|
2901
|
+
if (context.isNpmPackage) {
|
|
2902
|
+
const packageJsonPath = path.join(context.pluginPath, "package.json");
|
|
2903
|
+
try {
|
|
2904
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
2905
|
+
if (packageJson.exports) {
|
|
2906
|
+
const exportKey = "./" + docPath.replace(/^\.\//, "");
|
|
2907
|
+
if (!packageJson.exports[exportKey]) {
|
|
2908
|
+
result.errors.push({
|
|
2909
|
+
type: "export-mismatch",
|
|
2910
|
+
message: `Doc file for ${label} is not exported in package.json: ${docPath}`,
|
|
2911
|
+
location: packageJsonPath,
|
|
2912
|
+
suggestion: `Add "${exportKey}": "${docPath}" to the exports field`
|
|
2913
|
+
});
|
|
2914
|
+
}
|
|
2915
|
+
}
|
|
2916
|
+
} catch {
|
|
2917
|
+
}
|
|
2918
|
+
}
|
|
2919
|
+
}
|
|
2615
2920
|
async function validateSchema(context, result) {
|
|
2616
2921
|
const { manifest } = context;
|
|
2617
2922
|
if (!manifest.name) {
|
|
@@ -2702,46 +3007,111 @@ async function validateSchema(context, result) {
|
|
|
2702
3007
|
suggestion: 'Add either "contracts" or "dynamic_contracts" to expose functionality'
|
|
2703
3008
|
});
|
|
2704
3009
|
}
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
result.contractsChecked = (result.contractsChecked || 0) + 1;
|
|
2708
|
-
let contractPath;
|
|
2709
|
-
if (context.isNpmPackage) {
|
|
2710
|
-
const contractSpec = contract.contract;
|
|
2711
|
-
const possiblePaths = [
|
|
2712
|
-
path.join(context.pluginPath, "dist", contractSpec),
|
|
2713
|
-
path.join(context.pluginPath, "lib", contractSpec),
|
|
2714
|
-
path.join(context.pluginPath, contractSpec)
|
|
2715
|
-
];
|
|
2716
|
-
let found = false;
|
|
2717
|
-
for (const possiblePath of possiblePaths) {
|
|
2718
|
-
if (fs.existsSync(possiblePath)) {
|
|
2719
|
-
contractPath = possiblePath;
|
|
2720
|
-
found = true;
|
|
2721
|
-
break;
|
|
2722
|
-
}
|
|
2723
|
-
}
|
|
2724
|
-
if (!found) {
|
|
3010
|
+
if (manifest.services) {
|
|
3011
|
+
if (!Array.isArray(manifest.services)) {
|
|
2725
3012
|
result.errors.push({
|
|
2726
|
-
type: "
|
|
2727
|
-
message:
|
|
2728
|
-
location:
|
|
2729
|
-
|
|
3013
|
+
type: "schema",
|
|
3014
|
+
message: 'Field "services" must be an array',
|
|
3015
|
+
location: "plugin.yaml"
|
|
3016
|
+
});
|
|
3017
|
+
} else {
|
|
3018
|
+
manifest.services.forEach((service, index) => {
|
|
3019
|
+
if (!service.name) {
|
|
3020
|
+
result.errors.push({
|
|
3021
|
+
type: "schema",
|
|
3022
|
+
message: `Service at index ${index} is missing "name" field`,
|
|
3023
|
+
location: "plugin.yaml"
|
|
3024
|
+
});
|
|
3025
|
+
}
|
|
3026
|
+
if (!service.marker) {
|
|
3027
|
+
result.errors.push({
|
|
3028
|
+
type: "schema",
|
|
3029
|
+
message: `Service "${service.name || index}" is missing "marker" field`,
|
|
3030
|
+
location: "plugin.yaml",
|
|
3031
|
+
suggestion: "Specify the exported service marker constant name"
|
|
3032
|
+
});
|
|
3033
|
+
}
|
|
3034
|
+
if (service.doc) {
|
|
3035
|
+
validateDocFile(service.doc, `service "${service.name}"`, context, result);
|
|
3036
|
+
}
|
|
2730
3037
|
});
|
|
2731
|
-
return;
|
|
2732
3038
|
}
|
|
2733
|
-
}
|
|
2734
|
-
|
|
2735
|
-
if (!
|
|
3039
|
+
}
|
|
3040
|
+
if (manifest.contexts) {
|
|
3041
|
+
if (!Array.isArray(manifest.contexts)) {
|
|
2736
3042
|
result.errors.push({
|
|
2737
|
-
type: "
|
|
2738
|
-
message:
|
|
2739
|
-
location:
|
|
2740
|
-
|
|
3043
|
+
type: "schema",
|
|
3044
|
+
message: 'Field "contexts" must be an array',
|
|
3045
|
+
location: "plugin.yaml"
|
|
3046
|
+
});
|
|
3047
|
+
} else {
|
|
3048
|
+
manifest.contexts.forEach((ctx, index) => {
|
|
3049
|
+
if (!ctx.name) {
|
|
3050
|
+
result.errors.push({
|
|
3051
|
+
type: "schema",
|
|
3052
|
+
message: `Context at index ${index} is missing "name" field`,
|
|
3053
|
+
location: "plugin.yaml"
|
|
3054
|
+
});
|
|
3055
|
+
}
|
|
3056
|
+
if (!ctx.marker) {
|
|
3057
|
+
result.errors.push({
|
|
3058
|
+
type: "schema",
|
|
3059
|
+
message: `Context "${ctx.name || index}" is missing "marker" field`,
|
|
3060
|
+
location: "plugin.yaml",
|
|
3061
|
+
suggestion: "Specify the exported context marker constant name"
|
|
3062
|
+
});
|
|
3063
|
+
}
|
|
3064
|
+
if (ctx.doc) {
|
|
3065
|
+
validateDocFile(ctx.doc, `context "${ctx.name}"`, context, result);
|
|
3066
|
+
}
|
|
2741
3067
|
});
|
|
2742
|
-
return;
|
|
2743
3068
|
}
|
|
2744
3069
|
}
|
|
3070
|
+
}
|
|
3071
|
+
function resolveContractFile(contractSpec, context) {
|
|
3072
|
+
if (context.isNpmPackage) {
|
|
3073
|
+
const packageJsonPath = path.join(context.pluginPath, "package.json");
|
|
3074
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
3075
|
+
try {
|
|
3076
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
3077
|
+
if (packageJson.exports) {
|
|
3078
|
+
const exportKey = "./" + contractSpec;
|
|
3079
|
+
const exportValue = packageJson.exports[exportKey];
|
|
3080
|
+
if (exportValue) {
|
|
3081
|
+
const resolvedPath = typeof exportValue === "string" ? exportValue : exportValue.default || exportValue.import || exportValue.require;
|
|
3082
|
+
if (resolvedPath) {
|
|
3083
|
+
const fullPath = path.join(context.pluginPath, resolvedPath);
|
|
3084
|
+
if (fs.existsSync(fullPath))
|
|
3085
|
+
return fullPath;
|
|
3086
|
+
}
|
|
3087
|
+
}
|
|
3088
|
+
}
|
|
3089
|
+
} catch {
|
|
3090
|
+
}
|
|
3091
|
+
}
|
|
3092
|
+
for (const dir of ["dist", "lib", ""]) {
|
|
3093
|
+
const candidate = path.join(context.pluginPath, dir, contractSpec);
|
|
3094
|
+
if (fs.existsSync(candidate))
|
|
3095
|
+
return candidate;
|
|
3096
|
+
}
|
|
3097
|
+
return void 0;
|
|
3098
|
+
} else {
|
|
3099
|
+
const candidate = path.join(context.pluginPath, contractSpec);
|
|
3100
|
+
return fs.existsSync(candidate) ? candidate : void 0;
|
|
3101
|
+
}
|
|
3102
|
+
}
|
|
3103
|
+
async function validateContract(contract, index, context, generateTypes, result) {
|
|
3104
|
+
result.contractsChecked = (result.contractsChecked || 0) + 1;
|
|
3105
|
+
const contractPath = resolveContractFile(contract.contract, context);
|
|
3106
|
+
if (!contractPath) {
|
|
3107
|
+
result.errors.push({
|
|
3108
|
+
type: "file-missing",
|
|
3109
|
+
message: `Contract file not found: ${contract.contract}`,
|
|
3110
|
+
location: `plugin.yaml contracts[${index}]`,
|
|
3111
|
+
suggestion: context.isNpmPackage ? `Ensure the contract is exported in package.json and the file exists` : `Create the contract file at ${path.join(context.pluginPath, contract.contract)}`
|
|
3112
|
+
});
|
|
3113
|
+
return;
|
|
3114
|
+
}
|
|
2745
3115
|
try {
|
|
2746
3116
|
const contractContent = await fs.promises.readFile(contractPath, "utf-8");
|
|
2747
3117
|
const parsedContract = YAML.parse(contractContent);
|
|
@@ -2792,6 +3162,7 @@ async function validateComponent(contract, index, context, result) {
|
|
|
2792
3162
|
location: `plugin.yaml contracts[${index}]`,
|
|
2793
3163
|
suggestion: 'Component should be the exported member name (e.g., "moodTracker")'
|
|
2794
3164
|
});
|
|
3165
|
+
return;
|
|
2795
3166
|
}
|
|
2796
3167
|
if (contract.component.includes("/") || contract.component.includes(".")) {
|
|
2797
3168
|
result.warnings.push({
|
|
@@ -2801,6 +3172,143 @@ async function validateComponent(contract, index, context, result) {
|
|
|
2801
3172
|
suggestion: 'Component should be the exported member name (e.g., "moodTracker"), not a file path'
|
|
2802
3173
|
});
|
|
2803
3174
|
}
|
|
3175
|
+
await checkComponentContractConsistency(contract, context, result);
|
|
3176
|
+
}
|
|
3177
|
+
function hasExportModifier(node) {
|
|
3178
|
+
return node.modifiers?.some((m) => m.kind === u.SyntaxKind.ExportKeyword) ?? false;
|
|
3179
|
+
}
|
|
3180
|
+
function resolveModulePath(basePath) {
|
|
3181
|
+
for (const ext of ["", ".ts", ".js", "/index.ts", "/index.js"]) {
|
|
3182
|
+
const candidate = basePath + ext;
|
|
3183
|
+
if (fs.existsSync(candidate))
|
|
3184
|
+
return candidate;
|
|
3185
|
+
}
|
|
3186
|
+
return void 0;
|
|
3187
|
+
}
|
|
3188
|
+
function resolveComponentSourcePath(componentName, context) {
|
|
3189
|
+
const modulePath = context.manifest.module || "index";
|
|
3190
|
+
const entryBase = path.join(context.pluginPath, modulePath);
|
|
3191
|
+
const entryFile = resolveModulePath(entryBase);
|
|
3192
|
+
const libEntryFile = !entryFile ? resolveModulePath(path.join(context.pluginPath, "lib", modulePath)) : void 0;
|
|
3193
|
+
const sourceEntry = entryFile || libEntryFile;
|
|
3194
|
+
if (!sourceEntry)
|
|
3195
|
+
return void 0;
|
|
3196
|
+
if (!sourceEntry.endsWith(".ts"))
|
|
3197
|
+
return void 0;
|
|
3198
|
+
let sourceCode;
|
|
3199
|
+
try {
|
|
3200
|
+
sourceCode = fs.readFileSync(sourceEntry, "utf-8");
|
|
3201
|
+
} catch {
|
|
3202
|
+
return void 0;
|
|
3203
|
+
}
|
|
3204
|
+
const sourceFile = u.createSourceFile(
|
|
3205
|
+
sourceEntry,
|
|
3206
|
+
sourceCode,
|
|
3207
|
+
u.ScriptTarget.Latest,
|
|
3208
|
+
true,
|
|
3209
|
+
u.ScriptKind.TS
|
|
3210
|
+
);
|
|
3211
|
+
const starReexportModules = [];
|
|
3212
|
+
for (const statement of sourceFile.statements) {
|
|
3213
|
+
if (!u.isExportDeclaration(statement))
|
|
3214
|
+
continue;
|
|
3215
|
+
if (!statement.moduleSpecifier)
|
|
3216
|
+
continue;
|
|
3217
|
+
if (!u.isStringLiteral(statement.moduleSpecifier))
|
|
3218
|
+
continue;
|
|
3219
|
+
const moduleSpec = statement.moduleSpecifier.text;
|
|
3220
|
+
const exportClause = statement.exportClause;
|
|
3221
|
+
if (!exportClause) {
|
|
3222
|
+
starReexportModules.push(moduleSpec);
|
|
3223
|
+
continue;
|
|
3224
|
+
}
|
|
3225
|
+
if (u.isNamedExports(exportClause)) {
|
|
3226
|
+
for (const element of exportClause.elements) {
|
|
3227
|
+
const exportedName = element.name.text;
|
|
3228
|
+
if (exportedName === componentName) {
|
|
3229
|
+
const resolvedBase = path.resolve(path.dirname(sourceEntry), moduleSpec);
|
|
3230
|
+
return resolveModulePath(resolvedBase);
|
|
3231
|
+
}
|
|
3232
|
+
}
|
|
3233
|
+
}
|
|
3234
|
+
}
|
|
3235
|
+
for (const moduleSpec of starReexportModules) {
|
|
3236
|
+
if (!moduleSpec.startsWith("."))
|
|
3237
|
+
continue;
|
|
3238
|
+
const resolvedBase = path.resolve(path.dirname(sourceEntry), moduleSpec);
|
|
3239
|
+
const resolvedPath = resolveModulePath(resolvedBase);
|
|
3240
|
+
if (!resolvedPath || !resolvedPath.endsWith(".ts"))
|
|
3241
|
+
continue;
|
|
3242
|
+
try {
|
|
3243
|
+
const modSource = fs.readFileSync(resolvedPath, "utf-8");
|
|
3244
|
+
const modFile = u.createSourceFile(
|
|
3245
|
+
resolvedPath,
|
|
3246
|
+
modSource,
|
|
3247
|
+
u.ScriptTarget.Latest,
|
|
3248
|
+
true,
|
|
3249
|
+
u.ScriptKind.TS
|
|
3250
|
+
);
|
|
3251
|
+
for (const stmt of modFile.statements) {
|
|
3252
|
+
if (u.isVariableStatement(stmt) && hasExportModifier(stmt)) {
|
|
3253
|
+
for (const decl of stmt.declarationList.declarations) {
|
|
3254
|
+
if (u.isIdentifier(decl.name) && decl.name.text === componentName) {
|
|
3255
|
+
return resolvedPath;
|
|
3256
|
+
}
|
|
3257
|
+
}
|
|
3258
|
+
}
|
|
3259
|
+
if (u.isFunctionDeclaration(stmt) && hasExportModifier(stmt) && stmt.name?.text === componentName) {
|
|
3260
|
+
return resolvedPath;
|
|
3261
|
+
}
|
|
3262
|
+
}
|
|
3263
|
+
} catch {
|
|
3264
|
+
continue;
|
|
3265
|
+
}
|
|
3266
|
+
}
|
|
3267
|
+
return sourceEntry;
|
|
3268
|
+
}
|
|
3269
|
+
function resolveContractPath(contract, context) {
|
|
3270
|
+
return resolveContractFile(contract.contract, context);
|
|
3271
|
+
}
|
|
3272
|
+
async function checkComponentContractConsistency(contract, context, result) {
|
|
3273
|
+
const componentName = contract.component;
|
|
3274
|
+
if (!componentName)
|
|
3275
|
+
return;
|
|
3276
|
+
const sourcePath = resolveComponentSourcePath(componentName, context);
|
|
3277
|
+
if (!sourcePath)
|
|
3278
|
+
return;
|
|
3279
|
+
if (!sourcePath.endsWith(".ts"))
|
|
3280
|
+
return;
|
|
3281
|
+
const contractPath = resolveContractPath(contract, context);
|
|
3282
|
+
if (!contractPath)
|
|
3283
|
+
return;
|
|
3284
|
+
let contractContent;
|
|
3285
|
+
try {
|
|
3286
|
+
contractContent = await fs.promises.readFile(contractPath, "utf-8");
|
|
3287
|
+
} catch {
|
|
3288
|
+
return;
|
|
3289
|
+
}
|
|
3290
|
+
const parsed = parseContract(contractContent, path.basename(contractPath));
|
|
3291
|
+
if (parsed.validations.length > 0)
|
|
3292
|
+
return;
|
|
3293
|
+
let sourceCode;
|
|
3294
|
+
try {
|
|
3295
|
+
sourceCode = await fs.promises.readFile(sourcePath, "utf-8");
|
|
3296
|
+
} catch {
|
|
3297
|
+
return;
|
|
3298
|
+
}
|
|
3299
|
+
const contractName = contract.contract.replace(/\.jay-contract$/, "");
|
|
3300
|
+
const checkResult = checkComponentPropsAndParams(
|
|
3301
|
+
sourceCode,
|
|
3302
|
+
{
|
|
3303
|
+
props: parsed.val?.props,
|
|
3304
|
+
params: parsed.val?.params
|
|
3305
|
+
},
|
|
3306
|
+
contractName,
|
|
3307
|
+
contractPath,
|
|
3308
|
+
sourcePath
|
|
3309
|
+
);
|
|
3310
|
+
result.errors.push(...checkResult.errors);
|
|
3311
|
+
result.warnings.push(...checkResult.warnings);
|
|
2804
3312
|
}
|
|
2805
3313
|
async function validatePackageJson(context, result) {
|
|
2806
3314
|
const packageJsonPath = path.join(context.pluginPath, "package.json");
|
|
@@ -3327,6 +3835,101 @@ function checkRouteParams(parsedFile, filePath, pagesBase, jayHtmlContent) {
|
|
|
3327
3835
|
}
|
|
3328
3836
|
return warnings;
|
|
3329
3837
|
}
|
|
3838
|
+
function checkRouteToContractParams(parsedFile, filePath, pagesBase) {
|
|
3839
|
+
const routeParams = extractRouteParams(filePath, pagesBase);
|
|
3840
|
+
if (routeParams.size === 0)
|
|
3841
|
+
return [];
|
|
3842
|
+
const hasAnyContract = !!parsedFile.contract || parsedFile.headlessImports.some((imp) => !!imp.contract);
|
|
3843
|
+
if (!hasAnyContract)
|
|
3844
|
+
return [];
|
|
3845
|
+
const declaredParams = /* @__PURE__ */ new Set();
|
|
3846
|
+
if (parsedFile.contract?.params) {
|
|
3847
|
+
for (const p of parsedFile.contract.params) {
|
|
3848
|
+
declaredParams.add(p.name);
|
|
3849
|
+
}
|
|
3850
|
+
}
|
|
3851
|
+
for (const imp of parsedFile.headlessImports) {
|
|
3852
|
+
if (imp.contract?.params) {
|
|
3853
|
+
for (const p of imp.contract.params) {
|
|
3854
|
+
declaredParams.add(p.name);
|
|
3855
|
+
}
|
|
3856
|
+
}
|
|
3857
|
+
}
|
|
3858
|
+
const warnings = [];
|
|
3859
|
+
for (const routeParam of routeParams) {
|
|
3860
|
+
if (!declaredParams.has(routeParam)) {
|
|
3861
|
+
warnings.push(
|
|
3862
|
+
`Route provides param "${routeParam}" but no contract on this page declares it. Add params: { ${routeParam}: string } to the appropriate contract.`
|
|
3863
|
+
);
|
|
3864
|
+
}
|
|
3865
|
+
}
|
|
3866
|
+
return warnings;
|
|
3867
|
+
}
|
|
3868
|
+
const HEADLESS_SKIP_ATTRS = /* @__PURE__ */ new Set([
|
|
3869
|
+
"foreach",
|
|
3870
|
+
"if",
|
|
3871
|
+
"ref",
|
|
3872
|
+
"trackby",
|
|
3873
|
+
"slowforeach",
|
|
3874
|
+
"jayindex",
|
|
3875
|
+
"jaytrackby",
|
|
3876
|
+
"when-resolved",
|
|
3877
|
+
"when-loading",
|
|
3878
|
+
"when-rejected",
|
|
3879
|
+
"accessor",
|
|
3880
|
+
"props",
|
|
3881
|
+
"key",
|
|
3882
|
+
"jay-coordinate-base",
|
|
3883
|
+
"jay-scope"
|
|
3884
|
+
]);
|
|
3885
|
+
function checkHeadlessInstanceProps(jayHtml, file) {
|
|
3886
|
+
const imports = jayHtml.headlessImports;
|
|
3887
|
+
const warnings = [];
|
|
3888
|
+
function walkElement(element) {
|
|
3889
|
+
const tagName = element.rawTagName?.toLowerCase();
|
|
3890
|
+
if (tagName?.startsWith("jay:")) {
|
|
3891
|
+
const contractName = tagName.substring(4);
|
|
3892
|
+
const imp = imports.find((i) => i.contractName === contractName && i.contract);
|
|
3893
|
+
if (imp?.contract) {
|
|
3894
|
+
const contract = imp.contract;
|
|
3895
|
+
const attrs = element.attributes ?? {};
|
|
3896
|
+
const passedProps = /* @__PURE__ */ new Set();
|
|
3897
|
+
for (const attrName of Object.keys(attrs)) {
|
|
3898
|
+
if (!HEADLESS_SKIP_ATTRS.has(attrName.toLowerCase())) {
|
|
3899
|
+
passedProps.add(attrName);
|
|
3900
|
+
}
|
|
3901
|
+
}
|
|
3902
|
+
if (passedProps.size > 0) {
|
|
3903
|
+
const contractPropNames = new Set((contract.props || []).map((p) => p.name));
|
|
3904
|
+
for (const prop of passedProps) {
|
|
3905
|
+
if (!contractPropNames.has(prop)) {
|
|
3906
|
+
imp.key ? `${imp.key} (${contractName})` : contractName;
|
|
3907
|
+
warnings.push(
|
|
3908
|
+
`<jay:${contractName}> passes attribute "${prop}" but the "${contract.name}" contract does not declare it as a prop. Add to ${contractName}.jay-contract: props: [{ name: ${prop}, type: string }]`
|
|
3909
|
+
);
|
|
3910
|
+
}
|
|
3911
|
+
}
|
|
3912
|
+
}
|
|
3913
|
+
if (contract.props) {
|
|
3914
|
+
for (const contractProp of contract.props) {
|
|
3915
|
+
if (contractProp.required && !passedProps.has(contractProp.name)) {
|
|
3916
|
+
warnings.push(
|
|
3917
|
+
`<jay:${contractName}> is missing required prop "${contractProp.name}" declared in the "${contract.name}" contract.`
|
|
3918
|
+
);
|
|
3919
|
+
}
|
|
3920
|
+
}
|
|
3921
|
+
}
|
|
3922
|
+
}
|
|
3923
|
+
}
|
|
3924
|
+
for (const child of element.childNodes ?? []) {
|
|
3925
|
+
if (child.nodeType === 1) {
|
|
3926
|
+
walkElement(child);
|
|
3927
|
+
}
|
|
3928
|
+
}
|
|
3929
|
+
}
|
|
3930
|
+
walkElement(jayHtml.body);
|
|
3931
|
+
return warnings;
|
|
3932
|
+
}
|
|
3330
3933
|
async function validateJayFiles(options = {}) {
|
|
3331
3934
|
const config = loadConfig();
|
|
3332
3935
|
const resolvedConfig = getConfigWithDefaults(config);
|
|
@@ -3404,10 +4007,22 @@ async function validateJayFiles(options = {}) {
|
|
|
3404
4007
|
for (const msg of routeParamWarnings) {
|
|
3405
4008
|
warnings.push({ file: relativePath, message: msg });
|
|
3406
4009
|
}
|
|
4010
|
+
const routeToContractWarnings = checkRouteToContractParams(
|
|
4011
|
+
parsedFile.val,
|
|
4012
|
+
jayFile,
|
|
4013
|
+
scanDir
|
|
4014
|
+
);
|
|
4015
|
+
for (const msg of routeToContractWarnings) {
|
|
4016
|
+
warnings.push({ file: relativePath, message: msg });
|
|
4017
|
+
}
|
|
3407
4018
|
const refTypeErrors = checkRefElementTypes(parsedFile.val, relativePath);
|
|
3408
4019
|
for (const msg of refTypeErrors) {
|
|
3409
4020
|
errors.push({ file: relativePath, message: msg, stage: "generate" });
|
|
3410
4021
|
}
|
|
4022
|
+
const headlessPropWarnings = checkHeadlessInstanceProps(parsedFile.val, relativePath);
|
|
4023
|
+
for (const msg of headlessPropWarnings) {
|
|
4024
|
+
warnings.push({ file: relativePath, message: msg });
|
|
4025
|
+
}
|
|
3411
4026
|
const fileCoverage = analyzeTagCoverage(parsedFile.val, relativePath);
|
|
3412
4027
|
if (fileCoverage) {
|
|
3413
4028
|
coverage.push(fileCoverage);
|
|
@@ -3878,32 +4493,32 @@ program.command("validate-plugin [path]").description("Validate a Jay Stack plug
|
|
|
3878
4493
|
process.exit(1);
|
|
3879
4494
|
}
|
|
3880
4495
|
});
|
|
3881
|
-
|
|
4496
|
+
const ALL_ROLES = ["designer", "developer", "plugin"];
|
|
4497
|
+
async function ensureAgentKitDocs(projectRoot, _force, mode) {
|
|
3882
4498
|
const path2 = await import("node:path");
|
|
3883
4499
|
const fs2 = await import("node:fs/promises");
|
|
3884
4500
|
const { fileURLToPath } = await import("node:url");
|
|
3885
4501
|
const agentKitDir = path2.join(projectRoot, "agent-kit");
|
|
3886
|
-
await fs2.mkdir(agentKitDir, { recursive: true });
|
|
3887
4502
|
const thisDir = path2.dirname(fileURLToPath(import.meta.url));
|
|
3888
4503
|
const templateDir = path2.resolve(thisDir, "..", "agent-kit-template");
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
4504
|
+
const roles = mode && ALL_ROLES.includes(mode) ? [mode] : ALL_ROLES;
|
|
4505
|
+
for (const role of roles) {
|
|
4506
|
+
const roleTemplateDir = path2.join(templateDir, role);
|
|
4507
|
+
const roleOutputDir = path2.join(agentKitDir, role);
|
|
4508
|
+
let files;
|
|
4509
|
+
try {
|
|
4510
|
+
files = (await fs2.readdir(roleTemplateDir)).filter((f) => f.endsWith(".md"));
|
|
4511
|
+
} catch {
|
|
4512
|
+
continue;
|
|
4513
|
+
}
|
|
4514
|
+
await fs2.mkdir(roleOutputDir, { recursive: true });
|
|
4515
|
+
for (const filename of files) {
|
|
4516
|
+
await fs2.copyFile(
|
|
4517
|
+
path2.join(roleTemplateDir, filename),
|
|
4518
|
+
path2.join(roleOutputDir, filename)
|
|
4519
|
+
);
|
|
4520
|
+
getLogger().info(chalk.gray(` Created agent-kit/${role}/${filename}`));
|
|
3904
4521
|
}
|
|
3905
|
-
await fs2.copyFile(path2.join(templateDir, filename), destPath);
|
|
3906
|
-
getLogger().info(chalk.gray(` Created agent-kit/${filename}`));
|
|
3907
4522
|
}
|
|
3908
4523
|
}
|
|
3909
4524
|
async function generatePluginReferences(projectRoot, options, initErrors, viteServer) {
|
|
@@ -4025,7 +4640,10 @@ program.command("setup [plugin]").description(
|
|
|
4025
4640
|
});
|
|
4026
4641
|
program.command("agent-kit").description(
|
|
4027
4642
|
"Prepare the agent kit: materialize contracts, generate references, and write docs to agent-kit/"
|
|
4028
|
-
).option("-o, --output <dir>", "Output directory (default: agent-kit/materialized-contracts)").option("--yaml", "Output contract index as YAML to stdout").option("--list", "List contracts without writing files").option("--plugin <name>", "Filter to specific plugin").option("--dynamic-only", "Only process dynamic contracts").option("--force", "Force re-materialization").option("--no-references", "Skip reference data generation").option(
|
|
4643
|
+
).option("-o, --output <dir>", "Output directory (default: agent-kit/materialized-contracts)").option("--yaml", "Output contract index as YAML to stdout").option("--list", "List contracts without writing files").option("--plugin <name>", "Filter to specific plugin").option("--dynamic-only", "Only process dynamic contracts").option("--force", "Force re-materialization").option("--no-references", "Skip reference data generation").option(
|
|
4644
|
+
"-m, --mode <role>",
|
|
4645
|
+
"Generate guides for a specific role: designer, developer, or plugin (default: all)"
|
|
4646
|
+
).option("-v, --verbose", "Show detailed output").action(async (options) => {
|
|
4029
4647
|
const projectRoot = process.cwd();
|
|
4030
4648
|
const { initErrors, viteServer } = await runMaterialize(
|
|
4031
4649
|
projectRoot,
|
|
@@ -4036,7 +4654,7 @@ program.command("agent-kit").description(
|
|
|
4036
4654
|
);
|
|
4037
4655
|
try {
|
|
4038
4656
|
if (!options.list) {
|
|
4039
|
-
await ensureAgentKitDocs(projectRoot, options.force);
|
|
4657
|
+
await ensureAgentKitDocs(projectRoot, options.force, options.mode);
|
|
4040
4658
|
if (options.references !== false) {
|
|
4041
4659
|
await generatePluginReferences(projectRoot, options, initErrors, viteServer);
|
|
4042
4660
|
}
|