feat: migrate to starlight

This commit is contained in:
DuroCodes
2024-05-06 17:15:30 -04:00
parent 767acedea7
commit bb190f2d81
15140 changed files with 2828326 additions and 35408 deletions

11
node_modules/astro/dist/content/consts.d.ts generated vendored Normal file
View File

@@ -0,0 +1,11 @@
export declare const PROPAGATED_ASSET_FLAG = "astroPropagatedAssets";
export declare const CONTENT_RENDER_FLAG = "astroRenderContent";
export declare const CONTENT_FLAG = "astroContentCollectionEntry";
export declare const DATA_FLAG = "astroDataCollectionEntry";
export declare const VIRTUAL_MODULE_ID = "astro:content";
export declare const RESOLVED_VIRTUAL_MODULE_ID: string;
export declare const LINKS_PLACEHOLDER = "@@ASTRO-LINKS@@";
export declare const STYLES_PLACEHOLDER = "@@ASTRO-STYLES@@";
export declare const SCRIPTS_PLACEHOLDER = "@@ASTRO-SCRIPTS@@";
export declare const CONTENT_FLAGS: readonly ["astroContentCollectionEntry", "astroRenderContent", "astroDataCollectionEntry", "astroPropagatedAssets"];
export declare const CONTENT_TYPES_FILE = "types.d.ts";

29
node_modules/astro/dist/content/consts.js generated vendored Normal file
View File

@@ -0,0 +1,29 @@
const PROPAGATED_ASSET_FLAG = "astroPropagatedAssets";
const CONTENT_RENDER_FLAG = "astroRenderContent";
const CONTENT_FLAG = "astroContentCollectionEntry";
const DATA_FLAG = "astroDataCollectionEntry";
const VIRTUAL_MODULE_ID = "astro:content";
const RESOLVED_VIRTUAL_MODULE_ID = "\0" + VIRTUAL_MODULE_ID;
const LINKS_PLACEHOLDER = "@@ASTRO-LINKS@@";
const STYLES_PLACEHOLDER = "@@ASTRO-STYLES@@";
const SCRIPTS_PLACEHOLDER = "@@ASTRO-SCRIPTS@@";
const CONTENT_FLAGS = [
CONTENT_FLAG,
CONTENT_RENDER_FLAG,
DATA_FLAG,
PROPAGATED_ASSET_FLAG
];
const CONTENT_TYPES_FILE = "types.d.ts";
export {
CONTENT_FLAG,
CONTENT_FLAGS,
CONTENT_RENDER_FLAG,
CONTENT_TYPES_FILE,
DATA_FLAG,
LINKS_PLACEHOLDER,
PROPAGATED_ASSET_FLAG,
RESOLVED_VIRTUAL_MODULE_ID,
SCRIPTS_PLACEHOLDER,
STYLES_PLACEHOLDER,
VIRTUAL_MODULE_ID
};

7
node_modules/astro/dist/content/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1,7 @@
export { CONTENT_FLAG, PROPAGATED_ASSET_FLAG } from './consts.js';
export { attachContentServerListeners } from './server-listeners.js';
export { createContentTypesGenerator } from './types-generator.js';
export { contentObservable, getContentPaths, getDotAstroTypeReference, hasAssetPropagationFlag, } from './utils.js';
export { astroContentAssetPropagationPlugin } from './vite-plugin-content-assets.js';
export { astroContentImportPlugin } from './vite-plugin-content-imports.js';
export { astroContentVirtualModPlugin } from './vite-plugin-content-virtual-mod.js';

25
node_modules/astro/dist/content/index.js generated vendored Normal file
View File

@@ -0,0 +1,25 @@
import { CONTENT_FLAG, PROPAGATED_ASSET_FLAG } from "./consts.js";
import { attachContentServerListeners } from "./server-listeners.js";
import { createContentTypesGenerator } from "./types-generator.js";
import {
contentObservable,
getContentPaths,
getDotAstroTypeReference,
hasAssetPropagationFlag
} from "./utils.js";
import { astroContentAssetPropagationPlugin } from "./vite-plugin-content-assets.js";
import { astroContentImportPlugin } from "./vite-plugin-content-imports.js";
import { astroContentVirtualModPlugin } from "./vite-plugin-content-virtual-mod.js";
export {
CONTENT_FLAG,
PROPAGATED_ASSET_FLAG,
astroContentAssetPropagationPlugin,
astroContentImportPlugin,
astroContentVirtualModPlugin,
attachContentServerListeners,
contentObservable,
createContentTypesGenerator,
getContentPaths,
getDotAstroTypeReference,
hasAssetPropagationFlag
};

11
node_modules/astro/dist/content/runtime-assets.d.ts generated vendored Normal file
View File

@@ -0,0 +1,11 @@
import type { PluginContext } from 'rollup';
import { z } from 'zod';
export declare function createImage(pluginContext: PluginContext, shouldEmitFile: boolean, entryFilePath: string): () => z.ZodEffects<z.ZodString, z.ZodNever | {
ASTRO_ASSET: string;
src: string;
width: number;
height: number;
format: "jpeg" | "jpg" | "png" | "tiff" | "webp" | "gif" | "svg" | "avif";
orientation?: number | undefined;
fsPath: string;
}, string>;

26
node_modules/astro/dist/content/runtime-assets.js generated vendored Normal file
View File

@@ -0,0 +1,26 @@
import { z } from "zod";
import { emitESMImage } from "../assets/utils/emitAsset.js";
function createImage(pluginContext, shouldEmitFile, entryFilePath) {
return () => {
return z.string().transform(async (imagePath, ctx) => {
const resolvedFilePath = (await pluginContext.resolve(imagePath, entryFilePath))?.id;
const metadata = await emitESMImage(
resolvedFilePath,
pluginContext.meta.watchMode,
shouldEmitFile ? pluginContext.emitFile : void 0
);
if (!metadata) {
ctx.addIssue({
code: "custom",
message: `Image ${imagePath} does not exist. Is the path correct?`,
fatal: true
});
return z.never();
}
return { ...metadata, ASTRO_ASSET: metadata.fsPath };
});
};
}
export {
createImage
};

83
node_modules/astro/dist/content/runtime.d.ts generated vendored Normal file
View File

@@ -0,0 +1,83 @@
import type { MarkdownHeading } from '@astrojs/markdown-remark';
import { type AstroComponentFactory } from '../runtime/server/index.js';
import type { ContentLookupMap } from './utils.js';
type LazyImport = () => Promise<any>;
type GlobResult = Record<string, LazyImport>;
type CollectionToEntryMap = Record<string, GlobResult>;
type GetEntryImport = (collection: string, lookupId: string) => Promise<LazyImport>;
export declare function defineCollection(config: any): any;
export declare function createCollectionToGlobResultMap({ globResult, contentDir, }: {
globResult: GlobResult;
contentDir: string;
}): CollectionToEntryMap;
export declare function createGetCollection({ contentCollectionToEntryMap, dataCollectionToEntryMap, getRenderEntryImport, }: {
contentCollectionToEntryMap: CollectionToEntryMap;
dataCollectionToEntryMap: CollectionToEntryMap;
getRenderEntryImport: GetEntryImport;
}): (collection: string, filter?: (entry: any) => unknown) => Promise<any[]>;
export declare function createGetEntryBySlug({ getEntryImport, getRenderEntryImport, }: {
getEntryImport: GetEntryImport;
getRenderEntryImport: GetEntryImport;
}): (collection: string, slug: string) => Promise<{
id: any;
slug: any;
body: any;
collection: any;
data: any;
render(): Promise<RenderResult>;
} | undefined>;
export declare function createGetDataEntryById({ getEntryImport }: {
getEntryImport: GetEntryImport;
}): (collection: string, id: string) => Promise<{
id: any;
collection: any;
data: any;
}>;
type ContentEntryResult = {
id: string;
slug: string;
body: string;
collection: string;
data: Record<string, any>;
render(): Promise<RenderResult>;
};
type DataEntryResult = {
id: string;
collection: string;
data: Record<string, any>;
};
type EntryLookupObject = {
collection: string;
id: string;
} | {
collection: string;
slug: string;
};
export declare function createGetEntry({ getEntryImport, getRenderEntryImport, }: {
getEntryImport: GetEntryImport;
getRenderEntryImport: GetEntryImport;
}): (collectionOrLookupObject: string | EntryLookupObject, _lookupId?: string) => Promise<ContentEntryResult | DataEntryResult | undefined>;
export declare function createGetEntries(getEntry: ReturnType<typeof createGetEntry>): (entries: {
collection: string;
id: string;
}[] | {
collection: string;
slug: string;
}[]) => Promise<(ContentEntryResult | DataEntryResult | undefined)[]>;
type RenderResult = {
Content: AstroComponentFactory;
headings: MarkdownHeading[];
remarkPluginFrontmatter: Record<string, any>;
};
export declare function createReference({ lookupMap }: {
lookupMap: ContentLookupMap;
}): (collection: string) => import("zod").ZodEffects<import("zod").ZodString, {
slug: string;
collection: string;
id?: undefined;
} | {
id: string;
collection: string;
slug?: undefined;
} | undefined, string>;
export {};

307
node_modules/astro/dist/content/runtime.js generated vendored Normal file
View File

@@ -0,0 +1,307 @@
import pLimit from "p-limit";
import { ZodIssueCode, string as zodString } from "zod";
import { AstroError, AstroErrorData } from "../core/errors/index.js";
import { prependForwardSlash } from "../core/path.js";
import {
createComponent,
createHeadAndContent,
renderComponent,
renderScriptElement,
renderTemplate,
renderUniqueStylesheet,
unescapeHTML
} from "../runtime/server/index.js";
function defineCollection(config) {
if (!config.type)
config.type = "content";
return config;
}
function createCollectionToGlobResultMap({
globResult,
contentDir
}) {
const collectionToGlobResultMap = {};
for (const key in globResult) {
const keyRelativeToContentDir = key.replace(new RegExp(`^${contentDir}`), "");
const segments = keyRelativeToContentDir.split("/");
if (segments.length <= 1)
continue;
const collection = segments[0];
collectionToGlobResultMap[collection] ??= {};
collectionToGlobResultMap[collection][key] = globResult[key];
}
return collectionToGlobResultMap;
}
const cacheEntriesByCollection = /* @__PURE__ */ new Map();
function createGetCollection({
contentCollectionToEntryMap,
dataCollectionToEntryMap,
getRenderEntryImport
}) {
return async function getCollection(collection, filter) {
let type;
if (collection in contentCollectionToEntryMap) {
type = "content";
} else if (collection in dataCollectionToEntryMap) {
type = "data";
} else {
console.warn(
`The collection ${JSON.stringify(
collection
)} does not exist or is empty. Ensure a collection directory with this name exists.`
);
return [];
}
const lazyImports = Object.values(
type === "content" ? contentCollectionToEntryMap[collection] : dataCollectionToEntryMap[collection]
);
let entries = [];
if (!import.meta.env?.DEV && cacheEntriesByCollection.has(collection)) {
entries = [...cacheEntriesByCollection.get(collection)];
} else {
const limit = pLimit(10);
entries = await Promise.all(
lazyImports.map(
(lazyImport) => limit(async () => {
const entry = await lazyImport();
return type === "content" ? {
id: entry.id,
slug: entry.slug,
body: entry.body,
collection: entry.collection,
data: entry.data,
async render() {
return render({
collection: entry.collection,
id: entry.id,
renderEntryImport: await getRenderEntryImport(collection, entry.slug)
});
}
} : {
id: entry.id,
collection: entry.collection,
data: entry.data
};
})
)
);
cacheEntriesByCollection.set(collection, entries);
}
if (typeof filter === "function") {
return entries.filter(filter);
} else {
return entries;
}
};
}
function createGetEntryBySlug({
getEntryImport,
getRenderEntryImport
}) {
return async function getEntryBySlug(collection, slug) {
const entryImport = await getEntryImport(collection, slug);
if (typeof entryImport !== "function")
return void 0;
const entry = await entryImport();
return {
id: entry.id,
slug: entry.slug,
body: entry.body,
collection: entry.collection,
data: entry.data,
async render() {
return render({
collection: entry.collection,
id: entry.id,
renderEntryImport: await getRenderEntryImport(collection, slug)
});
}
};
};
}
function createGetDataEntryById({ getEntryImport }) {
return async function getDataEntryById(collection, id) {
const lazyImport = await getEntryImport(collection, id);
if (!lazyImport)
throw new Error(`Entry ${collection} \u2192 ${id} was not found.`);
const entry = await lazyImport();
return {
id: entry.id,
collection: entry.collection,
data: entry.data
};
};
}
function createGetEntry({
getEntryImport,
getRenderEntryImport
}) {
return async function getEntry(collectionOrLookupObject, _lookupId) {
let collection, lookupId;
if (typeof collectionOrLookupObject === "string") {
collection = collectionOrLookupObject;
if (!_lookupId)
throw new AstroError({
...AstroErrorData.UnknownContentCollectionError,
message: "`getEntry()` requires an entry identifier as the second argument."
});
lookupId = _lookupId;
} else {
collection = collectionOrLookupObject.collection;
lookupId = "id" in collectionOrLookupObject ? collectionOrLookupObject.id : collectionOrLookupObject.slug;
}
const entryImport = await getEntryImport(collection, lookupId);
if (typeof entryImport !== "function")
return void 0;
const entry = await entryImport();
if (entry._internal.type === "content") {
return {
id: entry.id,
slug: entry.slug,
body: entry.body,
collection: entry.collection,
data: entry.data,
async render() {
return render({
collection: entry.collection,
id: entry.id,
renderEntryImport: await getRenderEntryImport(collection, lookupId)
});
}
};
} else if (entry._internal.type === "data") {
return {
id: entry.id,
collection: entry.collection,
data: entry.data
};
}
return void 0;
};
}
function createGetEntries(getEntry) {
return async function getEntries(entries) {
return Promise.all(entries.map((e) => getEntry(e)));
};
}
async function render({
collection,
id,
renderEntryImport
}) {
const UnexpectedRenderError = new AstroError({
...AstroErrorData.UnknownContentCollectionError,
message: `Unexpected error while rendering ${String(collection)} \u2192 ${String(id)}.`
});
if (typeof renderEntryImport !== "function")
throw UnexpectedRenderError;
const baseMod = await renderEntryImport();
if (baseMod == null || typeof baseMod !== "object")
throw UnexpectedRenderError;
const { default: defaultMod } = baseMod;
if (isPropagatedAssetsModule(defaultMod)) {
const { collectedStyles, collectedLinks, collectedScripts, getMod } = defaultMod;
if (typeof getMod !== "function")
throw UnexpectedRenderError;
const propagationMod = await getMod();
if (propagationMod == null || typeof propagationMod !== "object")
throw UnexpectedRenderError;
const Content = createComponent({
factory(result, baseProps, slots) {
let styles = "", links = "", scripts = "";
if (Array.isArray(collectedStyles)) {
styles = collectedStyles.map((style) => {
return renderUniqueStylesheet(result, {
type: "inline",
content: style
});
}).join("");
}
if (Array.isArray(collectedLinks)) {
links = collectedLinks.map((link) => {
return renderUniqueStylesheet(result, {
type: "external",
src: prependForwardSlash(link)
});
}).join("");
}
if (Array.isArray(collectedScripts)) {
scripts = collectedScripts.map((script) => renderScriptElement(script)).join("");
}
let props = baseProps;
if (id.endsWith("mdx")) {
props = {
components: propagationMod.components ?? {},
...baseProps
};
}
return createHeadAndContent(
unescapeHTML(styles + links + scripts),
renderTemplate`${renderComponent(
result,
"Content",
propagationMod.Content,
props,
slots
)}`
);
},
propagation: "self"
});
return {
Content,
headings: propagationMod.getHeadings?.() ?? [],
remarkPluginFrontmatter: propagationMod.frontmatter ?? {}
};
} else if (baseMod.Content && typeof baseMod.Content === "function") {
return {
Content: baseMod.Content,
headings: baseMod.getHeadings?.() ?? [],
remarkPluginFrontmatter: baseMod.frontmatter ?? {}
};
} else {
throw UnexpectedRenderError;
}
}
function createReference({ lookupMap }) {
return function reference(collection) {
return zodString().transform((lookupId, ctx) => {
const flattenedErrorPath = ctx.path.join(".");
if (!lookupMap[collection]) {
ctx.addIssue({
code: ZodIssueCode.custom,
message: `**${flattenedErrorPath}:** Reference to ${collection} invalid. Collection does not exist or is empty.`
});
return;
}
const { type, entries } = lookupMap[collection];
const entry = entries[lookupId];
if (!entry) {
ctx.addIssue({
code: ZodIssueCode.custom,
message: `**${flattenedErrorPath}**: Reference to ${collection} invalid. Expected ${Object.keys(
entries
).map((c) => JSON.stringify(c)).join(" | ")}. Received ${JSON.stringify(lookupId)}.`
});
return;
}
if (type === "content") {
return { slug: lookupId, collection };
}
return { id: lookupId, collection };
});
};
}
function isPropagatedAssetsModule(module) {
return typeof module === "object" && module != null && "__astroPropagation" in module;
}
export {
createCollectionToGlobResultMap,
createGetCollection,
createGetDataEntryById,
createGetEntries,
createGetEntry,
createGetEntryBySlug,
createReference,
defineCollection
};

13
node_modules/astro/dist/content/server-listeners.d.ts generated vendored Normal file
View File

@@ -0,0 +1,13 @@
/// <reference types="node" resolution-mode="require"/>
import type fsMod from 'node:fs';
import type { ViteDevServer } from 'vite';
import type { AstroSettings } from '../@types/astro.js';
import type { Logger } from '../core/logger/core.js';
interface ContentServerListenerParams {
fs: typeof fsMod;
logger: Logger;
settings: AstroSettings;
viteServer: ViteDevServer;
}
export declare function attachContentServerListeners({ viteServer, fs, logger, settings, }: ContentServerListenerParams): Promise<void>;
export {};

105
node_modules/astro/dist/content/server-listeners.js generated vendored Normal file
View File

@@ -0,0 +1,105 @@
import path from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { bold, cyan, underline } from "kleur/colors";
import { loadTSConfig } from "../core/config/tsconfig.js";
import { appendForwardSlash } from "../core/path.js";
import { createContentTypesGenerator } from "./types-generator.js";
import { getContentPaths, globalContentConfigObserver } from "./utils.js";
async function attachContentServerListeners({
viteServer,
fs,
logger,
settings
}) {
const contentPaths = getContentPaths(settings.config, fs);
if (fs.existsSync(contentPaths.contentDir)) {
logger.debug(
"content",
`Watching ${cyan(
contentPaths.contentDir.href.replace(settings.config.root.href, "")
)} for changes`
);
const maybeTsConfigStats = await getTSConfigStatsWhenAllowJsFalse({ contentPaths, settings });
if (maybeTsConfigStats)
warnAllowJsIsFalse({ ...maybeTsConfigStats, logger });
await attachListeners();
} else {
viteServer.watcher.on("addDir", contentDirListener);
async function contentDirListener(dir) {
if (appendForwardSlash(pathToFileURL(dir).href) === contentPaths.contentDir.href) {
logger.debug("content", `Content directory found. Watching for changes`);
await attachListeners();
viteServer.watcher.removeListener("addDir", contentDirListener);
}
}
}
async function attachListeners() {
const contentGenerator = await createContentTypesGenerator({
fs,
settings,
logger,
viteServer,
contentConfigObserver: globalContentConfigObserver
});
await contentGenerator.init();
logger.debug("content", "Types generated");
viteServer.watcher.on("add", (entry) => {
contentGenerator.queueEvent({ name: "add", entry });
});
viteServer.watcher.on(
"addDir",
(entry) => contentGenerator.queueEvent({ name: "addDir", entry })
);
viteServer.watcher.on(
"change",
(entry) => contentGenerator.queueEvent({ name: "change", entry })
);
viteServer.watcher.on("unlink", (entry) => {
contentGenerator.queueEvent({ name: "unlink", entry });
});
viteServer.watcher.on(
"unlinkDir",
(entry) => contentGenerator.queueEvent({ name: "unlinkDir", entry })
);
}
}
function warnAllowJsIsFalse({
logger,
tsConfigFileName,
contentConfigFileName
}) {
logger.warn(
"content",
`Make sure you have the ${bold("allowJs")} compiler option set to ${bold(
"true"
)} in your ${bold(tsConfigFileName)} file to have autocompletion in your ${bold(
contentConfigFileName
)} file. See ${underline(
cyan("https://www.typescriptlang.org/tsconfig#allowJs")
)} for more information.`
);
}
async function getTSConfigStatsWhenAllowJsFalse({
contentPaths,
settings
}) {
const isContentConfigJsFile = [".js", ".mjs"].some(
(ext) => contentPaths.config.url.pathname.endsWith(ext)
);
if (!isContentConfigJsFile)
return;
const inputConfig = await loadTSConfig(fileURLToPath(settings.config.root));
if (typeof inputConfig === "string")
return;
const tsConfigFileName = inputConfig.tsconfigFile.split(path.sep).pop();
if (!tsConfigFileName)
return;
const contentConfigFileName = contentPaths.config.url.pathname.split(path.sep).pop();
const allowJSOption = inputConfig.tsconfig.compilerOptions?.allowJs;
if (allowJSOption)
return;
return { tsConfigFileName, contentConfigFileName };
}
export {
attachContentServerListeners
};

29
node_modules/astro/dist/content/types-generator.d.ts generated vendored Normal file
View File

@@ -0,0 +1,29 @@
/// <reference types="node" resolution-mode="require"/>
import type fsMod from 'node:fs';
import { type ViteDevServer } from 'vite';
import type { AstroSettings } from '../@types/astro.js';
import type { Logger } from '../core/logger/core.js';
import { type ContentObservable } from './utils.js';
type ChokidarEvent = 'add' | 'addDir' | 'change' | 'unlink' | 'unlinkDir';
type RawContentEvent = {
name: ChokidarEvent;
entry: string;
};
type CreateContentGeneratorParams = {
contentConfigObserver: ContentObservable;
logger: Logger;
settings: AstroSettings;
/** This is required for loading the content config */
viteServer: ViteDevServer;
fs: typeof fsMod;
};
export declare function createContentTypesGenerator({ contentConfigObserver, fs, logger, settings, viteServer, }: CreateContentGeneratorParams): Promise<{
init: () => Promise<{
typesGenerated: true;
} | {
typesGenerated: false;
reason: 'no-content-dir';
}>;
queueEvent: (rawEvent: RawContentEvent) => void;
}>;
export {};

420
node_modules/astro/dist/content/types-generator.js generated vendored Normal file
View File

@@ -0,0 +1,420 @@
import * as path from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import glob from "fast-glob";
import { bold, cyan } from "kleur/colors";
import { normalizePath } from "vite";
import { z } from "zod";
import { zodToJsonSchema } from "zod-to-json-schema";
import { AstroError } from "../core/errors/errors.js";
import { AstroErrorData } from "../core/errors/index.js";
import { isRelativePath } from "../core/path.js";
import { CONTENT_TYPES_FILE, VIRTUAL_MODULE_ID } from "./consts.js";
import {
getContentEntryIdAndSlug,
getContentPaths,
getDataEntryExts,
getDataEntryId,
getEntryCollectionName,
getEntryConfigByExtMap,
getEntrySlug,
getEntryType,
reloadContentConfigObserver
} from "./utils.js";
async function createContentTypesGenerator({
contentConfigObserver,
fs,
logger,
settings,
viteServer
}) {
const collectionEntryMap = {};
const contentPaths = getContentPaths(settings.config, fs);
const contentEntryConfigByExt = getEntryConfigByExtMap(settings.contentEntryTypes);
const contentEntryExts = [...contentEntryConfigByExt.keys()];
const dataEntryExts = getDataEntryExts(settings);
let events = [];
let debounceTimeout;
const typeTemplateContent = await fs.promises.readFile(contentPaths.typesTemplate, "utf-8");
async function init() {
if (!fs.existsSync(contentPaths.contentDir)) {
return { typesGenerated: false, reason: "no-content-dir" };
}
events.push({ name: "add", entry: contentPaths.config.url });
const globResult = await glob("**", {
cwd: fileURLToPath(contentPaths.contentDir),
fs: {
readdir: fs.readdir.bind(fs),
readdirSync: fs.readdirSync.bind(fs)
},
onlyFiles: false,
objectMode: true
});
for (const entry of globResult) {
const fullPath = path.join(fileURLToPath(contentPaths.contentDir), entry.path);
const entryURL = pathToFileURL(fullPath);
if (entryURL.href.startsWith(contentPaths.config.url.href))
continue;
if (entry.dirent.isFile()) {
events.push({ name: "add", entry: entryURL });
} else if (entry.dirent.isDirectory()) {
events.push({ name: "addDir", entry: entryURL });
}
}
await runEvents();
return { typesGenerated: true };
}
async function handleEvent(event) {
if (event.name === "addDir" || event.name === "unlinkDir") {
const collection2 = normalizePath(
path.relative(fileURLToPath(contentPaths.contentDir), fileURLToPath(event.entry))
);
const collectionKey2 = JSON.stringify(collection2);
const isCollectionEvent = collection2.split("/").length === 1;
if (!isCollectionEvent)
return { shouldGenerateTypes: false };
switch (event.name) {
case "addDir":
collectionEntryMap[JSON.stringify(collection2)] = {
type: "unknown",
entries: {}
};
logger.debug("content", `${cyan(collection2)} collection added`);
break;
case "unlinkDir":
if (collectionKey2 in collectionEntryMap) {
delete collectionEntryMap[JSON.stringify(collection2)];
}
break;
}
return { shouldGenerateTypes: true };
}
const fileType = getEntryType(
fileURLToPath(event.entry),
contentPaths,
contentEntryExts,
dataEntryExts
);
if (fileType === "ignored") {
return { shouldGenerateTypes: false };
}
if (fileType === "config") {
await reloadContentConfigObserver({ fs, settings, viteServer });
return { shouldGenerateTypes: true };
}
const { entry } = event;
const { contentDir } = contentPaths;
const collection = getEntryCollectionName({ entry, contentDir });
if (collection === void 0) {
logger.warn(
"content",
`${bold(
normalizePath(
path.relative(fileURLToPath(contentPaths.contentDir), fileURLToPath(event.entry))
)
)} must live in a ${bold("content/...")} collection subdirectory.`
);
return { shouldGenerateTypes: false };
}
if (fileType === "data") {
const id2 = getDataEntryId({ entry, contentDir, collection });
const collectionKey2 = JSON.stringify(collection);
const entryKey2 = JSON.stringify(id2);
switch (event.name) {
case "add":
if (!(collectionKey2 in collectionEntryMap)) {
collectionEntryMap[collectionKey2] = { type: "data", entries: {} };
}
const collectionInfo2 = collectionEntryMap[collectionKey2];
if (collectionInfo2.type === "content") {
viteServer.hot.send({
type: "error",
err: new AstroError({
...AstroErrorData.MixedContentDataCollectionError,
message: AstroErrorData.MixedContentDataCollectionError.message(collectionKey2),
location: { file: entry.pathname }
})
});
return { shouldGenerateTypes: false };
}
if (!(entryKey2 in collectionEntryMap[collectionKey2])) {
collectionEntryMap[collectionKey2] = {
type: "data",
entries: { ...collectionInfo2.entries, [entryKey2]: {} }
};
}
return { shouldGenerateTypes: true };
case "unlink":
if (collectionKey2 in collectionEntryMap && entryKey2 in collectionEntryMap[collectionKey2].entries) {
delete collectionEntryMap[collectionKey2].entries[entryKey2];
}
return { shouldGenerateTypes: true };
case "change":
return { shouldGenerateTypes: false };
}
}
const contentEntryType = contentEntryConfigByExt.get(path.extname(event.entry.pathname));
if (!contentEntryType)
return { shouldGenerateTypes: false };
const { id, slug: generatedSlug } = getContentEntryIdAndSlug({
entry,
contentDir,
collection
});
const collectionKey = JSON.stringify(collection);
if (!(collectionKey in collectionEntryMap)) {
collectionEntryMap[collectionKey] = { type: "content", entries: {} };
}
const collectionInfo = collectionEntryMap[collectionKey];
if (collectionInfo.type === "data") {
viteServer.hot.send({
type: "error",
err: new AstroError({
...AstroErrorData.MixedContentDataCollectionError,
message: AstroErrorData.MixedContentDataCollectionError.message(collectionKey),
location: { file: entry.pathname }
})
});
return { shouldGenerateTypes: false };
}
const entryKey = JSON.stringify(id);
switch (event.name) {
case "add":
const addedSlug = await getEntrySlug({
generatedSlug,
id,
collection,
fileUrl: event.entry,
contentEntryType,
fs
});
if (!(entryKey in collectionEntryMap[collectionKey].entries)) {
collectionEntryMap[collectionKey] = {
type: "content",
entries: {
...collectionInfo.entries,
[entryKey]: { slug: addedSlug }
}
};
}
return { shouldGenerateTypes: true };
case "unlink":
if (collectionKey in collectionEntryMap && entryKey in collectionEntryMap[collectionKey].entries) {
delete collectionEntryMap[collectionKey].entries[entryKey];
}
return { shouldGenerateTypes: true };
case "change":
const changedSlug = await getEntrySlug({
generatedSlug,
id,
collection,
fileUrl: event.entry,
contentEntryType,
fs
});
const entryMetadata = collectionInfo.entries[entryKey];
if (entryMetadata?.slug !== changedSlug) {
collectionInfo.entries[entryKey].slug = changedSlug;
return { shouldGenerateTypes: true };
}
return { shouldGenerateTypes: false };
}
}
function queueEvent(rawEvent) {
const event = {
entry: pathToFileURL(rawEvent.entry),
name: rawEvent.name
};
if (!event.entry.pathname.startsWith(contentPaths.contentDir.pathname))
return;
events.push(event);
debounceTimeout && clearTimeout(debounceTimeout);
const runEventsSafe = async () => {
try {
await runEvents();
} catch {
}
};
debounceTimeout = setTimeout(
runEventsSafe,
50
/* debounce to batch chokidar events */
);
}
async function runEvents() {
const eventResponses = [];
for (const event of events) {
const response = await handleEvent(event);
eventResponses.push(response);
}
events = [];
const observable = contentConfigObserver.get();
if (eventResponses.some((r) => r.shouldGenerateTypes)) {
await writeContentFiles({
fs,
collectionEntryMap,
contentPaths,
typeTemplateContent,
contentConfig: observable.status === "loaded" ? observable.config : void 0,
contentEntryTypes: settings.contentEntryTypes,
viteServer,
logger,
settings
});
invalidateVirtualMod(viteServer);
}
}
return { init, queueEvent };
}
function invalidateVirtualMod(viteServer) {
const virtualMod = viteServer.moduleGraph.getModuleById("\0" + VIRTUAL_MODULE_ID);
if (!virtualMod)
return;
viteServer.moduleGraph.invalidateModule(virtualMod);
}
function normalizeConfigPath(from, to) {
const configPath = path.relative(from, to).replace(/\.ts$/, ".js");
const normalizedPath = configPath.replaceAll("\\", "/");
return `"${isRelativePath(configPath) ? "" : "./"}${normalizedPath}"`;
}
async function writeContentFiles({
fs,
contentPaths,
collectionEntryMap,
typeTemplateContent,
contentEntryTypes,
contentConfig,
viteServer,
logger,
settings
}) {
let contentTypesStr = "";
let dataTypesStr = "";
const collectionSchemasDir = new URL("./collections/", contentPaths.cacheDir);
if (settings.config.experimental.contentCollectionJsonSchema && !fs.existsSync(collectionSchemasDir)) {
fs.mkdirSync(collectionSchemasDir, { recursive: true });
}
for (const [collection, config] of Object.entries(contentConfig?.collections ?? {})) {
collectionEntryMap[JSON.stringify(collection)] ??= {
type: config.type,
entries: {}
};
}
for (const collectionKey of Object.keys(collectionEntryMap).sort()) {
const collectionConfig = contentConfig?.collections[JSON.parse(collectionKey)];
const collection = collectionEntryMap[collectionKey];
if (collectionConfig?.type && collection.type !== "unknown" && collection.type !== collectionConfig.type) {
viteServer.hot.send({
type: "error",
err: new AstroError({
...AstroErrorData.ContentCollectionTypeMismatchError,
message: AstroErrorData.ContentCollectionTypeMismatchError.message(
collectionKey,
collection.type,
collectionConfig.type
),
hint: collection.type === "data" ? "Try adding `type: 'data'` to your collection config." : void 0,
location: {
file: ""
}
})
});
return;
}
const resolvedType = collection.type === "unknown" ? (
// Add empty / unknown collections to the data type map by default
// This ensures `getCollection('empty-collection')` doesn't raise a type error
collectionConfig?.type ?? "data"
) : collection.type;
switch (resolvedType) {
case "content":
contentTypesStr += `${collectionKey}: {
`;
for (const entryKey of Object.keys(collection.entries).sort()) {
const entryMetadata = collection.entries[entryKey];
const dataType = collectionConfig?.schema ? `InferEntrySchema<${collectionKey}>` : "any";
const renderType = `{ render(): Render[${JSON.stringify(
path.extname(JSON.parse(entryKey))
)}] }`;
const slugType = JSON.stringify(entryMetadata.slug);
contentTypesStr += `${entryKey}: {
id: ${entryKey};
slug: ${slugType};
body: string;
collection: ${collectionKey};
data: ${dataType}
} & ${renderType};
`;
}
contentTypesStr += `};
`;
break;
case "data":
dataTypesStr += `${collectionKey}: {
`;
for (const entryKey of Object.keys(collection.entries).sort()) {
const dataType = collectionConfig?.schema ? `InferEntrySchema<${collectionKey}>` : "any";
dataTypesStr += `${entryKey}: {
id: ${entryKey};
collection: ${collectionKey};
data: ${dataType}
};
`;
if (settings.config.experimental.contentCollectionJsonSchema && collectionConfig?.schema) {
let zodSchemaForJson = typeof collectionConfig.schema === "function" ? collectionConfig.schema({ image: () => z.string() }) : collectionConfig.schema;
if (zodSchemaForJson instanceof z.ZodObject) {
zodSchemaForJson = zodSchemaForJson.extend({
$schema: z.string().optional()
});
}
try {
await fs.promises.writeFile(
new URL(`./${collectionKey.replace(/"/g, "")}.schema.json`, collectionSchemasDir),
JSON.stringify(
zodToJsonSchema(zodSchemaForJson, {
name: collectionKey.replace(/"/g, ""),
markdownDescription: true,
errorMessages: true
}),
null,
2
)
);
} catch (err) {
logger.warn(
"content",
`An error was encountered while creating the JSON schema for the ${entryKey} entry in ${collectionKey} collection. Proceeding without it. Error: ${err}`
);
}
}
}
dataTypesStr += `};
`;
break;
}
}
if (!fs.existsSync(contentPaths.cacheDir)) {
fs.mkdirSync(contentPaths.cacheDir, { recursive: true });
}
const configPathRelativeToCacheDir = normalizeConfigPath(
contentPaths.cacheDir.pathname,
contentPaths.config.url.pathname
);
for (const contentEntryType of contentEntryTypes) {
if (contentEntryType.contentModuleTypes) {
typeTemplateContent = contentEntryType.contentModuleTypes + "\n" + typeTemplateContent;
}
}
typeTemplateContent = typeTemplateContent.replace("// @@CONTENT_ENTRY_MAP@@", contentTypesStr);
typeTemplateContent = typeTemplateContent.replace("// @@DATA_ENTRY_MAP@@", dataTypesStr);
typeTemplateContent = typeTemplateContent.replace(
"'@@CONTENT_CONFIG_TYPE@@'",
contentConfig ? `typeof import(${configPathRelativeToCacheDir})` : "never"
);
await fs.promises.writeFile(
new URL(CONTENT_TYPES_FILE, contentPaths.cacheDir),
typeTemplateContent
);
}
export {
createContentTypesGenerator
};

188
node_modules/astro/dist/content/utils.d.ts generated vendored Normal file
View File

@@ -0,0 +1,188 @@
/// <reference types="node" resolution-mode="require"/>
import fsMod from 'node:fs';
import matter from 'gray-matter';
import type { PluginContext } from 'rollup';
import { type ViteDevServer } from 'vite';
import { z } from 'zod';
import type { AstroConfig, AstroSettings, ContentEntryType, DataEntryType } from '../@types/astro.js';
import { CONTENT_FLAGS } from './consts.js';
/**
* Amap from a collection + slug to the local file path.
* This is used internally to resolve entry imports when using `getEntry()`.
* @see `content-module.template.mjs`
*/
export type ContentLookupMap = {
[collectionName: string]: {
type: 'content' | 'data';
entries: {
[lookupId: string]: string;
};
};
};
export declare const collectionConfigParser: z.ZodUnion<[z.ZodObject<{
type: z.ZodDefault<z.ZodOptional<z.ZodLiteral<"content">>>;
schema: z.ZodOptional<z.ZodAny>;
}, "strip", z.ZodTypeAny, {
type: "content";
schema?: any;
}, {
type?: "content" | undefined;
schema?: any;
}>, z.ZodObject<{
type: z.ZodLiteral<"data">;
schema: z.ZodOptional<z.ZodAny>;
}, "strip", z.ZodTypeAny, {
type: "data";
schema?: any;
}, {
type: "data";
schema?: any;
}>]>;
export declare function getDotAstroTypeReference({ root, srcDir }: {
root: URL;
srcDir: URL;
}): string;
export declare const contentConfigParser: z.ZodObject<{
collections: z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodObject<{
type: z.ZodDefault<z.ZodOptional<z.ZodLiteral<"content">>>;
schema: z.ZodOptional<z.ZodAny>;
}, "strip", z.ZodTypeAny, {
type: "content";
schema?: any;
}, {
type?: "content" | undefined;
schema?: any;
}>, z.ZodObject<{
type: z.ZodLiteral<"data">;
schema: z.ZodOptional<z.ZodAny>;
}, "strip", z.ZodTypeAny, {
type: "data";
schema?: any;
}, {
type: "data";
schema?: any;
}>]>>;
}, "strip", z.ZodTypeAny, {
collections: Record<string, {
type: "content";
schema?: any;
} | {
type: "data";
schema?: any;
}>;
}, {
collections: Record<string, {
type?: "content" | undefined;
schema?: any;
} | {
type: "data";
schema?: any;
}>;
}>;
export type CollectionConfig = z.infer<typeof collectionConfigParser>;
export type ContentConfig = z.infer<typeof contentConfigParser>;
type EntryInternal = {
rawData: string | undefined;
filePath: string;
};
export declare const msg: {
collectionConfigMissing: (collection: string) => string;
};
export declare function parseEntrySlug({ id, collection, generatedSlug, frontmatterSlug, }: {
id: string;
collection: string;
generatedSlug: string;
frontmatterSlug?: unknown;
}): string;
export declare function getEntryData(entry: {
id: string;
collection: string;
unvalidatedData: Record<string, unknown>;
_internal: EntryInternal;
}, collectionConfig: CollectionConfig, shouldEmitFile: boolean, pluginContext: PluginContext): Promise<Record<string, unknown>>;
export declare function getContentEntryExts(settings: Pick<AstroSettings, 'contentEntryTypes'>): string[];
export declare function getDataEntryExts(settings: Pick<AstroSettings, 'dataEntryTypes'>): string[];
export declare function getEntryConfigByExtMap<TEntryType extends ContentEntryType | DataEntryType>(entryTypes: TEntryType[]): Map<string, TEntryType>;
export declare function getEntryCollectionName({ contentDir, entry, }: Pick<ContentPaths, 'contentDir'> & {
entry: string | URL;
}): string | undefined;
export declare function getDataEntryId({ entry, contentDir, collection, }: Pick<ContentPaths, 'contentDir'> & {
entry: URL;
collection: string;
}): string;
export declare function getContentEntryIdAndSlug({ entry, contentDir, collection, }: Pick<ContentPaths, 'contentDir'> & {
entry: URL;
collection: string;
}): {
id: string;
slug: string;
};
export declare function getEntryType(entryPath: string, paths: Pick<ContentPaths, 'config' | 'contentDir'>, contentFileExts: string[], dataFileExts: string[]): 'content' | 'data' | 'config' | 'ignored';
export declare function hasUnderscoreBelowContentDirectoryPath(fileUrl: URL, contentDir: ContentPaths['contentDir']): boolean;
export declare function safeParseFrontmatter(source: string, id?: string): matter.GrayMatterFile<string>;
/**
* The content config is loaded separately from other `src/` files.
* This global observable lets dependent plugins (like the content flag plugin)
* subscribe to changes during dev server updates.
*/
export declare const globalContentConfigObserver: ContentObservable;
export declare function hasAnyContentFlag(viteId: string): boolean;
export declare function hasContentFlag(viteId: string, flag: (typeof CONTENT_FLAGS)[number]): boolean;
export declare function loadContentConfig({ fs, settings, viteServer, }: {
fs: typeof fsMod;
settings: AstroSettings;
viteServer: ViteDevServer;
}): Promise<ContentConfig | undefined>;
export declare function reloadContentConfigObserver({ observer, ...loadContentConfigOpts }: {
fs: typeof fsMod;
settings: AstroSettings;
viteServer: ViteDevServer;
observer?: ContentObservable;
}): Promise<void>;
type ContentCtx = {
status: 'init';
} | {
status: 'loading';
} | {
status: 'does-not-exist';
} | {
status: 'loaded';
config: ContentConfig;
} | {
status: 'error';
error: Error;
};
type Observable<C> = {
get: () => C;
set: (ctx: C) => void;
subscribe: (fn: (ctx: C) => void) => () => void;
};
export type ContentObservable = Observable<ContentCtx>;
export declare function contentObservable(initialCtx: ContentCtx): ContentObservable;
export type ContentPaths = {
contentDir: URL;
assetsDir: URL;
cacheDir: URL;
typesTemplate: URL;
virtualModTemplate: URL;
config: {
exists: boolean;
url: URL;
};
};
export declare function getContentPaths({ srcDir, root }: Pick<AstroConfig, 'root' | 'srcDir'>, fs?: typeof fsMod): ContentPaths;
/**
* Check for slug in content entry frontmatter and validate the type,
* falling back to the `generatedSlug` if none is found.
*/
export declare function getEntrySlug({ id, collection, generatedSlug, contentEntryType, fileUrl, fs, }: {
fs: typeof fsMod;
id: string;
collection: string;
generatedSlug: string;
fileUrl: URL;
contentEntryType: Pick<ContentEntryType, 'getEntryInfo'>;
}): Promise<string>;
export declare function getExtGlob(exts: string[]): string;
export declare function hasAssetPropagationFlag(id: string): boolean;
export {};

379
node_modules/astro/dist/content/utils.js generated vendored Normal file
View File

@@ -0,0 +1,379 @@
import fsMod from "node:fs";
import path from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { slug as githubSlug } from "github-slugger";
import matter from "gray-matter";
import { normalizePath } from "vite";
import { z } from "zod";
import { AstroError, AstroErrorData, MarkdownError, errorMap } from "../core/errors/index.js";
import { isYAMLException } from "../core/errors/utils.js";
import { CONTENT_FLAGS, CONTENT_TYPES_FILE, PROPAGATED_ASSET_FLAG } from "./consts.js";
import { createImage } from "./runtime-assets.js";
const collectionConfigParser = z.union([
z.object({
type: z.literal("content").optional().default("content"),
schema: z.any().optional()
}),
z.object({
type: z.literal("data"),
schema: z.any().optional()
})
]);
function getDotAstroTypeReference({ root, srcDir }) {
const { cacheDir } = getContentPaths({ root, srcDir });
const contentTypesRelativeToSrcDir = normalizePath(
path.relative(fileURLToPath(srcDir), fileURLToPath(new URL(CONTENT_TYPES_FILE, cacheDir)))
);
return `/// <reference path=${JSON.stringify(contentTypesRelativeToSrcDir)} />`;
}
const contentConfigParser = z.object({
collections: z.record(collectionConfigParser)
});
const msg = {
collectionConfigMissing: (collection) => `${collection} does not have a config. We suggest adding one for type safety!`
};
function parseEntrySlug({
id,
collection,
generatedSlug,
frontmatterSlug
}) {
try {
return z.string().default(generatedSlug).parse(frontmatterSlug);
} catch {
throw new AstroError({
...AstroErrorData.InvalidContentEntrySlugError,
message: AstroErrorData.InvalidContentEntrySlugError.message(collection, id)
});
}
}
async function getEntryData(entry, collectionConfig, shouldEmitFile, pluginContext) {
let data;
if (collectionConfig.type === "data") {
data = entry.unvalidatedData;
} else {
const { slug, ...unvalidatedData } = entry.unvalidatedData;
data = unvalidatedData;
}
let schema = collectionConfig.schema;
if (typeof schema === "function") {
schema = schema({
image: createImage(pluginContext, shouldEmitFile, entry._internal.filePath)
});
}
if (schema) {
if (collectionConfig.type === "content" && typeof schema === "object" && "shape" in schema && schema.shape.slug) {
throw new AstroError({
...AstroErrorData.ContentSchemaContainsSlugError,
message: AstroErrorData.ContentSchemaContainsSlugError.message(entry.collection)
});
}
let formattedError;
const parsed = await schema.safeParseAsync(data, {
errorMap(error, ctx) {
if (error.code === "custom" && error.params?.isHoistedAstroError) {
formattedError = error.params?.astroError;
}
return errorMap(error, ctx);
}
});
if (parsed.success) {
data = parsed.data;
} else {
if (!formattedError) {
formattedError = new AstroError({
...AstroErrorData.InvalidContentEntryFrontmatterError,
message: AstroErrorData.InvalidContentEntryFrontmatterError.message(
entry.collection,
entry.id,
parsed.error
),
location: {
file: entry._internal.filePath,
line: getYAMLErrorLine(entry._internal.rawData, String(parsed.error.errors[0].path[0])),
column: 0
}
});
}
throw formattedError;
}
}
return data;
}
function getContentEntryExts(settings) {
return settings.contentEntryTypes.map((t) => t.extensions).flat();
}
function getDataEntryExts(settings) {
return settings.dataEntryTypes.map((t) => t.extensions).flat();
}
function getEntryConfigByExtMap(entryTypes) {
const map = /* @__PURE__ */ new Map();
for (const entryType of entryTypes) {
for (const ext of entryType.extensions) {
map.set(ext, entryType);
}
}
return map;
}
function getEntryCollectionName({
contentDir,
entry
}) {
const entryPath = typeof entry === "string" ? entry : fileURLToPath(entry);
const rawRelativePath = path.relative(fileURLToPath(contentDir), entryPath);
const collectionName = path.dirname(rawRelativePath).split(path.sep)[0];
const isOutsideCollection = !collectionName || collectionName === "" || collectionName === ".." || collectionName === ".";
if (isOutsideCollection) {
return void 0;
}
return collectionName;
}
function getDataEntryId({
entry,
contentDir,
collection
}) {
const relativePath = getRelativeEntryPath(entry, collection, contentDir);
const withoutFileExt = normalizePath(relativePath).replace(
new RegExp(path.extname(relativePath) + "$"),
""
);
return withoutFileExt;
}
function getContentEntryIdAndSlug({
entry,
contentDir,
collection
}) {
const relativePath = getRelativeEntryPath(entry, collection, contentDir);
const withoutFileExt = relativePath.replace(new RegExp(path.extname(relativePath) + "$"), "");
const rawSlugSegments = withoutFileExt.split(path.sep);
const slug = rawSlugSegments.map((segment) => githubSlug(segment)).join("/").replace(/\/index$/, "");
const res = {
id: normalizePath(relativePath),
slug
};
return res;
}
function getRelativeEntryPath(entry, collection, contentDir) {
const relativeToContent = path.relative(fileURLToPath(contentDir), fileURLToPath(entry));
const relativeToCollection = path.relative(collection, relativeToContent);
return relativeToCollection;
}
function getEntryType(entryPath, paths, contentFileExts, dataFileExts) {
const { ext } = path.parse(entryPath);
const fileUrl = pathToFileURL(entryPath);
if (hasUnderscoreBelowContentDirectoryPath(fileUrl, paths.contentDir)) {
return "ignored";
} else if (contentFileExts.includes(ext)) {
return "content";
} else if (dataFileExts.includes(ext)) {
return "data";
} else if (fileUrl.href === paths.config.url.href) {
return "config";
} else {
return "ignored";
}
}
function hasUnderscoreBelowContentDirectoryPath(fileUrl, contentDir) {
const parts = fileUrl.pathname.replace(contentDir.pathname, "").split("/");
for (const part of parts) {
if (part.startsWith("_"))
return true;
}
return false;
}
function getYAMLErrorLine(rawData, objectKey) {
if (!rawData)
return 0;
const indexOfObjectKey = rawData.search(
// Match key either at the top of the file or after a newline
// Ensures matching on top-level object keys only
new RegExp(`(
|^)${objectKey}`)
);
if (indexOfObjectKey === -1)
return 0;
const dataBeforeKey = rawData.substring(0, indexOfObjectKey + 1);
const numNewlinesBeforeKey = dataBeforeKey.split("\n").length;
return numNewlinesBeforeKey;
}
function safeParseFrontmatter(source, id) {
try {
return matter(source);
} catch (err) {
const markdownError = new MarkdownError({
name: "MarkdownError",
message: err.message,
stack: err.stack,
location: id ? {
file: id
} : void 0
});
if (isYAMLException(err)) {
markdownError.setLocation({
file: id,
line: err.mark.line,
column: err.mark.column
});
markdownError.setMessage(err.reason);
}
throw markdownError;
}
}
const globalContentConfigObserver = contentObservable({ status: "init" });
function hasAnyContentFlag(viteId) {
const flags = new URLSearchParams(viteId.split("?")[1] ?? "");
const flag = Array.from(flags.keys()).at(0);
if (typeof flag !== "string") {
return false;
}
return CONTENT_FLAGS.includes(flag);
}
function hasContentFlag(viteId, flag) {
const flags = new URLSearchParams(viteId.split("?")[1] ?? "");
return flags.has(flag);
}
async function loadContentConfig({
fs,
settings,
viteServer
}) {
const contentPaths = getContentPaths(settings.config, fs);
let unparsedConfig;
if (!contentPaths.config.exists) {
return void 0;
}
const configPathname = fileURLToPath(contentPaths.config.url);
unparsedConfig = await viteServer.ssrLoadModule(configPathname);
const config = contentConfigParser.safeParse(unparsedConfig);
if (config.success) {
return config.data;
} else {
return void 0;
}
}
async function reloadContentConfigObserver({
observer = globalContentConfigObserver,
...loadContentConfigOpts
}) {
observer.set({ status: "loading" });
try {
const config = await loadContentConfig(loadContentConfigOpts);
if (config) {
observer.set({ status: "loaded", config });
} else {
observer.set({ status: "does-not-exist" });
}
} catch (e) {
observer.set({
status: "error",
error: e instanceof Error ? e : new AstroError(AstroErrorData.UnknownContentCollectionError)
});
}
}
function contentObservable(initialCtx) {
const subscribers = /* @__PURE__ */ new Set();
let ctx = initialCtx;
function get() {
return ctx;
}
function set(_ctx) {
ctx = _ctx;
subscribers.forEach((fn) => fn(ctx));
}
function subscribe(fn) {
subscribers.add(fn);
return () => {
subscribers.delete(fn);
};
}
return {
get,
set,
subscribe
};
}
function getContentPaths({ srcDir, root }, fs = fsMod) {
const configStats = search(fs, srcDir);
const pkgBase = new URL("../../", import.meta.url);
return {
cacheDir: new URL(".astro/", root),
contentDir: new URL("./content/", srcDir),
assetsDir: new URL("./assets/", srcDir),
typesTemplate: new URL("content-types.template.d.ts", pkgBase),
virtualModTemplate: new URL("content-module.template.mjs", pkgBase),
config: configStats
};
}
function search(fs, srcDir) {
const paths = ["config.mjs", "config.js", "config.mts", "config.ts"].map(
(p) => new URL(`./content/${p}`, srcDir)
);
for (const file of paths) {
if (fs.existsSync(file)) {
return { exists: true, url: file };
}
}
return { exists: false, url: paths[0] };
}
async function getEntrySlug({
id,
collection,
generatedSlug,
contentEntryType,
fileUrl,
fs
}) {
let contents;
try {
contents = await fs.promises.readFile(fileUrl, "utf-8");
} catch (e) {
throw new AstroError(AstroErrorData.UnknownContentCollectionError, { cause: e });
}
const { slug: frontmatterSlug } = await contentEntryType.getEntryInfo({
fileUrl,
contents
});
return parseEntrySlug({ generatedSlug, frontmatterSlug, id, collection });
}
function getExtGlob(exts) {
return exts.length === 1 ? (
// Wrapping {...} breaks when there is only one extension
exts[0]
) : `{${exts.join(",")}}`;
}
function hasAssetPropagationFlag(id) {
try {
return new URL(id, "file://").searchParams.has(PROPAGATED_ASSET_FLAG);
} catch {
return false;
}
}
export {
collectionConfigParser,
contentConfigParser,
contentObservable,
getContentEntryExts,
getContentEntryIdAndSlug,
getContentPaths,
getDataEntryExts,
getDataEntryId,
getDotAstroTypeReference,
getEntryCollectionName,
getEntryConfigByExtMap,
getEntryData,
getEntrySlug,
getEntryType,
getExtGlob,
globalContentConfigObserver,
hasAnyContentFlag,
hasAssetPropagationFlag,
hasContentFlag,
hasUnderscoreBelowContentDirectoryPath,
loadContentConfig,
msg,
parseEntrySlug,
reloadContentConfigObserver,
safeParseFrontmatter
};

View File

@@ -0,0 +1,10 @@
import type { Plugin } from 'vite';
import type { AstroSettings } from '../@types/astro.js';
import { type BuildInternals } from '../core/build/internal.js';
import type { AstroBuildPlugin } from '../core/build/plugin.js';
import type { StaticBuildOptions } from '../core/build/types.js';
export declare function astroContentAssetPropagationPlugin({ mode, settings, }: {
mode: string;
settings: AstroSettings;
}): Plugin;
export declare function astroConfigBuildPlugin(options: StaticBuildOptions, internals: BuildInternals): AstroBuildPlugin;

View File

@@ -0,0 +1,231 @@
import { extname } from "node:path";
import { pathToFileURL } from "node:url";
import { getAssetsPrefix } from "../assets/utils/getAssetsPrefix.js";
import { getParentModuleInfos, moduleIsTopLevelPage } from "../core/build/graph.js";
import { getPageDataByViteID } from "../core/build/internal.js";
import { createViteLoader } from "../core/module-loader/vite.js";
import { joinPaths, prependForwardSlash } from "../core/path.js";
import { getStylesForURL } from "../vite-plugin-astro-server/css.js";
import { getScriptsForURL } from "../vite-plugin-astro-server/scripts.js";
import {
CONTENT_RENDER_FLAG,
LINKS_PLACEHOLDER,
PROPAGATED_ASSET_FLAG,
SCRIPTS_PLACEHOLDER,
STYLES_PLACEHOLDER
} from "./consts.js";
import { hasContentFlag } from "./utils.js";
function astroContentAssetPropagationPlugin({
mode,
settings
}) {
let devModuleLoader;
return {
name: "astro:content-asset-propagation",
enforce: "pre",
async resolveId(id, importer, opts) {
if (hasContentFlag(id, CONTENT_RENDER_FLAG)) {
const base = id.split("?")[0];
for (const { extensions, handlePropagation = true } of settings.contentEntryTypes) {
if (handlePropagation && extensions.includes(extname(base))) {
return this.resolve(`${base}?${PROPAGATED_ASSET_FLAG}`, importer, {
skipSelf: true,
...opts
});
}
}
return this.resolve(base, importer, { skipSelf: true, ...opts });
}
},
configureServer(server) {
if (mode === "dev") {
devModuleLoader = createViteLoader(server);
}
},
async transform(_, id, options) {
if (hasContentFlag(id, PROPAGATED_ASSET_FLAG)) {
const basePath = id.split("?")[0];
let stringifiedLinks, stringifiedStyles, stringifiedScripts;
if (options?.ssr && devModuleLoader) {
if (!devModuleLoader.getModuleById(basePath)?.ssrModule) {
await devModuleLoader.import(basePath);
}
const {
styles,
urls,
crawledFiles: styleCrawledFiles
} = await getStylesForURL(pathToFileURL(basePath), devModuleLoader);
const { scripts: hoistedScripts, crawledFiles: scriptCrawledFiles } = settings.config.experimental.directRenderScript ? { scripts: /* @__PURE__ */ new Set(), crawledFiles: /* @__PURE__ */ new Set() } : await getScriptsForURL(
pathToFileURL(basePath),
settings.config.root,
devModuleLoader
);
for (const file of styleCrawledFiles) {
if (!file.includes("node_modules")) {
this.addWatchFile(file);
}
}
for (const file of scriptCrawledFiles) {
if (!file.includes("node_modules")) {
this.addWatchFile(file);
}
}
stringifiedLinks = JSON.stringify([...urls]);
stringifiedStyles = JSON.stringify(styles.map((s) => s.content));
stringifiedScripts = JSON.stringify([...hoistedScripts]);
} else {
stringifiedLinks = JSON.stringify(LINKS_PLACEHOLDER);
stringifiedStyles = JSON.stringify(STYLES_PLACEHOLDER);
stringifiedScripts = JSON.stringify(SCRIPTS_PLACEHOLDER);
}
const code = `
async function getMod() {
return import(${JSON.stringify(basePath)});
}
const collectedLinks = ${stringifiedLinks};
const collectedStyles = ${stringifiedStyles};
const collectedScripts = ${stringifiedScripts};
const defaultMod = { __astroPropagation: true, getMod, collectedLinks, collectedStyles, collectedScripts };
export default defaultMod;
`;
return { code, map: { mappings: "" } };
}
}
};
}
function astroConfigBuildPlugin(options, internals) {
let ssrPluginContext = void 0;
return {
targets: ["server"],
hooks: {
"build:before": ({ target }) => {
return {
vitePlugin: {
name: "astro:content-build-plugin",
generateBundle() {
if (target === "server") {
ssrPluginContext = this;
}
}
}
};
},
"build:post": ({ ssrOutputs, clientOutputs, mutate }) => {
const outputs = ssrOutputs.flatMap((o) => o.output);
const prependBase = (src) => {
const { assetsPrefix } = options.settings.config.build;
if (assetsPrefix) {
const fileExtension = extname(src);
const pf = getAssetsPrefix(fileExtension, assetsPrefix);
return joinPaths(pf, src);
} else {
return prependForwardSlash(joinPaths(options.settings.config.base, src));
}
};
for (const chunk of outputs) {
if (chunk.type === "chunk" && (chunk.code.includes(LINKS_PLACEHOLDER) || chunk.code.includes(SCRIPTS_PLACEHOLDER))) {
let entryStyles = /* @__PURE__ */ new Set();
let entryLinks = /* @__PURE__ */ new Set();
let entryScripts = /* @__PURE__ */ new Set();
if (options.settings.config.experimental.contentCollectionCache) {
for (const id of chunk.moduleIds) {
const _entryCss = internals.propagatedStylesMap.get(id);
const _entryScripts = internals.propagatedScriptsMap.get(id);
if (_entryCss) {
for (const value of _entryCss) {
if (value.type === "inline")
entryStyles.add(value.content);
if (value.type === "external")
entryLinks.add(value.src);
}
}
if (_entryScripts) {
for (const value of _entryScripts) {
entryScripts.add(value);
}
}
}
} else {
for (const id of Object.keys(chunk.modules)) {
for (const pageInfo of getParentModuleInfos(id, ssrPluginContext)) {
if (moduleIsTopLevelPage(pageInfo)) {
const pageViteID = pageInfo.id;
const pageData = getPageDataByViteID(internals, pageViteID);
if (!pageData)
continue;
const _entryCss = pageData.propagatedStyles?.get(id);
const _entryScripts = pageData.propagatedScripts?.get(id);
if (_entryCss) {
for (const value of _entryCss) {
if (value.type === "inline")
entryStyles.add(value.content);
if (value.type === "external")
entryLinks.add(value.src);
}
}
if (_entryScripts) {
for (const value of _entryScripts) {
entryScripts.add(value);
}
}
}
}
}
}
let newCode = chunk.code;
if (entryStyles.size) {
newCode = newCode.replace(
JSON.stringify(STYLES_PLACEHOLDER),
JSON.stringify(Array.from(entryStyles))
);
} else {
newCode = newCode.replace(JSON.stringify(STYLES_PLACEHOLDER), "[]");
}
if (entryLinks.size) {
newCode = newCode.replace(
JSON.stringify(LINKS_PLACEHOLDER),
JSON.stringify(Array.from(entryLinks).map(prependBase))
);
} else {
newCode = newCode.replace(JSON.stringify(LINKS_PLACEHOLDER), "[]");
}
if (entryScripts.size) {
const entryFileNames = /* @__PURE__ */ new Set();
for (const output of clientOutputs) {
for (const clientChunk of output.output) {
if (clientChunk.type !== "chunk")
continue;
for (const [id] of Object.entries(clientChunk.modules)) {
if (entryScripts.has(id)) {
entryFileNames.add(clientChunk.fileName);
}
}
}
}
newCode = newCode.replace(
JSON.stringify(SCRIPTS_PLACEHOLDER),
JSON.stringify(
[...entryFileNames].map((src) => ({
props: {
src: prependBase(src),
type: "module"
},
children: ""
}))
)
);
} else {
newCode = newCode.replace(JSON.stringify(SCRIPTS_PLACEHOLDER), "[]");
}
mutate(chunk, ["server"], newCode);
}
}
ssrPluginContext = void 0;
}
}
};
}
export {
astroConfigBuildPlugin,
astroContentAssetPropagationPlugin
};

View File

@@ -0,0 +1,8 @@
/// <reference types="node" resolution-mode="require"/>
import type fsMod from 'node:fs';
import type { Plugin } from 'vite';
import type { AstroSettings } from '../@types/astro.js';
export declare function astroContentImportPlugin({ fs, settings, }: {
fs: typeof fsMod;
settings: AstroSettings;
}): Plugin[];

View File

@@ -0,0 +1,300 @@
import { extname } from "node:path";
import { pathToFileURL } from "node:url";
import * as devalue from "devalue";
import { getProxyCode } from "../assets/utils/proxy.js";
import { AstroError } from "../core/errors/errors.js";
import { AstroErrorData } from "../core/errors/index.js";
import { isServerLikeOutput } from "../prerender/utils.js";
import { CONTENT_FLAG, DATA_FLAG } from "./consts.js";
import {
getContentEntryExts,
getContentEntryIdAndSlug,
getContentPaths,
getDataEntryExts,
getDataEntryId,
getEntryCollectionName,
getEntryConfigByExtMap,
getEntryData,
getEntryType,
globalContentConfigObserver,
hasContentFlag,
parseEntrySlug,
reloadContentConfigObserver
} from "./utils.js";
function getContentRendererByViteId(viteId, settings) {
let ext = viteId.split(".").pop();
if (!ext)
return void 0;
for (const contentEntryType of settings.contentEntryTypes) {
if (Boolean(contentEntryType.getRenderModule) && contentEntryType.extensions.includes("." + ext)) {
return contentEntryType.getRenderModule;
}
}
return void 0;
}
const CHOKIDAR_MODIFIED_EVENTS = ["add", "unlink", "change"];
const COLLECTION_TYPES_TO_INVALIDATE_ON = ["data", "content", "config"];
function astroContentImportPlugin({
fs,
settings
}) {
const contentPaths = getContentPaths(settings.config, fs);
const contentEntryExts = getContentEntryExts(settings);
const dataEntryExts = getDataEntryExts(settings);
const contentEntryConfigByExt = getEntryConfigByExtMap(settings.contentEntryTypes);
const dataEntryConfigByExt = getEntryConfigByExtMap(settings.dataEntryTypes);
const { contentDir } = contentPaths;
let shouldEmitFile = false;
const plugins = [
{
name: "astro:content-imports",
config(_config, env) {
shouldEmitFile = env.command === "build";
},
async transform(_, viteId) {
if (hasContentFlag(viteId, DATA_FLAG)) {
const fileId = viteId.split("?")[0] ?? viteId;
const { id, data, collection, _internal } = await getDataEntryModule({
fileId,
entryConfigByExt: dataEntryConfigByExt,
contentDir,
config: settings.config,
fs,
pluginContext: this,
shouldEmitFile
});
const code = `
export const id = ${JSON.stringify(id)};
export const collection = ${JSON.stringify(collection)};
export const data = ${stringifyEntryData(data, isServerLikeOutput(settings.config))};
export const _internal = {
type: 'data',
filePath: ${JSON.stringify(_internal.filePath)},
rawData: ${JSON.stringify(_internal.rawData)},
};
`;
return code;
} else if (hasContentFlag(viteId, CONTENT_FLAG)) {
const fileId = viteId.split("?")[0];
const { id, slug, collection, body, data, _internal } = await getContentEntryModule({
fileId,
entryConfigByExt: contentEntryConfigByExt,
contentDir,
config: settings.config,
fs,
pluginContext: this,
shouldEmitFile
});
const code = `
export const id = ${JSON.stringify(id)};
export const collection = ${JSON.stringify(collection)};
export const slug = ${JSON.stringify(slug)};
export const body = ${JSON.stringify(body)};
export const data = ${stringifyEntryData(data, isServerLikeOutput(settings.config))};
export const _internal = {
type: 'content',
filePath: ${JSON.stringify(_internal.filePath)},
rawData: ${JSON.stringify(_internal.rawData)},
};`;
return { code, map: { mappings: "" } };
}
},
configureServer(viteServer) {
viteServer.watcher.on("all", async (event, entry) => {
if (CHOKIDAR_MODIFIED_EVENTS.includes(event)) {
const entryType = getEntryType(entry, contentPaths, contentEntryExts, dataEntryExts);
if (!COLLECTION_TYPES_TO_INVALIDATE_ON.includes(entryType))
return;
if (entryType === "content" || entryType === "data") {
await reloadContentConfigObserver({ fs, settings, viteServer });
}
for (const modUrl of viteServer.moduleGraph.urlToModuleMap.keys()) {
if (hasContentFlag(modUrl, CONTENT_FLAG) || hasContentFlag(modUrl, DATA_FLAG) || Boolean(getContentRendererByViteId(modUrl, settings))) {
try {
const mod = await viteServer.moduleGraph.getModuleByUrl(modUrl);
if (mod) {
viteServer.moduleGraph.invalidateModule(mod);
}
} catch (e) {
if (e.code === "ERR_CLOSED_SERVER")
break;
throw e;
}
}
}
}
});
}
}
];
if (settings.contentEntryTypes.some((t) => t.getRenderModule)) {
plugins.push({
name: "astro:content-render-imports",
async transform(contents, viteId) {
const contentRenderer = getContentRendererByViteId(viteId, settings);
if (!contentRenderer)
return;
const fileId = viteId.split("?")[0];
return contentRenderer.bind(this)({ viteId, contents, fileUrl: pathToFileURL(fileId) });
}
});
}
return plugins;
}
async function getContentEntryModule(params) {
const { fileId, contentDir, pluginContext } = params;
const { collectionConfig, entryConfig, entry, rawContents, collection } = await getEntryModuleBaseInfo(params);
const {
rawData,
data: unvalidatedData,
body,
slug: frontmatterSlug
} = await entryConfig.getEntryInfo({
fileUrl: pathToFileURL(fileId),
contents: rawContents
});
const _internal = { filePath: fileId, rawData };
const { id, slug: generatedSlug } = getContentEntryIdAndSlug({ entry, contentDir, collection });
const slug = parseEntrySlug({
id,
collection,
generatedSlug,
frontmatterSlug
});
const data = collectionConfig ? await getEntryData(
{ id, collection, _internal, unvalidatedData },
collectionConfig,
params.shouldEmitFile,
pluginContext
) : unvalidatedData;
const contentEntryModule = {
id,
slug,
collection,
data,
body,
_internal
};
return contentEntryModule;
}
async function getDataEntryModule(params) {
const { fileId, contentDir, pluginContext } = params;
const { collectionConfig, entryConfig, entry, rawContents, collection } = await getEntryModuleBaseInfo(params);
const { rawData = "", data: unvalidatedData } = await entryConfig.getEntryInfo({
fileUrl: pathToFileURL(fileId),
contents: rawContents
});
const _internal = { filePath: fileId, rawData };
const id = getDataEntryId({ entry, contentDir, collection });
const data = collectionConfig ? await getEntryData(
{ id, collection, _internal, unvalidatedData },
collectionConfig,
params.shouldEmitFile,
pluginContext
) : unvalidatedData;
const dataEntryModule = {
id,
collection,
data,
_internal
};
return dataEntryModule;
}
async function getEntryModuleBaseInfo({
fileId,
entryConfigByExt,
contentDir,
fs
}) {
const contentConfig = await getContentConfigFromGlobal();
let rawContents;
try {
rawContents = await fs.promises.readFile(fileId, "utf-8");
} catch (e) {
throw new AstroError({
...AstroErrorData.UnknownContentCollectionError,
message: `Unexpected error reading entry ${JSON.stringify(fileId)}.`,
stack: e instanceof Error ? e.stack : void 0
});
}
const fileExt = extname(fileId);
const entryConfig = entryConfigByExt.get(fileExt);
if (!entryConfig) {
throw new AstroError({
...AstroErrorData.UnknownContentCollectionError,
message: `No parser found for data entry ${JSON.stringify(
fileId
)}. Did you apply an integration for this file type?`
});
}
const entry = pathToFileURL(fileId);
const collection = getEntryCollectionName({ entry, contentDir });
if (collection === void 0)
throw new AstroError(AstroErrorData.UnknownContentCollectionError);
const collectionConfig = contentConfig?.collections[collection];
return {
collectionConfig,
entry,
entryConfig,
collection,
rawContents
};
}
async function getContentConfigFromGlobal() {
const observable = globalContentConfigObserver.get();
if (observable.status === "init") {
throw new AstroError({
...AstroErrorData.UnknownContentCollectionError,
message: "Content config failed to load."
});
}
if (observable.status === "error") {
throw observable.error;
}
let contentConfig = observable.status === "loaded" ? observable.config : void 0;
if (observable.status === "loading") {
contentConfig = await new Promise((resolve) => {
const unsubscribe = globalContentConfigObserver.subscribe((ctx) => {
if (ctx.status === "loaded") {
resolve(ctx.config);
unsubscribe();
}
if (ctx.status === "error") {
resolve(void 0);
unsubscribe();
}
});
});
}
return contentConfig;
}
function stringifyEntryData(data, isSSR) {
try {
return devalue.uneval(data, (value) => {
if (value instanceof URL) {
return `new URL(${JSON.stringify(value.href)})`;
}
if (typeof value === "object" && "ASTRO_ASSET" in value) {
const { ASTRO_ASSET, ...asset } = value;
asset.fsPath = ASTRO_ASSET;
return getProxyCode(asset, isSSR);
}
});
} catch (e) {
if (e instanceof Error) {
throw new AstroError({
...AstroErrorData.UnsupportedConfigTransformError,
message: AstroErrorData.UnsupportedConfigTransformError.message(e.message),
stack: e.stack
});
} else {
throw new AstroError({
name: "PluginContentImportsError",
message: "Unexpected error processing content collection data."
});
}
}
}
export {
astroContentImportPlugin
};

View File

@@ -0,0 +1,28 @@
/// <reference types="node" resolution-mode="require"/>
import nodeFs from 'node:fs';
import { type Plugin } from 'vite';
import type { AstroSettings } from '../@types/astro.js';
import { type ContentLookupMap } from './utils.js';
interface AstroContentVirtualModPluginParams {
settings: AstroSettings;
fs: typeof nodeFs;
}
export declare function astroContentVirtualModPlugin({ settings, fs, }: AstroContentVirtualModPluginParams): Plugin;
export declare function generateContentEntryFile({ settings, lookupMap, IS_DEV, IS_SERVER, isClient, }: {
settings: AstroSettings;
fs: typeof nodeFs;
lookupMap: ContentLookupMap;
IS_DEV: boolean;
IS_SERVER: boolean;
isClient: boolean;
}): Promise<string>;
/**
* Generate a map from a collection + slug to the local file path.
* This is used internally to resolve entry imports when using `getEntry()`.
* @see `content-module.template.mjs`
*/
export declare function generateLookupMap({ settings, fs, }: {
settings: AstroSettings;
fs: typeof nodeFs;
}): Promise<ContentLookupMap>;
export {};

View File

@@ -0,0 +1,271 @@
import nodeFs from "node:fs";
import { extname } from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import glob from "fast-glob";
import pLimit from "p-limit";
import {} from "vite";
import { encodeName } from "../core/build/util.js";
import { AstroError, AstroErrorData } from "../core/errors/index.js";
import { appendForwardSlash, removeFileExtension } from "../core/path.js";
import { rootRelativePath } from "../core/util.js";
import { isServerLikeOutput } from "../prerender/utils.js";
import {
CONTENT_FLAG,
CONTENT_RENDER_FLAG,
DATA_FLAG,
RESOLVED_VIRTUAL_MODULE_ID,
VIRTUAL_MODULE_ID
} from "./consts.js";
import {
getContentEntryIdAndSlug,
getContentPaths,
getDataEntryExts,
getDataEntryId,
getEntryCollectionName,
getEntryConfigByExtMap,
getEntrySlug,
getEntryType,
getExtGlob
} from "./utils.js";
function astroContentVirtualModPlugin({
settings,
fs
}) {
let IS_DEV = false;
const IS_SERVER = isServerLikeOutput(settings.config);
return {
name: "astro-content-virtual-mod-plugin",
enforce: "pre",
configResolved(config) {
IS_DEV = config.mode === "development";
},
resolveId(id) {
if (id === VIRTUAL_MODULE_ID) {
if (!settings.config.experimental.contentCollectionCache) {
return RESOLVED_VIRTUAL_MODULE_ID;
}
if (IS_DEV || IS_SERVER) {
return RESOLVED_VIRTUAL_MODULE_ID;
} else {
return { id: RESOLVED_VIRTUAL_MODULE_ID, external: true };
}
}
},
async load(id, args) {
if (id === RESOLVED_VIRTUAL_MODULE_ID) {
const lookupMap = await generateLookupMap({
settings,
fs
});
const isClient = !args?.ssr;
const code = await generateContentEntryFile({
settings,
fs,
lookupMap,
IS_DEV,
IS_SERVER,
isClient
});
return {
code,
meta: {
astro: {
hydratedComponents: [],
clientOnlyComponents: [],
scripts: [],
containsHead: false,
propagation: "in-tree",
pageOptions: {}
}
}
};
}
},
renderChunk(code, chunk) {
if (!settings.config.experimental.contentCollectionCache) {
return;
}
if (code.includes(RESOLVED_VIRTUAL_MODULE_ID)) {
const depth = chunk.fileName.split("/").length - 1;
const prefix = depth > 0 ? "../".repeat(depth) : "./";
return code.replaceAll(RESOLVED_VIRTUAL_MODULE_ID, `${prefix}content/entry.mjs`);
}
}
};
}
async function generateContentEntryFile({
settings,
lookupMap,
IS_DEV,
IS_SERVER,
isClient
}) {
const contentPaths = getContentPaths(settings.config);
const relContentDir = rootRelativePath(settings.config.root, contentPaths.contentDir);
let contentEntryGlobResult;
let dataEntryGlobResult;
let renderEntryGlobResult;
if (IS_DEV || IS_SERVER || !settings.config.experimental.contentCollectionCache) {
const contentEntryConfigByExt = getEntryConfigByExtMap(settings.contentEntryTypes);
const contentEntryExts = [...contentEntryConfigByExt.keys()];
const dataEntryExts = getDataEntryExts(settings);
const createGlob = (value, flag) => `import.meta.glob(${JSON.stringify(value)}, { query: { ${flag}: true } })`;
contentEntryGlobResult = createGlob(
globWithUnderscoresIgnored(relContentDir, contentEntryExts),
CONTENT_FLAG
);
dataEntryGlobResult = createGlob(
globWithUnderscoresIgnored(relContentDir, dataEntryExts),
DATA_FLAG
);
renderEntryGlobResult = createGlob(
globWithUnderscoresIgnored(relContentDir, contentEntryExts),
CONTENT_RENDER_FLAG
);
} else {
contentEntryGlobResult = getStringifiedCollectionFromLookup(
"content",
relContentDir,
lookupMap
);
dataEntryGlobResult = getStringifiedCollectionFromLookup("data", relContentDir, lookupMap);
renderEntryGlobResult = getStringifiedCollectionFromLookup("render", relContentDir, lookupMap);
}
let virtualModContents = nodeFs.readFileSync(contentPaths.virtualModTemplate, "utf-8").replace("@@CONTENT_DIR@@", relContentDir).replace("'@@CONTENT_ENTRY_GLOB_PATH@@'", contentEntryGlobResult).replace("'@@DATA_ENTRY_GLOB_PATH@@'", dataEntryGlobResult).replace("'@@RENDER_ENTRY_GLOB_PATH@@'", renderEntryGlobResult).replace("/* @@LOOKUP_MAP_ASSIGNMENT@@ */", `lookupMap = ${JSON.stringify(lookupMap)};`) + (isClient ? `
console.warn('astro:content is only supported running server-side. Using it in the browser will lead to bloated bundles and slow down page load. In the future it will not be supported.');` : "");
return virtualModContents;
}
function getStringifiedCollectionFromLookup(wantedType, relContentDir, lookupMap) {
let str = "{";
let normalize = (slug) => slug;
if (process.env.NODE_ENV === "production") {
const suffix = wantedType === "render" ? ".entry.mjs" : ".mjs";
normalize = (slug) => `${removeFileExtension(encodeName(slug)).replace(relContentDir, "./")}${suffix}`;
} else {
let suffix = "";
if (wantedType === "content")
suffix = CONTENT_FLAG;
else if (wantedType === "data")
suffix = DATA_FLAG;
else if (wantedType === "render")
suffix = CONTENT_RENDER_FLAG;
normalize = (slug) => `${slug}?${suffix}`;
}
for (const { type, entries } of Object.values(lookupMap)) {
if (type === wantedType || wantedType === "render" && type === "content") {
for (const slug of Object.values(entries)) {
str += `
"${slug}": () => import("${normalize(slug)}"),`;
}
}
}
str += "\n}";
return str;
}
async function generateLookupMap({
settings,
fs
}) {
const { root } = settings.config;
const contentPaths = getContentPaths(settings.config);
const relContentDir = rootRelativePath(root, contentPaths.contentDir, false);
const contentEntryConfigByExt = getEntryConfigByExtMap(settings.contentEntryTypes);
const dataEntryExts = getDataEntryExts(settings);
const { contentDir } = contentPaths;
const contentEntryExts = [...contentEntryConfigByExt.keys()];
let lookupMap = {};
const contentGlob = await glob(
`${relContentDir}**/*${getExtGlob([...dataEntryExts, ...contentEntryExts])}`,
{
absolute: true,
cwd: fileURLToPath(root),
fs
}
);
const limit = pLimit(10);
const promises = [];
for (const filePath of contentGlob) {
promises.push(
limit(async () => {
const entryType = getEntryType(filePath, contentPaths, contentEntryExts, dataEntryExts);
if (entryType !== "content" && entryType !== "data")
return;
const collection = getEntryCollectionName({ contentDir, entry: pathToFileURL(filePath) });
if (!collection)
throw UnexpectedLookupMapError;
if (lookupMap[collection]?.type && lookupMap[collection].type !== entryType) {
throw new AstroError({
...AstroErrorData.MixedContentDataCollectionError,
message: AstroErrorData.MixedContentDataCollectionError.message(collection)
});
}
if (entryType === "content") {
const contentEntryType = contentEntryConfigByExt.get(extname(filePath));
if (!contentEntryType)
throw UnexpectedLookupMapError;
const { id, slug: generatedSlug } = await getContentEntryIdAndSlug({
entry: pathToFileURL(filePath),
contentDir,
collection
});
const slug = await getEntrySlug({
id,
collection,
generatedSlug,
fs,
fileUrl: pathToFileURL(filePath),
contentEntryType
});
if (lookupMap[collection]?.entries?.[slug]) {
throw new AstroError({
...AstroErrorData.DuplicateContentEntrySlugError,
message: AstroErrorData.DuplicateContentEntrySlugError.message(
collection,
slug,
lookupMap[collection].entries[slug],
rootRelativePath(root, filePath)
),
hint: slug !== generatedSlug ? `Check the \`slug\` frontmatter property in **${id}**.` : void 0
});
}
lookupMap[collection] = {
type: "content",
entries: {
...lookupMap[collection]?.entries,
[slug]: rootRelativePath(root, filePath)
}
};
} else {
const id = getDataEntryId({ entry: pathToFileURL(filePath), contentDir, collection });
lookupMap[collection] = {
type: "data",
entries: {
...lookupMap[collection]?.entries,
[id]: rootRelativePath(root, filePath)
}
};
}
})
);
}
await Promise.all(promises);
return lookupMap;
}
function globWithUnderscoresIgnored(relContentDir, exts) {
const extGlob = getExtGlob(exts);
const contentDir = appendForwardSlash(relContentDir);
return [
`${contentDir}**/*${extGlob}`,
`!${contentDir}**/_*/**/*${extGlob}`,
`!${contentDir}**/_*${extGlob}`
];
}
const UnexpectedLookupMapError = new AstroError({
...AstroErrorData.UnknownContentCollectionError,
message: `Unexpected error while parsing content entry IDs and slugs.`
});
export {
astroContentVirtualModPlugin,
generateContentEntryFile,
generateLookupMap
};