@purview-dev/astroc4 1.0.0-prerelease.7 → 1.0.0-prerelease.8
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 +1 -0
- package/dist/fences.d.ts +13 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +96 -18
- package/dist/index.js.map +7 -6
- package/dist/integration.d.ts +11 -1
- package/dist/integration.js +94 -17
- package/dist/integration.js.map +6 -5
- package/dist/validation.d.ts +27 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -145,6 +145,7 @@ reload in `astro dev` like any other.
|
|
|
145
145
|
| `projects` | `{}` | Named LikeC4 project folders, relative to the site root or absolute. See above. |
|
|
146
146
|
| `vitePlugin` | `{}` | Passed to `LikeC4VitePlugin`. Setting `workspace` or `languageServices` takes discovery over: `projects` is then ignored (with a warning). |
|
|
147
147
|
| `fences` | `true` | Turn ` ```likec4 ` fences into diagrams. |
|
|
148
|
+
| `validation` | `'strict'` | Whether a fence that cannot be drawn (bad DSL, missing view, bad `project`, unresolved `file=`) fails the build or only warns. `'relaxed'` warns and leaves it as a highlighted code block; `{ default, collections, relaxed: [globs] }` scopes it by collection or path. See [Fences](docs/wiki/guide/Fences.md#partial-and-invalid-examples). |
|
|
148
149
|
| `highlighting` | `true` | Highlight LikeC4 source. Starlight only — a plain Astro site passes `likec4Language` to Expressive Code. See below. |
|
|
149
150
|
|
|
150
151
|
### Syntax highlighting
|
package/dist/fences.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { MdastPluginEntry } from 'satteri';
|
|
2
|
+
import { type ValidationOptions } from './validation.js';
|
|
2
3
|
type LikeC4Api = typeof import('likec4').LikeC4;
|
|
3
4
|
/**
|
|
4
5
|
* Finishes loading LikeC4 before the first page renders, so the load cannot race
|
|
@@ -39,8 +40,19 @@ export interface FenceOptions {
|
|
|
39
40
|
* `project` is checked at build time; without it, only the browser can.
|
|
40
41
|
*/
|
|
41
42
|
projects?: string[];
|
|
43
|
+
/**
|
|
44
|
+
* How a fence that cannot be turned into a diagram is treated. `strict` (the
|
|
45
|
+
* default) fails the build and `astro check`; `relaxed` logs a warning and
|
|
46
|
+
* leaves the fence as a highlighted code block. Relaxing can be scoped by
|
|
47
|
+
* page path glob or content collection. See `./validation.ts`.
|
|
48
|
+
*/
|
|
49
|
+
validation?: ValidationOptions;
|
|
50
|
+
/** The site's content directory, so a page's collection can be resolved. */
|
|
51
|
+
contentDir?: string;
|
|
52
|
+
/** Called with the reason for each fence `relaxed` validation leaves as a code block. */
|
|
53
|
+
onInvalid?: (message: string) => void;
|
|
42
54
|
}
|
|
43
|
-
export declare function likec4Fences({ root, projects }?: FenceOptions): MdastPluginEntry;
|
|
55
|
+
export declare function likec4Fences({ root, projects, validation, contentDir, onInvalid }?: FenceOptions): MdastPluginEntry;
|
|
44
56
|
export interface FencePlaceholder {
|
|
45
57
|
viewId: string;
|
|
46
58
|
/** The laid-out model of an inline or `file=` fence; absent for a project view. */
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { AstroIntegration } from 'astro';
|
|
|
3
3
|
import { missingOptimizedDependencies } from './integration.js';
|
|
4
4
|
import type { LikeC4IntegrationOptions } from './integration.js';
|
|
5
5
|
export { missingOptimizedDependencies };
|
|
6
|
+
export type { ValidationMode, ValidationOptions, ValidationScope } from './validation.js';
|
|
6
7
|
export interface LikeC4HighlightingOptions {
|
|
7
8
|
/** Branch, tag or commit SHA of `likec4/likec4`. Defaults to `main`; also `ASTROC4_REF`. */
|
|
8
9
|
ref?: string;
|
package/dist/index.js
CHANGED
|
@@ -37,6 +37,45 @@ var translations = {
|
|
|
37
37
|
}
|
|
38
38
|
};
|
|
39
39
|
|
|
40
|
+
// src/validation.ts
|
|
41
|
+
function globToRegExp(pattern) {
|
|
42
|
+
let body = "";
|
|
43
|
+
for (let index = 0;index < pattern.length; index += 1) {
|
|
44
|
+
const char = pattern[index];
|
|
45
|
+
if (char === "*") {
|
|
46
|
+
if (pattern[index + 1] === "*") {
|
|
47
|
+
body += ".*";
|
|
48
|
+
index += 1;
|
|
49
|
+
} else {
|
|
50
|
+
body += "[^/]*";
|
|
51
|
+
}
|
|
52
|
+
} else if (char === "?") {
|
|
53
|
+
body += "[^/]";
|
|
54
|
+
} else if (".+^${}()|[]\\".includes(char)) {
|
|
55
|
+
body += `\\${char}`;
|
|
56
|
+
} else {
|
|
57
|
+
body += char;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return new RegExp(`^(?:.*/)?${body}$`);
|
|
61
|
+
}
|
|
62
|
+
var toSlashes = (value) => value.replaceAll("\\", "/");
|
|
63
|
+
function validationModeFor(options, relativePath, collection) {
|
|
64
|
+
if (options === undefined)
|
|
65
|
+
return "strict";
|
|
66
|
+
if (typeof options === "string")
|
|
67
|
+
return options;
|
|
68
|
+
const fallback = options.default ?? "strict";
|
|
69
|
+
const collections = options.collections === undefined ? [] : [options.collections].flat();
|
|
70
|
+
if (collection !== undefined && collections.includes(collection))
|
|
71
|
+
return "relaxed";
|
|
72
|
+
if (relativePath === undefined)
|
|
73
|
+
return fallback;
|
|
74
|
+
const patterns = options.relaxed === undefined ? [] : [options.relaxed].flat();
|
|
75
|
+
const path = toSlashes(relativePath);
|
|
76
|
+
return patterns.some((pattern) => globToRegExp(pattern).test(path)) ? "relaxed" : fallback;
|
|
77
|
+
}
|
|
78
|
+
|
|
40
79
|
// src/fences.ts
|
|
41
80
|
var require2 = createRequire(import.meta.url);
|
|
42
81
|
var likec4ApiPromise;
|
|
@@ -56,6 +95,7 @@ var IMPORTS = {
|
|
|
56
95
|
[PROJECT_COMPONENT]: `${PACKAGE_NAME}/components/LikeC4View.astro`
|
|
57
96
|
};
|
|
58
97
|
var STRINGS = translations.en;
|
|
98
|
+
var RELAXED_NOTE = "Left as a highlighted code block because this page is validated in `relaxed` mode.";
|
|
59
99
|
var layoutCache = new Map;
|
|
60
100
|
function parseFenceMeta(meta) {
|
|
61
101
|
const options = {};
|
|
@@ -123,7 +163,23 @@ function describe(fileURL) {
|
|
|
123
163
|
return String(fileURL);
|
|
124
164
|
}
|
|
125
165
|
}
|
|
126
|
-
function
|
|
166
|
+
function pagePath(fileURL, root) {
|
|
167
|
+
if (!fileURL || !root)
|
|
168
|
+
return;
|
|
169
|
+
try {
|
|
170
|
+
return relative(resolve(root), fileURLToPath(fileURL));
|
|
171
|
+
} catch {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function collectionOf(fileURL, contentDir) {
|
|
176
|
+
const within = pagePath(fileURL, contentDir);
|
|
177
|
+
if (!within || within.startsWith(".."))
|
|
178
|
+
return;
|
|
179
|
+
const [first, ...rest] = within.split(/[\\/]/);
|
|
180
|
+
return rest.length > 0 ? first : undefined;
|
|
181
|
+
}
|
|
182
|
+
function likec4Fences({ root, projects, validation, contentDir, onInvalid } = {}) {
|
|
127
183
|
return (factory) => {
|
|
128
184
|
if (!factory.source.includes("```" + FENCE_LANG))
|
|
129
185
|
return null;
|
|
@@ -134,9 +190,21 @@ function likec4Fences({ root, projects } = {}) {
|
|
|
134
190
|
async code(node, ctx) {
|
|
135
191
|
if (node.lang !== FENCE_LANG)
|
|
136
192
|
return;
|
|
193
|
+
const mode = validationModeFor(validation, pagePath(ctx.fileURL, root), collectionOf(ctx.fileURL, contentDir));
|
|
194
|
+
const fail = (reason) => {
|
|
195
|
+
if (mode === "strict")
|
|
196
|
+
throw new Error(reason);
|
|
197
|
+
onInvalid?.(`${reason} ${RELAXED_NOTE}`);
|
|
198
|
+
};
|
|
137
199
|
const where = `${describe(ctx.fileURL)}: \`\`\`${FENCE_LANG} fence`;
|
|
138
200
|
const options = parseFenceMeta(node.meta);
|
|
139
|
-
|
|
201
|
+
let source;
|
|
202
|
+
try {
|
|
203
|
+
source = options.file ? await readFenceFile(options.file, ctx.fileURL, root, where) : (node.value ?? "").trim();
|
|
204
|
+
} catch (error) {
|
|
205
|
+
fail(error instanceof Error ? error.message : String(error));
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
140
208
|
const common = {
|
|
141
209
|
title: options.title,
|
|
142
210
|
description: options.description,
|
|
@@ -146,9 +214,14 @@ function likec4Fences({ root, projects } = {}) {
|
|
|
146
214
|
};
|
|
147
215
|
if (source.length === 0) {
|
|
148
216
|
if (!options.view) {
|
|
149
|
-
|
|
217
|
+
fail(`${where} is empty, so it needs \`view="<id>"\` to name a view in the project model.`);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const projectFailure = projectError(options.project, projects, where);
|
|
221
|
+
if (projectFailure !== undefined) {
|
|
222
|
+
fail(projectFailure);
|
|
223
|
+
return;
|
|
150
224
|
}
|
|
151
|
-
checkProject(options.project, projects, where);
|
|
152
225
|
if (markdown) {
|
|
153
226
|
ctx.replaceNode(node, {
|
|
154
227
|
raw: fencePlaceholder({ ...placeholderOptions(options), viewId: options.view, project: options.project }),
|
|
@@ -169,11 +242,13 @@ function likec4Fences({ root, projects } = {}) {
|
|
|
169
242
|
try {
|
|
170
243
|
layout = await layoutSource(source);
|
|
171
244
|
} catch (error) {
|
|
172
|
-
|
|
245
|
+
fail(`${where} is not valid LikeC4 — ${error instanceof Error ? error.message : String(error)}`);
|
|
246
|
+
return;
|
|
173
247
|
}
|
|
174
248
|
const viewId = options.view ?? defaultView(source, layout.viewIds);
|
|
175
249
|
if (!viewId || !layout.viewIds.includes(viewId)) {
|
|
176
|
-
|
|
250
|
+
fail(`${where} has no view "${viewId ?? "(none)"}". Declared: ${layout.viewIds.join(", ") || "(none)"}.`);
|
|
251
|
+
return;
|
|
177
252
|
}
|
|
178
253
|
if (markdown) {
|
|
179
254
|
ctx.replaceNode(node, {
|
|
@@ -198,20 +273,18 @@ function likec4Fences({ root, projects } = {}) {
|
|
|
198
273
|
};
|
|
199
274
|
};
|
|
200
275
|
}
|
|
201
|
-
function
|
|
276
|
+
function projectError(project, projects, where) {
|
|
202
277
|
if (projects === undefined)
|
|
203
278
|
return;
|
|
204
279
|
const requested = project?.trim();
|
|
205
280
|
const available = projects.join(", ");
|
|
206
281
|
if (!requested) {
|
|
207
|
-
|
|
208
|
-
throw new Error(`${where}: ${STRINGS["likec4.error.projectRequired"].replace("{available}", available)}`);
|
|
209
|
-
}
|
|
210
|
-
return;
|
|
282
|
+
return projects.length > 1 ? `${where}: ${STRINGS["likec4.error.projectRequired"].replace("{available}", available)}` : undefined;
|
|
211
283
|
}
|
|
212
284
|
if (!projects.includes(requested)) {
|
|
213
|
-
|
|
285
|
+
return `${where}: ${STRINGS["likec4.error.unknownProject"].replace("{projectId}", requested).replace("{available}", available || "(none)")}`;
|
|
214
286
|
}
|
|
287
|
+
return;
|
|
215
288
|
}
|
|
216
289
|
function defaultView(source, viewIds) {
|
|
217
290
|
for (const match of source.matchAll(/(?<![\w-])view\s+([\w-]+)/g)) {
|
|
@@ -728,7 +801,8 @@ function resolvesFrom(specifier, root) {
|
|
|
728
801
|
function likec4Integration({
|
|
729
802
|
vitePlugin = {},
|
|
730
803
|
projects,
|
|
731
|
-
fences = true
|
|
804
|
+
fences = true,
|
|
805
|
+
validation
|
|
732
806
|
} = {}) {
|
|
733
807
|
let icons;
|
|
734
808
|
return {
|
|
@@ -747,7 +821,7 @@ function likec4Integration({
|
|
|
747
821
|
}
|
|
748
822
|
});
|
|
749
823
|
if (fences) {
|
|
750
|
-
await registerFences(config, updateConfig, logger, resolved);
|
|
824
|
+
await registerFences(config, updateConfig, logger, resolved, validation);
|
|
751
825
|
injectScript("page", MD_FENCES_SCRIPT);
|
|
752
826
|
}
|
|
753
827
|
await ensureReactRenderer(config, updateConfig, logger);
|
|
@@ -796,12 +870,15 @@ async function ensureReactRenderer(config, updateConfig, logger) {
|
|
|
796
870
|
logger.warn("@astrojs/react is not installed, so <LikeC4View> cannot render. Install it (`astro add react`).");
|
|
797
871
|
}
|
|
798
872
|
}
|
|
799
|
-
async function registerFences(config, updateConfig, logger, projects) {
|
|
873
|
+
async function registerFences(config, updateConfig, logger, projects, validation) {
|
|
800
874
|
const { satteri, isSatteriProcessor } = await import("@astrojs/markdown-satteri");
|
|
801
875
|
const existing = config.markdown?.processor;
|
|
802
876
|
const fences = () => likec4Fences({
|
|
803
877
|
root: fileURLToPath4(config.root),
|
|
804
|
-
|
|
878
|
+
contentDir: config.srcDir ? fileURLToPath4(new URL("content/", config.srcDir)) : undefined,
|
|
879
|
+
projects: projects.length > 0 ? projects.map((project) => project.name) : undefined,
|
|
880
|
+
validation,
|
|
881
|
+
onInvalid: (message) => logger.warn(message)
|
|
805
882
|
});
|
|
806
883
|
if (existing === undefined) {
|
|
807
884
|
updateConfig({ markdown: { processor: satteri({ mdastPlugins: [fences()] }) } });
|
|
@@ -1648,7 +1725,8 @@ function starlightLikeC4(options = {}) {
|
|
|
1648
1725
|
const integration = likec4Integration({
|
|
1649
1726
|
vitePlugin: options.vitePlugin,
|
|
1650
1727
|
projects: options.projects,
|
|
1651
|
-
fences: options.fences
|
|
1728
|
+
fences: options.fences,
|
|
1729
|
+
validation: options.validation
|
|
1652
1730
|
});
|
|
1653
1731
|
return {
|
|
1654
1732
|
name: PACKAGE_NAME2,
|
|
@@ -1734,5 +1812,5 @@ export {
|
|
|
1734
1812
|
withLikeC4Language
|
|
1735
1813
|
};
|
|
1736
1814
|
|
|
1737
|
-
//# debugId=
|
|
1815
|
+
//# debugId=4A4B67FAE347BDD864756E2164756E21
|
|
1738
1816
|
//# sourceMappingURL=index.js.map
|