@jay-framework/jay-stack-cli 0.11.0 → 0.12.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/README.md +58 -0
- package/agent-kit-template/INSTRUCTIONS.md +116 -0
- package/agent-kit-template/cli-commands.md +229 -0
- package/agent-kit-template/contracts-and-plugins.md +229 -0
- package/agent-kit-template/jay-html-syntax.md +312 -0
- package/agent-kit-template/project-structure.md +242 -0
- package/agent-kit-template/routing.md +112 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +708 -160
- package/package.json +13 -8
package/dist/index.js
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import express from "express";
|
|
3
|
-
import { mkDevServer } from "@jay-framework/dev-server";
|
|
3
|
+
import { mkDevServer, createViteForCli } from "@jay-framework/dev-server";
|
|
4
4
|
import { createEditorServer } from "@jay-framework/editor-server";
|
|
5
5
|
import getPort from "get-port";
|
|
6
6
|
import path from "path";
|
|
7
7
|
import fs, { promises } from "fs";
|
|
8
8
|
import YAML from "yaml";
|
|
9
|
+
import { getLogger, createDevLogger, setDevLogger } from "@jay-framework/logger";
|
|
9
10
|
import { parse } from "node-html-parser";
|
|
10
11
|
import { createRequire } from "module";
|
|
11
12
|
import { parseJayFile, JAY_IMPORT_RESOLVER, generateElementDefinitionFile, parseContract, ContractTagType, generateElementFile } from "@jay-framework/compiler-jay-html";
|
|
12
13
|
import { JAY_CONTRACT_EXTENSION, JAY_EXTENSION, JayAtomicType, JayEnumType, loadPluginManifest, RuntimeMode, GenerateTarget } from "@jay-framework/compiler-shared";
|
|
14
|
+
import { listContracts, materializeContracts } from "@jay-framework/stack-server-runtime";
|
|
15
|
+
import { listContracts as listContracts2, materializeContracts as materializeContracts2 } from "@jay-framework/stack-server-runtime";
|
|
13
16
|
import { Command } from "commander";
|
|
14
17
|
import chalk from "chalk";
|
|
15
18
|
import { glob } from "glob";
|
|
@@ -44,7 +47,7 @@ function loadConfig() {
|
|
|
44
47
|
}
|
|
45
48
|
};
|
|
46
49
|
} catch (error) {
|
|
47
|
-
|
|
50
|
+
getLogger().warn(`Failed to parse .jay YAML config file, using defaults: ${error}`);
|
|
48
51
|
return DEFAULT_CONFIG;
|
|
49
52
|
}
|
|
50
53
|
}
|
|
@@ -82,7 +85,7 @@ function updateConfig(updates) {
|
|
|
82
85
|
const yamlContent = YAML.stringify(updatedConfig, { indent: 2 });
|
|
83
86
|
fs.writeFileSync(configPath, yamlContent);
|
|
84
87
|
} catch (error) {
|
|
85
|
-
|
|
88
|
+
getLogger().warn(`Failed to update .jay config file: ${error}`);
|
|
86
89
|
}
|
|
87
90
|
}
|
|
88
91
|
const PAGE_FILENAME = `page${JAY_EXTENSION}`;
|
|
@@ -158,7 +161,7 @@ async function scanPageDirectories(pagesBasePath, onPageFound) {
|
|
|
158
161
|
}
|
|
159
162
|
}
|
|
160
163
|
} catch (error) {
|
|
161
|
-
|
|
164
|
+
getLogger().warn(`Failed to scan directory ${dirPath}: ${error}`);
|
|
162
165
|
}
|
|
163
166
|
}
|
|
164
167
|
await scanDirectory(pagesBasePath);
|
|
@@ -168,9 +171,8 @@ async function parseContractFile(contractFilePath) {
|
|
|
168
171
|
const contractYaml = await fs.promises.readFile(contractFilePath, "utf-8");
|
|
169
172
|
const parsedContract = parseContract(contractYaml, contractFilePath);
|
|
170
173
|
if (parsedContract.validations.length > 0) {
|
|
171
|
-
|
|
172
|
-
`Contract validation errors in ${contractFilePath}
|
|
173
|
-
parsedContract.validations
|
|
174
|
+
getLogger().warn(
|
|
175
|
+
`Contract validation errors in ${contractFilePath}: ${parsedContract.validations.join(", ")}`
|
|
174
176
|
);
|
|
175
177
|
}
|
|
176
178
|
if (parsedContract.val) {
|
|
@@ -184,7 +186,7 @@ async function parseContractFile(contractFilePath) {
|
|
|
184
186
|
};
|
|
185
187
|
}
|
|
186
188
|
} catch (error) {
|
|
187
|
-
|
|
189
|
+
getLogger().warn(`Failed to parse contract file ${contractFilePath}: ${error}`);
|
|
188
190
|
}
|
|
189
191
|
return null;
|
|
190
192
|
}
|
|
@@ -210,11 +212,11 @@ async function resolveLinkedTags(tags, baseDir) {
|
|
|
210
212
|
}
|
|
211
213
|
resolvedTags.push(resolvedTag);
|
|
212
214
|
} else {
|
|
213
|
-
|
|
215
|
+
getLogger().warn(`Failed to load linked contract: ${tag.link} from ${baseDir}`);
|
|
214
216
|
resolvedTags.push(convertContractTagToProtocol(tag));
|
|
215
217
|
}
|
|
216
218
|
} catch (error) {
|
|
217
|
-
|
|
219
|
+
getLogger().warn(`Error resolving linked contract ${tag.link}: ${error}`);
|
|
218
220
|
resolvedTags.push(convertContractTagToProtocol(tag));
|
|
219
221
|
}
|
|
220
222
|
} else if (tag.tags) {
|
|
@@ -235,9 +237,8 @@ function resolveAppContractPath(appModule, contractFileName, projectRootPath) {
|
|
|
235
237
|
const resolvedPath = require2.resolve(modulePath);
|
|
236
238
|
return resolvedPath;
|
|
237
239
|
} catch (error) {
|
|
238
|
-
|
|
239
|
-
`Failed to resolve contract: ${appModule}/${contractFileName}
|
|
240
|
-
error instanceof Error ? error.message : error
|
|
240
|
+
getLogger().warn(
|
|
241
|
+
`Failed to resolve contract: ${appModule}/${contractFileName} - ${error instanceof Error ? error.message : error}`
|
|
241
242
|
);
|
|
242
243
|
return null;
|
|
243
244
|
}
|
|
@@ -316,12 +317,12 @@ async function scanInstalledAppContracts(configBasePath, projectRootPath) {
|
|
|
316
317
|
installedAppContracts[appName] = appContracts;
|
|
317
318
|
}
|
|
318
319
|
} catch (error) {
|
|
319
|
-
|
|
320
|
+
getLogger().warn(`Failed to parse app config ${appConfigPath}: ${error}`);
|
|
320
321
|
}
|
|
321
322
|
}
|
|
322
323
|
}
|
|
323
324
|
} catch (error) {
|
|
324
|
-
|
|
325
|
+
getLogger().warn(`Failed to scan installed apps directory ${installedAppsPath}: ${error}`);
|
|
325
326
|
}
|
|
326
327
|
return installedAppContracts;
|
|
327
328
|
}
|
|
@@ -420,7 +421,7 @@ async function scanProjectComponents(componentsBasePath) {
|
|
|
420
421
|
}
|
|
421
422
|
}
|
|
422
423
|
} catch (error) {
|
|
423
|
-
|
|
424
|
+
getLogger().warn(`Failed to scan components directory ${componentsBasePath}: ${error}`);
|
|
424
425
|
}
|
|
425
426
|
return components;
|
|
426
427
|
}
|
|
@@ -448,12 +449,12 @@ async function scanInstalledApps(configBasePath) {
|
|
|
448
449
|
});
|
|
449
450
|
}
|
|
450
451
|
} catch (error) {
|
|
451
|
-
|
|
452
|
+
getLogger().warn(`Failed to parse app config ${appConfigPath}: ${error}`);
|
|
452
453
|
}
|
|
453
454
|
}
|
|
454
455
|
}
|
|
455
456
|
} catch (error) {
|
|
456
|
-
|
|
457
|
+
getLogger().warn(`Failed to scan installed apps directory ${installedAppsPath}: ${error}`);
|
|
457
458
|
}
|
|
458
459
|
return installedApps;
|
|
459
460
|
}
|
|
@@ -466,7 +467,7 @@ async function getProjectName(configBasePath) {
|
|
|
466
467
|
return projectConfig.name || "Unnamed Project";
|
|
467
468
|
}
|
|
468
469
|
} catch (error) {
|
|
469
|
-
|
|
470
|
+
getLogger().warn(`Failed to read project config ${projectConfigPath}: ${error}`);
|
|
470
471
|
}
|
|
471
472
|
return "Unnamed Project";
|
|
472
473
|
}
|
|
@@ -493,12 +494,14 @@ async function scanPlugins(projectRootPath) {
|
|
|
493
494
|
}
|
|
494
495
|
});
|
|
495
496
|
} catch (error) {
|
|
496
|
-
|
|
497
|
+
getLogger().warn(`Failed to parse plugin.yaml for ${dir.name}: ${error}`);
|
|
497
498
|
}
|
|
498
499
|
}
|
|
499
500
|
}
|
|
500
501
|
} catch (error) {
|
|
501
|
-
|
|
502
|
+
getLogger().warn(
|
|
503
|
+
`Failed to scan local plugins directory ${localPluginsPath}: ${error}`
|
|
504
|
+
);
|
|
502
505
|
}
|
|
503
506
|
}
|
|
504
507
|
const nodeModulesPath = path.join(projectRootPath, "node_modules");
|
|
@@ -549,16 +552,15 @@ async function scanPlugins(projectRootPath) {
|
|
|
549
552
|
}
|
|
550
553
|
});
|
|
551
554
|
} catch (error) {
|
|
552
|
-
|
|
553
|
-
`Failed to parse plugin.yaml for package ${pkgPath}
|
|
554
|
-
error
|
|
555
|
+
getLogger().warn(
|
|
556
|
+
`Failed to parse plugin.yaml for package ${pkgPath}: ${error}`
|
|
555
557
|
);
|
|
556
558
|
}
|
|
557
559
|
}
|
|
558
560
|
}
|
|
559
561
|
}
|
|
560
562
|
} catch (error) {
|
|
561
|
-
|
|
563
|
+
getLogger().warn(`Failed to scan node_modules for plugins: ${error}`);
|
|
562
564
|
}
|
|
563
565
|
}
|
|
564
566
|
return plugins;
|
|
@@ -586,7 +588,7 @@ async function scanProjectInfo(pagesBasePath, componentsBasePath, configBasePath
|
|
|
586
588
|
contractSchema = parsedContract;
|
|
587
589
|
}
|
|
588
590
|
} catch (error) {
|
|
589
|
-
|
|
591
|
+
getLogger().warn(`Failed to parse contract file ${contractPath}: ${error}`);
|
|
590
592
|
}
|
|
591
593
|
}
|
|
592
594
|
if (hasPageHtml) {
|
|
@@ -598,7 +600,7 @@ async function scanProjectInfo(pagesBasePath, componentsBasePath, configBasePath
|
|
|
598
600
|
installedAppContracts
|
|
599
601
|
);
|
|
600
602
|
} catch (error) {
|
|
601
|
-
|
|
603
|
+
getLogger().warn(`Failed to read page file ${pageFilePath}: ${error}`);
|
|
602
604
|
}
|
|
603
605
|
} else if (hasPageConfig) {
|
|
604
606
|
try {
|
|
@@ -699,7 +701,7 @@ async function scanProjectInfo(pagesBasePath, componentsBasePath, configBasePath
|
|
|
699
701
|
}
|
|
700
702
|
}
|
|
701
703
|
} catch (error) {
|
|
702
|
-
|
|
704
|
+
getLogger().warn(`Failed to parse page config ${pageConfigPath}: ${error}`);
|
|
703
705
|
}
|
|
704
706
|
}
|
|
705
707
|
pages.push({
|
|
@@ -732,7 +734,7 @@ async function handlePagePublish(resolvedConfig, page) {
|
|
|
732
734
|
if (page.contract) {
|
|
733
735
|
contractPath = path.join(dirname, `page${JAY_CONTRACT_EXTENSION}`);
|
|
734
736
|
await fs.promises.writeFile(contractPath, page.contract, "utf-8");
|
|
735
|
-
|
|
737
|
+
getLogger().info(`📄 Published page contract: ${contractPath}`);
|
|
736
738
|
}
|
|
737
739
|
const createdJayHtml = {
|
|
738
740
|
jayHtml: page.jayHtml,
|
|
@@ -740,7 +742,7 @@ async function handlePagePublish(resolvedConfig, page) {
|
|
|
740
742
|
dirname,
|
|
741
743
|
fullPath
|
|
742
744
|
};
|
|
743
|
-
|
|
745
|
+
getLogger().info(`📝 Published page: ${fullPath}`);
|
|
744
746
|
return [
|
|
745
747
|
{
|
|
746
748
|
success: true,
|
|
@@ -750,7 +752,7 @@ async function handlePagePublish(resolvedConfig, page) {
|
|
|
750
752
|
createdJayHtml
|
|
751
753
|
];
|
|
752
754
|
} catch (error) {
|
|
753
|
-
|
|
755
|
+
getLogger().error(`Failed to publish page ${page.route}: ${error}`);
|
|
754
756
|
return [
|
|
755
757
|
{
|
|
756
758
|
success: false,
|
|
@@ -778,7 +780,7 @@ async function handleComponentPublish(resolvedConfig, component) {
|
|
|
778
780
|
dirname,
|
|
779
781
|
fullPath
|
|
780
782
|
};
|
|
781
|
-
|
|
783
|
+
getLogger().info(`🧩 Published component: ${fullPath}`);
|
|
782
784
|
return [
|
|
783
785
|
{
|
|
784
786
|
success: true,
|
|
@@ -788,7 +790,7 @@ async function handleComponentPublish(resolvedConfig, component) {
|
|
|
788
790
|
createdJayHtml
|
|
789
791
|
];
|
|
790
792
|
} catch (error) {
|
|
791
|
-
|
|
793
|
+
getLogger().error(`Failed to publish component ${component.name}: ${error}`);
|
|
792
794
|
return [
|
|
793
795
|
{
|
|
794
796
|
success: false,
|
|
@@ -832,7 +834,7 @@ function createEditorHandlers(config, tsConfigPath, projectRoot) {
|
|
|
832
834
|
);
|
|
833
835
|
const definitionFile = generateElementDefinitionFile(parsedJayHtml);
|
|
834
836
|
if (definitionFile.validations.length > 0)
|
|
835
|
-
|
|
837
|
+
getLogger().warn(
|
|
836
838
|
`failed to generate .d.ts for ${fullPath} with validation errors: ${definitionFile.validations.join("\n")}`
|
|
837
839
|
);
|
|
838
840
|
else
|
|
@@ -851,14 +853,14 @@ function createEditorHandlers(config, tsConfigPath, projectRoot) {
|
|
|
851
853
|
const filename = `${params.imageId}.png`;
|
|
852
854
|
const imagePath = path.join(imagesDir, filename);
|
|
853
855
|
await fs.promises.writeFile(imagePath, Buffer.from(params.imageData, "base64"));
|
|
854
|
-
|
|
856
|
+
getLogger().info(`🖼️ Saved image: ${imagePath}`);
|
|
855
857
|
return {
|
|
856
858
|
type: "saveImage",
|
|
857
859
|
success: true,
|
|
858
860
|
imageUrl: `/images/${filename}`
|
|
859
861
|
};
|
|
860
862
|
} catch (error) {
|
|
861
|
-
|
|
863
|
+
getLogger().error(`Failed to save image: ${error}`);
|
|
862
864
|
return {
|
|
863
865
|
type: "saveImage",
|
|
864
866
|
success: false,
|
|
@@ -882,7 +884,7 @@ function createEditorHandlers(config, tsConfigPath, projectRoot) {
|
|
|
882
884
|
imageUrl: exists ? `/images/${filename}` : void 0
|
|
883
885
|
};
|
|
884
886
|
} catch (error) {
|
|
885
|
-
|
|
887
|
+
getLogger().error(`Failed to check image: ${error}`);
|
|
886
888
|
return {
|
|
887
889
|
type: "hasImage",
|
|
888
890
|
success: false,
|
|
@@ -903,18 +905,18 @@ function createEditorHandlers(config, tsConfigPath, projectRoot) {
|
|
|
903
905
|
configBasePath,
|
|
904
906
|
projectRootPath
|
|
905
907
|
);
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
908
|
+
getLogger().info(`📋 Retrieved project info: ${info.name}`);
|
|
909
|
+
getLogger().info(` Pages: ${info.pages.length}`);
|
|
910
|
+
getLogger().info(` Components: ${info.components.length}`);
|
|
911
|
+
getLogger().info(` Installed Apps: ${info.installedApps.length}`);
|
|
912
|
+
getLogger().info(` App Contracts: ${Object.keys(info.installedAppContracts).length}`);
|
|
911
913
|
return {
|
|
912
914
|
type: "getProjectInfo",
|
|
913
915
|
success: true,
|
|
914
916
|
info
|
|
915
917
|
};
|
|
916
918
|
} catch (error) {
|
|
917
|
-
|
|
919
|
+
getLogger().error(`Failed to get project info: ${error}`);
|
|
918
920
|
return {
|
|
919
921
|
type: "getProjectInfo",
|
|
920
922
|
success: false,
|
|
@@ -969,16 +971,16 @@ async function generatePageDefinitionFiles(routes, tsConfigPath, projectRoot) {
|
|
|
969
971
|
);
|
|
970
972
|
const definitionFile = generateElementDefinitionFile(parsedJayHtml);
|
|
971
973
|
if (definitionFile.validations.length > 0) {
|
|
972
|
-
|
|
974
|
+
getLogger().warn(
|
|
973
975
|
`failed to generate .d.ts for ${jayHtmlPath} with validation errors: ${definitionFile.validations.join("\n")}`
|
|
974
976
|
);
|
|
975
977
|
} else {
|
|
976
978
|
const definitionFilePath2 = jayHtmlPath + ".d.ts";
|
|
977
979
|
await fs.promises.writeFile(definitionFilePath2, definitionFile.val, "utf-8");
|
|
978
|
-
|
|
980
|
+
getLogger().info(`📦 Generated definition file: ${definitionFilePath2}`);
|
|
979
981
|
}
|
|
980
982
|
} catch (error) {
|
|
981
|
-
|
|
983
|
+
getLogger().error(`Failed to generate definition file for ${jayHtmlPath}: ${error}`);
|
|
982
984
|
}
|
|
983
985
|
}
|
|
984
986
|
}
|
|
@@ -995,11 +997,12 @@ async function startDevServer(options = {}) {
|
|
|
995
997
|
};
|
|
996
998
|
const app = express();
|
|
997
999
|
const devServerPort = await getPort({ port: resolvedConfig.devServer.portRange });
|
|
1000
|
+
const log = getLogger();
|
|
998
1001
|
const editorServer = createEditorServer({
|
|
999
1002
|
portRange: resolvedConfig.editorServer.portRange,
|
|
1000
1003
|
editorId: resolvedConfig.editorServer.editorId,
|
|
1001
1004
|
onEditorId: (editorId2) => {
|
|
1002
|
-
|
|
1005
|
+
log.info(`Editor connected with ID: ${editorId2}`);
|
|
1003
1006
|
updateConfig({
|
|
1004
1007
|
editorServer: {
|
|
1005
1008
|
editorId: editorId2
|
|
@@ -1022,35 +1025,69 @@ async function startDevServer(options = {}) {
|
|
|
1022
1025
|
projectRootFolder: process.cwd(),
|
|
1023
1026
|
publicBaseUrlPath: "/",
|
|
1024
1027
|
dontCacheSlowly: false,
|
|
1025
|
-
jayRollupConfig: jayOptions
|
|
1028
|
+
jayRollupConfig: jayOptions,
|
|
1029
|
+
logLevel: options.logLevel
|
|
1026
1030
|
});
|
|
1027
1031
|
app.use(server);
|
|
1028
1032
|
const publicPath = path.resolve(resolvedConfig.devServer.publicFolder);
|
|
1029
1033
|
if (fs.existsSync(publicPath)) {
|
|
1030
1034
|
app.use(express.static(publicPath));
|
|
1031
1035
|
} else {
|
|
1032
|
-
|
|
1036
|
+
log.important(`⚠️ Public folder not found: ${resolvedConfig.devServer.publicFolder}`);
|
|
1033
1037
|
}
|
|
1034
1038
|
routes.forEach((route) => {
|
|
1035
1039
|
app.get(route.path, route.handler);
|
|
1036
1040
|
});
|
|
1037
1041
|
generatePageDefinitionFiles(routes, jayOptions.tsConfigFilePath, process.cwd());
|
|
1038
1042
|
const expressServer = app.listen(devServerPort, () => {
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
+
log.important(`🚀 Jay Stack dev server started successfully!`);
|
|
1044
|
+
log.important(`📱 Dev Server: http://localhost:${devServerPort}`);
|
|
1045
|
+
log.important(`🎨 Editor Server: http://localhost:${editorPort} (ID: ${editorId})`);
|
|
1046
|
+
log.important(`📁 Pages directory: ${resolvedConfig.devServer.pagesBase}`);
|
|
1043
1047
|
if (fs.existsSync(publicPath)) {
|
|
1044
|
-
|
|
1048
|
+
log.important(`📁 Public folder: ${resolvedConfig.devServer.publicFolder}`);
|
|
1049
|
+
}
|
|
1050
|
+
if (options.testMode) {
|
|
1051
|
+
log.important(`🧪 Test Mode: enabled`);
|
|
1052
|
+
log.important(` Health: http://localhost:${devServerPort}/_jay/health`);
|
|
1053
|
+
log.important(
|
|
1054
|
+
` Shutdown: curl -X POST http://localhost:${devServerPort}/_jay/shutdown`
|
|
1055
|
+
);
|
|
1056
|
+
if (options.timeout) {
|
|
1057
|
+
log.important(` Timeout: ${options.timeout}s`);
|
|
1058
|
+
}
|
|
1045
1059
|
}
|
|
1046
1060
|
});
|
|
1047
1061
|
const shutdown = async () => {
|
|
1048
|
-
|
|
1062
|
+
log.important("\n🛑 Shutting down servers...");
|
|
1049
1063
|
await editorServer.stop();
|
|
1050
1064
|
expressServer.closeAllConnections();
|
|
1051
1065
|
await new Promise((resolve) => expressServer.close(resolve));
|
|
1052
1066
|
process.exit(0);
|
|
1053
1067
|
};
|
|
1068
|
+
if (options.testMode) {
|
|
1069
|
+
app.get("/_jay/health", (_req, res) => {
|
|
1070
|
+
res.json({
|
|
1071
|
+
status: "ready",
|
|
1072
|
+
port: devServerPort,
|
|
1073
|
+
editorPort,
|
|
1074
|
+
uptime: process.uptime()
|
|
1075
|
+
});
|
|
1076
|
+
});
|
|
1077
|
+
app.post("/_jay/shutdown", async (_req, res) => {
|
|
1078
|
+
res.json({ status: "shutting_down" });
|
|
1079
|
+
setTimeout(async () => {
|
|
1080
|
+
await shutdown();
|
|
1081
|
+
}, 100);
|
|
1082
|
+
});
|
|
1083
|
+
if (options.timeout) {
|
|
1084
|
+
setTimeout(async () => {
|
|
1085
|
+
log.important(`
|
|
1086
|
+
⏰ Timeout (${options.timeout}s) reached, shutting down...`);
|
|
1087
|
+
await shutdown();
|
|
1088
|
+
}, options.timeout * 1e3);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1054
1091
|
process.on("SIGTERM", shutdown);
|
|
1055
1092
|
process.on("SIGINT", shutdown);
|
|
1056
1093
|
}
|
|
@@ -1208,29 +1245,33 @@ async function validateSchema(context, result) {
|
|
|
1208
1245
|
}
|
|
1209
1246
|
}
|
|
1210
1247
|
if (manifest.dynamic_contracts) {
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1248
|
+
const dynamicConfigs = Array.isArray(manifest.dynamic_contracts) ? manifest.dynamic_contracts : [manifest.dynamic_contracts];
|
|
1249
|
+
for (const config of dynamicConfigs) {
|
|
1250
|
+
const prefix = config.prefix || "(unknown)";
|
|
1251
|
+
if (!config.component) {
|
|
1252
|
+
result.errors.push({
|
|
1253
|
+
type: "schema",
|
|
1254
|
+
message: `dynamic_contracts[${prefix}] is missing "component" field`,
|
|
1255
|
+
location: "plugin.yaml",
|
|
1256
|
+
suggestion: "Specify path to shared component for dynamic contracts"
|
|
1257
|
+
});
|
|
1258
|
+
}
|
|
1259
|
+
if (!config.generator) {
|
|
1260
|
+
result.errors.push({
|
|
1261
|
+
type: "schema",
|
|
1262
|
+
message: `dynamic_contracts[${prefix}] is missing "generator" field`,
|
|
1263
|
+
location: "plugin.yaml",
|
|
1264
|
+
suggestion: "Specify path to generator file or export name"
|
|
1265
|
+
});
|
|
1266
|
+
}
|
|
1267
|
+
if (!config.prefix) {
|
|
1268
|
+
result.errors.push({
|
|
1269
|
+
type: "schema",
|
|
1270
|
+
message: 'dynamic_contracts entry is missing "prefix" field',
|
|
1271
|
+
location: "plugin.yaml",
|
|
1272
|
+
suggestion: 'Specify prefix for dynamic contract names (e.g., "cms")'
|
|
1273
|
+
});
|
|
1274
|
+
}
|
|
1234
1275
|
}
|
|
1235
1276
|
}
|
|
1236
1277
|
if (!manifest.contracts && !manifest.dynamic_contracts) {
|
|
@@ -1421,43 +1462,53 @@ async function validateDynamicContracts(context, result) {
|
|
|
1421
1462
|
const { dynamic_contracts } = context.manifest;
|
|
1422
1463
|
if (!dynamic_contracts)
|
|
1423
1464
|
return;
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
const
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
if (
|
|
1430
|
-
|
|
1431
|
-
|
|
1465
|
+
const dynamicConfigs = Array.isArray(dynamic_contracts) ? dynamic_contracts : [dynamic_contracts];
|
|
1466
|
+
for (const config of dynamicConfigs) {
|
|
1467
|
+
const prefix = config.prefix || "(unknown)";
|
|
1468
|
+
if (config.generator) {
|
|
1469
|
+
const isFilePath = config.generator.startsWith("./") || config.generator.startsWith("/") || config.generator.includes(".ts") || config.generator.includes(".js");
|
|
1470
|
+
if (isFilePath) {
|
|
1471
|
+
const generatorPath = path.join(context.pluginPath, config.generator);
|
|
1472
|
+
const possibleExtensions = ["", ".ts", ".js", "/index.ts", "/index.js"];
|
|
1473
|
+
let found = false;
|
|
1474
|
+
for (const ext of possibleExtensions) {
|
|
1475
|
+
if (fs.existsSync(generatorPath + ext)) {
|
|
1476
|
+
found = true;
|
|
1477
|
+
break;
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
if (!found && !context.isNpmPackage) {
|
|
1481
|
+
result.errors.push({
|
|
1482
|
+
type: "file-missing",
|
|
1483
|
+
message: `Generator file not found for ${prefix}: ${config.generator}`,
|
|
1484
|
+
location: "plugin.yaml dynamic_contracts",
|
|
1485
|
+
suggestion: `Create generator file at ${generatorPath}.ts`
|
|
1486
|
+
});
|
|
1487
|
+
}
|
|
1432
1488
|
}
|
|
1433
1489
|
}
|
|
1434
|
-
if (
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1490
|
+
if (config.component) {
|
|
1491
|
+
const isFilePath = config.component.startsWith("./") || config.component.startsWith("/") || config.component.includes(".ts") || config.component.includes(".js");
|
|
1492
|
+
if (isFilePath) {
|
|
1493
|
+
const componentPath = path.join(context.pluginPath, config.component);
|
|
1494
|
+
const possibleExtensions = ["", ".ts", ".js", "/index.ts", "/index.js"];
|
|
1495
|
+
let found = false;
|
|
1496
|
+
for (const ext of possibleExtensions) {
|
|
1497
|
+
if (fs.existsSync(componentPath + ext)) {
|
|
1498
|
+
found = true;
|
|
1499
|
+
break;
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
if (!found && !context.isNpmPackage) {
|
|
1503
|
+
result.errors.push({
|
|
1504
|
+
type: "file-missing",
|
|
1505
|
+
message: `Dynamic contracts component not found for ${prefix}: ${config.component}`,
|
|
1506
|
+
location: "plugin.yaml dynamic_contracts",
|
|
1507
|
+
suggestion: `Create component file at ${componentPath}.ts`
|
|
1508
|
+
});
|
|
1509
|
+
}
|
|
1451
1510
|
}
|
|
1452
1511
|
}
|
|
1453
|
-
if (!found && !context.isNpmPackage) {
|
|
1454
|
-
result.errors.push({
|
|
1455
|
-
type: "file-missing",
|
|
1456
|
-
message: `Dynamic contracts component not found: ${dynamic_contracts.component}`,
|
|
1457
|
-
location: "plugin.yaml dynamic_contracts",
|
|
1458
|
-
suggestion: `Create component file at ${componentPath}.ts`
|
|
1459
|
-
});
|
|
1460
|
-
}
|
|
1461
1512
|
}
|
|
1462
1513
|
}
|
|
1463
1514
|
async function findJayFiles(dir) {
|
|
@@ -1476,9 +1527,9 @@ async function validateJayFiles(options = {}) {
|
|
|
1476
1527
|
const jayHtmlFiles = await findJayFiles(scanDir);
|
|
1477
1528
|
const contractFiles = await findContractFiles(scanDir);
|
|
1478
1529
|
if (options.verbose) {
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1530
|
+
getLogger().info(chalk.gray(`Scanning directory: ${scanDir}`));
|
|
1531
|
+
getLogger().info(chalk.gray(`Found ${jayHtmlFiles.length} .jay-html files`));
|
|
1532
|
+
getLogger().info(chalk.gray(`Found ${contractFiles.length} .jay-contract files
|
|
1482
1533
|
`));
|
|
1483
1534
|
}
|
|
1484
1535
|
for (const contractFile of contractFiles) {
|
|
@@ -1495,10 +1546,10 @@ async function validateJayFiles(options = {}) {
|
|
|
1495
1546
|
});
|
|
1496
1547
|
}
|
|
1497
1548
|
if (options.verbose) {
|
|
1498
|
-
|
|
1549
|
+
getLogger().info(chalk.red(`❌ ${relativePath}`));
|
|
1499
1550
|
}
|
|
1500
1551
|
} else if (options.verbose) {
|
|
1501
|
-
|
|
1552
|
+
getLogger().info(chalk.green(`✓ ${relativePath}`));
|
|
1502
1553
|
}
|
|
1503
1554
|
} catch (error) {
|
|
1504
1555
|
errors.push({
|
|
@@ -1507,7 +1558,7 @@ async function validateJayFiles(options = {}) {
|
|
|
1507
1558
|
stage: "parse"
|
|
1508
1559
|
});
|
|
1509
1560
|
if (options.verbose) {
|
|
1510
|
-
|
|
1561
|
+
getLogger().info(chalk.red(`❌ ${relativePath}`));
|
|
1511
1562
|
}
|
|
1512
1563
|
}
|
|
1513
1564
|
}
|
|
@@ -1534,7 +1585,7 @@ async function validateJayFiles(options = {}) {
|
|
|
1534
1585
|
});
|
|
1535
1586
|
}
|
|
1536
1587
|
if (options.verbose) {
|
|
1537
|
-
|
|
1588
|
+
getLogger().info(chalk.red(`❌ ${relativePath}`));
|
|
1538
1589
|
}
|
|
1539
1590
|
continue;
|
|
1540
1591
|
}
|
|
@@ -1552,10 +1603,10 @@ async function validateJayFiles(options = {}) {
|
|
|
1552
1603
|
});
|
|
1553
1604
|
}
|
|
1554
1605
|
if (options.verbose) {
|
|
1555
|
-
|
|
1606
|
+
getLogger().info(chalk.red(`❌ ${relativePath}`));
|
|
1556
1607
|
}
|
|
1557
1608
|
} else if (options.verbose) {
|
|
1558
|
-
|
|
1609
|
+
getLogger().info(chalk.green(`✓ ${relativePath}`));
|
|
1559
1610
|
}
|
|
1560
1611
|
} catch (error) {
|
|
1561
1612
|
errors.push({
|
|
@@ -1564,7 +1615,7 @@ async function validateJayFiles(options = {}) {
|
|
|
1564
1615
|
stage: "parse"
|
|
1565
1616
|
});
|
|
1566
1617
|
if (options.verbose) {
|
|
1567
|
-
|
|
1618
|
+
getLogger().info(chalk.red(`❌ ${relativePath}`));
|
|
1568
1619
|
}
|
|
1569
1620
|
}
|
|
1570
1621
|
}
|
|
@@ -1577,40 +1628,361 @@ async function validateJayFiles(options = {}) {
|
|
|
1577
1628
|
};
|
|
1578
1629
|
}
|
|
1579
1630
|
function printJayValidationResult(result, options) {
|
|
1631
|
+
const logger = getLogger();
|
|
1580
1632
|
if (options.json) {
|
|
1581
|
-
|
|
1633
|
+
logger.important(JSON.stringify(result, null, 2));
|
|
1582
1634
|
return;
|
|
1583
1635
|
}
|
|
1584
|
-
|
|
1636
|
+
logger.important("");
|
|
1585
1637
|
if (result.valid) {
|
|
1586
|
-
|
|
1587
|
-
|
|
1638
|
+
logger.important(chalk.green("✅ Jay Stack validation successful!\n"));
|
|
1639
|
+
logger.important(
|
|
1588
1640
|
`Scanned ${result.jayHtmlFilesScanned} .jay-html files, ${result.contractFilesScanned} .jay-contract files`
|
|
1589
1641
|
);
|
|
1590
|
-
|
|
1642
|
+
logger.important("No errors found.");
|
|
1591
1643
|
} else {
|
|
1592
|
-
|
|
1593
|
-
|
|
1644
|
+
logger.important(chalk.red("❌ Jay Stack validation failed\n"));
|
|
1645
|
+
logger.important("Errors:");
|
|
1594
1646
|
for (const error of result.errors) {
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1647
|
+
logger.important(chalk.red(` ❌ ${error.file}`));
|
|
1648
|
+
logger.important(chalk.gray(` ${error.message}`));
|
|
1649
|
+
logger.important("");
|
|
1598
1650
|
}
|
|
1599
1651
|
const validFiles = result.jayHtmlFilesScanned + result.contractFilesScanned - result.errors.length;
|
|
1600
|
-
|
|
1652
|
+
logger.important(
|
|
1601
1653
|
chalk.red(`${result.errors.length} error(s) found, ${validFiles} file(s) valid.`)
|
|
1602
1654
|
);
|
|
1603
1655
|
}
|
|
1604
1656
|
}
|
|
1657
|
+
async function initializeServicesForCli(projectRoot, viteServer) {
|
|
1658
|
+
const path2 = await import("node:path");
|
|
1659
|
+
const fs2 = await import("node:fs");
|
|
1660
|
+
const {
|
|
1661
|
+
runInitCallbacks,
|
|
1662
|
+
getServiceRegistry,
|
|
1663
|
+
discoverPluginsWithInit,
|
|
1664
|
+
sortPluginsByDependencies,
|
|
1665
|
+
executePluginServerInits
|
|
1666
|
+
} = await import("@jay-framework/stack-server-runtime");
|
|
1667
|
+
try {
|
|
1668
|
+
const discoveredPlugins = await discoverPluginsWithInit({
|
|
1669
|
+
projectRoot,
|
|
1670
|
+
verbose: false
|
|
1671
|
+
});
|
|
1672
|
+
const pluginsWithInit = sortPluginsByDependencies(discoveredPlugins);
|
|
1673
|
+
try {
|
|
1674
|
+
await executePluginServerInits(pluginsWithInit, viteServer, false);
|
|
1675
|
+
} catch (error) {
|
|
1676
|
+
getLogger().warn(chalk.yellow(`⚠️ Plugin initialization skipped: ${error.message}`));
|
|
1677
|
+
}
|
|
1678
|
+
const initPathTs = path2.join(projectRoot, "src", "init.ts");
|
|
1679
|
+
const initPathJs = path2.join(projectRoot, "src", "init.js");
|
|
1680
|
+
let initModule;
|
|
1681
|
+
if (fs2.existsSync(initPathTs) && viteServer) {
|
|
1682
|
+
initModule = await viteServer.ssrLoadModule(initPathTs);
|
|
1683
|
+
} else if (fs2.existsSync(initPathJs)) {
|
|
1684
|
+
initModule = await import(initPathJs);
|
|
1685
|
+
}
|
|
1686
|
+
if (initModule?.init?._serverInit) {
|
|
1687
|
+
await initModule.init._serverInit();
|
|
1688
|
+
}
|
|
1689
|
+
await runInitCallbacks();
|
|
1690
|
+
} catch (error) {
|
|
1691
|
+
getLogger().warn(chalk.yellow(`⚠️ Service initialization failed: ${error.message}`));
|
|
1692
|
+
getLogger().warn(chalk.gray(" Static contracts will still be listed."));
|
|
1693
|
+
}
|
|
1694
|
+
return getServiceRegistry();
|
|
1695
|
+
}
|
|
1696
|
+
async function runAction(actionRef, options, projectRoot, initializeServices) {
|
|
1697
|
+
let viteServer;
|
|
1698
|
+
try {
|
|
1699
|
+
const slashIndex = actionRef.indexOf("/");
|
|
1700
|
+
if (slashIndex === -1) {
|
|
1701
|
+
getLogger().error(
|
|
1702
|
+
chalk.red("❌ Invalid action reference. Use format: <plugin>/<action>")
|
|
1703
|
+
);
|
|
1704
|
+
process.exit(1);
|
|
1705
|
+
}
|
|
1706
|
+
const actionExport = actionRef.substring(slashIndex + 1);
|
|
1707
|
+
const input = options.input ? JSON.parse(options.input) : {};
|
|
1708
|
+
if (options.verbose) {
|
|
1709
|
+
getLogger().info("Starting Vite for TypeScript support...");
|
|
1710
|
+
}
|
|
1711
|
+
viteServer = await createViteForCli({ projectRoot });
|
|
1712
|
+
await initializeServices(projectRoot, viteServer);
|
|
1713
|
+
const { discoverAndRegisterActions, discoverAllPluginActions, ActionRegistry } = await import("@jay-framework/stack-server-runtime");
|
|
1714
|
+
const registry = new ActionRegistry();
|
|
1715
|
+
await discoverAndRegisterActions({
|
|
1716
|
+
projectRoot,
|
|
1717
|
+
registry,
|
|
1718
|
+
verbose: options.verbose,
|
|
1719
|
+
viteServer
|
|
1720
|
+
});
|
|
1721
|
+
await discoverAllPluginActions({
|
|
1722
|
+
projectRoot,
|
|
1723
|
+
registry,
|
|
1724
|
+
verbose: options.verbose,
|
|
1725
|
+
viteServer
|
|
1726
|
+
});
|
|
1727
|
+
const allNames = registry.getNames();
|
|
1728
|
+
const matchedName = allNames.find((name) => name === actionExport) || allNames.find((name) => name.endsWith("." + actionExport)) || allNames.find((name) => name === actionRef);
|
|
1729
|
+
if (!matchedName) {
|
|
1730
|
+
getLogger().error(chalk.red(`❌ Action "${actionExport}" not found.`));
|
|
1731
|
+
if (allNames.length > 0) {
|
|
1732
|
+
getLogger().error(` Available actions: ${allNames.join(", ")}`);
|
|
1733
|
+
} else {
|
|
1734
|
+
getLogger().error(" No actions registered. Does the plugin have actions?");
|
|
1735
|
+
}
|
|
1736
|
+
process.exit(1);
|
|
1737
|
+
}
|
|
1738
|
+
if (options.verbose) {
|
|
1739
|
+
getLogger().info(`Executing action: ${matchedName}`);
|
|
1740
|
+
getLogger().info(`Input: ${JSON.stringify(input)}`);
|
|
1741
|
+
}
|
|
1742
|
+
const result = await registry.execute(matchedName, input);
|
|
1743
|
+
if (result.success) {
|
|
1744
|
+
if (options.yaml) {
|
|
1745
|
+
getLogger().important(YAML.stringify(result.data));
|
|
1746
|
+
} else {
|
|
1747
|
+
getLogger().important(JSON.stringify(result.data, null, 2));
|
|
1748
|
+
}
|
|
1749
|
+
} else {
|
|
1750
|
+
getLogger().error(
|
|
1751
|
+
chalk.red(`❌ Action failed: [${result.error.code}] ${result.error.message}`)
|
|
1752
|
+
);
|
|
1753
|
+
process.exit(1);
|
|
1754
|
+
}
|
|
1755
|
+
} catch (error) {
|
|
1756
|
+
getLogger().error(chalk.red("❌ Failed to run action:") + " " + error.message);
|
|
1757
|
+
if (options.verbose) {
|
|
1758
|
+
getLogger().error(error.stack);
|
|
1759
|
+
}
|
|
1760
|
+
process.exit(1);
|
|
1761
|
+
} finally {
|
|
1762
|
+
if (viteServer) {
|
|
1763
|
+
await viteServer.close();
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
async function runParams(contractRef, options, projectRoot, initializeServices) {
|
|
1768
|
+
let viteServer;
|
|
1769
|
+
try {
|
|
1770
|
+
const slashIndex = contractRef.indexOf("/");
|
|
1771
|
+
if (slashIndex === -1) {
|
|
1772
|
+
getLogger().error(
|
|
1773
|
+
chalk.red("❌ Invalid contract reference. Use format: <plugin>/<contract>")
|
|
1774
|
+
);
|
|
1775
|
+
process.exit(1);
|
|
1776
|
+
}
|
|
1777
|
+
const pluginName = contractRef.substring(0, slashIndex);
|
|
1778
|
+
const contractName = contractRef.substring(slashIndex + 1);
|
|
1779
|
+
if (options.verbose) {
|
|
1780
|
+
getLogger().info("Starting Vite for TypeScript support...");
|
|
1781
|
+
}
|
|
1782
|
+
viteServer = await createViteForCli({ projectRoot });
|
|
1783
|
+
await initializeServices(projectRoot, viteServer);
|
|
1784
|
+
const { resolvePluginComponent } = await import("@jay-framework/compiler-shared");
|
|
1785
|
+
const resolution = resolvePluginComponent(projectRoot, pluginName, contractName);
|
|
1786
|
+
if (resolution.validations.length > 0 || !resolution.val) {
|
|
1787
|
+
getLogger().error(
|
|
1788
|
+
chalk.red(`❌ Could not resolve plugin "${pluginName}" contract "${contractName}"`)
|
|
1789
|
+
);
|
|
1790
|
+
for (const msg of resolution.validations) {
|
|
1791
|
+
getLogger().error(` ${msg}`);
|
|
1792
|
+
}
|
|
1793
|
+
process.exit(1);
|
|
1794
|
+
}
|
|
1795
|
+
const componentPath = resolution.val.componentPath;
|
|
1796
|
+
const componentName = resolution.val.componentName;
|
|
1797
|
+
if (options.verbose) {
|
|
1798
|
+
getLogger().info(`Loading component "${componentName}" from ${componentPath}`);
|
|
1799
|
+
}
|
|
1800
|
+
const module = await viteServer.ssrLoadModule(componentPath);
|
|
1801
|
+
const component = module[componentName];
|
|
1802
|
+
if (!component || !component.loadParams) {
|
|
1803
|
+
getLogger().error(
|
|
1804
|
+
chalk.red(`❌ Component "${componentName}" does not have loadParams.`)
|
|
1805
|
+
);
|
|
1806
|
+
getLogger().error(
|
|
1807
|
+
" Only components with withLoadParams() expose discoverable URL params."
|
|
1808
|
+
);
|
|
1809
|
+
process.exit(1);
|
|
1810
|
+
}
|
|
1811
|
+
const { resolveServices } = await import("@jay-framework/stack-server-runtime");
|
|
1812
|
+
const resolvedServices = resolveServices(component.services || []);
|
|
1813
|
+
const allParams = [];
|
|
1814
|
+
const paramsGenerator = component.loadParams(resolvedServices);
|
|
1815
|
+
for await (const batch of paramsGenerator) {
|
|
1816
|
+
allParams.push(...batch);
|
|
1817
|
+
}
|
|
1818
|
+
if (options.yaml) {
|
|
1819
|
+
getLogger().important(YAML.stringify(allParams));
|
|
1820
|
+
} else {
|
|
1821
|
+
getLogger().important(JSON.stringify(allParams, null, 2));
|
|
1822
|
+
}
|
|
1823
|
+
if (!options.yaml) {
|
|
1824
|
+
getLogger().important(
|
|
1825
|
+
chalk.green(`
|
|
1826
|
+
✅ Found ${allParams.length} param combination(s)`)
|
|
1827
|
+
);
|
|
1828
|
+
}
|
|
1829
|
+
} catch (error) {
|
|
1830
|
+
getLogger().error(chalk.red("❌ Failed to discover params:") + " " + error.message);
|
|
1831
|
+
if (options.verbose) {
|
|
1832
|
+
getLogger().error(error.stack);
|
|
1833
|
+
}
|
|
1834
|
+
process.exit(1);
|
|
1835
|
+
} finally {
|
|
1836
|
+
if (viteServer) {
|
|
1837
|
+
await viteServer.close();
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
async function runSetup(pluginFilter, options, projectRoot, initializeServices) {
|
|
1842
|
+
let viteServer;
|
|
1843
|
+
try {
|
|
1844
|
+
const logger = getLogger();
|
|
1845
|
+
const path2 = await import("node:path");
|
|
1846
|
+
const jayConfig = loadConfig();
|
|
1847
|
+
const configDir = path2.resolve(projectRoot, jayConfig.devServer?.configBase || "./config");
|
|
1848
|
+
logger.important(chalk.bold("\n🔧 Setting up plugins...\n"));
|
|
1849
|
+
if (options.verbose) {
|
|
1850
|
+
logger.info("Starting Vite for TypeScript support...");
|
|
1851
|
+
}
|
|
1852
|
+
viteServer = await createViteForCli({ projectRoot });
|
|
1853
|
+
const { discoverPluginsWithSetup, executePluginSetup } = await import("@jay-framework/stack-server-runtime");
|
|
1854
|
+
const pluginsWithSetup = await discoverPluginsWithSetup({
|
|
1855
|
+
projectRoot,
|
|
1856
|
+
verbose: options.verbose,
|
|
1857
|
+
pluginFilter
|
|
1858
|
+
});
|
|
1859
|
+
if (pluginsWithSetup.length === 0) {
|
|
1860
|
+
if (pluginFilter) {
|
|
1861
|
+
logger.important(
|
|
1862
|
+
chalk.yellow(
|
|
1863
|
+
`⚠️ Plugin "${pluginFilter}" not found or has no setup handler.`
|
|
1864
|
+
)
|
|
1865
|
+
);
|
|
1866
|
+
} else {
|
|
1867
|
+
logger.important(chalk.gray("No plugins with setup handlers found."));
|
|
1868
|
+
}
|
|
1869
|
+
return;
|
|
1870
|
+
}
|
|
1871
|
+
if (options.verbose) {
|
|
1872
|
+
logger.info(
|
|
1873
|
+
`Found ${pluginsWithSetup.length} plugin(s) with setup: ${pluginsWithSetup.map((p) => p.name).join(", ")}`
|
|
1874
|
+
);
|
|
1875
|
+
}
|
|
1876
|
+
let initError;
|
|
1877
|
+
try {
|
|
1878
|
+
await initializeServices(projectRoot, viteServer);
|
|
1879
|
+
} catch (error) {
|
|
1880
|
+
initError = error;
|
|
1881
|
+
if (options.verbose) {
|
|
1882
|
+
logger.info(chalk.yellow(`⚠️ Service init error: ${error.message}`));
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
let configured = 0;
|
|
1886
|
+
let needsConfig = 0;
|
|
1887
|
+
let errors = 0;
|
|
1888
|
+
for (const plugin of pluginsWithSetup) {
|
|
1889
|
+
logger.important(chalk.bold(`📦 ${plugin.name}`));
|
|
1890
|
+
if (plugin.setupDescription && options.verbose) {
|
|
1891
|
+
logger.important(chalk.gray(` ${plugin.setupDescription}`));
|
|
1892
|
+
}
|
|
1893
|
+
try {
|
|
1894
|
+
const result = await executePluginSetup(plugin, {
|
|
1895
|
+
projectRoot,
|
|
1896
|
+
configDir,
|
|
1897
|
+
force: options.force ?? false,
|
|
1898
|
+
initError,
|
|
1899
|
+
viteServer,
|
|
1900
|
+
verbose: options.verbose
|
|
1901
|
+
});
|
|
1902
|
+
switch (result.status) {
|
|
1903
|
+
case "configured":
|
|
1904
|
+
configured++;
|
|
1905
|
+
logger.important(chalk.green(" ✅ Services verified"));
|
|
1906
|
+
if (result.configCreated?.length) {
|
|
1907
|
+
for (const cfg of result.configCreated) {
|
|
1908
|
+
logger.important(chalk.green(` ✅ Created ${cfg}`));
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
if (result.message) {
|
|
1912
|
+
logger.important(chalk.gray(` ${result.message}`));
|
|
1913
|
+
}
|
|
1914
|
+
break;
|
|
1915
|
+
case "needs-config":
|
|
1916
|
+
needsConfig++;
|
|
1917
|
+
if (result.configCreated?.length) {
|
|
1918
|
+
for (const cfg of result.configCreated) {
|
|
1919
|
+
logger.important(chalk.yellow(` ⚠️ Config template created: ${cfg}`));
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1922
|
+
if (result.message) {
|
|
1923
|
+
logger.important(chalk.yellow(` → ${result.message}`));
|
|
1924
|
+
} else {
|
|
1925
|
+
logger.important(
|
|
1926
|
+
chalk.yellow(
|
|
1927
|
+
` → Fill in credentials and re-run: jay-stack setup ${plugin.name}`
|
|
1928
|
+
)
|
|
1929
|
+
);
|
|
1930
|
+
}
|
|
1931
|
+
break;
|
|
1932
|
+
case "error":
|
|
1933
|
+
errors++;
|
|
1934
|
+
logger.important(
|
|
1935
|
+
chalk.red(` ❌ ${result.message || "Setup failed"}`)
|
|
1936
|
+
);
|
|
1937
|
+
break;
|
|
1938
|
+
}
|
|
1939
|
+
} catch (error) {
|
|
1940
|
+
errors++;
|
|
1941
|
+
logger.important(chalk.red(` ❌ Setup failed: ${error.message}`));
|
|
1942
|
+
if (options.verbose) {
|
|
1943
|
+
logger.error(error.stack);
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
logger.important("");
|
|
1947
|
+
}
|
|
1948
|
+
const parts = [];
|
|
1949
|
+
if (configured > 0)
|
|
1950
|
+
parts.push(`${configured} configured`);
|
|
1951
|
+
if (needsConfig > 0)
|
|
1952
|
+
parts.push(`${needsConfig} needs config`);
|
|
1953
|
+
if (errors > 0)
|
|
1954
|
+
parts.push(`${errors} error(s)`);
|
|
1955
|
+
logger.important(`Setup complete: ${parts.join(", ")}`);
|
|
1956
|
+
if (errors > 0) {
|
|
1957
|
+
process.exit(1);
|
|
1958
|
+
}
|
|
1959
|
+
} catch (error) {
|
|
1960
|
+
getLogger().error(chalk.red("❌ Setup failed:") + " " + error.message);
|
|
1961
|
+
if (options.verbose) {
|
|
1962
|
+
getLogger().error(error.stack);
|
|
1963
|
+
}
|
|
1964
|
+
process.exit(1);
|
|
1965
|
+
} finally {
|
|
1966
|
+
if (viteServer) {
|
|
1967
|
+
await viteServer.close();
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
}
|
|
1605
1971
|
const program = new Command();
|
|
1606
1972
|
program.name("jay-stack").description("Jay Stack CLI - Development server and plugin validation").version("0.9.0");
|
|
1607
|
-
program.command("dev [path]").description("Start the Jay Stack development server").action(async (path2, options) => {
|
|
1973
|
+
program.command("dev [path]").description("Start the Jay Stack development server").option("-v, --verbose", "Enable verbose logging output").option("-q, --quiet", "Suppress all non-error output").option("--test-mode", "Enable test endpoints (/_jay/health, /_jay/shutdown)").option("--timeout <seconds>", "Auto-shutdown after N seconds (implies --test-mode)", parseInt).action(async (path2, options) => {
|
|
1608
1974
|
try {
|
|
1975
|
+
const logLevel = options.quiet ? "silent" : options.verbose ? "verbose" : "info";
|
|
1976
|
+
setDevLogger(createDevLogger(logLevel));
|
|
1977
|
+
const testMode = options.testMode || options.timeout !== void 0;
|
|
1609
1978
|
await startDevServer({
|
|
1610
|
-
projectPath: path2 || process.cwd()
|
|
1979
|
+
projectPath: path2 || process.cwd(),
|
|
1980
|
+
testMode,
|
|
1981
|
+
timeout: options.timeout,
|
|
1982
|
+
logLevel
|
|
1611
1983
|
});
|
|
1612
1984
|
} catch (error) {
|
|
1613
|
-
|
|
1985
|
+
getLogger().error(chalk.red("Error starting dev server:") + " " + error.message);
|
|
1614
1986
|
process.exit(1);
|
|
1615
1987
|
}
|
|
1616
1988
|
});
|
|
@@ -1627,9 +1999,11 @@ program.command("validate [path]").description("Validate all .jay-html and .jay-
|
|
|
1627
1999
|
}
|
|
1628
2000
|
} catch (error) {
|
|
1629
2001
|
if (options.json) {
|
|
1630
|
-
|
|
2002
|
+
getLogger().important(
|
|
2003
|
+
JSON.stringify({ valid: false, error: error.message }, null, 2)
|
|
2004
|
+
);
|
|
1631
2005
|
} else {
|
|
1632
|
-
|
|
2006
|
+
getLogger().error(chalk.red("Validation error:") + " " + error.message);
|
|
1633
2007
|
}
|
|
1634
2008
|
process.exit(1);
|
|
1635
2009
|
}
|
|
@@ -1648,64 +2022,238 @@ program.command("validate-plugin [path]").description("Validate a Jay Stack plug
|
|
|
1648
2022
|
process.exit(1);
|
|
1649
2023
|
}
|
|
1650
2024
|
} catch (error) {
|
|
1651
|
-
|
|
2025
|
+
getLogger().error(chalk.red("Validation error:") + " " + error.message);
|
|
1652
2026
|
process.exit(1);
|
|
1653
2027
|
}
|
|
1654
2028
|
});
|
|
2029
|
+
async function ensureAgentKitDocs(projectRoot, force) {
|
|
2030
|
+
const path2 = await import("node:path");
|
|
2031
|
+
const fs2 = await import("node:fs/promises");
|
|
2032
|
+
const { fileURLToPath } = await import("node:url");
|
|
2033
|
+
const agentKitDir = path2.join(projectRoot, "agent-kit");
|
|
2034
|
+
await fs2.mkdir(agentKitDir, { recursive: true });
|
|
2035
|
+
const thisDir = path2.dirname(fileURLToPath(import.meta.url));
|
|
2036
|
+
const templateDir = path2.resolve(thisDir, "..", "agent-kit-template");
|
|
2037
|
+
let files;
|
|
2038
|
+
try {
|
|
2039
|
+
files = (await fs2.readdir(templateDir)).filter((f) => f.endsWith(".md"));
|
|
2040
|
+
} catch {
|
|
2041
|
+
getLogger().warn(
|
|
2042
|
+
chalk.yellow(" Agent-kit template folder not found: " + templateDir)
|
|
2043
|
+
);
|
|
2044
|
+
return;
|
|
2045
|
+
}
|
|
2046
|
+
for (const filename of files) {
|
|
2047
|
+
const destPath = path2.join(agentKitDir, filename);
|
|
2048
|
+
if (!force) {
|
|
2049
|
+
try {
|
|
2050
|
+
await fs2.access(destPath);
|
|
2051
|
+
continue;
|
|
2052
|
+
} catch {
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
await fs2.copyFile(path2.join(templateDir, filename), destPath);
|
|
2056
|
+
getLogger().info(chalk.gray(` Created agent-kit/${filename}`));
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
async function generatePluginReferences(projectRoot, options) {
|
|
2060
|
+
const { discoverPluginsWithReferences, executePluginReferences } = await import("@jay-framework/stack-server-runtime");
|
|
2061
|
+
const plugins = await discoverPluginsWithReferences({
|
|
2062
|
+
projectRoot,
|
|
2063
|
+
verbose: options.verbose,
|
|
2064
|
+
pluginFilter: options.plugin
|
|
2065
|
+
});
|
|
2066
|
+
if (plugins.length === 0)
|
|
2067
|
+
return;
|
|
2068
|
+
const logger = getLogger();
|
|
2069
|
+
logger.important("");
|
|
2070
|
+
logger.important(chalk.bold("📚 Generating plugin references..."));
|
|
2071
|
+
for (const plugin of plugins) {
|
|
2072
|
+
try {
|
|
2073
|
+
const result = await executePluginReferences(plugin, {
|
|
2074
|
+
projectRoot,
|
|
2075
|
+
force: options.force ?? false,
|
|
2076
|
+
verbose: options.verbose
|
|
2077
|
+
});
|
|
2078
|
+
if (result.referencesCreated.length > 0) {
|
|
2079
|
+
logger.important(chalk.green(` ✅ ${plugin.name}:`));
|
|
2080
|
+
for (const ref of result.referencesCreated) {
|
|
2081
|
+
logger.important(chalk.gray(` ${ref}`));
|
|
2082
|
+
}
|
|
2083
|
+
if (result.message) {
|
|
2084
|
+
logger.important(chalk.gray(` ${result.message}`));
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
} catch (error) {
|
|
2088
|
+
logger.warn(
|
|
2089
|
+
chalk.yellow(` ⚠️ ${plugin.name}: references skipped — ${error.message}`)
|
|
2090
|
+
);
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
async function runMaterialize(projectRoot, options, defaultOutputRelative) {
|
|
2095
|
+
const path2 = await import("node:path");
|
|
2096
|
+
const outputDir = options.output ?? path2.join(projectRoot, defaultOutputRelative);
|
|
2097
|
+
let viteServer;
|
|
2098
|
+
try {
|
|
2099
|
+
if (options.list) {
|
|
2100
|
+
const index = await listContracts({
|
|
2101
|
+
projectRoot,
|
|
2102
|
+
dynamicOnly: options.dynamicOnly,
|
|
2103
|
+
pluginFilter: options.plugin
|
|
2104
|
+
});
|
|
2105
|
+
if (options.yaml) {
|
|
2106
|
+
getLogger().important(YAML.stringify(index));
|
|
2107
|
+
} else {
|
|
2108
|
+
printContractList(index);
|
|
2109
|
+
}
|
|
2110
|
+
return;
|
|
2111
|
+
}
|
|
2112
|
+
if (options.verbose) {
|
|
2113
|
+
getLogger().info("Starting Vite for TypeScript support...");
|
|
2114
|
+
}
|
|
2115
|
+
viteServer = await createViteForCli({ projectRoot });
|
|
2116
|
+
const services = await initializeServicesForCli(projectRoot, viteServer);
|
|
2117
|
+
const result = await materializeContracts(
|
|
2118
|
+
{
|
|
2119
|
+
projectRoot,
|
|
2120
|
+
outputDir,
|
|
2121
|
+
force: options.force,
|
|
2122
|
+
dynamicOnly: options.dynamicOnly,
|
|
2123
|
+
pluginFilter: options.plugin,
|
|
2124
|
+
verbose: options.verbose,
|
|
2125
|
+
viteServer
|
|
2126
|
+
},
|
|
2127
|
+
services
|
|
2128
|
+
);
|
|
2129
|
+
if (options.yaml) {
|
|
2130
|
+
getLogger().important(YAML.stringify(result.index));
|
|
2131
|
+
} else {
|
|
2132
|
+
getLogger().important(
|
|
2133
|
+
chalk.green(`
|
|
2134
|
+
✅ Materialized ${result.index.contracts.length} contracts`)
|
|
2135
|
+
);
|
|
2136
|
+
getLogger().important(` Static: ${result.staticCount}`);
|
|
2137
|
+
getLogger().important(` Dynamic: ${result.dynamicCount}`);
|
|
2138
|
+
getLogger().important(` Output: ${result.outputDir}`);
|
|
2139
|
+
}
|
|
2140
|
+
} catch (error) {
|
|
2141
|
+
getLogger().error(chalk.red("❌ Failed to materialize contracts:") + " " + error.message);
|
|
2142
|
+
if (options.verbose) {
|
|
2143
|
+
getLogger().error(error.stack);
|
|
2144
|
+
}
|
|
2145
|
+
process.exit(1);
|
|
2146
|
+
} finally {
|
|
2147
|
+
if (viteServer) {
|
|
2148
|
+
await viteServer.close();
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
program.command("setup [plugin]").description(
|
|
2153
|
+
"Run plugin setup: create config templates, validate credentials, generate reference data"
|
|
2154
|
+
).option("--force", "Force re-run (overwrite config templates and regenerate references)").option("-v, --verbose", "Show detailed output").action(async (plugin, options) => {
|
|
2155
|
+
await runSetup(plugin, options, process.cwd(), initializeServicesForCli);
|
|
2156
|
+
});
|
|
2157
|
+
program.command("agent-kit").description(
|
|
2158
|
+
"Prepare the agent kit: materialize contracts, generate references, and write docs to agent-kit/"
|
|
2159
|
+
).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("-v, --verbose", "Show detailed output").action(async (options) => {
|
|
2160
|
+
const projectRoot = process.cwd();
|
|
2161
|
+
await runMaterialize(projectRoot, options, "agent-kit/materialized-contracts");
|
|
2162
|
+
if (!options.list) {
|
|
2163
|
+
await ensureAgentKitDocs(projectRoot, options.force);
|
|
2164
|
+
if (options.references !== false) {
|
|
2165
|
+
await generatePluginReferences(projectRoot, options);
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
});
|
|
2169
|
+
program.command("action <plugin/action>").description(
|
|
2170
|
+
`Run a plugin action (e.g., jay-stack action wix-stores/searchProducts --input '{"query":""}')`
|
|
2171
|
+
).option("--input <json>", "JSON input for the action (default: {})").option("--yaml", "Output result as YAML instead of JSON").option("-v, --verbose", "Show detailed output").action(async (actionRef, options) => {
|
|
2172
|
+
await runAction(actionRef, options, process.cwd(), initializeServicesForCli);
|
|
2173
|
+
});
|
|
2174
|
+
program.command("params <plugin/contract>").description(
|
|
2175
|
+
"Discover load param values for a contract (e.g., jay-stack params wix-stores/product-page)"
|
|
2176
|
+
).option("--yaml", "Output result as YAML instead of JSON").option("-v, --verbose", "Show detailed output").action(async (contractRef, options) => {
|
|
2177
|
+
await runParams(contractRef, options, process.cwd(), initializeServicesForCli);
|
|
2178
|
+
});
|
|
1655
2179
|
program.parse(process.argv);
|
|
1656
2180
|
if (!process.argv.slice(2).length) {
|
|
1657
2181
|
program.outputHelp();
|
|
1658
2182
|
}
|
|
1659
2183
|
function printValidationResult(result, verbose) {
|
|
2184
|
+
const logger = getLogger();
|
|
1660
2185
|
if (result.valid && result.warnings.length === 0) {
|
|
1661
|
-
|
|
2186
|
+
logger.important(chalk.green("✅ Plugin validation successful!\n"));
|
|
1662
2187
|
if (verbose) {
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
2188
|
+
logger.important("Plugin: " + result.pluginName);
|
|
2189
|
+
logger.important(" ✅ plugin.yaml valid");
|
|
2190
|
+
logger.important(` ✅ ${result.contractsChecked} contracts validated`);
|
|
1666
2191
|
if (result.typesGenerated) {
|
|
1667
|
-
|
|
2192
|
+
logger.important(` ✅ ${result.typesGenerated} type definitions generated`);
|
|
1668
2193
|
}
|
|
1669
|
-
|
|
2194
|
+
logger.important(` ✅ ${result.componentsChecked} components validated`);
|
|
1670
2195
|
if (result.packageJsonChecked) {
|
|
1671
|
-
|
|
2196
|
+
logger.important(" ✅ package.json valid");
|
|
1672
2197
|
}
|
|
1673
|
-
|
|
2198
|
+
logger.important("\nNo errors found.");
|
|
1674
2199
|
}
|
|
1675
2200
|
} else if (result.valid && result.warnings.length > 0) {
|
|
1676
|
-
|
|
1677
|
-
|
|
2201
|
+
logger.important(chalk.yellow("⚠️ Plugin validation passed with warnings\n"));
|
|
2202
|
+
logger.important("Warnings:");
|
|
1678
2203
|
result.warnings.forEach((warning) => {
|
|
1679
|
-
|
|
2204
|
+
logger.important(chalk.yellow(` ⚠️ ${warning.message}`));
|
|
1680
2205
|
if (warning.location) {
|
|
1681
|
-
|
|
2206
|
+
logger.important(chalk.gray(` Location: ${warning.location}`));
|
|
1682
2207
|
}
|
|
1683
2208
|
if (warning.suggestion) {
|
|
1684
|
-
|
|
2209
|
+
logger.important(chalk.gray(` → ${warning.suggestion}`));
|
|
1685
2210
|
}
|
|
1686
|
-
|
|
2211
|
+
logger.important("");
|
|
1687
2212
|
});
|
|
1688
|
-
|
|
2213
|
+
logger.important(chalk.gray("Use --strict to treat warnings as errors."));
|
|
1689
2214
|
} else {
|
|
1690
|
-
|
|
1691
|
-
|
|
2215
|
+
logger.important(chalk.red("❌ Plugin validation failed\n"));
|
|
2216
|
+
logger.important("Errors:");
|
|
1692
2217
|
result.errors.forEach((error) => {
|
|
1693
|
-
|
|
2218
|
+
logger.important(chalk.red(` ❌ ${error.message}`));
|
|
1694
2219
|
if (error.location) {
|
|
1695
|
-
|
|
2220
|
+
logger.important(chalk.gray(` Location: ${error.location}`));
|
|
1696
2221
|
}
|
|
1697
2222
|
if (error.suggestion) {
|
|
1698
|
-
|
|
2223
|
+
logger.important(chalk.gray(` → ${error.suggestion}`));
|
|
1699
2224
|
}
|
|
1700
|
-
|
|
2225
|
+
logger.important("");
|
|
1701
2226
|
});
|
|
1702
|
-
|
|
2227
|
+
logger.important(chalk.red(`${result.errors.length} errors found.`));
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
function printContractList(index) {
|
|
2231
|
+
const logger = getLogger();
|
|
2232
|
+
logger.important("\nAvailable Contracts:\n");
|
|
2233
|
+
const byPlugin = /* @__PURE__ */ new Map();
|
|
2234
|
+
for (const contract of index.contracts) {
|
|
2235
|
+
const existing = byPlugin.get(contract.plugin) || [];
|
|
2236
|
+
existing.push(contract);
|
|
2237
|
+
byPlugin.set(contract.plugin, existing);
|
|
2238
|
+
}
|
|
2239
|
+
for (const [plugin, contracts] of byPlugin) {
|
|
2240
|
+
logger.important(chalk.bold(`📦 ${plugin}`));
|
|
2241
|
+
for (const contract of contracts) {
|
|
2242
|
+
const typeIcon = contract.type === "static" ? "📄" : "⚡";
|
|
2243
|
+
logger.important(` ${typeIcon} ${contract.name}`);
|
|
2244
|
+
}
|
|
2245
|
+
logger.important("");
|
|
2246
|
+
}
|
|
2247
|
+
if (index.contracts.length === 0) {
|
|
2248
|
+
logger.important(chalk.gray("No contracts found."));
|
|
1703
2249
|
}
|
|
1704
2250
|
}
|
|
1705
2251
|
export {
|
|
1706
2252
|
createEditorHandlers,
|
|
1707
2253
|
getConfigWithDefaults,
|
|
2254
|
+
listContracts2 as listContracts,
|
|
1708
2255
|
loadConfig,
|
|
2256
|
+
materializeContracts2 as materializeContracts,
|
|
1709
2257
|
startDevServer,
|
|
1710
2258
|
updateConfig
|
|
1711
2259
|
};
|