Resolve images in the frontmatter relative to the current file (#6627)

* feat(images): Resolve images in the frontmatter relative to the current file

* fix(images): Only recursively go through the object if it's not undefined

* fix(images): Add more safeguards

* test(images): Update content collections tests to be relative

* chore: changeset
This commit is contained in:
Erika 2023-03-23 17:50:44 +01:00 committed by GitHub
parent 0c8c5fc919
commit d338b6f74a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 53 additions and 12 deletions

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Update frontmatter assets to be relative to the current file instead of `src/assets`

View file

@ -1,3 +1,4 @@
import { pathToFileURL } from 'url';
import { z } from 'zod';
import { imageMetadata, type Metadata } from '../assets/utils/metadata.js';
@ -7,9 +8,8 @@ export function createImage(options: { assetsDir: string; relAssetsDir: string }
throw new Error('Enable `experimental.assets` in your Astro config to use image()');
}
return z.string().transform(async (imagePath) => {
const fullPath = new URL(imagePath, options.assetsDir);
return await getImageMetadata(fullPath);
return z.string({ description: '__image' }).transform(async (imagePath) => {
return await getImageMetadata(pathToFileURL(imagePath));
});
};
}

View file

@ -3,8 +3,8 @@ import matter from 'gray-matter';
import fsMod from 'node:fs';
import path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import type { EmitFile } from 'rollup';
import { normalizePath, type ErrorPayload as ViteErrorPayload, type ViteDevServer } from 'vite';
import type { EmitFile, PluginContext } from 'rollup';
import { normalizePath, type ViteDevServer, type ErrorPayload as ViteErrorPayload } from 'vite';
import { z } from 'zod';
import type { AstroConfig, AstroSettings } from '../@types/astro.js';
import { emitESMImage } from '../assets/utils/emitAsset.js';
@ -88,7 +88,8 @@ export function getEntrySlug({
export async function getEntryData(
entry: EntryInfo & { unvalidatedData: Record<string, unknown>; _internal: EntryInternal },
collectionConfig: CollectionConfig
collectionConfig: CollectionConfig,
resolver: (idToResolve: string) => ReturnType<PluginContext['resolve']>
) {
// Remove reserved `slug` field before parsing data
let { slug, ...data } = entry.unvalidatedData;
@ -117,6 +118,37 @@ export async function getEntryData(
message: AstroErrorData.ContentSchemaContainsSlugError.message(entry.collection),
});
}
/**
* Resolve all the images referred to in the frontmatter from the file requesting them
*/
async function preprocessAssetPaths(object: Record<string, any>) {
if (typeof object !== 'object' || object === null) return;
for (let [schemaName, schema] of Object.entries<any>(object)) {
if (schema._def.description === '__image') {
object[schemaName] = z.preprocess(
async (value: unknown) => {
if (!value || typeof value !== 'string') return value;
return (await resolver(value))?.id;
},
schema,
{ description: undefined }
);
} else if ('shape' in schema) {
await preprocessAssetPaths(schema.shape);
} else if ('unwrap' in schema) {
const unwrapped = schema.unwrap().shape;
if (unwrapped) {
await preprocessAssetPaths(unwrapped);
}
}
}
}
await preprocessAssetPaths(collectionConfig.schema.shape);
// Use `safeParseAsync` to allow async transforms
const parsed = await collectionConfig.schema.safeParseAsync(entry.unvalidatedData, {
errorMap,

View file

@ -10,6 +10,7 @@ import { AstroError } from '../core/errors/errors.js';
import { escapeViteEnvReferences, getFileInfo } from '../vite-plugin-utils/index.js';
import { CONTENT_FLAG } from './consts.js';
import {
NoCollectionError,
getContentEntryExts,
getContentPaths,
getEntryData,
@ -17,7 +18,6 @@ import {
getEntrySlug,
getEntryType,
globalContentConfigObserver,
NoCollectionError,
patchAssets,
type ContentConfig,
} from './utils.js';
@ -232,7 +232,11 @@ export const _internal = {
const collectionConfig = contentConfig?.collections[collection];
let data = collectionConfig
? await getEntryData({ id, collection, slug, _internal, unvalidatedData }, collectionConfig)
? await getEntryData(
{ id, collection, slug, _internal, unvalidatedData },
collectionConfig,
(idToResolve: string) => pluginContext.resolve(idToResolve, fileId)
)
: unvalidatedData;
await patchAssets(data, pluginContext.meta.watchMode, pluginContext.emitFile, settings);

View file

@ -1,8 +1,8 @@
---
title: One
image: penguin2.jpg
image: ~/assets/penguin2.jpg
cover:
image: penguin1.jpg
image: ../../assets/penguin1.jpg
---
# A post

View file

@ -1,8 +1,8 @@
---
title: One
image: penguin2.jpg
image: ~/assets/penguin2.jpg
cover:
image: penguin1.jpg
image: ../../assets/penguin1.jpg
---
# A post