astro-ghostcms/.pnpm-store/v3/files/51/865538ce037d2fef0a2224db83b...

293 lines
9.6 KiB
Plaintext

import * as devalue from "devalue";
import { extname } from "node:path";
import { pathToFileURL } from "node:url";
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;
const plugins = [
{
name: "astro:content-imports",
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
});
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
});
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,
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,
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
};