@jay-framework/jay-stack-cli 0.15.5 → 0.16.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/{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/dev-server-service.md +126 -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 +175 -0
- package/agent-kit-template/developer/seo-guide.md +93 -0
- package/agent-kit-template/plugin/INSTRUCTIONS.md +43 -0
- package/agent-kit-template/plugin/actions-guide.md +184 -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/dev-server-service.md +137 -0
- package/agent-kit-template/plugin/plugin-routes.md +146 -0
- package/agent-kit-template/plugin/plugin-structure.md +210 -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 +791 -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
|
};
|
|
@@ -2440,7 +2441,7 @@ async function startDevServer(options = {}) {
|
|
|
2440
2441
|
editorServer.onGetProjectInfo(handlers.onGetProjectInfo);
|
|
2441
2442
|
editorServer.onExport(handlers.onExport);
|
|
2442
2443
|
editorServer.onImport(handlers.onImport);
|
|
2443
|
-
const { server, viteServer, routes } = await mkDevServer({
|
|
2444
|
+
const { server, viteServer, routes, service } = await mkDevServer({
|
|
2444
2445
|
pagesRootFolder: path.resolve(resolvedConfig.devServer.pagesBase),
|
|
2445
2446
|
projectRootFolder: process.cwd(),
|
|
2446
2447
|
publicBaseUrlPath: "/",
|
|
@@ -2449,6 +2450,73 @@ async function startDevServer(options = {}) {
|
|
|
2449
2450
|
httpServer
|
|
2450
2451
|
});
|
|
2451
2452
|
app.use(server);
|
|
2453
|
+
const { freezeStore } = service;
|
|
2454
|
+
editorServer.onListRoutes(async () => ({
|
|
2455
|
+
type: "listRoutes",
|
|
2456
|
+
success: true,
|
|
2457
|
+
routes: service.listRoutes()
|
|
2458
|
+
}));
|
|
2459
|
+
if (freezeStore) {
|
|
2460
|
+
editorServer.onListFreezes(async (params) => ({
|
|
2461
|
+
type: "listFreezes",
|
|
2462
|
+
success: true,
|
|
2463
|
+
freezes: (await freezeStore.list(params.route)).map(
|
|
2464
|
+
({ id, name, route, routePattern, createdAt }) => ({
|
|
2465
|
+
id,
|
|
2466
|
+
name,
|
|
2467
|
+
route,
|
|
2468
|
+
routePattern,
|
|
2469
|
+
createdAt
|
|
2470
|
+
})
|
|
2471
|
+
)
|
|
2472
|
+
}));
|
|
2473
|
+
editorServer.onRenameFreeze(async (params) => ({
|
|
2474
|
+
type: "renameFreeze",
|
|
2475
|
+
success: await freezeStore.rename(params.id, params.name)
|
|
2476
|
+
}));
|
|
2477
|
+
editorServer.onDeleteFreeze(async (params) => ({
|
|
2478
|
+
type: "deleteFreeze",
|
|
2479
|
+
success: await freezeStore.delete(params.id)
|
|
2480
|
+
}));
|
|
2481
|
+
viteServer.watcher.on("change", (changedPath) => {
|
|
2482
|
+
if (changedPath.endsWith(".jay-html") || changedPath.endsWith(".css")) {
|
|
2483
|
+
editorServer.emitFreezeChanged();
|
|
2484
|
+
}
|
|
2485
|
+
});
|
|
2486
|
+
}
|
|
2487
|
+
editorServer.onLoadRouteParams(async (params) => {
|
|
2488
|
+
const routePath = params.route;
|
|
2489
|
+
try {
|
|
2490
|
+
(async () => {
|
|
2491
|
+
try {
|
|
2492
|
+
for await (const batch of service.loadRouteParams(routePath)) {
|
|
2493
|
+
editorServer.emitRouteParamsBatch({
|
|
2494
|
+
type: "routeParamsBatch",
|
|
2495
|
+
route: routePath,
|
|
2496
|
+
params: batch,
|
|
2497
|
+
hasMore: true
|
|
2498
|
+
});
|
|
2499
|
+
}
|
|
2500
|
+
editorServer.emitRouteParamsBatch({
|
|
2501
|
+
type: "routeParamsBatch",
|
|
2502
|
+
route: routePath,
|
|
2503
|
+
params: [],
|
|
2504
|
+
hasMore: false
|
|
2505
|
+
});
|
|
2506
|
+
} catch (err) {
|
|
2507
|
+
editorServer.emitRouteParamsBatch({
|
|
2508
|
+
type: "routeParamsBatch",
|
|
2509
|
+
route: routePath,
|
|
2510
|
+
params: [],
|
|
2511
|
+
hasMore: false
|
|
2512
|
+
});
|
|
2513
|
+
}
|
|
2514
|
+
})();
|
|
2515
|
+
return { type: "loadRouteParams", success: true };
|
|
2516
|
+
} catch (err) {
|
|
2517
|
+
return { type: "loadRouteParams", success: false, error: err.message };
|
|
2518
|
+
}
|
|
2519
|
+
});
|
|
2452
2520
|
const publicPath = path.resolve(resolvedConfig.devServer.publicFolder);
|
|
2453
2521
|
if (fs.existsSync(publicPath)) {
|
|
2454
2522
|
app.use(express.static(publicPath));
|
|
@@ -2511,6 +2579,280 @@ async function startDevServer(options = {}) {
|
|
|
2511
2579
|
process.on("SIGTERM", shutdown);
|
|
2512
2580
|
process.on("SIGINT", shutdown);
|
|
2513
2581
|
}
|
|
2582
|
+
const s = createRequire(import.meta.url), e = s("typescript"), u = e;
|
|
2583
|
+
new Proxy(e, {
|
|
2584
|
+
get(t, r) {
|
|
2585
|
+
return t[r];
|
|
2586
|
+
}
|
|
2587
|
+
});
|
|
2588
|
+
const FRAMEWORK_PROP_TYPES = /* @__PURE__ */ new Set(["PageProps", "RequestQuery"]);
|
|
2589
|
+
function checkComponentPropsAndParams(sourceCode, contract, contractName, contractPath, sourcePath) {
|
|
2590
|
+
const errors = [];
|
|
2591
|
+
const warnings = [];
|
|
2592
|
+
const sourceFile = u.createSourceFile(
|
|
2593
|
+
sourcePath,
|
|
2594
|
+
sourceCode,
|
|
2595
|
+
u.ScriptTarget.Latest,
|
|
2596
|
+
true,
|
|
2597
|
+
u.ScriptKind.TS
|
|
2598
|
+
);
|
|
2599
|
+
const localInterfaces = collectLocalInterfaces(sourceFile);
|
|
2600
|
+
const contractImportedTypes = collectContractImportedTypes(sourceFile);
|
|
2601
|
+
const builderInfo = analyzeBuilderChains(sourceFile);
|
|
2602
|
+
for (const propsTypeName of builderInfo.propsTypeNames) {
|
|
2603
|
+
checkPropsConsistency(
|
|
2604
|
+
propsTypeName,
|
|
2605
|
+
localInterfaces,
|
|
2606
|
+
contractImportedTypes,
|
|
2607
|
+
contract,
|
|
2608
|
+
contractName,
|
|
2609
|
+
contractPath,
|
|
2610
|
+
sourcePath,
|
|
2611
|
+
errors,
|
|
2612
|
+
warnings
|
|
2613
|
+
);
|
|
2614
|
+
}
|
|
2615
|
+
if (builderInfo.hasLoadParams) {
|
|
2616
|
+
checkParamsConsistency(
|
|
2617
|
+
builderInfo.paramsTypeNames,
|
|
2618
|
+
localInterfaces,
|
|
2619
|
+
contractImportedTypes,
|
|
2620
|
+
contract,
|
|
2621
|
+
contractName,
|
|
2622
|
+
contractPath,
|
|
2623
|
+
sourcePath,
|
|
2624
|
+
errors,
|
|
2625
|
+
warnings
|
|
2626
|
+
);
|
|
2627
|
+
}
|
|
2628
|
+
return { errors, warnings };
|
|
2629
|
+
}
|
|
2630
|
+
function collectLocalInterfaces(sourceFile) {
|
|
2631
|
+
const interfaces = /* @__PURE__ */ new Map();
|
|
2632
|
+
for (const statement of sourceFile.statements) {
|
|
2633
|
+
if (u.isInterfaceDeclaration(statement)) {
|
|
2634
|
+
const name = statement.name.text;
|
|
2635
|
+
const properties = [];
|
|
2636
|
+
for (const member of statement.members) {
|
|
2637
|
+
if (u.isPropertySignature(member) && member.name) {
|
|
2638
|
+
if (u.isIdentifier(member.name)) {
|
|
2639
|
+
properties.push(member.name.text);
|
|
2640
|
+
} else if (u.isStringLiteral(member.name)) {
|
|
2641
|
+
properties.push(member.name.text);
|
|
2642
|
+
}
|
|
2643
|
+
}
|
|
2644
|
+
}
|
|
2645
|
+
const extendsTypes = [];
|
|
2646
|
+
if (statement.heritageClauses) {
|
|
2647
|
+
for (const clause of statement.heritageClauses) {
|
|
2648
|
+
for (const type of clause.types) {
|
|
2649
|
+
if (u.isIdentifier(type.expression)) {
|
|
2650
|
+
extendsTypes.push(type.expression.text);
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
2653
|
+
}
|
|
2654
|
+
}
|
|
2655
|
+
interfaces.set(name, { name, properties, extendsTypes });
|
|
2656
|
+
}
|
|
2657
|
+
if (u.isTypeAliasDeclaration(statement)) {
|
|
2658
|
+
const name = statement.name.text;
|
|
2659
|
+
if (u.isTypeLiteralNode(statement.type)) {
|
|
2660
|
+
const properties = [];
|
|
2661
|
+
for (const member of statement.type.members) {
|
|
2662
|
+
if (u.isPropertySignature(member) && member.name) {
|
|
2663
|
+
if (u.isIdentifier(member.name)) {
|
|
2664
|
+
properties.push(member.name.text);
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
interfaces.set(name, { name, properties, extendsTypes: [] });
|
|
2669
|
+
}
|
|
2670
|
+
}
|
|
2671
|
+
}
|
|
2672
|
+
return interfaces;
|
|
2673
|
+
}
|
|
2674
|
+
function collectContractImportedTypes(sourceFile) {
|
|
2675
|
+
const contractTypes = /* @__PURE__ */ new Set();
|
|
2676
|
+
for (const statement of sourceFile.statements) {
|
|
2677
|
+
if (!u.isImportDeclaration(statement))
|
|
2678
|
+
continue;
|
|
2679
|
+
const moduleSpecifier = statement.moduleSpecifier;
|
|
2680
|
+
if (!u.isStringLiteral(moduleSpecifier))
|
|
2681
|
+
continue;
|
|
2682
|
+
const modulePath = moduleSpecifier.text;
|
|
2683
|
+
if (!modulePath.includes(".jay-contract"))
|
|
2684
|
+
continue;
|
|
2685
|
+
const importClause = statement.importClause;
|
|
2686
|
+
if (!importClause)
|
|
2687
|
+
continue;
|
|
2688
|
+
const namedBindings = importClause.namedBindings;
|
|
2689
|
+
if (namedBindings && u.isNamedImports(namedBindings)) {
|
|
2690
|
+
for (const element of namedBindings.elements) {
|
|
2691
|
+
contractTypes.add(element.name.text);
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2695
|
+
return contractTypes;
|
|
2696
|
+
}
|
|
2697
|
+
function analyzeBuilderChains(sourceFile) {
|
|
2698
|
+
const result = {
|
|
2699
|
+
propsTypeNames: [],
|
|
2700
|
+
hasLoadParams: false,
|
|
2701
|
+
paramsTypeNames: []
|
|
2702
|
+
};
|
|
2703
|
+
visitNode(sourceFile, result);
|
|
2704
|
+
return result;
|
|
2705
|
+
}
|
|
2706
|
+
function visitNode(node, result) {
|
|
2707
|
+
if (u.isCallExpression(node)) {
|
|
2708
|
+
const expr = node.expression;
|
|
2709
|
+
if (u.isPropertyAccessExpression(expr) && expr.name.text === "withProps") {
|
|
2710
|
+
if (node.typeArguments && node.typeArguments.length > 0) {
|
|
2711
|
+
const typeNames = extractTypeNames(node.typeArguments[0]);
|
|
2712
|
+
result.propsTypeNames.push(...typeNames);
|
|
2713
|
+
}
|
|
2714
|
+
}
|
|
2715
|
+
if (u.isPropertyAccessExpression(expr) && expr.name.text === "withLoadParams") {
|
|
2716
|
+
result.hasLoadParams = true;
|
|
2717
|
+
if (node.typeArguments && node.typeArguments.length > 0) {
|
|
2718
|
+
const typeNames = extractTypeNames(node.typeArguments[0]);
|
|
2719
|
+
result.paramsTypeNames.push(...typeNames);
|
|
2720
|
+
}
|
|
2721
|
+
if (node.arguments.length > 0) {
|
|
2722
|
+
const arg = node.arguments[0];
|
|
2723
|
+
if (u.isIdentifier(arg))
|
|
2724
|
+
;
|
|
2725
|
+
}
|
|
2726
|
+
}
|
|
2727
|
+
}
|
|
2728
|
+
u.forEachChild(node, (child) => visitNode(child, result));
|
|
2729
|
+
}
|
|
2730
|
+
function extractTypeNames(typeNode) {
|
|
2731
|
+
if (u.isTypeReferenceNode(typeNode)) {
|
|
2732
|
+
if (u.isIdentifier(typeNode.typeName)) {
|
|
2733
|
+
return [typeNode.typeName.text];
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2736
|
+
if (u.isIntersectionTypeNode(typeNode)) {
|
|
2737
|
+
const names = [];
|
|
2738
|
+
for (const member of typeNode.types) {
|
|
2739
|
+
names.push(...extractTypeNames(member));
|
|
2740
|
+
}
|
|
2741
|
+
return names;
|
|
2742
|
+
}
|
|
2743
|
+
return [];
|
|
2744
|
+
}
|
|
2745
|
+
function checkPropsConsistency(propsTypeName, localInterfaces, contractImportedTypes, contract, contractName, contractPath, sourcePath, errors, warnings) {
|
|
2746
|
+
if (FRAMEWORK_PROP_TYPES.has(propsTypeName))
|
|
2747
|
+
return;
|
|
2748
|
+
if (contractImportedTypes.has(propsTypeName))
|
|
2749
|
+
return;
|
|
2750
|
+
const prefix = `[${contractName}]`;
|
|
2751
|
+
const iface = localInterfaces.get(propsTypeName);
|
|
2752
|
+
if (!iface) {
|
|
2753
|
+
if (!contract.props || contract.props.length === 0) {
|
|
2754
|
+
errors.push({
|
|
2755
|
+
type: "contract-invalid",
|
|
2756
|
+
message: `${prefix} component uses .withProps<${propsTypeName}>() but the contract does not declare any props`,
|
|
2757
|
+
location: contractPath,
|
|
2758
|
+
suggestion: `Add a props section to the contract`
|
|
2759
|
+
});
|
|
2760
|
+
}
|
|
2761
|
+
return;
|
|
2762
|
+
}
|
|
2763
|
+
const ownProperties = iface.properties;
|
|
2764
|
+
if (ownProperties.length === 0)
|
|
2765
|
+
return;
|
|
2766
|
+
if (!contract.props || contract.props.length === 0) {
|
|
2767
|
+
errors.push({
|
|
2768
|
+
type: "contract-invalid",
|
|
2769
|
+
message: `${prefix} component uses .withProps<${propsTypeName}>() with properties [${ownProperties.join(", ")}] but the contract does not declare any props`,
|
|
2770
|
+
location: contractPath,
|
|
2771
|
+
suggestion: `Add to contract: props:
|
|
2772
|
+
` + ownProperties.map((p) => ` - name: ${p}
|
|
2773
|
+
type: string`).join("\n")
|
|
2774
|
+
});
|
|
2775
|
+
return;
|
|
2776
|
+
}
|
|
2777
|
+
const contractPropNames = new Set(contract.props.map((p) => p.name));
|
|
2778
|
+
for (const prop of ownProperties) {
|
|
2779
|
+
if (!contractPropNames.has(prop)) {
|
|
2780
|
+
errors.push({
|
|
2781
|
+
type: "contract-invalid",
|
|
2782
|
+
message: `${prefix} component prop "${prop}" (from ${propsTypeName}) is not declared in the contract`,
|
|
2783
|
+
location: contractPath,
|
|
2784
|
+
suggestion: `Add to contract props: - name: ${prop}`
|
|
2785
|
+
});
|
|
2786
|
+
}
|
|
2787
|
+
}
|
|
2788
|
+
for (const contractProp of contract.props) {
|
|
2789
|
+
if (!ownProperties.includes(contractProp.name)) {
|
|
2790
|
+
warnings.push({
|
|
2791
|
+
type: "contract-invalid",
|
|
2792
|
+
message: `${prefix} contract declares prop "${contractProp.name}" but the component's ${propsTypeName} interface does not include it`,
|
|
2793
|
+
location: sourcePath,
|
|
2794
|
+
suggestion: `Add "${contractProp.name}" to the ${propsTypeName} interface, or remove it from the contract`
|
|
2795
|
+
});
|
|
2796
|
+
}
|
|
2797
|
+
}
|
|
2798
|
+
}
|
|
2799
|
+
function checkParamsConsistency(paramsTypeNames, localInterfaces, contractImportedTypes, contract, contractName, contractPath, sourcePath, errors, warnings) {
|
|
2800
|
+
const prefix = `[${contractName}]`;
|
|
2801
|
+
if (!contract.params || contract.params.length === 0) {
|
|
2802
|
+
errors.push({
|
|
2803
|
+
type: "contract-invalid",
|
|
2804
|
+
message: `${prefix} component uses .withLoadParams() but the contract does not declare any params`,
|
|
2805
|
+
location: contractPath,
|
|
2806
|
+
suggestion: `Add a params section to the contract (e.g., params: { slug: string })`
|
|
2807
|
+
});
|
|
2808
|
+
for (const typeName of paramsTypeNames) {
|
|
2809
|
+
if (FRAMEWORK_PROP_TYPES.has(typeName))
|
|
2810
|
+
continue;
|
|
2811
|
+
if (contractImportedTypes.has(typeName))
|
|
2812
|
+
continue;
|
|
2813
|
+
const iface = localInterfaces.get(typeName);
|
|
2814
|
+
if (iface) {
|
|
2815
|
+
const ownProps = iface.properties;
|
|
2816
|
+
if (ownProps.length > 0) {
|
|
2817
|
+
errors[errors.length - 1].suggestion = `Add to contract: params:
|
|
2818
|
+
` + ownProps.map((p) => ` ${p}: string`).join("\n");
|
|
2819
|
+
}
|
|
2820
|
+
}
|
|
2821
|
+
}
|
|
2822
|
+
return;
|
|
2823
|
+
}
|
|
2824
|
+
for (const typeName of paramsTypeNames) {
|
|
2825
|
+
if (FRAMEWORK_PROP_TYPES.has(typeName))
|
|
2826
|
+
continue;
|
|
2827
|
+
if (contractImportedTypes.has(typeName))
|
|
2828
|
+
continue;
|
|
2829
|
+
const iface = localInterfaces.get(typeName);
|
|
2830
|
+
if (!iface)
|
|
2831
|
+
continue;
|
|
2832
|
+
const ownProperties = iface.properties;
|
|
2833
|
+
const contractParamNames = new Set(contract.params.map((p) => p.name));
|
|
2834
|
+
for (const prop of ownProperties) {
|
|
2835
|
+
if (!contractParamNames.has(prop)) {
|
|
2836
|
+
errors.push({
|
|
2837
|
+
type: "contract-invalid",
|
|
2838
|
+
message: `${prefix} component param "${prop}" (from ${typeName}) is not declared in the contract`,
|
|
2839
|
+
location: contractPath,
|
|
2840
|
+
suggestion: `Add to contract params: ${prop}: string`
|
|
2841
|
+
});
|
|
2842
|
+
}
|
|
2843
|
+
}
|
|
2844
|
+
for (const contractParam of contract.params) {
|
|
2845
|
+
if (!ownProperties.includes(contractParam.name)) {
|
|
2846
|
+
warnings.push({
|
|
2847
|
+
type: "contract-invalid",
|
|
2848
|
+
message: `${prefix} contract declares param "${contractParam.name}" but the component's ${typeName} interface does not include it`,
|
|
2849
|
+
location: sourcePath,
|
|
2850
|
+
suggestion: `Add "${contractParam.name}" to the ${typeName} interface, or remove it from the contract`
|
|
2851
|
+
});
|
|
2852
|
+
}
|
|
2853
|
+
}
|
|
2854
|
+
}
|
|
2855
|
+
}
|
|
2514
2856
|
async function validatePlugin(options = {}) {
|
|
2515
2857
|
const pluginPath = options.pluginPath || process.cwd();
|
|
2516
2858
|
if (options.local) {
|
|
@@ -2612,6 +2954,36 @@ async function validateLocalPlugins(projectPath, options) {
|
|
|
2612
2954
|
typesGenerated: allResults.reduce((sum, r) => sum + (r.typesGenerated || 0), 0)
|
|
2613
2955
|
};
|
|
2614
2956
|
}
|
|
2957
|
+
function validateDocFile(docPath, label, context, result) {
|
|
2958
|
+
const resolvedPath = path.join(context.pluginPath, docPath);
|
|
2959
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
2960
|
+
result.errors.push({
|
|
2961
|
+
type: "file-missing",
|
|
2962
|
+
message: `Doc file for ${label} not found: ${docPath}`,
|
|
2963
|
+
location: "plugin.yaml",
|
|
2964
|
+
suggestion: `Create the documentation file at ${resolvedPath}`
|
|
2965
|
+
});
|
|
2966
|
+
return;
|
|
2967
|
+
}
|
|
2968
|
+
if (context.isNpmPackage) {
|
|
2969
|
+
const packageJsonPath = path.join(context.pluginPath, "package.json");
|
|
2970
|
+
try {
|
|
2971
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
2972
|
+
if (packageJson.exports) {
|
|
2973
|
+
const exportKey = "./" + docPath.replace(/^\.\//, "");
|
|
2974
|
+
if (!packageJson.exports[exportKey]) {
|
|
2975
|
+
result.errors.push({
|
|
2976
|
+
type: "export-mismatch",
|
|
2977
|
+
message: `Doc file for ${label} is not exported in package.json: ${docPath}`,
|
|
2978
|
+
location: packageJsonPath,
|
|
2979
|
+
suggestion: `Add "${exportKey}": "${docPath}" to the exports field`
|
|
2980
|
+
});
|
|
2981
|
+
}
|
|
2982
|
+
}
|
|
2983
|
+
} catch {
|
|
2984
|
+
}
|
|
2985
|
+
}
|
|
2986
|
+
}
|
|
2615
2987
|
async function validateSchema(context, result) {
|
|
2616
2988
|
const { manifest } = context;
|
|
2617
2989
|
if (!manifest.name) {
|
|
@@ -2702,46 +3074,157 @@ async function validateSchema(context, result) {
|
|
|
2702
3074
|
suggestion: 'Add either "contracts" or "dynamic_contracts" to expose functionality'
|
|
2703
3075
|
});
|
|
2704
3076
|
}
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
3077
|
+
if (manifest.services) {
|
|
3078
|
+
if (!Array.isArray(manifest.services)) {
|
|
3079
|
+
result.errors.push({
|
|
3080
|
+
type: "schema",
|
|
3081
|
+
message: 'Field "services" must be an array',
|
|
3082
|
+
location: "plugin.yaml"
|
|
3083
|
+
});
|
|
3084
|
+
} else {
|
|
3085
|
+
manifest.services.forEach((service, index) => {
|
|
3086
|
+
if (!service.name) {
|
|
3087
|
+
result.errors.push({
|
|
3088
|
+
type: "schema",
|
|
3089
|
+
message: `Service at index ${index} is missing "name" field`,
|
|
3090
|
+
location: "plugin.yaml"
|
|
3091
|
+
});
|
|
3092
|
+
}
|
|
3093
|
+
if (!service.marker) {
|
|
3094
|
+
result.errors.push({
|
|
3095
|
+
type: "schema",
|
|
3096
|
+
message: `Service "${service.name || index}" is missing "marker" field`,
|
|
3097
|
+
location: "plugin.yaml",
|
|
3098
|
+
suggestion: "Specify the exported service marker constant name"
|
|
3099
|
+
});
|
|
3100
|
+
}
|
|
3101
|
+
if (service.doc) {
|
|
3102
|
+
validateDocFile(service.doc, `service "${service.name}"`, context, result);
|
|
3103
|
+
}
|
|
3104
|
+
});
|
|
2723
3105
|
}
|
|
2724
|
-
|
|
3106
|
+
}
|
|
3107
|
+
if (manifest.contexts) {
|
|
3108
|
+
if (!Array.isArray(manifest.contexts)) {
|
|
2725
3109
|
result.errors.push({
|
|
2726
|
-
type: "
|
|
2727
|
-
message:
|
|
2728
|
-
location:
|
|
2729
|
-
|
|
3110
|
+
type: "schema",
|
|
3111
|
+
message: 'Field "contexts" must be an array',
|
|
3112
|
+
location: "plugin.yaml"
|
|
3113
|
+
});
|
|
3114
|
+
} else {
|
|
3115
|
+
manifest.contexts.forEach((ctx, index) => {
|
|
3116
|
+
if (!ctx.name) {
|
|
3117
|
+
result.errors.push({
|
|
3118
|
+
type: "schema",
|
|
3119
|
+
message: `Context at index ${index} is missing "name" field`,
|
|
3120
|
+
location: "plugin.yaml"
|
|
3121
|
+
});
|
|
3122
|
+
}
|
|
3123
|
+
if (!ctx.marker) {
|
|
3124
|
+
result.errors.push({
|
|
3125
|
+
type: "schema",
|
|
3126
|
+
message: `Context "${ctx.name || index}" is missing "marker" field`,
|
|
3127
|
+
location: "plugin.yaml",
|
|
3128
|
+
suggestion: "Specify the exported context marker constant name"
|
|
3129
|
+
});
|
|
3130
|
+
}
|
|
3131
|
+
if (ctx.doc) {
|
|
3132
|
+
validateDocFile(ctx.doc, `context "${ctx.name}"`, context, result);
|
|
3133
|
+
}
|
|
2730
3134
|
});
|
|
2731
|
-
return;
|
|
2732
3135
|
}
|
|
2733
|
-
}
|
|
2734
|
-
|
|
2735
|
-
if (!
|
|
3136
|
+
}
|
|
3137
|
+
if (manifest.routes) {
|
|
3138
|
+
if (!Array.isArray(manifest.routes)) {
|
|
2736
3139
|
result.errors.push({
|
|
2737
|
-
type: "
|
|
2738
|
-
message:
|
|
2739
|
-
location:
|
|
2740
|
-
|
|
3140
|
+
type: "schema",
|
|
3141
|
+
message: 'Field "routes" must be an array',
|
|
3142
|
+
location: "plugin.yaml"
|
|
3143
|
+
});
|
|
3144
|
+
} else {
|
|
3145
|
+
manifest.routes.forEach((route, index) => {
|
|
3146
|
+
if (!route.path) {
|
|
3147
|
+
result.errors.push({
|
|
3148
|
+
type: "schema",
|
|
3149
|
+
message: `Route at index ${index} is missing "path" field`,
|
|
3150
|
+
location: "plugin.yaml"
|
|
3151
|
+
});
|
|
3152
|
+
}
|
|
3153
|
+
if (!route.jayHtml) {
|
|
3154
|
+
result.errors.push({
|
|
3155
|
+
type: "schema",
|
|
3156
|
+
message: `Route "${route.path || index}" is missing "jayHtml" field`,
|
|
3157
|
+
location: "plugin.yaml",
|
|
3158
|
+
suggestion: "Specify the export subpath for the jay-html file"
|
|
3159
|
+
});
|
|
3160
|
+
}
|
|
3161
|
+
if (!route.component) {
|
|
3162
|
+
result.errors.push({
|
|
3163
|
+
type: "schema",
|
|
3164
|
+
message: `Route "${route.path || index}" is missing "component" field`,
|
|
3165
|
+
location: "plugin.yaml",
|
|
3166
|
+
suggestion: "Specify the exported member name for the page component"
|
|
3167
|
+
});
|
|
3168
|
+
}
|
|
3169
|
+
if (route.jayHtml) {
|
|
3170
|
+
validateDocFile(
|
|
3171
|
+
route.jayHtml,
|
|
3172
|
+
`route "${route.path}" jayHtml`,
|
|
3173
|
+
context,
|
|
3174
|
+
result
|
|
3175
|
+
);
|
|
3176
|
+
}
|
|
3177
|
+
if (route.css) {
|
|
3178
|
+
validateDocFile(route.css, `route "${route.path}" css`, context, result);
|
|
3179
|
+
}
|
|
2741
3180
|
});
|
|
2742
|
-
return;
|
|
2743
3181
|
}
|
|
2744
3182
|
}
|
|
3183
|
+
}
|
|
3184
|
+
function resolveContractFile(contractSpec, context) {
|
|
3185
|
+
if (context.isNpmPackage) {
|
|
3186
|
+
const packageJsonPath = path.join(context.pluginPath, "package.json");
|
|
3187
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
3188
|
+
try {
|
|
3189
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
3190
|
+
if (packageJson.exports) {
|
|
3191
|
+
const exportKey = "./" + contractSpec;
|
|
3192
|
+
const exportValue = packageJson.exports[exportKey];
|
|
3193
|
+
if (exportValue) {
|
|
3194
|
+
const resolvedPath = typeof exportValue === "string" ? exportValue : exportValue.default || exportValue.import || exportValue.require;
|
|
3195
|
+
if (resolvedPath) {
|
|
3196
|
+
const fullPath = path.join(context.pluginPath, resolvedPath);
|
|
3197
|
+
if (fs.existsSync(fullPath))
|
|
3198
|
+
return fullPath;
|
|
3199
|
+
}
|
|
3200
|
+
}
|
|
3201
|
+
}
|
|
3202
|
+
} catch {
|
|
3203
|
+
}
|
|
3204
|
+
}
|
|
3205
|
+
for (const dir of ["dist", "lib", ""]) {
|
|
3206
|
+
const candidate = path.join(context.pluginPath, dir, contractSpec);
|
|
3207
|
+
if (fs.existsSync(candidate))
|
|
3208
|
+
return candidate;
|
|
3209
|
+
}
|
|
3210
|
+
return void 0;
|
|
3211
|
+
} else {
|
|
3212
|
+
const candidate = path.join(context.pluginPath, contractSpec);
|
|
3213
|
+
return fs.existsSync(candidate) ? candidate : void 0;
|
|
3214
|
+
}
|
|
3215
|
+
}
|
|
3216
|
+
async function validateContract(contract, index, context, generateTypes, result) {
|
|
3217
|
+
result.contractsChecked = (result.contractsChecked || 0) + 1;
|
|
3218
|
+
const contractPath = resolveContractFile(contract.contract, context);
|
|
3219
|
+
if (!contractPath) {
|
|
3220
|
+
result.errors.push({
|
|
3221
|
+
type: "file-missing",
|
|
3222
|
+
message: `Contract file not found: ${contract.contract}`,
|
|
3223
|
+
location: `plugin.yaml contracts[${index}]`,
|
|
3224
|
+
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)}`
|
|
3225
|
+
});
|
|
3226
|
+
return;
|
|
3227
|
+
}
|
|
2745
3228
|
try {
|
|
2746
3229
|
const contractContent = await fs.promises.readFile(contractPath, "utf-8");
|
|
2747
3230
|
const parsedContract = YAML.parse(contractContent);
|
|
@@ -2792,6 +3275,7 @@ async function validateComponent(contract, index, context, result) {
|
|
|
2792
3275
|
location: `plugin.yaml contracts[${index}]`,
|
|
2793
3276
|
suggestion: 'Component should be the exported member name (e.g., "moodTracker")'
|
|
2794
3277
|
});
|
|
3278
|
+
return;
|
|
2795
3279
|
}
|
|
2796
3280
|
if (contract.component.includes("/") || contract.component.includes(".")) {
|
|
2797
3281
|
result.warnings.push({
|
|
@@ -2801,6 +3285,143 @@ async function validateComponent(contract, index, context, result) {
|
|
|
2801
3285
|
suggestion: 'Component should be the exported member name (e.g., "moodTracker"), not a file path'
|
|
2802
3286
|
});
|
|
2803
3287
|
}
|
|
3288
|
+
await checkComponentContractConsistency(contract, context, result);
|
|
3289
|
+
}
|
|
3290
|
+
function hasExportModifier(node) {
|
|
3291
|
+
return node.modifiers?.some((m) => m.kind === u.SyntaxKind.ExportKeyword) ?? false;
|
|
3292
|
+
}
|
|
3293
|
+
function resolveModulePath(basePath) {
|
|
3294
|
+
for (const ext of ["", ".ts", ".js", "/index.ts", "/index.js"]) {
|
|
3295
|
+
const candidate = basePath + ext;
|
|
3296
|
+
if (fs.existsSync(candidate))
|
|
3297
|
+
return candidate;
|
|
3298
|
+
}
|
|
3299
|
+
return void 0;
|
|
3300
|
+
}
|
|
3301
|
+
function resolveComponentSourcePath(componentName, context) {
|
|
3302
|
+
const modulePath = context.manifest.module || "index";
|
|
3303
|
+
const entryBase = path.join(context.pluginPath, modulePath);
|
|
3304
|
+
const entryFile = resolveModulePath(entryBase);
|
|
3305
|
+
const libEntryFile = !entryFile ? resolveModulePath(path.join(context.pluginPath, "lib", modulePath)) : void 0;
|
|
3306
|
+
const sourceEntry = entryFile || libEntryFile;
|
|
3307
|
+
if (!sourceEntry)
|
|
3308
|
+
return void 0;
|
|
3309
|
+
if (!sourceEntry.endsWith(".ts"))
|
|
3310
|
+
return void 0;
|
|
3311
|
+
let sourceCode;
|
|
3312
|
+
try {
|
|
3313
|
+
sourceCode = fs.readFileSync(sourceEntry, "utf-8");
|
|
3314
|
+
} catch {
|
|
3315
|
+
return void 0;
|
|
3316
|
+
}
|
|
3317
|
+
const sourceFile = u.createSourceFile(
|
|
3318
|
+
sourceEntry,
|
|
3319
|
+
sourceCode,
|
|
3320
|
+
u.ScriptTarget.Latest,
|
|
3321
|
+
true,
|
|
3322
|
+
u.ScriptKind.TS
|
|
3323
|
+
);
|
|
3324
|
+
const starReexportModules = [];
|
|
3325
|
+
for (const statement of sourceFile.statements) {
|
|
3326
|
+
if (!u.isExportDeclaration(statement))
|
|
3327
|
+
continue;
|
|
3328
|
+
if (!statement.moduleSpecifier)
|
|
3329
|
+
continue;
|
|
3330
|
+
if (!u.isStringLiteral(statement.moduleSpecifier))
|
|
3331
|
+
continue;
|
|
3332
|
+
const moduleSpec = statement.moduleSpecifier.text;
|
|
3333
|
+
const exportClause = statement.exportClause;
|
|
3334
|
+
if (!exportClause) {
|
|
3335
|
+
starReexportModules.push(moduleSpec);
|
|
3336
|
+
continue;
|
|
3337
|
+
}
|
|
3338
|
+
if (u.isNamedExports(exportClause)) {
|
|
3339
|
+
for (const element of exportClause.elements) {
|
|
3340
|
+
const exportedName = element.name.text;
|
|
3341
|
+
if (exportedName === componentName) {
|
|
3342
|
+
const resolvedBase = path.resolve(path.dirname(sourceEntry), moduleSpec);
|
|
3343
|
+
return resolveModulePath(resolvedBase);
|
|
3344
|
+
}
|
|
3345
|
+
}
|
|
3346
|
+
}
|
|
3347
|
+
}
|
|
3348
|
+
for (const moduleSpec of starReexportModules) {
|
|
3349
|
+
if (!moduleSpec.startsWith("."))
|
|
3350
|
+
continue;
|
|
3351
|
+
const resolvedBase = path.resolve(path.dirname(sourceEntry), moduleSpec);
|
|
3352
|
+
const resolvedPath = resolveModulePath(resolvedBase);
|
|
3353
|
+
if (!resolvedPath || !resolvedPath.endsWith(".ts"))
|
|
3354
|
+
continue;
|
|
3355
|
+
try {
|
|
3356
|
+
const modSource = fs.readFileSync(resolvedPath, "utf-8");
|
|
3357
|
+
const modFile = u.createSourceFile(
|
|
3358
|
+
resolvedPath,
|
|
3359
|
+
modSource,
|
|
3360
|
+
u.ScriptTarget.Latest,
|
|
3361
|
+
true,
|
|
3362
|
+
u.ScriptKind.TS
|
|
3363
|
+
);
|
|
3364
|
+
for (const stmt of modFile.statements) {
|
|
3365
|
+
if (u.isVariableStatement(stmt) && hasExportModifier(stmt)) {
|
|
3366
|
+
for (const decl of stmt.declarationList.declarations) {
|
|
3367
|
+
if (u.isIdentifier(decl.name) && decl.name.text === componentName) {
|
|
3368
|
+
return resolvedPath;
|
|
3369
|
+
}
|
|
3370
|
+
}
|
|
3371
|
+
}
|
|
3372
|
+
if (u.isFunctionDeclaration(stmt) && hasExportModifier(stmt) && stmt.name?.text === componentName) {
|
|
3373
|
+
return resolvedPath;
|
|
3374
|
+
}
|
|
3375
|
+
}
|
|
3376
|
+
} catch {
|
|
3377
|
+
continue;
|
|
3378
|
+
}
|
|
3379
|
+
}
|
|
3380
|
+
return sourceEntry;
|
|
3381
|
+
}
|
|
3382
|
+
function resolveContractPath(contract, context) {
|
|
3383
|
+
return resolveContractFile(contract.contract, context);
|
|
3384
|
+
}
|
|
3385
|
+
async function checkComponentContractConsistency(contract, context, result) {
|
|
3386
|
+
const componentName = contract.component;
|
|
3387
|
+
if (!componentName)
|
|
3388
|
+
return;
|
|
3389
|
+
const sourcePath = resolveComponentSourcePath(componentName, context);
|
|
3390
|
+
if (!sourcePath)
|
|
3391
|
+
return;
|
|
3392
|
+
if (!sourcePath.endsWith(".ts"))
|
|
3393
|
+
return;
|
|
3394
|
+
const contractPath = resolveContractPath(contract, context);
|
|
3395
|
+
if (!contractPath)
|
|
3396
|
+
return;
|
|
3397
|
+
let contractContent;
|
|
3398
|
+
try {
|
|
3399
|
+
contractContent = await fs.promises.readFile(contractPath, "utf-8");
|
|
3400
|
+
} catch {
|
|
3401
|
+
return;
|
|
3402
|
+
}
|
|
3403
|
+
const parsed = parseContract(contractContent, path.basename(contractPath));
|
|
3404
|
+
if (parsed.validations.length > 0)
|
|
3405
|
+
return;
|
|
3406
|
+
let sourceCode;
|
|
3407
|
+
try {
|
|
3408
|
+
sourceCode = await fs.promises.readFile(sourcePath, "utf-8");
|
|
3409
|
+
} catch {
|
|
3410
|
+
return;
|
|
3411
|
+
}
|
|
3412
|
+
const contractName = contract.contract.replace(/\.jay-contract$/, "");
|
|
3413
|
+
const checkResult = checkComponentPropsAndParams(
|
|
3414
|
+
sourceCode,
|
|
3415
|
+
{
|
|
3416
|
+
props: parsed.val?.props,
|
|
3417
|
+
params: parsed.val?.params
|
|
3418
|
+
},
|
|
3419
|
+
contractName,
|
|
3420
|
+
contractPath,
|
|
3421
|
+
sourcePath
|
|
3422
|
+
);
|
|
3423
|
+
result.errors.push(...checkResult.errors);
|
|
3424
|
+
result.warnings.push(...checkResult.warnings);
|
|
2804
3425
|
}
|
|
2805
3426
|
async function validatePackageJson(context, result) {
|
|
2806
3427
|
const packageJsonPath = path.join(context.pluginPath, "package.json");
|
|
@@ -3327,6 +3948,101 @@ function checkRouteParams(parsedFile, filePath, pagesBase, jayHtmlContent) {
|
|
|
3327
3948
|
}
|
|
3328
3949
|
return warnings;
|
|
3329
3950
|
}
|
|
3951
|
+
function checkRouteToContractParams(parsedFile, filePath, pagesBase) {
|
|
3952
|
+
const routeParams = extractRouteParams(filePath, pagesBase);
|
|
3953
|
+
if (routeParams.size === 0)
|
|
3954
|
+
return [];
|
|
3955
|
+
const hasAnyContract = !!parsedFile.contract || parsedFile.headlessImports.some((imp) => !!imp.contract);
|
|
3956
|
+
if (!hasAnyContract)
|
|
3957
|
+
return [];
|
|
3958
|
+
const declaredParams = /* @__PURE__ */ new Set();
|
|
3959
|
+
if (parsedFile.contract?.params) {
|
|
3960
|
+
for (const p of parsedFile.contract.params) {
|
|
3961
|
+
declaredParams.add(p.name);
|
|
3962
|
+
}
|
|
3963
|
+
}
|
|
3964
|
+
for (const imp of parsedFile.headlessImports) {
|
|
3965
|
+
if (imp.contract?.params) {
|
|
3966
|
+
for (const p of imp.contract.params) {
|
|
3967
|
+
declaredParams.add(p.name);
|
|
3968
|
+
}
|
|
3969
|
+
}
|
|
3970
|
+
}
|
|
3971
|
+
const warnings = [];
|
|
3972
|
+
for (const routeParam of routeParams) {
|
|
3973
|
+
if (!declaredParams.has(routeParam)) {
|
|
3974
|
+
warnings.push(
|
|
3975
|
+
`Route provides param "${routeParam}" but no contract on this page declares it. Add params: { ${routeParam}: string } to the appropriate contract.`
|
|
3976
|
+
);
|
|
3977
|
+
}
|
|
3978
|
+
}
|
|
3979
|
+
return warnings;
|
|
3980
|
+
}
|
|
3981
|
+
const HEADLESS_SKIP_ATTRS = /* @__PURE__ */ new Set([
|
|
3982
|
+
"foreach",
|
|
3983
|
+
"if",
|
|
3984
|
+
"ref",
|
|
3985
|
+
"trackby",
|
|
3986
|
+
"slowforeach",
|
|
3987
|
+
"jayindex",
|
|
3988
|
+
"jaytrackby",
|
|
3989
|
+
"when-resolved",
|
|
3990
|
+
"when-loading",
|
|
3991
|
+
"when-rejected",
|
|
3992
|
+
"accessor",
|
|
3993
|
+
"props",
|
|
3994
|
+
"key",
|
|
3995
|
+
"jay-coordinate-base",
|
|
3996
|
+
"jay-scope"
|
|
3997
|
+
]);
|
|
3998
|
+
function checkHeadlessInstanceProps(jayHtml, file) {
|
|
3999
|
+
const imports = jayHtml.headlessImports;
|
|
4000
|
+
const warnings = [];
|
|
4001
|
+
function walkElement(element) {
|
|
4002
|
+
const tagName = element.rawTagName?.toLowerCase();
|
|
4003
|
+
if (tagName?.startsWith("jay:")) {
|
|
4004
|
+
const contractName = tagName.substring(4);
|
|
4005
|
+
const imp = imports.find((i) => i.contractName === contractName && i.contract);
|
|
4006
|
+
if (imp?.contract) {
|
|
4007
|
+
const contract = imp.contract;
|
|
4008
|
+
const attrs = element.attributes ?? {};
|
|
4009
|
+
const passedProps = /* @__PURE__ */ new Set();
|
|
4010
|
+
for (const attrName of Object.keys(attrs)) {
|
|
4011
|
+
if (!HEADLESS_SKIP_ATTRS.has(attrName.toLowerCase())) {
|
|
4012
|
+
passedProps.add(attrName);
|
|
4013
|
+
}
|
|
4014
|
+
}
|
|
4015
|
+
if (passedProps.size > 0) {
|
|
4016
|
+
const contractPropNames = new Set((contract.props || []).map((p) => p.name));
|
|
4017
|
+
for (const prop of passedProps) {
|
|
4018
|
+
if (!contractPropNames.has(prop)) {
|
|
4019
|
+
imp.key ? `${imp.key} (${contractName})` : contractName;
|
|
4020
|
+
warnings.push(
|
|
4021
|
+
`<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 }]`
|
|
4022
|
+
);
|
|
4023
|
+
}
|
|
4024
|
+
}
|
|
4025
|
+
}
|
|
4026
|
+
if (contract.props) {
|
|
4027
|
+
for (const contractProp of contract.props) {
|
|
4028
|
+
if (contractProp.required && !passedProps.has(contractProp.name)) {
|
|
4029
|
+
warnings.push(
|
|
4030
|
+
`<jay:${contractName}> is missing required prop "${contractProp.name}" declared in the "${contract.name}" contract.`
|
|
4031
|
+
);
|
|
4032
|
+
}
|
|
4033
|
+
}
|
|
4034
|
+
}
|
|
4035
|
+
}
|
|
4036
|
+
}
|
|
4037
|
+
for (const child of element.childNodes ?? []) {
|
|
4038
|
+
if (child.nodeType === 1) {
|
|
4039
|
+
walkElement(child);
|
|
4040
|
+
}
|
|
4041
|
+
}
|
|
4042
|
+
}
|
|
4043
|
+
walkElement(jayHtml.body);
|
|
4044
|
+
return warnings;
|
|
4045
|
+
}
|
|
3330
4046
|
async function validateJayFiles(options = {}) {
|
|
3331
4047
|
const config = loadConfig();
|
|
3332
4048
|
const resolvedConfig = getConfigWithDefaults(config);
|
|
@@ -3404,10 +4120,22 @@ async function validateJayFiles(options = {}) {
|
|
|
3404
4120
|
for (const msg of routeParamWarnings) {
|
|
3405
4121
|
warnings.push({ file: relativePath, message: msg });
|
|
3406
4122
|
}
|
|
4123
|
+
const routeToContractWarnings = checkRouteToContractParams(
|
|
4124
|
+
parsedFile.val,
|
|
4125
|
+
jayFile,
|
|
4126
|
+
scanDir
|
|
4127
|
+
);
|
|
4128
|
+
for (const msg of routeToContractWarnings) {
|
|
4129
|
+
warnings.push({ file: relativePath, message: msg });
|
|
4130
|
+
}
|
|
3407
4131
|
const refTypeErrors = checkRefElementTypes(parsedFile.val, relativePath);
|
|
3408
4132
|
for (const msg of refTypeErrors) {
|
|
3409
4133
|
errors.push({ file: relativePath, message: msg, stage: "generate" });
|
|
3410
4134
|
}
|
|
4135
|
+
const headlessPropWarnings = checkHeadlessInstanceProps(parsedFile.val, relativePath);
|
|
4136
|
+
for (const msg of headlessPropWarnings) {
|
|
4137
|
+
warnings.push({ file: relativePath, message: msg });
|
|
4138
|
+
}
|
|
3411
4139
|
const fileCoverage = analyzeTagCoverage(parsedFile.val, relativePath);
|
|
3412
4140
|
if (fileCoverage) {
|
|
3413
4141
|
coverage.push(fileCoverage);
|
|
@@ -3878,32 +4606,32 @@ program.command("validate-plugin [path]").description("Validate a Jay Stack plug
|
|
|
3878
4606
|
process.exit(1);
|
|
3879
4607
|
}
|
|
3880
4608
|
});
|
|
3881
|
-
|
|
4609
|
+
const ALL_ROLES = ["designer", "developer", "plugin"];
|
|
4610
|
+
async function ensureAgentKitDocs(projectRoot, _force, mode) {
|
|
3882
4611
|
const path2 = await import("node:path");
|
|
3883
4612
|
const fs2 = await import("node:fs/promises");
|
|
3884
4613
|
const { fileURLToPath } = await import("node:url");
|
|
3885
4614
|
const agentKitDir = path2.join(projectRoot, "agent-kit");
|
|
3886
|
-
await fs2.mkdir(agentKitDir, { recursive: true });
|
|
3887
4615
|
const thisDir = path2.dirname(fileURLToPath(import.meta.url));
|
|
3888
4616
|
const templateDir = path2.resolve(thisDir, "..", "agent-kit-template");
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
4617
|
+
const roles = mode && ALL_ROLES.includes(mode) ? [mode] : ALL_ROLES;
|
|
4618
|
+
for (const role of roles) {
|
|
4619
|
+
const roleTemplateDir = path2.join(templateDir, role);
|
|
4620
|
+
const roleOutputDir = path2.join(agentKitDir, role);
|
|
4621
|
+
let files;
|
|
4622
|
+
try {
|
|
4623
|
+
files = (await fs2.readdir(roleTemplateDir)).filter((f) => f.endsWith(".md"));
|
|
4624
|
+
} catch {
|
|
4625
|
+
continue;
|
|
4626
|
+
}
|
|
4627
|
+
await fs2.mkdir(roleOutputDir, { recursive: true });
|
|
4628
|
+
for (const filename of files) {
|
|
4629
|
+
await fs2.copyFile(
|
|
4630
|
+
path2.join(roleTemplateDir, filename),
|
|
4631
|
+
path2.join(roleOutputDir, filename)
|
|
4632
|
+
);
|
|
4633
|
+
getLogger().info(chalk.gray(` Created agent-kit/${role}/${filename}`));
|
|
3904
4634
|
}
|
|
3905
|
-
await fs2.copyFile(path2.join(templateDir, filename), destPath);
|
|
3906
|
-
getLogger().info(chalk.gray(` Created agent-kit/${filename}`));
|
|
3907
4635
|
}
|
|
3908
4636
|
}
|
|
3909
4637
|
async function generatePluginReferences(projectRoot, options, initErrors, viteServer) {
|
|
@@ -4025,7 +4753,10 @@ program.command("setup [plugin]").description(
|
|
|
4025
4753
|
});
|
|
4026
4754
|
program.command("agent-kit").description(
|
|
4027
4755
|
"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(
|
|
4756
|
+
).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(
|
|
4757
|
+
"-m, --mode <role>",
|
|
4758
|
+
"Generate guides for a specific role: designer, developer, or plugin (default: all)"
|
|
4759
|
+
).option("-v, --verbose", "Show detailed output").action(async (options) => {
|
|
4029
4760
|
const projectRoot = process.cwd();
|
|
4030
4761
|
const { initErrors, viteServer } = await runMaterialize(
|
|
4031
4762
|
projectRoot,
|
|
@@ -4036,7 +4767,7 @@ program.command("agent-kit").description(
|
|
|
4036
4767
|
);
|
|
4037
4768
|
try {
|
|
4038
4769
|
if (!options.list) {
|
|
4039
|
-
await ensureAgentKitDocs(projectRoot, options.force);
|
|
4770
|
+
await ensureAgentKitDocs(projectRoot, options.force, options.mode);
|
|
4040
4771
|
if (options.references !== false) {
|
|
4041
4772
|
await generatePluginReferences(projectRoot, options, initErrors, viteServer);
|
|
4042
4773
|
}
|