Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions astro.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import react from "@astrojs/react";
import { readdir } from "fs/promises";
import { fileURLToPath } from "url";

import remarkValidateImages from "./src/plugins/remark/validate-images";

import rehypeTitleFigure from "rehype-title-figure";
import rehypeMermaid from "./src/plugins/rehype/mermaid.ts";
import rehypeAutolinkHeadings from "./src/plugins/rehype/autolink-headings.ts";
Expand Down Expand Up @@ -61,6 +63,7 @@ export default defineConfig({
site: "https://developers.cloudflare.com",
markdown: {
smartypants: false,
remarkPlugins: [remarkValidateImages],
rehypePlugins: [
rehypeMermaid,
rehypeExternalLinks,
Expand Down
62 changes: 62 additions & 0 deletions src/plugins/remark/validate-images.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { existsSync } from "node:fs";
import { join } from "node:path";
import { visit } from "unist-util-visit";

import type { Node } from "unist";
import type { VFile } from "vfile";

interface ImageNode extends Node {
type: "image";
url: string;
position?: {
start: { line: number; column: number };
end: { line: number; column: number };
};
}

export default function validateImages() {
const rootDir = process.cwd();

const assetsDir = join(rootDir, "src", "assets");
const publicDir = join(rootDir, "public");

return (tree: Node, file: VFile) => {
visit(tree, "image", (node: ImageNode) => {
const { url } = node;
let fullPath: string | null;

// Skip remote images
if (url.startsWith("http:") || url.startsWith("https:")) {
return;
}

// Handle relative paths to assets
if (url.startsWith("../") || url.startsWith("~/assets/")) {
// Remove the ~/assets/ or ../ prefix and join with the assets directory
const relativePath = url.startsWith("~/") ? url.slice(9) : url;
fullPath = join(assetsDir, relativePath);
} else if (url.startsWith("/")) {
// Handle absolute paths (public directory)
fullPath = join(publicDir, url);
} else {
// Skip if we don't know how to handle this URL
return;
}

// Check if the file exists
if (!existsSync(fullPath)) {
const position = node.position
? ` at line ${node.position.start.line}, column ${node.position.start.column}`
: "";

const error = new Error(
`Image not found: "${url}"${position} in ${file.path}\n` +
`Expected to find at: ${fullPath}`,
) as Error & { file?: string };

error.file = file.path;
throw error;
}
});
};
}
Loading