@jay-framework/jay-stack-cli 0.10.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 +134 -10
- 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 +847 -136
- package/package.json +14 -6
package/dist/index.js
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
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
|
-
import fs from "fs";
|
|
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
|
-
import { parseJayFile, JAY_IMPORT_RESOLVER, generateElementDefinitionFile, parseContract, ContractTagType } from "@jay-framework/compiler-jay-html";
|
|
12
|
-
import { JAY_CONTRACT_EXTENSION, JAY_EXTENSION, JayAtomicType, JayEnumType, loadPluginManifest } from "@jay-framework/compiler-shared";
|
|
12
|
+
import { parseJayFile, JAY_IMPORT_RESOLVER, generateElementDefinitionFile, parseContract, ContractTagType, generateElementFile } from "@jay-framework/compiler-jay-html";
|
|
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";
|
|
18
|
+
import { glob } from "glob";
|
|
15
19
|
const DEFAULT_CONFIG = {
|
|
16
20
|
devServer: {
|
|
17
21
|
portRange: [3e3, 3100],
|
|
@@ -43,7 +47,7 @@ function loadConfig() {
|
|
|
43
47
|
}
|
|
44
48
|
};
|
|
45
49
|
} catch (error) {
|
|
46
|
-
|
|
50
|
+
getLogger().warn(`Failed to parse .jay YAML config file, using defaults: ${error}`);
|
|
47
51
|
return DEFAULT_CONFIG;
|
|
48
52
|
}
|
|
49
53
|
}
|
|
@@ -81,7 +85,7 @@ function updateConfig(updates) {
|
|
|
81
85
|
const yamlContent = YAML.stringify(updatedConfig, { indent: 2 });
|
|
82
86
|
fs.writeFileSync(configPath, yamlContent);
|
|
83
87
|
} catch (error) {
|
|
84
|
-
|
|
88
|
+
getLogger().warn(`Failed to update .jay config file: ${error}`);
|
|
85
89
|
}
|
|
86
90
|
}
|
|
87
91
|
const PAGE_FILENAME = `page${JAY_EXTENSION}`;
|
|
@@ -157,7 +161,7 @@ async function scanPageDirectories(pagesBasePath, onPageFound) {
|
|
|
157
161
|
}
|
|
158
162
|
}
|
|
159
163
|
} catch (error) {
|
|
160
|
-
|
|
164
|
+
getLogger().warn(`Failed to scan directory ${dirPath}: ${error}`);
|
|
161
165
|
}
|
|
162
166
|
}
|
|
163
167
|
await scanDirectory(pagesBasePath);
|
|
@@ -167,9 +171,8 @@ async function parseContractFile(contractFilePath) {
|
|
|
167
171
|
const contractYaml = await fs.promises.readFile(contractFilePath, "utf-8");
|
|
168
172
|
const parsedContract = parseContract(contractYaml, contractFilePath);
|
|
169
173
|
if (parsedContract.validations.length > 0) {
|
|
170
|
-
|
|
171
|
-
`Contract validation errors in ${contractFilePath}
|
|
172
|
-
parsedContract.validations
|
|
174
|
+
getLogger().warn(
|
|
175
|
+
`Contract validation errors in ${contractFilePath}: ${parsedContract.validations.join(", ")}`
|
|
173
176
|
);
|
|
174
177
|
}
|
|
175
178
|
if (parsedContract.val) {
|
|
@@ -183,7 +186,7 @@ async function parseContractFile(contractFilePath) {
|
|
|
183
186
|
};
|
|
184
187
|
}
|
|
185
188
|
} catch (error) {
|
|
186
|
-
|
|
189
|
+
getLogger().warn(`Failed to parse contract file ${contractFilePath}: ${error}`);
|
|
187
190
|
}
|
|
188
191
|
return null;
|
|
189
192
|
}
|
|
@@ -209,11 +212,11 @@ async function resolveLinkedTags(tags, baseDir) {
|
|
|
209
212
|
}
|
|
210
213
|
resolvedTags.push(resolvedTag);
|
|
211
214
|
} else {
|
|
212
|
-
|
|
215
|
+
getLogger().warn(`Failed to load linked contract: ${tag.link} from ${baseDir}`);
|
|
213
216
|
resolvedTags.push(convertContractTagToProtocol(tag));
|
|
214
217
|
}
|
|
215
218
|
} catch (error) {
|
|
216
|
-
|
|
219
|
+
getLogger().warn(`Error resolving linked contract ${tag.link}: ${error}`);
|
|
217
220
|
resolvedTags.push(convertContractTagToProtocol(tag));
|
|
218
221
|
}
|
|
219
222
|
} else if (tag.tags) {
|
|
@@ -234,9 +237,8 @@ function resolveAppContractPath(appModule, contractFileName, projectRootPath) {
|
|
|
234
237
|
const resolvedPath = require2.resolve(modulePath);
|
|
235
238
|
return resolvedPath;
|
|
236
239
|
} catch (error) {
|
|
237
|
-
|
|
238
|
-
`Failed to resolve contract: ${appModule}/${contractFileName}
|
|
239
|
-
error instanceof Error ? error.message : error
|
|
240
|
+
getLogger().warn(
|
|
241
|
+
`Failed to resolve contract: ${appModule}/${contractFileName} - ${error instanceof Error ? error.message : error}`
|
|
240
242
|
);
|
|
241
243
|
return null;
|
|
242
244
|
}
|
|
@@ -315,12 +317,12 @@ async function scanInstalledAppContracts(configBasePath, projectRootPath) {
|
|
|
315
317
|
installedAppContracts[appName] = appContracts;
|
|
316
318
|
}
|
|
317
319
|
} catch (error) {
|
|
318
|
-
|
|
320
|
+
getLogger().warn(`Failed to parse app config ${appConfigPath}: ${error}`);
|
|
319
321
|
}
|
|
320
322
|
}
|
|
321
323
|
}
|
|
322
324
|
} catch (error) {
|
|
323
|
-
|
|
325
|
+
getLogger().warn(`Failed to scan installed apps directory ${installedAppsPath}: ${error}`);
|
|
324
326
|
}
|
|
325
327
|
return installedAppContracts;
|
|
326
328
|
}
|
|
@@ -419,7 +421,7 @@ async function scanProjectComponents(componentsBasePath) {
|
|
|
419
421
|
}
|
|
420
422
|
}
|
|
421
423
|
} catch (error) {
|
|
422
|
-
|
|
424
|
+
getLogger().warn(`Failed to scan components directory ${componentsBasePath}: ${error}`);
|
|
423
425
|
}
|
|
424
426
|
return components;
|
|
425
427
|
}
|
|
@@ -447,12 +449,12 @@ async function scanInstalledApps(configBasePath) {
|
|
|
447
449
|
});
|
|
448
450
|
}
|
|
449
451
|
} catch (error) {
|
|
450
|
-
|
|
452
|
+
getLogger().warn(`Failed to parse app config ${appConfigPath}: ${error}`);
|
|
451
453
|
}
|
|
452
454
|
}
|
|
453
455
|
}
|
|
454
456
|
} catch (error) {
|
|
455
|
-
|
|
457
|
+
getLogger().warn(`Failed to scan installed apps directory ${installedAppsPath}: ${error}`);
|
|
456
458
|
}
|
|
457
459
|
return installedApps;
|
|
458
460
|
}
|
|
@@ -465,7 +467,7 @@ async function getProjectName(configBasePath) {
|
|
|
465
467
|
return projectConfig.name || "Unnamed Project";
|
|
466
468
|
}
|
|
467
469
|
} catch (error) {
|
|
468
|
-
|
|
470
|
+
getLogger().warn(`Failed to read project config ${projectConfigPath}: ${error}`);
|
|
469
471
|
}
|
|
470
472
|
return "Unnamed Project";
|
|
471
473
|
}
|
|
@@ -492,12 +494,14 @@ async function scanPlugins(projectRootPath) {
|
|
|
492
494
|
}
|
|
493
495
|
});
|
|
494
496
|
} catch (error) {
|
|
495
|
-
|
|
497
|
+
getLogger().warn(`Failed to parse plugin.yaml for ${dir.name}: ${error}`);
|
|
496
498
|
}
|
|
497
499
|
}
|
|
498
500
|
}
|
|
499
501
|
} catch (error) {
|
|
500
|
-
|
|
502
|
+
getLogger().warn(
|
|
503
|
+
`Failed to scan local plugins directory ${localPluginsPath}: ${error}`
|
|
504
|
+
);
|
|
501
505
|
}
|
|
502
506
|
}
|
|
503
507
|
const nodeModulesPath = path.join(projectRootPath, "node_modules");
|
|
@@ -548,16 +552,15 @@ async function scanPlugins(projectRootPath) {
|
|
|
548
552
|
}
|
|
549
553
|
});
|
|
550
554
|
} catch (error) {
|
|
551
|
-
|
|
552
|
-
`Failed to parse plugin.yaml for package ${pkgPath}
|
|
553
|
-
error
|
|
555
|
+
getLogger().warn(
|
|
556
|
+
`Failed to parse plugin.yaml for package ${pkgPath}: ${error}`
|
|
554
557
|
);
|
|
555
558
|
}
|
|
556
559
|
}
|
|
557
560
|
}
|
|
558
561
|
}
|
|
559
562
|
} catch (error) {
|
|
560
|
-
|
|
563
|
+
getLogger().warn(`Failed to scan node_modules for plugins: ${error}`);
|
|
561
564
|
}
|
|
562
565
|
}
|
|
563
566
|
return plugins;
|
|
@@ -585,7 +588,7 @@ async function scanProjectInfo(pagesBasePath, componentsBasePath, configBasePath
|
|
|
585
588
|
contractSchema = parsedContract;
|
|
586
589
|
}
|
|
587
590
|
} catch (error) {
|
|
588
|
-
|
|
591
|
+
getLogger().warn(`Failed to parse contract file ${contractPath}: ${error}`);
|
|
589
592
|
}
|
|
590
593
|
}
|
|
591
594
|
if (hasPageHtml) {
|
|
@@ -597,7 +600,7 @@ async function scanProjectInfo(pagesBasePath, componentsBasePath, configBasePath
|
|
|
597
600
|
installedAppContracts
|
|
598
601
|
);
|
|
599
602
|
} catch (error) {
|
|
600
|
-
|
|
603
|
+
getLogger().warn(`Failed to read page file ${pageFilePath}: ${error}`);
|
|
601
604
|
}
|
|
602
605
|
} else if (hasPageConfig) {
|
|
603
606
|
try {
|
|
@@ -698,7 +701,7 @@ async function scanProjectInfo(pagesBasePath, componentsBasePath, configBasePath
|
|
|
698
701
|
}
|
|
699
702
|
}
|
|
700
703
|
} catch (error) {
|
|
701
|
-
|
|
704
|
+
getLogger().warn(`Failed to parse page config ${pageConfigPath}: ${error}`);
|
|
702
705
|
}
|
|
703
706
|
}
|
|
704
707
|
pages.push({
|
|
@@ -731,7 +734,7 @@ async function handlePagePublish(resolvedConfig, page) {
|
|
|
731
734
|
if (page.contract) {
|
|
732
735
|
contractPath = path.join(dirname, `page${JAY_CONTRACT_EXTENSION}`);
|
|
733
736
|
await fs.promises.writeFile(contractPath, page.contract, "utf-8");
|
|
734
|
-
|
|
737
|
+
getLogger().info(`📄 Published page contract: ${contractPath}`);
|
|
735
738
|
}
|
|
736
739
|
const createdJayHtml = {
|
|
737
740
|
jayHtml: page.jayHtml,
|
|
@@ -739,7 +742,7 @@ async function handlePagePublish(resolvedConfig, page) {
|
|
|
739
742
|
dirname,
|
|
740
743
|
fullPath
|
|
741
744
|
};
|
|
742
|
-
|
|
745
|
+
getLogger().info(`📝 Published page: ${fullPath}`);
|
|
743
746
|
return [
|
|
744
747
|
{
|
|
745
748
|
success: true,
|
|
@@ -749,7 +752,7 @@ async function handlePagePublish(resolvedConfig, page) {
|
|
|
749
752
|
createdJayHtml
|
|
750
753
|
];
|
|
751
754
|
} catch (error) {
|
|
752
|
-
|
|
755
|
+
getLogger().error(`Failed to publish page ${page.route}: ${error}`);
|
|
753
756
|
return [
|
|
754
757
|
{
|
|
755
758
|
success: false,
|
|
@@ -777,7 +780,7 @@ async function handleComponentPublish(resolvedConfig, component) {
|
|
|
777
780
|
dirname,
|
|
778
781
|
fullPath
|
|
779
782
|
};
|
|
780
|
-
|
|
783
|
+
getLogger().info(`🧩 Published component: ${fullPath}`);
|
|
781
784
|
return [
|
|
782
785
|
{
|
|
783
786
|
success: true,
|
|
@@ -787,7 +790,7 @@ async function handleComponentPublish(resolvedConfig, component) {
|
|
|
787
790
|
createdJayHtml
|
|
788
791
|
];
|
|
789
792
|
} catch (error) {
|
|
790
|
-
|
|
793
|
+
getLogger().error(`Failed to publish component ${component.name}: ${error}`);
|
|
791
794
|
return [
|
|
792
795
|
{
|
|
793
796
|
success: false,
|
|
@@ -831,7 +834,7 @@ function createEditorHandlers(config, tsConfigPath, projectRoot) {
|
|
|
831
834
|
);
|
|
832
835
|
const definitionFile = generateElementDefinitionFile(parsedJayHtml);
|
|
833
836
|
if (definitionFile.validations.length > 0)
|
|
834
|
-
|
|
837
|
+
getLogger().warn(
|
|
835
838
|
`failed to generate .d.ts for ${fullPath} with validation errors: ${definitionFile.validations.join("\n")}`
|
|
836
839
|
);
|
|
837
840
|
else
|
|
@@ -850,14 +853,14 @@ function createEditorHandlers(config, tsConfigPath, projectRoot) {
|
|
|
850
853
|
const filename = `${params.imageId}.png`;
|
|
851
854
|
const imagePath = path.join(imagesDir, filename);
|
|
852
855
|
await fs.promises.writeFile(imagePath, Buffer.from(params.imageData, "base64"));
|
|
853
|
-
|
|
856
|
+
getLogger().info(`🖼️ Saved image: ${imagePath}`);
|
|
854
857
|
return {
|
|
855
858
|
type: "saveImage",
|
|
856
859
|
success: true,
|
|
857
860
|
imageUrl: `/images/${filename}`
|
|
858
861
|
};
|
|
859
862
|
} catch (error) {
|
|
860
|
-
|
|
863
|
+
getLogger().error(`Failed to save image: ${error}`);
|
|
861
864
|
return {
|
|
862
865
|
type: "saveImage",
|
|
863
866
|
success: false,
|
|
@@ -881,7 +884,7 @@ function createEditorHandlers(config, tsConfigPath, projectRoot) {
|
|
|
881
884
|
imageUrl: exists ? `/images/${filename}` : void 0
|
|
882
885
|
};
|
|
883
886
|
} catch (error) {
|
|
884
|
-
|
|
887
|
+
getLogger().error(`Failed to check image: ${error}`);
|
|
885
888
|
return {
|
|
886
889
|
type: "hasImage",
|
|
887
890
|
success: false,
|
|
@@ -902,18 +905,18 @@ function createEditorHandlers(config, tsConfigPath, projectRoot) {
|
|
|
902
905
|
configBasePath,
|
|
903
906
|
projectRootPath
|
|
904
907
|
);
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
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}`);
|
|
910
913
|
return {
|
|
911
914
|
type: "getProjectInfo",
|
|
912
915
|
success: true,
|
|
913
916
|
info
|
|
914
917
|
};
|
|
915
918
|
} catch (error) {
|
|
916
|
-
|
|
919
|
+
getLogger().error(`Failed to get project info: ${error}`);
|
|
917
920
|
return {
|
|
918
921
|
type: "getProjectInfo",
|
|
919
922
|
success: false,
|
|
@@ -968,16 +971,16 @@ async function generatePageDefinitionFiles(routes, tsConfigPath, projectRoot) {
|
|
|
968
971
|
);
|
|
969
972
|
const definitionFile = generateElementDefinitionFile(parsedJayHtml);
|
|
970
973
|
if (definitionFile.validations.length > 0) {
|
|
971
|
-
|
|
974
|
+
getLogger().warn(
|
|
972
975
|
`failed to generate .d.ts for ${jayHtmlPath} with validation errors: ${definitionFile.validations.join("\n")}`
|
|
973
976
|
);
|
|
974
977
|
} else {
|
|
975
978
|
const definitionFilePath2 = jayHtmlPath + ".d.ts";
|
|
976
979
|
await fs.promises.writeFile(definitionFilePath2, definitionFile.val, "utf-8");
|
|
977
|
-
|
|
980
|
+
getLogger().info(`📦 Generated definition file: ${definitionFilePath2}`);
|
|
978
981
|
}
|
|
979
982
|
} catch (error) {
|
|
980
|
-
|
|
983
|
+
getLogger().error(`Failed to generate definition file for ${jayHtmlPath}: ${error}`);
|
|
981
984
|
}
|
|
982
985
|
}
|
|
983
986
|
}
|
|
@@ -994,11 +997,12 @@ async function startDevServer(options = {}) {
|
|
|
994
997
|
};
|
|
995
998
|
const app = express();
|
|
996
999
|
const devServerPort = await getPort({ port: resolvedConfig.devServer.portRange });
|
|
1000
|
+
const log = getLogger();
|
|
997
1001
|
const editorServer = createEditorServer({
|
|
998
1002
|
portRange: resolvedConfig.editorServer.portRange,
|
|
999
1003
|
editorId: resolvedConfig.editorServer.editorId,
|
|
1000
1004
|
onEditorId: (editorId2) => {
|
|
1001
|
-
|
|
1005
|
+
log.info(`Editor connected with ID: ${editorId2}`);
|
|
1002
1006
|
updateConfig({
|
|
1003
1007
|
editorServer: {
|
|
1004
1008
|
editorId: editorId2
|
|
@@ -1021,35 +1025,69 @@ async function startDevServer(options = {}) {
|
|
|
1021
1025
|
projectRootFolder: process.cwd(),
|
|
1022
1026
|
publicBaseUrlPath: "/",
|
|
1023
1027
|
dontCacheSlowly: false,
|
|
1024
|
-
jayRollupConfig: jayOptions
|
|
1028
|
+
jayRollupConfig: jayOptions,
|
|
1029
|
+
logLevel: options.logLevel
|
|
1025
1030
|
});
|
|
1026
1031
|
app.use(server);
|
|
1027
1032
|
const publicPath = path.resolve(resolvedConfig.devServer.publicFolder);
|
|
1028
1033
|
if (fs.existsSync(publicPath)) {
|
|
1029
1034
|
app.use(express.static(publicPath));
|
|
1030
1035
|
} else {
|
|
1031
|
-
|
|
1036
|
+
log.important(`⚠️ Public folder not found: ${resolvedConfig.devServer.publicFolder}`);
|
|
1032
1037
|
}
|
|
1033
1038
|
routes.forEach((route) => {
|
|
1034
1039
|
app.get(route.path, route.handler);
|
|
1035
1040
|
});
|
|
1036
1041
|
generatePageDefinitionFiles(routes, jayOptions.tsConfigFilePath, process.cwd());
|
|
1037
1042
|
const expressServer = app.listen(devServerPort, () => {
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
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}`);
|
|
1042
1047
|
if (fs.existsSync(publicPath)) {
|
|
1043
|
-
|
|
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
|
+
}
|
|
1044
1059
|
}
|
|
1045
1060
|
});
|
|
1046
1061
|
const shutdown = async () => {
|
|
1047
|
-
|
|
1062
|
+
log.important("\n🛑 Shutting down servers...");
|
|
1048
1063
|
await editorServer.stop();
|
|
1049
1064
|
expressServer.closeAllConnections();
|
|
1050
1065
|
await new Promise((resolve) => expressServer.close(resolve));
|
|
1051
1066
|
process.exit(0);
|
|
1052
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
|
+
}
|
|
1053
1091
|
process.on("SIGTERM", shutdown);
|
|
1054
1092
|
process.on("SIGINT", shutdown);
|
|
1055
1093
|
}
|
|
@@ -1207,29 +1245,33 @@ async function validateSchema(context, result) {
|
|
|
1207
1245
|
}
|
|
1208
1246
|
}
|
|
1209
1247
|
if (manifest.dynamic_contracts) {
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
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
|
+
}
|
|
1233
1275
|
}
|
|
1234
1276
|
}
|
|
1235
1277
|
if (!manifest.contracts && !manifest.dynamic_contracts) {
|
|
@@ -1420,54 +1462,549 @@ async function validateDynamicContracts(context, result) {
|
|
|
1420
1462
|
const { dynamic_contracts } = context.manifest;
|
|
1421
1463
|
if (!dynamic_contracts)
|
|
1422
1464
|
return;
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
const
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
if (
|
|
1429
|
-
|
|
1430
|
-
|
|
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
|
+
}
|
|
1431
1488
|
}
|
|
1432
1489
|
}
|
|
1433
|
-
if (
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
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
|
+
}
|
|
1510
|
+
}
|
|
1440
1511
|
}
|
|
1441
1512
|
}
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1513
|
+
}
|
|
1514
|
+
async function findJayFiles(dir) {
|
|
1515
|
+
return await glob(`${dir}/**/*${JAY_EXTENSION}`);
|
|
1516
|
+
}
|
|
1517
|
+
async function findContractFiles(dir) {
|
|
1518
|
+
return await glob(`${dir}/**/*${JAY_CONTRACT_EXTENSION}`);
|
|
1519
|
+
}
|
|
1520
|
+
async function validateJayFiles(options = {}) {
|
|
1521
|
+
const config = loadConfig();
|
|
1522
|
+
const resolvedConfig = getConfigWithDefaults(config);
|
|
1523
|
+
const projectRoot = process.cwd();
|
|
1524
|
+
const scanDir = options.path ? path.resolve(options.path) : path.resolve(resolvedConfig.devServer.pagesBase);
|
|
1525
|
+
const errors = [];
|
|
1526
|
+
const warnings = [];
|
|
1527
|
+
const jayHtmlFiles = await findJayFiles(scanDir);
|
|
1528
|
+
const contractFiles = await findContractFiles(scanDir);
|
|
1529
|
+
if (options.verbose) {
|
|
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
|
|
1533
|
+
`));
|
|
1534
|
+
}
|
|
1535
|
+
for (const contractFile of contractFiles) {
|
|
1536
|
+
const relativePath = path.relative(projectRoot, contractFile);
|
|
1537
|
+
try {
|
|
1538
|
+
const content = await promises.readFile(contractFile, "utf-8");
|
|
1539
|
+
const result = parseContract(content, path.basename(contractFile));
|
|
1540
|
+
if (result.validations.length > 0) {
|
|
1541
|
+
for (const validation of result.validations) {
|
|
1542
|
+
errors.push({
|
|
1543
|
+
file: relativePath,
|
|
1544
|
+
message: validation,
|
|
1545
|
+
stage: "parse"
|
|
1546
|
+
});
|
|
1547
|
+
}
|
|
1548
|
+
if (options.verbose) {
|
|
1549
|
+
getLogger().info(chalk.red(`❌ ${relativePath}`));
|
|
1550
|
+
}
|
|
1551
|
+
} else if (options.verbose) {
|
|
1552
|
+
getLogger().info(chalk.green(`✓ ${relativePath}`));
|
|
1553
|
+
}
|
|
1554
|
+
} catch (error) {
|
|
1555
|
+
errors.push({
|
|
1556
|
+
file: relativePath,
|
|
1557
|
+
message: error.message,
|
|
1558
|
+
stage: "parse"
|
|
1559
|
+
});
|
|
1560
|
+
if (options.verbose) {
|
|
1561
|
+
getLogger().info(chalk.red(`❌ ${relativePath}`));
|
|
1450
1562
|
}
|
|
1451
1563
|
}
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1564
|
+
}
|
|
1565
|
+
for (const jayFile of jayHtmlFiles) {
|
|
1566
|
+
const relativePath = path.relative(projectRoot, jayFile);
|
|
1567
|
+
const filename = path.basename(jayFile.replace(JAY_EXTENSION, ""));
|
|
1568
|
+
const dirname = path.dirname(jayFile);
|
|
1569
|
+
try {
|
|
1570
|
+
const content = await promises.readFile(jayFile, "utf-8");
|
|
1571
|
+
const parsedFile = await parseJayFile(
|
|
1572
|
+
content,
|
|
1573
|
+
filename,
|
|
1574
|
+
dirname,
|
|
1575
|
+
{},
|
|
1576
|
+
JAY_IMPORT_RESOLVER,
|
|
1577
|
+
projectRoot
|
|
1578
|
+
);
|
|
1579
|
+
if (parsedFile.validations.length > 0) {
|
|
1580
|
+
for (const validation of parsedFile.validations) {
|
|
1581
|
+
errors.push({
|
|
1582
|
+
file: relativePath,
|
|
1583
|
+
message: validation,
|
|
1584
|
+
stage: "parse"
|
|
1585
|
+
});
|
|
1586
|
+
}
|
|
1587
|
+
if (options.verbose) {
|
|
1588
|
+
getLogger().info(chalk.red(`❌ ${relativePath}`));
|
|
1589
|
+
}
|
|
1590
|
+
continue;
|
|
1591
|
+
}
|
|
1592
|
+
const generatedFile = generateElementFile(
|
|
1593
|
+
parsedFile.val,
|
|
1594
|
+
RuntimeMode.MainTrusted,
|
|
1595
|
+
GenerateTarget.jay
|
|
1596
|
+
);
|
|
1597
|
+
if (generatedFile.validations.length > 0) {
|
|
1598
|
+
for (const validation of generatedFile.validations) {
|
|
1599
|
+
errors.push({
|
|
1600
|
+
file: relativePath,
|
|
1601
|
+
message: validation,
|
|
1602
|
+
stage: "generate"
|
|
1603
|
+
});
|
|
1604
|
+
}
|
|
1605
|
+
if (options.verbose) {
|
|
1606
|
+
getLogger().info(chalk.red(`❌ ${relativePath}`));
|
|
1607
|
+
}
|
|
1608
|
+
} else if (options.verbose) {
|
|
1609
|
+
getLogger().info(chalk.green(`✓ ${relativePath}`));
|
|
1610
|
+
}
|
|
1611
|
+
} catch (error) {
|
|
1612
|
+
errors.push({
|
|
1613
|
+
file: relativePath,
|
|
1614
|
+
message: error.message,
|
|
1615
|
+
stage: "parse"
|
|
1458
1616
|
});
|
|
1617
|
+
if (options.verbose) {
|
|
1618
|
+
getLogger().info(chalk.red(`❌ ${relativePath}`));
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
return {
|
|
1623
|
+
valid: errors.length === 0,
|
|
1624
|
+
jayHtmlFilesScanned: jayHtmlFiles.length,
|
|
1625
|
+
contractFilesScanned: contractFiles.length,
|
|
1626
|
+
errors,
|
|
1627
|
+
warnings
|
|
1628
|
+
};
|
|
1629
|
+
}
|
|
1630
|
+
function printJayValidationResult(result, options) {
|
|
1631
|
+
const logger = getLogger();
|
|
1632
|
+
if (options.json) {
|
|
1633
|
+
logger.important(JSON.stringify(result, null, 2));
|
|
1634
|
+
return;
|
|
1635
|
+
}
|
|
1636
|
+
logger.important("");
|
|
1637
|
+
if (result.valid) {
|
|
1638
|
+
logger.important(chalk.green("✅ Jay Stack validation successful!\n"));
|
|
1639
|
+
logger.important(
|
|
1640
|
+
`Scanned ${result.jayHtmlFilesScanned} .jay-html files, ${result.contractFilesScanned} .jay-contract files`
|
|
1641
|
+
);
|
|
1642
|
+
logger.important("No errors found.");
|
|
1643
|
+
} else {
|
|
1644
|
+
logger.important(chalk.red("❌ Jay Stack validation failed\n"));
|
|
1645
|
+
logger.important("Errors:");
|
|
1646
|
+
for (const error of result.errors) {
|
|
1647
|
+
logger.important(chalk.red(` ❌ ${error.file}`));
|
|
1648
|
+
logger.important(chalk.gray(` ${error.message}`));
|
|
1649
|
+
logger.important("");
|
|
1650
|
+
}
|
|
1651
|
+
const validFiles = result.jayHtmlFilesScanned + result.contractFilesScanned - result.errors.length;
|
|
1652
|
+
logger.important(
|
|
1653
|
+
chalk.red(`${result.errors.length} error(s) found, ${validFiles} file(s) valid.`)
|
|
1654
|
+
);
|
|
1655
|
+
}
|
|
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();
|
|
1459
1968
|
}
|
|
1460
1969
|
}
|
|
1461
1970
|
}
|
|
1462
1971
|
const program = new Command();
|
|
1463
1972
|
program.name("jay-stack").description("Jay Stack CLI - Development server and plugin validation").version("0.9.0");
|
|
1464
|
-
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) => {
|
|
1465
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;
|
|
1466
1978
|
await startDevServer({
|
|
1467
|
-
projectPath: path2 || process.cwd()
|
|
1979
|
+
projectPath: path2 || process.cwd(),
|
|
1980
|
+
testMode,
|
|
1981
|
+
timeout: options.timeout,
|
|
1982
|
+
logLevel
|
|
1468
1983
|
});
|
|
1469
1984
|
} catch (error) {
|
|
1470
|
-
|
|
1985
|
+
getLogger().error(chalk.red("Error starting dev server:") + " " + error.message);
|
|
1986
|
+
process.exit(1);
|
|
1987
|
+
}
|
|
1988
|
+
});
|
|
1989
|
+
program.command("validate [path]").description("Validate all .jay-html and .jay-contract files in the project").option("-v, --verbose", "Show per-file validation status").option("--json", "Output results as JSON").action(async (scanPath, options) => {
|
|
1990
|
+
try {
|
|
1991
|
+
const result = await validateJayFiles({
|
|
1992
|
+
path: scanPath,
|
|
1993
|
+
verbose: options.verbose,
|
|
1994
|
+
json: options.json
|
|
1995
|
+
});
|
|
1996
|
+
printJayValidationResult(result, options);
|
|
1997
|
+
if (!result.valid) {
|
|
1998
|
+
process.exit(1);
|
|
1999
|
+
}
|
|
2000
|
+
} catch (error) {
|
|
2001
|
+
if (options.json) {
|
|
2002
|
+
getLogger().important(
|
|
2003
|
+
JSON.stringify({ valid: false, error: error.message }, null, 2)
|
|
2004
|
+
);
|
|
2005
|
+
} else {
|
|
2006
|
+
getLogger().error(chalk.red("Validation error:") + " " + error.message);
|
|
2007
|
+
}
|
|
1471
2008
|
process.exit(1);
|
|
1472
2009
|
}
|
|
1473
2010
|
});
|
|
@@ -1485,64 +2022,238 @@ program.command("validate-plugin [path]").description("Validate a Jay Stack plug
|
|
|
1485
2022
|
process.exit(1);
|
|
1486
2023
|
}
|
|
1487
2024
|
} catch (error) {
|
|
1488
|
-
|
|
2025
|
+
getLogger().error(chalk.red("Validation error:") + " " + error.message);
|
|
1489
2026
|
process.exit(1);
|
|
1490
2027
|
}
|
|
1491
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
|
+
});
|
|
1492
2179
|
program.parse(process.argv);
|
|
1493
2180
|
if (!process.argv.slice(2).length) {
|
|
1494
2181
|
program.outputHelp();
|
|
1495
2182
|
}
|
|
1496
2183
|
function printValidationResult(result, verbose) {
|
|
2184
|
+
const logger = getLogger();
|
|
1497
2185
|
if (result.valid && result.warnings.length === 0) {
|
|
1498
|
-
|
|
2186
|
+
logger.important(chalk.green("✅ Plugin validation successful!\n"));
|
|
1499
2187
|
if (verbose) {
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
2188
|
+
logger.important("Plugin: " + result.pluginName);
|
|
2189
|
+
logger.important(" ✅ plugin.yaml valid");
|
|
2190
|
+
logger.important(` ✅ ${result.contractsChecked} contracts validated`);
|
|
1503
2191
|
if (result.typesGenerated) {
|
|
1504
|
-
|
|
2192
|
+
logger.important(` ✅ ${result.typesGenerated} type definitions generated`);
|
|
1505
2193
|
}
|
|
1506
|
-
|
|
2194
|
+
logger.important(` ✅ ${result.componentsChecked} components validated`);
|
|
1507
2195
|
if (result.packageJsonChecked) {
|
|
1508
|
-
|
|
2196
|
+
logger.important(" ✅ package.json valid");
|
|
1509
2197
|
}
|
|
1510
|
-
|
|
2198
|
+
logger.important("\nNo errors found.");
|
|
1511
2199
|
}
|
|
1512
2200
|
} else if (result.valid && result.warnings.length > 0) {
|
|
1513
|
-
|
|
1514
|
-
|
|
2201
|
+
logger.important(chalk.yellow("⚠️ Plugin validation passed with warnings\n"));
|
|
2202
|
+
logger.important("Warnings:");
|
|
1515
2203
|
result.warnings.forEach((warning) => {
|
|
1516
|
-
|
|
2204
|
+
logger.important(chalk.yellow(` ⚠️ ${warning.message}`));
|
|
1517
2205
|
if (warning.location) {
|
|
1518
|
-
|
|
2206
|
+
logger.important(chalk.gray(` Location: ${warning.location}`));
|
|
1519
2207
|
}
|
|
1520
2208
|
if (warning.suggestion) {
|
|
1521
|
-
|
|
2209
|
+
logger.important(chalk.gray(` → ${warning.suggestion}`));
|
|
1522
2210
|
}
|
|
1523
|
-
|
|
2211
|
+
logger.important("");
|
|
1524
2212
|
});
|
|
1525
|
-
|
|
2213
|
+
logger.important(chalk.gray("Use --strict to treat warnings as errors."));
|
|
1526
2214
|
} else {
|
|
1527
|
-
|
|
1528
|
-
|
|
2215
|
+
logger.important(chalk.red("❌ Plugin validation failed\n"));
|
|
2216
|
+
logger.important("Errors:");
|
|
1529
2217
|
result.errors.forEach((error) => {
|
|
1530
|
-
|
|
2218
|
+
logger.important(chalk.red(` ❌ ${error.message}`));
|
|
1531
2219
|
if (error.location) {
|
|
1532
|
-
|
|
2220
|
+
logger.important(chalk.gray(` Location: ${error.location}`));
|
|
1533
2221
|
}
|
|
1534
2222
|
if (error.suggestion) {
|
|
1535
|
-
|
|
2223
|
+
logger.important(chalk.gray(` → ${error.suggestion}`));
|
|
1536
2224
|
}
|
|
1537
|
-
|
|
2225
|
+
logger.important("");
|
|
1538
2226
|
});
|
|
1539
|
-
|
|
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."));
|
|
1540
2249
|
}
|
|
1541
2250
|
}
|
|
1542
2251
|
export {
|
|
1543
2252
|
createEditorHandlers,
|
|
1544
2253
|
getConfigWithDefaults,
|
|
2254
|
+
listContracts2 as listContracts,
|
|
1545
2255
|
loadConfig,
|
|
2256
|
+
materializeContracts2 as materializeContracts,
|
|
1546
2257
|
startDevServer,
|
|
1547
2258
|
updateConfig
|
|
1548
2259
|
};
|