@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/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
- console.warn("Failed to parse .jay YAML config file, using defaults:", error);
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
- console.warn("Failed to update .jay config file:", error);
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
- console.warn(`Failed to scan directory ${dirPath}:`, error);
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
- console.warn(
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
- console.warn(`Failed to parse contract file ${contractFilePath}:`, error);
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
- console.warn(`Failed to load linked contract: ${tag.link} from ${baseDir}`);
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
- console.warn(`Error resolving linked contract ${tag.link}:`, error);
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
- console.warn(
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
- console.warn(`Failed to parse app config ${appConfigPath}:`, error);
320
+ getLogger().warn(`Failed to parse app config ${appConfigPath}: ${error}`);
320
321
  }
321
322
  }
322
323
  }
323
324
  } catch (error) {
324
- console.warn(`Failed to scan installed apps directory ${installedAppsPath}:`, error);
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
- console.warn(`Failed to scan components directory ${componentsBasePath}:`, error);
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
- console.warn(`Failed to parse app config ${appConfigPath}:`, error);
452
+ getLogger().warn(`Failed to parse app config ${appConfigPath}: ${error}`);
452
453
  }
453
454
  }
454
455
  }
455
456
  } catch (error) {
456
- console.warn(`Failed to scan installed apps directory ${installedAppsPath}:`, error);
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
- console.warn(`Failed to read project config ${projectConfigPath}:`, error);
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
- console.warn(`Failed to parse plugin.yaml for ${dir.name}:`, error);
497
+ getLogger().warn(`Failed to parse plugin.yaml for ${dir.name}: ${error}`);
497
498
  }
498
499
  }
499
500
  }
500
501
  } catch (error) {
501
- console.warn(`Failed to scan local plugins directory ${localPluginsPath}:`, error);
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
- console.warn(
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
- console.warn(`Failed to scan node_modules for plugins:`, error);
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
- console.warn(`Failed to parse contract file ${contractPath}:`, error);
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
- console.warn(`Failed to read page file ${pageFilePath}:`, error);
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
- console.warn(`Failed to parse page config ${pageConfigPath}:`, error);
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
- console.log(`📄 Published page contract: ${contractPath}`);
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
- console.log(`📝 Published page: ${fullPath}`);
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
- console.error(`Failed to publish page ${page.route}:`, error);
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
- console.log(`🧩 Published component: ${fullPath}`);
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
- console.error(`Failed to publish component ${component.name}:`, error);
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
- console.log(
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
- console.log(`🖼️ Saved image: ${imagePath}`);
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
- console.error("Failed to save image:", error);
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
- console.error("Failed to check image:", error);
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
- console.log(`📋 Retrieved project info: ${info.name}`);
907
- console.log(` Pages: ${info.pages.length}`);
908
- console.log(` Components: ${info.components.length}`);
909
- console.log(` Installed Apps: ${info.installedApps.length}`);
910
- console.log(` App Contracts: ${Object.keys(info.installedAppContracts).length}`);
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
- console.error("Failed to get project info:", error);
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
- console.log(
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
- console.log(`📦 Generated definition file: ${definitionFilePath2}`);
980
+ getLogger().info(`📦 Generated definition file: ${definitionFilePath2}`);
979
981
  }
980
982
  } catch (error) {
981
- console.error(`Failed to generate definition file for ${jayHtmlPath}:`, error);
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
- console.log(`Editor connected with ID: ${editorId2}`);
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
- console.log(`⚠️ Public folder not found: ${resolvedConfig.devServer.publicFolder}`);
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
- console.log(`🚀 Jay Stack dev server started successfully!`);
1040
- console.log(`📱 Dev Server: http://localhost:${devServerPort}`);
1041
- console.log(`🎨 Editor Server: http://localhost:${editorPort} (ID: ${editorId})`);
1042
- console.log(`📁 Pages directory: ${resolvedConfig.devServer.pagesBase}`);
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
- console.log(`📁 Public folder: ${resolvedConfig.devServer.publicFolder}`);
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
- console.log("\n🛑 Shutting down servers...");
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
- if (!manifest.dynamic_contracts.component) {
1212
- result.errors.push({
1213
- type: "schema",
1214
- message: 'dynamic_contracts is missing "component" field',
1215
- location: "plugin.yaml",
1216
- suggestion: "Specify path to shared component for dynamic contracts"
1217
- });
1218
- }
1219
- if (!manifest.dynamic_contracts.generator) {
1220
- result.errors.push({
1221
- type: "schema",
1222
- message: 'dynamic_contracts is missing "generator" field',
1223
- location: "plugin.yaml",
1224
- suggestion: "Specify path to generator file"
1225
- });
1226
- }
1227
- if (!manifest.dynamic_contracts.prefix) {
1228
- result.errors.push({
1229
- type: "schema",
1230
- message: 'dynamic_contracts is missing "prefix" field',
1231
- location: "plugin.yaml",
1232
- suggestion: 'Specify prefix for dynamic contract names (e.g., "cms")'
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
- if (dynamic_contracts.generator) {
1425
- const generatorPath = path.join(context.pluginPath, dynamic_contracts.generator);
1426
- const possibleExtensions = [".ts", ".js", "/index.ts", "/index.js"];
1427
- let found = false;
1428
- for (const ext of possibleExtensions) {
1429
- if (fs.existsSync(generatorPath + ext)) {
1430
- found = true;
1431
- break;
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 (!found && !context.isNpmPackage) {
1435
- result.errors.push({
1436
- type: "file-missing",
1437
- message: `Generator file not found: ${dynamic_contracts.generator}`,
1438
- location: "plugin.yaml dynamic_contracts",
1439
- suggestion: `Create generator file at ${generatorPath}.ts`
1440
- });
1441
- }
1442
- }
1443
- if (dynamic_contracts.component) {
1444
- const componentPath = path.join(context.pluginPath, dynamic_contracts.component);
1445
- const possibleExtensions = [".ts", ".js", "/index.ts", "/index.js"];
1446
- let found = false;
1447
- for (const ext of possibleExtensions) {
1448
- if (fs.existsSync(componentPath + ext)) {
1449
- found = true;
1450
- break;
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
- console.log(chalk.gray(`Scanning directory: ${scanDir}`));
1480
- console.log(chalk.gray(`Found ${jayHtmlFiles.length} .jay-html files`));
1481
- console.log(chalk.gray(`Found ${contractFiles.length} .jay-contract files
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
- console.log(chalk.red(`❌ ${relativePath}`));
1549
+ getLogger().info(chalk.red(`❌ ${relativePath}`));
1499
1550
  }
1500
1551
  } else if (options.verbose) {
1501
- console.log(chalk.green(`✓ ${relativePath}`));
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
- console.log(chalk.red(`❌ ${relativePath}`));
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
- console.log(chalk.red(`❌ ${relativePath}`));
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
- console.log(chalk.red(`❌ ${relativePath}`));
1606
+ getLogger().info(chalk.red(`❌ ${relativePath}`));
1556
1607
  }
1557
1608
  } else if (options.verbose) {
1558
- console.log(chalk.green(`✓ ${relativePath}`));
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
- console.log(chalk.red(`❌ ${relativePath}`));
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
- console.log(JSON.stringify(result, null, 2));
1633
+ logger.important(JSON.stringify(result, null, 2));
1582
1634
  return;
1583
1635
  }
1584
- console.log("");
1636
+ logger.important("");
1585
1637
  if (result.valid) {
1586
- console.log(chalk.green("✅ Jay Stack validation successful!\n"));
1587
- console.log(
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
- console.log("No errors found.");
1642
+ logger.important("No errors found.");
1591
1643
  } else {
1592
- console.log(chalk.red("❌ Jay Stack validation failed\n"));
1593
- console.log("Errors:");
1644
+ logger.important(chalk.red("❌ Jay Stack validation failed\n"));
1645
+ logger.important("Errors:");
1594
1646
  for (const error of result.errors) {
1595
- console.log(chalk.red(` ❌ ${error.file}`));
1596
- console.log(chalk.gray(` ${error.message}`));
1597
- console.log("");
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
- console.log(
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
- console.error(chalk.red("Error starting dev server:"), error.message);
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
- console.log(JSON.stringify({ valid: false, error: error.message }, null, 2));
2002
+ getLogger().important(
2003
+ JSON.stringify({ valid: false, error: error.message }, null, 2)
2004
+ );
1631
2005
  } else {
1632
- console.error(chalk.red("Validation error:"), error.message);
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
- console.error(chalk.red("Validation error:"), error.message);
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
- console.log(chalk.green("✅ Plugin validation successful!\n"));
2186
+ logger.important(chalk.green("✅ Plugin validation successful!\n"));
1662
2187
  if (verbose) {
1663
- console.log("Plugin:", result.pluginName);
1664
- console.log(" ✅ plugin.yaml valid");
1665
- console.log(` ✅ ${result.contractsChecked} contracts validated`);
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
- console.log(` ✅ ${result.typesGenerated} type definitions generated`);
2192
+ logger.important(` ✅ ${result.typesGenerated} type definitions generated`);
1668
2193
  }
1669
- console.log(` ✅ ${result.componentsChecked} components validated`);
2194
+ logger.important(` ✅ ${result.componentsChecked} components validated`);
1670
2195
  if (result.packageJsonChecked) {
1671
- console.log(" ✅ package.json valid");
2196
+ logger.important(" ✅ package.json valid");
1672
2197
  }
1673
- console.log("\nNo errors found.");
2198
+ logger.important("\nNo errors found.");
1674
2199
  }
1675
2200
  } else if (result.valid && result.warnings.length > 0) {
1676
- console.log(chalk.yellow("⚠️ Plugin validation passed with warnings\n"));
1677
- console.log("Warnings:");
2201
+ logger.important(chalk.yellow("⚠️ Plugin validation passed with warnings\n"));
2202
+ logger.important("Warnings:");
1678
2203
  result.warnings.forEach((warning) => {
1679
- console.log(chalk.yellow(` ⚠️ ${warning.message}`));
2204
+ logger.important(chalk.yellow(` ⚠️ ${warning.message}`));
1680
2205
  if (warning.location) {
1681
- console.log(chalk.gray(` Location: ${warning.location}`));
2206
+ logger.important(chalk.gray(` Location: ${warning.location}`));
1682
2207
  }
1683
2208
  if (warning.suggestion) {
1684
- console.log(chalk.gray(` → ${warning.suggestion}`));
2209
+ logger.important(chalk.gray(` → ${warning.suggestion}`));
1685
2210
  }
1686
- console.log();
2211
+ logger.important("");
1687
2212
  });
1688
- console.log(chalk.gray("Use --strict to treat warnings as errors."));
2213
+ logger.important(chalk.gray("Use --strict to treat warnings as errors."));
1689
2214
  } else {
1690
- console.log(chalk.red("❌ Plugin validation failed\n"));
1691
- console.log("Errors:");
2215
+ logger.important(chalk.red("❌ Plugin validation failed\n"));
2216
+ logger.important("Errors:");
1692
2217
  result.errors.forEach((error) => {
1693
- console.log(chalk.red(` ❌ ${error.message}`));
2218
+ logger.important(chalk.red(` ❌ ${error.message}`));
1694
2219
  if (error.location) {
1695
- console.log(chalk.gray(` Location: ${error.location}`));
2220
+ logger.important(chalk.gray(` Location: ${error.location}`));
1696
2221
  }
1697
2222
  if (error.suggestion) {
1698
- console.log(chalk.gray(` → ${error.suggestion}`));
2223
+ logger.important(chalk.gray(` → ${error.suggestion}`));
1699
2224
  }
1700
- console.log();
2225
+ logger.important("");
1701
2226
  });
1702
- console.log(chalk.red(`${result.errors.length} errors found.`));
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
  };