@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 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 likec4Fences({ root, projects } = {}) {
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
- const source = options.file ? await readFenceFile(options.file, ctx.fileURL, root, where) : (node.value ?? "").trim();
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
- throw new Error(`${where} is empty, so it needs \`view="<id>"\` to name a view in the project model.`);
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
- throw new Error(`${where} is not valid LikeC4 — ${error instanceof Error ? error.message : String(error)}`);
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
- throw new Error(`${where} has no view "${viewId ?? "(none)"}". Declared: ${layout.viewIds.join(", ") || "(none)"}.`);
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 checkProject(project, projects, where) {
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
- if (projects.length > 1) {
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
- throw new Error(`${where}: ${STRINGS["likec4.error.unknownProject"].replace("{projectId}", requested).replace("{available}", available || "(none)")}`);
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
- projects: projects.length > 0 ? projects.map((project) => project.name) : undefined
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=B9FCAC3D36CE7F6464756E2164756E21
1815
+ //# debugId=4A4B67FAE347BDD864756E2164756E21
1738
1816
  //# sourceMappingURL=index.js.map