@jay-framework/jay-stack-cli 0.17.3 → 0.18.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
@@ -7,18 +7,18 @@ import getPort from "get-port";
7
7
  import path from "path";
8
8
  import fs, { promises } from "fs";
9
9
  import YAML from "yaml";
10
- import { getLogger, createDevLogger, setDevLogger } from "@jay-framework/logger";
10
+ import { getLogger, setDevLogger, createDevLogger } from "@jay-framework/logger";
11
11
  import { parseJayFile, JAY_IMPORT_RESOLVER, generateElementDefinitionFile, ContractTagType, parseContract, generateElementFile, generateServerElementFile, htmlElementTagNameMap } from "@jay-framework/compiler-jay-html";
12
12
  import { JAY_CONTRACT_EXTENSION, JAY_EXTENSION, resolvePluginManifest, LOCAL_PLUGIN_PATH, JayAtomicType, JayEnumType, loadPluginManifest, RuntimeMode, GenerateTarget } from "@jay-framework/compiler-shared";
13
- import { scanPlugins as scanPlugins$1, listContracts, materializeContracts } from "@jay-framework/stack-server-runtime";
13
+ import { listContracts, materializeContracts, scanPlugins as scanPlugins$1 } from "@jay-framework/stack-server-runtime";
14
14
  import { listContracts as listContracts2, materializeContracts as materializeContracts2 } from "@jay-framework/stack-server-runtime";
15
15
  import { Command } from "commander";
16
16
  import chalk from "chalk";
17
+ import path$1 from "node:path";
18
+ import fs$1 from "node:fs/promises";
17
19
  import { createRequire } from "module";
18
20
  import { glob } from "glob";
19
21
  import { parse } from "node-html-parser";
20
- import path$1 from "node:path";
21
- import fs$1 from "node:fs/promises";
22
22
  import fsSync from "node:fs";
23
23
  import { fileURLToPath } from "node:url";
24
24
  const DEFAULT_CONFIG = {
@@ -2530,6 +2530,11 @@ async function startDevServer(options = {}) {
2530
2530
  routes.forEach((route) => {
2531
2531
  app.get(route.path, route.handler);
2532
2532
  });
2533
+ service.attachRouteRegistrar((added) => {
2534
+ added.forEach((route) => {
2535
+ app.get(route.path, route.handler);
2536
+ });
2537
+ });
2533
2538
  generatePageDefinitionFiles(routes, jayOptions.tsConfigFilePath, process.cwd());
2534
2539
  httpServer.listen(devServerPort, () => {
2535
2540
  log.important(`🚀 Jay Stack dev server started successfully!`);
@@ -2583,6 +2588,175 @@ async function startDevServer(options = {}) {
2583
2588
  process.on("SIGTERM", shutdown);
2584
2589
  process.on("SIGINT", shutdown);
2585
2590
  }
2591
+ async function initializeServicesForCli(projectRoot, viteServer) {
2592
+ const path2 = await import("node:path");
2593
+ const fs2 = await import("node:fs");
2594
+ const {
2595
+ runInitCallbacks,
2596
+ getServiceRegistry,
2597
+ discoverPluginsWithInit,
2598
+ sortPluginsByDependencies,
2599
+ executePluginServerInits
2600
+ } = await import("@jay-framework/stack-server-runtime");
2601
+ let initErrors = /* @__PURE__ */ new Map();
2602
+ try {
2603
+ const discoveredPlugins = await discoverPluginsWithInit({
2604
+ projectRoot,
2605
+ verbose: false
2606
+ });
2607
+ const pluginsWithInit = sortPluginsByDependencies(discoveredPlugins);
2608
+ try {
2609
+ initErrors = await executePluginServerInits(pluginsWithInit, viteServer, false);
2610
+ } catch (error) {
2611
+ getLogger().warn(chalk.yellow(`⚠️ Plugin initialization skipped: ${error.message}`));
2612
+ }
2613
+ const initPathTs = path2.join(projectRoot, "src", "init.ts");
2614
+ const initPathJs = path2.join(projectRoot, "src", "init.js");
2615
+ let initModule;
2616
+ if (fs2.existsSync(initPathTs) && viteServer) {
2617
+ initModule = await viteServer.ssrLoadModule(initPathTs);
2618
+ } else if (fs2.existsSync(initPathJs)) {
2619
+ initModule = await import(initPathJs);
2620
+ }
2621
+ if (initModule?.init?._serverInit) {
2622
+ await initModule.init._serverInit();
2623
+ }
2624
+ await runInitCallbacks();
2625
+ } catch (error) {
2626
+ getLogger().warn(chalk.yellow(`⚠️ Service initialization failed: ${error.message}`));
2627
+ getLogger().warn(chalk.gray(" Static contracts will still be listed."));
2628
+ }
2629
+ return { services: getServiceRegistry(), initErrors };
2630
+ }
2631
+ async function runDev(projectPath, options) {
2632
+ const logLevel = options.quiet ? "silent" : options.verbose ? "verbose" : "info";
2633
+ setDevLogger(createDevLogger(logLevel));
2634
+ const testMode = options.testMode || options.timeout !== void 0;
2635
+ await startDevServer({
2636
+ projectPath: projectPath || process.cwd(),
2637
+ testMode,
2638
+ timeout: options.timeout,
2639
+ logLevel
2640
+ });
2641
+ }
2642
+ async function resolveProductionContext(projectPath, versionOverride) {
2643
+ const resolvedPath = path$1.resolve(projectPath || process.cwd());
2644
+ const jayConfigPath = path$1.join(resolvedPath, ".jay");
2645
+ let pagesBase = "./src/pages";
2646
+ try {
2647
+ const jayConfig = YAML.parse(await fs$1.readFile(jayConfigPath, "utf-8"));
2648
+ pagesBase = jayConfig?.devServer?.pagesBase || pagesBase;
2649
+ } catch {
2650
+ }
2651
+ const version = versionOverride || await resolveVersionFromPackageJson(resolvedPath);
2652
+ return {
2653
+ resolvedPath,
2654
+ pagesRoot: path$1.resolve(resolvedPath, pagesBase),
2655
+ buildRoot: path$1.join(resolvedPath, "build"),
2656
+ version,
2657
+ tsConfigFilePath: path$1.join(resolvedPath, "tsconfig.json")
2658
+ };
2659
+ }
2660
+ async function resolveVersionFromPackageJson(projectRoot) {
2661
+ try {
2662
+ const pkgJson = JSON.parse(
2663
+ await fs$1.readFile(path$1.join(projectRoot, "package.json"), "utf-8")
2664
+ );
2665
+ if (pkgJson.version) {
2666
+ return pkgJson.version;
2667
+ }
2668
+ } catch {
2669
+ }
2670
+ return "1";
2671
+ }
2672
+ function initLogger(verbose) {
2673
+ const logLevel = verbose ? "verbose" : "info";
2674
+ setDevLogger(createDevLogger(logLevel));
2675
+ }
2676
+ async function runBuild(projectPath, options) {
2677
+ initLogger(options.verbose);
2678
+ const ctx = await resolveProductionContext(projectPath, options.version);
2679
+ const { buildVersion } = await import("@jay-framework/production-server");
2680
+ await buildVersion({
2681
+ version: ctx.version,
2682
+ projectRoot: ctx.resolvedPath,
2683
+ pagesRoot: ctx.pagesRoot,
2684
+ buildRoot: ctx.buildRoot,
2685
+ concurrency: 4,
2686
+ tsConfigFilePath: ctx.tsConfigFilePath,
2687
+ minify: options.minify
2688
+ });
2689
+ }
2690
+ async function runServe(projectPath, options) {
2691
+ initLogger(options.verbose);
2692
+ const ctx = await resolveProductionContext(projectPath, options.version);
2693
+ if (options.role === "renderer") {
2694
+ const { startRendererServer } = await import("@jay-framework/production-server");
2695
+ await startRendererServer({
2696
+ buildRoot: ctx.buildRoot,
2697
+ version: ctx.version,
2698
+ port: parseInt(options.port, 10),
2699
+ projectRoot: ctx.resolvedPath,
2700
+ pagesRoot: ctx.pagesRoot,
2701
+ tsConfigFilePath: ctx.tsConfigFilePath
2702
+ });
2703
+ } else {
2704
+ const { startMainServer } = await import("@jay-framework/production-server");
2705
+ await startMainServer({
2706
+ buildRoot: ctx.buildRoot,
2707
+ version: ctx.version,
2708
+ port: parseInt(options.port, 10),
2709
+ testMode: options.testMode,
2710
+ publicBasePath: options.staticBaseUrl,
2711
+ serveStatic: options.serveStatic
2712
+ });
2713
+ }
2714
+ }
2715
+ async function runRebuild(projectPath, options) {
2716
+ initLogger(options.verbose);
2717
+ const ctx = await resolveProductionContext(projectPath, options.version);
2718
+ let params;
2719
+ if (options.params) {
2720
+ params = JSON.parse(options.params);
2721
+ }
2722
+ const { rebuild } = await import("@jay-framework/production-server");
2723
+ let target;
2724
+ if (options.contract) {
2725
+ target = { mode: "contract", contractName: options.contract, params };
2726
+ } else if (options.route) {
2727
+ target = { mode: "route", routePattern: options.route, params };
2728
+ } else if (options.url) {
2729
+ target = { mode: "url", url: options.url };
2730
+ } else {
2731
+ getLogger().error(chalk.red("One of --contract, --route, or --url is required"));
2732
+ process.exit(1);
2733
+ }
2734
+ const result = await rebuild({
2735
+ projectRoot: ctx.resolvedPath,
2736
+ pagesRoot: ctx.pagesRoot,
2737
+ buildRoot: ctx.buildRoot,
2738
+ version: ctx.version,
2739
+ target,
2740
+ tsConfigFilePath: ctx.tsConfigFilePath
2741
+ });
2742
+ if (result.errors.length > 0) {
2743
+ for (const err of result.errors) {
2744
+ getLogger().error(
2745
+ chalk.red(` Error: ${err.route} ${JSON.stringify(err.params)}: ${err.error}`)
2746
+ );
2747
+ }
2748
+ process.exit(1);
2749
+ }
2750
+ }
2751
+ const runProduction = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
2752
+ __proto__: null,
2753
+ initLogger,
2754
+ resolveProductionContext,
2755
+ resolveVersionFromPackageJson,
2756
+ runBuild,
2757
+ runRebuild,
2758
+ runServe
2759
+ }, Symbol.toStringTag, { value: "Module" }));
2586
2760
  const s = createRequire(import.meta.url), e = s("typescript"), u = e;
2587
2761
  new Proxy(e, {
2588
2762
  get(t, r) {
@@ -3605,9 +3779,6 @@ const SKIP_ATTRS = /* @__PURE__ */ new Set([
3605
3779
  "if",
3606
3780
  "ref",
3607
3781
  "trackBy",
3608
- "slowForEach",
3609
- "jayIndex",
3610
- "jayTrackBy",
3611
3782
  "when-resolved",
3612
3783
  "when-loading",
3613
3784
  "when-rejected",
@@ -4260,95 +4431,369 @@ function printJayValidationResult(result, options) {
4260
4431
  }
4261
4432
  }
4262
4433
  }
4263
- async function initializeServicesForCli(projectRoot, viteServer) {
4264
- const path2 = await import("node:path");
4265
- const fs2 = await import("node:fs");
4266
- const {
4267
- runInitCallbacks,
4268
- getServiceRegistry,
4269
- discoverPluginsWithInit,
4270
- sortPluginsByDependencies,
4271
- executePluginServerInits
4272
- } = await import("@jay-framework/stack-server-runtime");
4273
- let initErrors = /* @__PURE__ */ new Map();
4274
- try {
4275
- const discoveredPlugins = await discoverPluginsWithInit({
4276
- projectRoot,
4277
- verbose: false
4278
- });
4279
- const pluginsWithInit = sortPluginsByDependencies(discoveredPlugins);
4280
- try {
4281
- initErrors = await executePluginServerInits(pluginsWithInit, viteServer, false);
4282
- } catch (error) {
4283
- getLogger().warn(chalk.yellow(`⚠️ Plugin initialization skipped: ${error.message}`));
4434
+ async function runValidate(scanPath, options) {
4435
+ const result = await validateJayFiles({
4436
+ path: scanPath,
4437
+ verbose: options.verbose,
4438
+ json: options.json
4439
+ });
4440
+ printJayValidationResult(result, options);
4441
+ if (!result.valid) {
4442
+ process.exit(1);
4443
+ }
4444
+ }
4445
+ async function runValidatePlugin(pluginPath, options) {
4446
+ const result = await validatePlugin({
4447
+ pluginPath: pluginPath || process.cwd(),
4448
+ local: options.local,
4449
+ verbose: options.verbose,
4450
+ strict: options.strict,
4451
+ generateTypes: options.generateTypes
4452
+ });
4453
+ printValidationResult(result, options.verbose ?? false);
4454
+ if (!result.valid || options.strict && result.warnings.length > 0) {
4455
+ process.exit(1);
4456
+ }
4457
+ }
4458
+ function printValidationResult(result, verbose) {
4459
+ const logger = getLogger();
4460
+ if (result.valid && result.warnings.length === 0) {
4461
+ logger.important(chalk.green("Plugin validation successful!\n"));
4462
+ if (verbose) {
4463
+ logger.important("Plugin: " + result.pluginName);
4464
+ logger.important(" plugin.yaml valid");
4465
+ logger.important(` ${result.contractsChecked} contracts validated`);
4466
+ if (result.typesGenerated) {
4467
+ logger.important(` ${result.typesGenerated} type definitions generated`);
4468
+ }
4469
+ logger.important(` ${result.componentsChecked} components validated`);
4470
+ if (result.packageJsonChecked) {
4471
+ logger.important(" package.json valid");
4472
+ }
4473
+ logger.important("\nNo errors found.");
4284
4474
  }
4285
- const initPathTs = path2.join(projectRoot, "src", "init.ts");
4286
- const initPathJs = path2.join(projectRoot, "src", "init.js");
4287
- let initModule;
4288
- if (fs2.existsSync(initPathTs) && viteServer) {
4289
- initModule = await viteServer.ssrLoadModule(initPathTs);
4290
- } else if (fs2.existsSync(initPathJs)) {
4291
- initModule = await import(initPathJs);
4475
+ } else if (result.valid && result.warnings.length > 0) {
4476
+ logger.important(chalk.yellow("Plugin validation passed with warnings\n"));
4477
+ logger.important("Warnings:");
4478
+ result.warnings.forEach((warning) => {
4479
+ logger.important(chalk.yellow(` ${warning.message}`));
4480
+ if (warning.location) {
4481
+ logger.important(chalk.gray(` Location: ${warning.location}`));
4482
+ }
4483
+ if (warning.suggestion) {
4484
+ logger.important(chalk.gray(` ${warning.suggestion}`));
4485
+ }
4486
+ logger.important("");
4487
+ });
4488
+ logger.important(chalk.gray("Use --strict to treat warnings as errors."));
4489
+ } else {
4490
+ logger.important(chalk.red("Plugin validation failed\n"));
4491
+ logger.important("Errors:");
4492
+ result.errors.forEach((error) => {
4493
+ logger.important(chalk.red(` ${error.message}`));
4494
+ if (error.location) {
4495
+ logger.important(chalk.gray(` Location: ${error.location}`));
4496
+ }
4497
+ if (error.suggestion) {
4498
+ logger.important(chalk.gray(` ${error.suggestion}`));
4499
+ }
4500
+ logger.important("");
4501
+ });
4502
+ logger.important(chalk.red(`${result.errors.length} errors found.`));
4503
+ }
4504
+ }
4505
+ const ALL_ROLES = ["designer", "developer", "plugin", "devops"];
4506
+ async function runAgentKit(options) {
4507
+ const projectRoot = process.cwd();
4508
+ const { initErrors, viteServer } = await runMaterialize(
4509
+ projectRoot,
4510
+ options,
4511
+ "agent-kit/materialized-contracts",
4512
+ true
4513
+ );
4514
+ try {
4515
+ if (!options.list) {
4516
+ await ensureAgentKitDocs(projectRoot, options.force, options.mode);
4517
+ await mergePluginAgentKitGuides(projectRoot, options.mode);
4518
+ if (options.references !== false) {
4519
+ await generatePluginReferences(projectRoot, options, initErrors, viteServer);
4520
+ }
4292
4521
  }
4293
- if (initModule?.init?._serverInit) {
4294
- await initModule.init._serverInit();
4522
+ } finally {
4523
+ if (viteServer) {
4524
+ await viteServer.close();
4295
4525
  }
4296
- await runInitCallbacks();
4297
- } catch (error) {
4298
- getLogger().warn(chalk.yellow(`⚠️ Service initialization failed: ${error.message}`));
4299
- getLogger().warn(chalk.gray(" Static contracts will still be listed."));
4300
4526
  }
4301
- return { services: getServiceRegistry(), initErrors };
4302
4527
  }
4303
- async function runAction(actionRef, options, projectRoot, initializeServices) {
4528
+ async function runMaterialize(projectRoot, options, defaultOutputRelative, keepViteAlive = false) {
4529
+ const outputDir = options.output ?? path$1.join(projectRoot, defaultOutputRelative);
4304
4530
  let viteServer;
4531
+ let initErrors = /* @__PURE__ */ new Map();
4305
4532
  try {
4306
- const slashIndex = actionRef.indexOf("/");
4307
- if (slashIndex === -1) {
4308
- getLogger().error(
4309
- chalk.red("❌ Invalid action reference. Use format: <plugin>/<action>")
4310
- );
4311
- process.exit(1);
4533
+ if (options.list) {
4534
+ const index = await listContracts({
4535
+ projectRoot,
4536
+ dynamicOnly: options.dynamicOnly,
4537
+ pluginFilter: options.plugin
4538
+ });
4539
+ if (options.yaml) {
4540
+ getLogger().important(YAML.stringify(index));
4541
+ } else {
4542
+ printContractList(index);
4543
+ }
4544
+ return { initErrors };
4312
4545
  }
4313
- const actionExport = actionRef.substring(slashIndex + 1);
4314
- const input = options.input ? JSON.parse(options.input) : {};
4315
4546
  if (options.verbose) {
4316
4547
  getLogger().info("Starting Vite for TypeScript support...");
4317
4548
  }
4318
4549
  viteServer = await createViteForCli({ projectRoot });
4319
- await initializeServices(projectRoot, viteServer);
4320
- const { discoverAndRegisterActions, discoverAllPluginActions, ActionRegistry } = await import("@jay-framework/stack-server-runtime");
4321
- const registry = new ActionRegistry();
4322
- await discoverAndRegisterActions({
4323
- projectRoot,
4324
- registry,
4325
- verbose: options.verbose,
4326
- viteServer
4327
- });
4328
- await discoverAllPluginActions({
4550
+ const { services, initErrors: errors } = await initializeServicesForCli(
4329
4551
  projectRoot,
4330
- registry,
4331
- verbose: options.verbose,
4332
4552
  viteServer
4333
- });
4334
- const allNames = registry.getNames();
4335
- const matchedName = allNames.find((name) => name === actionExport) || allNames.find((name) => name.endsWith("." + actionExport)) || allNames.find((name) => name === actionRef);
4336
- if (!matchedName) {
4337
- getLogger().error(chalk.red(`❌ Action "${actionExport}" not found.`));
4338
- if (allNames.length > 0) {
4339
- getLogger().error(` Available actions: ${allNames.join(", ")}`);
4340
- } else {
4341
- getLogger().error(" No actions registered. Does the plugin have actions?");
4342
- }
4343
- process.exit(1);
4553
+ );
4554
+ initErrors = errors;
4555
+ const result = await materializeContracts(
4556
+ {
4557
+ projectRoot,
4558
+ outputDir,
4559
+ force: options.force,
4560
+ dynamicOnly: options.dynamicOnly,
4561
+ pluginFilter: options.plugin,
4562
+ verbose: options.verbose,
4563
+ viteServer
4564
+ },
4565
+ services
4566
+ );
4567
+ if (options.yaml) {
4568
+ getLogger().important(YAML.stringify(result.pluginsIndex));
4569
+ } else {
4570
+ const totalContracts = result.pluginsIndex.plugins.reduce(
4571
+ (sum, p) => sum + p.contracts.length,
4572
+ 0
4573
+ );
4574
+ getLogger().important(chalk.green(`
4575
+ Materialized ${totalContracts} contracts`));
4576
+ getLogger().important(` Static: ${result.staticCount}`);
4577
+ getLogger().important(` Dynamic: ${result.dynamicCount}`);
4578
+ getLogger().important(` Output: ${result.outputDir}`);
4344
4579
  }
4580
+ return { initErrors, viteServer: keepViteAlive ? viteServer : void 0 };
4581
+ } catch (error) {
4582
+ getLogger().error(chalk.red("Failed to materialize contracts:") + " " + error.message);
4345
4583
  if (options.verbose) {
4346
- getLogger().info(`Executing action: ${matchedName}`);
4347
- getLogger().info(`Input: ${JSON.stringify(input)}`);
4584
+ getLogger().error(error.stack);
4348
4585
  }
4349
- const result = await registry.execute(matchedName, input);
4350
- if (result.success) {
4351
- if (options.yaml) {
4586
+ process.exit(1);
4587
+ } finally {
4588
+ if (viteServer && !keepViteAlive) {
4589
+ await viteServer.close();
4590
+ }
4591
+ }
4592
+ return { initErrors };
4593
+ }
4594
+ async function ensureAgentKitDocs(projectRoot, _force, mode) {
4595
+ const agentKitDir = path$1.join(projectRoot, "agent-kit");
4596
+ const thisDir = path$1.dirname(fileURLToPath(import.meta.url));
4597
+ const templateDir = path$1.resolve(thisDir, "..", "agent-kit-template");
4598
+ const roles = mode && ALL_ROLES.includes(mode) ? [mode] : ALL_ROLES;
4599
+ for (const role of roles) {
4600
+ const roleTemplateDir = path$1.join(templateDir, role);
4601
+ const roleOutputDir = path$1.join(agentKitDir, role);
4602
+ let files;
4603
+ try {
4604
+ files = (await fs$1.readdir(roleTemplateDir)).filter((f) => f.endsWith(".md"));
4605
+ } catch {
4606
+ continue;
4607
+ }
4608
+ await fs$1.mkdir(roleOutputDir, { recursive: true });
4609
+ for (const filename of files) {
4610
+ await fs$1.copyFile(
4611
+ path$1.join(roleTemplateDir, filename),
4612
+ path$1.join(roleOutputDir, filename)
4613
+ );
4614
+ getLogger().info(chalk.gray(` Created agent-kit/${role}/${filename}`));
4615
+ }
4616
+ }
4617
+ }
4618
+ async function mergePluginAgentKitGuides(projectRoot, mode) {
4619
+ const plugins = await scanPlugins$1({ projectRoot });
4620
+ const agentKitDir = path$1.join(projectRoot, "agent-kit");
4621
+ const roles = mode && ALL_ROLES.includes(mode) ? [mode] : ALL_ROLES;
4622
+ const copiedPerRole = /* @__PURE__ */ new Map();
4623
+ for (const [, plugin] of plugins) {
4624
+ const pluginAgentKitDir = path$1.join(plugin.pluginPath, "agent-kit");
4625
+ if (!fsSync.existsSync(pluginAgentKitDir))
4626
+ continue;
4627
+ for (const role of roles) {
4628
+ const roleSourceDir = path$1.join(pluginAgentKitDir, role);
4629
+ let files;
4630
+ try {
4631
+ files = (await fs$1.readdir(roleSourceDir)).filter(
4632
+ (f) => f.endsWith(".md") && f !== "INSTRUCTIONS.md"
4633
+ );
4634
+ } catch {
4635
+ continue;
4636
+ }
4637
+ if (files.length === 0)
4638
+ continue;
4639
+ const roleOutputDir = path$1.join(agentKitDir, role);
4640
+ await fs$1.mkdir(roleOutputDir, { recursive: true });
4641
+ for (const filename of files) {
4642
+ const sourcePath = path$1.join(roleSourceDir, filename);
4643
+ await fs$1.copyFile(sourcePath, path$1.join(roleOutputDir, filename));
4644
+ let description = "";
4645
+ try {
4646
+ const content = await fs$1.readFile(sourcePath, "utf-8");
4647
+ const lines = content.split("\n");
4648
+ let pastHeading = false;
4649
+ for (const line of lines) {
4650
+ if (line.startsWith("# ")) {
4651
+ pastHeading = true;
4652
+ continue;
4653
+ }
4654
+ if (pastHeading && line.trim()) {
4655
+ description = line.trim();
4656
+ break;
4657
+ }
4658
+ }
4659
+ } catch {
4660
+ }
4661
+ if (!copiedPerRole.has(role))
4662
+ copiedPerRole.set(role, []);
4663
+ copiedPerRole.get(role).push({ filename, pluginName: plugin.name, description });
4664
+ getLogger().info(
4665
+ chalk.gray(
4666
+ ` Copied agent-kit/${role}/${filename} from plugin "${plugin.name}"`
4667
+ )
4668
+ );
4669
+ }
4670
+ }
4671
+ }
4672
+ for (const [role, entries] of copiedPerRole) {
4673
+ const instructionsPath = path$1.join(agentKitDir, role, "INSTRUCTIONS.md");
4674
+ if (!fsSync.existsSync(instructionsPath))
4675
+ continue;
4676
+ const lines = [
4677
+ "",
4678
+ "## Plugin-Contributed Guides",
4679
+ "",
4680
+ "| File | Plugin | Description |",
4681
+ "| --- | --- | --- |"
4682
+ ];
4683
+ for (const { filename, pluginName, description } of entries) {
4684
+ lines.push(`| [${filename}](${filename}) | ${pluginName} | ${description} |`);
4685
+ }
4686
+ lines.push("");
4687
+ await fs$1.appendFile(instructionsPath, lines.join("\n"));
4688
+ }
4689
+ }
4690
+ async function generatePluginReferences(projectRoot, options, initErrors, viteServer) {
4691
+ const { discoverPluginsWithReferences, executePluginReferences } = await import("@jay-framework/stack-server-runtime");
4692
+ const plugins = await discoverPluginsWithReferences({
4693
+ projectRoot,
4694
+ verbose: options.verbose,
4695
+ pluginFilter: options.plugin
4696
+ });
4697
+ if (plugins.length === 0)
4698
+ return;
4699
+ const logger = getLogger();
4700
+ logger.important("");
4701
+ logger.important(chalk.bold("Generating plugin references..."));
4702
+ for (const plugin of plugins) {
4703
+ const pluginInitError = initErrors.get(plugin.name);
4704
+ if (pluginInitError) {
4705
+ logger.warn(
4706
+ chalk.yellow(
4707
+ ` ${plugin.name}: references skipped — init failed: ${pluginInitError.message}`
4708
+ )
4709
+ );
4710
+ continue;
4711
+ }
4712
+ try {
4713
+ const result = await executePluginReferences(plugin, {
4714
+ projectRoot,
4715
+ force: options.force ?? false,
4716
+ viteServer,
4717
+ verbose: options.verbose
4718
+ });
4719
+ if (result.referencesCreated.length > 0) {
4720
+ logger.important(chalk.green(` ${plugin.name}:`));
4721
+ for (const ref of result.referencesCreated) {
4722
+ logger.important(chalk.gray(` ${ref}`));
4723
+ }
4724
+ if (result.message) {
4725
+ logger.important(chalk.gray(` ${result.message}`));
4726
+ }
4727
+ }
4728
+ } catch (error) {
4729
+ logger.warn(chalk.yellow(` ${plugin.name}: references skipped — ${error.message}`));
4730
+ }
4731
+ }
4732
+ }
4733
+ function printContractList(index) {
4734
+ const logger = getLogger();
4735
+ logger.important("\nAvailable Contracts:\n");
4736
+ for (const plugin of index.plugins) {
4737
+ logger.important(chalk.bold(plugin.name));
4738
+ for (const contract of plugin.contracts) {
4739
+ const typeIcon = contract.type === "static" ? "static" : "dynamic";
4740
+ logger.important(` [${typeIcon}] ${contract.name}`);
4741
+ }
4742
+ logger.important("");
4743
+ }
4744
+ if (index.plugins.length === 0) {
4745
+ logger.important(chalk.gray("No contracts found."));
4746
+ }
4747
+ }
4748
+ async function runAction(actionRef, options, projectRoot, initializeServices) {
4749
+ let viteServer;
4750
+ try {
4751
+ const slashIndex = actionRef.indexOf("/");
4752
+ if (slashIndex === -1) {
4753
+ getLogger().error(
4754
+ chalk.red("❌ Invalid action reference. Use format: <plugin>/<action>")
4755
+ );
4756
+ process.exit(1);
4757
+ }
4758
+ const actionExport = actionRef.substring(slashIndex + 1);
4759
+ const input = options.input ? JSON.parse(options.input) : {};
4760
+ if (options.verbose) {
4761
+ getLogger().info("Starting Vite for TypeScript support...");
4762
+ }
4763
+ viteServer = await createViteForCli({ projectRoot });
4764
+ await initializeServices(projectRoot, viteServer);
4765
+ const { discoverAndRegisterActions, discoverAllPluginActions, ActionRegistry } = await import("@jay-framework/stack-server-runtime");
4766
+ const registry = new ActionRegistry();
4767
+ await discoverAndRegisterActions({
4768
+ projectRoot,
4769
+ registry,
4770
+ verbose: options.verbose,
4771
+ viteServer
4772
+ });
4773
+ await discoverAllPluginActions({
4774
+ projectRoot,
4775
+ registry,
4776
+ verbose: options.verbose,
4777
+ viteServer
4778
+ });
4779
+ const allNames = registry.getNames();
4780
+ const matchedName = allNames.find((name) => name === actionExport) || allNames.find((name) => name.endsWith("." + actionExport)) || allNames.find((name) => name === actionRef);
4781
+ if (!matchedName) {
4782
+ getLogger().error(chalk.red(`❌ Action "${actionExport}" not found.`));
4783
+ if (allNames.length > 0) {
4784
+ getLogger().error(` Available actions: ${allNames.join(", ")}`);
4785
+ } else {
4786
+ getLogger().error(" No actions registered. Does the plugin have actions?");
4787
+ }
4788
+ process.exit(1);
4789
+ }
4790
+ if (options.verbose) {
4791
+ getLogger().info(`Executing action: ${matchedName}`);
4792
+ getLogger().info(`Input: ${JSON.stringify(input)}`);
4793
+ }
4794
+ const result = await registry.execute(matchedName, input);
4795
+ if (result.success) {
4796
+ if (options.yaml) {
4352
4797
  getLogger().important(YAML.stringify(result.data));
4353
4798
  } else {
4354
4799
  getLogger().important(JSON.stringify(result.data, null, 2));
@@ -4584,65 +5029,184 @@ async function runSetup(pluginFilter, options, projectRoot, initializeServices)
4584
5029
  }
4585
5030
  }
4586
5031
  }
4587
- const program = new Command();
4588
- program.name("jay-stack").description("Jay Stack CLI - Development server and plugin validation").version("0.9.0");
4589
- 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) => {
5032
+ async function runCommand(commandRef, rawArgs, options, projectRoot, initializeServices) {
5033
+ let viteServer;
4590
5034
  try {
4591
- const logLevel = options.quiet ? "silent" : options.verbose ? "verbose" : "info";
4592
- setDevLogger(createDevLogger(logLevel));
4593
- const testMode = options.testMode || options.timeout !== void 0;
4594
- await startDevServer({
4595
- projectPath: path2 || process.cwd(),
4596
- testMode,
4597
- timeout: options.timeout,
4598
- logLevel
5035
+ const {
5036
+ discoverPluginCommands,
5037
+ commandSchemaToFlags,
5038
+ parseInputFromFlags,
5039
+ executePluginCommand
5040
+ } = await import("@jay-framework/stack-server-runtime");
5041
+ const commands = await discoverPluginCommands({
5042
+ projectRoot,
5043
+ verbose: options.verbose
4599
5044
  });
4600
- } catch (error) {
4601
- getLogger().error(chalk.red("Error starting dev server:") + " " + error.message);
4602
- process.exit(1);
4603
- }
4604
- });
4605
- program.command("build [path]").description("Build production artifacts").option("--version <n>", "Build version number", "1").option("--no-minify", "Disable minification (useful for debugging)").option("-v, --verbose", "Enable verbose logging output").action(async (projectPath, options) => {
4606
- try {
4607
- const logLevel = options.verbose ? "verbose" : "info";
4608
- setDevLogger(createDevLogger(logLevel));
4609
- const resolvedPath = path$1.resolve(projectPath || process.cwd());
4610
- const jayConfigPath = path$1.join(resolvedPath, ".jay");
4611
- let pagesBase = "./src/pages";
4612
- try {
4613
- const jayConfig = YAML.parse(await fs$1.readFile(jayConfigPath, "utf-8"));
4614
- pagesBase = jayConfig?.devServer?.pagesBase || pagesBase;
4615
- } catch {
5045
+ if (options.list || !commandRef) {
5046
+ printCommandList(commands);
5047
+ return;
5048
+ }
5049
+ const slashIndex = commandRef.indexOf("/");
5050
+ if (slashIndex === -1) {
5051
+ getLogger().error(
5052
+ chalk.red("Invalid command reference. Use format: <plugin>/<command>")
5053
+ );
5054
+ getLogger().error(chalk.gray("Example: jay-stack run media/upload-public"));
5055
+ process.exit(1);
5056
+ }
5057
+ const pluginName = commandRef.substring(0, slashIndex);
5058
+ const commandName = commandRef.substring(slashIndex + 1);
5059
+ const command = commands.find(
5060
+ (c) => (c.pluginName === pluginName || c.packageName === pluginName) && c.commandName === commandName
5061
+ );
5062
+ if (!command) {
5063
+ getLogger().error(chalk.red(`Command "${commandRef}" not found.`));
5064
+ const pluginCommands = commands.filter(
5065
+ (c) => c.pluginName === pluginName || c.packageName === pluginName
5066
+ );
5067
+ if (pluginCommands.length > 0) {
5068
+ getLogger().error(`Available commands for ${pluginName}:`);
5069
+ for (const c of pluginCommands) {
5070
+ getLogger().error(
5071
+ ` ${c.commandName}${c.metadata?.description ? " " + c.metadata.description : ""}`
5072
+ );
5073
+ }
5074
+ } else if (commands.length > 0) {
5075
+ getLogger().error("Available plugins with commands:");
5076
+ const plugins = [...new Set(commands.map((c) => c.pluginName))];
5077
+ for (const p of plugins) {
5078
+ getLogger().error(` ${p}`);
5079
+ }
5080
+ } else {
5081
+ getLogger().error("No plugins with CLI commands found.");
5082
+ }
5083
+ process.exit(1);
5084
+ }
5085
+ let input = {};
5086
+ if (command.metadata?.inputSchema) {
5087
+ const flagDefs = commandSchemaToFlags(command.metadata.inputSchema);
5088
+ const rawOptions = parseRawFlags(rawArgs, flagDefs);
5089
+ input = parseInputFromFlags(rawOptions, command.metadata.inputSchema);
4616
5090
  }
4617
- const { buildVersion } = await import("@jay-framework/production-server");
4618
- await buildVersion({
4619
- version: parseInt(options.version, 10),
4620
- projectRoot: resolvedPath,
4621
- pagesRoot: path$1.resolve(resolvedPath, pagesBase),
4622
- buildRoot: path$1.join(resolvedPath, "build"),
4623
- publicBasePath: "/",
4624
- concurrency: 4,
4625
- tsConfigFilePath: path$1.join(resolvedPath, "tsconfig.json"),
4626
- minify: options.minify
5091
+ if (options.verbose) {
5092
+ getLogger().info("Starting Vite for TypeScript support...");
5093
+ }
5094
+ viteServer = await createViteForCli({ projectRoot });
5095
+ await initializeServices(projectRoot, viteServer);
5096
+ const { registerService } = await import("@jay-framework/stack-server-runtime");
5097
+ const { CONSOLE_CONTEXT } = await import("@jay-framework/fullstack-component");
5098
+ const jayConfig = loadConfig();
5099
+ const publicFolder = path$1.resolve(
5100
+ projectRoot,
5101
+ jayConfig.devServer?.publicFolder || "public"
5102
+ );
5103
+ const version = await resolveVersionFromPackageJson(projectRoot);
5104
+ const buildRoot = path$1.resolve(projectRoot, `build/v${version}`);
5105
+ registerService(CONSOLE_CONTEXT, {
5106
+ projectRoot,
5107
+ publicFolder,
5108
+ build: {
5109
+ frontend: path$1.join(buildRoot, "frontend"),
5110
+ backend: path$1.join(buildRoot, "backend")
5111
+ },
5112
+ verbose: options.verbose ?? false,
5113
+ log: (msg) => getLogger().important(msg),
5114
+ warn: (msg) => getLogger().warn(chalk.yellow(msg)),
5115
+ error: (msg) => getLogger().error(chalk.red(msg))
4627
5116
  });
5117
+ if (options.verbose) {
5118
+ getLogger().info(`Executing ${command.pluginName}/${command.commandName}...`);
5119
+ }
5120
+ const result = await executePluginCommand(command, input, viteServer);
5121
+ if (!result.success) {
5122
+ process.exit(1);
5123
+ }
4628
5124
  } catch (error) {
4629
- getLogger().error(chalk.red("Build failed:") + " " + error.message);
4630
- if (error.stack)
5125
+ getLogger().error(chalk.red("Command failed:") + " " + error.message);
5126
+ if (options.verbose) {
4631
5127
  getLogger().error(error.stack);
5128
+ }
4632
5129
  process.exit(1);
5130
+ } finally {
5131
+ if (viteServer) {
5132
+ await viteServer.close();
5133
+ }
4633
5134
  }
4634
- });
4635
- program.command("serve [path]").description("Start production server").option("--version <n>", "Build version to serve", "1").option("--port <n>", "Port number", "3000").action(async (projectPath, options) => {
4636
- try {
4637
- setDevLogger(createDevLogger("info"));
4638
- const resolvedPath = path$1.resolve(projectPath || process.cwd());
4639
- const { startMainServer } = await import("@jay-framework/production-server");
4640
- await startMainServer({
4641
- buildRoot: path$1.join(resolvedPath, "build"),
4642
- version: parseInt(options.version, 10),
4643
- port: parseInt(options.port, 10),
4644
- publicBasePath: "/"
4645
- });
5135
+ }
5136
+ function printCommandList(commands) {
5137
+ const logger = getLogger();
5138
+ if (commands.length === 0) {
5139
+ logger.important(chalk.gray("No plugins with CLI commands found."));
5140
+ return;
5141
+ }
5142
+ logger.important("\nAvailable plugin commands:\n");
5143
+ const byPlugin = /* @__PURE__ */ new Map();
5144
+ for (const cmd of commands) {
5145
+ if (!byPlugin.has(cmd.pluginName))
5146
+ byPlugin.set(cmd.pluginName, []);
5147
+ byPlugin.get(cmd.pluginName).push(cmd);
5148
+ }
5149
+ for (const [pluginName, cmds] of byPlugin) {
5150
+ logger.important(chalk.bold(` ${pluginName}`));
5151
+ for (const cmd of cmds) {
5152
+ const desc = cmd.metadata?.description ? ` ${cmd.metadata.description}` : "";
5153
+ logger.important(` ${cmd.commandName}${desc}`);
5154
+ }
5155
+ logger.important("");
5156
+ }
5157
+ }
5158
+ function parseRawFlags(args, flagDefs) {
5159
+ const result = {};
5160
+ const booleanFlags = new Set(
5161
+ flagDefs.filter((f) => f.type === "boolean").map((f) => {
5162
+ const match = f.flag.match(/^--(\S+)/);
5163
+ return match ? match[1] : "";
5164
+ })
5165
+ );
5166
+ let i = 0;
5167
+ while (i < args.length) {
5168
+ const arg = args[i];
5169
+ if (arg.startsWith("--")) {
5170
+ const name = arg.slice(2);
5171
+ if (booleanFlags.has(name)) {
5172
+ result[name] = true;
5173
+ i++;
5174
+ } else if (i + 1 < args.length && !args[i + 1].startsWith("--")) {
5175
+ result[name] = args[i + 1];
5176
+ i += 2;
5177
+ } else {
5178
+ result[name] = true;
5179
+ i++;
5180
+ }
5181
+ } else {
5182
+ i++;
5183
+ }
5184
+ }
5185
+ return result;
5186
+ }
5187
+ const program = new Command();
5188
+ program.name("jay-stack").description("Jay Stack CLI - Development server and plugin validation").version("0.9.0");
5189
+ program.command("dev").description("Start the Jay Stack development server").option("-p, --path <path>", "Project root (default: cwd)").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 (options) => {
5190
+ try {
5191
+ await runDev(options.path, options);
5192
+ } catch (error) {
5193
+ getLogger().error(chalk.red("Error starting dev server:") + " " + error.message);
5194
+ process.exit(1);
5195
+ }
5196
+ });
5197
+ program.command("build").description("Build production artifacts").option("-p, --path <path>", "Project root (default: cwd)").option("--version <n>", "Build version number (default: from package.json)").option("--no-minify", "Disable minification (useful for debugging)").option("-v, --verbose", "Enable verbose logging output").action(async (options) => {
5198
+ try {
5199
+ await runBuild(options.path, options);
5200
+ } catch (error) {
5201
+ getLogger().error(chalk.red("Build failed:") + " " + error.message);
5202
+ if (error.stack)
5203
+ getLogger().error(error.stack);
5204
+ process.exit(1);
5205
+ }
5206
+ });
5207
+ program.command("serve").description("Start production server").option("-p, --path <path>", "Project root (default: cwd)").option("--version <n>", "Build version to serve (default: from package.json)").option("--port <n>", "Port number", "3000").option("--role <role>", "Server role: main (default) or renderer", "main").option("--static-base-url <url>", "Base URL for browser-facing assets (default: /)").option("--no-serve-static", "Disable serving static files from frontend/").option("--test-mode", "Enable test endpoints (/_jay/health, /_jay/shutdown)").option("-v, --verbose", "Enable verbose logging output").action(async (options) => {
5208
+ try {
5209
+ await runServe(options.path, options);
4646
5210
  } catch (error) {
4647
5211
  getLogger().error(chalk.red("Server failed:") + " " + error.message);
4648
5212
  if (error.stack)
@@ -4650,17 +5214,31 @@ program.command("serve [path]").description("Start production server").option("-
4650
5214
  process.exit(1);
4651
5215
  }
4652
5216
  });
4653
- 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) => {
5217
+ program.command("rebuild").description("Rebuild instances by contract, route, or URL").option("--contract <name>", "Rebuild by contract name (e.g., product-page)").option("--route <pattern>", "Rebuild by route pattern (e.g., /products/[slug])").option("--url <url>", "Rebuild by URL (e.g., /products/blue-widget)").option("--params <json>", `JSON params to rebuild specific instance (e.g., '{"slug":"x"}')`).option("--version <n>", "Build version (default: from package.json)").option("-p, --path <path>", "Project root (default: cwd)").option("-v, --verbose", "Enable verbose logging output").action(async (options) => {
4654
5218
  try {
4655
- const result = await validateJayFiles({
4656
- path: scanPath,
4657
- verbose: options.verbose,
4658
- json: options.json
4659
- });
4660
- printJayValidationResult(result, options);
4661
- if (!result.valid) {
4662
- process.exit(1);
4663
- }
5219
+ await runRebuild(options.path, options);
5220
+ } catch (error) {
5221
+ getLogger().error(chalk.red("Rebuild failed:") + " " + error.message);
5222
+ if (error.stack)
5223
+ getLogger().error(error.stack);
5224
+ process.exit(1);
5225
+ }
5226
+ });
5227
+ program.command("cleanup").description("Delete orphaned files from previous rebuilds").option("--version <n>", "Build version (default: from package.json)").option("-p, --path <path>", "Project root (default: cwd)").action(async (options) => {
5228
+ try {
5229
+ const { initLogger: initLogger2, resolveProductionContext: resolveProductionContext2 } = await Promise.resolve().then(() => runProduction);
5230
+ initLogger2();
5231
+ const ctx = await resolveProductionContext2(options.path, options.version);
5232
+ const { cleanupOrphanedFiles } = await import("@jay-framework/production-server");
5233
+ await cleanupOrphanedFiles(ctx.buildRoot, ctx.version);
5234
+ } catch (error) {
5235
+ getLogger().error(chalk.red("Cleanup failed:") + " " + error.message);
5236
+ process.exit(1);
5237
+ }
5238
+ });
5239
+ program.command("validate").description("Validate all .jay-html and .jay-contract files").option("-p, --path <path>", "Scan root (default: cwd)").option("-v, --verbose", "Show per-file validation status").option("--json", "Output results as JSON").action(async (options) => {
5240
+ try {
5241
+ await runValidate(options.path, options);
4664
5242
  } catch (error) {
4665
5243
  if (options.json) {
4666
5244
  getLogger().important(
@@ -4672,342 +5250,40 @@ program.command("validate [path]").description("Validate all .jay-html and .jay-
4672
5250
  process.exit(1);
4673
5251
  }
4674
5252
  });
4675
- program.command("validate-plugin [path]").description("Validate a Jay Stack plugin package").option("--local", "Validate local plugins in src/plugins/").option("-v, --verbose", "Show detailed validation output").option("--strict", "Treat warnings as errors (for CI)").option("--generate-types", "Generate .d.ts files for contracts").action(async (pluginPath, options) => {
5253
+ program.command("validate-plugin").description("Validate a Jay Stack plugin package").option("-p, --path <path>", "Plugin root (default: cwd)").option("--local", "Validate local plugins in src/plugins/").option("-v, --verbose", "Show detailed validation output").option("--strict", "Treat warnings as errors (for CI)").option("--generate-types", "Generate .d.ts files for contracts").action(async (options) => {
4676
5254
  try {
4677
- const result = await validatePlugin({
4678
- pluginPath: pluginPath || process.cwd(),
4679
- local: options.local,
4680
- verbose: options.verbose,
4681
- strict: options.strict,
4682
- generateTypes: options.generateTypes
4683
- });
4684
- printValidationResult(result, options.verbose);
4685
- if (!result.valid || options.strict && result.warnings.length > 0) {
4686
- process.exit(1);
4687
- }
5255
+ await runValidatePlugin(options.path, options);
4688
5256
  } catch (error) {
4689
5257
  getLogger().error(chalk.red("Validation error:") + " " + error.message);
4690
5258
  process.exit(1);
4691
5259
  }
4692
5260
  });
4693
- const ALL_ROLES = ["designer", "developer", "plugin"];
4694
- async function ensureAgentKitDocs(projectRoot, _force, mode) {
4695
- const agentKitDir = path$1.join(projectRoot, "agent-kit");
4696
- const thisDir = path$1.dirname(fileURLToPath(import.meta.url));
4697
- const templateDir = path$1.resolve(thisDir, "..", "agent-kit-template");
4698
- const roles = mode && ALL_ROLES.includes(mode) ? [mode] : ALL_ROLES;
4699
- for (const role of roles) {
4700
- const roleTemplateDir = path$1.join(templateDir, role);
4701
- const roleOutputDir = path$1.join(agentKitDir, role);
4702
- let files;
4703
- try {
4704
- files = (await fs$1.readdir(roleTemplateDir)).filter((f) => f.endsWith(".md"));
4705
- } catch {
4706
- continue;
4707
- }
4708
- await fs$1.mkdir(roleOutputDir, { recursive: true });
4709
- for (const filename of files) {
4710
- await fs$1.copyFile(
4711
- path$1.join(roleTemplateDir, filename),
4712
- path$1.join(roleOutputDir, filename)
4713
- );
4714
- getLogger().info(chalk.gray(` Created agent-kit/${role}/${filename}`));
4715
- }
4716
- }
4717
- }
4718
- async function mergePluginAgentKitGuides(projectRoot, mode) {
4719
- const plugins = await scanPlugins$1({ projectRoot });
4720
- const agentKitDir = path$1.join(projectRoot, "agent-kit");
4721
- const roles = mode && ALL_ROLES.includes(mode) ? [mode] : ALL_ROLES;
4722
- const copiedPerRole = /* @__PURE__ */ new Map();
4723
- for (const [, plugin] of plugins) {
4724
- const pluginAgentKitDir = path$1.join(plugin.pluginPath, "agent-kit");
4725
- if (!fsSync.existsSync(pluginAgentKitDir))
4726
- continue;
4727
- for (const role of roles) {
4728
- const roleSourceDir = path$1.join(pluginAgentKitDir, role);
4729
- let files;
4730
- try {
4731
- files = (await fs$1.readdir(roleSourceDir)).filter(
4732
- (f) => f.endsWith(".md") && f !== "INSTRUCTIONS.md"
4733
- );
4734
- } catch {
4735
- continue;
4736
- }
4737
- if (files.length === 0)
4738
- continue;
4739
- const roleOutputDir = path$1.join(agentKitDir, role);
4740
- await fs$1.mkdir(roleOutputDir, { recursive: true });
4741
- for (const filename of files) {
4742
- const sourcePath = path$1.join(roleSourceDir, filename);
4743
- await fs$1.copyFile(sourcePath, path$1.join(roleOutputDir, filename));
4744
- let description = "";
4745
- try {
4746
- const content = await fs$1.readFile(sourcePath, "utf-8");
4747
- const lines = content.split("\n");
4748
- let pastHeading = false;
4749
- for (const line of lines) {
4750
- if (line.startsWith("# ")) {
4751
- pastHeading = true;
4752
- continue;
4753
- }
4754
- if (pastHeading && line.trim()) {
4755
- description = line.trim();
4756
- break;
4757
- }
4758
- }
4759
- } catch {
4760
- }
4761
- if (!copiedPerRole.has(role))
4762
- copiedPerRole.set(role, []);
4763
- copiedPerRole.get(role).push({ filename, pluginName: plugin.name, description });
4764
- getLogger().info(
4765
- chalk.gray(
4766
- ` Copied agent-kit/${role}/${filename} from plugin "${plugin.name}"`
4767
- )
4768
- );
4769
- }
4770
- }
4771
- }
4772
- for (const [role, entries] of copiedPerRole) {
4773
- const instructionsPath = path$1.join(agentKitDir, role, "INSTRUCTIONS.md");
4774
- if (!fsSync.existsSync(instructionsPath))
4775
- continue;
4776
- const lines = [
4777
- "",
4778
- "## Plugin-Contributed Guides",
4779
- "",
4780
- "| File | Plugin | Description |",
4781
- "| --- | --- | --- |"
4782
- ];
4783
- for (const { filename, pluginName, description } of entries) {
4784
- lines.push(`| [${filename}](${filename}) | ${pluginName} | ${description} |`);
4785
- }
4786
- lines.push("");
4787
- await fs$1.appendFile(instructionsPath, lines.join("\n"));
4788
- }
4789
- }
4790
- async function generatePluginReferences(projectRoot, options, initErrors, viteServer) {
4791
- const { discoverPluginsWithReferences, executePluginReferences } = await import("@jay-framework/stack-server-runtime");
4792
- const plugins = await discoverPluginsWithReferences({
4793
- projectRoot,
4794
- verbose: options.verbose,
4795
- pluginFilter: options.plugin
4796
- });
4797
- if (plugins.length === 0)
4798
- return;
4799
- const logger = getLogger();
4800
- logger.important("");
4801
- logger.important(chalk.bold("📚 Generating plugin references..."));
4802
- for (const plugin of plugins) {
4803
- const pluginInitError = initErrors.get(plugin.name);
4804
- if (pluginInitError) {
4805
- logger.warn(
4806
- chalk.yellow(
4807
- ` ⚠️ ${plugin.name}: references skipped — init failed: ${pluginInitError.message}`
4808
- )
4809
- );
4810
- continue;
4811
- }
4812
- try {
4813
- const result = await executePluginReferences(plugin, {
4814
- projectRoot,
4815
- force: options.force ?? false,
4816
- viteServer,
4817
- verbose: options.verbose
4818
- });
4819
- if (result.referencesCreated.length > 0) {
4820
- logger.important(chalk.green(` ✅ ${plugin.name}:`));
4821
- for (const ref of result.referencesCreated) {
4822
- logger.important(chalk.gray(` ${ref}`));
4823
- }
4824
- if (result.message) {
4825
- logger.important(chalk.gray(` ${result.message}`));
4826
- }
4827
- }
4828
- } catch (error) {
4829
- logger.warn(
4830
- chalk.yellow(` ⚠️ ${plugin.name}: references skipped — ${error.message}`)
4831
- );
4832
- }
4833
- }
4834
- }
4835
- async function runMaterialize(projectRoot, options, defaultOutputRelative, keepViteAlive = false) {
4836
- const path2 = await import("node:path");
4837
- const outputDir = options.output ?? path2.join(projectRoot, defaultOutputRelative);
4838
- let viteServer;
4839
- let initErrors = /* @__PURE__ */ new Map();
4840
- try {
4841
- if (options.list) {
4842
- const index = await listContracts({
4843
- projectRoot,
4844
- dynamicOnly: options.dynamicOnly,
4845
- pluginFilter: options.plugin
4846
- });
4847
- if (options.yaml) {
4848
- getLogger().important(YAML.stringify(index));
4849
- } else {
4850
- printContractList(index);
4851
- }
4852
- return { initErrors };
4853
- }
4854
- if (options.verbose) {
4855
- getLogger().info("Starting Vite for TypeScript support...");
4856
- }
4857
- viteServer = await createViteForCli({ projectRoot });
4858
- const { services, initErrors: errors } = await initializeServicesForCli(
4859
- projectRoot,
4860
- viteServer
4861
- );
4862
- initErrors = errors;
4863
- const result = await materializeContracts(
4864
- {
4865
- projectRoot,
4866
- outputDir,
4867
- force: options.force,
4868
- dynamicOnly: options.dynamicOnly,
4869
- pluginFilter: options.plugin,
4870
- verbose: options.verbose,
4871
- viteServer
4872
- },
4873
- services
4874
- );
4875
- if (options.yaml) {
4876
- getLogger().important(YAML.stringify(result.pluginsIndex));
4877
- } else {
4878
- const totalContracts = result.pluginsIndex.plugins.reduce(
4879
- (sum, p) => sum + p.contracts.length,
4880
- 0
4881
- );
4882
- getLogger().important(chalk.green(`
4883
- ✅ Materialized ${totalContracts} contracts`));
4884
- getLogger().important(` Static: ${result.staticCount}`);
4885
- getLogger().important(` Dynamic: ${result.dynamicCount}`);
4886
- getLogger().important(` Output: ${result.outputDir}`);
4887
- }
4888
- return { initErrors, viteServer: keepViteAlive ? viteServer : void 0 };
4889
- } catch (error) {
4890
- getLogger().error(chalk.red("❌ Failed to materialize contracts:") + " " + error.message);
4891
- if (options.verbose) {
4892
- getLogger().error(error.stack);
4893
- }
4894
- process.exit(1);
4895
- } finally {
4896
- if (viteServer && !keepViteAlive) {
4897
- await viteServer.close();
4898
- }
4899
- }
4900
- return { initErrors };
4901
- }
4902
- program.command("setup [plugin]").description(
4903
- "Run plugin setup: create config templates, validate credentials, generate reference data"
4904
- ).option("--force", "Force re-run (overwrite config templates and regenerate references)").option("-v, --verbose", "Show detailed output").action(async (plugin, options) => {
5261
+ program.command("setup [plugin]").description("Run plugin setup: config templates, credential validation, reference data").option("--force", "Force re-run (overwrite config templates and regenerate references)").option("-v, --verbose", "Show detailed output").action(async (plugin, options) => {
4905
5262
  await runSetup(plugin, options, process.cwd(), initializeServicesForCli);
4906
5263
  });
4907
- program.command("agent-kit").description(
4908
- "Prepare the agent kit: materialize contracts, generate references, and write docs to agent-kit/"
4909
- ).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(
5264
+ program.command("agent-kit").description("Prepare agent kit: materialize contracts, generate references, write docs").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(
4910
5265
  "-m, --mode <role>",
4911
5266
  "Generate guides for a specific role: designer, developer, or plugin (default: all)"
4912
5267
  ).option("-v, --verbose", "Show detailed output").action(async (options) => {
4913
- const projectRoot = process.cwd();
4914
- const { initErrors, viteServer } = await runMaterialize(
4915
- projectRoot,
4916
- options,
4917
- "agent-kit/materialized-contracts",
4918
- /* keepViteAlive */
4919
- true
4920
- );
4921
- try {
4922
- if (!options.list) {
4923
- await ensureAgentKitDocs(projectRoot, options.force, options.mode);
4924
- await mergePluginAgentKitGuides(projectRoot, options.mode);
4925
- if (options.references !== false) {
4926
- await generatePluginReferences(projectRoot, options, initErrors, viteServer);
4927
- }
4928
- }
4929
- } finally {
4930
- if (viteServer) {
4931
- await viteServer.close();
4932
- }
4933
- }
5268
+ await runAgentKit(options);
4934
5269
  });
4935
- program.command("action <plugin/action>").description(
4936
- `Run a plugin action (e.g., jay-stack action wix-stores/searchProducts --input '{"query":""}')`
4937
- ).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) => {
5270
+ program.command("action <plugin/action>").description("Run a plugin action (e.g., jay-stack action wix-stores/searchProducts)").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) => {
4938
5271
  await runAction(actionRef, options, process.cwd(), initializeServicesForCli);
4939
5272
  });
4940
- program.command("params <plugin/contract>").description(
4941
- "Discover load param values for a contract. Streams results as NDJSON (one JSON object per line) or YAML."
4942
- ).option("--yaml", "Output as YAML instead of NDJSON").option("-v, --verbose", "Show detailed output").action(async (contractRef, options) => {
5273
+ program.command("run [plugin/command]").description("Run a plugin CLI command (e.g., jay-stack run media/upload-public)").option("--list", "List all available plugin commands").option("-v, --verbose", "Show detailed output").allowUnknownOption().allowExcessArguments().action(async (commandRef, options, command) => {
5274
+ const rawArgs = command.parent?.rawArgs.slice(3) ?? [];
5275
+ const extraArgs = rawArgs.filter(
5276
+ (a) => a !== commandRef && a !== "-v" && a !== "--verbose" && a !== "--list"
5277
+ );
5278
+ await runCommand(commandRef, extraArgs, options, process.cwd(), initializeServicesForCli);
5279
+ });
5280
+ program.command("params <plugin/contract>").description("Discover load param values for a contract (NDJSON or YAML)").option("--yaml", "Output as YAML instead of NDJSON").option("-v, --verbose", "Show detailed output").action(async (contractRef, options) => {
4943
5281
  await runParams(contractRef, options, process.cwd(), initializeServicesForCli);
4944
5282
  });
4945
5283
  program.parse(process.argv);
4946
5284
  if (!process.argv.slice(2).length) {
4947
5285
  program.outputHelp();
4948
5286
  }
4949
- function printValidationResult(result, verbose) {
4950
- const logger = getLogger();
4951
- if (result.valid && result.warnings.length === 0) {
4952
- logger.important(chalk.green("✅ Plugin validation successful!\n"));
4953
- if (verbose) {
4954
- logger.important("Plugin: " + result.pluginName);
4955
- logger.important(" ✅ plugin.yaml valid");
4956
- logger.important(` ✅ ${result.contractsChecked} contracts validated`);
4957
- if (result.typesGenerated) {
4958
- logger.important(` ✅ ${result.typesGenerated} type definitions generated`);
4959
- }
4960
- logger.important(` ✅ ${result.componentsChecked} components validated`);
4961
- if (result.packageJsonChecked) {
4962
- logger.important(" ✅ package.json valid");
4963
- }
4964
- logger.important("\nNo errors found.");
4965
- }
4966
- } else if (result.valid && result.warnings.length > 0) {
4967
- logger.important(chalk.yellow("⚠️ Plugin validation passed with warnings\n"));
4968
- logger.important("Warnings:");
4969
- result.warnings.forEach((warning) => {
4970
- logger.important(chalk.yellow(` ⚠️ ${warning.message}`));
4971
- if (warning.location) {
4972
- logger.important(chalk.gray(` Location: ${warning.location}`));
4973
- }
4974
- if (warning.suggestion) {
4975
- logger.important(chalk.gray(` → ${warning.suggestion}`));
4976
- }
4977
- logger.important("");
4978
- });
4979
- logger.important(chalk.gray("Use --strict to treat warnings as errors."));
4980
- } else {
4981
- logger.important(chalk.red("❌ Plugin validation failed\n"));
4982
- logger.important("Errors:");
4983
- result.errors.forEach((error) => {
4984
- logger.important(chalk.red(` ❌ ${error.message}`));
4985
- if (error.location) {
4986
- logger.important(chalk.gray(` Location: ${error.location}`));
4987
- }
4988
- if (error.suggestion) {
4989
- logger.important(chalk.gray(` → ${error.suggestion}`));
4990
- }
4991
- logger.important("");
4992
- });
4993
- logger.important(chalk.red(`${result.errors.length} errors found.`));
4994
- }
4995
- }
4996
- function printContractList(index) {
4997
- const logger = getLogger();
4998
- logger.important("\nAvailable Contracts:\n");
4999
- for (const plugin of index.plugins) {
5000
- logger.important(chalk.bold(`📦 ${plugin.name}`));
5001
- for (const contract of plugin.contracts) {
5002
- const typeIcon = contract.type === "static" ? "📄" : "⚡";
5003
- logger.important(` ${typeIcon} ${contract.name}`);
5004
- }
5005
- logger.important("");
5006
- }
5007
- if (index.plugins.length === 0) {
5008
- logger.important(chalk.gray("No contracts found."));
5009
- }
5010
- }
5011
5287
  export {
5012
5288
  createEditorHandlers,
5013
5289
  getConfigWithDefaults,