full site update
This commit is contained in:
268
node_modules/astro/dist/content/utils.js
generated
vendored
268
node_modules/astro/dist/content/utils.js
generated
vendored
@@ -1,22 +1,42 @@
|
||||
import fsMod from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
import { parseFrontmatter } from "@astrojs/markdown-remark";
|
||||
import { slug as githubSlug } from "github-slugger";
|
||||
import matter from "gray-matter";
|
||||
import { green } from "kleur/colors";
|
||||
import xxhash from "xxhash-wasm";
|
||||
import { z } from "zod";
|
||||
import { AstroError, AstroErrorData, MarkdownError, errorMap } from "../core/errors/index.js";
|
||||
import { AstroError, AstroErrorData, errorMap, MarkdownError } from "../core/errors/index.js";
|
||||
import { isYAMLException } from "../core/errors/utils.js";
|
||||
import { appendForwardSlash } from "../core/path.js";
|
||||
import { normalizePath } from "../core/viteUtils.js";
|
||||
import {
|
||||
CONTENT_FLAGS,
|
||||
CONTENT_LAYER_TYPE,
|
||||
CONTENT_MODULE_FLAG,
|
||||
DEFERRED_MODULE,
|
||||
IMAGE_IMPORT_PREFIX,
|
||||
LIVE_CONTENT_TYPE,
|
||||
PROPAGATED_ASSET_FLAG
|
||||
} from "./consts.js";
|
||||
import { glob } from "./loaders/glob.js";
|
||||
import { createImage } from "./runtime-assets.js";
|
||||
const entryTypeSchema = z.object({
|
||||
id: z.string({
|
||||
invalid_type_error: "Content entry `id` must be a string"
|
||||
// Default to empty string so we can validate properly in the loader
|
||||
})
|
||||
}).passthrough();
|
||||
const loaderReturnSchema = z.union([
|
||||
z.array(entryTypeSchema),
|
||||
z.record(
|
||||
z.string(),
|
||||
z.object({
|
||||
id: z.string({
|
||||
invalid_type_error: "Content entry `id` must be a string"
|
||||
}).optional()
|
||||
}).passthrough()
|
||||
)
|
||||
]);
|
||||
const collectionConfigParser = z.union([
|
||||
z.object({
|
||||
type: z.literal("content").optional().default("content"),
|
||||
@@ -30,22 +50,7 @@ const collectionConfigParser = z.union([
|
||||
type: z.literal(CONTENT_LAYER_TYPE),
|
||||
schema: z.any().optional(),
|
||||
loader: z.union([
|
||||
z.function().returns(
|
||||
z.union([
|
||||
z.array(
|
||||
z.object({
|
||||
id: z.string()
|
||||
}).catchall(z.unknown())
|
||||
),
|
||||
z.promise(
|
||||
z.array(
|
||||
z.object({
|
||||
id: z.string()
|
||||
}).catchall(z.unknown())
|
||||
)
|
||||
)
|
||||
])
|
||||
),
|
||||
z.function(),
|
||||
z.object({
|
||||
name: z.string(),
|
||||
load: z.function(
|
||||
@@ -59,8 +64,10 @@ const collectionConfigParser = z.union([
|
||||
config: z.any(),
|
||||
entryTypes: z.any(),
|
||||
parseData: z.any(),
|
||||
renderMarkdown: z.any(),
|
||||
generateDigest: z.function(z.tuple([z.any()], z.string())),
|
||||
watcher: z.any().optional()
|
||||
watcher: z.any().optional(),
|
||||
refreshContextData: z.record(z.unknown()).optional()
|
||||
})
|
||||
],
|
||||
z.unknown()
|
||||
@@ -69,7 +76,14 @@ const collectionConfigParser = z.union([
|
||||
schema: z.any().optional(),
|
||||
render: z.function(z.tuple([z.any()], z.unknown())).optional()
|
||||
})
|
||||
])
|
||||
]),
|
||||
/** deprecated */
|
||||
_legacy: z.boolean().optional()
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal(LIVE_CONTENT_TYPE).optional().default(LIVE_CONTENT_TYPE),
|
||||
schema: z.any().optional(),
|
||||
loader: z.function()
|
||||
})
|
||||
]);
|
||||
const contentConfigParser = z.object({
|
||||
@@ -90,20 +104,25 @@ function parseEntrySlug({
|
||||
});
|
||||
}
|
||||
}
|
||||
async function getEntryDataAndImages(entry, collectionConfig, shouldEmitFile, pluginContext) {
|
||||
async function getEntryDataAndImages(entry, collectionConfig, shouldEmitFile, experimentalSvgEnabled, pluginContext) {
|
||||
let data;
|
||||
if (collectionConfig.type === "data" || collectionConfig.type === CONTENT_LAYER_TYPE) {
|
||||
data = entry.unvalidatedData;
|
||||
} else {
|
||||
if (collectionConfig.type === "content" || collectionConfig._legacy) {
|
||||
const { slug, ...unvalidatedData } = entry.unvalidatedData;
|
||||
data = unvalidatedData;
|
||||
} else {
|
||||
data = entry.unvalidatedData;
|
||||
}
|
||||
let schema = collectionConfig.schema;
|
||||
const imageImports = /* @__PURE__ */ new Set();
|
||||
if (typeof schema === "function") {
|
||||
if (pluginContext) {
|
||||
schema = schema({
|
||||
image: createImage(pluginContext, shouldEmitFile, entry._internal.filePath)
|
||||
image: createImage(
|
||||
pluginContext,
|
||||
shouldEmitFile,
|
||||
entry._internal.filePath,
|
||||
experimentalSvgEnabled
|
||||
)
|
||||
});
|
||||
} else if (collectionConfig.type === CONTENT_LAYER_TYPE) {
|
||||
schema = schema({
|
||||
@@ -134,16 +153,16 @@ async function getEntryDataAndImages(entry, collectionConfig, shouldEmitFile, pl
|
||||
data = parsed.data;
|
||||
} else {
|
||||
if (!formattedError) {
|
||||
const errorType = collectionConfig.type === "content" ? AstroErrorData.InvalidContentEntryFrontmatterError : AstroErrorData.InvalidContentEntryDataError;
|
||||
formattedError = new AstroError({
|
||||
...AstroErrorData.InvalidContentEntryFrontmatterError,
|
||||
message: AstroErrorData.InvalidContentEntryFrontmatterError.message(
|
||||
entry.collection,
|
||||
entry.id,
|
||||
parsed.error
|
||||
),
|
||||
...errorType,
|
||||
message: errorType.message(entry.collection, entry.id, parsed.error),
|
||||
location: {
|
||||
file: entry._internal.filePath,
|
||||
line: getYAMLErrorLine(entry._internal.rawData, String(parsed.error.errors[0].path[0])),
|
||||
file: entry._internal?.filePath,
|
||||
line: getYAMLErrorLine(
|
||||
entry._internal?.rawData,
|
||||
String(parsed.error.errors[0].path[0])
|
||||
),
|
||||
column: 0
|
||||
}
|
||||
});
|
||||
@@ -153,11 +172,12 @@ async function getEntryDataAndImages(entry, collectionConfig, shouldEmitFile, pl
|
||||
}
|
||||
return { data, imageImports: Array.from(imageImports) };
|
||||
}
|
||||
async function getEntryData(entry, collectionConfig, shouldEmitFile, pluginContext) {
|
||||
async function getEntryData(entry, collectionConfig, shouldEmitFile, experimentalSvgEnabled, pluginContext) {
|
||||
const { data } = await getEntryDataAndImages(
|
||||
entry,
|
||||
collectionConfig,
|
||||
shouldEmitFile,
|
||||
experimentalSvgEnabled,
|
||||
pluginContext
|
||||
);
|
||||
return data;
|
||||
@@ -269,17 +289,24 @@ function getRelativeEntryPath(entry, collection, contentDir) {
|
||||
const relativeToCollection = path.relative(collection, relativeToContent);
|
||||
return relativeToCollection;
|
||||
}
|
||||
function isParentDirectory(parent, child) {
|
||||
const relative = path.relative(fileURLToPath(parent), fileURLToPath(child));
|
||||
return !relative.startsWith("..") && !path.isAbsolute(relative);
|
||||
}
|
||||
function getEntryType(entryPath, paths, contentFileExts, dataFileExts) {
|
||||
const { ext } = path.parse(entryPath);
|
||||
const fileUrl = pathToFileURL(entryPath);
|
||||
if (hasUnderscoreBelowContentDirectoryPath(fileUrl, paths.contentDir)) {
|
||||
const dotAstroDir = new URL("./.astro/", paths.root);
|
||||
if (fileUrl.href === paths.config.url.href) {
|
||||
return "config";
|
||||
} else if (hasUnderscoreBelowContentDirectoryPath(fileUrl, paths.contentDir)) {
|
||||
return "ignored";
|
||||
} else if (isParentDirectory(dotAstroDir, fileUrl)) {
|
||||
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";
|
||||
}
|
||||
@@ -306,7 +333,7 @@ function getYAMLErrorLine(rawData, objectKey) {
|
||||
}
|
||||
function safeParseFrontmatter(source, id) {
|
||||
try {
|
||||
return matter(source);
|
||||
return parseFrontmatter(source, { frontmatter: "empty-with-spaces" });
|
||||
} catch (err) {
|
||||
const markdownError = new MarkdownError({
|
||||
name: "MarkdownError",
|
||||
@@ -328,14 +355,6 @@ function safeParseFrontmatter(source, id) {
|
||||
}
|
||||
}
|
||||
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);
|
||||
@@ -365,13 +384,90 @@ async function loadContentConfig({
|
||||
return void 0;
|
||||
}
|
||||
}
|
||||
async function autogenerateCollections({
|
||||
config,
|
||||
settings,
|
||||
fs
|
||||
}) {
|
||||
if (settings.config.legacy.collections) {
|
||||
return config;
|
||||
}
|
||||
const contentDir = new URL("./content/", settings.config.srcDir);
|
||||
const collections = config?.collections ?? {};
|
||||
const contentExts = getContentEntryExts(settings);
|
||||
const dataExts = getDataEntryExts(settings);
|
||||
const contentPattern = globWithUnderscoresIgnored("", contentExts);
|
||||
const dataPattern = globWithUnderscoresIgnored("", dataExts);
|
||||
let usesContentLayer = false;
|
||||
for (const collectionName of Object.keys(collections)) {
|
||||
if (collections[collectionName]?.type === "content_layer" || collections[collectionName]?.type === "live") {
|
||||
usesContentLayer = true;
|
||||
continue;
|
||||
}
|
||||
const isDataCollection = collections[collectionName]?.type === "data";
|
||||
const base = new URL(`${collectionName}/`, contentDir);
|
||||
const _legacy = !isDataCollection || void 0;
|
||||
collections[collectionName] = {
|
||||
...collections[collectionName],
|
||||
type: "content_layer",
|
||||
_legacy,
|
||||
loader: glob({
|
||||
base,
|
||||
pattern: isDataCollection ? dataPattern : contentPattern,
|
||||
_legacy,
|
||||
// Legacy data collections IDs aren't slugified
|
||||
generateId: isDataCollection ? ({ entry }) => getDataEntryId({
|
||||
entry: new URL(entry, base),
|
||||
collection: collectionName,
|
||||
contentDir
|
||||
}) : void 0
|
||||
// Zod weirdness has trouble with typing the args to the load function
|
||||
})
|
||||
};
|
||||
}
|
||||
if (!usesContentLayer && fs.existsSync(contentDir)) {
|
||||
const orphanedCollections = [];
|
||||
for (const entry of await fs.promises.readdir(contentDir, { withFileTypes: true })) {
|
||||
const collectionName = entry.name;
|
||||
if (["_", "."].includes(collectionName.at(0) ?? "")) {
|
||||
continue;
|
||||
}
|
||||
if (entry.isDirectory() && !(collectionName in collections)) {
|
||||
orphanedCollections.push(collectionName);
|
||||
const base = new URL(`${collectionName}/`, contentDir);
|
||||
collections[collectionName] = {
|
||||
type: "content_layer",
|
||||
loader: glob({
|
||||
base,
|
||||
pattern: contentPattern,
|
||||
_legacy: true
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
||||
if (orphanedCollections.length > 0) {
|
||||
console.warn(
|
||||
`
|
||||
Auto-generating collections for folders in "src/content/" that are not defined as collections.
|
||||
This is deprecated, so you should define these collections yourself in "src/content.config.ts".
|
||||
The following collections have been auto-generated: ${orphanedCollections.map((name) => green(name)).join(", ")}
|
||||
`
|
||||
);
|
||||
}
|
||||
}
|
||||
return { ...config, collections };
|
||||
}
|
||||
async function reloadContentConfigObserver({
|
||||
observer = globalContentConfigObserver,
|
||||
...loadContentConfigOpts
|
||||
}) {
|
||||
observer.set({ status: "loading" });
|
||||
try {
|
||||
const config = await loadContentConfig(loadContentConfigOpts);
|
||||
let config = await loadContentConfig(loadContentConfigOpts);
|
||||
config = await autogenerateCollections({
|
||||
config,
|
||||
...loadContentConfigOpts
|
||||
});
|
||||
if (config) {
|
||||
observer.set({ status: "loaded", config });
|
||||
} else {
|
||||
@@ -406,27 +502,47 @@ function contentObservable(initialCtx) {
|
||||
subscribe
|
||||
};
|
||||
}
|
||||
function getContentPaths({ srcDir }, fs = fsMod) {
|
||||
const configStats = search(fs, srcDir);
|
||||
function getContentPaths({
|
||||
srcDir,
|
||||
legacy,
|
||||
root,
|
||||
experimental
|
||||
}, fs = fsMod) {
|
||||
const configStats = searchConfig(fs, srcDir, legacy?.collections);
|
||||
const liveConfigStats = experimental?.liveContentCollections ? searchLiveConfig(fs, srcDir) : { exists: false, url: new URL("./", srcDir) };
|
||||
const pkgBase = new URL("../../", import.meta.url);
|
||||
return {
|
||||
root: new URL("./", root),
|
||||
contentDir: new URL("./content/", srcDir),
|
||||
assetsDir: new URL("./assets/", srcDir),
|
||||
typesTemplate: new URL("templates/content/types.d.ts", pkgBase),
|
||||
virtualModTemplate: new URL("templates/content/module.mjs", pkgBase),
|
||||
config: configStats
|
||||
config: configStats,
|
||||
liveConfig: liveConfigStats
|
||||
};
|
||||
}
|
||||
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) {
|
||||
function searchConfig(fs, srcDir, legacy) {
|
||||
const paths = [
|
||||
...legacy ? [] : ["content.config.mjs", "content.config.js", "content.config.mts", "content.config.ts"],
|
||||
"content/config.mjs",
|
||||
"content/config.js",
|
||||
"content/config.mts",
|
||||
"content/config.ts"
|
||||
];
|
||||
return search(fs, srcDir, paths);
|
||||
}
|
||||
function searchLiveConfig(fs, srcDir) {
|
||||
const paths = ["live.config.mjs", "live.config.js", "live.config.mts", "live.config.ts"];
|
||||
return search(fs, srcDir, paths);
|
||||
}
|
||||
function search(fs, srcDir, paths) {
|
||||
const urls = paths.map((p) => new URL(`./${p}`, srcDir));
|
||||
for (const file of urls) {
|
||||
if (fs.existsSync(file)) {
|
||||
return { exists: true, url: file };
|
||||
}
|
||||
}
|
||||
return { exists: false, url: paths[0] };
|
||||
return { exists: false, url: urls[0] };
|
||||
}
|
||||
async function getEntrySlug({
|
||||
id,
|
||||
@@ -461,6 +577,15 @@ function hasAssetPropagationFlag(id) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
function globWithUnderscoresIgnored(relContentDir, exts) {
|
||||
const extGlob = getExtGlob(exts);
|
||||
const contentDir = relContentDir.length > 0 ? appendForwardSlash(relContentDir) : relContentDir;
|
||||
return [
|
||||
`${contentDir}**/*${extGlob}`,
|
||||
`!${contentDir}**/_*/**/*${extGlob}`,
|
||||
`!${contentDir}**/_*${extGlob}`
|
||||
];
|
||||
}
|
||||
function posixifyPath(filePath) {
|
||||
return filePath.split(path.sep).join("/");
|
||||
}
|
||||
@@ -473,9 +598,29 @@ function contentModuleToId(fileName) {
|
||||
params.set(CONTENT_MODULE_FLAG, "true");
|
||||
return `${DEFERRED_MODULE}?${params.toString()}`;
|
||||
}
|
||||
function safeStringifyReplacer(seen) {
|
||||
return function(_key, value) {
|
||||
if (!(value !== null && typeof value === "object")) {
|
||||
return value;
|
||||
}
|
||||
if (seen.has(value)) {
|
||||
return "[Circular]";
|
||||
}
|
||||
seen.add(value);
|
||||
const newValue = Array.isArray(value) ? [] : {};
|
||||
for (const [key2, value2] of Object.entries(value)) {
|
||||
newValue[key2] = safeStringifyReplacer(seen)(key2, value2);
|
||||
}
|
||||
seen.delete(value);
|
||||
return newValue;
|
||||
};
|
||||
}
|
||||
function safeStringify(value) {
|
||||
const seen = /* @__PURE__ */ new WeakSet();
|
||||
return JSON.stringify(value, safeStringifyReplacer(seen));
|
||||
}
|
||||
export {
|
||||
contentModuleToId,
|
||||
contentObservable,
|
||||
getContentEntryExts,
|
||||
getContentEntryIdAndSlug,
|
||||
getContentPaths,
|
||||
@@ -489,15 +634,16 @@ export {
|
||||
getEntryType,
|
||||
getExtGlob,
|
||||
getSymlinkedContentCollections,
|
||||
globWithUnderscoresIgnored,
|
||||
globalContentConfigObserver,
|
||||
hasAnyContentFlag,
|
||||
hasAssetPropagationFlag,
|
||||
hasContentFlag,
|
||||
isDeferredModule,
|
||||
loaderReturnSchema,
|
||||
parseEntrySlug,
|
||||
posixRelative,
|
||||
posixifyPath,
|
||||
reloadContentConfigObserver,
|
||||
reverseSymlink,
|
||||
safeParseFrontmatter
|
||||
safeParseFrontmatter,
|
||||
safeStringify
|
||||
};
|
||||
|
Reference in New Issue
Block a user