@purview-dev/astroc4 1.0.0-prerelease.6 → 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 +100 -19
- package/dist/index.js.map +7 -6
- package/dist/integration.d.ts +11 -1
- package/dist/integration.js +98 -18
- 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)) {
|
|
@@ -702,7 +775,10 @@ var OPTIMIZED_DEPENDENCIES = ["react/compiler-runtime"];
|
|
|
702
775
|
var MD_FENCES_SCRIPT = [
|
|
703
776
|
`const mountLikeC4Fences = () => {`,
|
|
704
777
|
` if (!document.querySelector('[data-astroc4-fence]:not([data-astroc4-mounted])')) return;`,
|
|
705
|
-
`
|
|
778
|
+
` const preamble = window.$RefreshReg$ ? Promise.resolve() : import('@vitejs/plugin-react/preamble');`,
|
|
779
|
+
` preamble`,
|
|
780
|
+
` .then(() => import('${PACKAGE_NAME2}/components/md-fences'))`,
|
|
781
|
+
` .then((module) => module.mountFences());`,
|
|
706
782
|
`};`,
|
|
707
783
|
`if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', mountLikeC4Fences);`,
|
|
708
784
|
`else mountLikeC4Fences();`,
|
|
@@ -725,7 +801,8 @@ function resolvesFrom(specifier, root) {
|
|
|
725
801
|
function likec4Integration({
|
|
726
802
|
vitePlugin = {},
|
|
727
803
|
projects,
|
|
728
|
-
fences = true
|
|
804
|
+
fences = true,
|
|
805
|
+
validation
|
|
729
806
|
} = {}) {
|
|
730
807
|
let icons;
|
|
731
808
|
return {
|
|
@@ -744,7 +821,7 @@ function likec4Integration({
|
|
|
744
821
|
}
|
|
745
822
|
});
|
|
746
823
|
if (fences) {
|
|
747
|
-
await registerFences(config, updateConfig, logger, resolved);
|
|
824
|
+
await registerFences(config, updateConfig, logger, resolved, validation);
|
|
748
825
|
injectScript("page", MD_FENCES_SCRIPT);
|
|
749
826
|
}
|
|
750
827
|
await ensureReactRenderer(config, updateConfig, logger);
|
|
@@ -793,12 +870,15 @@ async function ensureReactRenderer(config, updateConfig, logger) {
|
|
|
793
870
|
logger.warn("@astrojs/react is not installed, so <LikeC4View> cannot render. Install it (`astro add react`).");
|
|
794
871
|
}
|
|
795
872
|
}
|
|
796
|
-
async function registerFences(config, updateConfig, logger, projects) {
|
|
873
|
+
async function registerFences(config, updateConfig, logger, projects, validation) {
|
|
797
874
|
const { satteri, isSatteriProcessor } = await import("@astrojs/markdown-satteri");
|
|
798
875
|
const existing = config.markdown?.processor;
|
|
799
876
|
const fences = () => likec4Fences({
|
|
800
877
|
root: fileURLToPath4(config.root),
|
|
801
|
-
|
|
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)
|
|
802
882
|
});
|
|
803
883
|
if (existing === undefined) {
|
|
804
884
|
updateConfig({ markdown: { processor: satteri({ mdastPlugins: [fences()] }) } });
|
|
@@ -1645,7 +1725,8 @@ function starlightLikeC4(options = {}) {
|
|
|
1645
1725
|
const integration = likec4Integration({
|
|
1646
1726
|
vitePlugin: options.vitePlugin,
|
|
1647
1727
|
projects: options.projects,
|
|
1648
|
-
fences: options.fences
|
|
1728
|
+
fences: options.fences,
|
|
1729
|
+
validation: options.validation
|
|
1649
1730
|
});
|
|
1650
1731
|
return {
|
|
1651
1732
|
name: PACKAGE_NAME2,
|
|
@@ -1731,5 +1812,5 @@ export {
|
|
|
1731
1812
|
withLikeC4Language
|
|
1732
1813
|
};
|
|
1733
1814
|
|
|
1734
|
-
//# debugId=
|
|
1815
|
+
//# debugId=4A4B67FAE347BDD864756E2164756E21
|
|
1735
1816
|
//# sourceMappingURL=index.js.map
|