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 { z } from 'zod';
|
||||||
import { imageMetadata, type Metadata } from '../assets/utils/metadata.js';
|
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()');
|
throw new Error('Enable `experimental.assets` in your Astro config to use image()');
|
||||||
}
|
}
|
||||||
|
|
||||||
return z.string().transform(async (imagePath) => {
|
return z.string({ description: '__image' }).transform(async (imagePath) => {
|
||||||
const fullPath = new URL(imagePath, options.assetsDir);
|
return await getImageMetadata(pathToFileURL(imagePath));
|
||||||
return await getImageMetadata(fullPath);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,8 @@ import matter from 'gray-matter';
|
||||||
import fsMod from 'node:fs';
|
import fsMod from 'node:fs';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { fileURLToPath, pathToFileURL } from 'node:url';
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
||||||
import type { EmitFile } from 'rollup';
|
import type { EmitFile, PluginContext } from 'rollup';
|
||||||
import { normalizePath, type ErrorPayload as ViteErrorPayload, type ViteDevServer } from 'vite';
|
import { normalizePath, type ViteDevServer, type ErrorPayload as ViteErrorPayload } from 'vite';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import type { AstroConfig, AstroSettings } from '../@types/astro.js';
|
import type { AstroConfig, AstroSettings } from '../@types/astro.js';
|
||||||
import { emitESMImage } from '../assets/utils/emitAsset.js';
|
import { emitESMImage } from '../assets/utils/emitAsset.js';
|
||||||
|
@ -88,7 +88,8 @@ export function getEntrySlug({
|
||||||
|
|
||||||
export async function getEntryData(
|
export async function getEntryData(
|
||||||
entry: EntryInfo & { unvalidatedData: Record<string, unknown>; _internal: EntryInternal },
|
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
|
// Remove reserved `slug` field before parsing data
|
||||||
let { slug, ...data } = entry.unvalidatedData;
|
let { slug, ...data } = entry.unvalidatedData;
|
||||||
|
@ -117,6 +118,37 @@ export async function getEntryData(
|
||||||
message: AstroErrorData.ContentSchemaContainsSlugError.message(entry.collection),
|
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
|
// Use `safeParseAsync` to allow async transforms
|
||||||
const parsed = await collectionConfig.schema.safeParseAsync(entry.unvalidatedData, {
|
const parsed = await collectionConfig.schema.safeParseAsync(entry.unvalidatedData, {
|
||||||
errorMap,
|
errorMap,
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { AstroError } from '../core/errors/errors.js';
|
||||||
import { escapeViteEnvReferences, getFileInfo } from '../vite-plugin-utils/index.js';
|
import { escapeViteEnvReferences, getFileInfo } from '../vite-plugin-utils/index.js';
|
||||||
import { CONTENT_FLAG } from './consts.js';
|
import { CONTENT_FLAG } from './consts.js';
|
||||||
import {
|
import {
|
||||||
|
NoCollectionError,
|
||||||
getContentEntryExts,
|
getContentEntryExts,
|
||||||
getContentPaths,
|
getContentPaths,
|
||||||
getEntryData,
|
getEntryData,
|
||||||
|
@ -17,7 +18,6 @@ import {
|
||||||
getEntrySlug,
|
getEntrySlug,
|
||||||
getEntryType,
|
getEntryType,
|
||||||
globalContentConfigObserver,
|
globalContentConfigObserver,
|
||||||
NoCollectionError,
|
|
||||||
patchAssets,
|
patchAssets,
|
||||||
type ContentConfig,
|
type ContentConfig,
|
||||||
} from './utils.js';
|
} from './utils.js';
|
||||||
|
@ -232,7 +232,11 @@ export const _internal = {
|
||||||
|
|
||||||
const collectionConfig = contentConfig?.collections[collection];
|
const collectionConfig = contentConfig?.collections[collection];
|
||||||
let data = collectionConfig
|
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;
|
: unvalidatedData;
|
||||||
|
|
||||||
await patchAssets(data, pluginContext.meta.watchMode, pluginContext.emitFile, settings);
|
await patchAssets(data, pluginContext.meta.watchMode, pluginContext.emitFile, settings);
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
---
|
---
|
||||||
title: One
|
title: One
|
||||||
image: penguin2.jpg
|
image: ~/assets/penguin2.jpg
|
||||||
cover:
|
cover:
|
||||||
image: penguin1.jpg
|
image: ../../assets/penguin1.jpg
|
||||||
---
|
---
|
||||||
|
|
||||||
# A post
|
# A post
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
---
|
---
|
||||||
title: One
|
title: One
|
||||||
image: penguin2.jpg
|
image: ~/assets/penguin2.jpg
|
||||||
cover:
|
cover:
|
||||||
image: penguin1.jpg
|
image: ../../assets/penguin1.jpg
|
||||||
---
|
---
|
||||||
|
|
||||||
# A post
|
# A post
|
||||||
|
|
Loading…
Reference in a new issue