@jay-framework/dev-server 0.24.1 → 0.24.3
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 +2 -1
- package/dist/index.js +2163 -253
- package/package.json +15 -14
package/dist/index.js
CHANGED
|
@@ -7,31 +7,33 @@ import { createRequire } from "module";
|
|
|
7
7
|
import "@jay-framework/compiler-shared";
|
|
8
8
|
import * as path from "node:path";
|
|
9
9
|
import path__default from "node:path";
|
|
10
|
-
import {
|
|
10
|
+
import { parseJayFile, JAY_IMPORT_RESOLVER, injectHeadfullFSTemplates, discoverHeadlessInstances, assignCoordinatesToJayHtml, parseAction, generateServerElementFile, parseContract, slowRenderTransform } from "@jay-framework/compiler-jay-html";
|
|
11
11
|
import { getLogger, getDevLogger } from "@jay-framework/logger";
|
|
12
12
|
import { createRequire as createRequire$1 } from "node:module";
|
|
13
|
-
import * as
|
|
14
|
-
import
|
|
13
|
+
import * as fs from "node:fs";
|
|
14
|
+
import fs__default$1 from "node:fs";
|
|
15
15
|
import { scanRoutes, createRoute, routeToExpressRoute } from "@jay-framework/stack-route-scanner";
|
|
16
|
-
import {
|
|
17
|
-
import * as fs from "node:fs/promises";
|
|
16
|
+
import { actionRegistry, scanPlugins, serializeHeadTags, mergeHeadTags, discoverPluginsWithInit, sortPluginsByDependencies, executePluginServerInits, runInitCallbacks, runShutdownCallbacks, clearLifecycleCallbacks, clearServiceRegistry, clearClientInitData, runLoadParams, DevSlowlyChangingPhase, preparePluginClientInits, registerService, getServiceRegistry, parseCookies, renderFastChangingData, getClientInitData, slowRenderInstances, normalizeAndResolveInstanceProps } from "@jay-framework/stack-server-runtime";
|
|
17
|
+
import * as fs$1 from "node:fs/promises";
|
|
18
18
|
import fs__default from "node:fs/promises";
|
|
19
|
+
import jsBeautify from "js-beautify";
|
|
20
|
+
import fs$2 from "fs";
|
|
21
|
+
import path$1 from "path";
|
|
22
|
+
import YAML from "yaml";
|
|
23
|
+
import crypto, { randomUUID } from "node:crypto";
|
|
24
|
+
import { isJayAction, isJayStreamAction, createJayService } from "@jay-framework/fullstack-component";
|
|
19
25
|
import { pathToFileURL } from "node:url";
|
|
20
26
|
import Busboy from "busboy";
|
|
21
|
-
import
|
|
22
|
-
import
|
|
23
|
-
import crypto from "crypto";
|
|
24
|
-
import { randomUUID } from "node:crypto";
|
|
25
|
-
import { createJayService } from "@jay-framework/fullstack-component";
|
|
26
|
-
const s$1 = createRequire(import.meta.url), e$1 = s$1("typescript"), c$1 = new Proxy(e$1, {
|
|
27
|
+
import crypto$1 from "crypto";
|
|
28
|
+
const s$2 = createRequire(import.meta.url), e$2 = s$2("typescript"), c$1 = new Proxy(e$2, {
|
|
27
29
|
get(t, r) {
|
|
28
30
|
return t[r];
|
|
29
31
|
}
|
|
30
32
|
});
|
|
31
|
-
var
|
|
32
|
-
var
|
|
33
|
-
var
|
|
34
|
-
const s = createRequire(import.meta.url), e = s("typescript"), c = new Proxy(e, {
|
|
33
|
+
var __defProp$1 = Object.defineProperty;
|
|
34
|
+
var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
35
|
+
var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
36
|
+
const s$1 = createRequire(import.meta.url), e$1 = s$1("typescript"), c = new Proxy(e$1, {
|
|
35
37
|
get(t, r) {
|
|
36
38
|
return t[r];
|
|
37
39
|
}
|
|
@@ -180,7 +182,7 @@ function tsBindingNameToVariable(binding, accessedFrom, assignedFrom, propertyNa
|
|
|
180
182
|
}
|
|
181
183
|
class NameBindingResolver {
|
|
182
184
|
constructor(parentNameResolver) {
|
|
183
|
-
|
|
185
|
+
__publicField$1(this, "variables", /* @__PURE__ */ new Map());
|
|
184
186
|
this.parentNameResolver = parentNameResolver;
|
|
185
187
|
}
|
|
186
188
|
addVariable(name, variable) {
|
|
@@ -455,7 +457,7 @@ class GlobalResolvedType {
|
|
|
455
457
|
}
|
|
456
458
|
class SourceFileBindingResolver {
|
|
457
459
|
constructor(sourceFile) {
|
|
458
|
-
|
|
460
|
+
__publicField$1(this, "nameBindingResolvers", /* @__PURE__ */ new Map());
|
|
459
461
|
this.nameBindingResolvers.set(sourceFile, new NameBindingResolver());
|
|
460
462
|
const nbResolversQueue = [
|
|
461
463
|
this.nameBindingResolvers.get(sourceFile)
|
|
@@ -1244,7 +1246,7 @@ function createImportChainTracker(options = {}) {
|
|
|
1244
1246
|
}
|
|
1245
1247
|
};
|
|
1246
1248
|
}
|
|
1247
|
-
const require$1 = createRequire$1(import.meta.url);
|
|
1249
|
+
const require$1$1 = createRequire$1(import.meta.url);
|
|
1248
1250
|
function createDefaultPluginDetector() {
|
|
1249
1251
|
const cache = /* @__PURE__ */ new Map();
|
|
1250
1252
|
return {
|
|
@@ -1255,9 +1257,9 @@ function createDefaultPluginDetector() {
|
|
|
1255
1257
|
}
|
|
1256
1258
|
let result = false;
|
|
1257
1259
|
try {
|
|
1258
|
-
require$1.resolve(`${packageName}/plugin.yaml`, { paths: [projectRoot] });
|
|
1260
|
+
require$1$1.resolve(`${packageName}/plugin.yaml`, { paths: [projectRoot] });
|
|
1259
1261
|
try {
|
|
1260
|
-
require$1.resolve(`${packageName}/client`, { paths: [projectRoot] });
|
|
1262
|
+
require$1$1.resolve(`${packageName}/client`, { paths: [projectRoot] });
|
|
1261
1263
|
result = true;
|
|
1262
1264
|
} catch {
|
|
1263
1265
|
result = false;
|
|
@@ -1428,16 +1430,16 @@ function jayStackCompiler(options = {}) {
|
|
|
1428
1430
|
const importerDir = path.dirname(importer);
|
|
1429
1431
|
let resolvedPath = path.resolve(importerDir, source);
|
|
1430
1432
|
if (!resolvedPath.endsWith(".ts") && !resolvedPath.endsWith(".js")) {
|
|
1431
|
-
if (
|
|
1433
|
+
if (fs.existsSync(resolvedPath + ".ts")) {
|
|
1432
1434
|
resolvedPath += ".ts";
|
|
1433
|
-
} else if (
|
|
1435
|
+
} else if (fs.existsSync(resolvedPath + ".js")) {
|
|
1434
1436
|
resolvedPath += ".js";
|
|
1435
1437
|
} else {
|
|
1436
1438
|
return null;
|
|
1437
1439
|
}
|
|
1438
|
-
} else if (resolvedPath.endsWith(".js") && !
|
|
1440
|
+
} else if (resolvedPath.endsWith(".js") && !fs.existsSync(resolvedPath)) {
|
|
1439
1441
|
const tsPath = resolvedPath.slice(0, -3) + ".ts";
|
|
1440
|
-
if (
|
|
1442
|
+
if (fs.existsSync(tsPath)) {
|
|
1441
1443
|
resolvedPath = tsPath;
|
|
1442
1444
|
} else {
|
|
1443
1445
|
return null;
|
|
@@ -1452,7 +1454,7 @@ function jayStackCompiler(options = {}) {
|
|
|
1452
1454
|
const actualPath = id.slice("\0jay-action:".length);
|
|
1453
1455
|
let code;
|
|
1454
1456
|
try {
|
|
1455
|
-
code = await
|
|
1457
|
+
code = await fs.promises.readFile(actualPath, "utf-8");
|
|
1456
1458
|
} catch (err) {
|
|
1457
1459
|
getLogger().error(
|
|
1458
1460
|
`[action-transform] Could not read ${actualPath}: ${err}`
|
|
@@ -1610,224 +1612,2128 @@ async function createViteForCli(options) {
|
|
|
1610
1612
|
});
|
|
1611
1613
|
return vite;
|
|
1612
1614
|
}
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
for (const ext of extensions) {
|
|
1636
|
-
const filePath = path.join(this.projectRoot, this.sourceBase, "init" + ext);
|
|
1637
|
-
if (fsSync.existsSync(filePath)) {
|
|
1638
|
-
return filePath;
|
|
1639
|
-
}
|
|
1615
|
+
var __defProp2 = Object.defineProperty;
|
|
1616
|
+
var __defNormalProp2 = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
1617
|
+
var __publicField2 = (obj, key, value) => __defNormalProp2(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
1618
|
+
const require$3 = createRequire(import.meta.url);
|
|
1619
|
+
async function loadPageParts(vite, route, pagesBase, projectBase, jayRollupConfig, options) {
|
|
1620
|
+
const exists = await fs__default.access(route.compPath, fs__default.constants.F_OK).then(() => true).catch(() => false);
|
|
1621
|
+
const parts = [];
|
|
1622
|
+
const validations = [];
|
|
1623
|
+
if (exists) {
|
|
1624
|
+
const exportName = route.componentExport || "page";
|
|
1625
|
+
const pageComponent = (await vite.ssrLoadModule(route.compPath))[exportName];
|
|
1626
|
+
if (!pageComponent) {
|
|
1627
|
+
validations.push(
|
|
1628
|
+
`${route.compPath} does not export "${exportName}". The file exists but the expected export is missing — remove the file or add the export.`
|
|
1629
|
+
);
|
|
1630
|
+
} else {
|
|
1631
|
+
const clientImportPath = route.componentExport ? route.compPath.replace(/index\.js$/, "index.client.js") : route.compPath;
|
|
1632
|
+
parts.push({
|
|
1633
|
+
compDefinition: pageComponent,
|
|
1634
|
+
clientImport: `import {${exportName}} from '${clientImportPath}'`,
|
|
1635
|
+
clientPart: `{comp: ${exportName}.comp, contextMarkers: ${exportName}.contexts || []}`
|
|
1636
|
+
});
|
|
1640
1637
|
}
|
|
1641
|
-
return null;
|
|
1642
1638
|
}
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1639
|
+
const jayHtmlFilePath = options?.preRenderedPath ?? route.jayHtmlPath;
|
|
1640
|
+
const jayHtmlSource = options?.preRenderedContent ?? (await fs__default.readFile(jayHtmlFilePath)).toString();
|
|
1641
|
+
const fileName = path__default.basename(route.jayHtmlPath);
|
|
1642
|
+
const dirName = path__default.dirname(route.jayHtmlPath);
|
|
1643
|
+
const jayHtmlWithValidations = await parseJayFile(
|
|
1644
|
+
jayHtmlSource,
|
|
1645
|
+
fileName,
|
|
1646
|
+
dirName,
|
|
1647
|
+
{
|
|
1648
|
+
relativePath: jayRollupConfig.tsConfigFilePath
|
|
1649
|
+
},
|
|
1650
|
+
JAY_IMPORT_RESOLVER,
|
|
1651
|
+
projectBase
|
|
1652
|
+
);
|
|
1653
|
+
jayHtmlWithValidations.validations.push(...validations);
|
|
1654
|
+
return jayHtmlWithValidations.mapAsync(async (jayHtml) => {
|
|
1655
|
+
const usedPackages = /* @__PURE__ */ new Set();
|
|
1656
|
+
const headlessInstanceComponents = [];
|
|
1657
|
+
for await (const headlessImport of jayHtml.headlessImports) {
|
|
1658
|
+
const module = headlessImport.codeLink.module;
|
|
1659
|
+
const name = headlessImport.codeLink.names[0].name;
|
|
1660
|
+
const isLocalModule = module[0] === "." || module[0] === "/";
|
|
1661
|
+
let compDefinition;
|
|
1662
|
+
let clientModuleImport;
|
|
1663
|
+
let isNpmPackage;
|
|
1664
|
+
if (headlessImport.structural) {
|
|
1665
|
+
continue;
|
|
1666
|
+
} else {
|
|
1667
|
+
const modulePath = isLocalModule ? path__default.resolve(dirName, module) : require$3.resolve(module, { paths: require$3.resolve.paths(dirName) });
|
|
1668
|
+
compDefinition = (await vite.ssrLoadModule(modulePath))[name];
|
|
1669
|
+
const moduleImport = isLocalModule ? path__default.resolve(dirName, module) : module;
|
|
1670
|
+
isNpmPackage = !isLocalModule;
|
|
1671
|
+
clientModuleImport = isNpmPackage ? `${moduleImport}/client` : `${moduleImport}`;
|
|
1672
|
+
}
|
|
1673
|
+
if (isNpmPackage) {
|
|
1674
|
+
const packageName = module.startsWith("@") ? module.split("/").slice(0, 2).join("/") : module.split("/")[0];
|
|
1675
|
+
usedPackages.add(packageName);
|
|
1676
|
+
}
|
|
1677
|
+
if (headlessImport.key) {
|
|
1678
|
+
const key = headlessImport.key;
|
|
1679
|
+
const hasClientComp = !!compDefinition?.fastRender || !!compDefinition?.hasInteractive;
|
|
1680
|
+
const part = {
|
|
1681
|
+
key,
|
|
1682
|
+
compDefinition,
|
|
1683
|
+
clientImport: hasClientComp ? `import {${name}} from '${clientModuleImport}'` : "",
|
|
1684
|
+
clientPart: hasClientComp ? `{comp: ${name}.comp, contextMarkers: ${name}.contexts || [], key: '${key}'}` : "",
|
|
1685
|
+
contractInfo: headlessImport.contract ? {
|
|
1686
|
+
contractName: headlessImport.contract.name,
|
|
1687
|
+
metadata: headlessImport.metadata
|
|
1688
|
+
} : void 0,
|
|
1689
|
+
headlessProps: headlessImport.headlessProps
|
|
1690
|
+
};
|
|
1691
|
+
parts.push(part);
|
|
1692
|
+
}
|
|
1693
|
+
if (!headlessImport.key && headlessImport.contract) {
|
|
1694
|
+
headlessInstanceComponents.push({
|
|
1695
|
+
contractName: headlessImport.contractName,
|
|
1696
|
+
compDefinition,
|
|
1697
|
+
contract: headlessImport.contract
|
|
1698
|
+
});
|
|
1699
|
+
}
|
|
1655
1700
|
}
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
});
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1701
|
+
const headlessContracts = jayHtml.headlessImports.filter((hi) => hi.contract !== void 0 && hi.key !== void 0).map((hi) => ({
|
|
1702
|
+
key: hi.key,
|
|
1703
|
+
contract: hi.contract,
|
|
1704
|
+
contractPath: hi.contractPath
|
|
1705
|
+
}));
|
|
1706
|
+
const jayHtmlForDiscovery = injectHeadfullFSTemplates(
|
|
1707
|
+
jayHtmlSource,
|
|
1708
|
+
dirName,
|
|
1709
|
+
JAY_IMPORT_RESOLVER
|
|
1710
|
+
);
|
|
1711
|
+
let discoveryResult;
|
|
1712
|
+
if (headlessInstanceComponents.length > 0) {
|
|
1713
|
+
const firstDiscovery = discoverHeadlessInstances(jayHtmlForDiscovery);
|
|
1714
|
+
const headlessContractNameSet = new Set(
|
|
1715
|
+
jayHtml.headlessImports.map((hi) => hi.contractName)
|
|
1665
1716
|
);
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
if (this.viteServer) {
|
|
1672
|
-
const module = await this.viteServer.ssrLoadModule(this.projectInitFilePath);
|
|
1673
|
-
if (module.init?._serverInit) {
|
|
1674
|
-
log.info("[DevServer] Running server init: project");
|
|
1675
|
-
const { setClientInitData } = await import("@jay-framework/stack-server-runtime");
|
|
1676
|
-
const clientData = await module.init._serverInit();
|
|
1677
|
-
if (clientData !== void 0 && clientData !== null) {
|
|
1678
|
-
setClientInitData("project", clientData);
|
|
1679
|
-
}
|
|
1680
|
-
}
|
|
1681
|
-
} else {
|
|
1682
|
-
const fileUrl = pathToFileURL(this.projectInitFilePath).href;
|
|
1683
|
-
await import(fileUrl);
|
|
1684
|
-
}
|
|
1685
|
-
} catch (error) {
|
|
1686
|
-
log.error(`[Services] Failed to load project init: ${error}`);
|
|
1687
|
-
throw error;
|
|
1688
|
-
}
|
|
1717
|
+
const jayHtmlWithCoords = assignCoordinatesToJayHtml(
|
|
1718
|
+
firstDiscovery.preRenderedJayHtml,
|
|
1719
|
+
headlessContractNameSet
|
|
1720
|
+
);
|
|
1721
|
+
discoveryResult = discoverHeadlessInstances(jayHtmlWithCoords);
|
|
1689
1722
|
} else {
|
|
1690
|
-
|
|
1723
|
+
discoveryResult = {
|
|
1724
|
+
instances: [],
|
|
1725
|
+
forEachInstances: [],
|
|
1726
|
+
preRenderedJayHtml: jayHtmlSource
|
|
1727
|
+
};
|
|
1691
1728
|
}
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1729
|
+
return {
|
|
1730
|
+
parts,
|
|
1731
|
+
serverTrackByMap: jayHtml.serverTrackByMap,
|
|
1732
|
+
clientTrackByMap: jayHtml.clientTrackByMap,
|
|
1733
|
+
usedPackages,
|
|
1734
|
+
headlessContracts,
|
|
1735
|
+
headlessInstanceComponents,
|
|
1736
|
+
discoveredInstances: discoveryResult.instances,
|
|
1737
|
+
forEachInstances: discoveryResult.forEachInstances,
|
|
1738
|
+
linkedCssFiles: jayHtml.linkedCssFiles ?? [],
|
|
1739
|
+
linkedComponentFiles: jayHtml.linkedComponentFiles ?? [],
|
|
1740
|
+
scripts: jayHtml.scripts
|
|
1741
|
+
};
|
|
1742
|
+
});
|
|
1743
|
+
}
|
|
1744
|
+
var __defProp22 = Object.defineProperty;
|
|
1745
|
+
var __defNormalProp22 = (obj, key, value) => key in obj ? __defProp22(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
1746
|
+
var __publicField22 = (obj, key, value) => __defNormalProp22(obj, key + "", value);
|
|
1747
|
+
class JayAtomicType {
|
|
1748
|
+
constructor(name) {
|
|
1749
|
+
__publicField22(this, "kind", 0);
|
|
1750
|
+
this.name = name;
|
|
1696
1751
|
}
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1752
|
+
}
|
|
1753
|
+
new JayAtomicType("string");
|
|
1754
|
+
new JayAtomicType("string");
|
|
1755
|
+
new JayAtomicType("number");
|
|
1756
|
+
new JayAtomicType("boolean");
|
|
1757
|
+
new JayAtomicType("Date");
|
|
1758
|
+
new JayAtomicType("file");
|
|
1759
|
+
new JayAtomicType("Unknown");
|
|
1760
|
+
class JayObjectType {
|
|
1761
|
+
constructor(name, props) {
|
|
1762
|
+
__publicField22(this, "kind", 8);
|
|
1763
|
+
this.name = name;
|
|
1764
|
+
this.props = props;
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
new JayObjectType("Error", {
|
|
1768
|
+
message: new JayAtomicType("string"),
|
|
1769
|
+
name: new JayAtomicType("string"),
|
|
1770
|
+
stack: new JayAtomicType("string")
|
|
1771
|
+
});
|
|
1772
|
+
function isOptionalType(aType) {
|
|
1773
|
+
return aType.kind === 14;
|
|
1774
|
+
}
|
|
1775
|
+
function isAtomicType(aType) {
|
|
1776
|
+
return aType.kind === 0;
|
|
1777
|
+
}
|
|
1778
|
+
function isEnumType(aType) {
|
|
1779
|
+
return aType.kind === 2;
|
|
1780
|
+
}
|
|
1781
|
+
function isImportedType(aType) {
|
|
1782
|
+
return aType.kind === 4;
|
|
1783
|
+
}
|
|
1784
|
+
function isObjectType(aType) {
|
|
1785
|
+
return aType.kind === 8;
|
|
1786
|
+
}
|
|
1787
|
+
function isArrayType(aType) {
|
|
1788
|
+
return aType.kind === 9;
|
|
1789
|
+
}
|
|
1790
|
+
function isRecordType(aType) {
|
|
1791
|
+
return aType.kind === 11;
|
|
1792
|
+
}
|
|
1793
|
+
function jayTypeToJsonSchema(type) {
|
|
1794
|
+
if (isOptionalType(type)) {
|
|
1795
|
+
return jayTypeToJsonSchema(type.innerType);
|
|
1796
|
+
}
|
|
1797
|
+
if (isAtomicType(type)) {
|
|
1798
|
+
const name = type.name.toLowerCase();
|
|
1799
|
+
if (name === "string" || name === "number" || name === "boolean") {
|
|
1800
|
+
return { type: name };
|
|
1714
1801
|
}
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
projectRoot: this.projectRoot,
|
|
1718
|
-
registry: actionRegistry,
|
|
1719
|
-
verbose: true,
|
|
1720
|
-
viteServer: this.viteServer ?? void 0
|
|
1721
|
-
});
|
|
1722
|
-
totalActions += pluginActions.length;
|
|
1723
|
-
} catch (error) {
|
|
1724
|
-
log.error(`[Actions] Failed to auto-discover plugin actions: ${error}`);
|
|
1802
|
+
if (name === "file") {
|
|
1803
|
+
return { type: "string", description: "Binary file upload (JayFile)" };
|
|
1725
1804
|
}
|
|
1726
|
-
|
|
1727
|
-
|
|
1805
|
+
return { type: "string" };
|
|
1806
|
+
}
|
|
1807
|
+
if (isEnumType(type)) {
|
|
1808
|
+
return { type: "string", enum: type.values };
|
|
1809
|
+
}
|
|
1810
|
+
if (isImportedType(type)) {
|
|
1811
|
+
return { type: "object", description: `Contract: ${type.name}` };
|
|
1812
|
+
}
|
|
1813
|
+
if (isArrayType(type)) {
|
|
1814
|
+
const itemSchema = jayTypeToJsonSchema(type.itemType);
|
|
1815
|
+
if (itemSchema) {
|
|
1816
|
+
return { type: "array", items: itemSchema };
|
|
1728
1817
|
}
|
|
1818
|
+
return { type: "array" };
|
|
1729
1819
|
}
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
const log = getLogger();
|
|
1735
|
-
if (!this.isInitialized) {
|
|
1736
|
-
return;
|
|
1820
|
+
if (isRecordType(type)) {
|
|
1821
|
+
const valueSchema = jayTypeToJsonSchema(type.itemType);
|
|
1822
|
+
if (valueSchema) {
|
|
1823
|
+
return { type: "object", additionalProperties: valueSchema };
|
|
1737
1824
|
}
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
log.error(`[Services] Shutdown error: ${error}`);
|
|
1825
|
+
return { type: "object" };
|
|
1826
|
+
}
|
|
1827
|
+
if (isObjectType(type)) {
|
|
1828
|
+
const properties = {};
|
|
1829
|
+
const required = [];
|
|
1830
|
+
for (const [key, propType] of Object.entries(type.props)) {
|
|
1831
|
+
const isOpt = isOptionalType(propType);
|
|
1832
|
+
const schema = jayTypeToJsonSchema(propType);
|
|
1833
|
+
if (schema) {
|
|
1834
|
+
properties[key] = schema;
|
|
1835
|
+
if (!isOpt) {
|
|
1836
|
+
required.push(key);
|
|
1837
|
+
}
|
|
1752
1838
|
}
|
|
1753
|
-
} finally {
|
|
1754
|
-
this.isInitialized = false;
|
|
1755
1839
|
}
|
|
1840
|
+
return {
|
|
1841
|
+
type: "object",
|
|
1842
|
+
properties,
|
|
1843
|
+
...required.length > 0 && { required }
|
|
1844
|
+
};
|
|
1756
1845
|
}
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1846
|
+
return null;
|
|
1847
|
+
}
|
|
1848
|
+
var RuntimeMode = /* @__PURE__ */ ((RuntimeMode2) => {
|
|
1849
|
+
RuntimeMode2["MainTrusted"] = "mainTrusted";
|
|
1850
|
+
RuntimeMode2["MainSandbox"] = "mainSandbox";
|
|
1851
|
+
RuntimeMode2["WorkerTrusted"] = "workerTrusted";
|
|
1852
|
+
RuntimeMode2["WorkerSandbox"] = "workerSandbox";
|
|
1853
|
+
return RuntimeMode2;
|
|
1854
|
+
})(RuntimeMode || {});
|
|
1855
|
+
const TS_EXTENSION = ".ts";
|
|
1856
|
+
const JAY_QUERY_PREFIX = "?jay-";
|
|
1857
|
+
const JAY_QUERY_HYDRATE = `${JAY_QUERY_PREFIX}hydrate`;
|
|
1858
|
+
[
|
|
1859
|
+
// Hydrate target
|
|
1860
|
+
{
|
|
1861
|
+
pattern: JAY_QUERY_HYDRATE,
|
|
1862
|
+
buildEnv: "client",
|
|
1863
|
+
isHydrate: true
|
|
1864
|
+
},
|
|
1865
|
+
// Build environments
|
|
1866
|
+
{
|
|
1867
|
+
pattern: `${JAY_QUERY_PREFIX}${"client"}`,
|
|
1868
|
+
buildEnv: "client"
|
|
1869
|
+
/* Client */
|
|
1870
|
+
},
|
|
1871
|
+
{
|
|
1872
|
+
pattern: `${JAY_QUERY_PREFIX}${"server"}`,
|
|
1873
|
+
buildEnv: "server"
|
|
1874
|
+
/* Server */
|
|
1875
|
+
},
|
|
1876
|
+
// Runtime modes (with .ts suffix)
|
|
1877
|
+
{
|
|
1878
|
+
pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.MainSandbox}${TS_EXTENSION}`,
|
|
1879
|
+
runtimeMode: RuntimeMode.MainSandbox
|
|
1880
|
+
},
|
|
1881
|
+
{
|
|
1882
|
+
pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.WorkerTrusted}${TS_EXTENSION}`,
|
|
1883
|
+
runtimeMode: RuntimeMode.WorkerTrusted
|
|
1884
|
+
},
|
|
1885
|
+
{
|
|
1886
|
+
pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.WorkerSandbox}${TS_EXTENSION}`,
|
|
1887
|
+
runtimeMode: RuntimeMode.WorkerSandbox
|
|
1888
|
+
},
|
|
1889
|
+
// Runtime modes (without .ts suffix)
|
|
1890
|
+
{
|
|
1891
|
+
pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.MainSandbox}`,
|
|
1892
|
+
runtimeMode: RuntimeMode.MainSandbox
|
|
1893
|
+
},
|
|
1894
|
+
{
|
|
1895
|
+
pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.WorkerTrusted}`,
|
|
1896
|
+
runtimeMode: RuntimeMode.WorkerTrusted
|
|
1897
|
+
},
|
|
1898
|
+
{
|
|
1899
|
+
pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.WorkerSandbox}`,
|
|
1900
|
+
runtimeMode: RuntimeMode.WorkerSandbox
|
|
1779
1901
|
}
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1902
|
+
];
|
|
1903
|
+
function checkValidationErrors(withValidations) {
|
|
1904
|
+
const { validations } = withValidations;
|
|
1905
|
+
if (validations.length > 0) {
|
|
1906
|
+
throw new Error(validations.join("\n"));
|
|
1785
1907
|
}
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
}
|
|
1793
|
-
return {
|
|
1794
|
-
importPath: this.projectInitFilePath,
|
|
1795
|
-
initExport: "init"
|
|
1796
|
-
};
|
|
1908
|
+
return withValidations.val;
|
|
1909
|
+
}
|
|
1910
|
+
const { html: htmlBeautify } = jsBeautify;
|
|
1911
|
+
createRequire(import.meta.url);
|
|
1912
|
+
function normalizeActionEntry(entry) {
|
|
1913
|
+
if (typeof entry === "string") {
|
|
1914
|
+
return { name: entry };
|
|
1797
1915
|
}
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
return
|
|
1916
|
+
return { name: entry.name, action: entry.action, devOnly: entry.devOnly };
|
|
1917
|
+
}
|
|
1918
|
+
function loadPluginManifest(pluginDir) {
|
|
1919
|
+
const pluginYamlPath = path$1.join(pluginDir, "plugin.yaml");
|
|
1920
|
+
if (!fs$2.existsSync(pluginYamlPath)) {
|
|
1921
|
+
return null;
|
|
1804
1922
|
}
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
return
|
|
1923
|
+
try {
|
|
1924
|
+
const yamlContent = fs$2.readFileSync(pluginYamlPath, "utf-8");
|
|
1925
|
+
return YAML.parse(yamlContent);
|
|
1926
|
+
} catch (error) {
|
|
1927
|
+
return null;
|
|
1810
1928
|
}
|
|
1811
1929
|
}
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1930
|
+
const s = createRequire(import.meta.url), e = s("typescript");
|
|
1931
|
+
new Proxy(e, {
|
|
1932
|
+
get(t, r) {
|
|
1933
|
+
return t[r];
|
|
1934
|
+
}
|
|
1935
|
+
});
|
|
1936
|
+
function asyncSwapScript(id, html) {
|
|
1937
|
+
const escapedHtml = html.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
1938
|
+
return `<script>(function(){var t=document.querySelector('[jay-async="${id}:pending"]');if(t){var d=document.createElement('div');d.innerHTML='${escapedHtml}';t.replaceWith(d.firstChild);}window.__jay&&window.__jay.hydrateAsync&&window.__jay.hydrateAsync('${id}');})()<\/script>`;
|
|
1939
|
+
}
|
|
1940
|
+
function generatePromiseReconstruction(outcomes) {
|
|
1941
|
+
if (outcomes.length === 0) return "";
|
|
1942
|
+
return outcomes.map((outcome) => {
|
|
1943
|
+
if (outcome.status === "resolved") {
|
|
1944
|
+
return ` viewState[${JSON.stringify(outcome.id)}] = Promise.resolve(${JSON.stringify(outcome.value)});`;
|
|
1945
|
+
} else {
|
|
1946
|
+
const errMsg = outcome.value?.message ?? "Unknown error";
|
|
1947
|
+
return ` viewState[${JSON.stringify(outcome.id)}] = Promise.reject(new Error(${JSON.stringify(errMsg)}));`;
|
|
1948
|
+
}
|
|
1949
|
+
}).join("\n") + "\n";
|
|
1950
|
+
}
|
|
1951
|
+
function buildScriptFragments(parts, clientInitData, projectInit, pluginInits, options) {
|
|
1952
|
+
const { enableAutomation = true, slowViewState } = options;
|
|
1953
|
+
const hasSlowViewState = slowViewState && Object.keys(slowViewState).length > 0;
|
|
1954
|
+
const clientParts = parts.filter((part) => part.clientPart);
|
|
1955
|
+
const partImports = clientParts.length > 0 ? clientParts.map((part) => part.clientImport).join("\n") + "\n" : "";
|
|
1956
|
+
const compositeParts = clientParts.length > 0 ? `[
|
|
1957
|
+
${clientParts.map((part) => " " + part.clientPart).join(",\n")}
|
|
1958
|
+
]` : "[]";
|
|
1959
|
+
const hasClientInit = projectInit || pluginInits.length > 0;
|
|
1960
|
+
const pluginClientInitImports = pluginInits.map((plugin, idx) => {
|
|
1961
|
+
return `import { ${plugin.initExport} as jayInit${idx} } from "${plugin.importPath}";`;
|
|
1962
|
+
}).join("\n ");
|
|
1963
|
+
const projectInitImport = projectInit ? `import { ${projectInit.initExport || "init"} as projectJayInit } from "${projectInit.importPath}";` : "";
|
|
1964
|
+
const pluginClientInitCalls = pluginInits.map((plugin, idx) => {
|
|
1965
|
+
const pluginData = clientInitData[plugin.name] || {};
|
|
1966
|
+
return `if (typeof jayInit${idx}._clientInit === 'function') {
|
|
1967
|
+
console.log('[DevServer] Running client init: ${plugin.name}');
|
|
1968
|
+
await jayInit${idx}._clientInit(${JSON.stringify(pluginData)});
|
|
1969
|
+
}`;
|
|
1970
|
+
}).join("\n ");
|
|
1971
|
+
const projectInitCall = projectInit ? `if (typeof projectJayInit._clientInit === 'function') {
|
|
1972
|
+
console.log('[DevServer] Running client init: project');
|
|
1973
|
+
const projectData = ${JSON.stringify(clientInitData["project"] || {})};
|
|
1974
|
+
await projectJayInit._clientInit(projectData);
|
|
1975
|
+
}` : "";
|
|
1976
|
+
const clientInitExecution = hasClientInit ? `
|
|
1977
|
+
// Plugin client initialization (in dependency order)
|
|
1978
|
+
${pluginClientInitCalls}
|
|
1979
|
+
|
|
1980
|
+
// Project client initialization
|
|
1981
|
+
${projectInitCall}
|
|
1982
|
+
` : "";
|
|
1983
|
+
const automationImport = enableAutomation ? hasSlowViewState ? `import { wrapWithAutomation, AUTOMATION_CONTEXT } from "@jay-framework/runtime-automation";
|
|
1984
|
+
import { registerGlobalContext } from "@jay-framework/runtime";
|
|
1985
|
+
import { deepMergeViewStates } from "@jay-framework/view-state-merge";` : `import { wrapWithAutomation, AUTOMATION_CONTEXT } from "@jay-framework/runtime-automation";
|
|
1986
|
+
import { registerGlobalContext } from "@jay-framework/runtime";` : "";
|
|
1987
|
+
const slowViewStateDecl = enableAutomation && hasSlowViewState ? `const slowViewState = ${JSON.stringify(slowViewState)};` : "";
|
|
1988
|
+
return {
|
|
1989
|
+
partImports,
|
|
1990
|
+
compositeParts,
|
|
1991
|
+
pluginClientInitImports,
|
|
1992
|
+
projectInitImport,
|
|
1993
|
+
clientInitExecution,
|
|
1994
|
+
automationImport,
|
|
1995
|
+
slowViewStateDecl
|
|
1996
|
+
};
|
|
1997
|
+
}
|
|
1998
|
+
function buildPageReloadHmrScript() {
|
|
1999
|
+
return `
|
|
2000
|
+
if (import.meta.hot) {
|
|
2001
|
+
import.meta.hot.on('jay:page-reload', (data) => {
|
|
2002
|
+
const prefix = data.routePrefix;
|
|
2003
|
+
const pathname = window.location.pathname;
|
|
2004
|
+
if (pathname === prefix || pathname.startsWith(prefix + '/')) {
|
|
2005
|
+
window.location.reload();
|
|
2006
|
+
}
|
|
2007
|
+
});
|
|
2008
|
+
}`;
|
|
2009
|
+
}
|
|
2010
|
+
function buildFreezeScript(routePattern) {
|
|
2011
|
+
const routePatternLiteral = routePattern ? `'${routePattern}'` : "undefined";
|
|
2012
|
+
return `
|
|
2013
|
+
// Page Freeze (DL#127, DL#128 iframe addendum)
|
|
2014
|
+
// Sticky embed mode: URL param sets a session cookie so in-iframe navigation preserves it
|
|
2015
|
+
if (new URLSearchParams(window.location.search).has('_jay_embed')) {
|
|
2016
|
+
document.cookie = '_jay_embed=1;path=/;samesite=lax';
|
|
2017
|
+
}
|
|
2018
|
+
const __jayEmbedMode = document.cookie.split(';').some(c => c.trim().startsWith('_jay_embed='));
|
|
2019
|
+
|
|
2020
|
+
async function __jayDoFreeze() {
|
|
2021
|
+
const automation = window.__jay?.automation;
|
|
2022
|
+
if (!automation) return;
|
|
2023
|
+
|
|
2024
|
+
// Visual feedback: white flash
|
|
2025
|
+
const flash = document.createElement('div');
|
|
2026
|
+
flash.style.cssText = 'position:fixed;inset:0;background:white;z-index:999999;opacity:0.8;pointer-events:none;transition:opacity 0.3s';
|
|
2027
|
+
document.body.appendChild(flash);
|
|
2028
|
+
requestAnimationFrame(() => { flash.style.opacity = '0'; });
|
|
2029
|
+
setTimeout(() => flash.remove(), 400);
|
|
2030
|
+
|
|
2031
|
+
// Audio feedback: camera shutter
|
|
2032
|
+
try {
|
|
2033
|
+
const ctx = new AudioContext();
|
|
2034
|
+
const buf = ctx.createBuffer(1, ctx.sampleRate * 0.15, ctx.sampleRate);
|
|
2035
|
+
const data = buf.getChannelData(0);
|
|
2036
|
+
for (let i = 0; i < data.length; i++) {
|
|
2037
|
+
data[i] = (Math.random() * 2 - 1) * Math.exp(-i / (ctx.sampleRate * 0.02));
|
|
2038
|
+
}
|
|
2039
|
+
const src = ctx.createBufferSource();
|
|
2040
|
+
src.buffer = buf;
|
|
2041
|
+
src.connect(ctx.destination);
|
|
2042
|
+
src.start();
|
|
2043
|
+
} catch {}
|
|
2044
|
+
|
|
2045
|
+
// Capture and save
|
|
2046
|
+
try {
|
|
2047
|
+
const state = automation.getPageState();
|
|
2048
|
+
const route = window.location.pathname;
|
|
2049
|
+
const routePattern = ${routePatternLiteral};
|
|
2050
|
+
const resp = await fetch('/_jay/freeze', {
|
|
2051
|
+
method: 'POST',
|
|
2052
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2053
|
+
body: JSON.stringify({ route, routePattern, viewState: state.viewState }),
|
|
2054
|
+
});
|
|
2055
|
+
const { id } = await resp.json();
|
|
2056
|
+
if (__jayEmbedMode) {
|
|
2057
|
+
window.parent.postMessage({ type: 'jay:freeze', id, route }, '*');
|
|
2058
|
+
} else {
|
|
2059
|
+
window.open(route + '?_jay_freeze=' + id, '_blank');
|
|
2060
|
+
}
|
|
2061
|
+
} catch (err) {
|
|
2062
|
+
console.error('[Freeze] Failed:', err);
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
|
|
2066
|
+
if (__jayEmbedMode) {
|
|
2067
|
+
// Notify parent of the current route on load (DL#128 route addendum)
|
|
2068
|
+
window.parent.postMessage({ type: 'jay:route', route: window.location.pathname, routePattern: ${routePatternLiteral} }, '*');
|
|
2069
|
+
|
|
2070
|
+
// Embed mode: parent triggers freeze via postMessage
|
|
2071
|
+
window.addEventListener('message', (e) => {
|
|
2072
|
+
if (e.data?.type === 'jay:requestFreeze') __jayDoFreeze();
|
|
2073
|
+
});
|
|
2074
|
+
} else {
|
|
2075
|
+
// Standalone: Alt+S / Option+S keyboard shortcut
|
|
2076
|
+
document.addEventListener('keydown', (e) => {
|
|
2077
|
+
if (e.altKey && e.code === 'KeyS') {
|
|
2078
|
+
e.preventDefault();
|
|
2079
|
+
__jayDoFreeze();
|
|
2080
|
+
}
|
|
2081
|
+
});
|
|
2082
|
+
}
|
|
2083
|
+
|
|
2084
|
+
// Targeted page reload: only reload if this page matches the changed route prefix
|
|
2085
|
+
${buildPageReloadHmrScript()}`;
|
|
2086
|
+
}
|
|
2087
|
+
function buildAutomationWrap(options, mode) {
|
|
2088
|
+
const { enableAutomation = true, slowViewState } = options;
|
|
2089
|
+
const hasSlowViewState = slowViewState && Object.keys(slowViewState).length > 0;
|
|
2090
|
+
const appendDom = mode === "client";
|
|
2091
|
+
if (!enableAutomation) {
|
|
2092
|
+
return appendDom ? `
|
|
2093
|
+
target.appendChild(instance.element.dom);` : "";
|
|
2094
|
+
}
|
|
2095
|
+
const appendLine = appendDom ? `
|
|
2096
|
+
target.appendChild(wrapped.element.dom);` : "";
|
|
2097
|
+
const freezeScript = buildFreezeScript(options.routePattern);
|
|
2098
|
+
if (hasSlowViewState) {
|
|
2099
|
+
return `
|
|
2100
|
+
// Wrap with automation for dev tooling
|
|
2101
|
+
// Deep merge slow+fast ViewState so automation can see full page state
|
|
2102
|
+
const fullViewState = deepMergeViewStates(slowViewState, {...viewState, ...fastCarryForward}, trackByMap);
|
|
2103
|
+
const wrapped = wrapWithAutomation(instance, { initialViewState: fullViewState, trackByMap });
|
|
2104
|
+
registerGlobalContext(AUTOMATION_CONTEXT, wrapped.automation);
|
|
2105
|
+
window.__jay = window.__jay || {};
|
|
2106
|
+
window.__jay.automation = wrapped.automation;
|
|
2107
|
+
window.dispatchEvent(new Event('jay:automation-ready'));${appendLine}
|
|
2108
|
+
${freezeScript}`;
|
|
2109
|
+
}
|
|
2110
|
+
return `
|
|
2111
|
+
// Wrap with automation for dev tooling
|
|
2112
|
+
const wrapped = wrapWithAutomation(instance);
|
|
2113
|
+
registerGlobalContext(AUTOMATION_CONTEXT, wrapped.automation);
|
|
2114
|
+
window.__jay = window.__jay || {};
|
|
2115
|
+
window.__jay.automation = wrapped.automation;
|
|
2116
|
+
window.dispatchEvent(new Event('jay:automation-ready'));${appendLine}
|
|
2117
|
+
${freezeScript}`;
|
|
2118
|
+
}
|
|
2119
|
+
async function resolveViewStatePromises(viewState) {
|
|
2120
|
+
const entries = Object.entries(viewState);
|
|
2121
|
+
const hasPromises = entries.some(([, v]) => v instanceof Promise);
|
|
2122
|
+
if (!hasPromises) return { resolved: viewState, outcomes: [] };
|
|
2123
|
+
const result = { ...viewState };
|
|
2124
|
+
const outcomes = [];
|
|
2125
|
+
for (const [key, value] of entries) {
|
|
2126
|
+
if (value instanceof Promise) {
|
|
2127
|
+
try {
|
|
2128
|
+
const val = await value;
|
|
2129
|
+
result[key] = val;
|
|
2130
|
+
outcomes.push({ id: key, status: "resolved", value: val });
|
|
2131
|
+
} catch (err) {
|
|
2132
|
+
delete result[key];
|
|
2133
|
+
outcomes.push({
|
|
2134
|
+
id: key,
|
|
2135
|
+
status: "rejected",
|
|
2136
|
+
value: { message: err?.message ?? String(err) }
|
|
2137
|
+
});
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
return { resolved: result, outcomes };
|
|
2142
|
+
}
|
|
2143
|
+
async function generateClientScript(defaultViewState, fastCarryForward, parts, jayHtmlPath, trackByMap = {}, clientInitData = {}, projectInit, pluginInits = [], options = {}) {
|
|
2144
|
+
const { resolved, outcomes } = await resolveViewStatePromises(defaultViewState);
|
|
2145
|
+
defaultViewState = resolved;
|
|
2146
|
+
const {
|
|
2147
|
+
partImports,
|
|
2148
|
+
compositeParts,
|
|
2149
|
+
pluginClientInitImports,
|
|
2150
|
+
projectInitImport,
|
|
2151
|
+
clientInitExecution,
|
|
2152
|
+
automationImport,
|
|
2153
|
+
slowViewStateDecl
|
|
2154
|
+
} = buildScriptFragments(parts, clientInitData, projectInit, pluginInits, options);
|
|
2155
|
+
const automationWrap = buildAutomationWrap(options, "client");
|
|
2156
|
+
return `<!doctype html>
|
|
2157
|
+
<html lang="en">
|
|
2158
|
+
<head>
|
|
2159
|
+
<meta charset="UTF-8" />
|
|
2160
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
2161
|
+
<title>Vite + TS</title>
|
|
2162
|
+
</head>
|
|
2163
|
+
<body>
|
|
2164
|
+
<div id="target"></div>
|
|
2165
|
+
<script type="module">
|
|
2166
|
+
import {makeCompositeJayComponent} from "@jay-framework/stack-client-runtime";
|
|
2167
|
+
${automationImport}
|
|
2168
|
+
${pluginClientInitImports}
|
|
2169
|
+
${projectInitImport}
|
|
2170
|
+
import { render } from '${jayHtmlPath}';
|
|
2171
|
+
${partImports}${slowViewStateDecl}
|
|
2172
|
+
const viewState = ${JSON.stringify(defaultViewState)};
|
|
2173
|
+
${generatePromiseReconstruction(outcomes)}
|
|
2174
|
+
const fastCarryForward = ${JSON.stringify(fastCarryForward)};
|
|
2175
|
+
const trackByMap = ${JSON.stringify(trackByMap)};
|
|
2176
|
+
${clientInitExecution}
|
|
2177
|
+
const target = document.getElementById('target');
|
|
2178
|
+
const pageComp = makeCompositeJayComponent(render, viewState, fastCarryForward, ${compositeParts}, trackByMap)
|
|
2179
|
+
|
|
2180
|
+
const instance = pageComp({/* placeholder for page props */})
|
|
2181
|
+
${automationWrap}
|
|
2182
|
+
<\/script>
|
|
2183
|
+
</body>
|
|
2184
|
+
</html>`;
|
|
2185
|
+
}
|
|
2186
|
+
const serverModuleCache = /* @__PURE__ */ new Map();
|
|
2187
|
+
function clearServerElementCache() {
|
|
2188
|
+
serverModuleCache.clear();
|
|
2189
|
+
}
|
|
2190
|
+
function getByPath(obj, dotPath) {
|
|
2191
|
+
const segments = dotPath.split(".");
|
|
2192
|
+
let current = obj;
|
|
2193
|
+
for (const seg of segments) {
|
|
2194
|
+
if (current == null || typeof current !== "object") return void 0;
|
|
2195
|
+
current = current[seg];
|
|
2196
|
+
}
|
|
2197
|
+
return current;
|
|
2198
|
+
}
|
|
2199
|
+
function resolveParts(parts, viewState) {
|
|
2200
|
+
return parts.map((p) => {
|
|
2201
|
+
if (p.kind === "static") return p.value;
|
|
2202
|
+
if (!viewState) return `{${p.value}}`;
|
|
2203
|
+
const resolved = getByPath(viewState, p.value);
|
|
2204
|
+
return resolved !== void 0 && resolved !== null ? String(resolved) : `{${p.value}}`;
|
|
2205
|
+
}).join("");
|
|
2206
|
+
}
|
|
2207
|
+
function headMetaToHeadTags(headMeta, viewState) {
|
|
2208
|
+
if (!headMeta) return [];
|
|
2209
|
+
const tags = [];
|
|
2210
|
+
if (headMeta.title) {
|
|
2211
|
+
tags.push({ tag: "title", children: resolveParts(headMeta.title, viewState) });
|
|
2212
|
+
}
|
|
2213
|
+
for (const m of headMeta.meta) {
|
|
2214
|
+
const attrs = {};
|
|
2215
|
+
if (m.name) attrs.name = m.name;
|
|
2216
|
+
if (m.property) attrs.property = m.property;
|
|
2217
|
+
attrs.content = resolveParts(m.content, viewState);
|
|
2218
|
+
tags.push({ tag: "meta", attrs });
|
|
2219
|
+
}
|
|
2220
|
+
for (const l of headMeta.links) {
|
|
2221
|
+
if (l.rel === "stylesheet" || l.rel === "import") continue;
|
|
2222
|
+
const attrs = { rel: l.rel };
|
|
2223
|
+
attrs.href = resolveParts(l.href, viewState);
|
|
2224
|
+
for (const [k, v] of Object.entries(l)) {
|
|
2225
|
+
if (k !== "rel" && k !== "href" && typeof v === "string") attrs[k] = v;
|
|
2226
|
+
}
|
|
2227
|
+
tags.push({ tag: "link", attrs });
|
|
2228
|
+
}
|
|
2229
|
+
return tags;
|
|
2230
|
+
}
|
|
2231
|
+
async function generateSSRPageHtml(vite, jayHtmlContent, jayHtmlFilename, jayHtmlDir, viewState, jayHtmlImportPath, parts, carryForward, trackByMap = {}, clientInitData = {}, buildFolder, projectRoot, routeDir, tsConfigFilePath, projectInit, pluginInits = [], options = {}, sourceDir, headTags, scripts) {
|
|
2232
|
+
const jayHtmlPath = path__default.join(jayHtmlDir, jayHtmlFilename);
|
|
2233
|
+
let cached = serverModuleCache.get(jayHtmlPath);
|
|
2234
|
+
if (!cached) {
|
|
2235
|
+
cached = await compileAndLoadServerElement(
|
|
2236
|
+
vite,
|
|
2237
|
+
jayHtmlContent,
|
|
2238
|
+
jayHtmlFilename,
|
|
2239
|
+
jayHtmlDir,
|
|
2240
|
+
buildFolder,
|
|
2241
|
+
projectRoot,
|
|
2242
|
+
routeDir,
|
|
2243
|
+
tsConfigFilePath,
|
|
2244
|
+
sourceDir
|
|
2245
|
+
);
|
|
2246
|
+
serverModuleCache.set(jayHtmlPath, cached);
|
|
2247
|
+
}
|
|
2248
|
+
const htmlChunks = [];
|
|
2249
|
+
const asyncPromises = [];
|
|
2250
|
+
const asyncOutcomes = [];
|
|
2251
|
+
const ctx = {
|
|
2252
|
+
write: (chunk) => {
|
|
2253
|
+
htmlChunks.push(chunk);
|
|
2254
|
+
},
|
|
2255
|
+
onAsync: (promise, id, templates) => {
|
|
2256
|
+
const asyncPromise = promise.then(
|
|
2257
|
+
(val) => {
|
|
2258
|
+
asyncOutcomes.push({ id, status: "resolved", value: val });
|
|
2259
|
+
if (templates.resolved) {
|
|
2260
|
+
return asyncSwapScript(id, templates.resolved(val));
|
|
2261
|
+
}
|
|
2262
|
+
return "";
|
|
2263
|
+
},
|
|
2264
|
+
(err) => {
|
|
2265
|
+
asyncOutcomes.push({
|
|
2266
|
+
id,
|
|
2267
|
+
status: "rejected",
|
|
2268
|
+
value: { message: err?.message ?? String(err) }
|
|
2269
|
+
});
|
|
2270
|
+
if (templates.rejected) {
|
|
2271
|
+
return asyncSwapScript(id, templates.rejected(err));
|
|
2272
|
+
}
|
|
2273
|
+
return "";
|
|
2274
|
+
}
|
|
2275
|
+
);
|
|
2276
|
+
asyncPromises.push(asyncPromise);
|
|
2277
|
+
}
|
|
2278
|
+
};
|
|
2279
|
+
cached.renderToStream(viewState, ctx);
|
|
2280
|
+
const asyncResults = await Promise.all(asyncPromises);
|
|
2281
|
+
const ssrHtml = htmlChunks.join("");
|
|
2282
|
+
const asyncScripts = asyncResults.filter((r) => r !== "").join("");
|
|
2283
|
+
const hydrationScript = generateHydrationScript(
|
|
2284
|
+
viewState,
|
|
2285
|
+
carryForward,
|
|
2286
|
+
parts,
|
|
2287
|
+
jayHtmlImportPath,
|
|
2288
|
+
trackByMap,
|
|
2289
|
+
clientInitData,
|
|
2290
|
+
projectInit,
|
|
2291
|
+
pluginInits,
|
|
2292
|
+
options,
|
|
2293
|
+
asyncOutcomes
|
|
2294
|
+
);
|
|
2295
|
+
const cssLink = cached.cssHref ? ` <link rel="stylesheet" href="${cached.cssHref}" />` : "";
|
|
2296
|
+
const tagSources = [];
|
|
2297
|
+
if (headTags && headTags.length > 0) tagSources.push(headTags);
|
|
2298
|
+
const headMetaTags = headMetaToHeadTags(cached.headMeta, viewState);
|
|
2299
|
+
if (headMetaTags.length > 0) tagSources.push(headMetaTags);
|
|
2300
|
+
const mergedTags = mergeHeadTags(tagSources);
|
|
2301
|
+
const hasCustomTitle = mergedTags.some((t) => t.tag.toLowerCase() === "title");
|
|
2302
|
+
const titleTag = hasCustomTitle ? "" : " <title>Vite + TS</title>\n";
|
|
2303
|
+
const headTagsHtml = mergedTags.length > 0 ? serializeHeadTags(mergedTags) : "";
|
|
2304
|
+
const headScriptsHtml = serializeScripts(scripts, "head");
|
|
2305
|
+
const bodyScriptsHtml = serializeScripts(scripts, "body");
|
|
2306
|
+
const headExtras = [cssLink, headTagsHtml, headScriptsHtml].filter((_) => _).join("\n");
|
|
2307
|
+
return `<!doctype html>
|
|
2308
|
+
<html lang="en">
|
|
2309
|
+
<head>
|
|
2310
|
+
<meta charset="UTF-8" />
|
|
2311
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
2312
|
+
${titleTag}${headExtras ? headExtras + "\n" : ""} </head>
|
|
2313
|
+
<body>
|
|
2314
|
+
<div id="target">${ssrHtml}</div>${asyncScripts}
|
|
2315
|
+
${hydrationScript}${bodyScriptsHtml ? "\n " + bodyScriptsHtml : ""}
|
|
2316
|
+
</body>
|
|
2317
|
+
</html>`;
|
|
2318
|
+
}
|
|
2319
|
+
async function generateFrozenPageHtml(vite, jayHtmlContent, jayHtmlFilename, jayHtmlDir, viewState, buildFolder, projectRoot, routeDir, tsConfigFilePath, sourceDir, format = "page", freezeName, options) {
|
|
2320
|
+
const jayHtmlPath = path__default.join(jayHtmlDir, jayHtmlFilename);
|
|
2321
|
+
let cached = serverModuleCache.get(jayHtmlPath);
|
|
2322
|
+
if (!cached) {
|
|
2323
|
+
cached = await compileAndLoadServerElement(
|
|
2324
|
+
vite,
|
|
2325
|
+
jayHtmlContent,
|
|
2326
|
+
jayHtmlFilename,
|
|
2327
|
+
jayHtmlDir,
|
|
2328
|
+
buildFolder,
|
|
2329
|
+
projectRoot,
|
|
2330
|
+
routeDir,
|
|
2331
|
+
tsConfigFilePath,
|
|
2332
|
+
sourceDir
|
|
2333
|
+
);
|
|
2334
|
+
serverModuleCache.set(jayHtmlPath, cached);
|
|
2335
|
+
}
|
|
2336
|
+
const htmlChunks = [];
|
|
2337
|
+
const ctx = {
|
|
2338
|
+
write: (chunk) => {
|
|
2339
|
+
htmlChunks.push(chunk);
|
|
2340
|
+
},
|
|
2341
|
+
onAsync: () => {
|
|
2342
|
+
}
|
|
2343
|
+
};
|
|
2344
|
+
cached.renderToStream(viewState, ctx);
|
|
2345
|
+
const ssrHtml = htmlChunks.join("");
|
|
2346
|
+
if (format === "fragment") {
|
|
2347
|
+
let inlineCss = "";
|
|
2348
|
+
if (cached.cssHref) {
|
|
2349
|
+
try {
|
|
2350
|
+
const cssPath = cached.cssHref.replace(/^\/@fs/, "").replace(/\?.*$/, "");
|
|
2351
|
+
const cssContent = await fs__default.readFile(cssPath, "utf-8");
|
|
2352
|
+
inlineCss = `<style>${cssContent}</style>`;
|
|
2353
|
+
} catch {
|
|
2354
|
+
inlineCss = `<link rel="stylesheet" href="${cached.cssHref}" />`;
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
return `${inlineCss}
|
|
2358
|
+
${ssrHtml}`;
|
|
2359
|
+
}
|
|
2360
|
+
const frozenHeadTags = headMetaToHeadTags(cached.headMeta, viewState);
|
|
2361
|
+
const frozenHeadTagsHtml = frozenHeadTags.length > 0 ? serializeHeadTags(frozenHeadTags) : "";
|
|
2362
|
+
const cssLink = cached.cssHref ? ` <link rel="stylesheet" href="${cached.cssHref}" />` : "";
|
|
2363
|
+
const headExtras = [cssLink, frozenHeadTagsHtml].filter((_) => _).join("\n");
|
|
2364
|
+
const label = freezeName ? ` — ${freezeName}` : "";
|
|
2365
|
+
const devHmrScript = options?.injectDevHmr === true ? `
|
|
2366
|
+
<script type="module">${buildPageReloadHmrScript()}
|
|
2367
|
+
<\/script>` : "";
|
|
2368
|
+
return `<!doctype html>
|
|
2369
|
+
<html lang="en">
|
|
2370
|
+
<head>
|
|
2371
|
+
<meta charset="UTF-8" />
|
|
2372
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
2373
|
+
<title>Frozen${label}</title>
|
|
2374
|
+
${headExtras ? headExtras + "\n" : ""} <style>
|
|
2375
|
+
body::before {
|
|
2376
|
+
content: 'FROZEN${label ? `: ${freezeName}` : ""}';
|
|
2377
|
+
position: fixed;
|
|
2378
|
+
top: 0;
|
|
2379
|
+
right: 0;
|
|
2380
|
+
background: #1a1a2e;
|
|
2381
|
+
color: #e0e0ff;
|
|
2382
|
+
padding: 2px 10px;
|
|
2383
|
+
font: 11px/1.6 system-ui;
|
|
2384
|
+
z-index: 99999;
|
|
2385
|
+
border-bottom-left-radius: 4px;
|
|
2386
|
+
opacity: 0.8;
|
|
2387
|
+
}
|
|
2388
|
+
</style>
|
|
2389
|
+
</head>
|
|
2390
|
+
<body>
|
|
2391
|
+
<div id="target">${ssrHtml}</div>${devHmrScript}
|
|
2392
|
+
</body>
|
|
2393
|
+
</html>`;
|
|
2394
|
+
}
|
|
2395
|
+
function serializeScripts(scripts, position) {
|
|
2396
|
+
if (!scripts) return "";
|
|
2397
|
+
const filtered = scripts.filter((s2) => s2.position === position);
|
|
2398
|
+
if (filtered.length === 0) return "";
|
|
2399
|
+
return filtered.map((s2) => {
|
|
2400
|
+
const attrs = Object.entries(s2.attributes).map(([k, v]) => v === "" ? k : `${k}="${v}"`).join(" ");
|
|
2401
|
+
const attrStr = attrs ? " " + attrs : "";
|
|
2402
|
+
if (s2.src) {
|
|
2403
|
+
return ` <script src="${s2.src}"${attrStr}><\/script>`;
|
|
2404
|
+
}
|
|
2405
|
+
return ` <script${attrStr}>${s2.inline}<\/script>`;
|
|
2406
|
+
}).join("\n");
|
|
2407
|
+
}
|
|
2408
|
+
function rebaseRelativeImports(code, fromDir, toDir) {
|
|
2409
|
+
return code.replace(/from "(\.\.\/[^"]+)"/g, (_match, relPath) => {
|
|
2410
|
+
const absolutePath = path__default.resolve(fromDir, relPath);
|
|
2411
|
+
let newRelPath = path__default.relative(toDir, absolutePath);
|
|
2412
|
+
if (!newRelPath.startsWith(".")) {
|
|
2413
|
+
newRelPath = "./" + newRelPath;
|
|
2414
|
+
}
|
|
2415
|
+
return `from "${newRelPath}"`;
|
|
2416
|
+
});
|
|
2417
|
+
}
|
|
2418
|
+
async function compileAndLoadServerElement(vite, jayHtmlContent, jayHtmlFilename, jayHtmlDir, buildFolder, projectRoot, routeDir, tsConfigFilePath, sourceDir) {
|
|
2419
|
+
const jayFile = await parseJayFile(
|
|
2420
|
+
jayHtmlContent,
|
|
2421
|
+
jayHtmlFilename,
|
|
2422
|
+
jayHtmlDir,
|
|
2423
|
+
{ relativePath: tsConfigFilePath },
|
|
2424
|
+
JAY_IMPORT_RESOLVER,
|
|
2425
|
+
projectRoot,
|
|
2426
|
+
sourceDir
|
|
2427
|
+
);
|
|
2428
|
+
const parsedJayFile = checkValidationErrors(jayFile);
|
|
2429
|
+
const pageName = jayHtmlFilename.replace(".jay-html", "");
|
|
2430
|
+
const debugCoordinatePreprocessPath = path__default.join(
|
|
2431
|
+
buildFolder,
|
|
2432
|
+
"debug",
|
|
2433
|
+
routeDir,
|
|
2434
|
+
`${pageName}.coordinate-preprocess.jay-html`
|
|
2435
|
+
);
|
|
2436
|
+
const serverElementOptions = {
|
|
2437
|
+
debugCoordinatePreprocessPath
|
|
2438
|
+
};
|
|
2439
|
+
const serverElementCode = checkValidationErrors(
|
|
2440
|
+
generateServerElementFile(parsedJayFile, serverElementOptions)
|
|
2441
|
+
);
|
|
2442
|
+
const serverElementDir = path__default.join(buildFolder, "pre-rendered", routeDir);
|
|
2443
|
+
await fs__default.mkdir(serverElementDir, { recursive: true });
|
|
2444
|
+
const adjustedCode = rebaseRelativeImports(serverElementCode, jayHtmlDir, serverElementDir);
|
|
2445
|
+
const serverElementFilename = jayHtmlFilename.replace(".jay-html", ".server-element.ts");
|
|
2446
|
+
const serverElementPath = path__default.join(serverElementDir, serverElementFilename);
|
|
2447
|
+
await fs__default.writeFile(serverElementPath, adjustedCode, "utf-8");
|
|
2448
|
+
const existingModule = vite.moduleGraph.getModuleById(serverElementPath);
|
|
2449
|
+
if (existingModule) {
|
|
2450
|
+
vite.moduleGraph.invalidateModule(existingModule);
|
|
2451
|
+
}
|
|
2452
|
+
const jayHtmlPath = path__default.join(serverElementDir, jayHtmlFilename);
|
|
2453
|
+
invalidateJayHtmlModules(vite, jayHtmlPath);
|
|
2454
|
+
const serverModule = await vite.ssrLoadModule(serverElementPath);
|
|
2455
|
+
let cssHref;
|
|
2456
|
+
if (parsedJayFile.css) {
|
|
2457
|
+
const cssFilename = jayHtmlFilename.replace(".jay-html", ".css");
|
|
2458
|
+
const cssPath = path__default.join(serverElementDir, cssFilename);
|
|
2459
|
+
await fs__default.writeFile(cssPath, parsedJayFile.css, "utf-8");
|
|
2460
|
+
const cssModules = vite.moduleGraph.getModulesByFile(cssPath);
|
|
2461
|
+
if (cssModules) {
|
|
2462
|
+
for (const mod of cssModules) {
|
|
2463
|
+
vite.moduleGraph.invalidateModule(mod);
|
|
2464
|
+
}
|
|
2465
|
+
}
|
|
2466
|
+
const hash = crypto.createHash("md5").update(parsedJayFile.css).digest("hex").slice(0, 8);
|
|
2467
|
+
cssHref = "/@fs" + cssPath + "?v=" + hash + "&direct";
|
|
2468
|
+
}
|
|
2469
|
+
return {
|
|
2470
|
+
renderToStream: serverModule.renderToStream,
|
|
2471
|
+
headLinks: parsedJayFile.headLinks,
|
|
2472
|
+
headMeta: parsedJayFile.headMeta,
|
|
2473
|
+
cssHref
|
|
2474
|
+
};
|
|
2475
|
+
}
|
|
2476
|
+
function invalidateJayHtmlModules(vite, jayHtmlPath) {
|
|
2477
|
+
let count = 0;
|
|
2478
|
+
const byFile = vite.moduleGraph.getModulesByFile(jayHtmlPath);
|
|
2479
|
+
if (byFile) {
|
|
2480
|
+
for (const mod of byFile) {
|
|
2481
|
+
vite.moduleGraph.invalidateModule(mod);
|
|
2482
|
+
count++;
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
const knownIds = [jayHtmlPath + ".ts", jayHtmlPath + JAY_QUERY_HYDRATE + ".ts"];
|
|
2486
|
+
for (const id of knownIds) {
|
|
2487
|
+
const mod = vite.moduleGraph.getModuleById(id);
|
|
2488
|
+
if (mod) {
|
|
2489
|
+
vite.moduleGraph.invalidateModule(mod);
|
|
2490
|
+
count++;
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
const idMap = vite.moduleGraph.idToModuleMap;
|
|
2494
|
+
for (const [id, mod] of idMap) {
|
|
2495
|
+
if (id.includes(jayHtmlPath)) {
|
|
2496
|
+
vite.moduleGraph.invalidateModule(mod);
|
|
2497
|
+
count++;
|
|
2498
|
+
}
|
|
2499
|
+
}
|
|
2500
|
+
if (count > 0) {
|
|
2501
|
+
getLogger().info(`[SSR] Invalidated ${count} Vite module(s) for ${jayHtmlPath}`);
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
function generateHydrationScript(defaultViewState, fastCarryForward, parts, jayHtmlPath, trackByMap = {}, clientInitData = {}, projectInit, pluginInits = [], options = {}, asyncOutcomes = []) {
|
|
2505
|
+
const {
|
|
2506
|
+
partImports,
|
|
2507
|
+
compositeParts,
|
|
2508
|
+
pluginClientInitImports,
|
|
2509
|
+
projectInitImport,
|
|
2510
|
+
clientInitExecution,
|
|
2511
|
+
automationImport,
|
|
2512
|
+
slowViewStateDecl
|
|
2513
|
+
} = buildScriptFragments(parts, clientInitData, projectInit, pluginInits, options);
|
|
2514
|
+
const automationWrap = buildAutomationWrap(options, "hydrate");
|
|
2515
|
+
const hydrateImportPath = `${jayHtmlPath}${JAY_QUERY_HYDRATE}`;
|
|
2516
|
+
return `<script type="module">
|
|
2517
|
+
import {hydrateCompositeJayComponent} from "@jay-framework/stack-client-runtime";
|
|
2518
|
+
${automationImport}
|
|
2519
|
+
${pluginClientInitImports}
|
|
2520
|
+
${projectInitImport}
|
|
2521
|
+
import { hydrate } from '${hydrateImportPath}';
|
|
2522
|
+
${partImports}${slowViewStateDecl}
|
|
2523
|
+
const viewState = ${JSON.stringify(defaultViewState)};
|
|
2524
|
+
${generatePromiseReconstruction(asyncOutcomes)} const fastCarryForward = ${JSON.stringify(fastCarryForward)};
|
|
2525
|
+
const trackByMap = ${JSON.stringify(trackByMap)};
|
|
2526
|
+
|
|
2527
|
+
const target = document.getElementById('target');
|
|
2528
|
+
const rootElement = target.firstElementChild;
|
|
2529
|
+
const pageComp = hydrateCompositeJayComponent(hydrate, viewState, fastCarryForward, ${compositeParts}, trackByMap, rootElement);
|
|
2530
|
+
${clientInitExecution}
|
|
2531
|
+
const instance = pageComp({/* placeholder for page props */});
|
|
2532
|
+
${automationWrap}
|
|
2533
|
+
<\/script>`;
|
|
2534
|
+
}
|
|
2535
|
+
function parseActionMetadata(yamlContent, fileName) {
|
|
2536
|
+
try {
|
|
2537
|
+
const parsed = parseAction(yamlContent, fileName);
|
|
2538
|
+
if (parsed.validations.length > 0) {
|
|
2539
|
+
getLogger().warn(
|
|
2540
|
+
`[ActionMetadata] ${fileName}: validation errors: ${parsed.validations.join(", ")}`
|
|
2541
|
+
);
|
|
2542
|
+
return null;
|
|
2543
|
+
}
|
|
2544
|
+
if (!parsed.val) {
|
|
2545
|
+
getLogger().warn(`[ActionMetadata] ${fileName}: parsing returned no result`);
|
|
2546
|
+
return null;
|
|
2547
|
+
}
|
|
2548
|
+
const action = parsed.val;
|
|
2549
|
+
const inputJsonSchema = jayTypeToJsonSchema(action.inputType);
|
|
2550
|
+
let inputSchema;
|
|
2551
|
+
if (inputJsonSchema && inputJsonSchema.type === "object") {
|
|
2552
|
+
inputSchema = {
|
|
2553
|
+
type: "object",
|
|
2554
|
+
properties: inputJsonSchema.properties || {},
|
|
2555
|
+
...inputJsonSchema.required && inputJsonSchema.required.length > 0 && { required: inputJsonSchema.required }
|
|
2556
|
+
};
|
|
2557
|
+
} else {
|
|
2558
|
+
inputSchema = { type: "object", properties: {} };
|
|
2559
|
+
}
|
|
2560
|
+
const metadata = {
|
|
2561
|
+
name: action.name,
|
|
2562
|
+
description: action.description,
|
|
2563
|
+
inputSchema
|
|
2564
|
+
};
|
|
2565
|
+
if (action.outputType) {
|
|
2566
|
+
const outputJsonSchema = jayTypeToJsonSchema(action.outputType);
|
|
2567
|
+
if (outputJsonSchema) {
|
|
2568
|
+
metadata.outputSchema = outputJsonSchema;
|
|
2569
|
+
}
|
|
2570
|
+
}
|
|
2571
|
+
return metadata;
|
|
2572
|
+
} catch (error) {
|
|
2573
|
+
getLogger().error(
|
|
2574
|
+
`[ActionMetadata] Failed to parse ${fileName}: ${error instanceof Error ? error.message : error}`
|
|
2575
|
+
);
|
|
2576
|
+
return null;
|
|
2577
|
+
}
|
|
2578
|
+
}
|
|
2579
|
+
function loadActionMetadata(filePath) {
|
|
2580
|
+
if (!fs.existsSync(filePath)) {
|
|
2581
|
+
getLogger().warn(`[ActionMetadata] File not found: ${filePath}`);
|
|
2582
|
+
return null;
|
|
2583
|
+
}
|
|
2584
|
+
const yamlContent = fs.readFileSync(filePath, "utf-8");
|
|
2585
|
+
const fileName = path.basename(filePath);
|
|
2586
|
+
return parseActionMetadata(yamlContent, fileName);
|
|
2587
|
+
}
|
|
2588
|
+
function resolveActionMetadataPath(actionPath, pluginDir) {
|
|
2589
|
+
return path.resolve(pluginDir, actionPath);
|
|
2590
|
+
}
|
|
2591
|
+
const require$2 = createRequire$1(import.meta.url);
|
|
2592
|
+
async function discoverAndRegisterActions(options) {
|
|
2593
|
+
const {
|
|
2594
|
+
projectRoot,
|
|
2595
|
+
actionsDir = "src/actions",
|
|
2596
|
+
registry = actionRegistry,
|
|
2597
|
+
verbose = false,
|
|
2598
|
+
viteServer
|
|
2599
|
+
} = options;
|
|
2600
|
+
const result = {
|
|
2601
|
+
actionCount: 0,
|
|
2602
|
+
actionNames: [],
|
|
2603
|
+
scannedFiles: []
|
|
2604
|
+
};
|
|
2605
|
+
const actionsPath = path.resolve(projectRoot, actionsDir);
|
|
2606
|
+
if (!fs.existsSync(actionsPath)) {
|
|
2607
|
+
if (verbose) {
|
|
2608
|
+
getLogger().info(`[Actions] No actions directory found at ${actionsPath}`);
|
|
2609
|
+
}
|
|
2610
|
+
return result;
|
|
2611
|
+
}
|
|
2612
|
+
const actionFiles = await findActionFiles(actionsPath);
|
|
2613
|
+
if (verbose) {
|
|
2614
|
+
getLogger().info(`[Actions] Found ${actionFiles.length} action file(s)`);
|
|
2615
|
+
}
|
|
2616
|
+
for (const filePath of actionFiles) {
|
|
2617
|
+
result.scannedFiles.push(filePath);
|
|
2618
|
+
try {
|
|
2619
|
+
let module;
|
|
2620
|
+
if (viteServer) {
|
|
2621
|
+
module = await viteServer.ssrLoadModule(filePath);
|
|
2622
|
+
} else {
|
|
2623
|
+
module = await import(filePath);
|
|
2624
|
+
}
|
|
2625
|
+
for (const [exportName, exportValue] of Object.entries(module)) {
|
|
2626
|
+
if (isJayAction(exportValue)) {
|
|
2627
|
+
registry.register(exportValue);
|
|
2628
|
+
result.actionNames.push(exportValue.actionName);
|
|
2629
|
+
result.actionCount++;
|
|
2630
|
+
if (verbose) {
|
|
2631
|
+
getLogger().info(
|
|
2632
|
+
`[Actions] Registered: ${exportValue.actionName}`
|
|
2633
|
+
);
|
|
2634
|
+
}
|
|
2635
|
+
} else if (isJayStreamAction(exportValue)) {
|
|
2636
|
+
registry.registerStream(exportValue);
|
|
2637
|
+
result.actionNames.push(exportValue.actionName);
|
|
2638
|
+
result.actionCount++;
|
|
2639
|
+
if (verbose) {
|
|
2640
|
+
getLogger().info(
|
|
2641
|
+
`[Actions] Registered stream: ${exportValue.actionName}`
|
|
2642
|
+
);
|
|
2643
|
+
}
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2646
|
+
} catch (error) {
|
|
2647
|
+
getLogger().error(`[Actions] Failed to import ${filePath}: ${error}`);
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2650
|
+
return result;
|
|
2651
|
+
}
|
|
2652
|
+
async function findActionFiles(dir) {
|
|
2653
|
+
const files = [];
|
|
2654
|
+
const entries = await fs.promises.readdir(dir, { withFileTypes: true });
|
|
2655
|
+
for (const entry of entries) {
|
|
2656
|
+
const fullPath = path.join(dir, entry.name);
|
|
2657
|
+
if (entry.isDirectory()) {
|
|
2658
|
+
const subFiles = await findActionFiles(fullPath);
|
|
2659
|
+
files.push(...subFiles);
|
|
2660
|
+
} else if (entry.isFile() && entry.name.endsWith(".actions.ts")) {
|
|
2661
|
+
files.push(fullPath);
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
return files;
|
|
2665
|
+
}
|
|
2666
|
+
async function discoverAllPluginActions(options) {
|
|
2667
|
+
const { projectRoot, registry = actionRegistry, verbose = false, viteServer } = options;
|
|
2668
|
+
const allActions = [];
|
|
2669
|
+
const localPluginsPath = path.join(projectRoot, "src/plugins");
|
|
2670
|
+
if (fs.existsSync(localPluginsPath)) {
|
|
2671
|
+
const pluginDirs = await fs.promises.readdir(localPluginsPath, { withFileTypes: true });
|
|
2672
|
+
for (const entry of pluginDirs) {
|
|
2673
|
+
if (entry.isDirectory()) {
|
|
2674
|
+
const pluginPath = path.join(localPluginsPath, entry.name);
|
|
2675
|
+
const actions = await discoverPluginActions(
|
|
2676
|
+
pluginPath,
|
|
2677
|
+
projectRoot,
|
|
2678
|
+
registry,
|
|
2679
|
+
verbose,
|
|
2680
|
+
viteServer
|
|
2681
|
+
);
|
|
2682
|
+
allActions.push(...actions);
|
|
2683
|
+
}
|
|
2684
|
+
}
|
|
2685
|
+
}
|
|
2686
|
+
const npmActions = await discoverNpmPluginActions(projectRoot, registry, verbose, viteServer);
|
|
2687
|
+
allActions.push(...npmActions);
|
|
2688
|
+
return allActions;
|
|
2689
|
+
}
|
|
2690
|
+
async function discoverNpmPluginActions(projectRoot, registry, verbose, viteServer) {
|
|
2691
|
+
const allActions = [];
|
|
2692
|
+
const packageJsonPath = path.join(projectRoot, "package.json");
|
|
2693
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
2694
|
+
return allActions;
|
|
2695
|
+
}
|
|
2696
|
+
try {
|
|
2697
|
+
const packageJson = JSON.parse(await fs.promises.readFile(packageJsonPath, "utf-8"));
|
|
2698
|
+
const dependencies = {
|
|
2699
|
+
...packageJson.dependencies,
|
|
2700
|
+
...packageJson.devDependencies
|
|
2701
|
+
};
|
|
2702
|
+
for (const packageName of Object.keys(dependencies)) {
|
|
2703
|
+
try {
|
|
2704
|
+
const pluginYamlPath = tryResolvePluginYaml(packageName, projectRoot);
|
|
2705
|
+
if (!pluginYamlPath) {
|
|
2706
|
+
continue;
|
|
2707
|
+
}
|
|
2708
|
+
const pluginDir = path.dirname(pluginYamlPath);
|
|
2709
|
+
const pluginConfig = loadPluginManifest(pluginDir);
|
|
2710
|
+
if (!pluginConfig || !pluginConfig.actions || !Array.isArray(pluginConfig.actions)) {
|
|
2711
|
+
continue;
|
|
2712
|
+
}
|
|
2713
|
+
if (verbose) {
|
|
2714
|
+
getLogger().info(
|
|
2715
|
+
`[Actions] NPM plugin "${packageName}" declares actions: ${JSON.stringify(pluginConfig.actions)}`
|
|
2716
|
+
);
|
|
2717
|
+
}
|
|
2718
|
+
const actions = await registerNpmPluginActions(
|
|
2719
|
+
packageName,
|
|
2720
|
+
pluginConfig,
|
|
2721
|
+
pluginDir,
|
|
2722
|
+
registry,
|
|
2723
|
+
verbose,
|
|
2724
|
+
viteServer
|
|
2725
|
+
);
|
|
2726
|
+
allActions.push(...actions);
|
|
2727
|
+
} catch {
|
|
2728
|
+
continue;
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
2731
|
+
} catch (error) {
|
|
2732
|
+
getLogger().error(`[Actions] Failed to read project package.json: ${error}`);
|
|
2733
|
+
}
|
|
2734
|
+
return allActions;
|
|
2735
|
+
}
|
|
2736
|
+
function tryResolvePluginYaml(packageName, projectRoot) {
|
|
2737
|
+
try {
|
|
2738
|
+
return require$2.resolve(`${packageName}/plugin.yaml`, {
|
|
2739
|
+
paths: [projectRoot]
|
|
2740
|
+
});
|
|
2741
|
+
} catch {
|
|
2742
|
+
return null;
|
|
2743
|
+
}
|
|
2744
|
+
}
|
|
2745
|
+
function resolveNpmActionMetadataPath(actionPath, packageName, pluginDir) {
|
|
2746
|
+
if (!actionPath.startsWith(".")) {
|
|
2747
|
+
try {
|
|
2748
|
+
return require$2.resolve(`${packageName}/${actionPath}`, {
|
|
2749
|
+
paths: [pluginDir]
|
|
2750
|
+
});
|
|
2751
|
+
} catch {
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
const resolved = resolveActionMetadataPath(actionPath, pluginDir);
|
|
2755
|
+
if (fs.existsSync(resolved)) {
|
|
2756
|
+
return resolved;
|
|
2757
|
+
}
|
|
2758
|
+
getLogger().warn(
|
|
2759
|
+
`[Actions] Could not resolve .jay-action file "${actionPath}" for package "${packageName}"`
|
|
2760
|
+
);
|
|
2761
|
+
return null;
|
|
2762
|
+
}
|
|
2763
|
+
async function registerNpmPluginActions(packageName, pluginConfig, pluginDir, registry, verbose, viteServer) {
|
|
2764
|
+
const registeredActions = [];
|
|
2765
|
+
try {
|
|
2766
|
+
let pluginModule;
|
|
2767
|
+
if (viteServer) {
|
|
2768
|
+
pluginModule = await viteServer.ssrLoadModule(packageName);
|
|
2769
|
+
} else {
|
|
2770
|
+
pluginModule = await import(packageName);
|
|
2771
|
+
}
|
|
2772
|
+
let toolsModule;
|
|
2773
|
+
const loadToolsModule = async () => {
|
|
2774
|
+
if (!toolsModule) {
|
|
2775
|
+
const toolsEntry = `${packageName}/tools`;
|
|
2776
|
+
toolsModule = viteServer ? await viteServer.ssrLoadModule(toolsEntry) : await import(toolsEntry);
|
|
2777
|
+
}
|
|
2778
|
+
return toolsModule;
|
|
2779
|
+
};
|
|
2780
|
+
for (const entry of pluginConfig.actions) {
|
|
2781
|
+
const { name: actionName, action: actionPath, devOnly } = normalizeActionEntry(entry);
|
|
2782
|
+
const sourceModule = devOnly ? await loadToolsModule() : pluginModule;
|
|
2783
|
+
const actionExport = sourceModule[actionName];
|
|
2784
|
+
if (actionExport && isJayAction(actionExport)) {
|
|
2785
|
+
registry.register(actionExport);
|
|
2786
|
+
const registeredName = actionExport.actionName;
|
|
2787
|
+
registeredActions.push(registeredName);
|
|
2788
|
+
if (actionPath) {
|
|
2789
|
+
const metadataFilePath = resolveNpmActionMetadataPath(
|
|
2790
|
+
actionPath,
|
|
2791
|
+
packageName,
|
|
2792
|
+
pluginDir
|
|
2793
|
+
);
|
|
2794
|
+
if (metadataFilePath) {
|
|
2795
|
+
const metadata = loadActionMetadata(metadataFilePath);
|
|
2796
|
+
if (metadata) {
|
|
2797
|
+
registry.setMetadata(registeredName, metadata);
|
|
2798
|
+
if (verbose) {
|
|
2799
|
+
getLogger().info(
|
|
2800
|
+
`[Actions] Loaded metadata for "${registeredName}" from ${actionPath}`
|
|
2801
|
+
);
|
|
2802
|
+
}
|
|
2803
|
+
}
|
|
2804
|
+
}
|
|
2805
|
+
}
|
|
2806
|
+
if (verbose) {
|
|
2807
|
+
getLogger().info(`[Actions] Registered NPM plugin action: ${registeredName}`);
|
|
2808
|
+
}
|
|
2809
|
+
} else if (actionExport && isJayStreamAction(actionExport)) {
|
|
2810
|
+
registry.registerStream(actionExport);
|
|
2811
|
+
const registeredName = actionExport.actionName;
|
|
2812
|
+
registeredActions.push(registeredName);
|
|
2813
|
+
if (verbose) {
|
|
2814
|
+
getLogger().info(`[Actions] Registered NPM plugin stream: ${registeredName}`);
|
|
2815
|
+
}
|
|
2816
|
+
} else {
|
|
2817
|
+
getLogger().warn(
|
|
2818
|
+
`[Actions] NPM plugin "${packageName}" declares action "${actionName}" but it's not exported or not a JayAction`
|
|
2819
|
+
);
|
|
2820
|
+
}
|
|
2821
|
+
}
|
|
2822
|
+
} catch (importError) {
|
|
2823
|
+
getLogger().error(`[Actions] Failed to import NPM plugin "${packageName}": ${importError}`);
|
|
2824
|
+
}
|
|
2825
|
+
return registeredActions;
|
|
2826
|
+
}
|
|
2827
|
+
async function discoverPluginActions(pluginPath, projectRoot, registry = actionRegistry, verbose = false, viteServer) {
|
|
2828
|
+
const pluginConfig = loadPluginManifest(pluginPath);
|
|
2829
|
+
if (!pluginConfig) {
|
|
2830
|
+
return [];
|
|
2831
|
+
}
|
|
2832
|
+
if (!pluginConfig.actions || !Array.isArray(pluginConfig.actions)) {
|
|
2833
|
+
return [];
|
|
2834
|
+
}
|
|
2835
|
+
const registeredActions = [];
|
|
2836
|
+
const pluginName = pluginConfig.name || path.basename(pluginPath);
|
|
2837
|
+
if (verbose) {
|
|
2838
|
+
getLogger().info(
|
|
2839
|
+
`[Actions] Plugin "${pluginName}" declares actions: ${JSON.stringify(pluginConfig.actions)}`
|
|
2840
|
+
);
|
|
2841
|
+
}
|
|
2842
|
+
let modulePath = pluginConfig.module ? path.join(pluginPath, pluginConfig.module) : path.join(pluginPath, "index.ts");
|
|
2843
|
+
if (!fs.existsSync(modulePath)) {
|
|
2844
|
+
const tsPath = modulePath + ".ts";
|
|
2845
|
+
const jsPath = modulePath + ".js";
|
|
2846
|
+
if (fs.existsSync(tsPath)) {
|
|
2847
|
+
modulePath = tsPath;
|
|
2848
|
+
} else if (fs.existsSync(jsPath)) {
|
|
2849
|
+
modulePath = jsPath;
|
|
2850
|
+
} else {
|
|
2851
|
+
getLogger().warn(`[Actions] Plugin "${pluginName}" module not found at ${modulePath}`);
|
|
2852
|
+
return [];
|
|
2853
|
+
}
|
|
2854
|
+
}
|
|
2855
|
+
const resolveLocalToolsModulePath = () => {
|
|
2856
|
+
for (const candidate of ["tools.ts", "tools.js"]) {
|
|
2857
|
+
const candidatePath = path.join(pluginPath, candidate);
|
|
2858
|
+
if (fs.existsSync(candidatePath)) return candidatePath;
|
|
2859
|
+
}
|
|
2860
|
+
return null;
|
|
2861
|
+
};
|
|
2862
|
+
try {
|
|
2863
|
+
let pluginModule;
|
|
2864
|
+
if (viteServer) {
|
|
2865
|
+
pluginModule = await viteServer.ssrLoadModule(modulePath);
|
|
2866
|
+
} else {
|
|
2867
|
+
pluginModule = await import(modulePath);
|
|
2868
|
+
}
|
|
2869
|
+
let toolsModule;
|
|
2870
|
+
const loadToolsModule = async () => {
|
|
2871
|
+
if (toolsModule) return toolsModule;
|
|
2872
|
+
const toolsPath = resolveLocalToolsModulePath();
|
|
2873
|
+
if (!toolsPath) {
|
|
2874
|
+
getLogger().warn(
|
|
2875
|
+
`[Actions] Plugin "${pluginName}" declares a devOnly action but has no tools.ts/tools.js module`
|
|
2876
|
+
);
|
|
2877
|
+
return void 0;
|
|
2878
|
+
}
|
|
2879
|
+
toolsModule = viteServer ? await viteServer.ssrLoadModule(toolsPath) : await import(toolsPath);
|
|
2880
|
+
return toolsModule;
|
|
2881
|
+
};
|
|
2882
|
+
for (const entry of pluginConfig.actions) {
|
|
2883
|
+
const { name: actionName, action: actionPath, devOnly } = normalizeActionEntry(entry);
|
|
2884
|
+
const sourceModule = devOnly ? await loadToolsModule() : pluginModule;
|
|
2885
|
+
const actionExport = sourceModule?.[actionName];
|
|
2886
|
+
if (actionExport && isJayAction(actionExport)) {
|
|
2887
|
+
registry.register(actionExport);
|
|
2888
|
+
const registeredName = actionExport.actionName;
|
|
2889
|
+
registeredActions.push(registeredName);
|
|
2890
|
+
if (actionPath) {
|
|
2891
|
+
const metadataFilePath = resolveActionMetadataPath(actionPath, pluginPath);
|
|
2892
|
+
const metadata = loadActionMetadata(metadataFilePath);
|
|
2893
|
+
if (metadata) {
|
|
2894
|
+
registry.setMetadata(registeredName, metadata);
|
|
2895
|
+
if (verbose) {
|
|
2896
|
+
getLogger().info(
|
|
2897
|
+
`[Actions] Loaded metadata for "${registeredName}" from ${actionPath}`
|
|
2898
|
+
);
|
|
2899
|
+
}
|
|
2900
|
+
}
|
|
2901
|
+
}
|
|
2902
|
+
if (verbose) {
|
|
2903
|
+
getLogger().info(`[Actions] Registered plugin action: ${registeredName}`);
|
|
2904
|
+
}
|
|
2905
|
+
} else if (actionExport && isJayStreamAction(actionExport)) {
|
|
2906
|
+
registry.registerStream(actionExport);
|
|
2907
|
+
const registeredName = actionExport.actionName;
|
|
2908
|
+
registeredActions.push(registeredName);
|
|
2909
|
+
if (verbose) {
|
|
2910
|
+
getLogger().info(`[Actions] Registered plugin stream: ${registeredName}`);
|
|
2911
|
+
}
|
|
2912
|
+
} else {
|
|
2913
|
+
getLogger().warn(
|
|
2914
|
+
`[Actions] Plugin "${pluginName}" declares action "${actionName}" but it's not exported or not a JayAction`
|
|
2915
|
+
);
|
|
2916
|
+
}
|
|
2917
|
+
}
|
|
2918
|
+
} catch (importError) {
|
|
2919
|
+
getLogger().error(
|
|
2920
|
+
`[Actions] Failed to import plugin module at ${modulePath}: ${importError}`
|
|
2921
|
+
);
|
|
2922
|
+
}
|
|
2923
|
+
return registeredActions;
|
|
2924
|
+
}
|
|
2925
|
+
const require$1 = createRequire(import.meta.url);
|
|
2926
|
+
async function executeDynamicGenerator(plugin, config, projectRoot, services, verbose, viteServer) {
|
|
2927
|
+
const { pluginPath, name: pluginName, isLocal, packageName } = plugin;
|
|
2928
|
+
if (!config.generator) {
|
|
2929
|
+
throw new Error(
|
|
2930
|
+
`Plugin "${pluginName}" has dynamic_contracts entry but no generator specified`
|
|
2931
|
+
);
|
|
2932
|
+
}
|
|
2933
|
+
const isFilePath = config.generator.startsWith("./") || config.generator.startsWith("/") || config.generator.includes(".ts") || config.generator.includes(".js");
|
|
2934
|
+
let generator;
|
|
2935
|
+
if (isFilePath) {
|
|
2936
|
+
let generatorPath;
|
|
2937
|
+
if (!isLocal) {
|
|
2938
|
+
try {
|
|
2939
|
+
generatorPath = require$1.resolve(`${packageName}/${config.generator}`, {
|
|
2940
|
+
paths: [projectRoot]
|
|
2941
|
+
});
|
|
2942
|
+
} catch {
|
|
2943
|
+
generatorPath = path.join(pluginPath, config.generator);
|
|
2944
|
+
}
|
|
2945
|
+
} else {
|
|
2946
|
+
generatorPath = path.join(pluginPath, config.generator);
|
|
2947
|
+
}
|
|
2948
|
+
if (!fs.existsSync(generatorPath)) {
|
|
2949
|
+
const withTs = generatorPath + ".ts";
|
|
2950
|
+
const withJs = generatorPath + ".js";
|
|
2951
|
+
if (fs.existsSync(withTs)) {
|
|
2952
|
+
generatorPath = withTs;
|
|
2953
|
+
} else if (fs.existsSync(withJs)) {
|
|
2954
|
+
generatorPath = withJs;
|
|
2955
|
+
}
|
|
2956
|
+
}
|
|
2957
|
+
if (!fs.existsSync(generatorPath)) {
|
|
2958
|
+
throw new Error(
|
|
2959
|
+
`Generator file not found for plugin "${pluginName}": ${config.generator}`
|
|
2960
|
+
);
|
|
2961
|
+
}
|
|
2962
|
+
if (verbose) {
|
|
2963
|
+
getLogger().info(` Loading generator from file: ${generatorPath}`);
|
|
2964
|
+
}
|
|
2965
|
+
let generatorModule;
|
|
2966
|
+
if (viteServer) {
|
|
2967
|
+
generatorModule = await viteServer.ssrLoadModule(generatorPath);
|
|
2968
|
+
} else {
|
|
2969
|
+
generatorModule = await import(generatorPath);
|
|
2970
|
+
}
|
|
2971
|
+
generator = generatorModule.generator || generatorModule.default;
|
|
2972
|
+
} else {
|
|
2973
|
+
if (verbose) {
|
|
2974
|
+
getLogger().info(
|
|
2975
|
+
` Loading generator export: ${config.generator} from ${packageName}`
|
|
2976
|
+
);
|
|
2977
|
+
}
|
|
2978
|
+
let pluginModule;
|
|
2979
|
+
if (viteServer) {
|
|
2980
|
+
pluginModule = await viteServer.ssrLoadModule(packageName);
|
|
2981
|
+
} else {
|
|
2982
|
+
pluginModule = await import(packageName);
|
|
2983
|
+
}
|
|
2984
|
+
generator = pluginModule[config.generator];
|
|
2985
|
+
if (!generator) {
|
|
2986
|
+
throw new Error(
|
|
2987
|
+
`Generator "${config.generator}" not exported from plugin "${pluginName}". Ensure it's exported from the package's index.ts`
|
|
2988
|
+
);
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2991
|
+
if (!generator || typeof generator.generate !== "function") {
|
|
2992
|
+
throw new Error(
|
|
2993
|
+
`Generator "${config.generator}" for plugin "${pluginName}" must have a 'generate' function. Use makeContractGenerator() to create valid generators.`
|
|
2994
|
+
);
|
|
2995
|
+
}
|
|
2996
|
+
const resolvedServices = [];
|
|
2997
|
+
for (const marker of generator.services) {
|
|
2998
|
+
const service = services.get(marker);
|
|
2999
|
+
if (!service) {
|
|
3000
|
+
const markerName = marker.description ?? "unknown";
|
|
3001
|
+
throw new Error(
|
|
3002
|
+
`Service "${markerName}" required by ${pluginName} generator not found. Ensure it's registered in init.ts`
|
|
3003
|
+
);
|
|
3004
|
+
}
|
|
3005
|
+
resolvedServices.push(service);
|
|
3006
|
+
}
|
|
3007
|
+
if (verbose) {
|
|
3008
|
+
getLogger().info(` Executing generator...`);
|
|
3009
|
+
}
|
|
3010
|
+
return await generator.generate(...resolvedServices);
|
|
3011
|
+
}
|
|
3012
|
+
function resolveStaticContractPath(plugin, contractSpec, projectRoot) {
|
|
3013
|
+
const { pluginPath, isLocal, packageName } = plugin;
|
|
3014
|
+
if (!isLocal) {
|
|
3015
|
+
try {
|
|
3016
|
+
return require$1.resolve(`${packageName}/${contractSpec}`, {
|
|
3017
|
+
paths: [projectRoot]
|
|
3018
|
+
});
|
|
3019
|
+
} catch {
|
|
3020
|
+
const possiblePaths = [
|
|
3021
|
+
path.join(pluginPath, "dist", contractSpec),
|
|
3022
|
+
path.join(pluginPath, "lib", contractSpec),
|
|
3023
|
+
path.join(pluginPath, contractSpec)
|
|
3024
|
+
];
|
|
3025
|
+
const found = possiblePaths.find((p) => fs.existsSync(p));
|
|
3026
|
+
return found || possiblePaths[0];
|
|
3027
|
+
}
|
|
3028
|
+
} else {
|
|
3029
|
+
return path.join(pluginPath, contractSpec);
|
|
3030
|
+
}
|
|
3031
|
+
}
|
|
3032
|
+
function resolveActionFilePath(actionPath, packageName, pluginPath, isLocal, projectRoot) {
|
|
3033
|
+
if (!isLocal && !actionPath.startsWith(".")) {
|
|
3034
|
+
try {
|
|
3035
|
+
return require$1.resolve(`${packageName}/${actionPath}`, {
|
|
3036
|
+
paths: [projectRoot]
|
|
3037
|
+
});
|
|
3038
|
+
} catch {
|
|
3039
|
+
const possiblePaths = [
|
|
3040
|
+
path.join(pluginPath, "dist", actionPath),
|
|
3041
|
+
path.join(pluginPath, "lib", actionPath),
|
|
3042
|
+
path.join(pluginPath, actionPath)
|
|
3043
|
+
];
|
|
3044
|
+
const found = possiblePaths.find((p) => fs.existsSync(p));
|
|
3045
|
+
return found || null;
|
|
3046
|
+
}
|
|
3047
|
+
}
|
|
3048
|
+
const resolved = resolveActionMetadataPath(actionPath, pluginPath);
|
|
3049
|
+
return fs.existsSync(resolved) ? resolved : null;
|
|
3050
|
+
}
|
|
3051
|
+
function toKebabCase(str) {
|
|
3052
|
+
return str.replace(/([A-Z])/g, "-$1").toLowerCase().replace(/^-/, "");
|
|
3053
|
+
}
|
|
3054
|
+
async function materializeContracts(options, services = /* @__PURE__ */ new Map()) {
|
|
3055
|
+
const {
|
|
3056
|
+
projectRoot,
|
|
3057
|
+
outputDir = path.join(projectRoot, "agent-kit", "materialized-contracts"),
|
|
3058
|
+
dynamicOnly = false,
|
|
3059
|
+
pluginFilter,
|
|
3060
|
+
verbose = false,
|
|
3061
|
+
viteServer
|
|
3062
|
+
} = options;
|
|
3063
|
+
const pluginsIndexMap = /* @__PURE__ */ new Map();
|
|
3064
|
+
let staticCount = 0;
|
|
3065
|
+
let dynamicCount = 0;
|
|
3066
|
+
if (verbose) {
|
|
3067
|
+
getLogger().info("Scanning for plugins...");
|
|
3068
|
+
}
|
|
3069
|
+
const plugins = await scanPlugins({
|
|
3070
|
+
projectRoot,
|
|
3071
|
+
verbose,
|
|
3072
|
+
includeDevDeps: true
|
|
3073
|
+
// Include dev deps for contract discovery
|
|
3074
|
+
});
|
|
3075
|
+
if (verbose) {
|
|
3076
|
+
getLogger().info(`Found ${plugins.size} plugin(s)`);
|
|
3077
|
+
}
|
|
3078
|
+
for (const [pluginKey, plugin] of plugins) {
|
|
3079
|
+
if (pluginFilter && plugin.name !== pluginFilter && pluginKey !== pluginFilter) continue;
|
|
3080
|
+
if (verbose) {
|
|
3081
|
+
getLogger().info(`
|
|
3082
|
+
📦 Processing plugin: ${plugin.name}`);
|
|
3083
|
+
}
|
|
3084
|
+
const { manifest } = plugin;
|
|
3085
|
+
const pluginRelPath = path.relative(projectRoot, plugin.pluginPath);
|
|
3086
|
+
if (!pluginsIndexMap.has(plugin.name)) {
|
|
3087
|
+
const entry = {
|
|
3088
|
+
path: "./" + pluginRelPath.replace(/\\/g, "/"),
|
|
3089
|
+
contracts: [],
|
|
3090
|
+
actions: []
|
|
3091
|
+
};
|
|
3092
|
+
if (manifest.services?.length) {
|
|
3093
|
+
entry.services = manifest.services.map((s2) => {
|
|
3094
|
+
const docPath = s2.doc ? "./" + path.relative(projectRoot, path.resolve(plugin.pluginPath, s2.doc)) : void 0;
|
|
3095
|
+
return {
|
|
3096
|
+
name: s2.name,
|
|
3097
|
+
marker: s2.marker,
|
|
3098
|
+
...s2.description && { description: s2.description },
|
|
3099
|
+
...docPath && { doc: docPath }
|
|
3100
|
+
};
|
|
3101
|
+
});
|
|
3102
|
+
}
|
|
3103
|
+
if (manifest.contexts?.length) {
|
|
3104
|
+
entry.contexts = manifest.contexts.map((c2) => {
|
|
3105
|
+
const docPath = c2.doc ? "./" + path.relative(projectRoot, path.resolve(plugin.pluginPath, c2.doc)) : void 0;
|
|
3106
|
+
return {
|
|
3107
|
+
name: c2.name,
|
|
3108
|
+
marker: c2.marker,
|
|
3109
|
+
...c2.description && { description: c2.description },
|
|
3110
|
+
...docPath && { doc: docPath }
|
|
3111
|
+
};
|
|
3112
|
+
});
|
|
3113
|
+
}
|
|
3114
|
+
if (manifest.routes?.length) {
|
|
3115
|
+
entry.routes = manifest.routes.map((r) => ({
|
|
3116
|
+
path: r.path,
|
|
3117
|
+
...r.description && { description: r.description }
|
|
3118
|
+
}));
|
|
3119
|
+
}
|
|
3120
|
+
if (manifest.commands?.length) {
|
|
3121
|
+
entry.commands = manifest.commands.map((c2) => {
|
|
3122
|
+
let description;
|
|
3123
|
+
if (c2.command) {
|
|
3124
|
+
try {
|
|
3125
|
+
const cmdPath = path.resolve(plugin.pluginPath, c2.command);
|
|
3126
|
+
const cmdContent = fs.readFileSync(cmdPath, "utf-8");
|
|
3127
|
+
const parsed = YAML.parse(cmdContent);
|
|
3128
|
+
description = parsed?.description;
|
|
3129
|
+
} catch {
|
|
3130
|
+
}
|
|
3131
|
+
}
|
|
3132
|
+
return {
|
|
3133
|
+
name: c2.name,
|
|
3134
|
+
...description && { description }
|
|
3135
|
+
};
|
|
3136
|
+
});
|
|
3137
|
+
}
|
|
3138
|
+
pluginsIndexMap.set(plugin.name, entry);
|
|
3139
|
+
}
|
|
3140
|
+
if (!dynamicOnly && manifest.contracts) {
|
|
3141
|
+
for (const contract of manifest.contracts) {
|
|
3142
|
+
const contractPath = resolveStaticContractPath(
|
|
3143
|
+
plugin,
|
|
3144
|
+
contract.contract,
|
|
3145
|
+
projectRoot
|
|
3146
|
+
);
|
|
3147
|
+
const relativePath = path.relative(projectRoot, contractPath);
|
|
3148
|
+
let description = contract.description;
|
|
3149
|
+
if (!description) {
|
|
3150
|
+
try {
|
|
3151
|
+
const contractContent = fs.readFileSync(contractPath, "utf-8");
|
|
3152
|
+
const parsed = YAML.parse(contractContent);
|
|
3153
|
+
if (parsed?.description && typeof parsed.description === "string") {
|
|
3154
|
+
description = parsed.description;
|
|
3155
|
+
}
|
|
3156
|
+
} catch {
|
|
3157
|
+
}
|
|
3158
|
+
}
|
|
3159
|
+
pluginsIndexMap.get(plugin.name).contracts.push({
|
|
3160
|
+
name: contract.name,
|
|
3161
|
+
...description && { description },
|
|
3162
|
+
type: "static",
|
|
3163
|
+
path: "./" + relativePath
|
|
3164
|
+
});
|
|
3165
|
+
staticCount++;
|
|
3166
|
+
if (verbose) {
|
|
3167
|
+
getLogger().info(` 📄 Static: ${contract.name}`);
|
|
3168
|
+
}
|
|
3169
|
+
}
|
|
3170
|
+
}
|
|
3171
|
+
if (manifest.dynamic_contracts) {
|
|
3172
|
+
const dynamicConfigs = Array.isArray(manifest.dynamic_contracts) ? manifest.dynamic_contracts : [manifest.dynamic_contracts];
|
|
3173
|
+
const pluginOutputDir = path.join(outputDir, plugin.name.replace(/[@/]/g, "_"));
|
|
3174
|
+
fs.mkdirSync(pluginOutputDir, { recursive: true });
|
|
3175
|
+
for (const config of dynamicConfigs) {
|
|
3176
|
+
if (verbose) {
|
|
3177
|
+
getLogger().info(` ⚡ Dynamic contracts (prefix: ${config.prefix})`);
|
|
3178
|
+
}
|
|
3179
|
+
try {
|
|
3180
|
+
const generatedContracts = await executeDynamicGenerator(
|
|
3181
|
+
plugin,
|
|
3182
|
+
config,
|
|
3183
|
+
projectRoot,
|
|
3184
|
+
services,
|
|
3185
|
+
verbose,
|
|
3186
|
+
viteServer
|
|
3187
|
+
);
|
|
3188
|
+
const prefix = config.prefix;
|
|
3189
|
+
if (generatedContracts.length > 1) {
|
|
3190
|
+
const missing = generatedContracts.filter((c2) => !c2.name);
|
|
3191
|
+
if (missing.length > 0) {
|
|
3192
|
+
getLogger().error(
|
|
3193
|
+
` ❌ Dynamic contract generator for "${prefix}" returned ${generatedContracts.length} contracts but ${missing.length} are missing a name. Names are required when a generator returns multiple contracts.`
|
|
3194
|
+
);
|
|
3195
|
+
continue;
|
|
3196
|
+
}
|
|
3197
|
+
}
|
|
3198
|
+
for (const generated of generatedContracts) {
|
|
3199
|
+
const kebabName = generated.name ? toKebabCase(generated.name) : null;
|
|
3200
|
+
const fullName = kebabName ? `${prefix}/${kebabName}` : prefix;
|
|
3201
|
+
const fileName = kebabName ? `${prefix}-${kebabName}.jay-contract` : `${prefix}.jay-contract`;
|
|
3202
|
+
const filePath = path.join(pluginOutputDir, fileName);
|
|
3203
|
+
fs.writeFileSync(filePath, generated.yaml, "utf-8");
|
|
3204
|
+
const relativePath = path.relative(projectRoot, filePath);
|
|
3205
|
+
let dynDescription;
|
|
3206
|
+
try {
|
|
3207
|
+
const parsedYaml = YAML.parse(generated.yaml);
|
|
3208
|
+
if (parsedYaml?.description && typeof parsedYaml.description === "string") {
|
|
3209
|
+
dynDescription = parsedYaml.description;
|
|
3210
|
+
}
|
|
3211
|
+
} catch {
|
|
3212
|
+
}
|
|
3213
|
+
const contractEntry = {
|
|
3214
|
+
name: fullName,
|
|
3215
|
+
...dynDescription && { description: dynDescription },
|
|
3216
|
+
type: "dynamic",
|
|
3217
|
+
path: "./" + relativePath,
|
|
3218
|
+
...generated.metadata && { metadata: generated.metadata }
|
|
3219
|
+
};
|
|
3220
|
+
pluginsIndexMap.get(plugin.name).contracts.push(contractEntry);
|
|
3221
|
+
dynamicCount++;
|
|
3222
|
+
if (verbose) {
|
|
3223
|
+
getLogger().info(` ⚡ Materialized: ${fullName}`);
|
|
3224
|
+
}
|
|
3225
|
+
}
|
|
3226
|
+
} catch (error) {
|
|
3227
|
+
getLogger().error(
|
|
3228
|
+
` ❌ Failed to materialize dynamic contracts for ${plugin.name} (${config.prefix}): ${error}`
|
|
3229
|
+
);
|
|
3230
|
+
}
|
|
3231
|
+
}
|
|
3232
|
+
}
|
|
3233
|
+
if (manifest.actions && Array.isArray(manifest.actions)) {
|
|
3234
|
+
for (const entry of manifest.actions) {
|
|
3235
|
+
const { name: actionName, action: actionPath } = normalizeActionEntry(entry);
|
|
3236
|
+
if (!actionPath) continue;
|
|
3237
|
+
const metadataFilePath = resolveActionFilePath(
|
|
3238
|
+
actionPath,
|
|
3239
|
+
plugin.packageName,
|
|
3240
|
+
plugin.pluginPath,
|
|
3241
|
+
plugin.isLocal,
|
|
3242
|
+
projectRoot
|
|
3243
|
+
);
|
|
3244
|
+
if (!metadataFilePath) continue;
|
|
3245
|
+
const metadata = loadActionMetadata(metadataFilePath);
|
|
3246
|
+
if (!metadata) continue;
|
|
3247
|
+
const actionRelPath = path.relative(projectRoot, metadataFilePath);
|
|
3248
|
+
const pluginEntry = pluginsIndexMap.get(plugin.name);
|
|
3249
|
+
if (!pluginEntry.actions) pluginEntry.actions = [];
|
|
3250
|
+
pluginEntry.actions.push({
|
|
3251
|
+
name: metadata.name,
|
|
3252
|
+
description: metadata.description,
|
|
3253
|
+
path: "./" + actionRelPath.replace(/\\/g, "/")
|
|
3254
|
+
});
|
|
3255
|
+
if (verbose) {
|
|
3256
|
+
getLogger().info(` 🔧 Action: ${metadata.name} (${actionPath})`);
|
|
3257
|
+
}
|
|
3258
|
+
}
|
|
3259
|
+
}
|
|
3260
|
+
}
|
|
3261
|
+
const pluginsIndex = {
|
|
3262
|
+
plugins: Array.from(pluginsIndexMap.entries()).map(([name, data]) => ({
|
|
3263
|
+
name,
|
|
3264
|
+
path: data.path,
|
|
3265
|
+
contracts: data.contracts,
|
|
3266
|
+
...data.actions && data.actions.length > 0 && { actions: data.actions },
|
|
3267
|
+
...data.services?.length && { services: data.services },
|
|
3268
|
+
...data.contexts?.length && { contexts: data.contexts },
|
|
3269
|
+
...data.routes?.length && { routes: data.routes },
|
|
3270
|
+
...data.commands?.length && { commands: data.commands }
|
|
3271
|
+
}))
|
|
3272
|
+
};
|
|
3273
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
3274
|
+
const agentKitDir = path.dirname(outputDir);
|
|
3275
|
+
const pluginsIndexPath = path.join(agentKitDir, "plugins-index.yaml");
|
|
3276
|
+
fs.writeFileSync(pluginsIndexPath, YAML.stringify(pluginsIndex), "utf-8");
|
|
3277
|
+
if (verbose) {
|
|
3278
|
+
getLogger().info(`
|
|
3279
|
+
✅ Plugins index written to: ${pluginsIndexPath}`);
|
|
3280
|
+
}
|
|
3281
|
+
return {
|
|
3282
|
+
pluginsIndex,
|
|
3283
|
+
staticCount,
|
|
3284
|
+
dynamicCount,
|
|
3285
|
+
outputDir
|
|
3286
|
+
};
|
|
3287
|
+
}
|
|
3288
|
+
const CACHE_TAG_START = '<script type="application/jay-cache">';
|
|
3289
|
+
const CACHE_TAG_END = "<\/script>";
|
|
3290
|
+
function hashParams(params) {
|
|
3291
|
+
const sortedParams = Object.keys(params).sort().reduce(
|
|
3292
|
+
(acc, key) => {
|
|
3293
|
+
acc[key] = params[key];
|
|
3294
|
+
return acc;
|
|
3295
|
+
},
|
|
3296
|
+
{}
|
|
3297
|
+
);
|
|
3298
|
+
const json = JSON.stringify(sortedParams);
|
|
3299
|
+
if (json === "{}") return "";
|
|
3300
|
+
return "_" + crypto.createHash("md5").update(json).digest("hex").substring(0, 8);
|
|
3301
|
+
}
|
|
3302
|
+
function embedCacheMetadata(jayHtmlContent, slowViewState, carryForward, sourcePath) {
|
|
3303
|
+
const metadata = JSON.stringify({ slowViewState, carryForward, sourcePath });
|
|
3304
|
+
const cacheTag = `${CACHE_TAG_START}${metadata}${CACHE_TAG_END}`;
|
|
3305
|
+
const headMatch = jayHtmlContent.match(/<head[^>]*>/i);
|
|
3306
|
+
if (headMatch) {
|
|
3307
|
+
const insertPos = headMatch.index + headMatch[0].length;
|
|
3308
|
+
return jayHtmlContent.substring(0, insertPos) + "\n" + cacheTag + jayHtmlContent.substring(insertPos);
|
|
3309
|
+
}
|
|
3310
|
+
return `${cacheTag}
|
|
3311
|
+
${jayHtmlContent}`;
|
|
3312
|
+
}
|
|
3313
|
+
function extractCacheMetadata(fileContent) {
|
|
3314
|
+
const startIdx = fileContent.indexOf(CACHE_TAG_START);
|
|
3315
|
+
if (startIdx === -1) return void 0;
|
|
3316
|
+
const jsonStart = startIdx + CACHE_TAG_START.length;
|
|
3317
|
+
const endIdx = fileContent.indexOf(CACHE_TAG_END, jsonStart);
|
|
3318
|
+
if (endIdx === -1) return void 0;
|
|
3319
|
+
const jsonStr = fileContent.substring(jsonStart, endIdx);
|
|
3320
|
+
const metadata = JSON.parse(jsonStr);
|
|
3321
|
+
const tagEnd = endIdx + CACHE_TAG_END.length;
|
|
3322
|
+
const afterTag = fileContent[tagEnd] === "\n" ? tagEnd + 1 : tagEnd;
|
|
3323
|
+
const content = fileContent.substring(0, startIdx) + fileContent.substring(afterTag);
|
|
3324
|
+
return {
|
|
3325
|
+
content,
|
|
3326
|
+
slowViewState: metadata.slowViewState,
|
|
3327
|
+
carryForward: metadata.carryForward,
|
|
3328
|
+
sourcePath: metadata.sourcePath
|
|
3329
|
+
};
|
|
3330
|
+
}
|
|
3331
|
+
class SlowRenderCache {
|
|
3332
|
+
/**
|
|
3333
|
+
* @param cacheDir - Directory where pre-rendered jay-html files are stored
|
|
3334
|
+
* @param pagesRoot - Root directory of the pages (for relative path calculation)
|
|
3335
|
+
*/
|
|
3336
|
+
constructor(cacheDir, pagesRoot) {
|
|
3337
|
+
__publicField2(this, "pathToFiles", /* @__PURE__ */ new Map());
|
|
3338
|
+
__publicField2(this, "cacheDir");
|
|
3339
|
+
__publicField2(this, "pagesRoot");
|
|
3340
|
+
this.cacheDir = cacheDir;
|
|
3341
|
+
this.pagesRoot = pagesRoot;
|
|
3342
|
+
}
|
|
3343
|
+
/**
|
|
3344
|
+
* Get a cached pre-rendered jay-html entry by reading from disk.
|
|
3345
|
+
* Returns undefined if the cache file doesn't exist or has no metadata tag.
|
|
3346
|
+
*/
|
|
3347
|
+
async get(jayHtmlPath, params, routeDir) {
|
|
3348
|
+
const preRenderedPath = this.computeCachePath(jayHtmlPath, params, routeDir);
|
|
3349
|
+
let fileContent;
|
|
3350
|
+
try {
|
|
3351
|
+
fileContent = await fs__default.readFile(preRenderedPath, "utf-8");
|
|
3352
|
+
} catch {
|
|
3353
|
+
return void 0;
|
|
3354
|
+
}
|
|
3355
|
+
const extracted = extractCacheMetadata(fileContent);
|
|
3356
|
+
if (!extracted) return void 0;
|
|
3357
|
+
this.trackFile(jayHtmlPath, preRenderedPath);
|
|
3358
|
+
return {
|
|
3359
|
+
preRenderedPath,
|
|
3360
|
+
preRenderedContent: extracted.content,
|
|
3361
|
+
slowViewState: extracted.slowViewState,
|
|
3362
|
+
carryForward: extracted.carryForward,
|
|
3363
|
+
sourcePath: extracted.sourcePath
|
|
3364
|
+
};
|
|
3365
|
+
}
|
|
3366
|
+
/**
|
|
3367
|
+
* Store a pre-rendered jay-html entry.
|
|
3368
|
+
* Embeds metadata as a <script> tag and writes to disk.
|
|
3369
|
+
* Returns the full cache entry with stripped content.
|
|
3370
|
+
*/
|
|
3371
|
+
async set(jayHtmlPath, params, preRenderedJayHtml, slowViewState, carryForward, routeDir) {
|
|
3372
|
+
const preRenderedPath = this.computeCachePath(jayHtmlPath, params, routeDir);
|
|
3373
|
+
const fileContent = embedCacheMetadata(
|
|
3374
|
+
preRenderedJayHtml,
|
|
3375
|
+
slowViewState,
|
|
3376
|
+
carryForward,
|
|
3377
|
+
jayHtmlPath
|
|
3378
|
+
);
|
|
3379
|
+
await fs__default.mkdir(path__default.dirname(preRenderedPath), { recursive: true });
|
|
3380
|
+
await fs__default.writeFile(preRenderedPath, fileContent, "utf-8");
|
|
3381
|
+
this.trackFile(jayHtmlPath, preRenderedPath);
|
|
3382
|
+
return {
|
|
3383
|
+
preRenderedPath,
|
|
3384
|
+
preRenderedContent: preRenderedJayHtml,
|
|
3385
|
+
slowViewState,
|
|
3386
|
+
carryForward,
|
|
3387
|
+
sourcePath: jayHtmlPath
|
|
3388
|
+
};
|
|
3389
|
+
}
|
|
3390
|
+
/**
|
|
3391
|
+
* Check if a pre-rendered entry exists for the given path and params
|
|
3392
|
+
*/
|
|
3393
|
+
async has(jayHtmlPath, params, routeDir) {
|
|
3394
|
+
const preRenderedPath = this.computeCachePath(jayHtmlPath, params, routeDir);
|
|
3395
|
+
try {
|
|
3396
|
+
await fs__default.access(preRenderedPath);
|
|
3397
|
+
return true;
|
|
3398
|
+
} catch {
|
|
3399
|
+
return false;
|
|
3400
|
+
}
|
|
3401
|
+
}
|
|
3402
|
+
/**
|
|
3403
|
+
* Invalidate all cached entries for a given jay-html source path.
|
|
3404
|
+
* Deletes cached files from disk.
|
|
3405
|
+
*/
|
|
3406
|
+
async invalidate(jayHtmlPath) {
|
|
3407
|
+
const files = this.pathToFiles.get(jayHtmlPath);
|
|
3408
|
+
if (files) {
|
|
3409
|
+
for (const filePath of files) {
|
|
3410
|
+
try {
|
|
3411
|
+
await fs__default.unlink(filePath);
|
|
3412
|
+
} catch {
|
|
3413
|
+
}
|
|
3414
|
+
}
|
|
3415
|
+
this.pathToFiles.delete(jayHtmlPath);
|
|
3416
|
+
}
|
|
3417
|
+
await this.scanAndDeleteCacheFiles(jayHtmlPath);
|
|
3418
|
+
}
|
|
3419
|
+
/**
|
|
3420
|
+
* Invalidate all entries that depend on a changed file.
|
|
3421
|
+
* The changedPath could be:
|
|
3422
|
+
* - A jay-html file itself
|
|
3423
|
+
* - A component file (page.ts)
|
|
3424
|
+
* - Any other dependency
|
|
3425
|
+
*
|
|
3426
|
+
* @param changedPath - Absolute path to the changed file
|
|
3427
|
+
* @param resolveDependencies - Optional function to resolve which jay-html files depend on the changed file
|
|
3428
|
+
*/
|
|
3429
|
+
async invalidateByDependency(changedPath, resolveDependencies) {
|
|
3430
|
+
if (changedPath.endsWith(".jay-html")) {
|
|
3431
|
+
await this.invalidate(changedPath);
|
|
3432
|
+
return;
|
|
3433
|
+
}
|
|
3434
|
+
if (resolveDependencies) {
|
|
3435
|
+
const dependentPaths = resolveDependencies(changedPath);
|
|
3436
|
+
for (const depPath of dependentPaths) {
|
|
3437
|
+
await this.invalidate(depPath);
|
|
3438
|
+
}
|
|
3439
|
+
}
|
|
3440
|
+
}
|
|
3441
|
+
/**
|
|
3442
|
+
* Clear all cached entries and delete cached files from disk
|
|
3443
|
+
*/
|
|
3444
|
+
async clear() {
|
|
3445
|
+
for (const files of this.pathToFiles.values()) {
|
|
3446
|
+
for (const filePath of files) {
|
|
3447
|
+
try {
|
|
3448
|
+
await fs__default.unlink(filePath);
|
|
3449
|
+
} catch {
|
|
3450
|
+
}
|
|
3451
|
+
}
|
|
3452
|
+
}
|
|
3453
|
+
this.pathToFiles.clear();
|
|
3454
|
+
try {
|
|
3455
|
+
await fs__default.rm(this.cacheDir, { recursive: true, force: true });
|
|
3456
|
+
} catch {
|
|
3457
|
+
}
|
|
3458
|
+
}
|
|
3459
|
+
/**
|
|
3460
|
+
* Get all cached jay-html paths (for debugging/monitoring)
|
|
3461
|
+
*/
|
|
3462
|
+
getCachedPaths() {
|
|
3463
|
+
return Array.from(this.pathToFiles.keys());
|
|
3464
|
+
}
|
|
3465
|
+
/**
|
|
3466
|
+
* Compute the cache file path for a given jay-html path and params.
|
|
3467
|
+
*/
|
|
3468
|
+
computeCachePath(jayHtmlPath, params, routeDir) {
|
|
3469
|
+
let dir;
|
|
3470
|
+
if (routeDir) {
|
|
3471
|
+
dir = routeDir;
|
|
3472
|
+
} else {
|
|
3473
|
+
const relativePath = path__default.relative(this.pagesRoot, jayHtmlPath);
|
|
3474
|
+
dir = relativePath.startsWith("..") ? path__default.join(
|
|
3475
|
+
"_plugins",
|
|
3476
|
+
crypto.createHash("md5").update(jayHtmlPath).digest("hex").slice(0, 12)
|
|
3477
|
+
) : path__default.dirname(relativePath);
|
|
3478
|
+
}
|
|
3479
|
+
const basename = path__default.basename(jayHtmlPath, ".jay-html");
|
|
3480
|
+
const paramsHash = hashParams(params);
|
|
3481
|
+
const cacheFileName = `${basename}${paramsHash}.jay-html`;
|
|
3482
|
+
return path__default.join(this.cacheDir, dir, cacheFileName);
|
|
3483
|
+
}
|
|
3484
|
+
/**
|
|
3485
|
+
* Track a pre-rendered file path for invalidation.
|
|
3486
|
+
*/
|
|
3487
|
+
trackFile(jayHtmlPath, preRenderedPath) {
|
|
3488
|
+
if (!this.pathToFiles.has(jayHtmlPath)) {
|
|
3489
|
+
this.pathToFiles.set(jayHtmlPath, /* @__PURE__ */ new Set());
|
|
3490
|
+
}
|
|
3491
|
+
this.pathToFiles.get(jayHtmlPath).add(preRenderedPath);
|
|
3492
|
+
}
|
|
3493
|
+
/**
|
|
3494
|
+
* Scan the cache directory for files matching a route and delete them.
|
|
3495
|
+
* Handles the startup case where pathToFiles is not populated from a previous session.
|
|
3496
|
+
*/
|
|
3497
|
+
async scanAndDeleteCacheFiles(jayHtmlPath) {
|
|
3498
|
+
const relativePath = path__default.relative(this.pagesRoot, jayHtmlPath);
|
|
3499
|
+
const dir = relativePath.startsWith("..") ? path__default.join(
|
|
3500
|
+
"_plugins",
|
|
3501
|
+
crypto.createHash("md5").update(jayHtmlPath).digest("hex").slice(0, 12)
|
|
3502
|
+
) : path__default.dirname(relativePath);
|
|
3503
|
+
const basename = path__default.basename(jayHtmlPath, ".jay-html");
|
|
3504
|
+
const cacheSubDir = path__default.join(this.cacheDir, dir);
|
|
3505
|
+
try {
|
|
3506
|
+
const files = await fs__default.readdir(cacheSubDir);
|
|
3507
|
+
for (const file of files) {
|
|
3508
|
+
if (file.startsWith(basename)) {
|
|
3509
|
+
try {
|
|
3510
|
+
await fs__default.unlink(path__default.join(cacheSubDir, file));
|
|
3511
|
+
} catch {
|
|
3512
|
+
}
|
|
3513
|
+
}
|
|
3514
|
+
}
|
|
3515
|
+
} catch {
|
|
3516
|
+
}
|
|
3517
|
+
}
|
|
3518
|
+
}
|
|
3519
|
+
class ServiceLifecycleManager {
|
|
3520
|
+
constructor(projectRoot, sourceBase = "src") {
|
|
3521
|
+
/** Path to project's lib/init.ts (makeJayInit pattern) */
|
|
3522
|
+
__publicField(this, "projectInitFilePath", null);
|
|
3523
|
+
__publicField(this, "isInitialized", false);
|
|
3524
|
+
__publicField(this, "viteServer", null);
|
|
3525
|
+
__publicField(this, "pluginsWithInit", []);
|
|
3526
|
+
this.projectRoot = projectRoot;
|
|
3527
|
+
this.sourceBase = sourceBase;
|
|
3528
|
+
}
|
|
3529
|
+
/**
|
|
3530
|
+
* Set the Vite server instance for SSR module loading
|
|
3531
|
+
*/
|
|
3532
|
+
setViteServer(viteServer) {
|
|
3533
|
+
this.viteServer = viteServer;
|
|
3534
|
+
}
|
|
3535
|
+
/**
|
|
3536
|
+
* Finds the project init file using makeJayInit pattern.
|
|
3537
|
+
* Looks in: {projectRoot}/{sourceBase}/init.{ts,js}
|
|
3538
|
+
*/
|
|
3539
|
+
findProjectInitFile() {
|
|
3540
|
+
const extensions = [".ts", ".js"];
|
|
3541
|
+
for (const ext of extensions) {
|
|
3542
|
+
const filePath = path.join(this.projectRoot, this.sourceBase, "init" + ext);
|
|
3543
|
+
if (fs.existsSync(filePath)) {
|
|
3544
|
+
return filePath;
|
|
3545
|
+
}
|
|
3546
|
+
}
|
|
3547
|
+
return null;
|
|
3548
|
+
}
|
|
3549
|
+
/**
|
|
3550
|
+
* Initializes services by:
|
|
3551
|
+
* 1. Discovering and executing plugin server inits (in dependency order)
|
|
3552
|
+
* 2. Loading and executing project lib/init.ts
|
|
3553
|
+
* 3. Running all registered onInit callbacks
|
|
3554
|
+
* 4. Auto-discovering and registering actions
|
|
3555
|
+
*/
|
|
3556
|
+
async initialize() {
|
|
3557
|
+
const log = getLogger();
|
|
3558
|
+
if (this.isInitialized) {
|
|
3559
|
+
log.warn("[Services] Already initialized, skipping...");
|
|
3560
|
+
return;
|
|
3561
|
+
}
|
|
3562
|
+
this.projectInitFilePath = this.findProjectInitFile();
|
|
3563
|
+
const discoveredPlugins = await discoverPluginsWithInit({
|
|
3564
|
+
projectRoot: this.projectRoot,
|
|
3565
|
+
verbose: true
|
|
3566
|
+
});
|
|
3567
|
+
this.pluginsWithInit = sortPluginsByDependencies(discoveredPlugins);
|
|
3568
|
+
if (this.pluginsWithInit.length > 0) {
|
|
3569
|
+
log.info(
|
|
3570
|
+
`[Services] Found ${this.pluginsWithInit.length} plugin(s) with init: ${this.pluginsWithInit.map((p) => p.name).join(", ")}`
|
|
3571
|
+
);
|
|
3572
|
+
}
|
|
3573
|
+
await executePluginServerInits(this.pluginsWithInit, this.viteServer ?? void 0, true);
|
|
3574
|
+
if (this.projectInitFilePath) {
|
|
3575
|
+
log.info("[DevServer] Loading project init: src/init.ts");
|
|
3576
|
+
try {
|
|
3577
|
+
if (this.viteServer) {
|
|
3578
|
+
const module = await this.viteServer.ssrLoadModule(this.projectInitFilePath);
|
|
3579
|
+
if (module.init?._serverInit) {
|
|
3580
|
+
log.info("[DevServer] Running server init: project");
|
|
3581
|
+
const { setClientInitData } = await import("@jay-framework/stack-server-runtime");
|
|
3582
|
+
const clientData = await module.init._serverInit();
|
|
3583
|
+
if (clientData !== void 0 && clientData !== null) {
|
|
3584
|
+
setClientInitData("project", clientData);
|
|
3585
|
+
}
|
|
3586
|
+
}
|
|
3587
|
+
} else {
|
|
3588
|
+
const fileUrl = pathToFileURL(this.projectInitFilePath).href;
|
|
3589
|
+
await import(fileUrl);
|
|
3590
|
+
}
|
|
3591
|
+
} catch (error) {
|
|
3592
|
+
log.error(`[Services] Failed to load project init: ${error}`);
|
|
3593
|
+
throw error;
|
|
3594
|
+
}
|
|
3595
|
+
} else {
|
|
3596
|
+
log.info("[Services] No init.ts found, skipping project initialization");
|
|
3597
|
+
}
|
|
3598
|
+
await runInitCallbacks();
|
|
3599
|
+
log.info("[Services] Initialization complete");
|
|
3600
|
+
await this.discoverActions();
|
|
3601
|
+
this.isInitialized = true;
|
|
3602
|
+
}
|
|
3603
|
+
/**
|
|
3604
|
+
* Auto-discovers and registers actions from project and plugins.
|
|
3605
|
+
*/
|
|
3606
|
+
async discoverActions() {
|
|
3607
|
+
const log = getLogger();
|
|
3608
|
+
let totalActions = 0;
|
|
3609
|
+
try {
|
|
3610
|
+
const result = await discoverAndRegisterActions({
|
|
3611
|
+
projectRoot: this.projectRoot,
|
|
3612
|
+
actionsDir: path.join(this.sourceBase, "actions"),
|
|
3613
|
+
registry: actionRegistry,
|
|
3614
|
+
verbose: true,
|
|
3615
|
+
viteServer: this.viteServer ?? void 0
|
|
3616
|
+
});
|
|
3617
|
+
totalActions += result.actionCount;
|
|
3618
|
+
} catch (error) {
|
|
3619
|
+
log.error(`[Actions] Failed to auto-discover project actions: ${error}`);
|
|
3620
|
+
}
|
|
3621
|
+
try {
|
|
3622
|
+
const pluginActions = await discoverAllPluginActions({
|
|
3623
|
+
projectRoot: this.projectRoot,
|
|
3624
|
+
registry: actionRegistry,
|
|
3625
|
+
verbose: true,
|
|
3626
|
+
viteServer: this.viteServer ?? void 0
|
|
3627
|
+
});
|
|
3628
|
+
totalActions += pluginActions.length;
|
|
3629
|
+
} catch (error) {
|
|
3630
|
+
log.error(`[Actions] Failed to auto-discover plugin actions: ${error}`);
|
|
3631
|
+
}
|
|
3632
|
+
if (totalActions > 0) {
|
|
3633
|
+
log.info(`[Actions] Auto-registered ${totalActions} action(s) total`);
|
|
3634
|
+
}
|
|
3635
|
+
}
|
|
3636
|
+
/**
|
|
3637
|
+
* Shuts down services gracefully with timeout
|
|
3638
|
+
*/
|
|
3639
|
+
async shutdown(timeoutMs = 5e3) {
|
|
3640
|
+
const log = getLogger();
|
|
3641
|
+
if (!this.isInitialized) {
|
|
3642
|
+
return;
|
|
3643
|
+
}
|
|
3644
|
+
log.info("[Services] Shutting down...");
|
|
3645
|
+
try {
|
|
3646
|
+
await Promise.race([
|
|
3647
|
+
runShutdownCallbacks(),
|
|
3648
|
+
new Promise(
|
|
3649
|
+
(_, reject) => setTimeout(() => reject(new Error("Shutdown timeout")), timeoutMs)
|
|
3650
|
+
)
|
|
3651
|
+
]);
|
|
3652
|
+
log.info("[Services] Shutdown complete");
|
|
3653
|
+
} catch (error) {
|
|
3654
|
+
if (error.message === "Shutdown timeout") {
|
|
3655
|
+
log.warn(`[Services] Shutdown timed out after ${timeoutMs}ms`);
|
|
3656
|
+
} else {
|
|
3657
|
+
log.error(`[Services] Shutdown error: ${error}`);
|
|
3658
|
+
}
|
|
3659
|
+
} finally {
|
|
3660
|
+
this.isInitialized = false;
|
|
3661
|
+
}
|
|
3662
|
+
}
|
|
3663
|
+
/**
|
|
3664
|
+
* Hot reload: shutdown, clear caches, re-import, and re-initialize
|
|
3665
|
+
*/
|
|
3666
|
+
async reload() {
|
|
3667
|
+
const log = getLogger();
|
|
3668
|
+
log.info("[Services] Reloading services...");
|
|
3669
|
+
await this.shutdown();
|
|
3670
|
+
clearLifecycleCallbacks();
|
|
3671
|
+
clearServiceRegistry();
|
|
3672
|
+
clearClientInitData();
|
|
3673
|
+
actionRegistry.clear();
|
|
3674
|
+
if (this.projectInitFilePath && this.viteServer) {
|
|
3675
|
+
const moduleNode = this.viteServer.moduleGraph.getModuleById(this.projectInitFilePath);
|
|
3676
|
+
if (moduleNode) {
|
|
3677
|
+
await this.viteServer.moduleGraph.invalidateModule(moduleNode);
|
|
3678
|
+
}
|
|
3679
|
+
} else if (this.projectInitFilePath) {
|
|
3680
|
+
delete require.cache[require.resolve(this.projectInitFilePath)];
|
|
3681
|
+
}
|
|
3682
|
+
this.isInitialized = false;
|
|
3683
|
+
await this.initialize();
|
|
3684
|
+
log.info("[Services] Reload complete");
|
|
3685
|
+
}
|
|
3686
|
+
/**
|
|
3687
|
+
* Returns the path to the init file if found.
|
|
3688
|
+
*/
|
|
3689
|
+
getInitFilePath() {
|
|
3690
|
+
return this.projectInitFilePath;
|
|
3691
|
+
}
|
|
3692
|
+
/**
|
|
3693
|
+
* Returns project init info for client-side embedding.
|
|
3694
|
+
*/
|
|
3695
|
+
getProjectInit() {
|
|
3696
|
+
if (!this.projectInitFilePath) {
|
|
3697
|
+
return null;
|
|
3698
|
+
}
|
|
3699
|
+
return {
|
|
3700
|
+
importPath: this.projectInitFilePath,
|
|
3701
|
+
initExport: "init"
|
|
3702
|
+
};
|
|
3703
|
+
}
|
|
3704
|
+
/**
|
|
3705
|
+
* Returns the discovered plugins with init configurations.
|
|
3706
|
+
* Sorted by dependencies (plugins with no deps first).
|
|
3707
|
+
*/
|
|
3708
|
+
getPluginsWithInit() {
|
|
3709
|
+
return this.pluginsWithInit;
|
|
3710
|
+
}
|
|
3711
|
+
/**
|
|
3712
|
+
* Checks if services are initialized
|
|
3713
|
+
*/
|
|
3714
|
+
isReady() {
|
|
3715
|
+
return this.isInitialized;
|
|
3716
|
+
}
|
|
3717
|
+
}
|
|
3718
|
+
function deepMergeViewStates(base, overlay, trackByMap, path2 = "") {
|
|
3719
|
+
if (!base && !overlay) return {};
|
|
3720
|
+
if (!base) return overlay || {};
|
|
3721
|
+
if (!overlay) return base || {};
|
|
3722
|
+
const result = {};
|
|
3723
|
+
const allKeys = /* @__PURE__ */ new Set([...Object.keys(base), ...Object.keys(overlay)]);
|
|
3724
|
+
for (const key of allKeys) {
|
|
3725
|
+
const baseValue = base[key];
|
|
3726
|
+
const overlayValue = overlay[key];
|
|
3727
|
+
const currentPath = path2 ? `${path2}.${key}` : key;
|
|
3728
|
+
if (overlayValue === void 0) {
|
|
3729
|
+
result[key] = baseValue;
|
|
3730
|
+
} else if (baseValue === void 0) {
|
|
3731
|
+
result[key] = overlayValue;
|
|
3732
|
+
} else if (Array.isArray(baseValue) && Array.isArray(overlayValue)) {
|
|
3733
|
+
const trackByField = trackByMap[currentPath];
|
|
3734
|
+
if (trackByField) {
|
|
3735
|
+
result[key] = mergeArraysByTrackBy(
|
|
3736
|
+
baseValue,
|
|
1831
3737
|
overlayValue,
|
|
1832
3738
|
trackByField,
|
|
1833
3739
|
trackByMap,
|
|
@@ -2028,7 +3934,7 @@ function mergeDottedMultipartKeys(body) {
|
|
|
2028
3934
|
}
|
|
2029
3935
|
function parseMultipart(req, tempDir) {
|
|
2030
3936
|
return new Promise((resolve, reject) => {
|
|
2031
|
-
fs$
|
|
3937
|
+
fs$2.mkdirSync(tempDir, { recursive: true });
|
|
2032
3938
|
const files = {};
|
|
2033
3939
|
let jsonData = {};
|
|
2034
3940
|
let fileCount = 0;
|
|
@@ -2043,7 +3949,7 @@ function parseMultipart(req, tempDir) {
|
|
|
2043
3949
|
const filename = info.filename || `upload-${fileCount}`;
|
|
2044
3950
|
const tempPath = path$1.join(tempDir, `${fileCount}-${filename}`);
|
|
2045
3951
|
let size = 0;
|
|
2046
|
-
const writeStream = fs$
|
|
3952
|
+
const writeStream = fs$2.createWriteStream(tempPath);
|
|
2047
3953
|
stream.pipe(writeStream);
|
|
2048
3954
|
stream.on("data", (data) => {
|
|
2049
3955
|
size += data.length;
|
|
@@ -2102,7 +4008,7 @@ function parseMultipart(req, tempDir) {
|
|
|
2102
4008
|
}
|
|
2103
4009
|
function cleanupTempDir(dir) {
|
|
2104
4010
|
try {
|
|
2105
|
-
fs$
|
|
4011
|
+
fs$2.rmSync(dir, { recursive: true, force: true });
|
|
2106
4012
|
} catch {
|
|
2107
4013
|
}
|
|
2108
4014
|
}
|
|
@@ -2133,7 +4039,7 @@ function actionBodyParser(options) {
|
|
|
2133
4039
|
});
|
|
2134
4040
|
return;
|
|
2135
4041
|
}
|
|
2136
|
-
const requestId = crypto.randomUUID();
|
|
4042
|
+
const requestId = crypto$1.randomUUID();
|
|
2137
4043
|
const tempDir = path$1.join(buildFolder, ".tmp", "actions", requestId);
|
|
2138
4044
|
parseMultipart(req, tempDir).then(({ body: body2, tempDir: td }) => {
|
|
2139
4045
|
req.body = body2;
|
|
@@ -2180,7 +4086,7 @@ class FreezeStore {
|
|
|
2180
4086
|
this.dir = path.join(buildFolder, "freezes");
|
|
2181
4087
|
}
|
|
2182
4088
|
async save(route, viewState, routePattern) {
|
|
2183
|
-
await fs.mkdir(this.dir, { recursive: true });
|
|
4089
|
+
await fs$1.mkdir(this.dir, { recursive: true });
|
|
2184
4090
|
const entry = {
|
|
2185
4091
|
id: randomUUID().slice(0, 8),
|
|
2186
4092
|
route,
|
|
@@ -2188,7 +4094,7 @@ class FreezeStore {
|
|
|
2188
4094
|
viewState,
|
|
2189
4095
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2190
4096
|
};
|
|
2191
|
-
await fs.writeFile(
|
|
4097
|
+
await fs$1.writeFile(
|
|
2192
4098
|
path.join(this.dir, `${entry.id}.json`),
|
|
2193
4099
|
JSON.stringify(entry, null, 2),
|
|
2194
4100
|
"utf-8"
|
|
@@ -2197,7 +4103,7 @@ class FreezeStore {
|
|
|
2197
4103
|
}
|
|
2198
4104
|
async get(id) {
|
|
2199
4105
|
try {
|
|
2200
|
-
const content = await fs.readFile(path.join(this.dir, `${id}.json`), "utf-8");
|
|
4106
|
+
const content = await fs$1.readFile(path.join(this.dir, `${id}.json`), "utf-8");
|
|
2201
4107
|
return JSON.parse(content);
|
|
2202
4108
|
} catch {
|
|
2203
4109
|
return void 0;
|
|
@@ -2205,12 +4111,12 @@ class FreezeStore {
|
|
|
2205
4111
|
}
|
|
2206
4112
|
async list(route) {
|
|
2207
4113
|
try {
|
|
2208
|
-
const files = await fs.readdir(this.dir);
|
|
4114
|
+
const files = await fs$1.readdir(this.dir);
|
|
2209
4115
|
const entries = [];
|
|
2210
4116
|
for (const file of files) {
|
|
2211
4117
|
if (!file.endsWith(".json")) continue;
|
|
2212
4118
|
try {
|
|
2213
|
-
const content = await fs.readFile(path.join(this.dir, file), "utf-8");
|
|
4119
|
+
const content = await fs$1.readFile(path.join(this.dir, file), "utf-8");
|
|
2214
4120
|
const entry = JSON.parse(content);
|
|
2215
4121
|
if (!route || entry.routePattern === route || entry.route === route) {
|
|
2216
4122
|
entries.push(entry);
|
|
@@ -2229,7 +4135,7 @@ class FreezeStore {
|
|
|
2229
4135
|
const entry = await this.get(id);
|
|
2230
4136
|
if (!entry) return false;
|
|
2231
4137
|
entry.name = name;
|
|
2232
|
-
await fs.writeFile(
|
|
4138
|
+
await fs$1.writeFile(
|
|
2233
4139
|
path.join(this.dir, `${id}.json`),
|
|
2234
4140
|
JSON.stringify(entry, null, 2),
|
|
2235
4141
|
"utf-8"
|
|
@@ -2238,7 +4144,7 @@ class FreezeStore {
|
|
|
2238
4144
|
}
|
|
2239
4145
|
async delete(id) {
|
|
2240
4146
|
try {
|
|
2241
|
-
await fs.unlink(path.join(this.dir, `${id}.json`));
|
|
4147
|
+
await fs$1.unlink(path.join(this.dir, `${id}.json`));
|
|
2242
4148
|
return true;
|
|
2243
4149
|
} catch {
|
|
2244
4150
|
return false;
|
|
@@ -2343,7 +4249,7 @@ async function scanPluginRoutes(projectRoot, projectRoutes) {
|
|
|
2343
4249
|
continue;
|
|
2344
4250
|
}
|
|
2345
4251
|
const isLocalComponent = route.component.startsWith(".");
|
|
2346
|
-
const compPath = isLocalComponent ? path__default.resolve(plugin.pluginPath, route.component) : resolvePluginModule(plugin);
|
|
4252
|
+
const compPath = isLocalComponent ? path__default.resolve(plugin.pluginPath, route.component) : resolvePluginModule(plugin, route.devOnly === true ? "./tools" : ".");
|
|
2347
4253
|
const componentExport = isLocalComponent ? void 0 : route.component;
|
|
2348
4254
|
pluginRoutes.push(
|
|
2349
4255
|
createRoute(route.path, jayHtmlPath, compPath, componentExport, {
|
|
@@ -2361,7 +4267,7 @@ function resolvePluginExport(pluginPath, exportSubpath) {
|
|
|
2361
4267
|
const normalized = exportSubpath.replace(/^\.\//, "");
|
|
2362
4268
|
const packageJsonPath = path__default.join(pluginPath, "package.json");
|
|
2363
4269
|
try {
|
|
2364
|
-
const packageJson = JSON.parse(
|
|
4270
|
+
const packageJson = JSON.parse(fs__default$1.readFileSync(packageJsonPath, "utf-8"));
|
|
2365
4271
|
if (packageJson.exports) {
|
|
2366
4272
|
const exportKey = "./" + normalized;
|
|
2367
4273
|
const exportValue = packageJson.exports[exportKey];
|
|
@@ -2378,23 +4284,23 @@ function resolvePluginExport(pluginPath, exportSubpath) {
|
|
|
2378
4284
|
for (const dir of ["dist", "lib", ""]) {
|
|
2379
4285
|
const candidate = path__default.join(pluginPath, dir, normalized);
|
|
2380
4286
|
try {
|
|
2381
|
-
|
|
4287
|
+
fs__default$1.accessSync(candidate);
|
|
2382
4288
|
return candidate;
|
|
2383
4289
|
} catch {
|
|
2384
4290
|
}
|
|
2385
4291
|
}
|
|
2386
4292
|
return void 0;
|
|
2387
4293
|
}
|
|
2388
|
-
function resolvePluginModule(plugin) {
|
|
4294
|
+
function resolvePluginModule(plugin, exportKey = ".") {
|
|
2389
4295
|
const pkgJsonPath = path__default.join(plugin.pluginPath, "package.json");
|
|
2390
|
-
if (
|
|
4296
|
+
if (fs__default$1.existsSync(pkgJsonPath)) {
|
|
2391
4297
|
try {
|
|
2392
|
-
const pkg = JSON.parse(
|
|
2393
|
-
const mainExport = pkg.exports?.[
|
|
4298
|
+
const pkg = JSON.parse(fs__default$1.readFileSync(pkgJsonPath, "utf-8"));
|
|
4299
|
+
const mainExport = pkg.exports?.[exportKey];
|
|
2394
4300
|
const mainPath = typeof mainExport === "string" ? mainExport : mainExport?.default || mainExport?.import || pkg.main;
|
|
2395
4301
|
if (mainPath) {
|
|
2396
4302
|
const resolved = path__default.join(plugin.pluginPath, mainPath);
|
|
2397
|
-
if (
|
|
4303
|
+
if (fs__default$1.existsSync(resolved)) return resolved;
|
|
2398
4304
|
}
|
|
2399
4305
|
} catch {
|
|
2400
4306
|
}
|
|
@@ -2403,7 +4309,7 @@ function resolvePluginModule(plugin) {
|
|
|
2403
4309
|
for (const ext of [".ts", ".js", "/index.ts", "/index.js"]) {
|
|
2404
4310
|
const candidate = path__default.join(plugin.pluginPath, modulePath + ext);
|
|
2405
4311
|
try {
|
|
2406
|
-
|
|
4312
|
+
fs__default$1.accessSync(candidate);
|
|
2407
4313
|
return candidate;
|
|
2408
4314
|
} catch {
|
|
2409
4315
|
}
|
|
@@ -2411,7 +4317,7 @@ function resolvePluginModule(plugin) {
|
|
|
2411
4317
|
for (const ext of [".ts", ".js"]) {
|
|
2412
4318
|
const candidate = path__default.join(plugin.pluginPath, "lib", path__default.basename(modulePath) + ext);
|
|
2413
4319
|
try {
|
|
2414
|
-
|
|
4320
|
+
fs__default$1.accessSync(candidate);
|
|
2415
4321
|
return candidate;
|
|
2416
4322
|
} catch {
|
|
2417
4323
|
}
|
|
@@ -2507,6 +4413,7 @@ function mkRoute(route, vite, slowlyPhase, options, slowRenderCache, freezeStore
|
|
|
2507
4413
|
pageParams,
|
|
2508
4414
|
query["format"] === "fragment" ? "fragment" : "page",
|
|
2509
4415
|
res,
|
|
4416
|
+
url,
|
|
2510
4417
|
timing
|
|
2511
4418
|
);
|
|
2512
4419
|
return;
|
|
@@ -2713,9 +4620,7 @@ async function handlePreRenderRequest(vite, route, options, slowlyPhase, slowRen
|
|
|
2713
4620
|
);
|
|
2714
4621
|
if (renderedSlowly.kind !== "PhaseOutput") {
|
|
2715
4622
|
timing?.recordSlowRender(Date.now() - slowStart);
|
|
2716
|
-
|
|
2717
|
-
handleOtherResponseCodes(res, renderedSlowly);
|
|
2718
|
-
}
|
|
4623
|
+
handleOtherResponseCodes(res, renderedSlowly);
|
|
2719
4624
|
timing?.end();
|
|
2720
4625
|
return;
|
|
2721
4626
|
}
|
|
@@ -2825,9 +4730,7 @@ async function handleClientOnlyRequest(vite, route, options, slowlyPhase, pagePa
|
|
|
2825
4730
|
);
|
|
2826
4731
|
if (renderedSlowly.kind !== "PhaseOutput") {
|
|
2827
4732
|
timing?.recordSlowRender(Date.now() - slowStart);
|
|
2828
|
-
|
|
2829
|
-
handleOtherResponseCodes(res, renderedSlowly);
|
|
2830
|
-
}
|
|
4733
|
+
handleOtherResponseCodes(res, renderedSlowly);
|
|
2831
4734
|
timing?.end();
|
|
2832
4735
|
return;
|
|
2833
4736
|
}
|
|
@@ -2959,7 +4862,7 @@ async function sendResponse(vite, res, url, jayHtmlPath, sourceJayHtmlPath, page
|
|
|
2959
4862
|
res.status(200).set({ "Content-Type": "text/html" }).send(compiledPageHtml);
|
|
2960
4863
|
timing?.end();
|
|
2961
4864
|
}
|
|
2962
|
-
async function handleFrozenRequest(vite, route, options, freezeStore, slowRenderCache, freezeId, pageParams, format, res, timing) {
|
|
4865
|
+
async function handleFrozenRequest(vite, route, options, freezeStore, slowRenderCache, freezeId, pageParams, format, res, url, timing) {
|
|
2963
4866
|
const entry = await freezeStore.get(freezeId);
|
|
2964
4867
|
if (!entry) {
|
|
2965
4868
|
getLogger().warn(`[Freeze] Freeze "${freezeId}" not found`);
|
|
@@ -2982,6 +4885,7 @@ async function handleFrozenRequest(vite, route, options, freezeStore, slowRender
|
|
|
2982
4885
|
sourceDir,
|
|
2983
4886
|
JAY_IMPORT_RESOLVER
|
|
2984
4887
|
);
|
|
4888
|
+
const injectDevHmr = format === "page";
|
|
2985
4889
|
const html = await generateFrozenPageHtml(
|
|
2986
4890
|
vite,
|
|
2987
4891
|
fullJayHtml,
|
|
@@ -2994,13 +4898,19 @@ async function handleFrozenRequest(vite, route, options, freezeStore, slowRender
|
|
|
2994
4898
|
options.jayRollupConfig?.tsConfigFilePath,
|
|
2995
4899
|
sourceDir,
|
|
2996
4900
|
format,
|
|
2997
|
-
entry.name
|
|
4901
|
+
entry.name,
|
|
4902
|
+
injectDevHmr ? { injectDevHmr: true } : void 0
|
|
2998
4903
|
);
|
|
2999
4904
|
const headers = { "Content-Type": "text/html" };
|
|
3000
4905
|
if (format === "fragment") {
|
|
3001
4906
|
headers["Access-Control-Allow-Origin"] = "*";
|
|
3002
4907
|
}
|
|
3003
|
-
|
|
4908
|
+
if (injectDevHmr) {
|
|
4909
|
+
const compiledHtml = await vite.transformIndexHtml(url || "/", html);
|
|
4910
|
+
res.status(200).set(headers).send(compiledHtml);
|
|
4911
|
+
} else {
|
|
4912
|
+
res.status(200).set(headers).send(html);
|
|
4913
|
+
}
|
|
3004
4914
|
} catch (err) {
|
|
3005
4915
|
getLogger().warn(`[Freeze] Failed to render frozen page: ${err.message}`);
|
|
3006
4916
|
res.status(500).send(`Failed to render frozen page: ${err.message}`);
|
|
@@ -3184,7 +5094,7 @@ async function mkDevServer(rawOptions) {
|
|
|
3184
5094
|
for (let i = devServerRoutes.length - 1; i >= 0; i--) {
|
|
3185
5095
|
const route = devServerRoutes[i];
|
|
3186
5096
|
const fsPath = route.fsRoute.jayHtmlPath;
|
|
3187
|
-
const stillOnDisk = scannedRawRoutes.has(route.fsRoute.rawRoute) &&
|
|
5097
|
+
const stillOnDisk = scannedRawRoutes.has(route.fsRoute.rawRoute) && fs__default$1.existsSync(fsPath);
|
|
3188
5098
|
if (!stillOnDisk) {
|
|
3189
5099
|
devServerRoutes.splice(i, 1);
|
|
3190
5100
|
}
|