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:
parent
0c8c5fc919
commit
d338b6f74a
6 changed files with 53 additions and 12 deletions
5
.changeset/stupid-suns-beg.md
Normal file
5
.changeset/stupid-suns-beg.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'astro': patch
|
||||
---
|
||||
|
||||
Update frontmatter assets to be relative to the current file instead of `src/assets`
|
|
@ -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));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
title: One
|
||||
image: penguin2.jpg
|
||||
image: ~/assets/penguin2.jpg
|
||||
cover:
|
||||
image: penguin1.jpg
|
||||
image: ../../assets/penguin1.jpg
|
||||
---
|
||||
|
||||
# A post
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
title: One
|
||||
image: penguin2.jpg
|
||||
image: ~/assets/penguin2.jpg
|
||||
cover:
|
||||
image: penguin1.jpg
|
||||
image: ../../assets/penguin1.jpg
|
||||
---
|
||||
|
||||
# A post
|
||||
|
|
Loading…
Reference in a new issue