@purview-dev/astroc4 1.0.0-prerelease.4 → 1.0.0-prerelease.5
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 +101 -16
- package/dist/fences.d.ts +15 -1
- package/dist/index.d.ts +25 -10
- package/dist/index.js +118 -56
- package/dist/index.js.map +6 -5
- package/dist/integration.d.ts +37 -0
- package/dist/integration.js +261 -0
- package/dist/integration.js.map +11 -0
- package/package.json +17 -2
- package/src/components/DiagramFrame.astro +13 -5
- package/src/components/DiagramShell.astro +18 -7
- package/src/components/LikeC4Fence.astro +2 -1
- package/src/components/LikeC4View.astro +13 -4
- package/src/components/LoadingPlaceholder.astro +10 -4
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
// src/integration.ts
|
|
2
|
+
import { createRequire as createRequire2 } from "node:module";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
5
|
+
import { LikeC4VitePlugin } from "likec4/vite-plugin";
|
|
6
|
+
|
|
7
|
+
// src/fences.ts
|
|
8
|
+
import { createHash } from "node:crypto";
|
|
9
|
+
import { createRequire } from "node:module";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
var require2 = createRequire(import.meta.url);
|
|
12
|
+
var likec4ApiPromise;
|
|
13
|
+
function loadLikeC4() {
|
|
14
|
+
likec4ApiPromise ??= Promise.resolve(require2("likec4").LikeC4);
|
|
15
|
+
return likec4ApiPromise;
|
|
16
|
+
}
|
|
17
|
+
function preloadLikeC4() {
|
|
18
|
+
return loadLikeC4();
|
|
19
|
+
}
|
|
20
|
+
var FENCE_LANG = "likec4";
|
|
21
|
+
var INLINE_COMPONENT = "__LikeC4Fence";
|
|
22
|
+
var PROJECT_COMPONENT = "__LikeC4View";
|
|
23
|
+
var PACKAGE_NAME = "@purview-dev/astroc4";
|
|
24
|
+
var IMPORTS = {
|
|
25
|
+
[INLINE_COMPONENT]: `${PACKAGE_NAME}/components/LikeC4Fence.astro`,
|
|
26
|
+
[PROJECT_COMPONENT]: `${PACKAGE_NAME}/components/LikeC4View.astro`
|
|
27
|
+
};
|
|
28
|
+
var layoutCache = new Map;
|
|
29
|
+
function parseFenceMeta(meta) {
|
|
30
|
+
const options = {};
|
|
31
|
+
if (!meta)
|
|
32
|
+
return options;
|
|
33
|
+
const pattern = /(\w+)=(?:"([^"]*)"|'([^']*)'|(\S+))/g;
|
|
34
|
+
let match;
|
|
35
|
+
while ((match = pattern.exec(meta)) !== null) {
|
|
36
|
+
const key = match[1];
|
|
37
|
+
if (!key)
|
|
38
|
+
continue;
|
|
39
|
+
options[key] = match[2] ?? match[3] ?? match[4] ?? "";
|
|
40
|
+
}
|
|
41
|
+
return options;
|
|
42
|
+
}
|
|
43
|
+
async function layoutSnippet(source) {
|
|
44
|
+
const key = createHash("sha256").update(source).digest("hex");
|
|
45
|
+
const cached = layoutCache.get(key);
|
|
46
|
+
if (cached)
|
|
47
|
+
return cached;
|
|
48
|
+
const LikeC4 = await loadLikeC4();
|
|
49
|
+
const likec4 = await LikeC4.fromSource(source);
|
|
50
|
+
if (likec4.hasErrors()) {
|
|
51
|
+
const detail = likec4.getErrors().map((error) => String(error.message).split(`
|
|
52
|
+
`)[0]?.trim() ?? "").slice(0, 3).join("; ");
|
|
53
|
+
throw new Error(detail || "the snippet could not be parsed");
|
|
54
|
+
}
|
|
55
|
+
const model = await likec4.layoutedModel();
|
|
56
|
+
const dump = JSON.parse(JSON.stringify(model.$data));
|
|
57
|
+
const result = { dump, viewIds: Object.keys(dump.views ?? {}) };
|
|
58
|
+
layoutCache.set(key, result);
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
function importNode(local, source) {
|
|
62
|
+
return {
|
|
63
|
+
type: "mdxjsEsm",
|
|
64
|
+
value: `import ${local} from ${JSON.stringify(source)};`,
|
|
65
|
+
data: {
|
|
66
|
+
estree: {
|
|
67
|
+
type: "Program",
|
|
68
|
+
sourceType: "module",
|
|
69
|
+
body: [
|
|
70
|
+
{
|
|
71
|
+
type: "ImportDeclaration",
|
|
72
|
+
specifiers: [
|
|
73
|
+
{ type: "ImportDefaultSpecifier", local: { type: "Identifier", name: local } }
|
|
74
|
+
],
|
|
75
|
+
source: { type: "Literal", value: source, raw: JSON.stringify(source) },
|
|
76
|
+
attributes: []
|
|
77
|
+
}
|
|
78
|
+
]
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function attributes(pairs) {
|
|
84
|
+
return Object.entries(pairs).filter(([, value]) => value !== undefined && value !== null && value !== "").map(([name, value]) => ({ type: "mdxJsxAttribute", name, value: String(value) }));
|
|
85
|
+
}
|
|
86
|
+
function describe(fileURL) {
|
|
87
|
+
if (!fileURL)
|
|
88
|
+
return "a content file";
|
|
89
|
+
try {
|
|
90
|
+
return fileURLToPath(fileURL);
|
|
91
|
+
} catch {
|
|
92
|
+
return String(fileURL);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
var CONSOLE_REPORTER = {
|
|
96
|
+
warn: (message) => console.warn(`[likec4-fences] ${message}`)
|
|
97
|
+
};
|
|
98
|
+
function likec4Fences({ reporter = CONSOLE_REPORTER, mdx = true } = {}) {
|
|
99
|
+
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
|
+
if (!factory.source.includes("```" + FENCE_LANG))
|
|
114
|
+
return null;
|
|
115
|
+
const used = new Set;
|
|
116
|
+
return {
|
|
117
|
+
name: "likec4-fences",
|
|
118
|
+
async code(node, ctx) {
|
|
119
|
+
if (node.lang !== FENCE_LANG)
|
|
120
|
+
return;
|
|
121
|
+
const where = `${describe(ctx.fileURL)}: \`\`\`${FENCE_LANG} fence`;
|
|
122
|
+
const options = parseFenceMeta(node.meta);
|
|
123
|
+
const source = (node.value ?? "").trim();
|
|
124
|
+
const common = {
|
|
125
|
+
title: options.title,
|
|
126
|
+
description: options.description,
|
|
127
|
+
height: options.height,
|
|
128
|
+
collapsible: options.collapsible,
|
|
129
|
+
defaultOpen: options.defaultOpen
|
|
130
|
+
};
|
|
131
|
+
if (source.length === 0) {
|
|
132
|
+
if (!options.view) {
|
|
133
|
+
throw new Error(`${where} is empty, so it needs \`view="<id>"\` to name a view in the project model.`);
|
|
134
|
+
}
|
|
135
|
+
used.add(PROJECT_COMPONENT);
|
|
136
|
+
ctx.replaceNode(node, {
|
|
137
|
+
type: "mdxJsxFlowElement",
|
|
138
|
+
name: PROJECT_COMPONENT,
|
|
139
|
+
attributes: attributes({ ...common, viewId: options.view }),
|
|
140
|
+
children: []
|
|
141
|
+
});
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
let layout;
|
|
145
|
+
try {
|
|
146
|
+
layout = await layoutSnippet(source);
|
|
147
|
+
} catch (error) {
|
|
148
|
+
throw new Error(`${where} is not valid LikeC4 — ${error instanceof Error ? error.message : String(error)}`);
|
|
149
|
+
}
|
|
150
|
+
const viewId = options.view ?? layout.viewIds[0];
|
|
151
|
+
if (!viewId || !layout.viewIds.includes(viewId)) {
|
|
152
|
+
throw new Error(`${where} has no view "${viewId ?? "(none)"}". Declared: ${layout.viewIds.join(", ") || "(none)"}.`);
|
|
153
|
+
}
|
|
154
|
+
used.add(INLINE_COMPONENT);
|
|
155
|
+
ctx.replaceNode(node, {
|
|
156
|
+
type: "mdxJsxFlowElement",
|
|
157
|
+
name: INLINE_COMPONENT,
|
|
158
|
+
attributes: attributes({ ...common, viewId, model: JSON.stringify(layout.dump) }),
|
|
159
|
+
children: []
|
|
160
|
+
});
|
|
161
|
+
},
|
|
162
|
+
after(root, ctx) {
|
|
163
|
+
if (used.size === 0)
|
|
164
|
+
return;
|
|
165
|
+
ctx.insertChildAt(root, 0, [...used].map((local) => importNode(local, IMPORTS[local])));
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// src/integration.ts
|
|
172
|
+
var PACKAGE_NAME2 = "@purview-dev/astroc4";
|
|
173
|
+
var OPTIMIZED_DEPENDENCIES = ["react/compiler-runtime"];
|
|
174
|
+
function missingOptimizedDependencies(include, root) {
|
|
175
|
+
const existing = Array.isArray(include) ? include : [];
|
|
176
|
+
return OPTIMIZED_DEPENDENCIES.filter((dependency) => !existing.includes(dependency) && resolvesFrom(dependency, root));
|
|
177
|
+
}
|
|
178
|
+
function resolvesFrom(specifier, root) {
|
|
179
|
+
try {
|
|
180
|
+
createRequire2(join(root, "package.json")).resolve(specifier);
|
|
181
|
+
return true;
|
|
182
|
+
} catch {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function likec4Integration({
|
|
187
|
+
vitePlugin = {},
|
|
188
|
+
fences = true
|
|
189
|
+
} = {}) {
|
|
190
|
+
return {
|
|
191
|
+
name: `${PACKAGE_NAME2}/integration`,
|
|
192
|
+
hooks: {
|
|
193
|
+
"astro:config:setup": async ({ command, config, updateConfig, logger }) => {
|
|
194
|
+
const optimizeDeps = missingOptimizedDependencies(config.vite?.optimizeDeps?.include, fileURLToPath2(config.root));
|
|
195
|
+
updateConfig({
|
|
196
|
+
vite: {
|
|
197
|
+
plugins: [LikeC4VitePlugin(vitePlugin)],
|
|
198
|
+
ssr: { noExternal: [PACKAGE_NAME2] },
|
|
199
|
+
...optimizeDeps.length > 0 ? { optimizeDeps: { include: optimizeDeps } } : {}
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
if (fences) {
|
|
203
|
+
await registerFences(config, updateConfig, logger);
|
|
204
|
+
}
|
|
205
|
+
await ensureReactRenderer(config, updateConfig, logger);
|
|
206
|
+
if (command === "build") {
|
|
207
|
+
await preloadLikeC4();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
async function ensureReactRenderer(config, updateConfig, logger) {
|
|
214
|
+
if (config.integrations.some((integration) => integration.name === "@astrojs/react"))
|
|
215
|
+
return;
|
|
216
|
+
try {
|
|
217
|
+
const { default: react } = await import("@astrojs/react");
|
|
218
|
+
updateConfig({ integrations: [react()] });
|
|
219
|
+
logger.info('added @astrojs/react: the LikeC4 diagrams are React islands (client:only="react").');
|
|
220
|
+
} catch {
|
|
221
|
+
logger.warn("@astrojs/react is not installed, so <LikeC4View> cannot render. Install it (`astro add react`).");
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
async function registerFences(config, updateConfig, logger) {
|
|
225
|
+
const { satteri, isSatteriProcessor } = await import("@astrojs/markdown-satteri");
|
|
226
|
+
const existing = config.markdown?.processor;
|
|
227
|
+
const fences = () => {
|
|
228
|
+
const fenceLogger = logger.fork("likec4-fences");
|
|
229
|
+
return likec4Fences({
|
|
230
|
+
reporter: { warn: (message) => fenceLogger.warn(message) },
|
|
231
|
+
mdx: config.integrations.some((integration) => integration.name === "@astrojs/mdx")
|
|
232
|
+
});
|
|
233
|
+
};
|
|
234
|
+
if (existing === undefined) {
|
|
235
|
+
updateConfig({ markdown: { processor: satteri({ mdastPlugins: [fences()] }) } });
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
if (!isSatteriProcessor(existing)) {
|
|
239
|
+
logger.warn("```likec4 fences are disabled: the site is not using the Sätteri Markdown processor. " + "<LikeC4View /> still works.");
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
const existingOptions = existing.options ?? {};
|
|
243
|
+
updateConfig({
|
|
244
|
+
markdown: {
|
|
245
|
+
processor: satteri({
|
|
246
|
+
...existingOptions,
|
|
247
|
+
mdastPlugins: [...existingOptions.mdastPlugins ?? [], fences()]
|
|
248
|
+
})
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
var integration_default = likec4Integration;
|
|
253
|
+
export {
|
|
254
|
+
PACKAGE_NAME2 as PACKAGE_NAME,
|
|
255
|
+
integration_default as default,
|
|
256
|
+
likec4Integration,
|
|
257
|
+
missingOptimizedDependencies
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
//# debugId=C86C94AAF98979CF64756E2164756E21
|
|
261
|
+
//# sourceMappingURL=integration.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/integration.ts", "../src/fences.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"// integration.ts\n// path: src/integration.ts\n// The Astro integration: everything `<LikeC4View>` and ```likec4 fences need,\n// with no Starlight in the picture.\n//\n// import astroc4 from '@purview-dev/astroc4/integration';\n// export default defineConfig({ integrations: [astroc4(), react()] });\n//\n// `src/index.ts` is the Starlight plugin, and it is this integration plus the\n// three things that only exist inside Starlight: the UI-string injection\n// (`i18n:setup`), Starlight's `expressiveCode` option, and Starlight's\n// `config:setup` context (which is where `astroConfig` and its plugins array\n// live). Both entry points call *this* file, so the Vite plugin, the fence\n// transform, the Windows preload and the `react/compiler-runtime` fix cannot\n// drift between them.\n//\n// A plain Astro site still supplies, because they are not this package's to add:\n// - `@astrojs/react` — the diagrams are React islands (`client:only=\"react\"`);\n// - `@astrojs/mdx` — ```likec4 fences only become diagrams in .mdx pages;\n// - Expressive Code (`astro-expressive-code` + `ec.config.mjs`) with\n// `likec4Language` from `@purview-dev/astroc4/expressive-code` — Astro has no\n// `expressiveCode` option for this integration to fill in, which is why\n// highlighting is Starlight-only unless the site configures EC itself.\n\nimport { createRequire } from 'node:module';\nimport { join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport type { AstroConfig, AstroIntegration, AstroIntegrationLogger } from 'astro';\nimport { LikeC4VitePlugin } from 'likec4/vite-plugin';\n\nimport { likec4Fences, preloadLikeC4 } from './fences.js';\n\n/**\n * The published package name. Scoped because npm refuses the unscoped\n * `astroc4` — \"too similar to existing package astro\" — and because the scope\n * makes the specifiers this plugin writes into a site's pages unambiguous.\n */\nexport const PACKAGE_NAME = '@purview-dev/astroc4';\n\nexport interface LikeC4IntegrationOptions {\n\t/**\n\t * Passed through to `LikeC4VitePlugin` — e.g. `{ workspace: 'diagrams' }` to\n\t * look for .c4 files somewhere other than the site root.\n\t */\n\tvitePlugin?: Parameters<typeof LikeC4VitePlugin>[0];\n\t/** Render ```likec4 fences in .mdx pages as diagrams. Defaults to `true`. */\n\tfences?: boolean;\n}\n\n/**\n * Dependencies the browser entry point needs Vite to pre-bundle.\n *\n * `likec4/react` is compiled with the React Compiler, so it imports `c` from\n * `react/compiler-runtime` — a CommonJS file (`module.exports = require(...)`)\n * on every React 19. The island itself is source in this package, so in dev\n * Vite can serve that import raw instead of rewriting it to an optimized ES\n * module, and the island dies before it mounts:\n *\n * [astro-island] Error hydrating /node_modules/@purview-dev/astroc4/src/components/LikeC4ProjectView.tsx\n * SyntaxError: The requested module '/node_modules/react/compiler-runtime.js'\n * does not provide an export named 'c'\n *\n * `astro build` bundles the dependency correctly, which is exactly why the\n * failure is easy to miss: the site builds green with blank diagrams. Listing\n * it as an optimized dependency is the fix.\n */\nconst OPTIMIZED_DEPENDENCIES = ['react/compiler-runtime'];\n\n/**\n * The `optimizeDeps.include` entries a site still needs for the island to\n * hydrate.\n *\n * Only the missing ones are returned: Astro merges the `vite` option with\n * Vite's `mergeConfig`, which concatenates arrays, so re-sending a site's own\n * entries would duplicate them. An entry that does not resolve is left out —\n * a site without React cannot render the island anyway, and naming a package\n * Vite cannot find breaks the optimizer with a less useful error.\n *\n * @param include The site's existing `vite.optimizeDeps.include`.\n * @param root The site root, where the dependency has to resolve from.\n */\nexport function missingOptimizedDependencies(include: unknown, root: string): string[] {\n\tconst existing = Array.isArray(include) ? include : [];\n\n\treturn OPTIMIZED_DEPENDENCIES.filter(\n\t\t(dependency) => !existing.includes(dependency) && resolvesFrom(dependency, root),\n\t);\n}\n\n/** Whether `specifier` resolves from a site root. */\nfunction resolvesFrom(specifier: string, root: string): boolean {\n\ttry {\n\t\t// `createRequire` only uses the path as a resolution base, so the file it\n\t\t// names need not exist (`playground/` has no `package.json` of its own).\n\t\tcreateRequire(join(root, 'package.json')).resolve(specifier);\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * The integration both entry points install: LikeC4's Vite plugin, the fence\n * transform, and the dev-only bits that keep the islands hydrating.\n */\nexport function likec4Integration({\n\tvitePlugin = {},\n\tfences = true,\n}: LikeC4IntegrationOptions = {}): AstroIntegration {\n\treturn {\n\t\tname: `${PACKAGE_NAME}/integration`,\n\t\thooks: {\n\t\t\t'astro:config:setup': async ({ command, config, updateConfig, logger }) => {\n\t\t\t\tconst optimizeDeps = missingOptimizedDependencies(\n\t\t\t\t\tconfig.vite?.optimizeDeps?.include,\n\t\t\t\t\tfileURLToPath(config.root),\n\t\t\t\t);\n\n\t\t\t\tupdateConfig({\n\t\t\t\t\tvite: {\n\t\t\t\t\t\tplugins: [LikeC4VitePlugin(vitePlugin)],\n\t\t\t\t\t\t// The components ship as .astro/.tsx source, so Vite has to\n\t\t\t\t\t\t// transform them rather than hand them to Node as-is.\n\t\t\t\t\t\tssr: { noExternal: [PACKAGE_NAME] },\n\t\t\t\t\t\t// Omitted entirely when the site already lists them: `vite` is\n\t\t\t\t\t\t// merged with Vite's `mergeConfig`, which concatenates arrays.\n\t\t\t\t\t\t...(optimizeDeps.length > 0 ? { optimizeDeps: { include: optimizeDeps } } : {}),\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tif (fences) {\n\t\t\t\t\tawait registerFences(config, updateConfig, logger);\n\t\t\t\t}\n\n\t\t\t\tawait ensureReactRenderer(config, updateConfig, logger);\n\n\t\t\t\t// `command` is only available here, not in `astro:config:done` — and\n\t\t\t\t// this must stay build-only; see `loadLikeC4` in ./fences.ts.\n\t\t\t\tif (command === 'build') {\n\t\t\t\t\tawait preloadLikeC4();\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t};\n}\n\n/**\n * Add `@astrojs/react` when the site has not.\n *\n * The islands are `client:only=\"react\"`, so without a React renderer\n * `<LikeC4View>` fails at build with Astro's generic \"no renderer\" error, which\n * mentions React but not this package. `@astrojs/react` is a peer dependency, so\n * it is installed; adding it here is the difference between\n * `astro add @purview-dev/astroc4` producing a working site and producing one\n * that builds until the first diagram.\n *\n * Pushed integrations have their hooks run after this one — Astro's config loop\n * reads integrations added while it runs, which is how Starlight adds Expressive\n * Code — so the renderer registers in the same pass.\n *\n * Not added, on purpose: `@astrojs/mdx` (it changes what content types a site\n * has) and `astro-expressive-code` (it takes over every code block on the site).\n * Both are the site's decision; the [docs](docs/wiki/guide/Plain-Astro.md) say\n * what to install, and a fence in a `.md` page says what is missing.\n */\nasync function ensureReactRenderer(\n\tconfig: AstroConfig,\n\tupdateConfig: (config: Record<string, unknown>) => void,\n\tlogger: AstroIntegrationLogger,\n): Promise<void> {\n\tif (config.integrations.some((integration) => integration.name === '@astrojs/react')) return;\n\n\ttry {\n\t\tconst { default: react } = await import('@astrojs/react');\n\n\t\tupdateConfig({ integrations: [react()] });\n\t\tlogger.info('added @astrojs/react: the LikeC4 diagrams are React islands (client:only=\"react\").');\n\t} catch {\n\t\tlogger.warn(\n\t\t\t'@astrojs/react is not installed, so <LikeC4View> cannot render. Install it (`astro add react`).',\n\t\t);\n\t}\n}\n\n/**\n * Append the fence transform to the site's existing Sätteri processor, keeping\n * whatever Astro and other integrations already registered on it.\n *\n * Warnings go through Astro's logger, not `console`: the logger honours the log\n * level the site ran the CLI with, and `fork()` labels the message with the part\n * of this package that produced it (`[likec4-fences]`), which is what the\n * Troubleshooting page quotes.\n */\nasync function registerFences(\n\tconfig: AstroConfig,\n\tupdateConfig: (config: Record<string, unknown>) => void,\n\tlogger: AstroIntegrationLogger,\n): Promise<void> {\n\tconst { satteri, isSatteriProcessor } = await import('@astrojs/markdown-satteri');\n\tconst existing = config.markdown?.processor;\n\tconst fences = () => {\n\t\tconst fenceLogger = logger.fork('likec4-fences');\n\n\t\treturn likec4Fences({\n\t\t\treporter: { warn: (message) => fenceLogger.warn(message) },\n\t\t\t// Whether the advice \"rename the page to .mdx\" can actually work.\n\t\t\tmdx: config.integrations.some((integration) => integration.name === '@astrojs/mdx'),\n\t\t});\n\t};\n\n\t// No processor configured means Astro's default, which is Sätteri — also true\n\t// outside Starlight.\n\tif (existing === undefined) {\n\t\tupdateConfig({ markdown: { processor: satteri({ mdastPlugins: [fences()] }) } });\n\t\treturn;\n\t}\n\n\tif (!isSatteriProcessor(existing)) {\n\t\tlogger.warn(\n\t\t\t'```likec4 fences are disabled: the site is not using the Sätteri Markdown processor. ' +\n\t\t\t\t'<LikeC4View /> still works.',\n\t\t);\n\t\treturn;\n\t}\n\n\tconst existingOptions = existing.options ?? {};\n\n\tupdateConfig({\n\t\tmarkdown: {\n\t\t\tprocessor: satteri({\n\t\t\t\t...existingOptions,\n\t\t\t\tmdastPlugins: [...(existingOptions.mdastPlugins ?? []), fences()],\n\t\t\t}),\n\t\t},\n\t});\n}\n\nexport default likec4Integration;\n",
|
|
6
|
+
"// fences.mjs\n// path: src/fences.mjs\n// Turns a ```likec4 code fence into a rendered diagram instead of a code block.\n//\n// Two forms:\n//\n// ```likec4 view=\"requestPath\" title=\"Request path\"\n// ```\n// -> renders a view from the site's LikeC4 project (any likec4.config.json).\n//\n// ```likec4 title=\"Example\"\n// specification { element system }\n// model { a = system 'A' b = system 'B' a -> b }\n// views { view index { include * } }\n// ```\n// -> compiles and lays out this snippet at build time, then renders it.\n//\n// Layout needs LikeC4's WASM graph engine, so it has to happen during the\n// build; the browser only rehydrates the resulting JSON.\n//\n// This is a Sätteri mdast plugin, not a remark one. Astro 7 made Sätteri the\n// default Markdown processor, and `remarkPlugins` is a legacy option that is\n// silently ignored unless @astrojs/markdown-remark is installed. The plugin's\n// integration (src/index.mjs) registers it; sites do not add it themselves.\n\nimport { createHash } from 'node:crypto';\nimport { createRequire } from 'node:module';\nimport { fileURLToPath } from 'node:url';\n\nimport type { MdastPluginEntry, PluginFactoryContext } from 'satteri';\n\nconst require = createRequire(import.meta.url);\n\n// `likec4`'s Node API is deliberately NOT imported at module scope. It pulls in\n// Langium, chevrotain, an LSP and the WASM layout engine, and this module is\n// loaded as part of the site's astro.config — on a cold start that was\n// enough to make Astro report \"Configuration file updated. Restarting...\", and the restart\n// killed the content-layer transport, leaving every page a 500. Load it lazily\n// so the cost is paid only when a page actually contains an inline fence.\nlet likec4ApiPromise: Promise<LikeC4Api> | undefined;\n\n/** The one thing this module needs from `likec4`'s Node API. */\ntype LikeC4Api = typeof import('likec4').LikeC4;\n\nfunction loadLikeC4(): Promise<LikeC4Api> {\n\t// A plain `require('likec4')` rather than `import('likec4')`: Vite's SSR\n\t// module runner intercepts dynamic `import()` from any module it loaded\n\t// (this one included) to route it through the runner's own module graph —\n\t// and on Windows that runner can already be mid-teardown by the time a\n\t// page's ```likec4 fence triggers this, crashing the whole `astro build`\n\t// (see `preloadLikeC4` below). `require()` goes through Node's own\n\t// resolver instead, which Node 22+ supports for ESM-only packages like\n\t// this one, sidestepping the runner entirely.\n\tlikec4ApiPromise ??= Promise.resolve(require('likec4').LikeC4);\n\n\treturn likec4ApiPromise;\n}\n\n/**\n * Kick off (and await) the same lazy load `loadLikeC4()` uses, so a later\n * call inside Satteri's processing reuses the already-resolved promise\n * instead of issuing a fresh `import('likec4')`.\n *\n * `astro build` on Windows crashes mid-build (\"Vite module runner has been\n * closed\", followed by a native libuv assertion that kills the process) if\n * that first `import('likec4')` happens while Vite is tearing down the SSR\n * module runner used for the content layer — which is exactly when the first\n * page containing a ```likec4 fence is rendered. Finishing the import before\n * that point removes the race. src/index.mjs calls this from\n * `astro:config:setup` for builds only — the module-scope comment above\n * `loadLikeC4` explains why eager-loading it in dev is unsafe.\n */\nexport function preloadLikeC4(): Promise<LikeC4Api> {\n\treturn loadLikeC4();\n}\n\nconst FENCE_LANG = 'likec4';\n\n/** Component the fence is rewritten into, per form. */\nconst INLINE_COMPONENT = '__LikeC4Fence';\nconst PROJECT_COMPONENT = '__LikeC4View';\n\n// Package specifiers, not relative paths: the import is written into the page\n// being compiled, so it has to resolve from the site, not from this file.\n/** The published package name; see the comment on `PACKAGE_NAME` in index.ts. */\nconst PACKAGE_NAME = '@purview-dev/astroc4';\n\nconst IMPORTS = {\n\t[INLINE_COMPONENT]: `${PACKAGE_NAME}/components/LikeC4Fence.astro`,\n\t[PROJECT_COMPONENT]: `${PACKAGE_NAME}/components/LikeC4View.astro`,\n} as const;\n\n/** The fence components a page can need. */\ntype ComponentName = keyof typeof IMPORTS;\n\n/** What a fence's layout produces: the serialised model and its view ids. */\ninterface Layout {\n\tdump: any;\n\tviewIds: string[];\n}\n\n/**\n * Compiling a snippet costs ~250ms (a Langium workspace per call) and the same\n * page is re-rendered on every dev edit, so key on the source: a build pays\n * that once per distinct snippet, not once per render.\n */\nconst layoutCache = new Map<string, Layout>();\n\n/** The `key=\"value\"` pairs a fence's meta string can carry. */\nexport interface FenceMeta {\n\ttitle?: string;\n\tdescription?: string;\n\theight?: string;\n\tcollapsible?: string;\n\tdefaultOpen?: string;\n\tview?: string;\n\t[key: string]: string | undefined;\n}\n\n/** Parse `key=\"value\"` / `key=value` pairs from the fence's meta string. */\nexport function parseFenceMeta(meta: string | null | undefined): FenceMeta {\n\tconst options: FenceMeta = {};\n\n\tif (!meta) return options;\n\n\tconst pattern = /(\\w+)=(?:\"([^\"]*)\"|'([^']*)'|(\\S+))/g;\n\tlet match;\n\n\twhile ((match = pattern.exec(meta)) !== null) {\n\t\tconst key = match[1];\n\t\tif (!key) continue;\n\n\t\toptions[key] = match[2] ?? match[3] ?? match[4] ?? '';\n\t}\n\n\treturn options;\n}\n\nasync function layoutSnippet(source: string): Promise<Layout> {\n\tconst key = createHash('sha256').update(source).digest('hex');\n\tconst cached = layoutCache.get(key);\n\n\tif (cached) return cached;\n\n\tconst LikeC4 = await loadLikeC4();\n\tconst likec4 = await LikeC4.fromSource(source);\n\n\tif (likec4.hasErrors()) {\n\t\t// Only the first line of each error — LikeC4's parser errors enumerate\n\t\t// every token it expected, which runs to hundreds of lines.\n\t\tconst detail = likec4\n\t\t\t.getErrors()\n\t\t\t.map((error) => String(error.message).split('\\n')[0]?.trim() ?? '')\n\t\t\t.slice(0, 3)\n\t\t\t.join('; ');\n\n\t\tthrow new Error(detail || 'the snippet could not be parsed');\n\t}\n\n\tconst model = await likec4.layoutedModel();\n\t// `$data` is the plain serialisable model; `$model` is its deprecated alias.\n\tconst dump = JSON.parse(JSON.stringify(model.$data));\n\n\tconst result: Layout = { dump, viewIds: Object.keys(dump.views ?? {}) };\n\tlayoutCache.set(key, result);\n\n\treturn result;\n}\n\n/** A JSX attribute, as the MDX compiler wants it. */\ninterface JsxAttribute {\n\ttype: 'mdxJsxAttribute';\n\tname: string;\n\tvalue: string;\n}\n\n/** ESTree for `import <local> from '<source>';`, as the MDX compiler expects. */\nfunction importNode(local: string, source: string) {\n\treturn {\n\t\ttype: 'mdxjsEsm',\n\t\tvalue: `import ${local} from ${JSON.stringify(source)};`,\n\t\tdata: {\n\t\t\testree: {\n\t\t\t\ttype: 'Program',\n\t\t\t\tsourceType: 'module',\n\t\t\t\tbody: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: 'ImportDeclaration',\n\t\t\t\t\t\tspecifiers: [\n\t\t\t\t\t\t\t{ type: 'ImportDefaultSpecifier', local: { type: 'Identifier', name: local } },\n\t\t\t\t\t\t],\n\t\t\t\t\t\tsource: { type: 'Literal', value: source, raw: JSON.stringify(source) },\n\t\t\t\t\t\tattributes: [],\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t},\n\t\t},\n\t};\n}\n\nfunction attributes(pairs: Record<string, string | undefined>): JsxAttribute[] {\n\treturn Object.entries(pairs)\n\t\t.filter(([, value]) => value !== undefined && value !== null && value !== '')\n\t\t.map(([name, value]) => ({ type: 'mdxJsxAttribute', name, value: String(value) }));\n}\n\nfunction describe(fileURL: URL | undefined): string {\n\tif (!fileURL) return 'a content file';\n\n\ttry {\n\t\treturn fileURLToPath(fileURL);\n\t} catch {\n\t\treturn String(fileURL);\n\t}\n}\n\n/** Where a fence warning goes: the Astro logger in a site, `console` outside one. */\nexport interface FenceReporter {\n\twarn: (message: string) => void;\n}\n\n/** What the fence transform needs to know about its host. */\nexport interface FenceOptions {\n\t/** Where warnings go. Defaults to `console` with a `[likec4-fences]` label. */\n\treporter?: FenceReporter;\n\t/**\n\t * Whether the site has `@astrojs/mdx`, i.e. whether a `.mdx` page can exist.\n\t * Defaults to `true`; `false` changes the advice a misplaced fence gets.\n\t */\n\tmdx?: boolean;\n}\n\n/**\n * The fallback for callers without an Astro logger — the unit tests, and any\n * use of the mdast plugin outside a running Astro config. The label matches the\n * one `src/integration.ts` forks from the site's integration logger, so the\n * message reads the same either way.\n */\nconst CONSOLE_REPORTER: FenceReporter = {\n\twarn: (message) => console.warn(`[likec4-fences] ${message}`),\n};\n\n// region plugin\nexport function likec4Fences({ reporter = CONSOLE_REPORTER, mdx = true }: FenceOptions = {}): MdastPluginEntry {\n\t// A factory runs once per document, so `used` is safely per-file.\n\treturn (factory: PluginFactoryContext) => {\n\t\t// Astro only supports components in .mdx. Leaving the fence as a code\n\t\t// block is the graceful outcome, but say so — a silent code block where\n\t\t// a diagram was intended is easy to miss in review.\n\t\tif (factory.sourceFormat !== 'mdx') {\n\t\t\tif (!factory.source.includes('```' + FENCE_LANG)) return null;\n\n\t\t\t// Advice that cannot work is worse than none: renaming the page to\n\t\t\t// `.mdx` does nothing on a site without `@astrojs/mdx`.\n\t\t\tconst fix = mdx\n\t\t\t\t? 'Rename the page to .mdx, or use <LikeC4View /> directly.'\n\t\t\t\t: 'Add @astrojs/mdx (`astro add mdx`) and rename the page to .mdx, or use <LikeC4View /> directly.';\n\n\t\t\treturn {\n\t\t\t\tname: 'likec4-fences:warn',\n\t\t\t\tcode(node, ctx) {\n\t\t\t\t\tif (node.lang !== FENCE_LANG) return;\n\n\t\t\t\t\treporter.warn(\n\t\t\t\t\t\t`${describe(ctx.fileURL)} has a \\`\\`\\`${FENCE_LANG} fence, but fences only ` +\n\t\t\t\t\t\t\t`render as diagrams in .mdx files. ${fix}`,\n\t\t\t\t\t);\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tif (!factory.source.includes('```' + FENCE_LANG)) return null;\n\n\t\tconst used = new Set<ComponentName>();\n\n\t\treturn {\n\t\t\tname: 'likec4-fences',\n\n\t\t\tasync code(node, ctx) {\n\t\t\t\tif (node.lang !== FENCE_LANG) return;\n\n\t\t\t\tconst where = `${describe(ctx.fileURL)}: \\`\\`\\`${FENCE_LANG} fence`;\n\t\t\t\tconst options = parseFenceMeta(node.meta);\n\t\t\t\tconst source = (node.value ?? '').trim();\n\n\t\t\t\tconst common = {\n\t\t\t\t\ttitle: options.title,\n\t\t\t\t\tdescription: options.description,\n\t\t\t\t\theight: options.height,\n\t\t\t\t\tcollapsible: options.collapsible,\n\t\t\t\t\tdefaultOpen: options.defaultOpen,\n\t\t\t\t};\n\n\t\t\t\tif (source.length === 0) {\n\t\t\t\t\tif (!options.view) {\n\t\t\t\t\t\tthrow new Error(`${where} is empty, so it needs \\`view=\"<id>\"\\` to name a view in the project model.`);\n\t\t\t\t\t}\n\n\t\t\t\t\tused.add(PROJECT_COMPONENT);\n\n\t\t\t\t\tctx.replaceNode(node, {\n\t\t\t\t\t\ttype: 'mdxJsxFlowElement',\n\t\t\t\t\t\tname: PROJECT_COMPONENT,\n\t\t\t\t\t\tattributes: attributes({ ...common, viewId: options.view }),\n\t\t\t\t\t\tchildren: [],\n\t\t\t\t\t});\n\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tlet layout;\n\n\t\t\t\ttry {\n\t\t\t\t\tlayout = await layoutSnippet(source);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`${where} is not valid LikeC4 — ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tconst viewId = options.view ?? layout.viewIds[0];\n\n\t\t\t\tif (!viewId || !layout.viewIds.includes(viewId)) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`${where} has no view \"${viewId ?? '(none)'}\". Declared: ${layout.viewIds.join(', ') || '(none)'}.`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tused.add(INLINE_COMPONENT);\n\n\t\t\t\tctx.replaceNode(node, {\n\t\t\t\t\ttype: 'mdxJsxFlowElement',\n\t\t\t\t\tname: INLINE_COMPONENT,\n\t\t\t\t\tattributes: attributes({ ...common, viewId, model: JSON.stringify(layout.dump) }),\n\t\t\t\t\tchildren: [],\n\t\t\t\t});\n\t\t\t},\n\n\t\t\t// Imports go in once, after every fence on the page is known.\n\t\t\tafter(root, ctx) {\n\t\t\t\tif (used.size === 0) return;\n\n\t\t\t\tctx.insertChildAt(\n\t\t\t\t\troot,\n\t\t\t\t\t0,\n\t\t\t\t\t[...used].map((local) => importNode(local, IMPORTS[local])),\n\t\t\t\t);\n\t\t\t},\n\t\t};\n\t};\n}\n// endregion\n\nexport default likec4Fences;\n"
|
|
7
|
+
],
|
|
8
|
+
"mappings": "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",
|
|
9
|
+
"debugId": "C86C94AAF98979CF64756E2164756E21",
|
|
10
|
+
"names": []
|
|
11
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@purview-dev/astroc4",
|
|
3
|
-
"version": "1.0.0-prerelease.
|
|
3
|
+
"version": "1.0.0-prerelease.5",
|
|
4
4
|
"description": "Interactive LikeC4 architecture diagrams for Astro and Starlight: <LikeC4View>, ```likec4 fences, and LikeC4 syntax highlighting derived from the live Langium grammar.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -21,8 +21,10 @@
|
|
|
21
21
|
"architecture-as-code",
|
|
22
22
|
"architecture-diagrams",
|
|
23
23
|
"diagrams",
|
|
24
|
+
"ui",
|
|
24
25
|
"astro",
|
|
25
26
|
"astro-integration",
|
|
27
|
+
"withastro",
|
|
26
28
|
"starlight",
|
|
27
29
|
"starlight-plugin",
|
|
28
30
|
"expressive-code",
|
|
@@ -36,6 +38,10 @@
|
|
|
36
38
|
"types": "./dist/index.d.ts",
|
|
37
39
|
"import": "./dist/index.js"
|
|
38
40
|
},
|
|
41
|
+
"./integration": {
|
|
42
|
+
"types": "./dist/integration.d.ts",
|
|
43
|
+
"import": "./dist/integration.js"
|
|
44
|
+
},
|
|
39
45
|
"./expressive-code": {
|
|
40
46
|
"types": "./dist/expressive-code.d.ts",
|
|
41
47
|
"import": "./dist/expressive-code.js"
|
|
@@ -56,7 +62,9 @@
|
|
|
56
62
|
"dev": "bun run build:lib && cd playground && astro dev",
|
|
57
63
|
"build": "bun run build:lib && cd playground && astro build",
|
|
58
64
|
"preview": "cd playground && astro preview",
|
|
59
|
-
"
|
|
65
|
+
"dev:astro": "bun run build:lib && cd examples/astro && astro dev",
|
|
66
|
+
"build:astro": "bun run build:lib && cd examples/astro && astro build",
|
|
67
|
+
"check": "bun run build:lib && cd playground && astro check && cd ../examples/astro && astro check && cd ../.. && bunx tsc -p tsconfig.scripts.json --noEmit",
|
|
60
68
|
"test": "vitest run",
|
|
61
69
|
"test:watch": "vitest",
|
|
62
70
|
"test:network": "ASTROC4_NETWORK=1 vitest run src/grammar/__tests__/network.test.ts",
|
|
@@ -77,15 +85,22 @@
|
|
|
77
85
|
"react-dom": ">=19.0.0",
|
|
78
86
|
"shiki": ">=1.0.0"
|
|
79
87
|
},
|
|
88
|
+
"peerDependenciesMeta": {
|
|
89
|
+
"@astrojs/starlight": {
|
|
90
|
+
"optional": true
|
|
91
|
+
}
|
|
92
|
+
},
|
|
80
93
|
"devDependencies": {
|
|
81
94
|
"@astrojs/check": "^0.9.12",
|
|
82
95
|
"@astrojs/markdown-satteri": "^0.4.6",
|
|
96
|
+
"@astrojs/mdx": "^8.1.2",
|
|
83
97
|
"@astrojs/react": "^7.1.2",
|
|
84
98
|
"@astrojs/starlight": "^0.42.8",
|
|
85
99
|
"@likec4/core": "^1.59.4",
|
|
86
100
|
"@types/react": "^19.3.0",
|
|
87
101
|
"@types/react-dom": "^19.3.0",
|
|
88
102
|
"astro": "^7.4.4",
|
|
103
|
+
"astro-expressive-code": "^0.44.3",
|
|
89
104
|
"likec4": "^1.59.4",
|
|
90
105
|
"playwright": "^1.63.0",
|
|
91
106
|
"react": "^19.3.0",
|
|
@@ -20,7 +20,15 @@ const { height = 480 } = Astro.props;
|
|
|
20
20
|
/* LikeC4's renderer measures its container, so it needs a concrete height
|
|
21
21
|
rather than collapsing to the intrinsic size of an absolutely-positioned
|
|
22
22
|
canvas. The per-instance value arrives as an inline custom property. */
|
|
23
|
+
/* Same token contract as DiagramShell: Starlight's palette when present,
|
|
24
|
+
scheme-neutral fallbacks when not (see the note there). */
|
|
23
25
|
.likec4-frame {
|
|
26
|
+
--likec4-border: var(--sl-color-gray-5, rgba(128, 128, 128, 0.35));
|
|
27
|
+
--likec4-surface: var(--sl-color-bg, transparent);
|
|
28
|
+
--likec4-danger: var(--sl-color-red, #c0392b);
|
|
29
|
+
--likec4-danger-soft: var(--sl-color-red-low, rgba(192, 57, 43, 0.12));
|
|
30
|
+
--likec4-danger-text: var(--sl-color-red-high, #8b1d1d);
|
|
31
|
+
|
|
24
32
|
min-height: var(--likec4-height);
|
|
25
33
|
}
|
|
26
34
|
|
|
@@ -35,13 +43,13 @@ const { height = 480 } = Astro.props;
|
|
|
35
43
|
}
|
|
36
44
|
|
|
37
45
|
.likec4-frame :global(.likec4-error) {
|
|
38
|
-
border: 1px solid var(--
|
|
46
|
+
border: 1px solid var(--likec4-danger);
|
|
39
47
|
border-radius: 0.5rem;
|
|
40
|
-
background: var(--
|
|
48
|
+
background: var(--likec4-danger-soft);
|
|
41
49
|
padding: 1rem;
|
|
42
50
|
font-size: 0.875rem;
|
|
43
51
|
line-height: 1.25rem;
|
|
44
|
-
color: var(--
|
|
52
|
+
color: var(--likec4-danger-text);
|
|
45
53
|
}
|
|
46
54
|
|
|
47
55
|
.likec4-frame :global(.likec4-error p) {
|
|
@@ -55,7 +63,7 @@ const { height = 480 } = Astro.props;
|
|
|
55
63
|
|
|
56
64
|
.likec4-frame :global(.likec4-error__retry) {
|
|
57
65
|
margin-top: 0.75rem;
|
|
58
|
-
border: 1px solid var(--
|
|
66
|
+
border: 1px solid var(--likec4-danger);
|
|
59
67
|
border-radius: 0.375rem;
|
|
60
68
|
background: transparent;
|
|
61
69
|
padding: 0.375rem 0.75rem;
|
|
@@ -67,6 +75,6 @@ const { height = 480 } = Astro.props;
|
|
|
67
75
|
}
|
|
68
76
|
|
|
69
77
|
.likec4-frame :global(.likec4-error__retry:hover) {
|
|
70
|
-
background: var(--
|
|
78
|
+
background: var(--likec4-surface);
|
|
71
79
|
}
|
|
72
80
|
</style>
|
|
@@ -54,17 +54,28 @@ const { title, description, hint, collapsible = true, defaultOpen = true } = Ast
|
|
|
54
54
|
</section>
|
|
55
55
|
|
|
56
56
|
<style>
|
|
57
|
+
/* Starlight's palette when the site has it; scheme-neutral fallbacks when it
|
|
58
|
+
does not, so the same components frame a diagram on a plain Astro page.
|
|
59
|
+
Every fallback is a translucent grey or `inherit`, which stays legible on
|
|
60
|
+
light and dark backgrounds alike. */
|
|
57
61
|
.likec4-shell {
|
|
62
|
+
--likec4-border: var(--sl-color-gray-5, rgba(128, 128, 128, 0.35));
|
|
63
|
+
--likec4-surface: var(--sl-color-bg, transparent);
|
|
64
|
+
--likec4-surface-alt: var(--sl-color-bg-nav, rgba(128, 128, 128, 0.06));
|
|
65
|
+
--likec4-text: var(--sl-color-white, inherit);
|
|
66
|
+
--likec4-text-muted: var(--sl-color-gray-2, rgba(128, 128, 128, 0.95));
|
|
67
|
+
--likec4-accent: var(--sl-color-text-accent, inherit);
|
|
68
|
+
|
|
58
69
|
margin-block: 1em;
|
|
59
70
|
overflow: hidden;
|
|
60
|
-
border: 1px solid var(--
|
|
71
|
+
border: 1px solid var(--likec4-border);
|
|
61
72
|
border-radius: 0.75rem;
|
|
62
|
-
background: var(--
|
|
73
|
+
background: var(--likec4-surface-alt);
|
|
63
74
|
}
|
|
64
75
|
|
|
65
76
|
.likec4-shell__header {
|
|
66
|
-
border-bottom: 1px solid var(--
|
|
67
|
-
background: var(--
|
|
77
|
+
border-bottom: 1px solid var(--likec4-border);
|
|
78
|
+
background: var(--likec4-surface);
|
|
68
79
|
padding: 1em 1.25rem;
|
|
69
80
|
line-height: 1.4;
|
|
70
81
|
}
|
|
@@ -86,7 +97,7 @@ const { title, description, hint, collapsible = true, defaultOpen = true } = Ast
|
|
|
86
97
|
font-size: 1rem;
|
|
87
98
|
line-height: 1.5rem;
|
|
88
99
|
font-weight: 600;
|
|
89
|
-
color: var(--
|
|
100
|
+
color: var(--likec4-text);
|
|
90
101
|
}
|
|
91
102
|
|
|
92
103
|
.likec4-shell__title--block {
|
|
@@ -97,7 +108,7 @@ const { title, description, hint, collapsible = true, defaultOpen = true } = Ast
|
|
|
97
108
|
margin: 0.25rem 0 0;
|
|
98
109
|
font-size: 0.875rem;
|
|
99
110
|
line-height: 1.25rem;
|
|
100
|
-
color: var(--
|
|
111
|
+
color: var(--likec4-text-muted);
|
|
101
112
|
}
|
|
102
113
|
|
|
103
114
|
.likec4-shell__hint {
|
|
@@ -105,7 +116,7 @@ const { title, description, hint, collapsible = true, defaultOpen = true } = Ast
|
|
|
105
116
|
font-size: 0.75rem;
|
|
106
117
|
line-height: 1rem;
|
|
107
118
|
font-weight: 500;
|
|
108
|
-
color: var(--
|
|
119
|
+
color: var(--likec4-accent);
|
|
109
120
|
}
|
|
110
121
|
|
|
111
122
|
.likec4-shell__body {
|
|
@@ -23,7 +23,8 @@ interface Props {
|
|
|
23
23
|
defaultOpen?: string;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
// See `LikeC4View.astro`: `t` is Starlight's, and absent on a plain Astro page.
|
|
27
|
+
const t = getTranslator((Astro.locals as { t?: unknown }).t);
|
|
27
28
|
const labels = getIslandLabels(t, 'likec4.fence.loading');
|
|
28
29
|
|
|
29
30
|
const {
|
|
@@ -26,7 +26,11 @@ interface Props {
|
|
|
26
26
|
defaultOpen?: boolean;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
// Starlight augments `App.Locals` with `t`; a plain Astro page has no such
|
|
30
|
+
// property (and an empty `Locals` interface, so naming it would not type-check),
|
|
31
|
+
// which is why this is read as `unknown` and `getTranslator` falls back to
|
|
32
|
+
// English when it is absent.
|
|
33
|
+
const t = getTranslator((Astro.locals as { t?: unknown }).t);
|
|
30
34
|
const labels = getIslandLabels(t, 'likec4.view.loading');
|
|
31
35
|
|
|
32
36
|
const {
|
|
@@ -64,17 +68,22 @@ const {
|
|
|
64
68
|
</DiagramShell>
|
|
65
69
|
|
|
66
70
|
<style>
|
|
71
|
+
/* Same token contract as DiagramShell (see the note there). */
|
|
67
72
|
.likec4-badge {
|
|
73
|
+
--likec4-border: var(--sl-color-gray-5, rgba(128, 128, 128, 0.35));
|
|
74
|
+
--likec4-surface: var(--sl-color-bg, transparent);
|
|
75
|
+
--likec4-text-muted: var(--sl-color-gray-2, rgba(128, 128, 128, 0.95));
|
|
76
|
+
|
|
68
77
|
display: inline-flex;
|
|
69
78
|
align-items: center;
|
|
70
79
|
gap: 0.5rem;
|
|
71
80
|
margin-bottom: 0.75rem;
|
|
72
|
-
border: 1px solid var(--
|
|
81
|
+
border: 1px solid var(--likec4-border);
|
|
73
82
|
border-radius: 9999px;
|
|
74
|
-
background: var(--
|
|
83
|
+
background: var(--likec4-surface);
|
|
75
84
|
padding: 0.25rem 0.75rem;
|
|
76
85
|
font-size: 0.75rem;
|
|
77
86
|
line-height: 1rem;
|
|
78
|
-
color: var(--
|
|
87
|
+
color: var(--likec4-text-muted);
|
|
79
88
|
}
|
|
80
89
|
</style>
|
|
@@ -22,10 +22,16 @@ const { label } = Astro.props;
|
|
|
22
22
|
</div>
|
|
23
23
|
|
|
24
24
|
<style>
|
|
25
|
+
/* Same token contract as DiagramShell/DiagramFrame (see the note there). */
|
|
25
26
|
.likec4-loading {
|
|
26
|
-
border:
|
|
27
|
+
--likec4-border: var(--sl-color-gray-5, rgba(128, 128, 128, 0.35));
|
|
28
|
+
--likec4-surface: var(--sl-color-bg, transparent);
|
|
29
|
+
--likec4-text-muted: var(--sl-color-gray-2, rgba(128, 128, 128, 0.95));
|
|
30
|
+
--likec4-skeleton: var(--sl-color-gray-6, rgba(128, 128, 128, 0.18));
|
|
31
|
+
|
|
32
|
+
border: 1px dashed var(--likec4-border);
|
|
27
33
|
border-radius: 0.5rem;
|
|
28
|
-
background: var(--
|
|
34
|
+
background: var(--likec4-surface);
|
|
29
35
|
padding: 1.25rem;
|
|
30
36
|
}
|
|
31
37
|
|
|
@@ -34,7 +40,7 @@ const { label } = Astro.props;
|
|
|
34
40
|
font-size: 0.875rem;
|
|
35
41
|
line-height: 1.25rem;
|
|
36
42
|
font-weight: 500;
|
|
37
|
-
color: var(--
|
|
43
|
+
color: var(--likec4-text-muted);
|
|
38
44
|
}
|
|
39
45
|
|
|
40
46
|
.likec4-loading__lines {
|
|
@@ -45,7 +51,7 @@ const { label } = Astro.props;
|
|
|
45
51
|
.likec4-loading__line {
|
|
46
52
|
height: 1rem;
|
|
47
53
|
border-radius: 0.25rem;
|
|
48
|
-
background: var(--
|
|
54
|
+
background: var(--likec4-skeleton);
|
|
49
55
|
animation: likec4-pulse 1.6s ease-in-out infinite;
|
|
50
56
|
}
|
|
51
57
|
|