@cloudcannon/editable-regions 0.0.16 → 0.0.18
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/helpers/cloudcannon.mjs +7 -22
- package/integrations/astro/astro-integration.mjs +1 -4
- package/integrations/astro/index.mjs +15 -41
- package/integrations/astro/modules/assets.js +1 -4
- package/integrations/astro/modules/content.js +2 -11
- package/integrations/astro/react-renderer.mjs +4 -5
- package/integrations/eleventy/browser/collect-config.mjs +188 -0
- package/integrations/eleventy/browser/index.mjs +9 -0
- package/integrations/eleventy/browser/liquid-builtins.mjs +306 -0
- package/integrations/eleventy/browser/liquid-render.mjs +216 -0
- package/integrations/eleventy/index.cjs +1 -0
- package/integrations/eleventy/index.mjs +582 -0
- package/integrations/liquid/README.md +588 -0
- package/integrations/liquid/errors.mjs +39 -0
- package/integrations/liquid/fs.mjs +25 -74
- package/integrations/liquid/globals.mjs +209 -0
- package/integrations/liquid/include-with-tag.mjs +84 -0
- package/integrations/liquid/index.mjs +163 -170
- package/integrations/liquid/logger.mjs +15 -78
- package/integrations/liquid/page-map.mjs +32 -0
- package/integrations/liquid/shortcodes.mjs +62 -99
- package/integrations/react.mjs +5 -9
- package/nodes/editable-component.ts +6 -6
- package/package.json +19 -8
- package/types/eleventy.d.cts +20 -0
- package/types/eleventy.d.ts +81 -0
- package/types/liquid.d.ts +15 -12
- package/integrations/eleventy.mjs +0 -294
- package/integrations/liquid/11ty-filters.mjs +0 -69
|
@@ -1,104 +1,64 @@
|
|
|
1
1
|
import { log, warn } from "./logger.mjs";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* In-memory filesystem for LiquidJS
|
|
5
|
-
*
|
|
6
|
-
* @type {any} LiquidJS-compatible filesystem object
|
|
4
|
+
* In-memory filesystem for LiquidJS, reading from `window.cc_liquid_files`.
|
|
5
|
+
* @type {any}
|
|
7
6
|
*/
|
|
8
7
|
export const inMemoryFs = {
|
|
9
8
|
sep: "/",
|
|
10
9
|
|
|
11
|
-
/**
|
|
12
|
-
* Gets the directory name from a file path.
|
|
13
|
-
*
|
|
14
|
-
* @param {string} filePath - The file path
|
|
15
|
-
* @returns {string} The directory portion of the path
|
|
16
|
-
*/
|
|
17
|
-
dirname(filePath) {
|
|
10
|
+
dirname(/** @type {string} */ filePath) {
|
|
18
11
|
const parts = filePath.split("/");
|
|
19
12
|
parts.pop();
|
|
20
13
|
return parts.join("/") || "/";
|
|
21
14
|
},
|
|
22
15
|
|
|
23
|
-
/**
|
|
24
|
-
|
|
25
|
-
*
|
|
26
|
-
* @param {string} filePath - The file path to read
|
|
27
|
-
* @returns {string | undefined} The file contents or undefined if not found
|
|
28
|
-
*/
|
|
29
|
-
readFileSync(filePath) {
|
|
30
|
-
log("readFileSync:", filePath);
|
|
31
|
-
const fileContents = window.cc_files?.[filePath];
|
|
16
|
+
readFileSync(/** @type {string} */ filePath) {
|
|
17
|
+
const fileContents = window.cc_liquid_files?.[filePath];
|
|
32
18
|
|
|
33
|
-
if (fileContents === undefined) {
|
|
34
|
-
const availableFiles = Object.keys(window.
|
|
19
|
+
if (fileContents === undefined || fileContents === null) {
|
|
20
|
+
const availableFiles = Object.keys(window.cc_liquid_files || {});
|
|
35
21
|
warn("File not found:", filePath);
|
|
36
22
|
log("Available files:", availableFiles);
|
|
37
|
-
|
|
38
|
-
|
|
23
|
+
throw new Error(
|
|
24
|
+
`ENOENT: Failed to find "${filePath}" in the bundled template files. Please check that this file exists and is within your configured component directories.`,
|
|
25
|
+
);
|
|
39
26
|
}
|
|
40
27
|
|
|
41
28
|
return fileContents;
|
|
42
29
|
},
|
|
43
30
|
|
|
44
|
-
/**
|
|
45
|
-
* Asynchronously reads a file from the in-memory store.
|
|
46
|
-
*
|
|
47
|
-
* @param {string} filePath - The file path to read
|
|
48
|
-
* @returns {Promise<string>} The file contents
|
|
49
|
-
* @throws {Error} If filePath is empty
|
|
50
|
-
*/
|
|
51
|
-
async readFile(filePath) {
|
|
52
|
-
log("readFile:", filePath);
|
|
31
|
+
async readFile(/** @type {string} */ filePath) {
|
|
53
32
|
if (!filePath) {
|
|
54
33
|
throw new Error("readFile called with empty path");
|
|
55
34
|
}
|
|
56
35
|
return this.readFileSync(filePath);
|
|
57
36
|
},
|
|
58
37
|
|
|
59
|
-
/**
|
|
60
|
-
* Asynchronously checks if a file exists.
|
|
61
|
-
*
|
|
62
|
-
* @param {string} filePath - The file path to check
|
|
63
|
-
* @returns {Promise<boolean>} True if the file exists
|
|
64
|
-
*/
|
|
65
|
-
async exists(filePath) {
|
|
38
|
+
existsSync(/** @type {string} */ filePath) {
|
|
66
39
|
if (!filePath || typeof filePath !== "string") {
|
|
67
|
-
log("exists: invalid path", filePath);
|
|
68
40
|
return false;
|
|
69
41
|
}
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
return result;
|
|
42
|
+
const fileContents = window.cc_liquid_files?.[filePath];
|
|
43
|
+
return fileContents !== null && fileContents !== undefined;
|
|
73
44
|
},
|
|
74
45
|
|
|
75
|
-
/**
|
|
76
|
-
* Synchronously checks if a file exists.
|
|
77
|
-
*
|
|
78
|
-
* @param {string} filePath - The file path to check
|
|
79
|
-
* @returns {boolean} True if the file exists
|
|
80
|
-
*/
|
|
81
|
-
existsSync(filePath) {
|
|
46
|
+
async exists(/** @type {string} */ filePath) {
|
|
82
47
|
if (!filePath || typeof filePath !== "string") {
|
|
83
48
|
return false;
|
|
84
49
|
}
|
|
85
|
-
|
|
86
|
-
const exists = fileContents !== null && fileContents !== undefined;
|
|
87
|
-
log("existsSync:", filePath, "=", exists);
|
|
88
|
-
return exists;
|
|
50
|
+
return this.existsSync(filePath);
|
|
89
51
|
},
|
|
90
52
|
|
|
91
53
|
/**
|
|
92
|
-
*
|
|
93
|
-
* LiquidJS calls this once per root directory and checks exists() on the
|
|
54
|
+
* LiquidJS calls this once per root directory and checks `exists()` on the
|
|
94
55
|
* result, so no directory searching is needed here.
|
|
95
|
-
*
|
|
96
|
-
* @param {string} root - The root directory provided by LiquidJS
|
|
97
|
-
* @param {string} file - The file name to resolve
|
|
98
|
-
* @param {string} [ext] - The file extension (defaults to ".liquid")
|
|
99
|
-
* @returns {string} The resolved file path
|
|
100
56
|
*/
|
|
101
|
-
resolve(
|
|
57
|
+
resolve(
|
|
58
|
+
/** @type {string} */ root,
|
|
59
|
+
/** @type {string} */ file,
|
|
60
|
+
/** @type {string} */ ext,
|
|
61
|
+
) {
|
|
102
62
|
const extension = ext || ".liquid";
|
|
103
63
|
const fileWithExt = file.endsWith(extension) ? file : `${file}${extension}`;
|
|
104
64
|
const normalizedRoot = root.replace(/^\.\//, "").replace(/\/*$/, "/");
|
|
@@ -107,21 +67,12 @@ export const inMemoryFs = {
|
|
|
107
67
|
return resolved;
|
|
108
68
|
},
|
|
109
69
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
*
|
|
113
|
-
* @returns {Promise<{isFile: () => boolean}>}
|
|
114
|
-
*/
|
|
115
|
-
async statAsync() {
|
|
70
|
+
// The store is flat, so anything stat'd is a file.
|
|
71
|
+
statSync() {
|
|
116
72
|
return { isFile: () => true };
|
|
117
73
|
},
|
|
118
74
|
|
|
119
|
-
|
|
120
|
-
* Returns file stat synchronously (always returns isFile: true for compatibility).
|
|
121
|
-
*
|
|
122
|
-
* @returns {{isFile: () => boolean}}
|
|
123
|
-
*/
|
|
124
|
-
statSync() {
|
|
75
|
+
async statAsync() {
|
|
125
76
|
return { isFile: () => true };
|
|
126
77
|
},
|
|
127
78
|
};
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
// Builders for the `page` and `collections` globals on the shared Liquid
|
|
2
|
+
// engine. Both return Promises that LiquidJS awaits at the globals level;
|
|
3
|
+
// property access in templates is then synchronous on the resolved objects.
|
|
4
|
+
|
|
5
|
+
import { apiLoadedPromise, CloudCannon } from "../../helpers/cloudcannon.mjs";
|
|
6
|
+
import { getPageMap, normalizeInputPath } from "./page-map.mjs";
|
|
7
|
+
|
|
8
|
+
/** @type {{ directories?: { output?: string } } | null} */
|
|
9
|
+
let eleventyData = null;
|
|
10
|
+
|
|
11
|
+
/** @param {{ directories?: { output?: string } } | null} data */
|
|
12
|
+
export function setEleventyData(data) {
|
|
13
|
+
eleventyData = data;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Strips the file extension from a path. */
|
|
17
|
+
function stripExtension(/** @type {string} */ p) {
|
|
18
|
+
return p.replace(/\.[^./]+$/, "");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 11ty's folder-style permalink: trailing-slash URL, `index` files mapping to
|
|
23
|
+
* the parent dir. Last-resort fallback when neither a literal front-matter
|
|
24
|
+
* `permalink` nor the page map resolves a URL.
|
|
25
|
+
*/
|
|
26
|
+
function deriveDefaultUrl(/** @type {string} */ inputPath) {
|
|
27
|
+
const stem = stripExtension(inputPath).replace(/^\.?\//, "/");
|
|
28
|
+
const withLeadingSlash = stem.startsWith("/") ? stem : `/${stem}`;
|
|
29
|
+
const withoutIndex = withLeadingSlash.replace(/\/index$/, "/");
|
|
30
|
+
return withoutIndex.endsWith("/") ? withoutIndex : `${withoutIndex}/`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* A front-matter `permalink` usable verbatim: a plain string with no Liquid
|
|
35
|
+
* templating. Templated permalinks (e.g. `"/{{ page.date }}/"`) need the full
|
|
36
|
+
* build context to render, so we return `undefined` and let the caller fall
|
|
37
|
+
* back to the page map's already-resolved value.
|
|
38
|
+
*/
|
|
39
|
+
function literalPermalink(
|
|
40
|
+
/** @type {Record<string, any> | null | undefined} */ data,
|
|
41
|
+
) {
|
|
42
|
+
const permalink = data?.permalink;
|
|
43
|
+
if (typeof permalink !== "string") return undefined;
|
|
44
|
+
if (permalink.includes("{{") || permalink.includes("{%")) return undefined;
|
|
45
|
+
return permalink;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Resolves the URL for an input file, in priority order: literal front-matter
|
|
50
|
+
* `permalink` (so editor edits show before a rebuild) → build-time page map →
|
|
51
|
+
* 11ty's folder-style default.
|
|
52
|
+
*/
|
|
53
|
+
function resolveUrl(
|
|
54
|
+
/** @type {Record<string, any> | null | undefined} */ data,
|
|
55
|
+
/** @type {string} */ inputPath,
|
|
56
|
+
) {
|
|
57
|
+
const permalink = literalPermalink(data);
|
|
58
|
+
if (permalink) return permalink;
|
|
59
|
+
const mapped = getPageMap()[normalizeInputPath(inputPath)];
|
|
60
|
+
if (mapped?.url) return mapped.url;
|
|
61
|
+
return deriveDefaultUrl(inputPath);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Same priority layering as `resolveUrl`, for the output path. */
|
|
65
|
+
function resolveOutputPath(
|
|
66
|
+
/** @type {Record<string, any> | null | undefined} */ data,
|
|
67
|
+
/** @type {string} */ inputPath,
|
|
68
|
+
) {
|
|
69
|
+
const outputDir = eleventyData?.directories?.output;
|
|
70
|
+
const permalink = literalPermalink(data);
|
|
71
|
+
if (permalink) {
|
|
72
|
+
return outputDir ? joinOutputPath(outputDir, permalink) : undefined;
|
|
73
|
+
}
|
|
74
|
+
const mapped = getPageMap()[normalizeInputPath(inputPath)];
|
|
75
|
+
if (mapped?.outputPath) return mapped.outputPath;
|
|
76
|
+
if (!outputDir) return undefined;
|
|
77
|
+
return joinOutputPath(outputDir, deriveDefaultUrl(inputPath));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Basename minus extension. Matches 11ty's `fileSlug` derivation. */
|
|
81
|
+
function deriveFileSlug(/** @type {string} */ inputPath) {
|
|
82
|
+
const base = inputPath.split("/").pop() ?? "";
|
|
83
|
+
return stripExtension(base);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Full path minus extension, with a leading slash. */
|
|
87
|
+
function deriveFilePathStem(/** @type {string} */ inputPath) {
|
|
88
|
+
const stem = stripExtension(inputPath).replace(/^\.?\//, "/");
|
|
89
|
+
return stem.startsWith("/") ? stem : `/${stem}`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Coerces a front-matter date value into a Date, or `undefined`. */
|
|
93
|
+
function toDate(/** @type {unknown} */ raw) {
|
|
94
|
+
if (!raw) return undefined;
|
|
95
|
+
const d = new Date(/** @type {any} */ (raw));
|
|
96
|
+
return Number.isNaN(d.getTime()) ? undefined : d;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Joins an output dir and URL the way 11ty does: trailing-slash URLs become
|
|
101
|
+
* `<dir><url>index.html`; others are appended as-is.
|
|
102
|
+
*/
|
|
103
|
+
function joinOutputPath(
|
|
104
|
+
/** @type {string} */ outputDir,
|
|
105
|
+
/** @type {string} */ url,
|
|
106
|
+
) {
|
|
107
|
+
const dir = outputDir.replace(/\/+$/, "");
|
|
108
|
+
const tail = url.endsWith("/") ? `${url}index.html` : url;
|
|
109
|
+
return `${dir}${tail}`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Materialises a CC API file into the 11ty collection-item shape.
|
|
114
|
+
*
|
|
115
|
+
* @param {import("@cloudcannon/visual-editor-api").CloudCannonVisualEditorAPIV1File} file
|
|
116
|
+
*/
|
|
117
|
+
async function materialiseFile(file) {
|
|
118
|
+
const data = (await file.data.get()) ?? {};
|
|
119
|
+
return {
|
|
120
|
+
url: resolveUrl(data, file.path),
|
|
121
|
+
outputPath: resolveOutputPath(data, file.path),
|
|
122
|
+
inputPath: file.path,
|
|
123
|
+
fileSlug: deriveFileSlug(file.path),
|
|
124
|
+
filePathStem: deriveFilePathStem(file.path),
|
|
125
|
+
date: toDate(/** @type {any} */ (data).date),
|
|
126
|
+
data,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Builds the `page` object for the file open in the Visual Editor. Called
|
|
132
|
+
* before every render so live front-matter edits are reflected immediately.
|
|
133
|
+
*
|
|
134
|
+
* @returns {Promise<Record<string, any>>}
|
|
135
|
+
*/
|
|
136
|
+
export async function buildPageData() {
|
|
137
|
+
await apiLoadedPromise;
|
|
138
|
+
const file = CloudCannon?.currentFile?.();
|
|
139
|
+
if (!file) return {};
|
|
140
|
+
const inputPath = file.path;
|
|
141
|
+
const data = (await file.data.get()) ?? {};
|
|
142
|
+
return {
|
|
143
|
+
inputPath,
|
|
144
|
+
fileSlug: deriveFileSlug(inputPath),
|
|
145
|
+
filePathStem: deriveFilePathStem(inputPath),
|
|
146
|
+
outputFileExtension: "html",
|
|
147
|
+
url: resolveUrl(data, inputPath),
|
|
148
|
+
outputPath: resolveOutputPath(data, inputPath),
|
|
149
|
+
date: toDate(/** @type {any} */ (data).date),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** @type {Promise<Record<string, Array<any>>> | null} */
|
|
154
|
+
let collectionsCache = null;
|
|
155
|
+
|
|
156
|
+
/** @type {Array<{ target: any, event: "change" | "delete", handler: () => void }>} */
|
|
157
|
+
let collectionsSubscriptions = [];
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Builds (or returns cached) the `collections` object, keyed by collection
|
|
161
|
+
* name. Subscribes to `change`/`delete` on each collection and drops the cache
|
|
162
|
+
* when either fires, so edits during a session are picked up on the next render.
|
|
163
|
+
*
|
|
164
|
+
* @returns {Promise<Record<string, Array<any>>>}
|
|
165
|
+
*/
|
|
166
|
+
export function buildCollectionsData() {
|
|
167
|
+
if (!collectionsCache) {
|
|
168
|
+
collectionsCache = (async () => {
|
|
169
|
+
await apiLoadedPromise;
|
|
170
|
+
const allCollections = await CloudCannon?.collections?.();
|
|
171
|
+
if (!allCollections?.length) return {};
|
|
172
|
+
|
|
173
|
+
for (const collection of allCollections) {
|
|
174
|
+
const handler = () => resetCollectionsCache();
|
|
175
|
+
collection.addEventListener("change", handler);
|
|
176
|
+
collection.addEventListener("delete", handler);
|
|
177
|
+
collectionsSubscriptions.push(
|
|
178
|
+
{ target: collection, event: "change", handler },
|
|
179
|
+
{ target: collection, event: "delete", handler },
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const entries = await Promise.all(
|
|
184
|
+
allCollections.map(async (collection) => {
|
|
185
|
+
const key = collection.collectionKey;
|
|
186
|
+
let files;
|
|
187
|
+
try {
|
|
188
|
+
files = await collection.items();
|
|
189
|
+
} catch {
|
|
190
|
+
return /** @type {[string, any[]]} */ ([key, []]);
|
|
191
|
+
}
|
|
192
|
+
const items = await Promise.all(files.map(materialiseFile));
|
|
193
|
+
return /** @type {[string, any[]]} */ ([key, items]);
|
|
194
|
+
}),
|
|
195
|
+
);
|
|
196
|
+
return Object.fromEntries(entries);
|
|
197
|
+
})();
|
|
198
|
+
}
|
|
199
|
+
return collectionsCache;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** Clears the collections cache and tears down its invalidation listeners. */
|
|
203
|
+
export function resetCollectionsCache() {
|
|
204
|
+
for (const { target, event, handler } of collectionsSubscriptions) {
|
|
205
|
+
target.removeEventListener(event, handler);
|
|
206
|
+
}
|
|
207
|
+
collectionsSubscriptions = [];
|
|
208
|
+
collectionsCache = null;
|
|
209
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// `includeWith` tag — spreads an object into a Liquid `{% include %}` the way
|
|
2
|
+
// Astro's `{...props}` does. Kept in its own file so the Node-side Eleventy
|
|
3
|
+
// plugin can import it without pulling in browser-runtime modules.
|
|
4
|
+
|
|
5
|
+
import { evalToken, Tokenizer, toPromise } from "liquidjs";
|
|
6
|
+
import { enhanceLiquidError } from "./errors.mjs";
|
|
7
|
+
import { group, groupEnd, log } from "./logger.mjs";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Usage: {% includeWith "path/to/partial", objectToSpread %}
|
|
11
|
+
*
|
|
12
|
+
* @param {any} _liquidEngine - Unused; engine reached via `this.liquid`
|
|
13
|
+
* @returns {any}
|
|
14
|
+
*/
|
|
15
|
+
export function createIncludeWithTag(_liquidEngine) {
|
|
16
|
+
return {
|
|
17
|
+
parse(/** @type {any} */ tagToken) {
|
|
18
|
+
const tokenizer = new Tokenizer(
|
|
19
|
+
tagToken.args,
|
|
20
|
+
this.liquid.options.operatorsTrie,
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
this.pathToken = tokenizer.readValue();
|
|
24
|
+
if (!this.pathToken) {
|
|
25
|
+
throw new Error("includeWith: missing path argument");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
tokenizer.skipBlank();
|
|
29
|
+
if (tokenizer.peek() !== ",") {
|
|
30
|
+
throw new Error("includeWith: expected comma separator");
|
|
31
|
+
}
|
|
32
|
+
tokenizer.advance();
|
|
33
|
+
tokenizer.skipBlank();
|
|
34
|
+
|
|
35
|
+
this.objectToken = tokenizer.readValue();
|
|
36
|
+
if (!this.objectToken) {
|
|
37
|
+
throw new Error("includeWith: missing object argument");
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
async render(/** @type {any} */ context) {
|
|
42
|
+
group("includeWith rendering");
|
|
43
|
+
const path = await toPromise(evalToken(this.pathToken, context));
|
|
44
|
+
log("Path resolved to:", path);
|
|
45
|
+
|
|
46
|
+
const obj = await toPromise(evalToken(this.objectToken, context));
|
|
47
|
+
log("Object resolved to:", obj);
|
|
48
|
+
|
|
49
|
+
if (!path || typeof path !== "string") {
|
|
50
|
+
groupEnd();
|
|
51
|
+
throw new Error(`includeWith: invalid path "${path}"`);
|
|
52
|
+
}
|
|
53
|
+
if (!obj || typeof obj !== "object") {
|
|
54
|
+
groupEnd();
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
log(
|
|
59
|
+
"Including:",
|
|
60
|
+
path,
|
|
61
|
+
"with",
|
|
62
|
+
Object.keys(obj).length,
|
|
63
|
+
"props:",
|
|
64
|
+
Object.keys(obj),
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
context.push(obj);
|
|
68
|
+
try {
|
|
69
|
+
const templates = await this.liquid.parseFile(path);
|
|
70
|
+
const result = await this.liquid.render(templates, context);
|
|
71
|
+
log("Rendered result preview:", result?.substring?.(0, 200) || result);
|
|
72
|
+
groupEnd();
|
|
73
|
+
return result;
|
|
74
|
+
} catch (err) {
|
|
75
|
+
const error = /** @type {Error} */ (err);
|
|
76
|
+
log("Error during render:", error.message);
|
|
77
|
+
groupEnd();
|
|
78
|
+
throw enhanceLiquidError(err, `includeWith "${path}"`);
|
|
79
|
+
} finally {
|
|
80
|
+
context.pop();
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|