@purview-dev/astroc4 1.0.0-prerelease.5 → 1.0.0-prerelease.6
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/README.md +155 -95
- package/dist/expressive-code.d.ts +0 -4
- package/dist/expressive-code.js.map +5 -5
- package/dist/fences.d.ts +37 -22
- package/dist/file-layout.d.ts +4 -0
- package/dist/grammar/langium.d.ts +0 -4
- package/dist/grammar/source.d.ts +0 -12
- package/dist/grammar/textmate.d.ts +0 -2
- package/dist/icon-assets.d.ts +28 -0
- package/dist/icon-plugin.d.ts +16 -0
- package/dist/index.d.ts +12 -49
- package/dist/index.js +626 -78
- package/dist/index.js.map +15 -10
- package/dist/integration.d.ts +14 -24
- package/dist/integration.js +609 -43
- package/dist/integration.js.map +11 -5
- package/dist/projects.d.ts +24 -0
- package/dist/translations.d.ts +1 -0
- package/dist/workspace.d.ts +5 -0
- package/package.json +8 -3
- package/src/components/DiagramFrame.astro +13 -23
- package/src/components/DiagramShell.astro +18 -30
- package/src/components/ErrorBoundary.tsx +1 -5
- package/src/components/LikeC4Fence.astro +1 -5
- package/src/components/LikeC4InlineView.tsx +6 -16
- package/src/components/LikeC4ProjectView.tsx +0 -5
- package/src/components/LikeC4View.astro +73 -28
- package/src/components/LoadingPlaceholder.astro +9 -14
- package/src/components/icon-source.ts +5 -22
- package/src/components/labels.ts +10 -16
- package/src/components/md-fences.tsx +37 -0
- package/src/components/render-icon.tsx +0 -9
- package/src/components/utils.ts +40 -7
- package/src/translations.ts +3 -10
package/dist/integration.js
CHANGED
|
@@ -1,13 +1,40 @@
|
|
|
1
1
|
// src/integration.ts
|
|
2
|
-
import {
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import { fileURLToPath as
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { dirname as dirname3, join as join5, resolve as resolve5 } from "node:path";
|
|
4
|
+
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
5
5
|
import { LikeC4VitePlugin } from "likec4/vite-plugin";
|
|
6
6
|
|
|
7
7
|
// src/fences.ts
|
|
8
8
|
import { createHash } from "node:crypto";
|
|
9
|
+
import { readFile } from "node:fs/promises";
|
|
10
|
+
import { dirname, isAbsolute, relative, resolve } from "node:path";
|
|
9
11
|
import { createRequire } from "node:module";
|
|
10
12
|
import { fileURLToPath } from "node:url";
|
|
13
|
+
|
|
14
|
+
// src/translations.ts
|
|
15
|
+
var translations = {
|
|
16
|
+
en: {
|
|
17
|
+
"likec4.view.title": "Architecture diagram",
|
|
18
|
+
"likec4.view.description": "Interactive LikeC4 diagram for this section.",
|
|
19
|
+
"likec4.view.hint": "Click a node in the diagram to open its details.",
|
|
20
|
+
"likec4.view.badge": "Click any node to open the modal",
|
|
21
|
+
"likec4.view.loading": "Loading architecture diagram…",
|
|
22
|
+
"likec4.fence.title": "Diagram",
|
|
23
|
+
"likec4.fence.hint": "Click any node to open its details.",
|
|
24
|
+
"likec4.fence.loading": "Loading diagram…",
|
|
25
|
+
"likec4.error.title": "We couldn’t load this diagram.",
|
|
26
|
+
"likec4.error.body": "Please retry, or refresh the page if the issue persists.",
|
|
27
|
+
"likec4.error.retry": "Retry",
|
|
28
|
+
"likec4.error.viewIdRequired": "A LikeC4 viewId is required to render the diagram.",
|
|
29
|
+
"likec4.error.noProjects": "No LikeC4 projects are available in this workspace.",
|
|
30
|
+
"likec4.error.unknownProject": 'Unknown LikeC4 projectId "{projectId}". Available projects: {available}.',
|
|
31
|
+
"likec4.error.projectRequired": 'Several LikeC4 projects exist ({available}); pass project="…".',
|
|
32
|
+
"likec4.error.unresolvedProject": "Unable to resolve a LikeC4 project.",
|
|
33
|
+
"likec4.error.loadFailed": 'Unable to load LikeC4 project "{projectId}".'
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// src/fences.ts
|
|
11
38
|
var require2 = createRequire(import.meta.url);
|
|
12
39
|
var likec4ApiPromise;
|
|
13
40
|
function loadLikeC4() {
|
|
@@ -25,6 +52,7 @@ var IMPORTS = {
|
|
|
25
52
|
[INLINE_COMPONENT]: `${PACKAGE_NAME}/components/LikeC4Fence.astro`,
|
|
26
53
|
[PROJECT_COMPONENT]: `${PACKAGE_NAME}/components/LikeC4View.astro`
|
|
27
54
|
};
|
|
55
|
+
var STRINGS = translations.en;
|
|
28
56
|
var layoutCache = new Map;
|
|
29
57
|
function parseFenceMeta(meta) {
|
|
30
58
|
const options = {};
|
|
@@ -40,7 +68,7 @@ function parseFenceMeta(meta) {
|
|
|
40
68
|
}
|
|
41
69
|
return options;
|
|
42
70
|
}
|
|
43
|
-
async function
|
|
71
|
+
async function layoutSource(source) {
|
|
44
72
|
const key = createHash("sha256").update(source).digest("hex");
|
|
45
73
|
const cached = layoutCache.get(key);
|
|
46
74
|
if (cached)
|
|
@@ -92,26 +120,11 @@ function describe(fileURL) {
|
|
|
92
120
|
return String(fileURL);
|
|
93
121
|
}
|
|
94
122
|
}
|
|
95
|
-
|
|
96
|
-
warn: (message) => console.warn(`[likec4-fences] ${message}`)
|
|
97
|
-
};
|
|
98
|
-
function likec4Fences({ reporter = CONSOLE_REPORTER, mdx = true } = {}) {
|
|
123
|
+
function likec4Fences({ root, projects } = {}) {
|
|
99
124
|
return (factory) => {
|
|
100
|
-
if (factory.sourceFormat !== "mdx") {
|
|
101
|
-
if (!factory.source.includes("```" + FENCE_LANG))
|
|
102
|
-
return null;
|
|
103
|
-
const fix = mdx ? "Rename the page to .mdx, or use <LikeC4View /> directly." : "Add @astrojs/mdx (`astro add mdx`) and rename the page to .mdx, or use <LikeC4View /> directly.";
|
|
104
|
-
return {
|
|
105
|
-
name: "likec4-fences:warn",
|
|
106
|
-
code(node, ctx) {
|
|
107
|
-
if (node.lang !== FENCE_LANG)
|
|
108
|
-
return;
|
|
109
|
-
reporter.warn(`${describe(ctx.fileURL)} has a \`\`\`${FENCE_LANG} fence, but fences only ` + `render as diagrams in .mdx files. ${fix}`);
|
|
110
|
-
}
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
125
|
if (!factory.source.includes("```" + FENCE_LANG))
|
|
114
126
|
return null;
|
|
127
|
+
const markdown = factory.sourceFormat !== "mdx";
|
|
115
128
|
const used = new Set;
|
|
116
129
|
return {
|
|
117
130
|
name: "likec4-fences",
|
|
@@ -120,7 +133,7 @@ function likec4Fences({ reporter = CONSOLE_REPORTER, mdx = true } = {}) {
|
|
|
120
133
|
return;
|
|
121
134
|
const where = `${describe(ctx.fileURL)}: \`\`\`${FENCE_LANG} fence`;
|
|
122
135
|
const options = parseFenceMeta(node.meta);
|
|
123
|
-
const source = (node.value ?? "").trim();
|
|
136
|
+
const source = options.file ? await readFenceFile(options.file, ctx.fileURL, root, where) : (node.value ?? "").trim();
|
|
124
137
|
const common = {
|
|
125
138
|
title: options.title,
|
|
126
139
|
description: options.description,
|
|
@@ -132,25 +145,40 @@ function likec4Fences({ reporter = CONSOLE_REPORTER, mdx = true } = {}) {
|
|
|
132
145
|
if (!options.view) {
|
|
133
146
|
throw new Error(`${where} is empty, so it needs \`view="<id>"\` to name a view in the project model.`);
|
|
134
147
|
}
|
|
148
|
+
checkProject(options.project, projects, where);
|
|
149
|
+
if (markdown) {
|
|
150
|
+
ctx.replaceNode(node, {
|
|
151
|
+
raw: fencePlaceholder({ ...placeholderOptions(options), viewId: options.view, project: options.project }),
|
|
152
|
+
mdxExpressions: false
|
|
153
|
+
});
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
135
156
|
used.add(PROJECT_COMPONENT);
|
|
136
157
|
ctx.replaceNode(node, {
|
|
137
158
|
type: "mdxJsxFlowElement",
|
|
138
159
|
name: PROJECT_COMPONENT,
|
|
139
|
-
attributes: attributes({ ...common, viewId: options.view }),
|
|
160
|
+
attributes: attributes({ ...common, viewId: options.view, project: options.project }),
|
|
140
161
|
children: []
|
|
141
162
|
});
|
|
142
163
|
return;
|
|
143
164
|
}
|
|
144
165
|
let layout;
|
|
145
166
|
try {
|
|
146
|
-
layout = await
|
|
167
|
+
layout = await layoutSource(source);
|
|
147
168
|
} catch (error) {
|
|
148
169
|
throw new Error(`${where} is not valid LikeC4 — ${error instanceof Error ? error.message : String(error)}`);
|
|
149
170
|
}
|
|
150
|
-
const viewId = options.view ?? layout.viewIds
|
|
171
|
+
const viewId = options.view ?? defaultView(source, layout.viewIds);
|
|
151
172
|
if (!viewId || !layout.viewIds.includes(viewId)) {
|
|
152
173
|
throw new Error(`${where} has no view "${viewId ?? "(none)"}". Declared: ${layout.viewIds.join(", ") || "(none)"}.`);
|
|
153
174
|
}
|
|
175
|
+
if (markdown) {
|
|
176
|
+
ctx.replaceNode(node, {
|
|
177
|
+
raw: fencePlaceholder({ ...placeholderOptions(options), viewId, model: layout.dump }),
|
|
178
|
+
mdxExpressions: false
|
|
179
|
+
});
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
154
182
|
used.add(INLINE_COMPONENT);
|
|
155
183
|
ctx.replaceNode(node, {
|
|
156
184
|
type: "mdxJsxFlowElement",
|
|
@@ -167,49 +195,590 @@ function likec4Fences({ reporter = CONSOLE_REPORTER, mdx = true } = {}) {
|
|
|
167
195
|
};
|
|
168
196
|
};
|
|
169
197
|
}
|
|
198
|
+
function checkProject(project, projects, where) {
|
|
199
|
+
if (projects === undefined)
|
|
200
|
+
return;
|
|
201
|
+
const requested = project?.trim();
|
|
202
|
+
const available = projects.join(", ");
|
|
203
|
+
if (!requested) {
|
|
204
|
+
if (projects.length > 1) {
|
|
205
|
+
throw new Error(`${where}: ${STRINGS["likec4.error.projectRequired"].replace("{available}", available)}`);
|
|
206
|
+
}
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if (!projects.includes(requested)) {
|
|
210
|
+
throw new Error(`${where}: ${STRINGS["likec4.error.unknownProject"].replace("{projectId}", requested).replace("{available}", available || "(none)")}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
function defaultView(source, viewIds) {
|
|
214
|
+
for (const match of source.matchAll(/(?<![\w-])view\s+([\w-]+)/g)) {
|
|
215
|
+
if (viewIds.includes(match[1]))
|
|
216
|
+
return match[1];
|
|
217
|
+
}
|
|
218
|
+
return viewIds[0];
|
|
219
|
+
}
|
|
220
|
+
var token = {
|
|
221
|
+
border: "var(--likec4-border, var(--sl-color-gray-5, rgba(128, 128, 128, 0.35)))",
|
|
222
|
+
surface: "var(--likec4-surface, var(--sl-color-bg, transparent))",
|
|
223
|
+
surfaceAlt: "var(--likec4-surface-alt, var(--sl-color-bg-nav, rgba(128, 128, 128, 0.06)))",
|
|
224
|
+
text: "var(--likec4-text, var(--sl-color-white, inherit))",
|
|
225
|
+
textMuted: "var(--likec4-text-muted, var(--sl-color-gray-2, rgba(128, 128, 128, 0.95)))"
|
|
226
|
+
};
|
|
227
|
+
function fencePlaceholder({
|
|
228
|
+
viewId,
|
|
229
|
+
model,
|
|
230
|
+
project,
|
|
231
|
+
title = STRINGS["likec4.fence.title"],
|
|
232
|
+
description,
|
|
233
|
+
height = 420,
|
|
234
|
+
collapsible = true,
|
|
235
|
+
defaultOpen = true
|
|
236
|
+
}) {
|
|
237
|
+
const data = [
|
|
238
|
+
`data-astroc4-fence`,
|
|
239
|
+
`data-view-id="${escapeHtml(viewId)}"`,
|
|
240
|
+
project ? `data-project="${escapeHtml(project)}"` : "",
|
|
241
|
+
model === undefined ? "" : `data-model="${escapeHtml(JSON.stringify(model))}"`
|
|
242
|
+
].filter(Boolean).join(" ");
|
|
243
|
+
const headerStyle = `display: block; padding: 1em 1.25rem; border-bottom: 1px solid ${token.border}; background: ${token.surface};`;
|
|
244
|
+
const heading = [
|
|
245
|
+
`<h3 style="display: inline; margin: 0; font-size: 1rem; font-weight: 600; color: ${token.text};">${escapeHtml(title)}</h3>`,
|
|
246
|
+
description ? `<p style="margin: 0.25rem 0 0; font-size: 0.875rem; color: ${token.textMuted};">${escapeHtml(description)}</p>` : ""
|
|
247
|
+
].join("");
|
|
248
|
+
const body = [
|
|
249
|
+
`<div style="padding: 1em 1.25rem;">`,
|
|
250
|
+
`<div class="likec4-frame" style="position: relative; width: 100%; height: ${height}px; min-height: ${height}px;">`,
|
|
251
|
+
`<p class="likec4-loading" aria-live="polite" style="color: ${token.textMuted};">${escapeHtml(STRINGS["likec4.fence.loading"])}</p>`,
|
|
252
|
+
`</div>`,
|
|
253
|
+
`</div>`
|
|
254
|
+
].join("");
|
|
255
|
+
const shell = collapsible ? `<details${defaultOpen ? " open" : ""}><summary style="cursor: pointer; ${headerStyle}">${heading}</summary>${body}</details>` : `<header style="${headerStyle}">${heading}</header>${body}`;
|
|
256
|
+
return [
|
|
257
|
+
`<section class="not-content likec4-md-fence" ${data} style="margin: 1.5rem 0; overflow: hidden; border: 1px solid ${token.border}; border-radius: 0.75rem; background: ${token.surfaceAlt};">`,
|
|
258
|
+
shell,
|
|
259
|
+
`</section>`
|
|
260
|
+
].join("");
|
|
261
|
+
}
|
|
262
|
+
function placeholderOptions(options) {
|
|
263
|
+
const height = Number.parseInt(options.height ?? "", 10);
|
|
264
|
+
return {
|
|
265
|
+
title: options.title || undefined,
|
|
266
|
+
description: options.description || undefined,
|
|
267
|
+
height: Number.isFinite(height) ? height : undefined,
|
|
268
|
+
collapsible: options.collapsible !== "false",
|
|
269
|
+
defaultOpen: options.defaultOpen !== "false"
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
function escapeHtml(value) {
|
|
273
|
+
return value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
274
|
+
}
|
|
275
|
+
async function readFenceFile(file, pageURL, root, where) {
|
|
276
|
+
if (!pageURL || !root) {
|
|
277
|
+
throw new Error(`${where} uses file="${file}", which needs the page's location and the site root.`);
|
|
278
|
+
}
|
|
279
|
+
const path = resolve(dirname(fileURLToPath(pageURL)), file);
|
|
280
|
+
const fromRoot = relative(resolve(root), path);
|
|
281
|
+
if (fromRoot.startsWith("..") || isAbsolute(fromRoot)) {
|
|
282
|
+
throw new Error(`${where} uses file="${file}", which is outside the site (${path}).`);
|
|
283
|
+
}
|
|
284
|
+
try {
|
|
285
|
+
return (await readFile(path, "utf8")).trim();
|
|
286
|
+
} catch {
|
|
287
|
+
throw new Error(`${where} uses file="${file}", which does not exist (${path}).`);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// src/file-layout.ts
|
|
292
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
293
|
+
var FILE_LAYOUT_QUERY = "?astroc4-layout";
|
|
294
|
+
function fileLayoutPlugin() {
|
|
295
|
+
return {
|
|
296
|
+
name: "@purview-dev/astroc4/file-layout",
|
|
297
|
+
async load(id) {
|
|
298
|
+
if (!id.endsWith(FILE_LAYOUT_QUERY))
|
|
299
|
+
return null;
|
|
300
|
+
const file = id.slice(0, -FILE_LAYOUT_QUERY.length);
|
|
301
|
+
this.addWatchFile(file);
|
|
302
|
+
let layout;
|
|
303
|
+
try {
|
|
304
|
+
layout = await layoutSource(await readFile2(file, "utf8"));
|
|
305
|
+
} catch (error) {
|
|
306
|
+
throw new Error(`[astroc4] ${file} is not valid LikeC4 — ${error.message}`);
|
|
307
|
+
}
|
|
308
|
+
return `export default ${JSON.stringify(layout)};`;
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// src/icon-plugin.ts
|
|
314
|
+
import { readFile as readFile4, copyFile, mkdir } from "node:fs/promises";
|
|
315
|
+
import { dirname as dirname2, extname, join as join3 } from "node:path";
|
|
316
|
+
import { fileURLToPath as fileURLToPath3, pathToFileURL as pathToFileURL2 } from "node:url";
|
|
317
|
+
|
|
318
|
+
// src/icon-assets.ts
|
|
319
|
+
import { realpathSync } from "node:fs";
|
|
320
|
+
import { isAbsolute as isAbsolute2, join as join2, relative as relative2, resolve as resolve3, sep } from "node:path";
|
|
321
|
+
import { fileURLToPath as fileURLToPath2, pathToFileURL } from "node:url";
|
|
322
|
+
|
|
323
|
+
// src/projects.ts
|
|
324
|
+
import { readdir, readFile as readFile3, stat } from "node:fs/promises";
|
|
325
|
+
import { basename, join, resolve as resolve2 } from "node:path";
|
|
326
|
+
var CONFIG_FILES = [
|
|
327
|
+
".likec4rc",
|
|
328
|
+
".likec4.config.json",
|
|
329
|
+
"likec4.config.json",
|
|
330
|
+
"likec4.config.js",
|
|
331
|
+
"likec4.config.cjs",
|
|
332
|
+
"likec4.config.mjs",
|
|
333
|
+
"likec4.config.ts",
|
|
334
|
+
"likec4.config.cts",
|
|
335
|
+
"likec4.config.mts"
|
|
336
|
+
];
|
|
337
|
+
var JSON_CONFIG_FILES = CONFIG_FILES.slice(0, 3);
|
|
338
|
+
|
|
339
|
+
class Astroc4ConfigError extends Error {
|
|
340
|
+
constructor(message) {
|
|
341
|
+
super(`[astroc4] ${message}`);
|
|
342
|
+
this.name = "Astroc4ConfigError";
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
async function discoverProjects(root) {
|
|
346
|
+
const found = [];
|
|
347
|
+
async function walk(folder) {
|
|
348
|
+
const entries = await readdir(folder, { withFileTypes: true });
|
|
349
|
+
const file = configFileIn(entries.filter((entry) => entry.isFile()).map((entry) => entry.name));
|
|
350
|
+
if (file) {
|
|
351
|
+
const config = await readConfig(folder, file);
|
|
352
|
+
found.push(toProject(config.name, folder, config, "discovered"));
|
|
353
|
+
}
|
|
354
|
+
for (const entry of entries) {
|
|
355
|
+
if (!entry.isDirectory() || entry.name === "node_modules" || entry.name.startsWith("."))
|
|
356
|
+
continue;
|
|
357
|
+
await walk(join(folder, entry.name));
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
await walk(resolve2(root));
|
|
361
|
+
return found;
|
|
362
|
+
}
|
|
363
|
+
async function resolveProjects(options) {
|
|
364
|
+
const configured = [];
|
|
365
|
+
for (const [name, path] of Object.entries(options.configured ?? {})) {
|
|
366
|
+
const folder = resolve2(options.root, path);
|
|
367
|
+
if (!await isDirectory(folder)) {
|
|
368
|
+
throw new Astroc4ConfigError(`project "${name}": folder ${folder} does not exist.`);
|
|
369
|
+
}
|
|
370
|
+
const file = configFileIn(await readdir(folder));
|
|
371
|
+
if (!file) {
|
|
372
|
+
throw new Astroc4ConfigError(`project "${name}": no LikeC4 config file (likec4.config.json, .likec4rc, likec4.config.ts, …) in ${folder}.`);
|
|
373
|
+
}
|
|
374
|
+
configured.push(toProject(name, folder, await readConfig(folder, file), "configured"));
|
|
375
|
+
}
|
|
376
|
+
const configuredFolders = new Set(configured.map((project) => project.folder));
|
|
377
|
+
const projects = [
|
|
378
|
+
...configured,
|
|
379
|
+
...options.discovered.filter((project) => !configuredFolders.has(project.folder))
|
|
380
|
+
];
|
|
381
|
+
const byName = new Map;
|
|
382
|
+
for (const project of projects) {
|
|
383
|
+
const existing = byName.get(project.name);
|
|
384
|
+
if (existing) {
|
|
385
|
+
throw new Astroc4ConfigError(`two LikeC4 projects are named "${project.name}": ${existing.folder} and ${project.folder}.`);
|
|
386
|
+
}
|
|
387
|
+
byName.set(project.name, project);
|
|
388
|
+
}
|
|
389
|
+
const byId = new Map;
|
|
390
|
+
for (const project of projects) {
|
|
391
|
+
const existing = byId.get(project.likec4Id);
|
|
392
|
+
if (existing) {
|
|
393
|
+
throw new Astroc4ConfigError(`two LikeC4 projects declare "name": "${project.likec4Id}" in their configs: ${existing.folder} and ${project.folder}. Rename one.`);
|
|
394
|
+
}
|
|
395
|
+
byId.set(project.likec4Id, project);
|
|
396
|
+
}
|
|
397
|
+
return projects;
|
|
398
|
+
}
|
|
399
|
+
function configFileIn(names) {
|
|
400
|
+
return CONFIG_FILES.find((file) => names.includes(file));
|
|
401
|
+
}
|
|
402
|
+
async function readConfig(folder, file) {
|
|
403
|
+
const path = join(folder, file);
|
|
404
|
+
let config;
|
|
405
|
+
try {
|
|
406
|
+
config = JSON_CONFIG_FILES.includes(file) ? await readJsonConfig(path) : await evaluateConfig(path, folder);
|
|
407
|
+
} catch (error) {
|
|
408
|
+
throw new Astroc4ConfigError(`${path} could not be read: ${error.message}`);
|
|
409
|
+
}
|
|
410
|
+
if (typeof config !== "object" || config === null || Array.isArray(config)) {
|
|
411
|
+
throw new Astroc4ConfigError(`${path} must define a config object.`);
|
|
412
|
+
}
|
|
413
|
+
const { name = basename(folder), imageAliases } = config;
|
|
414
|
+
if (typeof name !== "string" || name.trim().length === 0) {
|
|
415
|
+
throw new Astroc4ConfigError(`${path} has an empty "name".`);
|
|
416
|
+
}
|
|
417
|
+
return { name, imageAliases: isRecord(imageAliases) ? imageAliases : undefined };
|
|
418
|
+
}
|
|
419
|
+
async function readJsonConfig(path) {
|
|
420
|
+
const { default: JSON5 } = await import("json5");
|
|
421
|
+
const text = (await readFile3(path, "utf8")).trim();
|
|
422
|
+
return JSON5.parse(text || "{}");
|
|
423
|
+
}
|
|
424
|
+
async function evaluateConfig(path, folder) {
|
|
425
|
+
const { bundleRequire } = await import("bundle-require");
|
|
426
|
+
const { mod } = await bundleRequire({
|
|
427
|
+
filepath: path,
|
|
428
|
+
cwd: folder,
|
|
429
|
+
esbuildOptions: {
|
|
430
|
+
resolveExtensions: [".ts", ".mts", ".cts", ".mjs", ".js", ".cjs"],
|
|
431
|
+
plugins: [
|
|
432
|
+
{
|
|
433
|
+
name: "astroc4-likec4-config",
|
|
434
|
+
setup(build) {
|
|
435
|
+
build.onResolve({ filter: /^@?likec4\/config$/ }, (args) => ({
|
|
436
|
+
path: args.path,
|
|
437
|
+
namespace: "astroc4-likec4-config"
|
|
438
|
+
}));
|
|
439
|
+
build.onLoad({ filter: /.*/, namespace: "astroc4-likec4-config" }, () => ({
|
|
440
|
+
contents: [
|
|
441
|
+
"const identity = (value) => value;",
|
|
442
|
+
"export { identity as defineConfig, identity as defineGenerators, identity as defineStyle, identity as defineTheme, identity as defineThemeColor };"
|
|
443
|
+
].join(`
|
|
444
|
+
`),
|
|
445
|
+
loader: "js"
|
|
446
|
+
}));
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
]
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
return mod.default ?? mod;
|
|
453
|
+
}
|
|
454
|
+
function toProject(name, folder, config, source) {
|
|
455
|
+
const imageAliases = Object.fromEntries(Object.entries(config.imageAliases ?? {}).map(([alias, path]) => [alias, resolve2(folder, path)]));
|
|
456
|
+
return { name, folder: resolve2(folder), likec4Id: config.name, source, imageAliases };
|
|
457
|
+
}
|
|
458
|
+
function isRecord(value) {
|
|
459
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && Object.values(value).every((entry) => typeof entry === "string");
|
|
460
|
+
}
|
|
461
|
+
async function isDirectory(path) {
|
|
462
|
+
return (await stat(path).catch(() => null))?.isDirectory() ?? false;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// src/icon-assets.ts
|
|
466
|
+
var ICON_ROUTE = "/_astroc4/";
|
|
467
|
+
var UP = "__up";
|
|
468
|
+
function iconAssetFor(project, fileUrl, base, siteRoot) {
|
|
469
|
+
const file = realFile(fileUrl);
|
|
470
|
+
const prefix = base.replace(/\/+$/, "");
|
|
471
|
+
const publicFolder = tryRealpath(join2(siteRoot, "public"));
|
|
472
|
+
if (isInside(file, publicFolder)) {
|
|
473
|
+
const path = split(relative2(publicFolder, file)).map(encodeURIComponent).join("/");
|
|
474
|
+
return { file, url: `${prefix}/${path}`, projectName: project.name, public: true };
|
|
475
|
+
}
|
|
476
|
+
const segments = aliasSegments(project, file) ?? relativeSegments(tryRealpath(project.folder), file);
|
|
477
|
+
const path = [project.name, ...segments].map(encodeURIComponent).join("/");
|
|
478
|
+
return { file, url: `${prefix}${ICON_ROUTE}${path}`, projectName: project.name };
|
|
479
|
+
}
|
|
480
|
+
function unstage(fileUrl, project, stagingDir) {
|
|
481
|
+
if (stagingDir === undefined)
|
|
482
|
+
return fileUrl;
|
|
483
|
+
let path;
|
|
484
|
+
try {
|
|
485
|
+
path = fileURLToPath2(decodeDrive(fileUrl));
|
|
486
|
+
} catch {
|
|
487
|
+
return fileUrl;
|
|
488
|
+
}
|
|
489
|
+
const fromLink = relative2(join2(stagingDir, project.name), path);
|
|
490
|
+
return pathToFileURL(resolve3(project.folder, fromLink)).href;
|
|
491
|
+
}
|
|
492
|
+
function buildIconManifest(entries, base, siteRoot) {
|
|
493
|
+
const manifest = new Map;
|
|
494
|
+
const byUrl = new Map;
|
|
495
|
+
for (const { project, fileUrl, source } of entries) {
|
|
496
|
+
let asset;
|
|
497
|
+
try {
|
|
498
|
+
asset = iconAssetFor(project, fileUrl, base, siteRoot);
|
|
499
|
+
} catch (error) {
|
|
500
|
+
throw new Astroc4ConfigError(`${source}: ${error.message.replace(/^\[astroc4\] /, "")}`);
|
|
501
|
+
}
|
|
502
|
+
if (asset === null)
|
|
503
|
+
continue;
|
|
504
|
+
const existing = byUrl.get(asset.url);
|
|
505
|
+
if (existing && !samePath(existing.asset.file, asset.file)) {
|
|
506
|
+
throw new Astroc4ConfigError(`two icons would be served at ${asset.url}: ${existing.asset.file} (${existing.source}) and ${asset.file} (${source}). Rename one, or give its alias another name.`);
|
|
507
|
+
}
|
|
508
|
+
byUrl.set(asset.url, { asset, source });
|
|
509
|
+
manifest.set(fileUrl, asset);
|
|
510
|
+
}
|
|
511
|
+
return manifest;
|
|
512
|
+
}
|
|
513
|
+
function aliasSegments(project, file) {
|
|
514
|
+
let best = null;
|
|
515
|
+
for (const [alias, folder] of Object.entries(project.imageAliases)) {
|
|
516
|
+
const real = tryRealpath(folder);
|
|
517
|
+
if (isInside(file, real) && (best === null || real.length > best.folder.length)) {
|
|
518
|
+
best = { folder: real, alias };
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
if (best === null)
|
|
522
|
+
return null;
|
|
523
|
+
const name = best.alias.replace(/^@/, "");
|
|
524
|
+
return [...name ? [name] : [], ...split(relative2(best.folder, file))];
|
|
525
|
+
}
|
|
526
|
+
function relativeSegments(folder, file) {
|
|
527
|
+
return split(relative2(folder, file)).map((segment) => segment === ".." ? UP : segment);
|
|
528
|
+
}
|
|
529
|
+
function realFile(fileUrl) {
|
|
530
|
+
let path;
|
|
531
|
+
try {
|
|
532
|
+
path = fileURLToPath2(decodeDrive(fileUrl));
|
|
533
|
+
} catch {
|
|
534
|
+
throw new Astroc4ConfigError(`icon ${fileUrl} is not a file URL.`);
|
|
535
|
+
}
|
|
536
|
+
try {
|
|
537
|
+
return realpathSync(path);
|
|
538
|
+
} catch {
|
|
539
|
+
throw new Astroc4ConfigError(`icon ${path} does not exist.`);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
function decodeDrive(fileUrl) {
|
|
543
|
+
return fileUrl.replace(/^file:\/\/\/([A-Za-z])%3A/i, "file:///$1:");
|
|
544
|
+
}
|
|
545
|
+
function tryRealpath(path) {
|
|
546
|
+
try {
|
|
547
|
+
return realpathSync(path);
|
|
548
|
+
} catch {
|
|
549
|
+
return path;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
function isInside(file, folder) {
|
|
553
|
+
const path = relative2(folder, file);
|
|
554
|
+
return path.length > 0 && !path.startsWith("..") && !isAbsolute2(path);
|
|
555
|
+
}
|
|
556
|
+
function samePath(a, b) {
|
|
557
|
+
return sep === "\\" ? a.toLowerCase() === b.toLowerCase() : a === b;
|
|
558
|
+
}
|
|
559
|
+
function split(path) {
|
|
560
|
+
return path.split(/[\\/]+/).filter((segment) => segment.length > 0);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// src/icon-plugin.ts
|
|
564
|
+
var LIKEC4_MODULE = /likec4:plugin\/(?<projectId>[^/]+)\/[^/]+$/;
|
|
565
|
+
var FILE_URL = /file:(?:\\u002F|\/){3}[^"'`\s]+/g;
|
|
566
|
+
var CONTENT_TYPES = {
|
|
567
|
+
".svg": "image/svg+xml",
|
|
568
|
+
".png": "image/png",
|
|
569
|
+
".jpg": "image/jpeg",
|
|
570
|
+
".jpeg": "image/jpeg",
|
|
571
|
+
".gif": "image/gif",
|
|
572
|
+
".webp": "image/webp",
|
|
573
|
+
".ico": "image/x-icon"
|
|
574
|
+
};
|
|
575
|
+
function astroc4IconPlugin(state) {
|
|
576
|
+
const manifest = new Map;
|
|
577
|
+
const byUrl = new Map;
|
|
578
|
+
const base = state.base.replace(/\/+$/, "");
|
|
579
|
+
function gather(project, fileUrl) {
|
|
580
|
+
const known = manifest.get(fileUrl);
|
|
581
|
+
if (known)
|
|
582
|
+
return known;
|
|
583
|
+
const entries = [
|
|
584
|
+
...[...byUrl.values()].map((asset) => ({
|
|
585
|
+
project: state.projects.find((p) => p.name === asset.projectName),
|
|
586
|
+
fileUrl: pathToFileURL2(asset.file).href,
|
|
587
|
+
source: `LikeC4 project "${asset.projectName}"`
|
|
588
|
+
})),
|
|
589
|
+
{ project, fileUrl, source: `LikeC4 project "${project.name}"` }
|
|
590
|
+
];
|
|
591
|
+
const asset = buildIconManifest(entries, state.base, state.siteRoot).get(fileUrl);
|
|
592
|
+
if (!asset)
|
|
593
|
+
return;
|
|
594
|
+
manifest.set(fileUrl, asset);
|
|
595
|
+
if (!asset.public)
|
|
596
|
+
byUrl.set(asset.url, asset);
|
|
597
|
+
return asset;
|
|
598
|
+
}
|
|
599
|
+
return {
|
|
600
|
+
name: "@purview-dev/astroc4/icons",
|
|
601
|
+
enforce: "post",
|
|
602
|
+
transform(code, id) {
|
|
603
|
+
const match = LIKEC4_MODULE.exec(id);
|
|
604
|
+
if (!match)
|
|
605
|
+
return null;
|
|
606
|
+
const project = state.projects.find((p) => p.likec4Id === match.groups.projectId);
|
|
607
|
+
if (!project || !code.includes("file:"))
|
|
608
|
+
return null;
|
|
609
|
+
const rewritten = code.replace(FILE_URL, (found) => {
|
|
610
|
+
const escapedForm = found.includes("\\u002F");
|
|
611
|
+
const [fileUrl, query] = found.replaceAll("\\u002F", "/").split("?", 2);
|
|
612
|
+
const real = unstage(fileUrl, project, state.stagingDir);
|
|
613
|
+
if (query !== undefined) {
|
|
614
|
+
return `${fileURLToPath3(real).replaceAll("\\", "/")}?${query}`;
|
|
615
|
+
}
|
|
616
|
+
const asset = gather(project, real);
|
|
617
|
+
if (!asset)
|
|
618
|
+
return found;
|
|
619
|
+
return escapedForm ? asset.url.replaceAll("/", "\\u002F") : asset.url;
|
|
620
|
+
});
|
|
621
|
+
return rewritten === code ? null : { code: rewritten, map: null };
|
|
622
|
+
},
|
|
623
|
+
configureServer(server) {
|
|
624
|
+
const prefix = `${base}${ICON_ROUTE}`;
|
|
625
|
+
server.middlewares.use(async (req, res, next) => {
|
|
626
|
+
const url = req.url?.split("?")[0];
|
|
627
|
+
if (!url?.startsWith(prefix))
|
|
628
|
+
return next();
|
|
629
|
+
const asset = byUrl.get(url);
|
|
630
|
+
if (!asset) {
|
|
631
|
+
res.statusCode = 404;
|
|
632
|
+
res.end();
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
res.setHeader("Content-Type", CONTENT_TYPES[extname(asset.file).toLowerCase()] ?? "application/octet-stream");
|
|
636
|
+
res.end(await readFile4(asset.file));
|
|
637
|
+
});
|
|
638
|
+
},
|
|
639
|
+
manifest: () => manifest,
|
|
640
|
+
async emit(outDir) {
|
|
641
|
+
for (const asset of byUrl.values()) {
|
|
642
|
+
const target = join3(outDir, ...decodeURIComponent(asset.url.slice(base.length)).split("/"));
|
|
643
|
+
await mkdir(dirname2(target), { recursive: true });
|
|
644
|
+
await copyFile(asset.file, target);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// src/workspace.ts
|
|
651
|
+
import { lstat, mkdir as mkdir2, readdir as readdir2, readlink, realpath, rm, symlink } from "node:fs/promises";
|
|
652
|
+
import { join as join4, resolve as resolve4 } from "node:path";
|
|
653
|
+
var PROJECTS_MODULE_ID = "virtual:astroc4/projects";
|
|
654
|
+
async function prepareWorkspace(projects, cacheDir) {
|
|
655
|
+
const staging = join4(cacheDir, "workspace");
|
|
656
|
+
await mkdir2(staging, { recursive: true });
|
|
657
|
+
const wanted = new Map(projects.map((project) => [project.name, resolve4(project.folder)]));
|
|
658
|
+
for (const entry of await readdir2(staging)) {
|
|
659
|
+
const target = wanted.get(entry);
|
|
660
|
+
if (target === undefined || !await pointsAt(join4(staging, entry), target)) {
|
|
661
|
+
await rm(join4(staging, entry), { recursive: false, force: true });
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
for (const [name, folder] of wanted) {
|
|
665
|
+
const link = join4(staging, name);
|
|
666
|
+
if (await exists(link))
|
|
667
|
+
continue;
|
|
668
|
+
await symlink(folder, link, "junction");
|
|
669
|
+
}
|
|
670
|
+
return staging;
|
|
671
|
+
}
|
|
672
|
+
function projectsModule(projects) {
|
|
673
|
+
const names = projects.map((project) => project.name);
|
|
674
|
+
const ids = Object.fromEntries(projects.map((project) => [project.name, project.likec4Id]));
|
|
675
|
+
return [
|
|
676
|
+
`export const projectNames = ${JSON.stringify(names)};`,
|
|
677
|
+
`export const likec4IdFor = ${JSON.stringify(ids)};`
|
|
678
|
+
].join(`
|
|
679
|
+
`);
|
|
680
|
+
}
|
|
681
|
+
async function pointsAt(link, target) {
|
|
682
|
+
try {
|
|
683
|
+
const stats = await lstat(link);
|
|
684
|
+
if (!stats.isSymbolicLink())
|
|
685
|
+
return false;
|
|
686
|
+
await readlink(link);
|
|
687
|
+
return await realpath(link) === await realpath(target);
|
|
688
|
+
} catch {
|
|
689
|
+
return false;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
async function exists(path) {
|
|
693
|
+
return await lstat(path).catch(() => null) !== null;
|
|
694
|
+
}
|
|
170
695
|
|
|
171
696
|
// src/integration.ts
|
|
172
697
|
var PACKAGE_NAME2 = "@purview-dev/astroc4";
|
|
173
698
|
var OPTIMIZED_DEPENDENCIES = ["react/compiler-runtime"];
|
|
699
|
+
var MD_FENCES_SCRIPT = [
|
|
700
|
+
`const mountLikeC4Fences = () => {`,
|
|
701
|
+
` if (!document.querySelector('[data-astroc4-fence]:not([data-astroc4-mounted])')) return;`,
|
|
702
|
+
` import('${PACKAGE_NAME2}/components/md-fences').then((module) => module.mountFences());`,
|
|
703
|
+
`};`,
|
|
704
|
+
`if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', mountLikeC4Fences);`,
|
|
705
|
+
`else mountLikeC4Fences();`,
|
|
706
|
+
`document.addEventListener('astro:page-load', mountLikeC4Fences);`
|
|
707
|
+
].join(`
|
|
708
|
+
`);
|
|
174
709
|
function missingOptimizedDependencies(include, root) {
|
|
175
710
|
const existing = Array.isArray(include) ? include : [];
|
|
176
711
|
return OPTIMIZED_DEPENDENCIES.filter((dependency) => !existing.includes(dependency) && resolvesFrom(dependency, root));
|
|
177
712
|
}
|
|
178
713
|
function resolvesFrom(specifier, root) {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
714
|
+
const name = specifier.split("/", specifier.startsWith("@") ? 2 : 1).join("/");
|
|
715
|
+
for (let dir = resolve5(root);; dir = dirname3(dir)) {
|
|
716
|
+
if (existsSync(join5(dir, "node_modules", name, "package.json")))
|
|
717
|
+
return true;
|
|
718
|
+
if (dirname3(dir) === dir)
|
|
719
|
+
return false;
|
|
184
720
|
}
|
|
185
721
|
}
|
|
186
722
|
function likec4Integration({
|
|
187
723
|
vitePlugin = {},
|
|
724
|
+
projects,
|
|
188
725
|
fences = true
|
|
189
726
|
} = {}) {
|
|
727
|
+
let icons;
|
|
190
728
|
return {
|
|
191
729
|
name: `${PACKAGE_NAME2}/integration`,
|
|
192
730
|
hooks: {
|
|
193
|
-
"astro:config:setup": async ({ command, config, updateConfig, logger }) => {
|
|
194
|
-
const
|
|
731
|
+
"astro:config:setup": async ({ command, config, updateConfig, injectScript, logger }) => {
|
|
732
|
+
const root = fileURLToPath4(config.root);
|
|
733
|
+
const optimizeDeps = missingOptimizedDependencies(config.vite?.optimizeDeps?.include, root);
|
|
734
|
+
const { pluginOptions, resolved, stagingDir } = await workspaceFor(root, vitePlugin, projects, logger);
|
|
735
|
+
icons = astroc4IconPlugin({ projects: resolved, base: config.base ?? "/", siteRoot: root, stagingDir });
|
|
195
736
|
updateConfig({
|
|
196
737
|
vite: {
|
|
197
|
-
plugins: [LikeC4VitePlugin(
|
|
738
|
+
plugins: [LikeC4VitePlugin(pluginOptions), projectsPlugin(resolved), icons, fileLayoutPlugin()],
|
|
198
739
|
ssr: { noExternal: [PACKAGE_NAME2] },
|
|
199
740
|
...optimizeDeps.length > 0 ? { optimizeDeps: { include: optimizeDeps } } : {}
|
|
200
741
|
}
|
|
201
742
|
});
|
|
202
743
|
if (fences) {
|
|
203
|
-
await registerFences(config, updateConfig, logger);
|
|
744
|
+
await registerFences(config, updateConfig, logger, resolved);
|
|
745
|
+
injectScript("page", MD_FENCES_SCRIPT);
|
|
204
746
|
}
|
|
205
747
|
await ensureReactRenderer(config, updateConfig, logger);
|
|
206
748
|
if (command === "build") {
|
|
207
749
|
await preloadLikeC4();
|
|
208
750
|
}
|
|
751
|
+
},
|
|
752
|
+
"astro:build:done": async ({ dir }) => {
|
|
753
|
+
await icons?.emit(fileURLToPath4(dir));
|
|
209
754
|
}
|
|
210
755
|
}
|
|
211
756
|
};
|
|
212
757
|
}
|
|
758
|
+
async function workspaceFor(root, vitePlugin, configured, logger) {
|
|
759
|
+
const hasConfigured = configured !== undefined && Object.keys(configured).length > 0;
|
|
760
|
+
if (vitePlugin.workspace !== undefined || "languageServices" in vitePlugin) {
|
|
761
|
+
if (hasConfigured) {
|
|
762
|
+
logger.warn("`projects` is ignored because `vitePlugin.workspace` or `vitePlugin.languageServices` is set; remove one of them.");
|
|
763
|
+
}
|
|
764
|
+
return { pluginOptions: vitePlugin, resolved: [] };
|
|
765
|
+
}
|
|
766
|
+
const discovered = await discoverProjects(root);
|
|
767
|
+
if (!hasConfigured) {
|
|
768
|
+
return { pluginOptions: vitePlugin, resolved: discovered };
|
|
769
|
+
}
|
|
770
|
+
const resolved = await resolveProjects({ root, configured, discovered });
|
|
771
|
+
const workspace = await prepareWorkspace(resolved, join5(root, ".astro", "astroc4"));
|
|
772
|
+
return { pluginOptions: { ...vitePlugin, workspace }, resolved, stagingDir: workspace };
|
|
773
|
+
}
|
|
774
|
+
function projectsPlugin(projects) {
|
|
775
|
+
const resolvedId = `\x00${PROJECTS_MODULE_ID}`;
|
|
776
|
+
return {
|
|
777
|
+
name: `${PACKAGE_NAME2}/projects`,
|
|
778
|
+
resolveId: (id) => id === PROJECTS_MODULE_ID ? resolvedId : undefined,
|
|
779
|
+
load: (id) => id === resolvedId ? projectsModule(projects) : undefined
|
|
780
|
+
};
|
|
781
|
+
}
|
|
213
782
|
async function ensureReactRenderer(config, updateConfig, logger) {
|
|
214
783
|
if (config.integrations.some((integration) => integration.name === "@astrojs/react"))
|
|
215
784
|
return;
|
|
@@ -221,16 +790,13 @@ async function ensureReactRenderer(config, updateConfig, logger) {
|
|
|
221
790
|
logger.warn("@astrojs/react is not installed, so <LikeC4View> cannot render. Install it (`astro add react`).");
|
|
222
791
|
}
|
|
223
792
|
}
|
|
224
|
-
async function registerFences(config, updateConfig, logger) {
|
|
793
|
+
async function registerFences(config, updateConfig, logger, projects) {
|
|
225
794
|
const { satteri, isSatteriProcessor } = await import("@astrojs/markdown-satteri");
|
|
226
795
|
const existing = config.markdown?.processor;
|
|
227
|
-
const fences = () => {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
mdx: config.integrations.some((integration) => integration.name === "@astrojs/mdx")
|
|
232
|
-
});
|
|
233
|
-
};
|
|
796
|
+
const fences = () => likec4Fences({
|
|
797
|
+
root: fileURLToPath4(config.root),
|
|
798
|
+
projects: projects.length > 0 ? projects.map((project) => project.name) : undefined
|
|
799
|
+
});
|
|
234
800
|
if (existing === undefined) {
|
|
235
801
|
updateConfig({ markdown: { processor: satteri({ mdastPlugins: [fences()] }) } });
|
|
236
802
|
return;
|
|
@@ -257,5 +823,5 @@ export {
|
|
|
257
823
|
missingOptimizedDependencies
|
|
258
824
|
};
|
|
259
825
|
|
|
260
|
-
//# debugId=
|
|
826
|
+
//# debugId=6B1EB5C52F4EEAE864756E2164756E21
|
|
261
827
|
//# sourceMappingURL=integration.js.map
|