summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Ben Holmes <hey@bholmes.dev> 2022-12-22 10:38:03 -0500
committerGravatar GitHub <noreply@github.com> 2022-12-22 10:38:03 -0500
commit853081d1c857d8ad8a9634c37ed8fd123d32d241 (patch)
tree39bbad51f665c2f771fc0d9900656e7d09640b76
parentb64081deed88271a4aa55d66468a10fea235e0a9 (diff)
downloadastro-853081d1c857d8ad8a9634c37ed8fd123d32d241.tar.gz
astro-853081d1c857d8ad8a9634c37ed8fd123d32d241.tar.zst
astro-853081d1c857d8ad8a9634c37ed8fd123d32d241.zip
[Content] Throw on relative image usage (#5648)
* chore: add rel image error plugin * deps: mdast, mdast types * chore: add rel image throw to mdx * refactor: doc rel image path plugin * fix: respect experimental flag in md remark * chore: changeset * deps: remove mdast package * fix: resolve contentDir from config * fix: apply MDX plugin after user plugins * fix: stub out contentDir
-rw-r--r--.changeset/khaki-crabs-develop.md7
-rw-r--r--packages/astro/src/content/index.ts1
-rw-r--r--packages/astro/src/core/build/generate.ts3
-rw-r--r--packages/astro/src/core/build/vite-plugin-ssr.ts3
-rw-r--r--packages/astro/src/core/render/dev/environment.ts3
-rw-r--r--packages/astro/src/core/render/environment.ts6
-rw-r--r--packages/astro/src/vite-plugin-markdown-legacy/index.ts3
-rw-r--r--packages/astro/src/vite-plugin-markdown/index.ts3
-rw-r--r--packages/integrations/mdx/package.json1
-rw-r--r--packages/integrations/mdx/src/plugins.ts38
-rw-r--r--packages/integrations/mdx/src/utils.ts19
-rw-r--r--packages/markdown/remark/src/index.ts8
-rw-r--r--packages/markdown/remark/src/remark-content-rel-image-error.ts52
-rw-r--r--packages/markdown/remark/src/types.ts4
-rw-r--r--pnpm-lock.yaml2
15 files changed, 151 insertions, 2 deletions
diff --git a/.changeset/khaki-crabs-develop.md b/.changeset/khaki-crabs-develop.md
new file mode 100644
index 000000000..b42001b6e
--- /dev/null
+++ b/.changeset/khaki-crabs-develop.md
@@ -0,0 +1,7 @@
+---
+'astro': patch
+'@astrojs/mdx': patch
+'@astrojs/markdown-remark': patch
+---
+
+Prevent relative image paths in `src/content/`
diff --git a/packages/astro/src/content/index.ts b/packages/astro/src/content/index.ts
index 7fc96be29..5c3e6defe 100644
--- a/packages/astro/src/content/index.ts
+++ b/packages/astro/src/content/index.ts
@@ -4,3 +4,4 @@ export {
} from './vite-plugin-content-assets.js';
export { astroContentServerPlugin } from './vite-plugin-content-server.js';
export { astroContentVirtualModPlugin } from './vite-plugin-content-virtual-mod.js';
+export { getContentPaths } from './utils.js';
diff --git a/packages/astro/src/core/build/generate.ts b/packages/astro/src/core/build/generate.ts
index a20b33e18..11dfb1788 100644
--- a/packages/astro/src/core/build/generate.ts
+++ b/packages/astro/src/core/build/generate.ts
@@ -12,6 +12,7 @@ import type {
RouteType,
SSRLoadedRenderer,
} from '../../@types/astro';
+import { getContentPaths } from '../../content/index.js';
import { BuildInternals, hasPrerenderedPages } from '../../core/build/internal.js';
import {
prependForwardSlash,
@@ -352,6 +353,8 @@ async function generatePath(
markdown: {
...settings.config.markdown,
isAstroFlavoredMd: settings.config.legacy.astroFlavoredMarkdown,
+ isExperimentalContentCollections: settings.config.experimental.contentCollections,
+ contentDir: getContentPaths(settings.config).contentDir,
},
mode: opts.mode,
renderers,
diff --git a/packages/astro/src/core/build/vite-plugin-ssr.ts b/packages/astro/src/core/build/vite-plugin-ssr.ts
index 71a3b513a..dbe5b1b4c 100644
--- a/packages/astro/src/core/build/vite-plugin-ssr.ts
+++ b/packages/astro/src/core/build/vite-plugin-ssr.ts
@@ -15,6 +15,7 @@ import { serializeRouteData } from '../routing/index.js';
import { addRollupInput } from './add-rollup-input.js';
import { getOutFile, getOutFolder } from './common.js';
import { eachPrerenderedPageData, eachServerPageData, sortedCSS } from './internal.js';
+import { getContentPaths } from '../../content/index.js';
export const virtualModuleId = '@astrojs-ssr-virtual-entry';
const resolvedVirtualModuleId = '\0' + virtualModuleId;
@@ -208,6 +209,8 @@ function buildManifest(
markdown: {
...settings.config.markdown,
isAstroFlavoredMd: settings.config.legacy.astroFlavoredMarkdown,
+ isExperimentalContentCollections: settings.config.experimental.contentCollections,
+ contentDir: getContentPaths(settings.config).contentDir,
},
pageMap: null as any,
renderers: [],
diff --git a/packages/astro/src/core/render/dev/environment.ts b/packages/astro/src/core/render/dev/environment.ts
index 5c4a8ed73..1fabf486d 100644
--- a/packages/astro/src/core/render/dev/environment.ts
+++ b/packages/astro/src/core/render/dev/environment.ts
@@ -1,4 +1,5 @@
import type { AstroSettings, RuntimeMode } from '../../../@types/astro';
+import { getContentPaths } from '../../../content/index.js';
import type { LogOptions } from '../../logger/core.js';
import type { ModuleLoader } from '../../module-loader/index';
import type { Environment } from '../index';
@@ -24,6 +25,8 @@ export function createDevelopmentEnvironment(
markdown: {
...settings.config.markdown,
isAstroFlavoredMd: settings.config.legacy.astroFlavoredMarkdown,
+ isExperimentalContentCollections: settings.config.experimental.contentCollections,
+ contentDir: getContentPaths(settings.config).contentDir,
},
mode,
// This will be overridden in the dev server
diff --git a/packages/astro/src/core/render/environment.ts b/packages/astro/src/core/render/environment.ts
index 8746df5f1..edf3cc0e6 100644
--- a/packages/astro/src/core/render/environment.ts
+++ b/packages/astro/src/core/render/environment.ts
@@ -37,7 +37,11 @@ export function createBasicEnvironment(options: CreateBasicEnvironmentArgs): Env
const mode = options.mode ?? 'development';
return createEnvironment({
...options,
- markdown: options.markdown ?? {},
+ markdown: {
+ ...(options.markdown ?? {}),
+ // Stub out, not important for basic rendering
+ contentDir: new URL('file:///src/content/'),
+ },
mode,
renderers: options.renderers ?? [],
resolve: options.resolve ?? ((s: string) => Promise.resolve(s)),
diff --git a/packages/astro/src/vite-plugin-markdown-legacy/index.ts b/packages/astro/src/vite-plugin-markdown-legacy/index.ts
index b72418bdb..5203a2b3f 100644
--- a/packages/astro/src/vite-plugin-markdown-legacy/index.ts
+++ b/packages/astro/src/vite-plugin-markdown-legacy/index.ts
@@ -4,6 +4,7 @@ import matter from 'gray-matter';
import { fileURLToPath } from 'url';
import { Plugin, ResolvedConfig, transformWithEsbuild } from 'vite';
import type { AstroSettings } from '../@types/astro';
+import { getContentPaths } from '../content/index.js';
import { pagesVirtualModuleId } from '../core/app/index.js';
import { cachedCompilation, CompileProps } from '../core/compile/index.js';
import { AstroErrorData, MarkdownError } from '../core/errors/index.js';
@@ -162,6 +163,8 @@ export default function markdown({ settings }: AstroPluginOptions): Plugin {
...renderOpts,
fileURL: fileUrl,
isAstroFlavoredMd: true,
+ isExperimentalContentCollections: settings.config.experimental.contentCollections,
+ contentDir: getContentPaths(settings.config).contentDir,
} as any);
let { code: astroResult, metadata } = renderResult;
const { layout = '', components = '', setup = '', ...content } = frontmatter;
diff --git a/packages/astro/src/vite-plugin-markdown/index.ts b/packages/astro/src/vite-plugin-markdown/index.ts
index 66955dca5..67ad4c1e0 100644
--- a/packages/astro/src/vite-plugin-markdown/index.ts
+++ b/packages/astro/src/vite-plugin-markdown/index.ts
@@ -5,6 +5,7 @@ import { fileURLToPath } from 'node:url';
import type { Plugin } from 'vite';
import { normalizePath } from 'vite';
import type { AstroSettings } from '../@types/astro';
+import { getContentPaths } from '../content/index.js';
import { AstroErrorData, MarkdownError } from '../core/errors/index.js';
import type { LogOptions } from '../core/logger/core.js';
import { warn } from '../core/logger/core.js';
@@ -71,6 +72,8 @@ export default function markdown({ settings, logging }: AstroPluginOptions): Plu
...settings.config.markdown,
fileURL: new URL(`file://${fileId}`),
isAstroFlavoredMd: false,
+ isExperimentalContentCollections: settings.config.experimental.contentCollections,
+ contentDir: getContentPaths(settings.config).contentDir,
} as any);
const html = renderResult.code;
diff --git a/packages/integrations/mdx/package.json b/packages/integrations/mdx/package.json
index 08a681097..e2ae2521f 100644
--- a/packages/integrations/mdx/package.json
+++ b/packages/integrations/mdx/package.json
@@ -52,6 +52,7 @@
"@types/chai": "^4.3.1",
"@types/estree": "^1.0.0",
"@types/github-slugger": "^1.3.0",
+ "@types/mdast": "^3.0.10",
"@types/mocha": "^9.1.1",
"@types/yargs-parser": "^21.0.0",
"astro": "workspace:*",
diff --git a/packages/integrations/mdx/src/plugins.ts b/packages/integrations/mdx/src/plugins.ts
index d46ab6cd4..a93db383b 100644
--- a/packages/integrations/mdx/src/plugins.ts
+++ b/packages/integrations/mdx/src/plugins.ts
@@ -1,9 +1,11 @@
import { rehypeHeadingIds } from '@astrojs/markdown-remark';
+import type { Image } from 'mdast';
import { nodeTypes } from '@mdx-js/mdx';
import type { PluggableList } from '@mdx-js/mdx/lib/core.js';
import type { Options as MdxRollupPluginOptions } from '@mdx-js/rollup';
import type { AstroConfig, MarkdownAstroData } from 'astro';
import type { Literal, MemberExpression } from 'estree';
+import { visit } from 'unist-util-visit';
import { visit as estreeVisit } from 'estree-util-visit';
import { bold, yellow } from 'kleur/colors';
import rehypeRaw from 'rehype-raw';
@@ -15,7 +17,8 @@ import { rehypeInjectHeadingsExport } from './rehype-collect-headings.js';
import rehypeMetaString from './rehype-meta-string.js';
import remarkPrism from './remark-prism.js';
import remarkShiki from './remark-shiki.js';
-import { jsToTreeNode } from './utils.js';
+import { jsToTreeNode, isRelativePath } from './utils.js';
+import { pathToFileURL } from 'node:url';
export function recmaInjectImportMetaEnvPlugin({
importMetaEnv,
@@ -113,6 +116,34 @@ export function rehypeApplyFrontmatterExport(pageFrontmatter: Record<string, any
};
}
+/**
+ * `src/content/` does not support relative image paths.
+ * This plugin throws an error if any are found
+ */
+function toRemarkContentRelImageError({ srcDir }: { srcDir: URL }) {
+ const contentDir = new URL('content/', srcDir);
+ return function remarkContentRelImageError() {
+ return (tree: any, vfile: VFile) => {
+ const isContentFile = pathToFileURL(vfile.path).href.startsWith(contentDir.href);
+ if (!isContentFile) return;
+
+ const relImagePaths = new Set<string>();
+ visit(tree, 'image', function raiseError(node: Image) {
+ if (isRelativePath(node.url)) {
+ relImagePaths.add(node.url);
+ }
+ });
+ if (relImagePaths.size === 0) return;
+
+ const errorMessage =
+ `Relative image paths are not supported in the content/ directory. Place local images in the public/ directory and use absolute paths (see https://docs.astro.build/en/guides/images/#in-markdown-files):\n` +
+ [...relImagePaths].map((path) => JSON.stringify(path)).join(',\n');
+
+ throw new Error(errorMessage);
+ };
+ };
+}
+
const DEFAULT_REMARK_PLUGINS: PluggableList = [remarkGfm, remarkSmartypants];
const DEFAULT_REHYPE_PLUGINS: PluggableList = [];
@@ -146,6 +177,11 @@ export async function getRemarkPlugins(
}
remarkPlugins = [...remarkPlugins, ...(mdxOptions.remarkPlugins ?? [])];
+
+ // Apply last in case user plugins resolve relative image paths
+ if (config.experimental.contentCollections) {
+ remarkPlugins.push(toRemarkContentRelImageError(config));
+ }
return remarkPlugins;
}
diff --git a/packages/integrations/mdx/src/utils.ts b/packages/integrations/mdx/src/utils.ts
index 60a50c85a..e803dc300 100644
--- a/packages/integrations/mdx/src/utils.ts
+++ b/packages/integrations/mdx/src/utils.ts
@@ -95,3 +95,22 @@ export function handleExtendsNotSupported(pluginConfig: any) {
);
}
}
+
+// Following utils taken from `packages/astro/src/core/path.ts`:
+
+export function isRelativePath(path: string) {
+ return startsWithDotDotSlash(path) || startsWithDotSlash(path);
+}
+
+function startsWithDotDotSlash(path: string) {
+ const c1 = path[0];
+ const c2 = path[1];
+ const c3 = path[2];
+ return c1 === '.' && c2 === '.' && c3 === '/';
+}
+
+function startsWithDotSlash(path: string) {
+ const c1 = path[0];
+ const c2 = path[1];
+ return c1 === '.' && c2 === '/';
+}
diff --git a/packages/markdown/remark/src/index.ts b/packages/markdown/remark/src/index.ts
index 6d69bcd20..07df39ee8 100644
--- a/packages/markdown/remark/src/index.ts
+++ b/packages/markdown/remark/src/index.ts
@@ -14,6 +14,7 @@ import remarkPrism from './remark-prism.js';
import scopedStyles from './remark-scoped-styles.js';
import remarkShiki from './remark-shiki.js';
import remarkUnwrap from './remark-unwrap.js';
+import toRemarkContentRelImageError from './remark-content-rel-image-error.js';
import rehypeRaw from 'rehype-raw';
import rehypeStringify from 'rehype-stringify';
@@ -42,6 +43,8 @@ export async function renderMarkdown(
remarkRehype = {},
extendDefaultPlugins = false,
isAstroFlavoredMd = false,
+ isExperimentalContentCollections = false,
+ contentDir,
} = opts;
const input = new VFile({ value: content, path: fileURL });
const scopedClassName = opts.$?.scopedClassName;
@@ -73,6 +76,11 @@ export async function renderMarkdown(
parser.use([remarkPrism(scopedClassName)]);
}
+ // Apply later in case user plugins resolve relative image paths
+ if (isExperimentalContentCollections) {
+ parser.use([toRemarkContentRelImageError({ contentDir })]);
+ }
+
parser.use([
[
markdownToHtml as any,
diff --git a/packages/markdown/remark/src/remark-content-rel-image-error.ts b/packages/markdown/remark/src/remark-content-rel-image-error.ts
new file mode 100644
index 000000000..0704ebdd1
--- /dev/null
+++ b/packages/markdown/remark/src/remark-content-rel-image-error.ts
@@ -0,0 +1,52 @@
+import type { Image } from 'mdast';
+import { visit } from 'unist-util-visit';
+import { pathToFileURL } from 'url';
+import type { VFile } from 'vfile';
+
+/**
+ * `src/content/` does not support relative image paths.
+ * This plugin throws an error if any are found
+ */
+export default function toRemarkContentRelImageError({ contentDir }: { contentDir: URL }) {
+ return function remarkContentRelImageError() {
+ return (tree: any, vfile: VFile) => {
+ const isContentFile = pathToFileURL(vfile.path).href.startsWith(contentDir.href);
+ if (!isContentFile) return;
+
+ const relImagePaths = new Set<string>();
+ visit(tree, 'image', function raiseError(node: Image) {
+ console.log(node.url);
+ if (isRelativePath(node.url)) {
+ relImagePaths.add(node.url);
+ }
+ });
+ if (relImagePaths.size === 0) return;
+
+ const errorMessage =
+ `Relative image paths are not supported in the content/ directory. Place local images in the public/ directory and use absolute paths (see https://docs.astro.build/en/guides/images/#in-markdown-files)\n` +
+ [...relImagePaths].map((path) => JSON.stringify(path)).join(',\n');
+
+ // Throw raw string to use `astro:markdown` default formatting
+ throw errorMessage;
+ };
+ };
+}
+
+// Following utils taken from `packages/astro/src/core/path.ts`:
+
+function isRelativePath(path: string) {
+ return startsWithDotDotSlash(path) || startsWithDotSlash(path);
+}
+
+function startsWithDotDotSlash(path: string) {
+ const c1 = path[0];
+ const c2 = path[1];
+ const c3 = path[2];
+ return c1 === '.' && c2 === '.' && c3 === '/';
+}
+
+function startsWithDotSlash(path: string) {
+ const c1 = path[0];
+ const c2 = path[1];
+ return c1 === '.' && c2 === '/';
+}
diff --git a/packages/markdown/remark/src/types.ts b/packages/markdown/remark/src/types.ts
index 76dfe9b73..15465d950 100644
--- a/packages/markdown/remark/src/types.ts
+++ b/packages/markdown/remark/src/types.ts
@@ -54,6 +54,10 @@ export interface MarkdownRenderingOptions extends AstroMarkdownOptions {
scopedClassName: string | null;
};
isAstroFlavoredMd?: boolean;
+ /** Used to prevent relative image imports from `src/content/` */
+ isExperimentalContentCollections?: boolean;
+ /** Used to prevent relative image imports from `src/content/` */
+ contentDir: URL;
}
export interface MarkdownHeading {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 9abcaf0a3..8fd5d9273 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -2889,6 +2889,7 @@ importers:
'@types/chai': ^4.3.1
'@types/estree': ^1.0.0
'@types/github-slugger': ^1.3.0
+ '@types/mdast': ^3.0.10
'@types/mocha': ^9.1.1
'@types/yargs-parser': ^21.0.0
acorn: ^8.8.0
@@ -2940,6 +2941,7 @@ importers:
'@types/chai': 4.3.4
'@types/estree': 1.0.0
'@types/github-slugger': 1.3.0
+ '@types/mdast': 3.0.10
'@types/mocha': 9.1.1
'@types/yargs-parser': 21.0.0
astro: link:../../astro