fix: consider frontmatter slugs
This commit is contained in:
parent
aa18be7fed
commit
0d5fe3186e
3 changed files with 62 additions and 29 deletions
|
@ -10,7 +10,6 @@ import { info, warn, type LogOptions } from '../core/logger/core.js';
|
|||
import { isRelativePath } from '../core/path.js';
|
||||
import { CONTENT_TYPES_FILE } from './consts.js';
|
||||
import {
|
||||
getContentEntryExts,
|
||||
getContentPaths,
|
||||
getEntryInfo,
|
||||
getEntrySlug,
|
||||
|
@ -23,6 +22,7 @@ import {
|
|||
type ContentPaths,
|
||||
type EntryInfo,
|
||||
updateLookupMaps,
|
||||
getContentEntryConfigByExtMap,
|
||||
} from './utils.js';
|
||||
|
||||
type ChokidarEvent = 'add' | 'addDir' | 'change' | 'unlink' | 'unlinkDir';
|
||||
|
@ -59,7 +59,8 @@ export async function createContentTypesGenerator({
|
|||
}: CreateContentGeneratorParams) {
|
||||
const contentTypes: ContentTypes = {};
|
||||
const contentPaths = getContentPaths(settings.config, fs);
|
||||
const contentEntryExts = getContentEntryExts(settings);
|
||||
const contentEntryConfigByExt = getContentEntryConfigByExtMap(settings);
|
||||
const contentEntryExts = [...contentEntryConfigByExt.keys()];
|
||||
|
||||
let events: EventWithOptions[] = [];
|
||||
let debounceTimeout: NodeJS.Timeout | undefined;
|
||||
|
@ -280,7 +281,7 @@ export async function createContentTypesGenerator({
|
|||
contentEntryTypes: settings.contentEntryTypes,
|
||||
});
|
||||
await updateLookupMaps({
|
||||
contentEntryExts,
|
||||
contentEntryConfigByExt,
|
||||
contentPaths,
|
||||
root: settings.config.root,
|
||||
fs,
|
||||
|
@ -308,7 +309,7 @@ function removeCollection(contentMap: ContentTypes, collectionKey: string) {
|
|||
async function parseSlug({
|
||||
fs,
|
||||
event,
|
||||
entryInfo,
|
||||
entryInfo: { id, collection, slug: generatedSlug },
|
||||
}: {
|
||||
fs: typeof fsMod;
|
||||
event: ContentEvent;
|
||||
|
@ -321,7 +322,7 @@ async function parseSlug({
|
|||
// on dev server startup or production build init.
|
||||
const rawContents = await fs.promises.readFile(event.entry, 'utf-8');
|
||||
const { data: frontmatter } = parseFrontmatter(rawContents, fileURLToPath(event.entry));
|
||||
return getEntrySlug({ ...entryInfo, unvalidatedSlug: frontmatter.slug });
|
||||
return getEntrySlug({ id, collection, generatedSlug, frontmatterSlug: frontmatter.slug });
|
||||
}
|
||||
|
||||
function setEntry(
|
||||
|
|
|
@ -2,12 +2,17 @@ import glob, { type Options as FastGlobOptions } from 'fast-glob';
|
|||
import { slug as githubSlug } from 'github-slugger';
|
||||
import matter from 'gray-matter';
|
||||
import fsMod from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import path, { extname } from 'node:path';
|
||||
import { fileURLToPath, pathToFileURL } from 'node:url';
|
||||
import type { PluginContext } from 'rollup';
|
||||
import { normalizePath, type ErrorPayload as ViteErrorPayload, type ViteDevServer } from 'vite';
|
||||
import { z } from 'zod';
|
||||
import type { AstroConfig, AstroSettings, ImageInputFormat } from '../@types/astro.js';
|
||||
import type {
|
||||
AstroConfig,
|
||||
AstroSettings,
|
||||
ContentEntryType,
|
||||
ImageInputFormat,
|
||||
} from '../@types/astro.js';
|
||||
import { VALID_INPUT_FORMATS } from '../assets/consts.js';
|
||||
import { AstroError, AstroErrorData } from '../core/errors/index.js';
|
||||
import { CONTENT_TYPES_FILE } from './consts.js';
|
||||
|
@ -50,11 +55,16 @@ export const msg = {
|
|||
export function getEntrySlug({
|
||||
id,
|
||||
collection,
|
||||
slug,
|
||||
unvalidatedSlug,
|
||||
}: EntryInfo & { unvalidatedSlug?: unknown }) {
|
||||
generatedSlug,
|
||||
frontmatterSlug,
|
||||
}: {
|
||||
id: string;
|
||||
collection: string;
|
||||
generatedSlug: string;
|
||||
frontmatterSlug?: unknown;
|
||||
}) {
|
||||
try {
|
||||
return z.string().default(slug).parse(unvalidatedSlug);
|
||||
return z.string().default(generatedSlug).parse(frontmatterSlug);
|
||||
} catch {
|
||||
throw new AstroError({
|
||||
...AstroErrorData.InvalidContentEntrySlugError,
|
||||
|
@ -128,6 +138,16 @@ export function getContentEntryExts(settings: Pick<AstroSettings, 'contentEntryT
|
|||
return settings.contentEntryTypes.map((t) => t.extensions).flat();
|
||||
}
|
||||
|
||||
export function getContentEntryConfigByExtMap(settings: Pick<AstroSettings, 'contentEntryTypes'>) {
|
||||
const map: Map<string, ContentEntryType> = new Map();
|
||||
for (const entryType of settings.contentEntryTypes) {
|
||||
for (const ext of entryType.extensions) {
|
||||
map.set(ext, entryType);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
export class NoCollectionError extends Error {}
|
||||
|
||||
export function getEntryInfo(
|
||||
|
@ -370,18 +390,18 @@ function search(fs: typeof fsMod, srcDir: URL) {
|
|||
|
||||
export async function updateLookupMaps({
|
||||
contentPaths,
|
||||
contentEntryExts,
|
||||
contentEntryConfigByExt,
|
||||
root,
|
||||
fs,
|
||||
}: {
|
||||
contentEntryExts: string[];
|
||||
contentEntryConfigByExt: ReturnType<typeof getContentEntryConfigByExtMap>;
|
||||
contentPaths: Pick<ContentPaths, 'contentDir' | 'cacheDir'>;
|
||||
root: URL;
|
||||
fs: typeof fsMod;
|
||||
}) {
|
||||
const { contentDir } = contentPaths;
|
||||
const globOpts: FastGlobOptions = {
|
||||
absolute: false,
|
||||
absolute: true,
|
||||
cwd: fileURLToPath(root),
|
||||
fs: {
|
||||
readdir: fs.readdir.bind(fs),
|
||||
|
@ -390,7 +410,10 @@ export async function updateLookupMaps({
|
|||
};
|
||||
|
||||
const relContentDir = rootRelativePath(root, contentDir, false);
|
||||
const contentGlob = await glob(`${relContentDir}/**/*${getExtGlob(contentEntryExts)}`, globOpts);
|
||||
const contentGlob = await glob(
|
||||
`${relContentDir}**/*${getExtGlob([...contentEntryConfigByExt.keys()])}`,
|
||||
globOpts
|
||||
);
|
||||
let filePathByLookupId: {
|
||||
[collection: string]: Record<string, string>;
|
||||
} = {};
|
||||
|
@ -398,9 +421,17 @@ export async function updateLookupMaps({
|
|||
for (const filePath of contentGlob) {
|
||||
const info = getEntryInfo({ contentDir, entry: filePath });
|
||||
if (info instanceof NoCollectionError) continue;
|
||||
filePathByLookupId[info.collection] ??= {};
|
||||
// TODO: frontmatter slugs
|
||||
filePathByLookupId[info.collection][info.slug] = '/' + filePath;
|
||||
const contentEntryConfig = contentEntryConfigByExt.get(extname(filePath));
|
||||
if (!contentEntryConfig) continue;
|
||||
|
||||
const { id, collection, slug: generatedSlug } = info;
|
||||
filePathByLookupId[collection] ??= {};
|
||||
const { slug: frontmatterSlug } = await contentEntryConfig.getEntryInfo({
|
||||
fileUrl: pathToFileURL(filePath),
|
||||
contents: await fs.promises.readFile(filePath, 'utf-8'),
|
||||
});
|
||||
const slug = getEntrySlug({ id, collection, generatedSlug, frontmatterSlug });
|
||||
filePathByLookupId[collection][slug] = rootRelativePath(root, filePath);
|
||||
}
|
||||
|
||||
await fs.promises.writeFile(
|
||||
|
|
|
@ -19,6 +19,7 @@ import {
|
|||
globalContentConfigObserver,
|
||||
NoCollectionError,
|
||||
type ContentConfig,
|
||||
getContentEntryConfigByExtMap,
|
||||
} from './utils.js';
|
||||
|
||||
function isContentFlagImport(viteId: string) {
|
||||
|
@ -55,12 +56,7 @@ export function astroContentImportPlugin({
|
|||
const contentPaths = getContentPaths(settings.config, fs);
|
||||
const contentEntryExts = getContentEntryExts(settings);
|
||||
|
||||
const contentEntryExtToParser: Map<string, ContentEntryType> = new Map();
|
||||
for (const entryType of settings.contentEntryTypes) {
|
||||
for (const ext of entryType.extensions) {
|
||||
contentEntryExtToParser.set(ext, entryType);
|
||||
}
|
||||
}
|
||||
const contentEntryConfigByExt = getContentEntryConfigByExtMap(settings);
|
||||
|
||||
const plugins: Plugin[] = [
|
||||
{
|
||||
|
@ -196,7 +192,7 @@ export function astroContentImportPlugin({
|
|||
const contentConfig = await getContentConfigFromGlobal();
|
||||
const rawContents = await fs.promises.readFile(fileId, 'utf-8');
|
||||
const fileExt = extname(fileId);
|
||||
if (!contentEntryExtToParser.has(fileExt)) {
|
||||
if (!contentEntryConfigByExt.has(fileExt)) {
|
||||
throw new AstroError({
|
||||
...AstroErrorData.UnknownContentCollectionError,
|
||||
message: `No parser found for content entry ${JSON.stringify(
|
||||
|
@ -204,13 +200,13 @@ export function astroContentImportPlugin({
|
|||
)}. Did you apply an integration for this file type?`,
|
||||
});
|
||||
}
|
||||
const contentEntryParser = contentEntryExtToParser.get(fileExt)!;
|
||||
const contentEntryConfig = contentEntryConfigByExt.get(fileExt)!;
|
||||
const {
|
||||
rawData,
|
||||
body,
|
||||
slug: unvalidatedSlug,
|
||||
slug: frontmatterSlug,
|
||||
data: unvalidatedData,
|
||||
} = await contentEntryParser.getEntryInfo({
|
||||
} = await contentEntryConfig.getEntryInfo({
|
||||
fileUrl: pathToFileURL(fileId),
|
||||
contents: rawContents,
|
||||
});
|
||||
|
@ -225,7 +221,12 @@ export function astroContentImportPlugin({
|
|||
const _internal = { filePath: fileId, rawData: rawData };
|
||||
// TODO: move slug calculation to the start of the build
|
||||
// to generate a performant lookup map for `getEntryBySlug`
|
||||
const slug = getEntrySlug({ id, collection, slug: generatedSlug, unvalidatedSlug });
|
||||
const slug = getEntrySlug({
|
||||
id,
|
||||
collection,
|
||||
generatedSlug,
|
||||
frontmatterSlug,
|
||||
});
|
||||
|
||||
const collectionConfig = contentConfig?.collections[collection];
|
||||
let data = collectionConfig
|
||||
|
|
Loading…
Reference in a new issue