@jay-framework/stack-server-runtime 0.11.0 → 0.13.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.
Files changed (3) hide show
  1. package/dist/index.d.ts +430 -30
  2. package/dist/index.js +968 -100
  3. package/package.json +13 -11
package/dist/index.js CHANGED
@@ -8,7 +8,7 @@ import { notFound, partialRender, phaseOutput, isJayAction } from "@jay-framewor
8
8
  import fs$1 from "node:fs/promises";
9
9
  import * as path from "node:path";
10
10
  import path__default from "node:path";
11
- import { parseJayFile, JAY_IMPORT_RESOLVER } from "@jay-framework/compiler-jay-html";
11
+ import { parseJayFile, JAY_IMPORT_RESOLVER, parseAction } from "@jay-framework/compiler-jay-html";
12
12
  import { createRequire } from "module";
13
13
  import * as fs from "node:fs";
14
14
  import { createRequire as createRequire$1 } from "node:module";
@@ -17,6 +17,7 @@ import "js-beautify";
17
17
  import fs$2 from "fs";
18
18
  import path$1 from "path";
19
19
  import YAML from "yaml";
20
+ import { getLogger } from "@jay-framework/logger";
20
21
  import crypto from "node:crypto";
21
22
  const serviceRegistry = /* @__PURE__ */ new Map();
22
23
  function registerService(marker, service) {
@@ -40,9 +41,13 @@ function hasService(marker) {
40
41
  function clearServiceRegistry() {
41
42
  serviceRegistry.clear();
42
43
  }
44
+ function getServiceRegistry() {
45
+ return serviceRegistry;
46
+ }
43
47
  function resolveServices(serviceMarkers) {
44
48
  return serviceMarkers.map((marker) => getService(marker));
45
49
  }
50
+ globalThis.__JAY_SERVICE_RESOLVER__ = resolveServices;
46
51
  const initCallbacks = [];
47
52
  const shutdownCallbacks = [];
48
53
  function onInit(callback) {
@@ -94,10 +99,17 @@ class DevSlowlyChangingPhase {
94
99
  }
95
100
  async runSlowlyForPage(pageParams, pageProps, parts) {
96
101
  for (const part of parts) {
97
- const { compDefinition } = part;
102
+ const { compDefinition, contractInfo } = part;
98
103
  if (compDefinition.loadParams) {
99
104
  const services = resolveServices(compDefinition.services);
100
- const compParams = compDefinition.loadParams(services);
105
+ const loadParamsArgs = contractInfo ? [
106
+ ...services,
107
+ {
108
+ contractName: contractInfo.contractName,
109
+ metadata: contractInfo.metadata
110
+ }
111
+ ] : services;
112
+ const compParams = compDefinition.loadParams(loadParamsArgs);
101
113
  if (!await findMatchingParams(pageParams, compParams))
102
114
  return notFound();
103
115
  }
@@ -105,11 +117,19 @@ class DevSlowlyChangingPhase {
105
117
  let slowlyViewState = {};
106
118
  let carryForward = {};
107
119
  for (const part of parts) {
108
- const { compDefinition, key } = part;
120
+ const { compDefinition, key, contractInfo } = part;
109
121
  if (compDefinition.slowlyRender) {
110
122
  const services = resolveServices(compDefinition.services);
123
+ const partProps = {
124
+ ...pageProps,
125
+ ...pageParams,
126
+ ...contractInfo && {
127
+ contractName: contractInfo.contractName,
128
+ metadata: contractInfo.metadata
129
+ }
130
+ };
111
131
  const slowlyRenderedPart = await compDefinition.slowlyRender(
112
- { ...pageProps, ...pageParams },
132
+ partProps,
113
133
  ...services
114
134
  );
115
135
  if (slowlyRenderedPart.kind === "PhaseOutput") {
@@ -136,12 +156,20 @@ async function renderFastChangingData(pageParams, pageProps, carryForward, parts
136
156
  let fastViewState = {};
137
157
  let fastCarryForward = {};
138
158
  for (const part of parts) {
139
- const { compDefinition, key } = part;
159
+ const { compDefinition, key, contractInfo } = part;
140
160
  if (compDefinition.fastRender) {
141
161
  const partSlowlyCarryForward = key ? carryForward[key] : carryForward;
142
162
  const services = resolveServices(compDefinition.services);
163
+ const partProps = {
164
+ ...pageProps,
165
+ ...pageParams,
166
+ ...contractInfo && {
167
+ contractName: contractInfo.contractName,
168
+ metadata: contractInfo.metadata
169
+ }
170
+ };
143
171
  const fastRenderedPart = await compDefinition.fastRender(
144
- { ...pageProps, ...pageParams },
172
+ partProps,
145
173
  partSlowlyCarryForward,
146
174
  ...services
147
175
  );
@@ -234,13 +262,13 @@ ${clientInitExecution}
234
262
  const target = document.getElementById('target');
235
263
  const pageComp = makeCompositeJayComponent(render, viewState, fastCarryForward, ${compositeParts}, trackByMap)
236
264
 
237
- const instance = pageComp({...viewState, ...fastCarryForward})
265
+ const instance = pageComp({/* placeholder for page props */})
238
266
  ${automationWrap}
239
267
  <\/script>
240
268
  </body>
241
269
  </html>`;
242
270
  }
243
- const require$2 = createRequire(import.meta.url);
271
+ const require$3 = createRequire(import.meta.url);
244
272
  async function loadPageParts(vite, route, pagesBase, projectBase, jayRollupConfig, options) {
245
273
  const exists = await fs$1.access(route.compPath, fs$1.constants.F_OK).then(() => true).catch(() => false);
246
274
  const parts = [];
@@ -268,11 +296,12 @@ async function loadPageParts(vite, route, pagesBase, projectBase, jayRollupConfi
268
296
  );
269
297
  return jayHtmlWithValidations.mapAsync(async (jayHtml) => {
270
298
  const usedPackages = /* @__PURE__ */ new Set();
299
+ const headlessInstanceComponents = [];
271
300
  for await (const headlessImport of jayHtml.headlessImports) {
272
301
  const module = headlessImport.codeLink.module;
273
302
  const name = headlessImport.codeLink.names[0].name;
274
303
  const isLocalModule = module[0] === "." || module[0] === "/";
275
- const modulePath = isLocalModule ? path__default.resolve(dirName, module) : require$2.resolve(module, { paths: require$2.resolve.paths(dirName) });
304
+ const modulePath = isLocalModule ? path__default.resolve(dirName, module) : require$3.resolve(module, { paths: require$3.resolve.paths(dirName) });
276
305
  const compDefinition = (await vite.ssrLoadModule(modulePath))[name];
277
306
  const moduleImport = isLocalModule ? path__default.resolve(dirName, module) : module;
278
307
  const isNpmPackage = !isLocalModule;
@@ -281,23 +310,101 @@ async function loadPageParts(vite, route, pagesBase, projectBase, jayRollupConfi
281
310
  const packageName = module.startsWith("@") ? module.split("/").slice(0, 2).join("/") : module.split("/")[0];
282
311
  usedPackages.add(packageName);
283
312
  }
284
- const key = headlessImport.key;
285
- const part = {
286
- key,
287
- compDefinition,
288
- clientImport: `import {${name}} from '${clientModuleImport}'`,
289
- clientPart: `{comp: ${name}.comp, contextMarkers: ${name}.contexts || [], key: '${headlessImport.key}'}`
290
- };
291
- parts.push(part);
313
+ if (headlessImport.key) {
314
+ const key = headlessImport.key;
315
+ const part = {
316
+ key,
317
+ compDefinition,
318
+ clientImport: `import {${name}} from '${clientModuleImport}'`,
319
+ clientPart: `{comp: ${name}.comp, contextMarkers: ${name}.contexts || [], key: '${key}'}`,
320
+ // Include contract info for dynamic contract components
321
+ contractInfo: headlessImport.contract ? {
322
+ contractName: headlessImport.contract.name,
323
+ metadata: headlessImport.metadata
324
+ } : void 0
325
+ };
326
+ parts.push(part);
327
+ }
328
+ if (!headlessImport.key && headlessImport.contract) {
329
+ headlessInstanceComponents.push({
330
+ contractName: headlessImport.contractName,
331
+ compDefinition,
332
+ contract: headlessImport.contract
333
+ });
334
+ }
292
335
  }
336
+ const headlessContracts = jayHtml.headlessImports.filter((hi) => hi.contract !== void 0 && hi.key !== void 0).map((hi) => ({
337
+ key: hi.key,
338
+ contract: hi.contract,
339
+ contractPath: hi.contractPath
340
+ }));
293
341
  return {
294
342
  parts,
295
343
  serverTrackByMap: jayHtml.serverTrackByMap,
296
344
  clientTrackByMap: jayHtml.clientTrackByMap,
297
- usedPackages
345
+ usedPackages,
346
+ headlessContracts,
347
+ headlessInstanceComponents
298
348
  };
299
349
  });
300
350
  }
351
+ async function slowRenderInstances(discovered, headlessInstanceComponents) {
352
+ const componentByContractName = /* @__PURE__ */ new Map();
353
+ for (const comp of headlessInstanceComponents) {
354
+ componentByContractName.set(comp.contractName, comp);
355
+ }
356
+ const resolvedData = [];
357
+ const slowViewStates = {};
358
+ const discoveredForFast = [];
359
+ const carryForwards = {};
360
+ for (const instance of discovered) {
361
+ const comp = componentByContractName.get(instance.contractName);
362
+ if (!comp || !comp.compDefinition.slowlyRender) {
363
+ continue;
364
+ }
365
+ const services = resolveServices(comp.compDefinition.services);
366
+ const slowResult = await comp.compDefinition.slowlyRender(instance.props, ...services);
367
+ if (slowResult.kind === "PhaseOutput") {
368
+ const coordKey = instance.coordinate.join("/");
369
+ resolvedData.push({
370
+ coordinate: instance.coordinate,
371
+ contract: comp.contract,
372
+ slowViewState: slowResult.rendered
373
+ });
374
+ slowViewStates[coordKey] = slowResult.rendered;
375
+ carryForwards[coordKey] = slowResult.carryForward;
376
+ discoveredForFast.push({
377
+ contractName: instance.contractName,
378
+ props: instance.props,
379
+ coordinate: instance.coordinate
380
+ });
381
+ }
382
+ }
383
+ if (discoveredForFast.length === 0) {
384
+ return void 0;
385
+ }
386
+ return {
387
+ resolvedData,
388
+ slowViewStates,
389
+ instancePhaseData: { discovered: discoveredForFast, carryForwards }
390
+ };
391
+ }
392
+ function validateForEachInstances(forEachInstances, headlessInstanceComponents) {
393
+ const componentByContractName = /* @__PURE__ */ new Map();
394
+ for (const comp of headlessInstanceComponents) {
395
+ componentByContractName.set(comp.contractName, comp);
396
+ }
397
+ const validations = [];
398
+ for (const instance of forEachInstances) {
399
+ const comp = componentByContractName.get(instance.contractName);
400
+ if (comp?.compDefinition.slowlyRender) {
401
+ validations.push(
402
+ `<jay:${instance.contractName}> inside forEach has a slow rendering phase. Headless components with slow phases cannot be used inside forEach because forEach items are only known at request time, after slow rendering completes. Use slowForEach instead, or remove the slow phase from the component.`
403
+ );
404
+ }
405
+ }
406
+ return validations;
407
+ }
301
408
  class ActionRegistry {
302
409
  constructor() {
303
410
  __publicField(this, "actions", /* @__PURE__ */ new Map());
@@ -343,6 +450,34 @@ class ActionRegistry {
343
450
  getNames() {
344
451
  return Array.from(this.actions.keys());
345
452
  }
453
+ /**
454
+ * Attaches metadata from a .jay-action file to a registered action.
455
+ * Called during action discovery when a plugin declares action metadata.
456
+ *
457
+ * @param actionName - The action name
458
+ * @param metadata - Parsed ActionMetadata from .jay-action file
459
+ */
460
+ setMetadata(actionName, metadata) {
461
+ const action = this.actions.get(actionName);
462
+ if (action) {
463
+ action.metadata = metadata;
464
+ }
465
+ }
466
+ /**
467
+ * Gets all registered actions that have .jay-action metadata.
468
+ * These are the actions that should be exposed to AI agents.
469
+ *
470
+ * @returns Array of { actionName, metadata } for actions with metadata
471
+ */
472
+ getActionsWithMetadata() {
473
+ const result = [];
474
+ for (const action of this.actions.values()) {
475
+ if (action.metadata) {
476
+ result.push({ actionName: action.actionName, metadata: action.metadata });
477
+ }
478
+ }
479
+ return result;
480
+ }
346
481
  /**
347
482
  * Clears all registered actions.
348
483
  */
@@ -445,10 +580,6 @@ async function executeAction(actionName, input) {
445
580
  function getActionCacheHeaders(actionName) {
446
581
  return actionRegistry.getCacheHeaders(actionName);
447
582
  }
448
- async function runAction(action, input) {
449
- const services = resolveServices(action.services);
450
- return action.handler(input, ...services);
451
- }
452
583
  var __defProp2 = Object.defineProperty;
453
584
  var __defNormalProp2 = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
454
585
  var __publicField2 = (obj, key, value) => {
@@ -478,6 +609,69 @@ new JayObjectType("Error", {
478
609
  name: new JayAtomicType("string"),
479
610
  stack: new JayAtomicType("string")
480
611
  });
612
+ function isOptionalType(aType) {
613
+ return aType.kind === 13;
614
+ }
615
+ function isAtomicType(aType) {
616
+ return aType.kind === 0;
617
+ }
618
+ function isEnumType(aType) {
619
+ return aType.kind === 2;
620
+ }
621
+ function isImportedType(aType) {
622
+ return aType.kind === 4;
623
+ }
624
+ function isObjectType(aType) {
625
+ return aType.kind === 8;
626
+ }
627
+ function isArrayType(aType) {
628
+ return aType.kind === 9;
629
+ }
630
+ function jayTypeToJsonSchema(type) {
631
+ if (isOptionalType(type)) {
632
+ return jayTypeToJsonSchema(type.innerType);
633
+ }
634
+ if (isAtomicType(type)) {
635
+ const name = type.name.toLowerCase();
636
+ if (name === "string" || name === "number" || name === "boolean") {
637
+ return { type: name };
638
+ }
639
+ return { type: "string" };
640
+ }
641
+ if (isEnumType(type)) {
642
+ return { type: "string", enum: type.values };
643
+ }
644
+ if (isImportedType(type)) {
645
+ return { type: "object", description: `Contract: ${type.name}` };
646
+ }
647
+ if (isArrayType(type)) {
648
+ const itemSchema = jayTypeToJsonSchema(type.itemType);
649
+ if (itemSchema) {
650
+ return { type: "array", items: itemSchema };
651
+ }
652
+ return { type: "array" };
653
+ }
654
+ if (isObjectType(type)) {
655
+ const properties = {};
656
+ const required = [];
657
+ for (const [key, propType] of Object.entries(type.props)) {
658
+ const isOpt = isOptionalType(propType);
659
+ const schema = jayTypeToJsonSchema(propType);
660
+ if (schema) {
661
+ properties[key] = schema;
662
+ if (!isOpt) {
663
+ required.push(key);
664
+ }
665
+ }
666
+ }
667
+ return {
668
+ type: "object",
669
+ properties,
670
+ ...required.length > 0 && { required }
671
+ };
672
+ }
673
+ return null;
674
+ }
481
675
  var RuntimeMode = /* @__PURE__ */ ((RuntimeMode2) => {
482
676
  RuntimeMode2["MainTrusted"] = "mainTrusted";
483
677
  RuntimeMode2["MainSandbox"] = "mainSandbox";
@@ -527,6 +721,12 @@ const JAY_QUERY_PREFIX = "?jay-";
527
721
  }
528
722
  ];
529
723
  createRequire(import.meta.url);
724
+ function normalizeActionEntry(entry) {
725
+ if (typeof entry === "string") {
726
+ return { name: entry };
727
+ }
728
+ return { name: entry.name, action: entry.action };
729
+ }
530
730
  function loadPluginManifest(pluginDir) {
531
731
  const pluginYamlPath = path$1.join(pluginDir, "plugin.yaml");
532
732
  if (!fs$2.existsSync(pluginYamlPath)) {
@@ -545,7 +745,63 @@ new Proxy(e, {
545
745
  return t[r];
546
746
  }
547
747
  });
548
- const require$1 = createRequire$1(import.meta.url);
748
+ function parseActionMetadata(yamlContent, fileName) {
749
+ try {
750
+ const parsed = parseAction(yamlContent, fileName);
751
+ if (parsed.validations.length > 0) {
752
+ getLogger().warn(
753
+ `[ActionMetadata] ${fileName}: validation errors: ${parsed.validations.join(", ")}`
754
+ );
755
+ return null;
756
+ }
757
+ if (!parsed.val) {
758
+ getLogger().warn(`[ActionMetadata] ${fileName}: parsing returned no result`);
759
+ return null;
760
+ }
761
+ const action = parsed.val;
762
+ const inputJsonSchema = jayTypeToJsonSchema(action.inputType);
763
+ let inputSchema;
764
+ if (inputJsonSchema && inputJsonSchema.type === "object") {
765
+ inputSchema = {
766
+ type: "object",
767
+ properties: inputJsonSchema.properties || {},
768
+ ...inputJsonSchema.required && inputJsonSchema.required.length > 0 && { required: inputJsonSchema.required }
769
+ };
770
+ } else {
771
+ inputSchema = { type: "object", properties: {} };
772
+ }
773
+ const metadata = {
774
+ name: action.name,
775
+ description: action.description,
776
+ inputSchema
777
+ };
778
+ if (action.outputType) {
779
+ const outputJsonSchema = jayTypeToJsonSchema(action.outputType);
780
+ if (outputJsonSchema) {
781
+ metadata.outputSchema = outputJsonSchema;
782
+ }
783
+ }
784
+ return metadata;
785
+ } catch (error) {
786
+ getLogger().error(
787
+ `[ActionMetadata] Failed to parse ${fileName}: ${error instanceof Error ? error.message : error}`
788
+ );
789
+ return null;
790
+ }
791
+ }
792
+ function loadActionMetadata(filePath) {
793
+ if (!fs.existsSync(filePath)) {
794
+ getLogger().warn(`[ActionMetadata] File not found: ${filePath}`);
795
+ return null;
796
+ }
797
+ const yamlContent = fs.readFileSync(filePath, "utf-8");
798
+ const fileName = path.basename(filePath);
799
+ return parseActionMetadata(yamlContent, fileName);
800
+ }
801
+ function resolveActionMetadataPath(actionPath, pluginDir) {
802
+ return path.resolve(pluginDir, actionPath);
803
+ }
804
+ const require$2 = createRequire$1(import.meta.url);
549
805
  async function discoverAndRegisterActions(options) {
550
806
  const {
551
807
  projectRoot,
@@ -562,13 +818,13 @@ async function discoverAndRegisterActions(options) {
562
818
  const actionsPath = path.resolve(projectRoot, actionsDir);
563
819
  if (!fs.existsSync(actionsPath)) {
564
820
  if (verbose) {
565
- console.log(`[Actions] No actions directory found at ${actionsPath}`);
821
+ getLogger().info(`[Actions] No actions directory found at ${actionsPath}`);
566
822
  }
567
823
  return result;
568
824
  }
569
825
  const actionFiles = await findActionFiles(actionsPath);
570
826
  if (verbose) {
571
- console.log(`[Actions] Found ${actionFiles.length} action file(s)`);
827
+ getLogger().info(`[Actions] Found ${actionFiles.length} action file(s)`);
572
828
  }
573
829
  for (const filePath of actionFiles) {
574
830
  result.scannedFiles.push(filePath);
@@ -585,12 +841,14 @@ async function discoverAndRegisterActions(options) {
585
841
  result.actionNames.push(exportValue.actionName);
586
842
  result.actionCount++;
587
843
  if (verbose) {
588
- console.log(`[Actions] Registered: ${exportValue.actionName}`);
844
+ getLogger().info(
845
+ `[Actions] Registered: ${exportValue.actionName}`
846
+ );
589
847
  }
590
848
  }
591
849
  }
592
850
  } catch (error) {
593
- console.error(`[Actions] Failed to import ${filePath}:`, error);
851
+ getLogger().error(`[Actions] Failed to import ${filePath}: ${error}`);
594
852
  }
595
853
  }
596
854
  return result;
@@ -657,14 +915,14 @@ async function discoverNpmPluginActions(projectRoot, registry, verbose, viteServ
657
915
  continue;
658
916
  }
659
917
  if (verbose) {
660
- console.log(
661
- `[Actions] NPM plugin "${packageName}" declares actions:`,
662
- pluginConfig.actions
918
+ getLogger().info(
919
+ `[Actions] NPM plugin "${packageName}" declares actions: ${JSON.stringify(pluginConfig.actions)}`
663
920
  );
664
921
  }
665
922
  const actions = await registerNpmPluginActions(
666
923
  packageName,
667
924
  pluginConfig,
925
+ pluginDir,
668
926
  registry,
669
927
  verbose,
670
928
  viteServer
@@ -675,20 +933,38 @@ async function discoverNpmPluginActions(projectRoot, registry, verbose, viteServ
675
933
  }
676
934
  }
677
935
  } catch (error) {
678
- console.error("[Actions] Failed to read project package.json:", error);
936
+ getLogger().error(`[Actions] Failed to read project package.json: ${error}`);
679
937
  }
680
938
  return allActions;
681
939
  }
682
940
  function tryResolvePluginYaml(packageName, projectRoot) {
683
941
  try {
684
- return require$1.resolve(`${packageName}/plugin.yaml`, {
942
+ return require$2.resolve(`${packageName}/plugin.yaml`, {
685
943
  paths: [projectRoot]
686
944
  });
687
945
  } catch {
688
946
  return null;
689
947
  }
690
948
  }
691
- async function registerNpmPluginActions(packageName, pluginConfig, registry, verbose, viteServer) {
949
+ function resolveNpmActionMetadataPath(actionPath, packageName, pluginDir) {
950
+ if (!actionPath.startsWith(".")) {
951
+ try {
952
+ return require$2.resolve(`${packageName}/${actionPath}`, {
953
+ paths: [pluginDir]
954
+ });
955
+ } catch {
956
+ }
957
+ }
958
+ const resolved = resolveActionMetadataPath(actionPath, pluginDir);
959
+ if (fs.existsSync(resolved)) {
960
+ return resolved;
961
+ }
962
+ getLogger().warn(
963
+ `[Actions] Could not resolve .jay-action file "${actionPath}" for package "${packageName}"`
964
+ );
965
+ return null;
966
+ }
967
+ async function registerNpmPluginActions(packageName, pluginConfig, pluginDir, registry, verbose, viteServer) {
692
968
  const registeredActions = [];
693
969
  try {
694
970
  let pluginModule;
@@ -697,24 +973,42 @@ async function registerNpmPluginActions(packageName, pluginConfig, registry, ver
697
973
  } else {
698
974
  pluginModule = await import(packageName);
699
975
  }
700
- for (const actionName of pluginConfig.actions) {
976
+ for (const entry of pluginConfig.actions) {
977
+ const { name: actionName, action: actionPath } = normalizeActionEntry(entry);
701
978
  const actionExport = pluginModule[actionName];
702
979
  if (actionExport && isJayAction(actionExport)) {
703
980
  registry.register(actionExport);
704
- registeredActions.push(actionExport.actionName);
705
- if (verbose) {
706
- console.log(
707
- `[Actions] Registered NPM plugin action: ${actionExport.actionName}`
981
+ const registeredName = actionExport.actionName;
982
+ registeredActions.push(registeredName);
983
+ if (actionPath) {
984
+ const metadataFilePath = resolveNpmActionMetadataPath(
985
+ actionPath,
986
+ packageName,
987
+ pluginDir
708
988
  );
989
+ if (metadataFilePath) {
990
+ const metadata = loadActionMetadata(metadataFilePath);
991
+ if (metadata) {
992
+ registry.setMetadata(registeredName, metadata);
993
+ if (verbose) {
994
+ getLogger().info(
995
+ `[Actions] Loaded metadata for "${registeredName}" from ${actionPath}`
996
+ );
997
+ }
998
+ }
999
+ }
1000
+ }
1001
+ if (verbose) {
1002
+ getLogger().info(`[Actions] Registered NPM plugin action: ${registeredName}`);
709
1003
  }
710
1004
  } else {
711
- console.warn(
1005
+ getLogger().warn(
712
1006
  `[Actions] NPM plugin "${packageName}" declares action "${actionName}" but it's not exported or not a JayAction`
713
1007
  );
714
1008
  }
715
1009
  }
716
1010
  } catch (importError) {
717
- console.error(`[Actions] Failed to import NPM plugin "${packageName}":`, importError);
1011
+ getLogger().error(`[Actions] Failed to import NPM plugin "${packageName}": ${importError}`);
718
1012
  }
719
1013
  return registeredActions;
720
1014
  }
@@ -729,7 +1023,9 @@ async function discoverPluginActions(pluginPath, projectRoot, registry = actionR
729
1023
  const registeredActions = [];
730
1024
  const pluginName = pluginConfig.name || path.basename(pluginPath);
731
1025
  if (verbose) {
732
- console.log(`[Actions] Plugin "${pluginName}" declares actions:`, pluginConfig.actions);
1026
+ getLogger().info(
1027
+ `[Actions] Plugin "${pluginName}" declares actions: ${JSON.stringify(pluginConfig.actions)}`
1028
+ );
733
1029
  }
734
1030
  let modulePath = pluginConfig.module ? path.join(pluginPath, pluginConfig.module) : path.join(pluginPath, "index.ts");
735
1031
  if (!fs.existsSync(modulePath)) {
@@ -740,7 +1036,7 @@ async function discoverPluginActions(pluginPath, projectRoot, registry = actionR
740
1036
  } else if (fs.existsSync(jsPath)) {
741
1037
  modulePath = jsPath;
742
1038
  } else {
743
- console.warn(`[Actions] Plugin "${pluginName}" module not found at ${modulePath}`);
1039
+ getLogger().warn(`[Actions] Plugin "${pluginName}" module not found at ${modulePath}`);
744
1040
  return [];
745
1041
  }
746
1042
  }
@@ -751,31 +1047,50 @@ async function discoverPluginActions(pluginPath, projectRoot, registry = actionR
751
1047
  } else {
752
1048
  pluginModule = await import(modulePath);
753
1049
  }
754
- for (const actionName of pluginConfig.actions) {
1050
+ for (const entry of pluginConfig.actions) {
1051
+ const { name: actionName, action: actionPath } = normalizeActionEntry(entry);
755
1052
  const actionExport = pluginModule[actionName];
756
1053
  if (actionExport && isJayAction(actionExport)) {
757
1054
  registry.register(actionExport);
758
- registeredActions.push(actionExport.actionName);
1055
+ const registeredName = actionExport.actionName;
1056
+ registeredActions.push(registeredName);
1057
+ if (actionPath) {
1058
+ const metadataFilePath = resolveActionMetadataPath(actionPath, pluginPath);
1059
+ const metadata = loadActionMetadata(metadataFilePath);
1060
+ if (metadata) {
1061
+ registry.setMetadata(registeredName, metadata);
1062
+ if (verbose) {
1063
+ getLogger().info(
1064
+ `[Actions] Loaded metadata for "${registeredName}" from ${actionPath}`
1065
+ );
1066
+ }
1067
+ }
1068
+ }
759
1069
  if (verbose) {
760
- console.log(
761
- `[Actions] Registered plugin action: ${actionExport.actionName}`
762
- );
1070
+ getLogger().info(`[Actions] Registered plugin action: ${registeredName}`);
763
1071
  }
764
1072
  } else {
765
- console.warn(
1073
+ getLogger().warn(
766
1074
  `[Actions] Plugin "${pluginName}" declares action "${actionName}" but it's not exported or not a JayAction`
767
1075
  );
768
1076
  }
769
1077
  }
770
1078
  } catch (importError) {
771
- console.error(`[Actions] Failed to import plugin module at ${modulePath}:`, importError);
1079
+ getLogger().error(
1080
+ `[Actions] Failed to import plugin module at ${modulePath}: ${importError}`
1081
+ );
772
1082
  }
773
1083
  return registeredActions;
774
1084
  }
775
- const require2 = createRequire$1(import.meta.url);
776
- async function discoverPluginsWithInit(options) {
777
- const { projectRoot, verbose = false } = options;
778
- const plugins = [];
1085
+ const require$1 = createRequire$1(import.meta.url);
1086
+ async function scanPlugins(options) {
1087
+ const {
1088
+ projectRoot,
1089
+ verbose = false,
1090
+ includeDevDeps = false,
1091
+ discoverTransitive = false
1092
+ } = options;
1093
+ const plugins = /* @__PURE__ */ new Map();
779
1094
  const visitedPackages = /* @__PURE__ */ new Set();
780
1095
  const localPluginsPath = path.join(projectRoot, "src/plugins");
781
1096
  if (fs.existsSync(localPluginsPath)) {
@@ -788,37 +1103,37 @@ async function discoverPluginsWithInit(options) {
788
1103
  const manifest = loadPluginManifest(pluginPath);
789
1104
  if (!manifest)
790
1105
  continue;
791
- const initConfig = resolvePluginInit(pluginPath, manifest.init, true);
792
- if (!initConfig)
793
- continue;
794
1106
  const dependencies = await getPackageDependencies(pluginPath);
795
- plugins.push({
796
- name: manifest.name || entry.name,
1107
+ const pluginName = manifest.name || entry.name;
1108
+ plugins.set(entry.name, {
1109
+ name: pluginName,
797
1110
  pluginPath,
798
1111
  packageName: pluginPath,
799
- // For local, use path
1112
+ // For local, use path as identifier
800
1113
  isLocal: true,
801
- initModule: initConfig.module,
802
- initExport: initConfig.export,
1114
+ manifest,
803
1115
  dependencies
804
1116
  });
805
1117
  visitedPackages.add(pluginPath);
806
1118
  if (verbose) {
807
- console.log(
808
- `[PluginInit] Found local plugin with init: ${manifest.name || entry.name}`
809
- );
1119
+ getLogger().info(`[PluginScanner] Found local plugin: ${pluginName}`);
810
1120
  }
811
1121
  }
812
1122
  } catch (error) {
813
- console.warn(`[PluginInit] Failed to scan local plugins: ${error}`);
1123
+ if (verbose) {
1124
+ getLogger().warn(`[PluginScanner] Failed to scan local plugins: ${error}`);
1125
+ }
814
1126
  }
815
1127
  }
816
1128
  const projectPackageJsonPath = path.join(projectRoot, "package.json");
817
1129
  if (fs.existsSync(projectPackageJsonPath)) {
818
1130
  try {
819
1131
  const projectPackageJson = JSON.parse(fs.readFileSync(projectPackageJsonPath, "utf-8"));
820
- const initialDeps = Object.keys(projectPackageJson.dependencies || {});
821
- const packagesToCheck = [...initialDeps];
1132
+ const deps = {
1133
+ ...projectPackageJson.dependencies,
1134
+ ...includeDevDeps ? projectPackageJson.devDependencies : {}
1135
+ };
1136
+ const packagesToCheck = Object.keys(deps);
822
1137
  while (packagesToCheck.length > 0) {
823
1138
  const depName = packagesToCheck.shift();
824
1139
  if (visitedPackages.has(depName))
@@ -826,7 +1141,7 @@ async function discoverPluginsWithInit(options) {
826
1141
  visitedPackages.add(depName);
827
1142
  let pluginYamlPath;
828
1143
  try {
829
- pluginYamlPath = require2.resolve(`${depName}/plugin.yaml`, {
1144
+ pluginYamlPath = require$1.resolve(`${depName}/plugin.yaml`, {
830
1145
  paths: [projectRoot]
831
1146
  });
832
1147
  } catch {
@@ -836,30 +1151,77 @@ async function discoverPluginsWithInit(options) {
836
1151
  const manifest = loadPluginManifest(pluginPath);
837
1152
  if (!manifest)
838
1153
  continue;
839
- const initConfig = resolvePluginInit(pluginPath, manifest.init, false);
840
- if (!initConfig)
841
- continue;
842
1154
  const dependencies = await getPackageDependencies(pluginPath);
843
- plugins.push({
1155
+ plugins.set(depName, {
844
1156
  name: manifest.name || depName,
845
1157
  pluginPath,
846
1158
  packageName: depName,
847
1159
  isLocal: false,
848
- initModule: initConfig.module,
849
- initExport: initConfig.export,
1160
+ manifest,
850
1161
  dependencies
851
1162
  });
852
1163
  if (verbose) {
853
- console.log(`[PluginInit] Found NPM plugin with init: ${depName}`);
1164
+ getLogger().info(`[PluginScanner] Found NPM plugin: ${depName}`);
854
1165
  }
855
- for (const transitiveDep of dependencies) {
856
- if (!visitedPackages.has(transitiveDep)) {
857
- packagesToCheck.push(transitiveDep);
1166
+ if (discoverTransitive) {
1167
+ for (const transitiveDep of dependencies) {
1168
+ if (!visitedPackages.has(transitiveDep)) {
1169
+ packagesToCheck.push(transitiveDep);
1170
+ }
858
1171
  }
859
1172
  }
860
1173
  }
861
1174
  } catch (error) {
862
- console.warn(`[PluginInit] Failed to scan NPM plugins: ${error}`);
1175
+ if (verbose) {
1176
+ getLogger().warn(`[PluginScanner] Failed to scan NPM plugins: ${error}`);
1177
+ }
1178
+ }
1179
+ }
1180
+ return plugins;
1181
+ }
1182
+ async function getPackageDependencies(pluginPath) {
1183
+ const packageJsonPath = path.join(pluginPath, "package.json");
1184
+ if (!fs.existsSync(packageJsonPath)) {
1185
+ return [];
1186
+ }
1187
+ try {
1188
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
1189
+ return Object.keys(packageJson.dependencies || {});
1190
+ } catch {
1191
+ return [];
1192
+ }
1193
+ }
1194
+ async function discoverPluginsWithInit(options) {
1195
+ const { projectRoot, verbose = false } = options;
1196
+ const scannedPlugins = await scanPlugins({
1197
+ projectRoot,
1198
+ verbose,
1199
+ includeDevDeps: false,
1200
+ // Only runtime dependencies
1201
+ discoverTransitive: true
1202
+ // Need transitive for dependency ordering
1203
+ });
1204
+ const plugins = [];
1205
+ for (const [key, scanned] of scannedPlugins) {
1206
+ const initConfig = resolvePluginInit(
1207
+ scanned.pluginPath,
1208
+ scanned.manifest.init,
1209
+ scanned.isLocal
1210
+ );
1211
+ if (!initConfig)
1212
+ continue;
1213
+ plugins.push({
1214
+ name: scanned.name,
1215
+ pluginPath: scanned.pluginPath,
1216
+ packageName: scanned.packageName,
1217
+ isLocal: scanned.isLocal,
1218
+ initModule: initConfig.module,
1219
+ initExport: initConfig.export,
1220
+ dependencies: scanned.dependencies,
1221
+ global: scanned.manifest.global === true
1222
+ });
1223
+ if (verbose) {
1224
+ getLogger().info(`[PluginInit] Found plugin with init: ${scanned.name}`);
863
1225
  }
864
1226
  }
865
1227
  return plugins;
@@ -899,18 +1261,6 @@ function resolvePluginInit(pluginPath, initConfig, isLocal) {
899
1261
  return null;
900
1262
  }
901
1263
  }
902
- async function getPackageDependencies(pluginPath) {
903
- const packageJsonPath = path.join(pluginPath, "package.json");
904
- if (!fs.existsSync(packageJsonPath)) {
905
- return [];
906
- }
907
- try {
908
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
909
- return Object.keys(packageJson.dependencies || {});
910
- } catch {
911
- return [];
912
- }
913
- }
914
1264
  function sortPluginsByDependencies(plugins) {
915
1265
  const pluginNames = new Set(plugins.map((p) => p.packageName));
916
1266
  const sorted = [];
@@ -920,7 +1270,7 @@ function sortPluginsByDependencies(plugins) {
920
1270
  if (visited.has(plugin.packageName))
921
1271
  return;
922
1272
  if (visiting.has(plugin.packageName)) {
923
- console.warn(`[PluginInit] Circular dependency detected for ${plugin.name}`);
1273
+ getLogger().warn(`[PluginInit] Circular dependency detected for ${plugin.name}`);
924
1274
  return;
925
1275
  }
926
1276
  visiting.add(plugin.packageName);
@@ -942,6 +1292,7 @@ function sortPluginsByDependencies(plugins) {
942
1292
  return sorted;
943
1293
  }
944
1294
  async function executePluginServerInits(plugins, viteServer, verbose = false) {
1295
+ const initErrors = /* @__PURE__ */ new Map();
945
1296
  for (const plugin of plugins) {
946
1297
  try {
947
1298
  let modulePath;
@@ -960,14 +1311,14 @@ async function executePluginServerInits(plugins, viteServer, verbose = false) {
960
1311
  }
961
1312
  const jayInit = pluginModule[plugin.initExport];
962
1313
  if (!jayInit || jayInit.__brand !== "JayInit") {
963
- console.warn(
1314
+ getLogger().warn(
964
1315
  `[PluginInit] Plugin "${plugin.name}" init module doesn't export a valid JayInit at "${plugin.initExport}"`
965
1316
  );
966
1317
  continue;
967
1318
  }
968
1319
  if (typeof jayInit._serverInit === "function") {
969
1320
  if (verbose) {
970
- console.log(`[DevServer] Running server init: ${plugin.name}`);
1321
+ getLogger().info(`[DevServer] Running server init: ${plugin.name}`);
971
1322
  }
972
1323
  const clientData = await jayInit._serverInit();
973
1324
  if (clientData !== void 0 && clientData !== null) {
@@ -975,12 +1326,14 @@ async function executePluginServerInits(plugins, viteServer, verbose = false) {
975
1326
  }
976
1327
  }
977
1328
  } catch (error) {
978
- console.error(
979
- `[PluginInit] Failed to execute server init for "${plugin.name}":`,
980
- error
1329
+ const err = error instanceof Error ? error : new Error(String(error));
1330
+ initErrors.set(plugin.name, err);
1331
+ getLogger().error(
1332
+ `[PluginInit] Failed to execute server init for "${plugin.name}": ${error}`
981
1333
  );
982
1334
  }
983
1335
  }
1336
+ return initErrors;
984
1337
  }
985
1338
  function preparePluginClientInits(plugins) {
986
1339
  return plugins.map((plugin) => {
@@ -1146,6 +1499,509 @@ class SlowRenderCache {
1146
1499
  return Array.from(this.pathToKeys.keys());
1147
1500
  }
1148
1501
  }
1502
+ const require2 = createRequire(import.meta.url);
1503
+ async function executeDynamicGenerator(plugin, config, projectRoot, services, verbose, viteServer) {
1504
+ const { pluginPath, name: pluginName, isLocal, packageName } = plugin;
1505
+ if (!config.generator) {
1506
+ throw new Error(
1507
+ `Plugin "${pluginName}" has dynamic_contracts entry but no generator specified`
1508
+ );
1509
+ }
1510
+ const isFilePath = config.generator.startsWith("./") || config.generator.startsWith("/") || config.generator.includes(".ts") || config.generator.includes(".js");
1511
+ let generator;
1512
+ if (isFilePath) {
1513
+ let generatorPath;
1514
+ if (!isLocal) {
1515
+ try {
1516
+ generatorPath = require2.resolve(`${packageName}/${config.generator}`, {
1517
+ paths: [projectRoot]
1518
+ });
1519
+ } catch {
1520
+ generatorPath = path.join(pluginPath, config.generator);
1521
+ }
1522
+ } else {
1523
+ generatorPath = path.join(pluginPath, config.generator);
1524
+ }
1525
+ if (!fs.existsSync(generatorPath)) {
1526
+ const withTs = generatorPath + ".ts";
1527
+ const withJs = generatorPath + ".js";
1528
+ if (fs.existsSync(withTs)) {
1529
+ generatorPath = withTs;
1530
+ } else if (fs.existsSync(withJs)) {
1531
+ generatorPath = withJs;
1532
+ }
1533
+ }
1534
+ if (!fs.existsSync(generatorPath)) {
1535
+ throw new Error(
1536
+ `Generator file not found for plugin "${pluginName}": ${config.generator}`
1537
+ );
1538
+ }
1539
+ if (verbose) {
1540
+ getLogger().info(` Loading generator from file: ${generatorPath}`);
1541
+ }
1542
+ let generatorModule;
1543
+ if (viteServer) {
1544
+ generatorModule = await viteServer.ssrLoadModule(generatorPath);
1545
+ } else {
1546
+ generatorModule = await import(generatorPath);
1547
+ }
1548
+ generator = generatorModule.generator || generatorModule.default;
1549
+ } else {
1550
+ if (verbose) {
1551
+ getLogger().info(
1552
+ ` Loading generator export: ${config.generator} from ${packageName}`
1553
+ );
1554
+ }
1555
+ let pluginModule;
1556
+ if (viteServer) {
1557
+ pluginModule = await viteServer.ssrLoadModule(packageName);
1558
+ } else {
1559
+ pluginModule = await import(packageName);
1560
+ }
1561
+ generator = pluginModule[config.generator];
1562
+ if (!generator) {
1563
+ throw new Error(
1564
+ `Generator "${config.generator}" not exported from plugin "${pluginName}". Ensure it's exported from the package's index.ts`
1565
+ );
1566
+ }
1567
+ }
1568
+ if (!generator || typeof generator.generate !== "function") {
1569
+ throw new Error(
1570
+ `Generator "${config.generator}" for plugin "${pluginName}" must have a 'generate' function. Use makeContractGenerator() to create valid generators.`
1571
+ );
1572
+ }
1573
+ const resolvedServices = [];
1574
+ for (const marker of generator.services) {
1575
+ const service = services.get(marker);
1576
+ if (!service) {
1577
+ const markerName = marker.description ?? "unknown";
1578
+ throw new Error(
1579
+ `Service "${markerName}" required by ${pluginName} generator not found. Ensure it's registered in init.ts`
1580
+ );
1581
+ }
1582
+ resolvedServices.push(service);
1583
+ }
1584
+ if (verbose) {
1585
+ getLogger().info(` Executing generator...`);
1586
+ }
1587
+ return await generator.generate(...resolvedServices);
1588
+ }
1589
+ function resolveStaticContractPath(plugin, contractSpec, projectRoot) {
1590
+ const { pluginPath, isLocal, packageName } = plugin;
1591
+ if (!isLocal) {
1592
+ try {
1593
+ return require2.resolve(`${packageName}/${contractSpec}`, {
1594
+ paths: [projectRoot]
1595
+ });
1596
+ } catch {
1597
+ const possiblePaths = [
1598
+ path.join(pluginPath, "dist", contractSpec),
1599
+ path.join(pluginPath, "lib", contractSpec),
1600
+ path.join(pluginPath, contractSpec)
1601
+ ];
1602
+ const found = possiblePaths.find((p) => fs.existsSync(p));
1603
+ return found || possiblePaths[0];
1604
+ }
1605
+ } else {
1606
+ return path.join(pluginPath, contractSpec);
1607
+ }
1608
+ }
1609
+ function resolveActionFilePath(actionPath, packageName, pluginPath, isLocal, projectRoot) {
1610
+ if (!isLocal && !actionPath.startsWith(".")) {
1611
+ try {
1612
+ return require2.resolve(`${packageName}/${actionPath}`, {
1613
+ paths: [projectRoot]
1614
+ });
1615
+ } catch {
1616
+ const possiblePaths = [
1617
+ path.join(pluginPath, "dist", actionPath),
1618
+ path.join(pluginPath, "lib", actionPath),
1619
+ path.join(pluginPath, actionPath)
1620
+ ];
1621
+ const found = possiblePaths.find((p) => fs.existsSync(p));
1622
+ return found || null;
1623
+ }
1624
+ }
1625
+ const resolved = resolveActionMetadataPath(actionPath, pluginPath);
1626
+ return fs.existsSync(resolved) ? resolved : null;
1627
+ }
1628
+ function toKebabCase(str) {
1629
+ return str.replace(/([A-Z])/g, "-$1").toLowerCase().replace(/^-/, "");
1630
+ }
1631
+ function getJayStackVersion() {
1632
+ try {
1633
+ const packageJsonPath = path.join(__dirname, "..", "package.json");
1634
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
1635
+ return packageJson.version || "0.0.0";
1636
+ } catch {
1637
+ return "0.0.0";
1638
+ }
1639
+ }
1640
+ async function materializeContracts(options, services = /* @__PURE__ */ new Map()) {
1641
+ const {
1642
+ projectRoot,
1643
+ outputDir = path.join(projectRoot, "build", "materialized-contracts"),
1644
+ dynamicOnly = false,
1645
+ pluginFilter,
1646
+ verbose = false,
1647
+ viteServer
1648
+ } = options;
1649
+ const contracts = [];
1650
+ const pluginsIndexMap = /* @__PURE__ */ new Map();
1651
+ let staticCount = 0;
1652
+ let dynamicCount = 0;
1653
+ if (verbose) {
1654
+ getLogger().info("Scanning for plugins...");
1655
+ }
1656
+ const plugins = await scanPlugins({
1657
+ projectRoot,
1658
+ verbose,
1659
+ includeDevDeps: true
1660
+ // Include dev deps for contract discovery
1661
+ });
1662
+ if (verbose) {
1663
+ getLogger().info(`Found ${plugins.size} plugin(s)`);
1664
+ }
1665
+ for (const [pluginKey, plugin] of plugins) {
1666
+ if (pluginFilter && plugin.name !== pluginFilter && pluginKey !== pluginFilter)
1667
+ continue;
1668
+ if (verbose) {
1669
+ getLogger().info(`
1670
+ 📦 Processing plugin: ${plugin.name}`);
1671
+ }
1672
+ const { manifest } = plugin;
1673
+ if (!dynamicOnly && manifest.contracts) {
1674
+ for (const contract of manifest.contracts) {
1675
+ const contractPath = resolveStaticContractPath(
1676
+ plugin,
1677
+ contract.contract,
1678
+ projectRoot
1679
+ );
1680
+ const relativePath = path.relative(projectRoot, contractPath);
1681
+ const entry = {
1682
+ plugin: plugin.name,
1683
+ name: contract.name,
1684
+ type: "static",
1685
+ path: "./" + relativePath
1686
+ };
1687
+ contracts.push(entry);
1688
+ staticCount++;
1689
+ const pluginRelPath = path.relative(projectRoot, plugin.pluginPath);
1690
+ if (!pluginsIndexMap.has(plugin.name)) {
1691
+ pluginsIndexMap.set(plugin.name, {
1692
+ path: "./" + pluginRelPath.replace(/\\/g, "/"),
1693
+ contracts: [],
1694
+ actions: []
1695
+ });
1696
+ }
1697
+ pluginsIndexMap.get(plugin.name).contracts.push({
1698
+ name: contract.name,
1699
+ type: "static",
1700
+ path: "./" + relativePath
1701
+ });
1702
+ if (verbose) {
1703
+ getLogger().info(` 📄 Static: ${contract.name}`);
1704
+ }
1705
+ }
1706
+ }
1707
+ if (manifest.dynamic_contracts) {
1708
+ const dynamicConfigs = Array.isArray(manifest.dynamic_contracts) ? manifest.dynamic_contracts : [manifest.dynamic_contracts];
1709
+ const pluginOutputDir = path.join(outputDir, plugin.name.replace(/[@/]/g, "_"));
1710
+ fs.mkdirSync(pluginOutputDir, { recursive: true });
1711
+ for (const config of dynamicConfigs) {
1712
+ if (verbose) {
1713
+ getLogger().info(` ⚡ Dynamic contracts (prefix: ${config.prefix})`);
1714
+ }
1715
+ try {
1716
+ const generatedContracts = await executeDynamicGenerator(
1717
+ plugin,
1718
+ config,
1719
+ projectRoot,
1720
+ services,
1721
+ verbose,
1722
+ viteServer
1723
+ );
1724
+ const prefix = config.prefix;
1725
+ for (const generated of generatedContracts) {
1726
+ const fullName = `${prefix}/${toKebabCase(generated.name)}`;
1727
+ const fileName = `${prefix}-${toKebabCase(generated.name)}.jay-contract`;
1728
+ const filePath = path.join(pluginOutputDir, fileName);
1729
+ fs.writeFileSync(filePath, generated.yaml, "utf-8");
1730
+ const relativePath = path.relative(projectRoot, filePath);
1731
+ const dynEntry = {
1732
+ plugin: plugin.name,
1733
+ name: fullName,
1734
+ type: "dynamic",
1735
+ path: "./" + relativePath,
1736
+ metadata: generated.metadata
1737
+ };
1738
+ contracts.push(dynEntry);
1739
+ dynamicCount++;
1740
+ const pluginRelPath = path.relative(projectRoot, plugin.pluginPath);
1741
+ if (!pluginsIndexMap.has(plugin.name)) {
1742
+ pluginsIndexMap.set(plugin.name, {
1743
+ path: "./" + pluginRelPath.replace(/\\/g, "/"),
1744
+ contracts: [],
1745
+ actions: []
1746
+ });
1747
+ }
1748
+ pluginsIndexMap.get(plugin.name).contracts.push({
1749
+ name: fullName,
1750
+ type: "dynamic",
1751
+ path: "./" + relativePath
1752
+ });
1753
+ if (verbose) {
1754
+ getLogger().info(` ⚡ Materialized: ${fullName}`);
1755
+ }
1756
+ }
1757
+ } catch (error) {
1758
+ getLogger().error(
1759
+ ` ❌ Failed to materialize dynamic contracts for ${plugin.name} (${config.prefix}): ${error}`
1760
+ );
1761
+ }
1762
+ }
1763
+ }
1764
+ if (manifest.actions && Array.isArray(manifest.actions)) {
1765
+ for (const entry of manifest.actions) {
1766
+ const { name: actionName, action: actionPath } = normalizeActionEntry(entry);
1767
+ if (!actionPath)
1768
+ continue;
1769
+ const metadataFilePath = resolveActionFilePath(
1770
+ actionPath,
1771
+ plugin.packageName,
1772
+ plugin.pluginPath,
1773
+ plugin.isLocal,
1774
+ projectRoot
1775
+ );
1776
+ if (!metadataFilePath)
1777
+ continue;
1778
+ const metadata = loadActionMetadata(metadataFilePath);
1779
+ if (!metadata)
1780
+ continue;
1781
+ const pluginRelPath = path.relative(projectRoot, plugin.pluginPath);
1782
+ if (!pluginsIndexMap.has(plugin.name)) {
1783
+ pluginsIndexMap.set(plugin.name, {
1784
+ path: "./" + pluginRelPath.replace(/\\/g, "/"),
1785
+ contracts: [],
1786
+ actions: []
1787
+ });
1788
+ }
1789
+ const actionRelPath = path.relative(projectRoot, metadataFilePath);
1790
+ pluginsIndexMap.get(plugin.name).actions.push({
1791
+ name: metadata.name,
1792
+ description: metadata.description,
1793
+ path: "./" + actionRelPath.replace(/\\/g, "/")
1794
+ });
1795
+ if (verbose) {
1796
+ getLogger().info(` 🔧 Action: ${metadata.name} (${actionPath})`);
1797
+ }
1798
+ }
1799
+ }
1800
+ }
1801
+ const index = {
1802
+ materialized_at: (/* @__PURE__ */ new Date()).toISOString(),
1803
+ jay_stack_version: getJayStackVersion(),
1804
+ contracts
1805
+ };
1806
+ fs.mkdirSync(outputDir, { recursive: true });
1807
+ const indexPath = path.join(outputDir, "contracts-index.yaml");
1808
+ fs.writeFileSync(indexPath, YAML.stringify(index), "utf-8");
1809
+ const pluginsIndex = {
1810
+ materialized_at: index.materialized_at,
1811
+ jay_stack_version: index.jay_stack_version,
1812
+ plugins: Array.from(pluginsIndexMap.entries()).map(([name, data]) => ({
1813
+ name,
1814
+ path: data.path,
1815
+ contracts: data.contracts,
1816
+ ...data.actions.length > 0 && { actions: data.actions }
1817
+ }))
1818
+ };
1819
+ const pluginsIndexPath = path.join(outputDir, "plugins-index.yaml");
1820
+ fs.writeFileSync(pluginsIndexPath, YAML.stringify(pluginsIndex), "utf-8");
1821
+ if (verbose) {
1822
+ getLogger().info(`
1823
+ ✅ Contracts index written to: ${indexPath}`);
1824
+ getLogger().info(`✅ Plugins index written to: ${pluginsIndexPath}`);
1825
+ }
1826
+ return {
1827
+ index,
1828
+ staticCount,
1829
+ dynamicCount,
1830
+ outputDir
1831
+ };
1832
+ }
1833
+ async function listContracts(options) {
1834
+ const { projectRoot, dynamicOnly = false, pluginFilter } = options;
1835
+ const contracts = [];
1836
+ const plugins = await scanPlugins({
1837
+ projectRoot,
1838
+ includeDevDeps: true
1839
+ });
1840
+ for (const [pluginKey, plugin] of plugins) {
1841
+ if (pluginFilter && plugin.name !== pluginFilter && pluginKey !== pluginFilter)
1842
+ continue;
1843
+ const { manifest } = plugin;
1844
+ if (!dynamicOnly && manifest.contracts) {
1845
+ for (const contract of manifest.contracts) {
1846
+ const contractPath = resolveStaticContractPath(
1847
+ plugin,
1848
+ contract.contract,
1849
+ projectRoot
1850
+ );
1851
+ const relativePath = path.relative(projectRoot, contractPath);
1852
+ contracts.push({
1853
+ plugin: plugin.name,
1854
+ name: contract.name,
1855
+ type: "static",
1856
+ path: "./" + relativePath
1857
+ });
1858
+ }
1859
+ }
1860
+ if (manifest.dynamic_contracts) {
1861
+ const dynamicConfigs = Array.isArray(manifest.dynamic_contracts) ? manifest.dynamic_contracts : [manifest.dynamic_contracts];
1862
+ for (const config of dynamicConfigs) {
1863
+ contracts.push({
1864
+ plugin: plugin.name,
1865
+ name: `${config.prefix}/*`,
1866
+ type: "dynamic",
1867
+ path: "(run materialization to generate)"
1868
+ });
1869
+ }
1870
+ }
1871
+ }
1872
+ return {
1873
+ materialized_at: (/* @__PURE__ */ new Date()).toISOString(),
1874
+ jay_stack_version: getJayStackVersion(),
1875
+ contracts
1876
+ };
1877
+ }
1878
+ async function discoverPluginsWithSetup(options) {
1879
+ const { projectRoot, verbose, pluginFilter } = options;
1880
+ const allPlugins = await scanPlugins({
1881
+ projectRoot,
1882
+ verbose,
1883
+ discoverTransitive: true
1884
+ });
1885
+ const pluginsWithSetup = [];
1886
+ for (const [packageName, plugin] of allPlugins) {
1887
+ if (!plugin.manifest.setup?.handler)
1888
+ continue;
1889
+ if (pluginFilter && plugin.name !== pluginFilter && packageName !== pluginFilter) {
1890
+ continue;
1891
+ }
1892
+ pluginsWithSetup.push({
1893
+ name: plugin.name,
1894
+ pluginPath: plugin.pluginPath,
1895
+ packageName: plugin.packageName,
1896
+ isLocal: plugin.isLocal,
1897
+ setupHandler: plugin.manifest.setup.handler,
1898
+ setupDescription: plugin.manifest.setup.description,
1899
+ dependencies: plugin.dependencies
1900
+ });
1901
+ if (verbose) {
1902
+ getLogger().info(`[Setup] Found plugin with setup: ${plugin.name}`);
1903
+ }
1904
+ }
1905
+ return sortByDependencies(pluginsWithSetup);
1906
+ }
1907
+ async function discoverPluginsWithReferences(options) {
1908
+ const { projectRoot, verbose, pluginFilter } = options;
1909
+ const allPlugins = await scanPlugins({
1910
+ projectRoot,
1911
+ verbose,
1912
+ discoverTransitive: true
1913
+ });
1914
+ const pluginsWithRefs = [];
1915
+ for (const [packageName, plugin] of allPlugins) {
1916
+ if (!plugin.manifest.setup?.references)
1917
+ continue;
1918
+ if (pluginFilter && plugin.name !== pluginFilter && packageName !== pluginFilter) {
1919
+ continue;
1920
+ }
1921
+ pluginsWithRefs.push({
1922
+ name: plugin.name,
1923
+ pluginPath: plugin.pluginPath,
1924
+ packageName: plugin.packageName,
1925
+ isLocal: plugin.isLocal,
1926
+ referencesHandler: plugin.manifest.setup.references,
1927
+ dependencies: plugin.dependencies
1928
+ });
1929
+ if (verbose) {
1930
+ getLogger().info(`[AgentKit] Found plugin with references: ${plugin.name}`);
1931
+ }
1932
+ }
1933
+ return sortByDependencies(pluginsWithRefs);
1934
+ }
1935
+ function sortByDependencies(plugins) {
1936
+ return [...plugins].sort((a, b) => {
1937
+ const aDepsOnB = a.dependencies.some((d) => d === b.name || d === b.packageName) ? 1 : 0;
1938
+ const bDepsOnA = b.dependencies.some((d) => d === a.name || d === a.packageName) ? 1 : 0;
1939
+ return aDepsOnB - bDepsOnA;
1940
+ });
1941
+ }
1942
+ async function executePluginSetup(plugin, options) {
1943
+ const { projectRoot, configDir, force, initError, viteServer, verbose } = options;
1944
+ const context = {
1945
+ pluginName: plugin.name,
1946
+ projectRoot,
1947
+ configDir,
1948
+ services: getServiceRegistry(),
1949
+ initError,
1950
+ force
1951
+ };
1952
+ const handler = await loadHandler(plugin, plugin.setupHandler, viteServer);
1953
+ return handler(context);
1954
+ }
1955
+ async function executePluginReferences(plugin, options) {
1956
+ const { projectRoot, force, initError, viteServer } = options;
1957
+ const referencesDir = path.join(projectRoot, "agent-kit", "references", plugin.name);
1958
+ const context = {
1959
+ pluginName: plugin.name,
1960
+ projectRoot,
1961
+ referencesDir,
1962
+ services: getServiceRegistry(),
1963
+ initError,
1964
+ force
1965
+ };
1966
+ const handler = await loadHandler(
1967
+ plugin,
1968
+ plugin.referencesHandler,
1969
+ viteServer
1970
+ );
1971
+ return handler(context);
1972
+ }
1973
+ async function loadHandler(plugin, handlerName, viteServer) {
1974
+ let module;
1975
+ if (plugin.isLocal) {
1976
+ const handlerPath = path.resolve(plugin.pluginPath, handlerName);
1977
+ if (viteServer) {
1978
+ module = await viteServer.ssrLoadModule(handlerPath);
1979
+ } else {
1980
+ module = await import(handlerPath);
1981
+ }
1982
+ if (typeof module[handlerName] === "function")
1983
+ return module[handlerName];
1984
+ if (typeof module.setup === "function")
1985
+ return module.setup;
1986
+ if (typeof module.default === "function")
1987
+ return module.default;
1988
+ throw new Error(
1989
+ `Handler "${handlerName}" not found in "${plugin.pluginPath}". Available exports: ${Object.keys(module).join(", ")}`
1990
+ );
1991
+ } else {
1992
+ if (viteServer) {
1993
+ module = await viteServer.ssrLoadModule(plugin.packageName);
1994
+ } else {
1995
+ module = await import(plugin.packageName);
1996
+ }
1997
+ if (typeof module[handlerName] !== "function") {
1998
+ throw new Error(
1999
+ `Handler "${handlerName}" not found as export in "${plugin.packageName}". Available exports: ${Object.keys(module).join(", ")}`
2000
+ );
2001
+ }
2002
+ return module[handlerName];
2003
+ }
2004
+ }
1149
2005
  export {
1150
2006
  ActionRegistry,
1151
2007
  DevSlowlyChangingPhase,
@@ -1159,8 +2015,12 @@ export {
1159
2015
  discoverAndRegisterActions,
1160
2016
  discoverPluginActions,
1161
2017
  discoverPluginsWithInit,
2018
+ discoverPluginsWithReferences,
2019
+ discoverPluginsWithSetup,
1162
2020
  executeAction,
2021
+ executePluginReferences,
1163
2022
  executePluginServerInits,
2023
+ executePluginSetup,
1164
2024
  generateClientScript,
1165
2025
  getActionCacheHeaders,
1166
2026
  getClientInitData,
@@ -1168,21 +2028,29 @@ export {
1168
2028
  getRegisteredAction,
1169
2029
  getRegisteredActionNames,
1170
2030
  getService,
2031
+ getServiceRegistry,
1171
2032
  hasAction,
1172
2033
  hasService,
2034
+ listContracts,
2035
+ loadActionMetadata,
1173
2036
  loadPageParts,
2037
+ materializeContracts,
1174
2038
  onInit,
1175
2039
  onShutdown,
2040
+ parseActionMetadata,
1176
2041
  preparePluginClientInits,
1177
2042
  registerAction,
1178
2043
  registerService,
1179
2044
  renderFastChangingData,
2045
+ resolveActionMetadataPath,
1180
2046
  resolveServices,
1181
- runAction,
1182
2047
  runInitCallbacks,
1183
2048
  runLoadParams,
1184
2049
  runShutdownCallbacks,
1185
2050
  runSlowlyChangingRender,
2051
+ scanPlugins,
1186
2052
  setClientInitData,
1187
- sortPluginsByDependencies
2053
+ slowRenderInstances,
2054
+ sortPluginsByDependencies,
2055
+ validateForEachInstances
1188
2056
  };