@lensmap/astro-loader 0.0.0-stage → 0.2.0
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/LICENSE +21 -0
- package/README.md +110 -2
- package/dist/adapter.d.ts +25 -0
- package/dist/components/Content.astro +20 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +253 -0
- package/dist/markdown.d.ts +8 -0
- package/package.json +58 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lensmap contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,111 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @lensmap/astro-loader
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Experimental `.lensmap` content collections for **Astro 7.4.0**. This release deliberately uses Astro's private `addContentEntryType` / `getRenderModule` seam and checks that exact version. It is not a stable Astro extension API. Node >=24.16 is required.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
pnpm add @lensmap/astro-loader @lensmap/document astro@7.4.0
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
// astro.config.mjs
|
|
11
|
+
import { defineConfig } from "astro/config";
|
|
12
|
+
import lensmap from "@lensmap/astro-loader";
|
|
13
|
+
export default defineConfig({ integrations: [lensmap()] });
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
// src/content.config.ts
|
|
18
|
+
import { defineCollection, z } from "astro:content";
|
|
19
|
+
import { lensmapLoader } from "@lensmap/astro-loader";
|
|
20
|
+
export const collections = {
|
|
21
|
+
maps: defineCollection({
|
|
22
|
+
loader: lensmapLoader({ base: "./src/maps" }),
|
|
23
|
+
schema: z.object({
|
|
24
|
+
title: z.string(),
|
|
25
|
+
category: z.string().default("Notes"),
|
|
26
|
+
}),
|
|
27
|
+
}),
|
|
28
|
+
};
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
```yaml
|
|
32
|
+
version: v1
|
|
33
|
+
frontmatter:
|
|
34
|
+
title: An expedition
|
|
35
|
+
nodes:
|
|
36
|
+
- summary: Arrival
|
|
37
|
+
full_text: |
|
|
38
|
+
# Arrival
|
|
39
|
+
|
|
40
|
+
The complete authored Markdown stays intact.
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
One source file becomes one native collection entry. `lensmapLoader()` is a small native `glob()` composition; it accepts `base`, `generateId`, `retainBody`, `deferRender` and an optional `pattern` (default `**/*.lensmap`). For mixed collections use `pattern: '**/*.{md,mdx,lensmap}'` or Astro's own `glob()`.
|
|
44
|
+
|
|
45
|
+
## Rendering in plain Astro
|
|
46
|
+
|
|
47
|
+
Use native `getCollection()` / `getEntry()` and `render()` from `astro:content`, then **pass the actual entry**:
|
|
48
|
+
|
|
49
|
+
```astro
|
|
50
|
+
---
|
|
51
|
+
import { getEntry, render } from 'astro:content';
|
|
52
|
+
const entry = await getEntry('maps', 'expedition');
|
|
53
|
+
if (!entry) throw new Error('Missing expedition');
|
|
54
|
+
const { Content } = await render(entry);
|
|
55
|
+
---
|
|
56
|
+
<Content entry={entry} />
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The default component renders the full thought article on the server, without an extra outer heading or disclosure. It does not provide an interactive viewer and has no React or Starlight dependency.
|
|
60
|
+
|
|
61
|
+
The native document engine validates the **original source**, including its optional open-ended `frontmatter`; no frontmatter stripping or YAML reserialization occurs. Authored metadata remains separate from schema-transformed/defaulted `entry.data`. The effective entry keeps native `id` and `filePath`. Whole entries, source frontmatter and request contexts are never automatically serialized into client props.
|
|
62
|
+
|
|
63
|
+
## Markdown is inert by default
|
|
64
|
+
|
|
65
|
+
Thoughts use the consuming site's configured Markdown processor, followed by HTML sanitization. MDX expressions and imports do not execute on this path. Relative Markdown images are imported from the original map directory as inert asset URLs (the supported local image formats are AVIF, GIF, JPEG, PNG, SVG, TIFF and WebP). Host Markdown transformations run first; executable scripts/handlers and unsafe HTML are then removed. Relative hyperlinks retain authored spelling, just like native Markdown; they are not automatically graph connections. This adapter does not provide Astro image optimization for inert Markdown images.
|
|
66
|
+
|
|
67
|
+
## Explicit build-time MDX admission
|
|
68
|
+
|
|
69
|
+
Install and configure **@astrojs/mdx 8.1.0**, then admit exact local files in Astro integration options:
|
|
70
|
+
|
|
71
|
+
```js
|
|
72
|
+
import mdx from "@astrojs/mdx";
|
|
73
|
+
import { fileURLToPath } from "node:url";
|
|
74
|
+
// alongside the other imports above
|
|
75
|
+
export default defineConfig({
|
|
76
|
+
integrations: [
|
|
77
|
+
mdx(),
|
|
78
|
+
lensmap({
|
|
79
|
+
mdx: {
|
|
80
|
+
admittedSources: [
|
|
81
|
+
fileURLToPath(
|
|
82
|
+
new URL("./src/maps/expedition.lensmap", import.meta.url),
|
|
83
|
+
),
|
|
84
|
+
],
|
|
85
|
+
},
|
|
86
|
+
}),
|
|
87
|
+
],
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Both host MDX capability and canonical local-file admission are required. A missing configured host MDX integration is an error. Other local maps remain inert even on the same origin. Fetched sources and precompiled output never grant admission; there is no runtime MDX compiler or remote-source execution API. Source options belong in Astro configuration, **not** portable Lensmap YAML.
|
|
92
|
+
|
|
93
|
+
Admitted thoughts compile through the actual host MDX integration as virtual sibling modules of the source map: relative imports and assets resolve beside the authored `.lensmap`, not in a generated cache directory. MDX receives `props.entry` (including effective `data`, `id`, `filePath`) and separate `props.authoredFrontmatter`. Expressions such as `{props.entry.data.title}` run on the build server. Source admission is **not an import/process sandbox**. MDX diagnostics identify the virtual sibling thought module; this adapter does not remap compiler diagnostics back to YAML line positions. Only admit source controlled and reviewed by the host. Hydrated components and pre-rendered HTML are not evidence of trust. Compiled client islands require the host's own renderer/capability setup and remain ordinary Astro islands; this package does not hydrate arbitrary selected HTML snapshots.
|
|
94
|
+
|
|
95
|
+
## Custom content composition
|
|
96
|
+
|
|
97
|
+
`lensmap({ contentComponent })` accepts a resolvable Astro component module specifier or an absolute component path. A custom component receives `graph` (the engine's semantic graph), `authoredFrontmatter`, `executable` and forwarded `<Content>` props. Each thought has a named callback slot keyed by `node.key`; render it with `String(await Astro.slots.render(node.key, [entry]) ?? '')`. The callback receives the host-selected effective entry. This is how the thin Starlight layer shares compilation without making this package depend on Starlight. Only serialize deliberately chosen public graph/rendered content into an island, never arbitrary server metadata.
|
|
98
|
+
|
|
99
|
+
Generated wrapper names include a source-path hash (no same-basename collisions); identical content is not rewritten, avoiding unnecessary HMR churn. Generated wrappers live below the host's Astro `cacheDir`; authored source files are not changed.
|
|
100
|
+
|
|
101
|
+
## Development evidence
|
|
102
|
+
|
|
103
|
+
From the Lensmap workspace, after dependencies and `@lensmap/document` are built:
|
|
104
|
+
|
|
105
|
+
```sh
|
|
106
|
+
pnpm --filter @lensmap/astro-loader build
|
|
107
|
+
pnpm --filter @lensmap/astro-loader test
|
|
108
|
+
pnpm --filter @lensmap/astro-loader typecheck
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The native synthetic consumer proves effective/raw metadata separation, entry-aware callback slots, relative MDX component/SVG imports, inert local image resolution, site smart punctuation, same-basename isolation, sanitization and unchanged generated-file mtimes across repeated builds. `test/site` is synthetic test source and is not shipped. Compatibility is qualified only for the exact peers above; no broad Astro-version or full client-island lifecycle guarantee is made.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type SemanticGraph } from "@lensmap/document";
|
|
2
|
+
export interface ExecutionOptions {
|
|
3
|
+
/** Exact local files, compared after realpath. Admission is not sandboxing. */
|
|
4
|
+
mdx?: {
|
|
5
|
+
admittedSources: readonly string[];
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
export type Source = {
|
|
9
|
+
kind: "local";
|
|
10
|
+
path: string;
|
|
11
|
+
} | {
|
|
12
|
+
kind: "fetched";
|
|
13
|
+
url: string;
|
|
14
|
+
} | {
|
|
15
|
+
kind: "compiled";
|
|
16
|
+
};
|
|
17
|
+
export declare function canExecute(source: Source, options: ExecutionOptions, installed: boolean): boolean;
|
|
18
|
+
export interface ParsedMap {
|
|
19
|
+
source: string;
|
|
20
|
+
authoredFrontmatter: Record<string, unknown>;
|
|
21
|
+
graph: SemanticGraph;
|
|
22
|
+
}
|
|
23
|
+
export declare function parseMap(source: string, uri: string): ParsedMap;
|
|
24
|
+
export declare function moduleStem(path: string): string;
|
|
25
|
+
export declare function writeGenerated(path: string, content: string): boolean;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { SemanticGraph } from "@lensmap/document";
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
graph: SemanticGraph;
|
|
6
|
+
entry?: unknown;
|
|
7
|
+
authoredFrontmatter: Record<string, unknown>;
|
|
8
|
+
executable: boolean;
|
|
9
|
+
}
|
|
10
|
+
const { graph, entry } = Astro.props;
|
|
11
|
+
const rendered = await Promise.all(
|
|
12
|
+
graph.nodes.map(async (node) => ({
|
|
13
|
+
node,
|
|
14
|
+
html: await Astro.slots.render(node.key, [entry]),
|
|
15
|
+
})),
|
|
16
|
+
);
|
|
17
|
+
---
|
|
18
|
+
<section data-static-thoughts>
|
|
19
|
+
{rendered.map(({ node, html }) => <section data-thought={node.key}><h2>{node.title}</h2><div set:html={html} /></section>)}
|
|
20
|
+
</section>
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { AstroIntegration } from "astro";
|
|
2
|
+
import { glob } from "astro/loaders";
|
|
3
|
+
import { type ExecutionOptions } from "./adapter.js";
|
|
4
|
+
export type { ExecutionOptions, ParsedMap, Source } from "./adapter.js";
|
|
5
|
+
export { canExecute, parseMap } from "./adapter.js";
|
|
6
|
+
export interface LensmapOptions extends ExecutionOptions {
|
|
7
|
+
/** An Astro component module. Receives graph, authoredFrontmatter and callback thought slots. */
|
|
8
|
+
contentComponent?: string;
|
|
9
|
+
}
|
|
10
|
+
/** Compose with native Astro collection schemas. Entry data is only authored frontmatter. */
|
|
11
|
+
export declare function lensmapLoader(options?: Omit<Parameters<typeof glob>[0], "pattern"> & {
|
|
12
|
+
pattern?: string | string[];
|
|
13
|
+
}): import("astro/loaders").Loader;
|
|
14
|
+
/** Experimental private content-entry seam, deliberately limited to Astro 7.4.0. */
|
|
15
|
+
export default function lensmap(options?: LensmapOptions): AstroIntegration;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { mkdirSync, readFileSync as readFileSync2 } from "node:fs";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
5
|
+
import { glob } from "astro/loaders";
|
|
6
|
+
|
|
7
|
+
// src/adapter.ts
|
|
8
|
+
import { createHash } from "node:crypto";
|
|
9
|
+
import { existsSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
|
|
10
|
+
import {
|
|
11
|
+
DocumentSchema,
|
|
12
|
+
DocumentSession
|
|
13
|
+
} from "@lensmap/document";
|
|
14
|
+
import { parseDocument } from "yaml";
|
|
15
|
+
function canExecute(source, options, installed) {
|
|
16
|
+
if (!installed || !options.mdx || source.kind !== "local") return false;
|
|
17
|
+
try {
|
|
18
|
+
const path = realpathSync(source.path);
|
|
19
|
+
return options.mdx.admittedSources.some((admitted) => {
|
|
20
|
+
try {
|
|
21
|
+
return realpathSync(admitted) === path;
|
|
22
|
+
} catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
} catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function parseMap(source, uri) {
|
|
31
|
+
const snapshot = new DocumentSession(uri, source).snapshot();
|
|
32
|
+
if (!snapshot.valid || !snapshot.graph)
|
|
33
|
+
throw new Error(
|
|
34
|
+
`Invalid Lensmap ${uri}: ${JSON.stringify(snapshot.diagnostics)}`
|
|
35
|
+
);
|
|
36
|
+
const raw = DocumentSchema.parse(parseDocument(source).toJS());
|
|
37
|
+
return {
|
|
38
|
+
source,
|
|
39
|
+
authoredFrontmatter: raw.frontmatter ?? {},
|
|
40
|
+
graph: snapshot.graph
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function moduleStem(path) {
|
|
44
|
+
return createHash("sha256").update(path).digest("hex");
|
|
45
|
+
}
|
|
46
|
+
function writeGenerated(path, content) {
|
|
47
|
+
if (existsSync(path) && readFileSync(path, "utf8") === content) return false;
|
|
48
|
+
writeFileSync(path, content);
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// src/markdown.ts
|
|
53
|
+
import { fileURLToPath } from "node:url";
|
|
54
|
+
import rehypeParse from "rehype-parse";
|
|
55
|
+
import rehypeSanitize from "rehype-sanitize";
|
|
56
|
+
import rehypeStringify from "rehype-stringify";
|
|
57
|
+
import { unified } from "unified";
|
|
58
|
+
async function sanitizeMarkdown(code, fileUrl, identity) {
|
|
59
|
+
const processor = unified().use(rehypeParse, { fragment: true }).use(rehypeSanitize).use(rehypeStringify);
|
|
60
|
+
const tree = processor.parse(code);
|
|
61
|
+
const assets = [];
|
|
62
|
+
function visit(node) {
|
|
63
|
+
if (!node || typeof node !== "object") return;
|
|
64
|
+
const element = node;
|
|
65
|
+
if (element.tagName === "img" && element.properties) {
|
|
66
|
+
const properties = element.properties;
|
|
67
|
+
const marker = Object.entries(properties).find(
|
|
68
|
+
([key]) => key.toLowerCase() === "__astro_image_"
|
|
69
|
+
);
|
|
70
|
+
if (marker && typeof marker[1] === "string") {
|
|
71
|
+
const restored = JSON.parse(marker[1]);
|
|
72
|
+
if (restored && typeof restored === "object")
|
|
73
|
+
Object.assign(properties, restored);
|
|
74
|
+
delete properties[marker[0]];
|
|
75
|
+
}
|
|
76
|
+
const src = properties.src;
|
|
77
|
+
if (typeof src === "string" && !/^(?:[a-z][a-z\d+.-]*:|\/|#)/i.test(src)) {
|
|
78
|
+
const url = new URL(src, fileUrl);
|
|
79
|
+
if (!/\.(?:avif|gif|jpe?g|png|svg|tiff?|webp)$/i.test(url.pathname))
|
|
80
|
+
throw new Error(`Unsupported local Markdown image: ${src}`);
|
|
81
|
+
const token = `__LENSMAP_ASSET_${identity}_${assets.length}__`;
|
|
82
|
+
assets.push({ path: fileURLToPath(url), token });
|
|
83
|
+
properties.src = token;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
element.children?.forEach(visit);
|
|
87
|
+
}
|
|
88
|
+
visit(tree);
|
|
89
|
+
return { html: processor.stringify(await processor.run(tree)), assets };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// src/index.ts
|
|
93
|
+
function lensmapLoader(options = {}) {
|
|
94
|
+
return glob({ ...options, pattern: options.pattern ?? "**/*.lensmap" });
|
|
95
|
+
}
|
|
96
|
+
var quote = JSON.stringify;
|
|
97
|
+
function lensmap(options = {}) {
|
|
98
|
+
let config;
|
|
99
|
+
let renderer;
|
|
100
|
+
let installedMdx = false;
|
|
101
|
+
const mdxModules = /* @__PURE__ */ new Map();
|
|
102
|
+
return {
|
|
103
|
+
name: "@lensmap/astro-loader",
|
|
104
|
+
hooks: {
|
|
105
|
+
"astro:config:setup": (setup) => {
|
|
106
|
+
const version = JSON.parse(
|
|
107
|
+
readFileSync2(
|
|
108
|
+
new URL(import.meta.resolve("astro/package.json")),
|
|
109
|
+
"utf8"
|
|
110
|
+
)
|
|
111
|
+
).version;
|
|
112
|
+
if (version !== "7.4.0")
|
|
113
|
+
throw new Error(
|
|
114
|
+
`Lensmap experimental content seam requires Astro 7.4.0; received ${version}`
|
|
115
|
+
);
|
|
116
|
+
const add = setup.addContentEntryType;
|
|
117
|
+
if (typeof add !== "function")
|
|
118
|
+
throw new Error(
|
|
119
|
+
"Lensmap requires Astro 7.4.0 private addContentEntryType support"
|
|
120
|
+
);
|
|
121
|
+
setup.updateConfig({
|
|
122
|
+
vite: {
|
|
123
|
+
plugins: [
|
|
124
|
+
{
|
|
125
|
+
name: "lensmap-source-relative-mdx",
|
|
126
|
+
enforce: "pre",
|
|
127
|
+
resolveId(id) {
|
|
128
|
+
if (mdxModules.has(id)) return id;
|
|
129
|
+
},
|
|
130
|
+
load(id) {
|
|
131
|
+
const module = mdxModules.get(id.split("?")[0] ?? id);
|
|
132
|
+
if (!module) return;
|
|
133
|
+
this.addWatchFile(module.source);
|
|
134
|
+
return module.code;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
]
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
add({
|
|
141
|
+
extensions: [".lensmap"],
|
|
142
|
+
handlePropagation: true,
|
|
143
|
+
contentModuleTypes: "declare module 'astro:content' { interface Render { '.lensmap': Promise<RenderResult>; } }",
|
|
144
|
+
getEntryInfo({ contents, fileUrl }) {
|
|
145
|
+
const { authoredFrontmatter: data, graph } = parseMap(
|
|
146
|
+
contents,
|
|
147
|
+
fileUrl.href
|
|
148
|
+
);
|
|
149
|
+
return {
|
|
150
|
+
data,
|
|
151
|
+
body: graph.nodes.map((node) => node.markdown).join("\n\n"),
|
|
152
|
+
rawData: quote(data),
|
|
153
|
+
slug: typeof data.slug === "string" ? data.slug : void 0
|
|
154
|
+
};
|
|
155
|
+
},
|
|
156
|
+
async getRenderModule({ contents, fileUrl }) {
|
|
157
|
+
const path = fileURLToPath2(fileUrl);
|
|
158
|
+
const { graph, authoredFrontmatter } = parseMap(
|
|
159
|
+
contents,
|
|
160
|
+
fileUrl.href
|
|
161
|
+
);
|
|
162
|
+
const executable = canExecute(
|
|
163
|
+
{ kind: "local", path },
|
|
164
|
+
options,
|
|
165
|
+
installedMdx
|
|
166
|
+
);
|
|
167
|
+
const generated = fileURLToPath2(
|
|
168
|
+
new URL("lensmap/", config.cacheDir)
|
|
169
|
+
);
|
|
170
|
+
mkdirSync(generated, { recursive: true });
|
|
171
|
+
const imports = [];
|
|
172
|
+
const slots = [];
|
|
173
|
+
for (const [index, node] of graph.nodes.entries()) {
|
|
174
|
+
if (executable) {
|
|
175
|
+
const id = `${path}.__lensmap_${index}.mdx`;
|
|
176
|
+
mdxModules.set(id, { code: node.markdown, source: path });
|
|
177
|
+
imports.push(`import Thought${index} from ${quote(id)};`);
|
|
178
|
+
slots.push(
|
|
179
|
+
`<fragment slot=${quote(node.key)}>{(entry) => <Thought${index} entry={entry} authoredFrontmatter={authoredFrontmatter} />}</fragment>`
|
|
180
|
+
);
|
|
181
|
+
} else {
|
|
182
|
+
renderer ??= await config.markdown.processor.createRenderer({
|
|
183
|
+
...config.markdown,
|
|
184
|
+
image: config.image
|
|
185
|
+
});
|
|
186
|
+
const rendered = await renderer.render(node.markdown, {
|
|
187
|
+
fileURL: fileUrl
|
|
188
|
+
});
|
|
189
|
+
const { html, assets } = await sanitizeMarkdown(
|
|
190
|
+
rendered.code,
|
|
191
|
+
fileUrl,
|
|
192
|
+
`${moduleStem(path)}_${index}`
|
|
193
|
+
);
|
|
194
|
+
let expression = quote(html);
|
|
195
|
+
for (const [assetIndex, asset] of assets.entries()) {
|
|
196
|
+
const name = `Asset${index}_${assetIndex}`;
|
|
197
|
+
imports.push(
|
|
198
|
+
`import ${name} from ${quote(`${asset.path}?url`)};`
|
|
199
|
+
);
|
|
200
|
+
expression += `.replaceAll(${quote(asset.token)}, ${name})`;
|
|
201
|
+
}
|
|
202
|
+
slots.push(
|
|
203
|
+
`<Fragment slot=${quote(node.key)} set:html={${expression}} />`
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
const component = options.contentComponent ?? fileURLToPath2(
|
|
208
|
+
new URL("./components/Content.astro", import.meta.url)
|
|
209
|
+
);
|
|
210
|
+
const wrapper = resolve(generated, `${moduleStem(path)}.astro`);
|
|
211
|
+
writeGenerated(
|
|
212
|
+
wrapper,
|
|
213
|
+
`---
|
|
214
|
+
import MapContent from ${quote(component)};
|
|
215
|
+
${imports.join("\n")}
|
|
216
|
+
const graph = ${quote(graph)};
|
|
217
|
+
const authoredFrontmatter = ${quote(authoredFrontmatter)};
|
|
218
|
+
---
|
|
219
|
+
<MapContent {...Astro.props} graph={graph} authoredFrontmatter={authoredFrontmatter} executable={${executable}}>
|
|
220
|
+
${slots.join("\n")}
|
|
221
|
+
</MapContent>
|
|
222
|
+
`
|
|
223
|
+
);
|
|
224
|
+
return {
|
|
225
|
+
code: `export { default as Content } from ${quote(wrapper)}; export const getHeadings = () => [];`,
|
|
226
|
+
map: null
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
},
|
|
231
|
+
"astro:config:done": ({ config: resolved }) => {
|
|
232
|
+
config = resolved;
|
|
233
|
+
installedMdx = resolved.integrations.some(
|
|
234
|
+
(integration) => integration.name === "@astrojs/mdx"
|
|
235
|
+
);
|
|
236
|
+
if (!config.markdown.processor?.createRenderer)
|
|
237
|
+
throw new Error(
|
|
238
|
+
"Lensmap requires the Astro 7.4.0 Markdown processor API"
|
|
239
|
+
);
|
|
240
|
+
if (options.mdx && !installedMdx)
|
|
241
|
+
throw new Error(
|
|
242
|
+
"Lensmap MDX admission requires the host @astrojs/mdx integration"
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
export {
|
|
249
|
+
canExecute,
|
|
250
|
+
lensmap as default,
|
|
251
|
+
lensmapLoader,
|
|
252
|
+
parseMap
|
|
253
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Restore Astro's inert image markers before sanitizing, importing local files as URLs only. */
|
|
2
|
+
export declare function sanitizeMarkdown(code: string, fileUrl: URL, identity: string): Promise<{
|
|
3
|
+
html: string;
|
|
4
|
+
assets: {
|
|
5
|
+
path: string;
|
|
6
|
+
token: string;
|
|
7
|
+
}[];
|
|
8
|
+
}>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,60 @@
|
|
|
1
1
|
{
|
|
2
|
+
"version": "0.2.0",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"files": [
|
|
6
|
+
"dist"
|
|
7
|
+
],
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=24.16.0"
|
|
10
|
+
},
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public",
|
|
13
|
+
"tag": "latest",
|
|
14
|
+
"registry": "https://registry.npmjs.org/"
|
|
15
|
+
},
|
|
2
16
|
"name": "@lensmap/astro-loader",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
|
|
6
|
-
|
|
17
|
+
"description": "Experimental portable Lensmap Astro content integration",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/nicodoggie/lensmap.git",
|
|
21
|
+
"directory": "packages/astro-loader"
|
|
22
|
+
},
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
26
|
+
"default": "./dist/index.js"
|
|
27
|
+
},
|
|
28
|
+
"./components/*": "./dist/components/*"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "node build.mjs",
|
|
32
|
+
"typecheck": "tsc --noEmit",
|
|
33
|
+
"test": "node --test test/*.test.ts",
|
|
34
|
+
"prepack": "pnpm build"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@lensmap/document": "workspace:*",
|
|
38
|
+
"rehype-parse": "9.0.1",
|
|
39
|
+
"rehype-sanitize": "6.0.0",
|
|
40
|
+
"rehype-stringify": "10.0.1",
|
|
41
|
+
"unified": "11.0.5",
|
|
42
|
+
"yaml": "2.9.1"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"astro": "7.4.0",
|
|
46
|
+
"@astrojs/mdx": "8.1.0"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"astro": "7.4.0",
|
|
50
|
+
"@astrojs/mdx": "8.1.0",
|
|
51
|
+
"esbuild": "0.28.2",
|
|
52
|
+
"typescript": "7.0.2",
|
|
53
|
+
"@types/node": "24.19.2"
|
|
54
|
+
},
|
|
55
|
+
"peerDependenciesMeta": {
|
|
56
|
+
"@astrojs/mdx": {
|
|
57
|
+
"optional": true
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|