@jay-framework/stack-server-runtime 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +440 -4
- package/dist/index.js +859 -89
- package/package.json +13 -11
package/dist/index.js
CHANGED
|
@@ -17,6 +17,8 @@ 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";
|
|
21
|
+
import crypto from "node:crypto";
|
|
20
22
|
const serviceRegistry = /* @__PURE__ */ new Map();
|
|
21
23
|
function registerService(marker, service) {
|
|
22
24
|
serviceRegistry.set(marker, service);
|
|
@@ -39,9 +41,13 @@ function hasService(marker) {
|
|
|
39
41
|
function clearServiceRegistry() {
|
|
40
42
|
serviceRegistry.clear();
|
|
41
43
|
}
|
|
44
|
+
function getServiceRegistry() {
|
|
45
|
+
return serviceRegistry;
|
|
46
|
+
}
|
|
42
47
|
function resolveServices(serviceMarkers) {
|
|
43
48
|
return serviceMarkers.map((marker) => getService(marker));
|
|
44
49
|
}
|
|
50
|
+
globalThis.__JAY_SERVICE_RESOLVER__ = resolveServices;
|
|
45
51
|
const initCallbacks = [];
|
|
46
52
|
const shutdownCallbacks = [];
|
|
47
53
|
function onInit(callback) {
|
|
@@ -93,10 +99,17 @@ class DevSlowlyChangingPhase {
|
|
|
93
99
|
}
|
|
94
100
|
async runSlowlyForPage(pageParams, pageProps, parts) {
|
|
95
101
|
for (const part of parts) {
|
|
96
|
-
const { compDefinition } = part;
|
|
102
|
+
const { compDefinition, contractInfo } = part;
|
|
97
103
|
if (compDefinition.loadParams) {
|
|
98
104
|
const services = resolveServices(compDefinition.services);
|
|
99
|
-
const
|
|
105
|
+
const loadParamsArgs = contractInfo ? [
|
|
106
|
+
...services,
|
|
107
|
+
{
|
|
108
|
+
contractName: contractInfo.contractName,
|
|
109
|
+
metadata: contractInfo.metadata
|
|
110
|
+
}
|
|
111
|
+
] : services;
|
|
112
|
+
const compParams = compDefinition.loadParams(loadParamsArgs);
|
|
100
113
|
if (!await findMatchingParams(pageParams, compParams))
|
|
101
114
|
return notFound();
|
|
102
115
|
}
|
|
@@ -104,11 +117,19 @@ class DevSlowlyChangingPhase {
|
|
|
104
117
|
let slowlyViewState = {};
|
|
105
118
|
let carryForward = {};
|
|
106
119
|
for (const part of parts) {
|
|
107
|
-
const { compDefinition, key } = part;
|
|
120
|
+
const { compDefinition, key, contractInfo } = part;
|
|
108
121
|
if (compDefinition.slowlyRender) {
|
|
109
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
|
+
};
|
|
110
131
|
const slowlyRenderedPart = await compDefinition.slowlyRender(
|
|
111
|
-
|
|
132
|
+
partProps,
|
|
112
133
|
...services
|
|
113
134
|
);
|
|
114
135
|
if (slowlyRenderedPart.kind === "PhaseOutput") {
|
|
@@ -135,12 +156,20 @@ async function renderFastChangingData(pageParams, pageProps, carryForward, parts
|
|
|
135
156
|
let fastViewState = {};
|
|
136
157
|
let fastCarryForward = {};
|
|
137
158
|
for (const part of parts) {
|
|
138
|
-
const { compDefinition, key } = part;
|
|
159
|
+
const { compDefinition, key, contractInfo } = part;
|
|
139
160
|
if (compDefinition.fastRender) {
|
|
140
161
|
const partSlowlyCarryForward = key ? carryForward[key] : carryForward;
|
|
141
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
|
+
};
|
|
142
171
|
const fastRenderedPart = await compDefinition.fastRender(
|
|
143
|
-
|
|
172
|
+
partProps,
|
|
144
173
|
partSlowlyCarryForward,
|
|
145
174
|
...services
|
|
146
175
|
);
|
|
@@ -158,7 +187,9 @@ async function renderFastChangingData(pageParams, pageProps, carryForward, parts
|
|
|
158
187
|
}
|
|
159
188
|
return Promise.resolve(phaseOutput(fastViewState, fastCarryForward));
|
|
160
189
|
}
|
|
161
|
-
function generateClientScript(defaultViewState, fastCarryForward, parts, jayHtmlPath, trackByMap = {}, clientInitData2 = {}, projectInit, pluginInits = []) {
|
|
190
|
+
function generateClientScript(defaultViewState, fastCarryForward, parts, jayHtmlPath, trackByMap = {}, clientInitData2 = {}, projectInit, pluginInits = [], options = {}) {
|
|
191
|
+
const { enableAutomation = true, slowViewState } = options;
|
|
192
|
+
const hasSlowViewState = slowViewState && Object.keys(slowViewState).length > 0;
|
|
162
193
|
const imports = parts.length > 0 ? parts.map((part) => part.clientImport).join("\n") + "\n" : "";
|
|
163
194
|
const compositeParts = parts.length > 0 ? `[
|
|
164
195
|
${parts.map((part) => " " + part.clientPart).join(",\n")}
|
|
@@ -187,6 +218,27 @@ ${parts.map((part) => " " + part.clientPart).join(",\n")}
|
|
|
187
218
|
// Project client initialization
|
|
188
219
|
${projectInitCall}
|
|
189
220
|
` : "";
|
|
221
|
+
const automationImport = enableAutomation ? hasSlowViewState ? `import { wrapWithAutomation, AUTOMATION_CONTEXT } from "@jay-framework/runtime-automation";
|
|
222
|
+
import { registerGlobalContext } from "@jay-framework/runtime";
|
|
223
|
+
import { deepMergeViewStates } from "@jay-framework/view-state-merge";` : `import { wrapWithAutomation, AUTOMATION_CONTEXT } from "@jay-framework/runtime-automation";
|
|
224
|
+
import { registerGlobalContext } from "@jay-framework/runtime";` : "";
|
|
225
|
+
const slowViewStateDecl = enableAutomation && hasSlowViewState ? `const slowViewState = ${JSON.stringify(slowViewState)};` : "";
|
|
226
|
+
const automationWrap = enableAutomation ? hasSlowViewState ? `
|
|
227
|
+
// Wrap with automation for dev tooling
|
|
228
|
+
// Deep merge slow+fast ViewState so automation can see full page state
|
|
229
|
+
const fullViewState = deepMergeViewStates(slowViewState, {...viewState, ...fastCarryForward}, trackByMap);
|
|
230
|
+
const wrapped = wrapWithAutomation(instance, { initialViewState: fullViewState, trackByMap });
|
|
231
|
+
registerGlobalContext(AUTOMATION_CONTEXT, wrapped.automation);
|
|
232
|
+
window.__jay = window.__jay || {};
|
|
233
|
+
window.__jay.automation = wrapped.automation;
|
|
234
|
+
target.appendChild(wrapped.element.dom);` : `
|
|
235
|
+
// Wrap with automation for dev tooling
|
|
236
|
+
const wrapped = wrapWithAutomation(instance);
|
|
237
|
+
registerGlobalContext(AUTOMATION_CONTEXT, wrapped.automation);
|
|
238
|
+
window.__jay = window.__jay || {};
|
|
239
|
+
window.__jay.automation = wrapped.automation;
|
|
240
|
+
target.appendChild(wrapped.element.dom);` : `
|
|
241
|
+
target.appendChild(instance.element.dom);`;
|
|
190
242
|
return `<!doctype html>
|
|
191
243
|
<html lang="en">
|
|
192
244
|
<head>
|
|
@@ -198,10 +250,11 @@ ${parts.map((part) => " " + part.clientPart).join(",\n")}
|
|
|
198
250
|
<div id="target"></div>
|
|
199
251
|
<script type="module">
|
|
200
252
|
import {makeCompositeJayComponent} from "@jay-framework/stack-client-runtime";
|
|
253
|
+
${automationImport}
|
|
201
254
|
${pluginClientInitImports}
|
|
202
255
|
${projectInitImport}
|
|
203
256
|
import { render } from '${jayHtmlPath}';
|
|
204
|
-
${imports}
|
|
257
|
+
${imports}${slowViewStateDecl}
|
|
205
258
|
const viewState = ${JSON.stringify(defaultViewState)};
|
|
206
259
|
const fastCarryForward = ${JSON.stringify(fastCarryForward)};
|
|
207
260
|
const trackByMap = ${JSON.stringify(trackByMap)};
|
|
@@ -209,26 +262,26 @@ ${clientInitExecution}
|
|
|
209
262
|
const target = document.getElementById('target');
|
|
210
263
|
const pageComp = makeCompositeJayComponent(render, viewState, fastCarryForward, ${compositeParts}, trackByMap)
|
|
211
264
|
|
|
212
|
-
const instance = pageComp({
|
|
213
|
-
|
|
265
|
+
const instance = pageComp({/* placeholder for page props */})
|
|
266
|
+
${automationWrap}
|
|
214
267
|
<\/script>
|
|
215
268
|
</body>
|
|
216
269
|
</html>`;
|
|
217
270
|
}
|
|
218
|
-
const require$
|
|
219
|
-
async function loadPageParts(vite, route, pagesBase, projectBase, jayRollupConfig) {
|
|
271
|
+
const require$3 = createRequire(import.meta.url);
|
|
272
|
+
async function loadPageParts(vite, route, pagesBase, projectBase, jayRollupConfig, options) {
|
|
220
273
|
const exists = await fs$1.access(route.compPath, fs$1.constants.F_OK).then(() => true).catch(() => false);
|
|
221
274
|
const parts = [];
|
|
222
275
|
if (exists) {
|
|
223
276
|
const pageComponent = (await vite.ssrLoadModule(route.compPath)).page;
|
|
224
277
|
parts.push({
|
|
225
278
|
compDefinition: pageComponent,
|
|
226
|
-
// Client import uses client-only code (server code stripped)
|
|
227
279
|
clientImport: `import {page} from '${route.compPath}'`,
|
|
228
|
-
clientPart: `{comp: page.comp, contextMarkers: []}`
|
|
280
|
+
clientPart: `{comp: page.comp, contextMarkers: page.contexts || []}`
|
|
229
281
|
});
|
|
230
282
|
}
|
|
231
|
-
const
|
|
283
|
+
const jayHtmlFilePath = options?.preRenderedPath ?? route.jayHtmlPath;
|
|
284
|
+
const jayHtmlSource = (await fs$1.readFile(jayHtmlFilePath)).toString();
|
|
232
285
|
const fileName = path__default.basename(route.jayHtmlPath);
|
|
233
286
|
const dirName = path__default.dirname(route.jayHtmlPath);
|
|
234
287
|
const jayHtmlWithValidations = await parseJayFile(
|
|
@@ -243,11 +296,12 @@ async function loadPageParts(vite, route, pagesBase, projectBase, jayRollupConfi
|
|
|
243
296
|
);
|
|
244
297
|
return jayHtmlWithValidations.mapAsync(async (jayHtml) => {
|
|
245
298
|
const usedPackages = /* @__PURE__ */ new Set();
|
|
299
|
+
const headlessInstanceComponents = [];
|
|
246
300
|
for await (const headlessImport of jayHtml.headlessImports) {
|
|
247
301
|
const module = headlessImport.codeLink.module;
|
|
248
302
|
const name = headlessImport.codeLink.names[0].name;
|
|
249
303
|
const isLocalModule = module[0] === "." || module[0] === "/";
|
|
250
|
-
const modulePath = isLocalModule ? path__default.resolve(dirName, module) : require$
|
|
304
|
+
const modulePath = isLocalModule ? path__default.resolve(dirName, module) : require$3.resolve(module, { paths: require$3.resolve.paths(dirName) });
|
|
251
305
|
const compDefinition = (await vite.ssrLoadModule(modulePath))[name];
|
|
252
306
|
const moduleImport = isLocalModule ? path__default.resolve(dirName, module) : module;
|
|
253
307
|
const isNpmPackage = !isLocalModule;
|
|
@@ -256,23 +310,85 @@ async function loadPageParts(vite, route, pagesBase, projectBase, jayRollupConfi
|
|
|
256
310
|
const packageName = module.startsWith("@") ? module.split("/").slice(0, 2).join("/") : module.split("/")[0];
|
|
257
311
|
usedPackages.add(packageName);
|
|
258
312
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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
|
+
}
|
|
267
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
|
+
}));
|
|
268
341
|
return {
|
|
269
342
|
parts,
|
|
270
343
|
serverTrackByMap: jayHtml.serverTrackByMap,
|
|
271
344
|
clientTrackByMap: jayHtml.clientTrackByMap,
|
|
272
|
-
usedPackages
|
|
345
|
+
usedPackages,
|
|
346
|
+
headlessContracts,
|
|
347
|
+
headlessInstanceComponents
|
|
273
348
|
};
|
|
274
349
|
});
|
|
275
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
|
+
}
|
|
276
392
|
class ActionRegistry {
|
|
277
393
|
constructor() {
|
|
278
394
|
__publicField(this, "actions", /* @__PURE__ */ new Map());
|
|
@@ -516,7 +632,7 @@ new Proxy(e, {
|
|
|
516
632
|
return t[r];
|
|
517
633
|
}
|
|
518
634
|
});
|
|
519
|
-
const require$
|
|
635
|
+
const require$2 = createRequire$1(import.meta.url);
|
|
520
636
|
async function discoverAndRegisterActions(options) {
|
|
521
637
|
const {
|
|
522
638
|
projectRoot,
|
|
@@ -533,13 +649,13 @@ async function discoverAndRegisterActions(options) {
|
|
|
533
649
|
const actionsPath = path.resolve(projectRoot, actionsDir);
|
|
534
650
|
if (!fs.existsSync(actionsPath)) {
|
|
535
651
|
if (verbose) {
|
|
536
|
-
|
|
652
|
+
getLogger().info(`[Actions] No actions directory found at ${actionsPath}`);
|
|
537
653
|
}
|
|
538
654
|
return result;
|
|
539
655
|
}
|
|
540
656
|
const actionFiles = await findActionFiles(actionsPath);
|
|
541
657
|
if (verbose) {
|
|
542
|
-
|
|
658
|
+
getLogger().info(`[Actions] Found ${actionFiles.length} action file(s)`);
|
|
543
659
|
}
|
|
544
660
|
for (const filePath of actionFiles) {
|
|
545
661
|
result.scannedFiles.push(filePath);
|
|
@@ -556,12 +672,14 @@ async function discoverAndRegisterActions(options) {
|
|
|
556
672
|
result.actionNames.push(exportValue.actionName);
|
|
557
673
|
result.actionCount++;
|
|
558
674
|
if (verbose) {
|
|
559
|
-
|
|
675
|
+
getLogger().info(
|
|
676
|
+
`[Actions] Registered: ${exportValue.actionName}`
|
|
677
|
+
);
|
|
560
678
|
}
|
|
561
679
|
}
|
|
562
680
|
}
|
|
563
681
|
} catch (error) {
|
|
564
|
-
|
|
682
|
+
getLogger().error(`[Actions] Failed to import ${filePath}: ${error}`);
|
|
565
683
|
}
|
|
566
684
|
}
|
|
567
685
|
return result;
|
|
@@ -628,9 +746,8 @@ async function discoverNpmPluginActions(projectRoot, registry, verbose, viteServ
|
|
|
628
746
|
continue;
|
|
629
747
|
}
|
|
630
748
|
if (verbose) {
|
|
631
|
-
|
|
632
|
-
`[Actions] NPM plugin "${packageName}" declares actions
|
|
633
|
-
pluginConfig.actions
|
|
749
|
+
getLogger().info(
|
|
750
|
+
`[Actions] NPM plugin "${packageName}" declares actions: ${JSON.stringify(pluginConfig.actions)}`
|
|
634
751
|
);
|
|
635
752
|
}
|
|
636
753
|
const actions = await registerNpmPluginActions(
|
|
@@ -646,13 +763,13 @@ async function discoverNpmPluginActions(projectRoot, registry, verbose, viteServ
|
|
|
646
763
|
}
|
|
647
764
|
}
|
|
648
765
|
} catch (error) {
|
|
649
|
-
|
|
766
|
+
getLogger().error(`[Actions] Failed to read project package.json: ${error}`);
|
|
650
767
|
}
|
|
651
768
|
return allActions;
|
|
652
769
|
}
|
|
653
770
|
function tryResolvePluginYaml(packageName, projectRoot) {
|
|
654
771
|
try {
|
|
655
|
-
return require$
|
|
772
|
+
return require$2.resolve(`${packageName}/plugin.yaml`, {
|
|
656
773
|
paths: [projectRoot]
|
|
657
774
|
});
|
|
658
775
|
} catch {
|
|
@@ -674,18 +791,18 @@ async function registerNpmPluginActions(packageName, pluginConfig, registry, ver
|
|
|
674
791
|
registry.register(actionExport);
|
|
675
792
|
registeredActions.push(actionExport.actionName);
|
|
676
793
|
if (verbose) {
|
|
677
|
-
|
|
794
|
+
getLogger().info(
|
|
678
795
|
`[Actions] Registered NPM plugin action: ${actionExport.actionName}`
|
|
679
796
|
);
|
|
680
797
|
}
|
|
681
798
|
} else {
|
|
682
|
-
|
|
799
|
+
getLogger().warn(
|
|
683
800
|
`[Actions] NPM plugin "${packageName}" declares action "${actionName}" but it's not exported or not a JayAction`
|
|
684
801
|
);
|
|
685
802
|
}
|
|
686
803
|
}
|
|
687
804
|
} catch (importError) {
|
|
688
|
-
|
|
805
|
+
getLogger().error(`[Actions] Failed to import NPM plugin "${packageName}": ${importError}`);
|
|
689
806
|
}
|
|
690
807
|
return registeredActions;
|
|
691
808
|
}
|
|
@@ -700,7 +817,9 @@ async function discoverPluginActions(pluginPath, projectRoot, registry = actionR
|
|
|
700
817
|
const registeredActions = [];
|
|
701
818
|
const pluginName = pluginConfig.name || path.basename(pluginPath);
|
|
702
819
|
if (verbose) {
|
|
703
|
-
|
|
820
|
+
getLogger().info(
|
|
821
|
+
`[Actions] Plugin "${pluginName}" declares actions: ${JSON.stringify(pluginConfig.actions)}`
|
|
822
|
+
);
|
|
704
823
|
}
|
|
705
824
|
let modulePath = pluginConfig.module ? path.join(pluginPath, pluginConfig.module) : path.join(pluginPath, "index.ts");
|
|
706
825
|
if (!fs.existsSync(modulePath)) {
|
|
@@ -711,7 +830,7 @@ async function discoverPluginActions(pluginPath, projectRoot, registry = actionR
|
|
|
711
830
|
} else if (fs.existsSync(jsPath)) {
|
|
712
831
|
modulePath = jsPath;
|
|
713
832
|
} else {
|
|
714
|
-
|
|
833
|
+
getLogger().warn(`[Actions] Plugin "${pluginName}" module not found at ${modulePath}`);
|
|
715
834
|
return [];
|
|
716
835
|
}
|
|
717
836
|
}
|
|
@@ -728,25 +847,33 @@ async function discoverPluginActions(pluginPath, projectRoot, registry = actionR
|
|
|
728
847
|
registry.register(actionExport);
|
|
729
848
|
registeredActions.push(actionExport.actionName);
|
|
730
849
|
if (verbose) {
|
|
731
|
-
|
|
850
|
+
getLogger().info(
|
|
732
851
|
`[Actions] Registered plugin action: ${actionExport.actionName}`
|
|
733
852
|
);
|
|
734
853
|
}
|
|
735
854
|
} else {
|
|
736
|
-
|
|
855
|
+
getLogger().warn(
|
|
737
856
|
`[Actions] Plugin "${pluginName}" declares action "${actionName}" but it's not exported or not a JayAction`
|
|
738
857
|
);
|
|
739
858
|
}
|
|
740
859
|
}
|
|
741
860
|
} catch (importError) {
|
|
742
|
-
|
|
861
|
+
getLogger().error(
|
|
862
|
+
`[Actions] Failed to import plugin module at ${modulePath}: ${importError}`
|
|
863
|
+
);
|
|
743
864
|
}
|
|
744
865
|
return registeredActions;
|
|
745
866
|
}
|
|
746
|
-
const
|
|
747
|
-
async function
|
|
748
|
-
const {
|
|
749
|
-
|
|
867
|
+
const require$1 = createRequire$1(import.meta.url);
|
|
868
|
+
async function scanPlugins(options) {
|
|
869
|
+
const {
|
|
870
|
+
projectRoot,
|
|
871
|
+
verbose = false,
|
|
872
|
+
includeDevDeps = false,
|
|
873
|
+
discoverTransitive = false
|
|
874
|
+
} = options;
|
|
875
|
+
const plugins = /* @__PURE__ */ new Map();
|
|
876
|
+
const visitedPackages = /* @__PURE__ */ new Set();
|
|
750
877
|
const localPluginsPath = path.join(projectRoot, "src/plugins");
|
|
751
878
|
if (fs.existsSync(localPluginsPath)) {
|
|
752
879
|
try {
|
|
@@ -758,42 +885,45 @@ async function discoverPluginsWithInit(options) {
|
|
|
758
885
|
const manifest = loadPluginManifest(pluginPath);
|
|
759
886
|
if (!manifest)
|
|
760
887
|
continue;
|
|
761
|
-
const initConfig = resolvePluginInit(pluginPath, manifest.init, true);
|
|
762
|
-
if (!initConfig)
|
|
763
|
-
continue;
|
|
764
888
|
const dependencies = await getPackageDependencies(pluginPath);
|
|
765
|
-
|
|
766
|
-
|
|
889
|
+
const pluginName = manifest.name || entry.name;
|
|
890
|
+
plugins.set(entry.name, {
|
|
891
|
+
name: pluginName,
|
|
767
892
|
pluginPath,
|
|
768
893
|
packageName: pluginPath,
|
|
769
|
-
// For local, use path
|
|
894
|
+
// For local, use path as identifier
|
|
770
895
|
isLocal: true,
|
|
771
|
-
|
|
772
|
-
initExport: initConfig.export,
|
|
896
|
+
manifest,
|
|
773
897
|
dependencies
|
|
774
898
|
});
|
|
899
|
+
visitedPackages.add(pluginPath);
|
|
775
900
|
if (verbose) {
|
|
776
|
-
|
|
777
|
-
`[PluginInit] Found local plugin with init: ${manifest.name || entry.name}`
|
|
778
|
-
);
|
|
901
|
+
getLogger().info(`[PluginScanner] Found local plugin: ${pluginName}`);
|
|
779
902
|
}
|
|
780
903
|
}
|
|
781
904
|
} catch (error) {
|
|
782
|
-
|
|
905
|
+
if (verbose) {
|
|
906
|
+
getLogger().warn(`[PluginScanner] Failed to scan local plugins: ${error}`);
|
|
907
|
+
}
|
|
783
908
|
}
|
|
784
909
|
}
|
|
785
910
|
const projectPackageJsonPath = path.join(projectRoot, "package.json");
|
|
786
911
|
if (fs.existsSync(projectPackageJsonPath)) {
|
|
787
912
|
try {
|
|
788
913
|
const projectPackageJson = JSON.parse(fs.readFileSync(projectPackageJsonPath, "utf-8"));
|
|
789
|
-
const
|
|
914
|
+
const deps = {
|
|
790
915
|
...projectPackageJson.dependencies,
|
|
791
|
-
...projectPackageJson.devDependencies
|
|
916
|
+
...includeDevDeps ? projectPackageJson.devDependencies : {}
|
|
792
917
|
};
|
|
793
|
-
|
|
918
|
+
const packagesToCheck = Object.keys(deps);
|
|
919
|
+
while (packagesToCheck.length > 0) {
|
|
920
|
+
const depName = packagesToCheck.shift();
|
|
921
|
+
if (visitedPackages.has(depName))
|
|
922
|
+
continue;
|
|
923
|
+
visitedPackages.add(depName);
|
|
794
924
|
let pluginYamlPath;
|
|
795
925
|
try {
|
|
796
|
-
pluginYamlPath =
|
|
926
|
+
pluginYamlPath = require$1.resolve(`${depName}/plugin.yaml`, {
|
|
797
927
|
paths: [projectRoot]
|
|
798
928
|
});
|
|
799
929
|
} catch {
|
|
@@ -803,25 +933,76 @@ async function discoverPluginsWithInit(options) {
|
|
|
803
933
|
const manifest = loadPluginManifest(pluginPath);
|
|
804
934
|
if (!manifest)
|
|
805
935
|
continue;
|
|
806
|
-
const initConfig = resolvePluginInit(pluginPath, manifest.init, false);
|
|
807
|
-
if (!initConfig)
|
|
808
|
-
continue;
|
|
809
936
|
const dependencies = await getPackageDependencies(pluginPath);
|
|
810
|
-
plugins.
|
|
937
|
+
plugins.set(depName, {
|
|
811
938
|
name: manifest.name || depName,
|
|
812
939
|
pluginPath,
|
|
813
940
|
packageName: depName,
|
|
814
941
|
isLocal: false,
|
|
815
|
-
|
|
816
|
-
initExport: initConfig.export,
|
|
942
|
+
manifest,
|
|
817
943
|
dependencies
|
|
818
944
|
});
|
|
819
945
|
if (verbose) {
|
|
820
|
-
|
|
946
|
+
getLogger().info(`[PluginScanner] Found NPM plugin: ${depName}`);
|
|
947
|
+
}
|
|
948
|
+
if (discoverTransitive) {
|
|
949
|
+
for (const transitiveDep of dependencies) {
|
|
950
|
+
if (!visitedPackages.has(transitiveDep)) {
|
|
951
|
+
packagesToCheck.push(transitiveDep);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
821
954
|
}
|
|
822
955
|
}
|
|
823
956
|
} catch (error) {
|
|
824
|
-
|
|
957
|
+
if (verbose) {
|
|
958
|
+
getLogger().warn(`[PluginScanner] Failed to scan NPM plugins: ${error}`);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
return plugins;
|
|
963
|
+
}
|
|
964
|
+
async function getPackageDependencies(pluginPath) {
|
|
965
|
+
const packageJsonPath = path.join(pluginPath, "package.json");
|
|
966
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
967
|
+
return [];
|
|
968
|
+
}
|
|
969
|
+
try {
|
|
970
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
971
|
+
return Object.keys(packageJson.dependencies || {});
|
|
972
|
+
} catch {
|
|
973
|
+
return [];
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
async function discoverPluginsWithInit(options) {
|
|
977
|
+
const { projectRoot, verbose = false } = options;
|
|
978
|
+
const scannedPlugins = await scanPlugins({
|
|
979
|
+
projectRoot,
|
|
980
|
+
verbose,
|
|
981
|
+
includeDevDeps: false,
|
|
982
|
+
// Only runtime dependencies
|
|
983
|
+
discoverTransitive: true
|
|
984
|
+
// Need transitive for dependency ordering
|
|
985
|
+
});
|
|
986
|
+
const plugins = [];
|
|
987
|
+
for (const [key, scanned] of scannedPlugins) {
|
|
988
|
+
const initConfig = resolvePluginInit(
|
|
989
|
+
scanned.pluginPath,
|
|
990
|
+
scanned.manifest.init,
|
|
991
|
+
scanned.isLocal
|
|
992
|
+
);
|
|
993
|
+
if (!initConfig)
|
|
994
|
+
continue;
|
|
995
|
+
plugins.push({
|
|
996
|
+
name: scanned.name,
|
|
997
|
+
pluginPath: scanned.pluginPath,
|
|
998
|
+
packageName: scanned.packageName,
|
|
999
|
+
isLocal: scanned.isLocal,
|
|
1000
|
+
initModule: initConfig.module,
|
|
1001
|
+
initExport: initConfig.export,
|
|
1002
|
+
dependencies: scanned.dependencies
|
|
1003
|
+
});
|
|
1004
|
+
if (verbose) {
|
|
1005
|
+
getLogger().info(`[PluginInit] Found plugin with init: ${scanned.name}`);
|
|
825
1006
|
}
|
|
826
1007
|
}
|
|
827
1008
|
return plugins;
|
|
@@ -861,18 +1042,6 @@ function resolvePluginInit(pluginPath, initConfig, isLocal) {
|
|
|
861
1042
|
return null;
|
|
862
1043
|
}
|
|
863
1044
|
}
|
|
864
|
-
async function getPackageDependencies(pluginPath) {
|
|
865
|
-
const packageJsonPath = path.join(pluginPath, "package.json");
|
|
866
|
-
if (!fs.existsSync(packageJsonPath)) {
|
|
867
|
-
return [];
|
|
868
|
-
}
|
|
869
|
-
try {
|
|
870
|
-
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
871
|
-
return Object.keys(packageJson.dependencies || {});
|
|
872
|
-
} catch {
|
|
873
|
-
return [];
|
|
874
|
-
}
|
|
875
|
-
}
|
|
876
1045
|
function sortPluginsByDependencies(plugins) {
|
|
877
1046
|
const pluginNames = new Set(plugins.map((p) => p.packageName));
|
|
878
1047
|
const sorted = [];
|
|
@@ -882,7 +1051,7 @@ function sortPluginsByDependencies(plugins) {
|
|
|
882
1051
|
if (visited.has(plugin.packageName))
|
|
883
1052
|
return;
|
|
884
1053
|
if (visiting.has(plugin.packageName)) {
|
|
885
|
-
|
|
1054
|
+
getLogger().warn(`[PluginInit] Circular dependency detected for ${plugin.name}`);
|
|
886
1055
|
return;
|
|
887
1056
|
}
|
|
888
1057
|
visiting.add(plugin.packageName);
|
|
@@ -922,14 +1091,14 @@ async function executePluginServerInits(plugins, viteServer, verbose = false) {
|
|
|
922
1091
|
}
|
|
923
1092
|
const jayInit = pluginModule[plugin.initExport];
|
|
924
1093
|
if (!jayInit || jayInit.__brand !== "JayInit") {
|
|
925
|
-
|
|
1094
|
+
getLogger().warn(
|
|
926
1095
|
`[PluginInit] Plugin "${plugin.name}" init module doesn't export a valid JayInit at "${plugin.initExport}"`
|
|
927
1096
|
);
|
|
928
1097
|
continue;
|
|
929
1098
|
}
|
|
930
1099
|
if (typeof jayInit._serverInit === "function") {
|
|
931
1100
|
if (verbose) {
|
|
932
|
-
|
|
1101
|
+
getLogger().info(`[DevServer] Running server init: ${plugin.name}`);
|
|
933
1102
|
}
|
|
934
1103
|
const clientData = await jayInit._serverInit();
|
|
935
1104
|
if (clientData !== void 0 && clientData !== null) {
|
|
@@ -937,9 +1106,8 @@ async function executePluginServerInits(plugins, viteServer, verbose = false) {
|
|
|
937
1106
|
}
|
|
938
1107
|
}
|
|
939
1108
|
} catch (error) {
|
|
940
|
-
|
|
941
|
-
`[PluginInit] Failed to execute server init for "${plugin.name}"
|
|
942
|
-
error
|
|
1109
|
+
getLogger().error(
|
|
1110
|
+
`[PluginInit] Failed to execute server init for "${plugin.name}": ${error}`
|
|
943
1111
|
);
|
|
944
1112
|
}
|
|
945
1113
|
}
|
|
@@ -961,9 +1129,602 @@ function preparePluginClientInits(plugins) {
|
|
|
961
1129
|
};
|
|
962
1130
|
});
|
|
963
1131
|
}
|
|
1132
|
+
function makeCacheKey(jayHtmlPath, params) {
|
|
1133
|
+
const sortedParams = Object.keys(params).sort().reduce(
|
|
1134
|
+
(acc, key) => {
|
|
1135
|
+
acc[key] = params[key];
|
|
1136
|
+
return acc;
|
|
1137
|
+
},
|
|
1138
|
+
{}
|
|
1139
|
+
);
|
|
1140
|
+
return `${jayHtmlPath}:${JSON.stringify(sortedParams)}`;
|
|
1141
|
+
}
|
|
1142
|
+
function hashParams(params) {
|
|
1143
|
+
const sortedParams = Object.keys(params).sort().reduce(
|
|
1144
|
+
(acc, key) => {
|
|
1145
|
+
acc[key] = params[key];
|
|
1146
|
+
return acc;
|
|
1147
|
+
},
|
|
1148
|
+
{}
|
|
1149
|
+
);
|
|
1150
|
+
const json = JSON.stringify(sortedParams);
|
|
1151
|
+
if (json === "{}")
|
|
1152
|
+
return "";
|
|
1153
|
+
return "_" + crypto.createHash("md5").update(json).digest("hex").substring(0, 8);
|
|
1154
|
+
}
|
|
1155
|
+
class SlowRenderCache {
|
|
1156
|
+
/**
|
|
1157
|
+
* @param cacheDir - Directory where pre-rendered jay-html files are stored
|
|
1158
|
+
* @param pagesRoot - Root directory of the pages (for relative path calculation)
|
|
1159
|
+
*/
|
|
1160
|
+
constructor(cacheDir, pagesRoot) {
|
|
1161
|
+
__publicField(this, "cache", /* @__PURE__ */ new Map());
|
|
1162
|
+
__publicField(this, "pathToKeys", /* @__PURE__ */ new Map());
|
|
1163
|
+
__publicField(this, "cacheDir");
|
|
1164
|
+
__publicField(this, "pagesRoot");
|
|
1165
|
+
this.cacheDir = cacheDir;
|
|
1166
|
+
this.pagesRoot = pagesRoot;
|
|
1167
|
+
}
|
|
1168
|
+
/**
|
|
1169
|
+
* Get a cached pre-rendered jay-html entry
|
|
1170
|
+
*/
|
|
1171
|
+
get(jayHtmlPath, params) {
|
|
1172
|
+
const key = makeCacheKey(jayHtmlPath, params);
|
|
1173
|
+
return this.cache.get(key);
|
|
1174
|
+
}
|
|
1175
|
+
/**
|
|
1176
|
+
* Store a pre-rendered jay-html entry in the cache.
|
|
1177
|
+
* Writes the pre-rendered content to disk and stores metadata in memory.
|
|
1178
|
+
*/
|
|
1179
|
+
async set(jayHtmlPath, params, preRenderedJayHtml, slowViewState, carryForward) {
|
|
1180
|
+
const key = makeCacheKey(jayHtmlPath, params);
|
|
1181
|
+
const relativePath = path__default.relative(this.pagesRoot, jayHtmlPath);
|
|
1182
|
+
const dir = path__default.dirname(relativePath);
|
|
1183
|
+
const basename = path__default.basename(relativePath, ".jay-html");
|
|
1184
|
+
const paramsHash = hashParams(params);
|
|
1185
|
+
const cacheFileName = `${basename}${paramsHash}.jay-html`;
|
|
1186
|
+
const preRenderedPath = path__default.join(this.cacheDir, dir, cacheFileName);
|
|
1187
|
+
await fs$1.mkdir(path__default.dirname(preRenderedPath), { recursive: true });
|
|
1188
|
+
await fs$1.writeFile(preRenderedPath, preRenderedJayHtml, "utf-8");
|
|
1189
|
+
if (!this.pathToKeys.has(jayHtmlPath)) {
|
|
1190
|
+
this.pathToKeys.set(jayHtmlPath, /* @__PURE__ */ new Set());
|
|
1191
|
+
}
|
|
1192
|
+
this.pathToKeys.get(jayHtmlPath).add(key);
|
|
1193
|
+
const entry = {
|
|
1194
|
+
preRenderedPath,
|
|
1195
|
+
slowViewState,
|
|
1196
|
+
carryForward,
|
|
1197
|
+
createdAt: Date.now(),
|
|
1198
|
+
sourcePath: jayHtmlPath
|
|
1199
|
+
};
|
|
1200
|
+
this.cache.set(key, entry);
|
|
1201
|
+
return preRenderedPath;
|
|
1202
|
+
}
|
|
1203
|
+
/**
|
|
1204
|
+
* Check if a pre-rendered entry exists for the given path and params
|
|
1205
|
+
*/
|
|
1206
|
+
has(jayHtmlPath, params) {
|
|
1207
|
+
const key = makeCacheKey(jayHtmlPath, params);
|
|
1208
|
+
return this.cache.has(key);
|
|
1209
|
+
}
|
|
1210
|
+
/**
|
|
1211
|
+
* Invalidate all cached entries for a given jay-html source path.
|
|
1212
|
+
* This is called when the source file changes.
|
|
1213
|
+
* Also deletes the cached files from disk.
|
|
1214
|
+
*/
|
|
1215
|
+
async invalidate(jayHtmlPath) {
|
|
1216
|
+
const keys = this.pathToKeys.get(jayHtmlPath);
|
|
1217
|
+
if (keys) {
|
|
1218
|
+
for (const key of keys) {
|
|
1219
|
+
const entry = this.cache.get(key);
|
|
1220
|
+
if (entry) {
|
|
1221
|
+
try {
|
|
1222
|
+
await fs$1.unlink(entry.preRenderedPath);
|
|
1223
|
+
} catch {
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
this.cache.delete(key);
|
|
1227
|
+
}
|
|
1228
|
+
this.pathToKeys.delete(jayHtmlPath);
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
/**
|
|
1232
|
+
* Invalidate all entries that depend on a changed file.
|
|
1233
|
+
* The changedPath could be:
|
|
1234
|
+
* - A jay-html file itself
|
|
1235
|
+
* - A component file (page.ts)
|
|
1236
|
+
* - Any other dependency
|
|
1237
|
+
*
|
|
1238
|
+
* @param changedPath - Absolute path to the changed file
|
|
1239
|
+
* @param resolveDependencies - Optional function to resolve which jay-html files depend on the changed file
|
|
1240
|
+
*/
|
|
1241
|
+
async invalidateByDependency(changedPath, resolveDependencies) {
|
|
1242
|
+
if (changedPath.endsWith(".jay-html")) {
|
|
1243
|
+
await this.invalidate(changedPath);
|
|
1244
|
+
return;
|
|
1245
|
+
}
|
|
1246
|
+
if (resolveDependencies) {
|
|
1247
|
+
const dependentPaths = resolveDependencies(changedPath);
|
|
1248
|
+
for (const depPath of dependentPaths) {
|
|
1249
|
+
await this.invalidate(depPath);
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
/**
|
|
1254
|
+
* Clear all cached entries and delete cached files from disk
|
|
1255
|
+
*/
|
|
1256
|
+
async clear() {
|
|
1257
|
+
for (const entry of this.cache.values()) {
|
|
1258
|
+
try {
|
|
1259
|
+
await fs$1.unlink(entry.preRenderedPath);
|
|
1260
|
+
} catch {
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
this.cache.clear();
|
|
1264
|
+
this.pathToKeys.clear();
|
|
1265
|
+
}
|
|
1266
|
+
/**
|
|
1267
|
+
* Get the number of cached entries
|
|
1268
|
+
*/
|
|
1269
|
+
get size() {
|
|
1270
|
+
return this.cache.size;
|
|
1271
|
+
}
|
|
1272
|
+
/**
|
|
1273
|
+
* Get all cached jay-html paths (for debugging/monitoring)
|
|
1274
|
+
*/
|
|
1275
|
+
getCachedPaths() {
|
|
1276
|
+
return Array.from(this.pathToKeys.keys());
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
const require2 = createRequire(import.meta.url);
|
|
1280
|
+
async function executeDynamicGenerator(plugin, config, projectRoot, services, verbose, viteServer) {
|
|
1281
|
+
const { pluginPath, name: pluginName, isLocal, packageName } = plugin;
|
|
1282
|
+
if (!config.generator) {
|
|
1283
|
+
throw new Error(
|
|
1284
|
+
`Plugin "${pluginName}" has dynamic_contracts entry but no generator specified`
|
|
1285
|
+
);
|
|
1286
|
+
}
|
|
1287
|
+
const isFilePath = config.generator.startsWith("./") || config.generator.startsWith("/") || config.generator.includes(".ts") || config.generator.includes(".js");
|
|
1288
|
+
let generator;
|
|
1289
|
+
if (isFilePath) {
|
|
1290
|
+
let generatorPath;
|
|
1291
|
+
if (!isLocal) {
|
|
1292
|
+
try {
|
|
1293
|
+
generatorPath = require2.resolve(`${packageName}/${config.generator}`, {
|
|
1294
|
+
paths: [projectRoot]
|
|
1295
|
+
});
|
|
1296
|
+
} catch {
|
|
1297
|
+
generatorPath = path.join(pluginPath, config.generator);
|
|
1298
|
+
}
|
|
1299
|
+
} else {
|
|
1300
|
+
generatorPath = path.join(pluginPath, config.generator);
|
|
1301
|
+
}
|
|
1302
|
+
if (!fs.existsSync(generatorPath)) {
|
|
1303
|
+
const withTs = generatorPath + ".ts";
|
|
1304
|
+
const withJs = generatorPath + ".js";
|
|
1305
|
+
if (fs.existsSync(withTs)) {
|
|
1306
|
+
generatorPath = withTs;
|
|
1307
|
+
} else if (fs.existsSync(withJs)) {
|
|
1308
|
+
generatorPath = withJs;
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
if (!fs.existsSync(generatorPath)) {
|
|
1312
|
+
throw new Error(
|
|
1313
|
+
`Generator file not found for plugin "${pluginName}": ${config.generator}`
|
|
1314
|
+
);
|
|
1315
|
+
}
|
|
1316
|
+
if (verbose) {
|
|
1317
|
+
getLogger().info(` Loading generator from file: ${generatorPath}`);
|
|
1318
|
+
}
|
|
1319
|
+
let generatorModule;
|
|
1320
|
+
if (viteServer) {
|
|
1321
|
+
generatorModule = await viteServer.ssrLoadModule(generatorPath);
|
|
1322
|
+
} else {
|
|
1323
|
+
generatorModule = await import(generatorPath);
|
|
1324
|
+
}
|
|
1325
|
+
generator = generatorModule.generator || generatorModule.default;
|
|
1326
|
+
} else {
|
|
1327
|
+
if (verbose) {
|
|
1328
|
+
getLogger().info(
|
|
1329
|
+
` Loading generator export: ${config.generator} from ${packageName}`
|
|
1330
|
+
);
|
|
1331
|
+
}
|
|
1332
|
+
let pluginModule;
|
|
1333
|
+
if (viteServer) {
|
|
1334
|
+
pluginModule = await viteServer.ssrLoadModule(packageName);
|
|
1335
|
+
} else {
|
|
1336
|
+
pluginModule = await import(packageName);
|
|
1337
|
+
}
|
|
1338
|
+
generator = pluginModule[config.generator];
|
|
1339
|
+
if (!generator) {
|
|
1340
|
+
throw new Error(
|
|
1341
|
+
`Generator "${config.generator}" not exported from plugin "${pluginName}". Ensure it's exported from the package's index.ts`
|
|
1342
|
+
);
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
if (!generator || typeof generator.generate !== "function") {
|
|
1346
|
+
throw new Error(
|
|
1347
|
+
`Generator "${config.generator}" for plugin "${pluginName}" must have a 'generate' function. Use makeContractGenerator() to create valid generators.`
|
|
1348
|
+
);
|
|
1349
|
+
}
|
|
1350
|
+
const resolvedServices = [];
|
|
1351
|
+
for (const marker of generator.services) {
|
|
1352
|
+
const service = services.get(marker);
|
|
1353
|
+
if (!service) {
|
|
1354
|
+
const markerName = marker.description ?? "unknown";
|
|
1355
|
+
throw new Error(
|
|
1356
|
+
`Service "${markerName}" required by ${pluginName} generator not found. Ensure it's registered in init.ts`
|
|
1357
|
+
);
|
|
1358
|
+
}
|
|
1359
|
+
resolvedServices.push(service);
|
|
1360
|
+
}
|
|
1361
|
+
if (verbose) {
|
|
1362
|
+
getLogger().info(` Executing generator...`);
|
|
1363
|
+
}
|
|
1364
|
+
const result = await generator.generate(...resolvedServices);
|
|
1365
|
+
return result;
|
|
1366
|
+
}
|
|
1367
|
+
function resolveStaticContractPath(plugin, contractSpec, projectRoot) {
|
|
1368
|
+
const { pluginPath, isLocal, packageName } = plugin;
|
|
1369
|
+
if (!isLocal) {
|
|
1370
|
+
try {
|
|
1371
|
+
return require2.resolve(`${packageName}/${contractSpec}`, {
|
|
1372
|
+
paths: [projectRoot]
|
|
1373
|
+
});
|
|
1374
|
+
} catch {
|
|
1375
|
+
const possiblePaths = [
|
|
1376
|
+
path.join(pluginPath, "dist", contractSpec),
|
|
1377
|
+
path.join(pluginPath, "lib", contractSpec),
|
|
1378
|
+
path.join(pluginPath, contractSpec)
|
|
1379
|
+
];
|
|
1380
|
+
const found = possiblePaths.find((p) => fs.existsSync(p));
|
|
1381
|
+
return found || possiblePaths[0];
|
|
1382
|
+
}
|
|
1383
|
+
} else {
|
|
1384
|
+
return path.join(pluginPath, contractSpec);
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
function toKebabCase(str) {
|
|
1388
|
+
return str.replace(/([A-Z])/g, "-$1").toLowerCase().replace(/^-/, "");
|
|
1389
|
+
}
|
|
1390
|
+
function getJayStackVersion() {
|
|
1391
|
+
try {
|
|
1392
|
+
const packageJsonPath = path.join(__dirname, "..", "package.json");
|
|
1393
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
1394
|
+
return packageJson.version || "0.0.0";
|
|
1395
|
+
} catch {
|
|
1396
|
+
return "0.0.0";
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
async function materializeContracts(options, services = /* @__PURE__ */ new Map()) {
|
|
1400
|
+
const {
|
|
1401
|
+
projectRoot,
|
|
1402
|
+
outputDir = path.join(projectRoot, "build", "materialized-contracts"),
|
|
1403
|
+
dynamicOnly = false,
|
|
1404
|
+
pluginFilter,
|
|
1405
|
+
verbose = false,
|
|
1406
|
+
viteServer
|
|
1407
|
+
} = options;
|
|
1408
|
+
const contracts = [];
|
|
1409
|
+
const pluginsIndexMap = /* @__PURE__ */ new Map();
|
|
1410
|
+
let staticCount = 0;
|
|
1411
|
+
let dynamicCount = 0;
|
|
1412
|
+
if (verbose) {
|
|
1413
|
+
getLogger().info("Scanning for plugins...");
|
|
1414
|
+
}
|
|
1415
|
+
const plugins = await scanPlugins({
|
|
1416
|
+
projectRoot,
|
|
1417
|
+
verbose,
|
|
1418
|
+
includeDevDeps: true
|
|
1419
|
+
// Include dev deps for contract discovery
|
|
1420
|
+
});
|
|
1421
|
+
if (verbose) {
|
|
1422
|
+
getLogger().info(`Found ${plugins.size} plugin(s)`);
|
|
1423
|
+
}
|
|
1424
|
+
for (const [pluginKey, plugin] of plugins) {
|
|
1425
|
+
if (pluginFilter && plugin.name !== pluginFilter && pluginKey !== pluginFilter)
|
|
1426
|
+
continue;
|
|
1427
|
+
if (verbose) {
|
|
1428
|
+
getLogger().info(`
|
|
1429
|
+
📦 Processing plugin: ${plugin.name}`);
|
|
1430
|
+
}
|
|
1431
|
+
const { manifest } = plugin;
|
|
1432
|
+
if (!dynamicOnly && manifest.contracts) {
|
|
1433
|
+
for (const contract of manifest.contracts) {
|
|
1434
|
+
const contractPath = resolveStaticContractPath(
|
|
1435
|
+
plugin,
|
|
1436
|
+
contract.contract,
|
|
1437
|
+
projectRoot
|
|
1438
|
+
);
|
|
1439
|
+
const relativePath = path.relative(projectRoot, contractPath);
|
|
1440
|
+
const entry = {
|
|
1441
|
+
plugin: plugin.name,
|
|
1442
|
+
name: contract.name,
|
|
1443
|
+
type: "static",
|
|
1444
|
+
path: "./" + relativePath
|
|
1445
|
+
};
|
|
1446
|
+
contracts.push(entry);
|
|
1447
|
+
staticCount++;
|
|
1448
|
+
const pluginRelPath = path.relative(projectRoot, plugin.pluginPath);
|
|
1449
|
+
if (!pluginsIndexMap.has(plugin.name)) {
|
|
1450
|
+
pluginsIndexMap.set(plugin.name, {
|
|
1451
|
+
path: "./" + pluginRelPath.replace(/\\/g, "/"),
|
|
1452
|
+
contracts: []
|
|
1453
|
+
});
|
|
1454
|
+
}
|
|
1455
|
+
pluginsIndexMap.get(plugin.name).contracts.push({
|
|
1456
|
+
name: contract.name,
|
|
1457
|
+
type: "static",
|
|
1458
|
+
path: "./" + relativePath
|
|
1459
|
+
});
|
|
1460
|
+
if (verbose) {
|
|
1461
|
+
getLogger().info(` 📄 Static: ${contract.name}`);
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
if (manifest.dynamic_contracts) {
|
|
1466
|
+
const dynamicConfigs = Array.isArray(manifest.dynamic_contracts) ? manifest.dynamic_contracts : [manifest.dynamic_contracts];
|
|
1467
|
+
const pluginOutputDir = path.join(outputDir, plugin.name.replace(/[@/]/g, "_"));
|
|
1468
|
+
fs.mkdirSync(pluginOutputDir, { recursive: true });
|
|
1469
|
+
for (const config of dynamicConfigs) {
|
|
1470
|
+
if (verbose) {
|
|
1471
|
+
getLogger().info(` ⚡ Dynamic contracts (prefix: ${config.prefix})`);
|
|
1472
|
+
}
|
|
1473
|
+
try {
|
|
1474
|
+
const generatedContracts = await executeDynamicGenerator(
|
|
1475
|
+
plugin,
|
|
1476
|
+
config,
|
|
1477
|
+
projectRoot,
|
|
1478
|
+
services,
|
|
1479
|
+
verbose,
|
|
1480
|
+
viteServer
|
|
1481
|
+
);
|
|
1482
|
+
const prefix = config.prefix;
|
|
1483
|
+
for (const generated of generatedContracts) {
|
|
1484
|
+
const fullName = `${prefix}/${toKebabCase(generated.name)}`;
|
|
1485
|
+
const fileName = `${prefix}-${toKebabCase(generated.name)}.jay-contract`;
|
|
1486
|
+
const filePath = path.join(pluginOutputDir, fileName);
|
|
1487
|
+
fs.writeFileSync(filePath, generated.yaml, "utf-8");
|
|
1488
|
+
const relativePath = path.relative(projectRoot, filePath);
|
|
1489
|
+
const dynEntry = {
|
|
1490
|
+
plugin: plugin.name,
|
|
1491
|
+
name: fullName,
|
|
1492
|
+
type: "dynamic",
|
|
1493
|
+
path: "./" + relativePath,
|
|
1494
|
+
metadata: generated.metadata
|
|
1495
|
+
};
|
|
1496
|
+
contracts.push(dynEntry);
|
|
1497
|
+
dynamicCount++;
|
|
1498
|
+
const pluginRelPath = path.relative(projectRoot, plugin.pluginPath);
|
|
1499
|
+
if (!pluginsIndexMap.has(plugin.name)) {
|
|
1500
|
+
pluginsIndexMap.set(plugin.name, {
|
|
1501
|
+
path: "./" + pluginRelPath.replace(/\\/g, "/"),
|
|
1502
|
+
contracts: []
|
|
1503
|
+
});
|
|
1504
|
+
}
|
|
1505
|
+
pluginsIndexMap.get(plugin.name).contracts.push({
|
|
1506
|
+
name: fullName,
|
|
1507
|
+
type: "dynamic",
|
|
1508
|
+
path: "./" + relativePath
|
|
1509
|
+
});
|
|
1510
|
+
if (verbose) {
|
|
1511
|
+
getLogger().info(` ⚡ Materialized: ${fullName}`);
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
} catch (error) {
|
|
1515
|
+
getLogger().error(
|
|
1516
|
+
` ❌ Failed to materialize dynamic contracts for ${plugin.name} (${config.prefix}): ${error}`
|
|
1517
|
+
);
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
const index = {
|
|
1523
|
+
materialized_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1524
|
+
jay_stack_version: getJayStackVersion(),
|
|
1525
|
+
contracts
|
|
1526
|
+
};
|
|
1527
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
1528
|
+
const indexPath = path.join(outputDir, "contracts-index.yaml");
|
|
1529
|
+
fs.writeFileSync(indexPath, YAML.stringify(index), "utf-8");
|
|
1530
|
+
const pluginsIndex = {
|
|
1531
|
+
materialized_at: index.materialized_at,
|
|
1532
|
+
jay_stack_version: index.jay_stack_version,
|
|
1533
|
+
plugins: Array.from(pluginsIndexMap.entries()).map(([name, data]) => ({
|
|
1534
|
+
name,
|
|
1535
|
+
path: data.path,
|
|
1536
|
+
contracts: data.contracts
|
|
1537
|
+
}))
|
|
1538
|
+
};
|
|
1539
|
+
const pluginsIndexPath = path.join(outputDir, "plugins-index.yaml");
|
|
1540
|
+
fs.writeFileSync(pluginsIndexPath, YAML.stringify(pluginsIndex), "utf-8");
|
|
1541
|
+
if (verbose) {
|
|
1542
|
+
getLogger().info(`
|
|
1543
|
+
✅ Contracts index written to: ${indexPath}`);
|
|
1544
|
+
getLogger().info(`✅ Plugins index written to: ${pluginsIndexPath}`);
|
|
1545
|
+
}
|
|
1546
|
+
return {
|
|
1547
|
+
index,
|
|
1548
|
+
staticCount,
|
|
1549
|
+
dynamicCount,
|
|
1550
|
+
outputDir
|
|
1551
|
+
};
|
|
1552
|
+
}
|
|
1553
|
+
async function listContracts(options) {
|
|
1554
|
+
const { projectRoot, dynamicOnly = false, pluginFilter } = options;
|
|
1555
|
+
const contracts = [];
|
|
1556
|
+
const plugins = await scanPlugins({
|
|
1557
|
+
projectRoot,
|
|
1558
|
+
includeDevDeps: true
|
|
1559
|
+
});
|
|
1560
|
+
for (const [pluginKey, plugin] of plugins) {
|
|
1561
|
+
if (pluginFilter && plugin.name !== pluginFilter && pluginKey !== pluginFilter)
|
|
1562
|
+
continue;
|
|
1563
|
+
const { manifest } = plugin;
|
|
1564
|
+
if (!dynamicOnly && manifest.contracts) {
|
|
1565
|
+
for (const contract of manifest.contracts) {
|
|
1566
|
+
const contractPath = resolveStaticContractPath(
|
|
1567
|
+
plugin,
|
|
1568
|
+
contract.contract,
|
|
1569
|
+
projectRoot
|
|
1570
|
+
);
|
|
1571
|
+
const relativePath = path.relative(projectRoot, contractPath);
|
|
1572
|
+
contracts.push({
|
|
1573
|
+
plugin: plugin.name,
|
|
1574
|
+
name: contract.name,
|
|
1575
|
+
type: "static",
|
|
1576
|
+
path: "./" + relativePath
|
|
1577
|
+
});
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
if (manifest.dynamic_contracts) {
|
|
1581
|
+
const dynamicConfigs = Array.isArray(manifest.dynamic_contracts) ? manifest.dynamic_contracts : [manifest.dynamic_contracts];
|
|
1582
|
+
for (const config of dynamicConfigs) {
|
|
1583
|
+
contracts.push({
|
|
1584
|
+
plugin: plugin.name,
|
|
1585
|
+
name: `${config.prefix}/*`,
|
|
1586
|
+
type: "dynamic",
|
|
1587
|
+
path: "(run materialization to generate)"
|
|
1588
|
+
});
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
return {
|
|
1593
|
+
materialized_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1594
|
+
jay_stack_version: getJayStackVersion(),
|
|
1595
|
+
contracts
|
|
1596
|
+
};
|
|
1597
|
+
}
|
|
1598
|
+
async function discoverPluginsWithSetup(options) {
|
|
1599
|
+
const { projectRoot, verbose, pluginFilter } = options;
|
|
1600
|
+
const allPlugins = await scanPlugins({
|
|
1601
|
+
projectRoot,
|
|
1602
|
+
verbose,
|
|
1603
|
+
discoverTransitive: true
|
|
1604
|
+
});
|
|
1605
|
+
const pluginsWithSetup = [];
|
|
1606
|
+
for (const [packageName, plugin] of allPlugins) {
|
|
1607
|
+
if (!plugin.manifest.setup?.handler)
|
|
1608
|
+
continue;
|
|
1609
|
+
if (pluginFilter && plugin.name !== pluginFilter && packageName !== pluginFilter) {
|
|
1610
|
+
continue;
|
|
1611
|
+
}
|
|
1612
|
+
pluginsWithSetup.push({
|
|
1613
|
+
name: plugin.name,
|
|
1614
|
+
pluginPath: plugin.pluginPath,
|
|
1615
|
+
packageName: plugin.packageName,
|
|
1616
|
+
isLocal: plugin.isLocal,
|
|
1617
|
+
setupHandler: plugin.manifest.setup.handler,
|
|
1618
|
+
setupDescription: plugin.manifest.setup.description,
|
|
1619
|
+
dependencies: plugin.dependencies
|
|
1620
|
+
});
|
|
1621
|
+
if (verbose) {
|
|
1622
|
+
getLogger().info(`[Setup] Found plugin with setup: ${plugin.name}`);
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
return sortByDependencies(pluginsWithSetup);
|
|
1626
|
+
}
|
|
1627
|
+
async function discoverPluginsWithReferences(options) {
|
|
1628
|
+
const { projectRoot, verbose, pluginFilter } = options;
|
|
1629
|
+
const allPlugins = await scanPlugins({
|
|
1630
|
+
projectRoot,
|
|
1631
|
+
verbose,
|
|
1632
|
+
discoverTransitive: true
|
|
1633
|
+
});
|
|
1634
|
+
const pluginsWithRefs = [];
|
|
1635
|
+
for (const [packageName, plugin] of allPlugins) {
|
|
1636
|
+
if (!plugin.manifest.setup?.references)
|
|
1637
|
+
continue;
|
|
1638
|
+
if (pluginFilter && plugin.name !== pluginFilter && packageName !== pluginFilter) {
|
|
1639
|
+
continue;
|
|
1640
|
+
}
|
|
1641
|
+
pluginsWithRefs.push({
|
|
1642
|
+
name: plugin.name,
|
|
1643
|
+
pluginPath: plugin.pluginPath,
|
|
1644
|
+
packageName: plugin.packageName,
|
|
1645
|
+
isLocal: plugin.isLocal,
|
|
1646
|
+
referencesHandler: plugin.manifest.setup.references,
|
|
1647
|
+
dependencies: plugin.dependencies
|
|
1648
|
+
});
|
|
1649
|
+
if (verbose) {
|
|
1650
|
+
getLogger().info(`[AgentKit] Found plugin with references: ${plugin.name}`);
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
return sortByDependencies(pluginsWithRefs);
|
|
1654
|
+
}
|
|
1655
|
+
function sortByDependencies(plugins) {
|
|
1656
|
+
return [...plugins].sort((a, b) => {
|
|
1657
|
+
const aDepsOnB = a.dependencies.some((d) => d === b.name || d === b.packageName) ? 1 : 0;
|
|
1658
|
+
const bDepsOnA = b.dependencies.some((d) => d === a.name || d === a.packageName) ? 1 : 0;
|
|
1659
|
+
return aDepsOnB - bDepsOnA;
|
|
1660
|
+
});
|
|
1661
|
+
}
|
|
1662
|
+
async function executePluginSetup(plugin, options) {
|
|
1663
|
+
const { projectRoot, configDir, force, initError, viteServer, verbose } = options;
|
|
1664
|
+
const context = {
|
|
1665
|
+
pluginName: plugin.name,
|
|
1666
|
+
projectRoot,
|
|
1667
|
+
configDir,
|
|
1668
|
+
services: getServiceRegistry(),
|
|
1669
|
+
initError,
|
|
1670
|
+
force
|
|
1671
|
+
};
|
|
1672
|
+
const handler = await loadHandler(plugin, plugin.setupHandler, viteServer);
|
|
1673
|
+
return handler(context);
|
|
1674
|
+
}
|
|
1675
|
+
async function executePluginReferences(plugin, options) {
|
|
1676
|
+
const { projectRoot, force, viteServer } = options;
|
|
1677
|
+
const referencesDir = path.join(projectRoot, "agent-kit", "references", plugin.name);
|
|
1678
|
+
const context = {
|
|
1679
|
+
pluginName: plugin.name,
|
|
1680
|
+
projectRoot,
|
|
1681
|
+
referencesDir,
|
|
1682
|
+
services: getServiceRegistry(),
|
|
1683
|
+
force
|
|
1684
|
+
};
|
|
1685
|
+
const handler = await loadHandler(
|
|
1686
|
+
plugin,
|
|
1687
|
+
plugin.referencesHandler,
|
|
1688
|
+
viteServer
|
|
1689
|
+
);
|
|
1690
|
+
return handler(context);
|
|
1691
|
+
}
|
|
1692
|
+
async function loadHandler(plugin, handlerName, viteServer) {
|
|
1693
|
+
let module;
|
|
1694
|
+
if (plugin.isLocal) {
|
|
1695
|
+
const handlerPath = path.resolve(plugin.pluginPath, handlerName);
|
|
1696
|
+
if (viteServer) {
|
|
1697
|
+
module = await viteServer.ssrLoadModule(handlerPath);
|
|
1698
|
+
} else {
|
|
1699
|
+
module = await import(handlerPath);
|
|
1700
|
+
}
|
|
1701
|
+
if (typeof module[handlerName] === "function")
|
|
1702
|
+
return module[handlerName];
|
|
1703
|
+
if (typeof module.setup === "function")
|
|
1704
|
+
return module.setup;
|
|
1705
|
+
if (typeof module.default === "function")
|
|
1706
|
+
return module.default;
|
|
1707
|
+
throw new Error(
|
|
1708
|
+
`Handler "${handlerName}" not found in "${plugin.pluginPath}". Available exports: ${Object.keys(module).join(", ")}`
|
|
1709
|
+
);
|
|
1710
|
+
} else {
|
|
1711
|
+
if (viteServer) {
|
|
1712
|
+
module = await viteServer.ssrLoadModule(plugin.packageName);
|
|
1713
|
+
} else {
|
|
1714
|
+
module = await import(plugin.packageName);
|
|
1715
|
+
}
|
|
1716
|
+
if (typeof module[handlerName] !== "function") {
|
|
1717
|
+
throw new Error(
|
|
1718
|
+
`Handler "${handlerName}" not found as export in "${plugin.packageName}". Available exports: ${Object.keys(module).join(", ")}`
|
|
1719
|
+
);
|
|
1720
|
+
}
|
|
1721
|
+
return module[handlerName];
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
964
1724
|
export {
|
|
965
1725
|
ActionRegistry,
|
|
966
1726
|
DevSlowlyChangingPhase,
|
|
1727
|
+
SlowRenderCache,
|
|
967
1728
|
actionRegistry,
|
|
968
1729
|
clearActionRegistry,
|
|
969
1730
|
clearClientInitData,
|
|
@@ -973,8 +1734,12 @@ export {
|
|
|
973
1734
|
discoverAndRegisterActions,
|
|
974
1735
|
discoverPluginActions,
|
|
975
1736
|
discoverPluginsWithInit,
|
|
1737
|
+
discoverPluginsWithReferences,
|
|
1738
|
+
discoverPluginsWithSetup,
|
|
976
1739
|
executeAction,
|
|
1740
|
+
executePluginReferences,
|
|
977
1741
|
executePluginServerInits,
|
|
1742
|
+
executePluginSetup,
|
|
978
1743
|
generateClientScript,
|
|
979
1744
|
getActionCacheHeaders,
|
|
980
1745
|
getClientInitData,
|
|
@@ -982,9 +1747,12 @@ export {
|
|
|
982
1747
|
getRegisteredAction,
|
|
983
1748
|
getRegisteredActionNames,
|
|
984
1749
|
getService,
|
|
1750
|
+
getServiceRegistry,
|
|
985
1751
|
hasAction,
|
|
986
1752
|
hasService,
|
|
1753
|
+
listContracts,
|
|
987
1754
|
loadPageParts,
|
|
1755
|
+
materializeContracts,
|
|
988
1756
|
onInit,
|
|
989
1757
|
onShutdown,
|
|
990
1758
|
preparePluginClientInits,
|
|
@@ -996,6 +1764,8 @@ export {
|
|
|
996
1764
|
runLoadParams,
|
|
997
1765
|
runShutdownCallbacks,
|
|
998
1766
|
runSlowlyChangingRender,
|
|
1767
|
+
scanPlugins,
|
|
999
1768
|
setClientInitData,
|
|
1769
|
+
slowRenderInstances,
|
|
1000
1770
|
sortPluginsByDependencies
|
|
1001
1771
|
};
|