@jxsuite/compiler 0.23.2 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/compiler.js +155 -155
- package/package.json +18 -16
- package/src/{cli.js → cli.ts} +2 -2
- package/src/{compile-cli.js → compile-cli.ts} +1 -1
- package/src/{compiler.js → compiler.ts} +10 -8
- package/src/{shared.js → shared.ts} +161 -123
- package/src/site/{content-loader.js → content-loader.ts} +57 -48
- package/src/site/{context-injection.js → context-injection.ts} +18 -12
- package/src/site/{head-merger.js → head-merger.ts} +17 -11
- package/src/site/{image-cache.js → image-cache.ts} +22 -22
- package/src/site/{image-optimizer.js → image-optimizer.ts} +35 -44
- package/src/site/{image-transform.js → image-transform.ts} +52 -22
- package/src/site/{layout-resolver.js → layout-resolver.ts} +31 -26
- package/src/site/{pages-discovery.js → pages-discovery.ts} +31 -30
- package/src/site/{prototype-resolver.js → prototype-resolver.ts} +16 -5
- package/src/site/{site-build.js → site-build.ts} +135 -135
- package/src/site/{site-loader.js → site-loader.ts} +2 -2
- package/src/targets/{compile-class.js → compile-class.ts} +20 -19
- package/src/targets/{compile-client.js → compile-client.ts} +84 -105
- package/src/targets/{compile-element.js → compile-element.ts} +82 -88
- package/src/targets/{compile-markdown.js → compile-markdown.ts} +95 -81
- package/src/targets/{compile-server.js → compile-server.ts} +23 -6
- package/src/targets/{compile-static.js → compile-static.ts} +35 -18
- package/src/types.ts +16 -0
|
@@ -8,16 +8,14 @@
|
|
|
8
8
|
|
|
9
9
|
import { existsSync } from "node:fs";
|
|
10
10
|
import { resolve, extname, basename } from "node:path";
|
|
11
|
-
import { processImage, buildSrcset, contentHash, configHash } from "./image-optimizer.
|
|
12
|
-
import { getCached, setCached } from "./image-cache.
|
|
11
|
+
import { processImage, buildSrcset, contentHash, configHash } from "./image-optimizer.ts";
|
|
12
|
+
import { getCached, setCached } from "./image-cache.ts";
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
* @typedef {import("./image-cache.js").CacheManifest} CacheManifest
|
|
20
|
-
*/
|
|
14
|
+
import type { ImageConfig } from "./image-optimizer.ts";
|
|
15
|
+
import type { ImageManifest } from "./image-optimizer.ts";
|
|
16
|
+
import type { JxMutableNode } from "@jxsuite/schema/types";
|
|
17
|
+
import type { CacheManifest } from "./image-cache.ts";
|
|
18
|
+
import type { JxDocument } from "@jxsuite/schema/types";
|
|
21
19
|
|
|
22
20
|
const SKIP_EXTENSIONS = new Set([".svg", ".gif"]);
|
|
23
21
|
const EXTERNAL_PREFIXES = ["http://", "https://", "data:", "//"];
|
|
@@ -30,7 +28,13 @@ const EXTERNAL_PREFIXES = ["http://", "https://", "data:", "//"];
|
|
|
30
28
|
* @param {CacheManifest} cache
|
|
31
29
|
* @returns {Promise<ImageManifest>}
|
|
32
30
|
*/
|
|
33
|
-
async function resolveManifest(
|
|
31
|
+
async function resolveManifest(
|
|
32
|
+
absoluteSrc: string,
|
|
33
|
+
src: string,
|
|
34
|
+
config: ImageConfig,
|
|
35
|
+
outDir: string,
|
|
36
|
+
cache: CacheManifest,
|
|
37
|
+
) {
|
|
34
38
|
const key = `${contentHash(absoluteSrc)}:${configHash(config)}`;
|
|
35
39
|
const cached = getCached(cache, key);
|
|
36
40
|
|
|
@@ -51,7 +55,7 @@ async function resolveManifest(absoluteSrc, src, config, outDir, cache) {
|
|
|
51
55
|
* @param {string} src
|
|
52
56
|
* @returns {boolean}
|
|
53
57
|
*/
|
|
54
|
-
function shouldSkip(src) {
|
|
58
|
+
function shouldSkip(src: string) {
|
|
55
59
|
if (typeof src !== "string") return true;
|
|
56
60
|
if (src.includes("${")) return true;
|
|
57
61
|
if (EXTERNAL_PREFIXES.some((p) => src.startsWith(p))) return true;
|
|
@@ -68,7 +72,7 @@ function shouldSkip(src) {
|
|
|
68
72
|
* @param {string} projectRoot
|
|
69
73
|
* @returns {string}
|
|
70
74
|
*/
|
|
71
|
-
function resolveImagePath(src, projectRoot) {
|
|
75
|
+
function resolveImagePath(src: string, projectRoot: string) {
|
|
72
76
|
if (src.startsWith("/")) {
|
|
73
77
|
return resolve(projectRoot, "public", src.slice(1));
|
|
74
78
|
}
|
|
@@ -88,9 +92,14 @@ function resolveImagePath(src, projectRoot) {
|
|
|
88
92
|
* @param {CacheManifest} cache
|
|
89
93
|
* @returns {Promise<{ imageRefs: Map<string, ImageManifest> }>}
|
|
90
94
|
*/
|
|
91
|
-
export async function transformImageNodes(
|
|
92
|
-
|
|
93
|
-
|
|
95
|
+
export async function transformImageNodes(
|
|
96
|
+
doc: JxMutableNode | JxDocument,
|
|
97
|
+
config: ImageConfig,
|
|
98
|
+
projectRoot: string,
|
|
99
|
+
outDir: string,
|
|
100
|
+
cache: CacheManifest,
|
|
101
|
+
) {
|
|
102
|
+
const imageRefs: Map<string, ImageManifest> = new Map();
|
|
94
103
|
|
|
95
104
|
if (!config.optimize) return { imageRefs };
|
|
96
105
|
|
|
@@ -107,7 +116,14 @@ export async function transformImageNodes(doc, config, projectRoot, outDir, cach
|
|
|
107
116
|
* @param {CacheManifest} cache
|
|
108
117
|
* @param {Map<string, ImageManifest>} imageRefs
|
|
109
118
|
*/
|
|
110
|
-
async function walkAndTransform(
|
|
119
|
+
async function walkAndTransform(
|
|
120
|
+
node: JxMutableNode | JxDocument,
|
|
121
|
+
config: ImageConfig,
|
|
122
|
+
projectRoot: string,
|
|
123
|
+
outDir: string,
|
|
124
|
+
cache: CacheManifest,
|
|
125
|
+
imageRefs: Map<string, ImageManifest>,
|
|
126
|
+
) {
|
|
111
127
|
if (!node || typeof node !== "object") return;
|
|
112
128
|
|
|
113
129
|
if (node.tagName === "img") {
|
|
@@ -141,7 +157,14 @@ async function walkAndTransform(node, config, projectRoot, outDir, cache, imageR
|
|
|
141
157
|
* @param {CacheManifest} cache
|
|
142
158
|
* @param {Map<string, ImageManifest>} imageRefs
|
|
143
159
|
*/
|
|
144
|
-
async function transformImgNode(
|
|
160
|
+
async function transformImgNode(
|
|
161
|
+
node: JxMutableNode | JxDocument,
|
|
162
|
+
config: ImageConfig,
|
|
163
|
+
projectRoot: string,
|
|
164
|
+
outDir: string,
|
|
165
|
+
cache: CacheManifest,
|
|
166
|
+
imageRefs: Map<string, ImageManifest>,
|
|
167
|
+
) {
|
|
145
168
|
if (!node.attributes) node.attributes = {};
|
|
146
169
|
|
|
147
170
|
const src = node.attributes.src ?? node.src;
|
|
@@ -166,10 +189,10 @@ async function transformImgNode(node, config, projectRoot, outDir, cache, imageR
|
|
|
166
189
|
node.attributes.sizes = node.attributes.sizes ?? config.sizes;
|
|
167
190
|
}
|
|
168
191
|
|
|
169
|
-
if (!node.attributes.width && manifest.original
|
|
192
|
+
if (!node.attributes.width && manifest.original?.width) {
|
|
170
193
|
node.attributes.width = String(manifest.original.width);
|
|
171
194
|
}
|
|
172
|
-
if (!node.attributes.height && manifest.original
|
|
195
|
+
if (!node.attributes.height && manifest.original?.height) {
|
|
173
196
|
node.attributes.height = String(manifest.original.height);
|
|
174
197
|
}
|
|
175
198
|
|
|
@@ -195,7 +218,14 @@ const DATA_NO_OPT_RE = /\bdata-no-optimize\b/;
|
|
|
195
218
|
* @param {Map<string, ImageManifest>} imageRefs
|
|
196
219
|
* @returns {Promise<string>}
|
|
197
220
|
*/
|
|
198
|
-
async function transformInnerHtmlImages(
|
|
221
|
+
async function transformInnerHtmlImages(
|
|
222
|
+
html: string,
|
|
223
|
+
config: ImageConfig,
|
|
224
|
+
projectRoot: string,
|
|
225
|
+
outDir: string,
|
|
226
|
+
cache: CacheManifest,
|
|
227
|
+
imageRefs: Map<string, ImageManifest>,
|
|
228
|
+
) {
|
|
199
229
|
/** @type {{ match: string; replacement: string }[]} */
|
|
200
230
|
const replacements = [];
|
|
201
231
|
|
|
@@ -226,10 +256,10 @@ async function transformInnerHtmlImages(html, config, projectRoot, outDir, cache
|
|
|
226
256
|
if (!srcset) continue;
|
|
227
257
|
|
|
228
258
|
let extra = ` srcset="${srcset}" sizes="${config.sizes}"`;
|
|
229
|
-
if (!/\bwidth=/.test(attrs) && manifest.original
|
|
259
|
+
if (!/\bwidth=/.test(attrs) && manifest.original?.width) {
|
|
230
260
|
extra += ` width="${manifest.original.width}"`;
|
|
231
261
|
}
|
|
232
|
-
if (!/\bheight=/.test(attrs) && manifest.original
|
|
262
|
+
if (!/\bheight=/.test(attrs) && manifest.original?.height) {
|
|
233
263
|
extra += ` height="${manifest.original.height}"`;
|
|
234
264
|
}
|
|
235
265
|
if (config.lazyLoad && !/\bloading="eager"/.test(attrs)) {
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
import { readFileSync, existsSync } from "node:fs";
|
|
17
17
|
import { resolve } from "node:path";
|
|
18
|
+
import type { JxElement, JxDocument } from "@jxsuite/schema/types";
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* Resolve a page's layout, wrapping the page content in the layout structure.
|
|
@@ -24,12 +25,14 @@ import { resolve } from "node:path";
|
|
|
24
25
|
* @param {string} projectRoot - Project root directory
|
|
25
26
|
* @returns {JxDocument} The merged document (layout wrapping page content)
|
|
26
27
|
*/
|
|
27
|
-
export function resolveLayout(
|
|
28
|
+
export function resolveLayout(
|
|
29
|
+
pageDoc: JxDocument,
|
|
30
|
+
projectConfig: Record<string, unknown>,
|
|
31
|
+
projectRoot: string,
|
|
32
|
+
) {
|
|
28
33
|
// Determine which layout to use
|
|
29
34
|
const layoutRef =
|
|
30
|
-
pageDoc.$layout ??
|
|
31
|
-
/** @type {Record<string, unknown>} */ (projectConfig.defaults)?.layout ??
|
|
32
|
-
null;
|
|
35
|
+
pageDoc.$layout ?? (projectConfig.defaults as Record<string, unknown>)?.layout ?? null;
|
|
33
36
|
|
|
34
37
|
if (!layoutRef) {
|
|
35
38
|
// No layout — return page as-is
|
|
@@ -37,7 +40,7 @@ export function resolveLayout(pageDoc, projectConfig, projectRoot) {
|
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
// Load the layout file
|
|
40
|
-
const layoutPath = resolve(projectRoot,
|
|
43
|
+
const layoutPath = resolve(projectRoot, layoutRef as string);
|
|
41
44
|
if (!existsSync(layoutPath)) {
|
|
42
45
|
throw new Error(`Layout not found: ${layoutRef} (resolved to ${layoutPath})`);
|
|
43
46
|
}
|
|
@@ -47,7 +50,7 @@ export function resolveLayout(pageDoc, projectConfig, projectRoot) {
|
|
|
47
50
|
try {
|
|
48
51
|
layoutDoc = JSON.parse(readFileSync(layoutPath, "utf8"));
|
|
49
52
|
} catch (e) {
|
|
50
|
-
const err =
|
|
53
|
+
const err = e as Error;
|
|
51
54
|
throw new Error(`Invalid layout JSON at ${layoutPath}: ${err.message}`);
|
|
52
55
|
}
|
|
53
56
|
|
|
@@ -58,9 +61,12 @@ export function resolveLayout(pageDoc, projectConfig, projectRoot) {
|
|
|
58
61
|
|
|
59
62
|
// Distribute page children into layout slots
|
|
60
63
|
const rawChildren = pageDoc.children ?? [];
|
|
61
|
-
const pageChildren =
|
|
62
|
-
typeof rawChildren === "string"
|
|
63
|
-
|
|
64
|
+
const pageChildren =
|
|
65
|
+
typeof rawChildren === "string"
|
|
66
|
+
? [rawChildren]
|
|
67
|
+
: Array.isArray(rawChildren)
|
|
68
|
+
? rawChildren
|
|
69
|
+
: ([] as (JxElement | string)[]);
|
|
64
70
|
const merged = deepClone(layoutDoc);
|
|
65
71
|
|
|
66
72
|
distributeSlots(merged, pageChildren);
|
|
@@ -110,21 +116,19 @@ export function resolveLayout(pageDoc, projectConfig, projectRoot) {
|
|
|
110
116
|
* @param {JxElement} node - Layout document tree (mutated in place)
|
|
111
117
|
* @param {(JxElement | string)[]} children - Page children to distribute
|
|
112
118
|
*/
|
|
113
|
-
function distributeSlots(node, children) {
|
|
119
|
+
function distributeSlots(node: JxElement, children: (JxElement | string)[]) {
|
|
114
120
|
if (!node || typeof node !== "object") return;
|
|
115
121
|
if (!Array.isArray(node.children)) return;
|
|
116
122
|
|
|
117
123
|
// Collect named and default children
|
|
118
|
-
|
|
119
|
-
const
|
|
120
|
-
/** @type {(JxElement | string)[]} */
|
|
121
|
-
const defaults = []; // children without a slot target
|
|
124
|
+
const named: Map<string, (JxElement | string)[]> = new Map(); // slot name → children[]
|
|
125
|
+
const defaults: (JxElement | string)[] = []; // children without a slot target
|
|
122
126
|
|
|
123
127
|
for (const child of children) {
|
|
124
128
|
if (child && typeof child === "object" && child.attributes?.slot) {
|
|
125
|
-
const slotName =
|
|
129
|
+
const slotName = child.attributes.slot as string;
|
|
126
130
|
if (!named.has(slotName)) named.set(slotName, []);
|
|
127
|
-
|
|
131
|
+
(named.get(slotName) as (JxElement | string)[]).push(child);
|
|
128
132
|
} else {
|
|
129
133
|
defaults.push(child);
|
|
130
134
|
}
|
|
@@ -141,23 +145,24 @@ function distributeSlots(node, children) {
|
|
|
141
145
|
* @param {Map<string, (JxElement | string)[]>} named
|
|
142
146
|
* @param {(JxElement | string)[]} defaults
|
|
143
147
|
*/
|
|
144
|
-
function fillSlots(
|
|
148
|
+
function fillSlots(
|
|
149
|
+
node: JxElement,
|
|
150
|
+
named: Map<string, (JxElement | string)[]>,
|
|
151
|
+
defaults: (JxElement | string)[],
|
|
152
|
+
) {
|
|
145
153
|
if (!node || typeof node !== "object") return;
|
|
146
154
|
if (!Array.isArray(node.children)) return;
|
|
147
155
|
|
|
148
|
-
|
|
149
|
-
const newChildren = [];
|
|
156
|
+
const newChildren: (JxElement | string)[] = [];
|
|
150
157
|
|
|
151
158
|
for (const child of node.children) {
|
|
152
159
|
if (child && typeof child === "object" && child.tagName === "slot") {
|
|
153
160
|
const slotName = child.attributes?.name;
|
|
154
161
|
|
|
155
|
-
if (slotName && named.has(
|
|
162
|
+
if (slotName && named.has(slotName as string)) {
|
|
156
163
|
// Named slot — replace with matching children
|
|
157
|
-
newChildren.push(
|
|
158
|
-
|
|
159
|
-
);
|
|
160
|
-
named.delete(/** @type {string} */ (slotName)); // consumed
|
|
164
|
+
newChildren.push(...(named.get(slotName as string) as (JxElement | string)[]));
|
|
165
|
+
named.delete(slotName as string); // consumed
|
|
161
166
|
} else if (!slotName && defaults.length > 0) {
|
|
162
167
|
// Default slot — replace with unassigned children
|
|
163
168
|
newChildren.push(...defaults);
|
|
@@ -171,7 +176,7 @@ function fillSlots(node, named, defaults) {
|
|
|
171
176
|
}
|
|
172
177
|
} else {
|
|
173
178
|
// Not a slot — recurse into it
|
|
174
|
-
fillSlots(
|
|
179
|
+
fillSlots(child as JxElement, named, defaults);
|
|
175
180
|
newChildren.push(child);
|
|
176
181
|
}
|
|
177
182
|
}
|
|
@@ -185,6 +190,6 @@ function fillSlots(node, named, defaults) {
|
|
|
185
190
|
* @param {JxDocument} obj
|
|
186
191
|
* @returns {JxDocument}
|
|
187
192
|
*/
|
|
188
|
-
function deepClone(obj) {
|
|
193
|
+
function deepClone(obj: JxDocument) {
|
|
189
194
|
return JSON.parse(JSON.stringify(obj));
|
|
190
195
|
}
|
|
@@ -12,18 +12,18 @@
|
|
|
12
12
|
|
|
13
13
|
import { readdirSync, readFileSync } from "node:fs";
|
|
14
14
|
import { resolve, relative, extname, join } from "node:path";
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
15
|
+
import type { ContentLoaderEntry } from "@jxsuite/parser/types";
|
|
16
|
+
|
|
17
|
+
interface Route {
|
|
18
|
+
urlPattern: string; // URL pattern (e.g. "/blog/:slug")
|
|
19
|
+
sourcePath: string; // Absolute path to the .json source file
|
|
20
|
+
relativePath: string; // Path relative to pages/ dir
|
|
21
|
+
isDynamic: boolean; // Whether route has parameters
|
|
22
|
+
isCatchAll: boolean; // Whether route uses [...param] spread
|
|
23
|
+
params: string[]; // Parameter names (e.g. ["slug"])
|
|
24
|
+
$layout: string | null; // Layout override from page frontmatter, if any
|
|
25
|
+
_pathParams?: Record<string, string>; // Resolved path parameters
|
|
26
|
+
}
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
29
|
* Discover all routable pages in a pages/ directory.
|
|
@@ -31,9 +31,8 @@ import { resolve, relative, extname, join } from "node:path";
|
|
|
31
31
|
* @param {string} pagesDir - Absolute path to the pages/ directory
|
|
32
32
|
* @returns {Route[]} Sorted route table (static routes first, then dynamic)
|
|
33
33
|
*/
|
|
34
|
-
export function discoverPages(pagesDir) {
|
|
35
|
-
|
|
36
|
-
const routes = [];
|
|
34
|
+
export function discoverPages(pagesDir: string) {
|
|
35
|
+
const routes: Route[] = [];
|
|
37
36
|
walkDir(pagesDir, pagesDir, routes);
|
|
38
37
|
|
|
39
38
|
// Sort: static routes first, then by specificity (more segments = more specific)
|
|
@@ -53,7 +52,7 @@ export function discoverPages(pagesDir) {
|
|
|
53
52
|
* @param {string} pagesRoot
|
|
54
53
|
* @param {Route[]} routes
|
|
55
54
|
*/
|
|
56
|
-
function walkDir(dir, pagesRoot, routes) {
|
|
55
|
+
function walkDir(dir: string, pagesRoot: string, routes: Route[]) {
|
|
57
56
|
const entries = readdirSync(dir, { withFileTypes: true });
|
|
58
57
|
|
|
59
58
|
for (const entry of entries) {
|
|
@@ -86,7 +85,7 @@ function walkDir(dir, pagesRoot, routes) {
|
|
|
86
85
|
* @param {string} absolutePath - Full filesystem path
|
|
87
86
|
* @returns {Route}
|
|
88
87
|
*/
|
|
89
|
-
function fileToRoute(relativePath, absolutePath) {
|
|
88
|
+
function fileToRoute(relativePath: string, absolutePath: string) {
|
|
90
89
|
// Remove .json or .md extension
|
|
91
90
|
let urlPath = relativePath.replace(/\.(json|md)$/, "");
|
|
92
91
|
|
|
@@ -104,15 +103,14 @@ function fileToRoute(relativePath, absolutePath) {
|
|
|
104
103
|
if (!urlPath.startsWith("/")) urlPath = "/" + urlPath;
|
|
105
104
|
|
|
106
105
|
// Extract parameters from bracket syntax
|
|
107
|
-
|
|
108
|
-
const params = [];
|
|
106
|
+
const params: string[] = [];
|
|
109
107
|
let isDynamic = false;
|
|
110
108
|
let isCatchAll = false;
|
|
111
109
|
|
|
112
110
|
// Convert [param] → :param and [...param] → *
|
|
113
111
|
const urlPattern = urlPath.replace(
|
|
114
112
|
/\[\.\.\.(\w+)\]|\[(\w+)\]/g,
|
|
115
|
-
(
|
|
113
|
+
(match: string, spread: string, named: string) => {
|
|
116
114
|
if (spread) {
|
|
117
115
|
isCatchAll = true;
|
|
118
116
|
isDynamic = true;
|
|
@@ -172,9 +170,12 @@ function fileToRoute(relativePath, absolutePath) {
|
|
|
172
170
|
* @param {Map<string, any[]>} [contentTypes] - Loaded content types (from content-loader)
|
|
173
171
|
* @returns {Promise<Route[]>} Expanded routes with concrete paths
|
|
174
172
|
*/
|
|
175
|
-
export async function expandDynamicRoutes(
|
|
176
|
-
|
|
177
|
-
|
|
173
|
+
export async function expandDynamicRoutes(
|
|
174
|
+
routes: Route[],
|
|
175
|
+
projectRoot: string,
|
|
176
|
+
contentTypes: Map<string, any[]> = new Map(),
|
|
177
|
+
) {
|
|
178
|
+
const expanded: Route[] = [];
|
|
178
179
|
|
|
179
180
|
for (const route of routes) {
|
|
180
181
|
if (!route.isDynamic) {
|
|
@@ -202,8 +203,8 @@ export async function expandDynamicRoutes(routes, projectRoot, contentTypes = ne
|
|
|
202
203
|
for (const pathEntry of pathEntries) {
|
|
203
204
|
let concreteUrl = route.urlPattern;
|
|
204
205
|
for (const [param, value] of Object.entries(pathEntry)) {
|
|
205
|
-
concreteUrl = concreteUrl.replace(`:${param}`,
|
|
206
|
-
concreteUrl = concreteUrl.replace("*",
|
|
206
|
+
concreteUrl = concreteUrl.replace(`:${param}`, value as string);
|
|
207
|
+
concreteUrl = concreteUrl.replace("*", value as string);
|
|
207
208
|
}
|
|
208
209
|
|
|
209
210
|
expanded.push({
|
|
@@ -228,7 +229,7 @@ export async function expandDynamicRoutes(routes, projectRoot, contentTypes = ne
|
|
|
228
229
|
* @param {Map<string, any[]>} contentTypes
|
|
229
230
|
* @returns {Record<string, any>[]} Array of { paramName: value } objects
|
|
230
231
|
*/
|
|
231
|
-
function resolvePathEntries($paths, projectRoot, contentTypes) {
|
|
232
|
+
function resolvePathEntries($paths: any, projectRoot: string, contentTypes: Map<string, any[]>) {
|
|
232
233
|
// Legacy: array of param objects
|
|
233
234
|
if (Array.isArray($paths)) {
|
|
234
235
|
return $paths;
|
|
@@ -245,7 +246,7 @@ function resolvePathEntries($paths, projectRoot, contentTypes) {
|
|
|
245
246
|
}
|
|
246
247
|
const param = $paths.param ?? "slug";
|
|
247
248
|
const field = $paths.field ?? "id";
|
|
248
|
-
return entries.map((
|
|
249
|
+
return entries.map((entry: ContentLoaderEntry) => ({
|
|
249
250
|
[param]: field === "id" ? entry.id : (entry.data[field] ?? entry.id),
|
|
250
251
|
}));
|
|
251
252
|
}
|
|
@@ -253,7 +254,7 @@ function resolvePathEntries($paths, projectRoot, contentTypes) {
|
|
|
253
254
|
// Explicit values: { values: ["en", "fr"], param: "lang" }
|
|
254
255
|
if (Array.isArray($paths.values)) {
|
|
255
256
|
const param = $paths.param ?? "value";
|
|
256
|
-
return $paths.values.map((
|
|
257
|
+
return $paths.values.map((v: string) => ({ [param]: v }));
|
|
257
258
|
}
|
|
258
259
|
|
|
259
260
|
// Data file ref: { "$ref": "./data/products.json", param: "id", field: "sku" }
|
|
@@ -264,7 +265,7 @@ function resolvePathEntries($paths, projectRoot, contentTypes) {
|
|
|
264
265
|
try {
|
|
265
266
|
data = JSON.parse(readFileSync(filePath, "utf8"));
|
|
266
267
|
} catch (e) {
|
|
267
|
-
const err =
|
|
268
|
+
const err = e as Error;
|
|
268
269
|
console.warn(`Warning: $paths.$ref could not load "${$paths.$ref}": ${err.message}`);
|
|
269
270
|
return [];
|
|
270
271
|
}
|
|
@@ -274,7 +275,7 @@ function resolvePathEntries($paths, projectRoot, contentTypes) {
|
|
|
274
275
|
}
|
|
275
276
|
const param = $paths.param ?? "id";
|
|
276
277
|
const field = $paths.field ?? "id";
|
|
277
|
-
return data.map((
|
|
278
|
+
return data.map((item: Record<string, unknown>) => ({
|
|
278
279
|
[param]: item[field] ?? item.id ?? String(item),
|
|
279
280
|
}));
|
|
280
281
|
}
|
|
@@ -18,6 +18,7 @@ import { readFileSync } from "node:fs";
|
|
|
18
18
|
import { dirname, resolve } from "node:path";
|
|
19
19
|
import { pathToFileURL } from "node:url";
|
|
20
20
|
import { createRequire } from "node:module";
|
|
21
|
+
import type { JxDocument, JxMutableNode } from "@jxsuite/schema/types";
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
24
|
* Prototype names handled elsewhere (builtins + legacy content system). These are skipped by the
|
|
@@ -54,7 +55,12 @@ const RESERVED_KEYS = new Set([
|
|
|
54
55
|
* @param {{ config?: Record<string, unknown>; contentTypes?: Map<string, unknown[]> }} [projectContext] -
|
|
55
56
|
* Project-level context injected into all classes
|
|
56
57
|
*/
|
|
57
|
-
export async function resolvePrototypes(
|
|
58
|
+
export async function resolvePrototypes(
|
|
59
|
+
doc: JxMutableNode | JxDocument,
|
|
60
|
+
route: { sourcePath?: string; _pathParams?: Record<string, string> },
|
|
61
|
+
projectRoot: string,
|
|
62
|
+
projectContext: { config?: Record<string, unknown>; contentTypes?: Map<string, unknown[]> } = {},
|
|
63
|
+
) {
|
|
58
64
|
const imports = doc.imports ?? {};
|
|
59
65
|
const state = doc.state;
|
|
60
66
|
if (!state) return;
|
|
@@ -83,7 +89,7 @@ export async function resolvePrototypes(doc, route, projectRoot, projectContext
|
|
|
83
89
|
} catch (err) {
|
|
84
90
|
console.warn(
|
|
85
91
|
`prototype-resolver: failed to resolve "${key}" ($prototype: "${def.$prototype}"):`,
|
|
86
|
-
|
|
92
|
+
(err as Error).message,
|
|
87
93
|
);
|
|
88
94
|
}
|
|
89
95
|
}
|
|
@@ -99,7 +105,13 @@ export async function resolvePrototypes(doc, route, projectRoot, projectContext
|
|
|
99
105
|
* @param {{ config?: Record<string, unknown>; contentTypes?: Map<string, unknown[]> }} projectContext
|
|
100
106
|
* @returns {Promise<any>} The resolved value
|
|
101
107
|
*/
|
|
102
|
-
async function resolveClassPrototype(
|
|
108
|
+
async function resolveClassPrototype(
|
|
109
|
+
def: Record<string, any>,
|
|
110
|
+
route: { sourcePath?: string; _pathParams?: Record<string, string> },
|
|
111
|
+
projectRoot: string,
|
|
112
|
+
state: Record<string, unknown>,
|
|
113
|
+
projectContext: { config?: Record<string, unknown>; contentTypes?: Map<string, unknown[]> },
|
|
114
|
+
) {
|
|
103
115
|
const src = def.$src;
|
|
104
116
|
|
|
105
117
|
// 1. Resolve .class.json path — handles both npm specifiers and relative paths
|
|
@@ -138,8 +150,7 @@ async function resolveClassPrototype(def, route, projectRoot, state, projectCont
|
|
|
138
150
|
}
|
|
139
151
|
|
|
140
152
|
// 6. Build config — filter out reserved keys
|
|
141
|
-
|
|
142
|
-
const config = {};
|
|
153
|
+
const config: Record<string, any> = {};
|
|
143
154
|
for (const [k, v] of Object.entries(def)) {
|
|
144
155
|
if (!RESERVED_KEYS.has(k)) config[k] = v;
|
|
145
156
|
}
|