@jxsuite/compiler 0.10.1 → 0.10.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jxsuite/compiler",
3
- "version": "0.10.1",
3
+ "version": "0.10.2",
4
4
  "description": "Jx static HTML compiler, island detector, and site builder",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -37,8 +37,8 @@
37
37
  },
38
38
  "dependencies": {
39
39
  "@apidevtools/json-schema-ref-parser": "^15.3.5",
40
- "@jxsuite/parser": "^0.10.0",
41
- "@jxsuite/runtime": "^0.10.0",
40
+ "@jxsuite/parser": "^0.10.1",
41
+ "@jxsuite/runtime": "^0.10.1",
42
42
  "remark-gfm": "^4.0.1",
43
43
  "remark-stringify": "^11.0.0",
44
44
  "sharp": "^0.34.5",
@@ -1,8 +1,8 @@
1
1
  /**
2
- * Content-loader.js — Content collection loader
2
+ * Content-loader.js — Content type loader
3
3
  *
4
- * Loads content collections defined in project.json's `collections` key. Supports Markdown (.md),
5
- * JSON (.json), and CSV (.csv) source files.
4
+ * Loads content types defined in project.json's `contentTypes` key. Supports Markdown (.md), JSON
5
+ * (.json), and CSV (.csv) source files.
6
6
  *
7
7
  * Phase 2 implementation of site-architecture spec §6.
8
8
  *
@@ -99,7 +99,7 @@ let _mdModule = null;
99
99
 
100
100
  /**
101
101
  * Lazily import @jxsuite/parser for Markdown support. This avoids hard dependency — only loads when
102
- * MD collections exist.
102
+ * MD content types exist.
103
103
  *
104
104
  * @returns {Promise<any>}
105
105
  */
@@ -172,7 +172,7 @@ function loadJSONEntries(filePath) {
172
172
  * Load a CSV file into ContentEntry(s).
173
173
  *
174
174
  * @param {string} filePath - Absolute path to .csv file
175
- * @param {any} [schema] - Collection schema (for type coercion)
175
+ * @param {any} [schema] - Content type schema (for type coercion)
176
176
  * @returns {object[]} Array of ContentEntry shapes
177
177
  */
178
178
  function loadCSVEntries(filePath, schema) {
@@ -198,10 +198,10 @@ function loadCSVEntries(filePath, schema) {
198
198
  // ─── Content Config ───────────────────────────────────────────────────────────
199
199
 
200
200
  /**
201
- * Load and parse content collections config from project.json.
201
+ * Load and parse content types config from project.json.
202
202
  *
203
203
  * @param {string} projectRoot - Project root directory
204
- * @param {Record<string, any>} [projectConfig] - Already-loaded project config with `collections`
204
+ * @param {Record<string, any>} [projectConfig] - Already-loaded project config with `contentTypes`
205
205
  * key
206
206
  * @returns {{ config: any; contentDir: string } | null} Parsed config or null if no content dir
207
207
  */
@@ -209,68 +209,68 @@ export function loadContentConfig(projectRoot, projectConfig = undefined) {
209
209
  const contentDir = resolve(projectRoot, "content");
210
210
 
211
211
  /** @type {any} */
212
- const config = { collections: projectConfig?.collections ?? {} };
212
+ const config = { contentTypes: projectConfig?.contentTypes ?? {} };
213
213
 
214
214
  return { config, contentDir };
215
215
  }
216
216
 
217
- // ─── Collection Loading ───────────────────────────────────────────────────────
217
+ // ─── Content Type Loading ────────────────────────────────────────────────────
218
218
 
219
219
  /**
220
- * Load all content collections defined in project.json.
220
+ * Load all content types defined in project.json.
221
221
  *
222
222
  * @param {string} projectRoot - Project root directory
223
223
  * @param {Record<string, any>} [projectConfig] - Already-loaded project config
224
- * @returns {Promise<Map<string, any[]>>} Map of collection name → array of ContentEntry
224
+ * @returns {Promise<Map<string, any[]>>} Map of content type name → array of ContentEntry
225
225
  */
226
- export async function loadCollections(projectRoot, projectConfig = undefined) {
226
+ export async function loadContentTypes(projectRoot, projectConfig = undefined) {
227
227
  const result = loadContentConfig(projectRoot, projectConfig);
228
228
  if (!result) return new Map();
229
229
 
230
230
  const { config } = result;
231
231
  /** @type {Map<string, any[]>} */
232
- const collections = new Map();
232
+ const contentTypes = new Map();
233
233
 
234
- for (const [name, collectionDef] of Object.entries(config.collections)) {
235
- const entries = await loadCollection(name, /** @type {any} */ (collectionDef), projectRoot);
236
- collections.set(name, entries);
234
+ for (const [name, contentTypeDef] of Object.entries(config.contentTypes)) {
235
+ const entries = await loadContentType(name, /** @type {any} */ (contentTypeDef), projectRoot);
236
+ contentTypes.set(name, entries);
237
237
  }
238
238
 
239
- return collections;
239
+ return contentTypes;
240
240
  }
241
241
 
242
242
  /**
243
- * Get the $elements array for a specific collection, if defined in project.json collections.
243
+ * Get the $elements array for a specific content type, if defined in project.json contentTypes.
244
244
  *
245
245
  * @param {string} projectRoot - Project root directory
246
- * @param {string} collectionName - Name of the collection
246
+ * @param {string} contentTypeName - Name of the content type
247
247
  * @param {Record<string, any>} [projectConfig] - Already-loaded project config
248
248
  * @returns {any[] | undefined}
249
249
  */
250
- export function getCollectionElements(projectRoot, collectionName, projectConfig = undefined) {
250
+ export function getContentTypeElements(projectRoot, contentTypeName, projectConfig = undefined) {
251
251
  const result = loadContentConfig(projectRoot, projectConfig);
252
252
  if (!result) return undefined;
253
- const def = result.config.collections?.[collectionName];
253
+ const def = result.config.contentTypes?.[contentTypeName];
254
254
  return def?.$elements;
255
255
  }
256
256
 
257
257
  /**
258
- * Load a single collection by its definition.
258
+ * Load a single content type by its definition.
259
259
  *
260
- * @param {string} name - Collection name
261
- * @param {any} collectionDef - Collection definition from project.json
260
+ * @param {string} name - Content type name
261
+ * @param {any} contentTypeDef - Content type definition from project.json
262
262
  * @param {string} projectRoot - Absolute path to project root directory
263
263
  * @returns {Promise<any[]>} Array of ContentEntry
264
264
  */
265
- async function loadCollection(name, collectionDef, projectRoot) {
266
- const source = collectionDef.source;
267
- const schema = collectionDef.schema;
265
+ async function loadContentType(name, contentTypeDef, projectRoot) {
266
+ const source = contentTypeDef.source;
267
+ const schema = contentTypeDef.schema;
268
268
 
269
- // Derive directive allowedNames from collection $elements (tag names from npm packages)
269
+ // Derive directive allowedNames from content type $elements (tag names from npm packages)
270
270
  /** @type {any} */
271
- const directiveOptions = collectionDef.$elements?.length
271
+ const directiveOptions = contentTypeDef.$elements?.length
272
272
  ? {
273
- allowedNames: collectionDef.$elements
273
+ allowedNames: contentTypeDef.$elements
274
274
  .filter((/** @type {any} */ e) => typeof e === "string" || e?.$ref)
275
275
  .map((/** @type {any} */ e) => (typeof e === "string" ? e : e.$ref)),
276
276
  }
@@ -306,14 +306,14 @@ async function loadCollection(name, collectionDef, projectRoot) {
306
306
  // ─── Schema Validation ────────────────────────────────────────────────────────
307
307
 
308
308
  /**
309
- * Validate content entries against their collection schema. Logs warnings for missing required
309
+ * Validate content entries against their content type schema. Logs warnings for missing required
310
310
  * fields and type mismatches.
311
311
  *
312
312
  * @param {any[]} entries - Array of ContentEntry
313
- * @param {any} schema - JSON Schema for the collection
314
- * @param {string} collectionName - For error messages
313
+ * @param {any} schema - JSON Schema for the content type
314
+ * @param {string} contentTypeName - For error messages
315
315
  */
316
- function validateEntries(entries, schema, collectionName) {
316
+ function validateEntries(entries, schema, contentTypeName) {
317
317
  const required = schema.required ?? [];
318
318
  const properties = schema.properties ?? {};
319
319
 
@@ -322,7 +322,7 @@ function validateEntries(entries, schema, collectionName) {
322
322
  for (const field of required) {
323
323
  if (!(field in entry.data) || entry.data[field] == null) {
324
324
  console.warn(
325
- `Content validation: "${collectionName}/${entry.id}" missing required field "${field}"`,
325
+ `Content validation: "${contentTypeName}/${entry.id}" missing required field "${field}"`,
326
326
  );
327
327
  }
328
328
  }
@@ -335,36 +335,36 @@ function validateEntries(entries, schema, collectionName) {
335
335
 
336
336
  if (d.type === "string" && typeof value !== "string") {
337
337
  console.warn(
338
- `Content validation: "${collectionName}/${entry.id}" field "${field}" expected string, got ${typeof value}`,
338
+ `Content validation: "${contentTypeName}/${entry.id}" field "${field}" expected string, got ${typeof value}`,
339
339
  );
340
340
  } else if (d.type === "number" && typeof value !== "number") {
341
341
  console.warn(
342
- `Content validation: "${collectionName}/${entry.id}" field "${field}" expected number, got ${typeof value}`,
342
+ `Content validation: "${contentTypeName}/${entry.id}" field "${field}" expected number, got ${typeof value}`,
343
343
  );
344
344
  } else if (d.type === "boolean" && typeof value !== "boolean") {
345
345
  console.warn(
346
- `Content validation: "${collectionName}/${entry.id}" field "${field}" expected boolean, got ${typeof value}`,
346
+ `Content validation: "${contentTypeName}/${entry.id}" field "${field}" expected boolean, got ${typeof value}`,
347
347
  );
348
348
  } else if (d.type === "array" && !Array.isArray(value)) {
349
349
  console.warn(
350
- `Content validation: "${collectionName}/${entry.id}" field "${field}" expected array, got ${typeof value}`,
350
+ `Content validation: "${contentTypeName}/${entry.id}" field "${field}" expected array, got ${typeof value}`,
351
351
  );
352
352
  }
353
353
  }
354
354
  }
355
355
  }
356
356
 
357
- // ─── Collection Querying ──────────────────────────────────────────────────────
357
+ // ─── Content Type Querying ───────────────────────────────────────────────────
358
358
 
359
359
  /**
360
- * Query a loaded collection with filter, sort, and limit. Implements the ContentCollection
360
+ * Query a loaded content type with filter, sort, and limit. Implements the ContentCollection
361
361
  * $prototype resolution.
362
362
  *
363
- * @param {any[]} entries - Full collection entries
363
+ * @param {any[]} entries - Full content type entries
364
364
  * @param {any} [query] - Query options
365
365
  * @returns {any[]} Filtered, sorted, limited entries
366
366
  */
367
- export function queryCollection(entries, query = {}) {
367
+ export function queryContentType(entries, query = {}) {
368
368
  let result = [...entries];
369
369
 
370
370
  // Filter
@@ -401,9 +401,9 @@ export function queryCollection(entries, query = {}) {
401
401
  }
402
402
 
403
403
  /**
404
- * Find a single entry by ID in a collection. Implements the ContentEntry $prototype resolution.
404
+ * Find a single entry by ID in a content type. Implements the ContentEntry $prototype resolution.
405
405
  *
406
- * @param {any[]} entries - Full collection entries
406
+ * @param {any[]} entries - Full content type entries
407
407
  * @param {string} id - Entry ID to find
408
408
  * @returns {any | null} The matching entry or null
409
409
  */
@@ -411,30 +411,30 @@ export function findEntry(entries, id) {
411
411
  return entries.find((/** @type {any} */ e) => e.id === id) ?? null;
412
412
  }
413
413
 
414
- // ─── Collection Reference Resolution ─────────────────────────────────────────
414
+ // ─── Content Type Reference Resolution ──────────────────────────────────────
415
415
 
416
416
  /**
417
- * Resolve cross-collection $ref references in entry data. For example, a blog post's `author:
418
- * "jane-doe"` with a schema `$ref` to the authors collection gets resolved to the full author
417
+ * Resolve cross-content-type $ref references in entry data. For example, a blog post's `author:
418
+ * "jane-doe"` with a schema `$ref` to the authors content type gets resolved to the full author
419
419
  * entry.
420
420
  *
421
- * @param {Map<string, any[]>} collections - All loaded collections @param {any} config -
421
+ * @param {Map<string, any[]>} contentTypes - All loaded content types @param {any} config -
422
422
  * Content.config.json
423
423
  */
424
- export function resolveCollectionRefs(collections, config) {
425
- for (const [name, collectionDef] of Object.entries(config.collections)) {
426
- const cd = /** @type {any} */ (collectionDef);
424
+ export function resolveContentTypeRefs(contentTypes, config) {
425
+ for (const [name, contentTypeDef] of Object.entries(config.contentTypes)) {
426
+ const cd = /** @type {any} */ (contentTypeDef);
427
427
  const schema = cd.schema;
428
428
  if (!schema?.properties) continue;
429
429
 
430
- const entries = collections.get(name);
430
+ const entries = contentTypes.get(name);
431
431
  if (!entries) continue;
432
432
 
433
433
  for (const [field, def] of Object.entries(schema.properties)) {
434
434
  const d = /** @type {any} */ (def);
435
- if (!d.$ref?.startsWith("#/collections/")) continue;
436
- const refCollection = d.$ref.replace("#/collections/", "");
437
- const refEntries = collections.get(refCollection);
435
+ if (!d.$ref?.startsWith("#/contentTypes/")) continue;
436
+ const refContentType = d.$ref.replace("#/contentTypes/", "");
437
+ const refEntries = contentTypes.get(refContentType);
438
438
  if (!refEntries) continue;
439
439
 
440
440
  for (const entry of entries) {
@@ -4,15 +4,15 @@
4
4
  * Injects project-level and page-level context variables into a page's state before compilation.
5
5
  * These are available as $site.* and $page.* in template expressions.
6
6
  *
7
- * Also resolves ContentCollection and ContentEntry $prototype entries against loaded content
8
- * collections (Phase 2, spec §6.4).
7
+ * Also resolves ContentCollection and ContentEntry $prototype entries against loaded content types
8
+ * (Phase 2, spec §6.4).
9
9
  *
10
10
  * Per site-architecture spec §10: $site.name — from project.json name $site.url — from project.json
11
11
  * url $site.state.* — site-wide reactive state $page.url — current page URL path $page.title — page
12
12
  * title $page.params — dynamic route parameters (if any)
13
13
  */
14
14
 
15
- import { queryCollection, findEntry } from "./content-loader.js";
15
+ import { queryContentType, findEntry } from "./content-loader.js";
16
16
  import { resolve, dirname, relative } from "node:path";
17
17
 
18
18
  /**
@@ -21,7 +21,7 @@ import { resolve, dirname, relative } from "node:path";
21
21
  * @param {any} doc - The page document (mutated)
22
22
  * @param {any} projectConfig - Loaded project configuration
23
23
  * @param {any} route - The resolved route for this page
24
- * @param {Map<string, any[]>} [collections] - Loaded content collections
24
+ * @param {Map<string, any[]>} [contentTypes] - Loaded content types
25
25
  * @param {string | null} [projectRoot] - Absolute path to the project root (for import rebasing)
26
26
  * @returns {any} The mutated document
27
27
  */
@@ -29,7 +29,7 @@ export function injectContext(
29
29
  doc,
30
30
  projectConfig,
31
31
  route,
32
- collections = new Map(),
32
+ contentTypes = new Map(),
33
33
  projectRoot = null,
34
34
  ) {
35
35
  if (!doc.state) doc.state = {};
@@ -49,8 +49,8 @@ export function injectContext(
49
49
  };
50
50
 
51
51
  // Resolve ContentCollection and ContentEntry $prototype entries
52
- if (collections.size > 0) {
53
- resolveContentPrototypes(doc.state, collections, route._pathParams ?? {});
52
+ if (contentTypes.size > 0) {
53
+ resolveContentPrototypes(doc.state, contentTypes, route._pathParams ?? {});
54
54
  }
55
55
 
56
56
  // Merge project-level state into page state (page wins on conflicts)
@@ -110,33 +110,33 @@ export function injectContext(
110
110
  /**
111
111
  * Resolve ContentCollection and ContentEntry $prototype state entries.
112
112
  *
113
- * Replaces state entries like: { "$prototype": "ContentCollection", "collection": "blog", ... }
114
- * with the actual resolved collection data.
113
+ * Replaces state entries like: { "$prototype": "ContentCollection", "contentType": "blog", ... }
114
+ * with the actual resolved content type data.
115
115
  *
116
116
  * @param {Record<string, any>} state - Page state (mutated)
117
- * @param {Map<string, any[]>} collections - Loaded collections
117
+ * @param {Map<string, any[]>} contentTypes - Loaded content types
118
118
  * @param {Record<string, any>} params - Route parameters for $ref resolution
119
119
  */
120
- function resolveContentPrototypes(state, collections, params) {
120
+ function resolveContentPrototypes(state, contentTypes, params) {
121
121
  for (const [key, value] of Object.entries(state)) {
122
122
  if (!value || typeof value !== "object" || !value.$prototype) continue;
123
123
 
124
124
  if (value.$prototype === "ContentCollection") {
125
- const entries = collections.get(value.collection);
125
+ const entries = contentTypes.get(value.contentType);
126
126
  if (!entries) {
127
- console.warn(`ContentCollection: collection "${value.collection}" not found`);
127
+ console.warn(`ContentCollection: content type "${value.contentType}" not found`);
128
128
  state[key] = [];
129
129
  continue;
130
130
  }
131
- state[key] = queryCollection(entries, {
131
+ state[key] = queryContentType(entries, {
132
132
  filter: value.filter,
133
133
  sort: value.sort,
134
134
  limit: value.limit,
135
135
  });
136
136
  } else if (value.$prototype === "ContentEntry") {
137
- const entries = collections.get(value.collection);
137
+ const entries = contentTypes.get(value.contentType);
138
138
  if (!entries) {
139
- console.warn(`ContentEntry: collection "${value.collection}" not found`);
139
+ console.warn(`ContentEntry: content type "${value.contentType}" not found`);
140
140
  state[key] = null;
141
141
  continue;
142
142
  }
@@ -162,17 +162,17 @@ function fileToRoute(relativePath, absolutePath) {
162
162
  /**
163
163
  * Expand dynamic routes by resolving $paths from each dynamic page.
164
164
  *
165
- * Supports three $paths shapes (per spec §4.3): 1. Collection-based: { collection: "blog", param:
166
- * "slug", field: "id" } 2. Explicit values: { values: ["en", "fr"], param: "lang" } 3. Data file
167
- * ref: { "$ref": "./data/products.json", param: "id", field: "sku" } 4. Legacy array: [{ slug:
165
+ * Supports three $paths shapes (per spec §4.3): 1. Content type-based: { contentType: "blog",
166
+ * param: "slug", field: "id" } 2. Explicit values: { values: ["en", "fr"], param: "lang" } 3. Data
167
+ * file ref: { "$ref": "./data/products.json", param: "id", field: "sku" } 4. Legacy array: [{ slug:
168
168
  * "hello" }, { slug: "world" }]
169
169
  *
170
170
  * @param {Route[]} routes - Discovered route table
171
171
  * @param {string} projectRoot - Project root for resolving $ref paths
172
- * @param {Map<string, any[]>} [collections] - Loaded content collections (from content-loader)
172
+ * @param {Map<string, any[]>} [contentTypes] - Loaded content types (from content-loader)
173
173
  * @returns {Promise<Route[]>} Expanded routes with concrete paths
174
174
  */
175
- export async function expandDynamicRoutes(routes, projectRoot, collections = new Map()) {
175
+ export async function expandDynamicRoutes(routes, projectRoot, contentTypes = new Map()) {
176
176
  /** @type {Route[]} */
177
177
  const expanded = [];
178
178
 
@@ -197,7 +197,7 @@ export async function expandDynamicRoutes(routes, projectRoot, collections = new
197
197
  continue;
198
198
  }
199
199
 
200
- const pathEntries = resolvePathEntries(raw.$paths, projectRoot, collections);
200
+ const pathEntries = resolvePathEntries(raw.$paths, projectRoot, contentTypes);
201
201
 
202
202
  for (const pathEntry of pathEntries) {
203
203
  let concreteUrl = route.urlPattern;
@@ -225,21 +225,21 @@ export async function expandDynamicRoutes(routes, projectRoot, collections = new
225
225
  *
226
226
  * @param {any} $paths - The $paths declaration
227
227
  * @param {string} projectRoot
228
- * @param {Map<string, any[]>} collections
228
+ * @param {Map<string, any[]>} contentTypes
229
229
  * @returns {Record<string, any>[]} Array of { paramName: value } objects
230
230
  */
231
- function resolvePathEntries($paths, projectRoot, collections) {
231
+ function resolvePathEntries($paths, projectRoot, contentTypes) {
232
232
  // Legacy: array of param objects
233
233
  if (Array.isArray($paths)) {
234
234
  return $paths;
235
235
  }
236
236
 
237
- // Collection-based: { collection: "blog", param: "slug", field: "id" }
238
- if ($paths.collection) {
239
- const entries = collections.get($paths.collection);
237
+ // Content type-based: { contentType: "blog", param: "slug", field: "id" }
238
+ if ($paths.contentType) {
239
+ const entries = contentTypes.get($paths.contentType);
240
240
  if (!entries || entries.length === 0) {
241
241
  console.warn(
242
- `Warning: $paths references collection "${$paths.collection}" but it has no entries`,
242
+ `Warning: $paths references content type "${$paths.contentType}" but it has no entries`,
243
243
  );
244
244
  return [];
245
245
  }
@@ -40,7 +40,7 @@ import {
40
40
  DEFAULT_REACTIVITY_SRC,
41
41
  DEFAULT_LIT_HTML_SRC,
42
42
  } from "../shared.js";
43
- import { loadCollections, loadContentConfig, resolveCollectionRefs } from "./content-loader.js";
43
+ import { loadContentTypes, loadContentConfig, resolveContentTypeRefs } from "./content-loader.js";
44
44
  import { resolvePrototypes } from "./prototype-resolver.js";
45
45
  import { compileMarkdown } from "../targets/compile-markdown.js";
46
46
  import { transformImageNodes } from "./image-transform.js";
@@ -97,20 +97,20 @@ export async function buildSite(projectRoot, options = {}) {
97
97
  const staticRoutes = discoverPages(pagesDir);
98
98
  log(` Found ${staticRoutes.length} page(s)`);
99
99
 
100
- // ── 3b. Load content collections ──────────────────────────────────────
101
- log("Loading content collections...");
102
- const collections = await loadCollections(projectRoot, projectConfig);
103
- if (collections.size > 0) {
104
- log(` Loaded ${collections.size} collection(s): ${[...collections.keys()].join(", ")}`);
105
- // Resolve cross-collection $ref references
100
+ // ── 3b. Load content types ─────────────────────────────────────────────
101
+ log("Loading content types...");
102
+ const contentTypes = await loadContentTypes(projectRoot, projectConfig);
103
+ if (contentTypes.size > 0) {
104
+ log(` Loaded ${contentTypes.size} content type(s): ${[...contentTypes.keys()].join(", ")}`);
105
+ // Resolve cross-content-type $ref references
106
106
  const contentConfig = loadContentConfig(projectRoot, projectConfig);
107
107
  if (contentConfig) {
108
- resolveCollectionRefs(collections, contentConfig.config);
108
+ resolveContentTypeRefs(contentTypes, contentConfig.config);
109
109
  }
110
110
  }
111
111
 
112
112
  // ── 4. Expand dynamic routes ────────────────────────────────────────────
113
- const routes = await expandDynamicRoutes(staticRoutes, projectRoot, collections);
113
+ const routes = await expandDynamicRoutes(staticRoutes, projectRoot, contentTypes);
114
114
  log(` ${routes.length} route(s) after expansion`);
115
115
 
116
116
  let fileCount = 0;
@@ -194,7 +194,7 @@ export async function buildSite(projectRoot, options = {}) {
194
194
  route,
195
195
  projectConfig,
196
196
  projectRoot,
197
- collections,
197
+ contentTypes,
198
198
  imageCache,
199
199
  outDir,
200
200
  componentDefs,
@@ -339,7 +339,7 @@ export async function buildSite(projectRoot, options = {}) {
339
339
  * @param {any} route
340
340
  * @param {any} projectConfig
341
341
  * @param {string} projectRoot
342
- * @param {Map<string, any[]>} [collections]
342
+ * @param {Map<string, any[]>} [contentTypes]
343
343
  * @param {import("./image-cache.js").CacheManifest | null} [imageCache]
344
344
  * @param {string} [outDir]
345
345
  * @returns {Promise<{ html: string; files: any[]; serverHandler: string | null; doc: any }>}
@@ -348,7 +348,7 @@ async function compilePage(
348
348
  route,
349
349
  projectConfig,
350
350
  projectRoot,
351
- collections = new Map(),
351
+ contentTypes = new Map(),
352
352
  imageCache = null,
353
353
  outDir = "",
354
354
  componentDefs = new Map(),
@@ -375,7 +375,7 @@ async function compilePage(
375
375
  delete layoutDoc._pageTitle;
376
376
 
377
377
  // Inject $site and $page context, resolve ContentCollection/ContentEntry
378
- injectContext(layoutDoc, projectConfig, route, collections, projectRoot);
378
+ injectContext(layoutDoc, projectConfig, route, contentTypes, projectRoot);
379
379
 
380
380
  // Resolve generic $prototype entries via .class.json imports
381
381
  await resolvePrototypes(layoutDoc, route, projectRoot);
@@ -25,7 +25,7 @@ const DEFAULTS = {
25
25
  $media: {},
26
26
  style: {},
27
27
  state: {},
28
- collections: {},
28
+ contentTypes: {},
29
29
  redirects: {},
30
30
  images: {
31
31
  optimize: true,
@@ -85,7 +85,7 @@ export function loadProjectConfig(projectRoot) {
85
85
  if (raw.state) config.state = raw.state;
86
86
  if (raw.redirects) config.redirects = raw.redirects;
87
87
  if (raw.imports) config.imports = raw.imports;
88
- if (raw.collections) config.collections = raw.collections;
88
+ if (raw.contentTypes) config.contentTypes = raw.contentTypes;
89
89
 
90
90
  return {
91
91
  config,