Initial commit: New MoreminiMore website with fresh design

This commit is contained in:
MoreminiMore
2026-04-22 01:59:05 +07:00
commit 76409638cc
14010 changed files with 2052041 additions and 0 deletions

84
node_modules/astro/dist/content/config.d.ts generated vendored Normal file
View File

@@ -0,0 +1,84 @@
import type * as zCore from 'zod/v4/core';
import type * as z from 'zod/v4';
import type { LiveLoader, Loader } from './loaders/types.js';
type ImageFunction = () => z.ZodObject<{
src: zCore.$ZodString;
width: zCore.$ZodNumber;
height: zCore.$ZodNumber;
format: zCore.$ZodUnion<[
zCore.$ZodLiteral<'png'>,
zCore.$ZodLiteral<'jpg'>,
zCore.$ZodLiteral<'jpeg'>,
zCore.$ZodLiteral<'tiff'>,
zCore.$ZodLiteral<'webp'>,
zCore.$ZodLiteral<'gif'>,
zCore.$ZodLiteral<'svg'>,
zCore.$ZodLiteral<'avif'>
]>;
}>;
export interface DataEntry {
id: string;
data: Record<string, unknown>;
filePath?: string;
body?: string;
}
export interface DataStore {
get: (key: string) => DataEntry;
entries: () => Array<[id: string, DataEntry]>;
set: (key: string, data: Record<string, unknown>, body?: string, filePath?: string) => void;
values: () => Array<DataEntry>;
keys: () => Array<string>;
delete: (key: string) => void;
clear: () => void;
has: (key: string) => boolean;
}
export interface MetaStore {
get: (key: string) => string | undefined;
set: (key: string, value: string) => void;
delete: (key: string) => void;
has: (key: string) => boolean;
}
export type BaseSchema = zCore.$ZodType;
export type { ImageFunction };
export type SchemaContext = {
image: ImageFunction;
};
type LoaderConstraint<TData extends {
id: string;
}> = Loader | (() => Array<TData> | Promise<Array<TData>> | Record<string, Omit<TData, 'id'> & {
id?: string;
}> | Promise<Record<string, Omit<TData, 'id'> & {
id?: string;
}>>);
type ContentLayerConfig<S extends BaseSchema, TLoader extends LoaderConstraint<{
id: string;
}>> = {
type?: 'content_layer';
schema?: S | ((context: SchemaContext) => S);
loader: TLoader;
};
type DataCollectionConfig<S extends BaseSchema> = {
type: 'data';
schema?: S | ((context: SchemaContext) => S);
};
type ContentCollectionConfig<S extends BaseSchema> = {
type?: 'content';
schema?: S | ((context: SchemaContext) => S);
loader?: never;
};
export type LiveCollectionConfig<L extends LiveLoader, S extends BaseSchema | undefined = undefined> = {
type?: 'live';
schema?: S;
loader: L;
};
export type CollectionConfig<S extends BaseSchema, TLoader extends LoaderConstraint<{
id: string;
}> = LoaderConstraint<{
id: string;
}>> = ContentCollectionConfig<S> | DataCollectionConfig<S> | ContentLayerConfig<S, TLoader>;
export declare function defineLiveCollection<L extends LiveLoader, S extends BaseSchema | undefined = undefined>(config: LiveCollectionConfig<L, S>): LiveCollectionConfig<L, S>;
export declare function defineCollection<S extends BaseSchema, TLoader extends LoaderConstraint<{
id: string;
}> = LoaderConstraint<{
id: string;
}>>(config: CollectionConfig<S, TLoader>): CollectionConfig<S, TLoader>;

93
node_modules/astro/dist/content/config.js generated vendored Normal file
View File

@@ -0,0 +1,93 @@
import { AstroError, AstroErrorData, AstroUserError } from "../core/errors/index.js";
import { CONTENT_LAYER_TYPE, LIVE_CONTENT_TYPE } from "./consts.js";
function getImporterFilename() {
const stackLine = new Error().stack?.split("\n").find(
(line) => !line.includes("defineCollection") && !line.includes("defineLiveCollection") && !line.includes("getImporterFilename") && !line.startsWith("Error")
);
if (!stackLine) {
return void 0;
}
const match = /\/((?:src|chunks)\/.*?):\d+:\d+/.exec(stackLine);
return match?.[1] ?? void 0;
}
function defineLiveCollection(config) {
const importerFilename = getImporterFilename();
if (importerFilename && !importerFilename.includes("live.config")) {
throw new AstroError({
...AstroErrorData.LiveContentConfigError,
message: AstroErrorData.LiveContentConfigError.message(
"Live collections must be defined in a `src/live.config.ts` file.",
importerFilename ?? "your content config file"
)
});
}
config.type ??= LIVE_CONTENT_TYPE;
if (config.type !== LIVE_CONTENT_TYPE) {
throw new AstroError({
...AstroErrorData.LiveContentConfigError,
message: AstroErrorData.LiveContentConfigError.message(
"Collections in a live config file must have a type of `live`.",
importerFilename
)
});
}
if (!config.loader) {
throw new AstroError({
...AstroErrorData.LiveContentConfigError,
message: AstroErrorData.LiveContentConfigError.message(
"Live collections must have a `loader` defined.",
importerFilename
)
});
}
if (!config.loader.loadCollection || !config.loader.loadEntry) {
throw new AstroError({
...AstroErrorData.LiveContentConfigError,
message: AstroErrorData.LiveContentConfigError.message(
"Live collection loaders must have `loadCollection()` and `loadEntry()` methods. Please check that you are not using a loader intended for build-time collections",
importerFilename
)
});
}
if (typeof config.schema === "function") {
throw new AstroError({
...AstroErrorData.LiveContentConfigError,
message: AstroErrorData.LiveContentConfigError.message(
"The schema cannot be a function for live collections. Please use a schema object instead.",
importerFilename
)
});
}
return config;
}
function defineCollection(config) {
const importerFilename = getImporterFilename();
if (importerFilename?.includes("live.config")) {
throw new AstroError({
...AstroErrorData.LiveContentConfigError,
message: AstroErrorData.LiveContentConfigError.message(
"Collections in a live config file must use `defineLiveCollection`.",
importerFilename
)
});
}
if ("loader" in config) {
if (config.type && config.type !== CONTENT_LAYER_TYPE) {
throw new AstroUserError(
`A content collection is defined with legacy features (e.g. missing a \`loader\` or has a \`type\`). Check your collection definitions in ${importerFilename ?? "your content config file"} to ensure that all collections are defined using the current properties.`
);
}
if (typeof config.loader === "object" && typeof config.loader.load !== "function" && ("loadEntry" in config.loader || "loadCollection" in config.loader)) {
throw new AstroUserError(
`Live content collections must be defined in "src/live.config.ts" file. Check the loaders used in "${importerFilename ?? "your content config file"}" to ensure you are not using a live loader to define a build-time content collection.`
);
}
config.type = CONTENT_LAYER_TYPE;
}
if (!config.type) config.type = "content";
return config;
}
export {
defineCollection,
defineLiveCollection
};

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

@@ -0,0 +1,28 @@
export declare const PROPAGATED_ASSET_FLAG = "astroPropagatedAssets";
export declare const PROPAGATED_ASSET_QUERY_PARAM = "?astroPropagatedAssets";
export declare const CONTENT_RENDER_FLAG = "astroRenderContent";
export declare const CONTENT_FLAG = "astroContentCollectionEntry";
export declare const DATA_FLAG = "astroDataCollectionEntry";
export declare const CONTENT_IMAGE_FLAG = "astroContentImageFlag";
export declare const CONTENT_MODULE_FLAG = "astroContentModuleFlag";
export declare const VIRTUAL_MODULE_ID = "astro:content";
export declare const RESOLVED_VIRTUAL_MODULE_ID: string;
export declare const DATA_STORE_VIRTUAL_ID = "astro:data-layer-content";
export declare const RESOLVED_DATA_STORE_VIRTUAL_ID: string;
export declare const MODULES_MJS_ID = "astro:content-module-imports";
export declare const MODULES_MJS_VIRTUAL_ID: string;
export declare const DEFERRED_MODULE = "astro:content-layer-deferred-module";
export declare const ASSET_IMPORTS_VIRTUAL_ID = "astro:asset-imports";
export declare const ASSET_IMPORTS_RESOLVED_STUB_ID: string;
export declare const LINKS_PLACEHOLDER = "@@ASTRO-LINKS@@";
export declare const STYLES_PLACEHOLDER = "@@ASTRO-STYLES@@";
export declare const IMAGE_IMPORT_PREFIX = "__ASTRO_IMAGE_";
export declare const CONTENT_FLAGS: readonly ["astroContentCollectionEntry", "astroRenderContent", "astroDataCollectionEntry", "astroPropagatedAssets", "astroContentImageFlag", "astroContentModuleFlag"];
export declare const CONTENT_TYPES_FILE = "content.d.ts";
export declare const DATA_STORE_FILE = "data-store.json";
export declare const ASSET_IMPORTS_FILE = "content-assets.mjs";
export declare const MODULES_IMPORTS_FILE = "content-modules.mjs";
export declare const COLLECTIONS_MANIFEST_FILE = "collections/collections.json";
export declare const COLLECTIONS_DIR = "collections/";
export declare const CONTENT_LAYER_TYPE = "content_layer";
export declare const LIVE_CONTENT_TYPE = "live";

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

@@ -0,0 +1,65 @@
const PROPAGATED_ASSET_FLAG = "astroPropagatedAssets";
const PROPAGATED_ASSET_QUERY_PARAM = `?${PROPAGATED_ASSET_FLAG}`;
const CONTENT_RENDER_FLAG = "astroRenderContent";
const CONTENT_FLAG = "astroContentCollectionEntry";
const DATA_FLAG = "astroDataCollectionEntry";
const CONTENT_IMAGE_FLAG = "astroContentImageFlag";
const CONTENT_MODULE_FLAG = "astroContentModuleFlag";
const VIRTUAL_MODULE_ID = "astro:content";
const RESOLVED_VIRTUAL_MODULE_ID = "\0" + VIRTUAL_MODULE_ID;
const DATA_STORE_VIRTUAL_ID = "astro:data-layer-content";
const RESOLVED_DATA_STORE_VIRTUAL_ID = "\0" + DATA_STORE_VIRTUAL_ID;
const MODULES_MJS_ID = "astro:content-module-imports";
const MODULES_MJS_VIRTUAL_ID = "\0" + MODULES_MJS_ID;
const DEFERRED_MODULE = "astro:content-layer-deferred-module";
const ASSET_IMPORTS_VIRTUAL_ID = "astro:asset-imports";
const ASSET_IMPORTS_RESOLVED_STUB_ID = "\0" + ASSET_IMPORTS_VIRTUAL_ID;
const LINKS_PLACEHOLDER = "@@ASTRO-LINKS@@";
const STYLES_PLACEHOLDER = "@@ASTRO-STYLES@@";
const IMAGE_IMPORT_PREFIX = "__ASTRO_IMAGE_";
const CONTENT_FLAGS = [
CONTENT_FLAG,
CONTENT_RENDER_FLAG,
DATA_FLAG,
PROPAGATED_ASSET_FLAG,
CONTENT_IMAGE_FLAG,
CONTENT_MODULE_FLAG
];
const CONTENT_TYPES_FILE = "content.d.ts";
const DATA_STORE_FILE = "data-store.json";
const ASSET_IMPORTS_FILE = "content-assets.mjs";
const MODULES_IMPORTS_FILE = "content-modules.mjs";
const COLLECTIONS_MANIFEST_FILE = "collections/collections.json";
const COLLECTIONS_DIR = "collections/";
const CONTENT_LAYER_TYPE = "content_layer";
const LIVE_CONTENT_TYPE = "live";
export {
ASSET_IMPORTS_FILE,
ASSET_IMPORTS_RESOLVED_STUB_ID,
ASSET_IMPORTS_VIRTUAL_ID,
COLLECTIONS_DIR,
COLLECTIONS_MANIFEST_FILE,
CONTENT_FLAG,
CONTENT_FLAGS,
CONTENT_IMAGE_FLAG,
CONTENT_LAYER_TYPE,
CONTENT_MODULE_FLAG,
CONTENT_RENDER_FLAG,
CONTENT_TYPES_FILE,
DATA_FLAG,
DATA_STORE_FILE,
DATA_STORE_VIRTUAL_ID,
DEFERRED_MODULE,
IMAGE_IMPORT_PREFIX,
LINKS_PLACEHOLDER,
LIVE_CONTENT_TYPE,
MODULES_IMPORTS_FILE,
MODULES_MJS_ID,
MODULES_MJS_VIRTUAL_ID,
PROPAGATED_ASSET_FLAG,
PROPAGATED_ASSET_QUERY_PARAM,
RESOLVED_DATA_STORE_VIRTUAL_ID,
RESOLVED_VIRTUAL_MODULE_ID,
STYLES_PLACEHOLDER,
VIRTUAL_MODULE_ID
};

41
node_modules/astro/dist/content/content-layer.d.ts generated vendored Normal file
View File

@@ -0,0 +1,41 @@
import type { FSWatcher } from 'vite';
import type { AstroLogger } from '../core/logger/core.js';
import type { AstroSettings } from '../types/astro.js';
import type { RefreshContentOptions } from '../types/public/content.js';
import type { MutableDataStore } from './mutable-data-store.js';
import { type ContentObservable } from './utils.js';
export interface ContentLayerOptions {
store: MutableDataStore;
settings: AstroSettings;
logger: AstroLogger;
watcher?: FSWatcher;
contentConfigObserver?: ContentObservable;
}
export declare class ContentLayer {
#private;
constructor({ settings, logger, store, watcher, contentConfigObserver, }: ContentLayerOptions);
/**
* Whether the content layer is currently loading content
*/
get loading(): boolean;
/**
* Watch for changes to the content config and trigger a sync when it changes.
*/
watchContentConfig(): void;
unwatchContentConfig(): void;
dispose(): void;
/**
* Enqueues a sync job that runs the `load()` method of each collection's loader, which will load the data and save it in the data store.
* The loader itself is responsible for deciding whether this will clear and reload the full collection, or
* perform an incremental update. After the data is loaded, the data store is written to disk. Jobs are queued,
* so that only one sync can run at a time. The function returns a promise that resolves when this sync job is complete.
*/
sync(options?: RefreshContentOptions): Promise<void>;
regenerateCollectionFileManifest(): Promise<void>;
}
/**
* Get the path to the data store file.
* During development, this is in the `.astro` directory so that the Vite watcher can see it.
* In production, it's in the cache directory so that it's preserved between builds.
*/
export declare function getDataStoreFile(settings: AstroSettings, isDev: boolean): URL;

371
node_modules/astro/dist/content/content-layer.js generated vendored Normal file
View File

@@ -0,0 +1,371 @@
import { existsSync, promises as fs } from "node:fs";
import {
createMarkdownProcessor,
parseFrontmatter
} from "@astrojs/markdown-remark";
import PQueue from "p-queue";
import xxhash from "xxhash-wasm";
import { AstroError, AstroErrorData } from "../core/errors/index.js";
import {
ASSET_IMPORTS_FILE,
COLLECTIONS_MANIFEST_FILE,
CONTENT_LAYER_TYPE,
DATA_STORE_FILE,
MODULES_IMPORTS_FILE
} from "./consts.js";
import {
getEntryConfigByExtMap,
getEntryData,
globalContentConfigObserver,
loaderReturnSchema,
safeStringify
} from "./utils.js";
import { createWatcherWrapper } from "./watcher.js";
class ContentLayer {
#logger;
#store;
#settings;
#watcher;
#lastConfigDigest;
#unsubscribe;
#markdownProcessor;
#generateDigest;
#contentConfigObserver;
#queue;
constructor({
settings,
logger,
store,
watcher,
contentConfigObserver = globalContentConfigObserver
}) {
watcher?.setMaxListeners(50);
this.#logger = logger;
this.#store = store;
this.#settings = settings;
this.#contentConfigObserver = contentConfigObserver;
if (watcher) {
this.#watcher = createWatcherWrapper(watcher);
}
this.#queue = new PQueue({ concurrency: 1 });
}
/**
* Whether the content layer is currently loading content
*/
get loading() {
return this.#queue.size > 0 || this.#queue.pending > 0;
}
/**
* Watch for changes to the content config and trigger a sync when it changes.
*/
watchContentConfig() {
this.#unsubscribe?.();
this.#unsubscribe = this.#contentConfigObserver.subscribe(async (ctx) => {
if (ctx.status === "loaded" && ctx.config.digest !== this.#lastConfigDigest) {
this.sync();
}
});
}
unwatchContentConfig() {
this.#unsubscribe?.();
}
dispose() {
this.#queue.clear();
this.#unsubscribe?.();
this.#watcher?.removeAllTrackedListeners();
}
async #getGenerateDigest() {
if (this.#generateDigest) {
return this.#generateDigest;
}
const { h64ToString } = await xxhash();
this.#generateDigest = (data) => {
const dataString = typeof data === "string" ? data : JSON.stringify(data);
return h64ToString(dataString);
};
return this.#generateDigest;
}
async #getLoaderContext({
collectionName,
loaderName = "content",
parseData,
refreshContextData
}) {
return {
collection: collectionName,
store: this.#store.scopedStore(collectionName),
meta: this.#store.metaStore(collectionName),
logger: this.#logger.forkIntegrationLogger(loaderName),
config: this.#settings.config,
parseData,
renderMarkdown: this.#processMarkdown.bind(this),
generateDigest: await this.#getGenerateDigest(),
watcher: this.#watcher,
refreshContextData,
entryTypes: getEntryConfigByExtMap([
...this.#settings.contentEntryTypes,
...this.#settings.dataEntryTypes
])
};
}
async #processMarkdown(content, options) {
this.#markdownProcessor ??= await createMarkdownProcessor(this.#settings.config.markdown);
const { frontmatter, content: body } = parseFrontmatter(content);
const { code, metadata } = await this.#markdownProcessor.render(body, {
frontmatter,
fileURL: options?.fileURL
});
return {
html: code,
metadata: {
...metadata,
imagePaths: (metadata.localImagePaths ?? []).concat(metadata.remoteImagePaths ?? [])
}
};
}
/**
* Enqueues a sync job that runs the `load()` method of each collection's loader, which will load the data and save it in the data store.
* The loader itself is responsible for deciding whether this will clear and reload the full collection, or
* perform an incremental update. After the data is loaded, the data store is written to disk. Jobs are queued,
* so that only one sync can run at a time. The function returns a promise that resolves when this sync job is complete.
*/
sync(options = {}) {
return this.#queue.add(() => this.#doSync(options));
}
async #doSync(options) {
let contentConfig = this.#contentConfigObserver.get();
const logger = this.#logger.forkIntegrationLogger("content");
if (contentConfig?.status === "loading") {
contentConfig = await Promise.race([
new Promise((resolve) => {
const unsub = this.#contentConfigObserver.subscribe((ctx) => {
unsub();
resolve(ctx);
});
}),
new Promise(
(resolve) => setTimeout(
() => resolve({ status: "error", error: new Error("Content config loading timed out") }),
5e3
)
)
]);
}
switch (contentConfig?.status) {
case "loaded":
break;
case "error":
logger.error(
`Error loading content config. Skipping sync.
${contentConfig.error.message}`
);
return;
case "does-not-exist":
return;
case "init":
case "loading":
case void 0:
logger.error(
`Content config not loaded, skipping sync. Status was ${contentConfig?.status}`
);
return;
}
logger.info("Syncing content");
const {
vite: _vite,
integrations: _integrations,
adapter: _adapter,
...hashableConfig
} = this.#settings.config;
const astroConfigDigest = safeStringify(hashableConfig);
const { digest: currentConfigDigest } = contentConfig.config;
this.#lastConfigDigest = currentConfigDigest;
let shouldClear = false;
const previousConfigDigest = this.#store.metaStore().get("content-config-digest");
const previousAstroConfigDigest = this.#store.metaStore().get("astro-config-digest");
const previousAstroVersion = this.#store.metaStore().get("astro-version");
if (previousAstroConfigDigest && previousAstroConfigDigest !== astroConfigDigest) {
logger.info("Astro config changed");
shouldClear = true;
}
if (previousConfigDigest && previousConfigDigest !== currentConfigDigest) {
logger.info("Content config changed");
shouldClear = true;
}
if (previousAstroVersion && previousAstroVersion !== "6.1.8") {
logger.info("Astro version changed");
shouldClear = true;
}
if (shouldClear) {
logger.info("Clearing content store");
this.#store.clearAll();
}
if ("6.1.8") {
this.#store.metaStore().set("astro-version", "6.1.8");
}
if (currentConfigDigest) {
this.#store.metaStore().set("content-config-digest", currentConfigDigest);
}
if (astroConfigDigest) {
this.#store.metaStore().set("astro-config-digest", astroConfigDigest);
}
if (!options?.loaders?.length) {
this.#watcher?.removeAllTrackedListeners();
}
const backwardsCompatEnabled = this.#settings.config.legacy?.collectionsBackwardsCompat ?? false;
await Promise.all(
Object.entries(contentConfig.config.collections).map(async ([name, collection]) => {
if (collection.type !== CONTENT_LAYER_TYPE && !backwardsCompatEnabled) {
return;
}
if (collection.type !== CONTENT_LAYER_TYPE && !("loader" in collection)) {
return;
}
let { schema } = collection;
const loaderName = "loader" in collection ? collection.loader.name : "content";
if (!schema && "loader" in collection && typeof collection.loader === "object") {
schema = collection.loader.schema;
if (!schema && collection.loader.createSchema) {
({ schema } = await collection.loader.createSchema());
}
}
if (options?.loaders && "loader" in collection && (typeof collection.loader !== "object" || !options.loaders.includes(collection.loader.name))) {
return;
}
const context = await this.#getLoaderContext({
collectionName: name,
parseData: ({ id, data, filePath = "" }) => getEntryData(
{
id,
collection: name,
unvalidatedData: data,
_internal: {
rawData: void 0,
filePath
}
},
{ ...collection, schema },
false
),
loaderName,
refreshContextData: options?.context
});
if ("loader" in collection) {
if (typeof collection.loader === "function") {
return simpleLoader(collection.loader, context);
}
if (!collection.loader?.load) {
throw new Error(`Collection loader for ${name} does not have a load method`);
}
return collection.loader.load(context);
}
})
);
await fs.mkdir(this.#settings.config.cacheDir, { recursive: true });
await fs.mkdir(this.#settings.dotAstroDir, { recursive: true });
const assetImportsFile = new URL(ASSET_IMPORTS_FILE, this.#settings.dotAstroDir);
await this.#store.writeAssetImports(assetImportsFile);
const modulesImportsFile = new URL(MODULES_IMPORTS_FILE, this.#settings.dotAstroDir);
await this.#store.writeModuleImports(modulesImportsFile);
await this.#store.waitUntilSaveComplete();
logger.info("Synced content");
if (this.#settings.config.experimental.contentIntellisense) {
await this.regenerateCollectionFileManifest();
}
}
async regenerateCollectionFileManifest() {
const collectionsManifest = new URL(COLLECTIONS_MANIFEST_FILE, this.#settings.dotAstroDir);
this.#logger.debug("content", "Regenerating collection file manifest");
if (existsSync(collectionsManifest)) {
try {
const collections = await fs.readFile(collectionsManifest, "utf-8");
const collectionsJson = JSON.parse(collections);
collectionsJson.entries ??= {};
for (const { hasSchema, name } of collectionsJson.collections) {
if (!hasSchema) {
continue;
}
const entries = this.#store.values(name);
if (!entries?.[0]?.filePath) {
continue;
}
for (const { filePath } of entries) {
if (!filePath) {
continue;
}
const key = new URL(filePath, this.#settings.config.root).href.toLowerCase();
collectionsJson.entries[key] = name;
}
}
await fs.writeFile(collectionsManifest, JSON.stringify(collectionsJson, null, 2));
} catch {
this.#logger.error("content", "Failed to regenerate collection file manifest");
}
}
this.#logger.debug("content", "Regenerated collection file manifest");
}
}
async function simpleLoader(handler, context) {
const unsafeData = await handler();
const parsedData = loaderReturnSchema.safeParse(unsafeData);
if (!parsedData.success) {
const issue = parsedData.error.issues[0];
const parseIssue = Array.isArray(unsafeData) ? issue.errors[0] : issue.errors[1];
const error = parseIssue[0];
const firstPathItem = error.path[0];
const entry = Array.isArray(unsafeData) ? unsafeData[firstPathItem] : unsafeData[firstPathItem];
throw new AstroError({
...AstroErrorData.ContentLoaderReturnsInvalidId,
message: AstroErrorData.ContentLoaderReturnsInvalidId.message(context.collection, entry)
});
}
const data = parsedData.data;
context.store.clear();
if (Array.isArray(data)) {
for (const raw of data) {
if (!raw.id) {
throw new AstroError({
...AstroErrorData.ContentLoaderInvalidDataError,
message: AstroErrorData.ContentLoaderInvalidDataError.message(
context.collection,
`Entry missing ID:
${JSON.stringify({ ...raw, id: void 0 }, null, 2)}`
)
});
}
const item = await context.parseData({ id: raw.id, data: raw });
context.store.set({ id: raw.id, data: item });
}
return;
}
if (typeof data === "object") {
for (const [id, raw] of Object.entries(data)) {
if (raw.id && raw.id !== id) {
throw new AstroError({
...AstroErrorData.ContentLoaderInvalidDataError,
message: AstroErrorData.ContentLoaderInvalidDataError.message(
context.collection,
`Object key ${JSON.stringify(id)} does not match ID ${JSON.stringify(raw.id)}`
)
});
}
const item = await context.parseData({ id, data: raw });
context.store.set({ id, data: item });
}
return;
}
throw new AstroError({
...AstroErrorData.ExpectedImageOptions,
message: AstroErrorData.ContentLoaderInvalidDataError.message(
context.collection,
`Invalid data type: ${typeof data}`
)
});
}
function getDataStoreFile(settings, isDev) {
return new URL(DATA_STORE_FILE, isDev ? settings.dotAstroDir : settings.config.cacheDir);
}
export {
ContentLayer,
getDataStoreFile
};

55
node_modules/astro/dist/content/data-store.d.ts generated vendored Normal file
View File

@@ -0,0 +1,55 @@
import type { MarkdownHeading } from '@astrojs/markdown-remark';
export interface RenderedContent {
/** Rendered HTML string. If present then `render(entry)` will return a component that renders this HTML. */
html: string;
metadata?: {
/** Any images that are present in this entry. Relative to the {@link DataEntry} filePath. */
imagePaths?: Array<string>;
/** Any headings that are present in this file. */
headings?: MarkdownHeading[];
/** Raw frontmatter, parsed from the file. This may include data from remark plugins. */
frontmatter?: Record<string, any>;
/** Any other metadata that is present in this file. */
[key: string]: unknown;
};
}
export interface DataEntry<TData extends Record<string, unknown> = Record<string, unknown>> {
/** The ID of the entry. Unique per collection. */
id: string;
/** The parsed entry data */
data: TData;
/** The file path of the content, if applicable. Relative to the site root. */
filePath?: string;
/** The raw body of the content, if applicable. */
body?: string;
/** An optional content digest, to check if the content has changed. */
digest?: number | string;
/** The rendered content of the entry, if applicable. */
rendered?: RenderedContent;
/**
* If an entry is a deferred, its rendering phase is delegated to a virtual module during the runtime phase when calling `renderEntry`.
*/
deferredRender?: boolean;
assetImports?: Array<string>;
}
/**
* A read-only data store for content collections. This is used to retrieve data from the content layer at runtime.
* To add or modify data, use {@link MutableDataStore} instead.
*/
export declare class ImmutableDataStore {
protected _collections: Map<string, Map<string, any>>;
constructor();
get<T = DataEntry>(collectionName: string, key: string): T | undefined;
entries<T = DataEntry>(collectionName: string): Array<[id: string, T]>;
values<T = DataEntry>(collectionName: string): Array<T>;
keys(collectionName: string): Array<string>;
has(collectionName: string, key: string): boolean;
hasCollection(collectionName: string): boolean;
collections(): Map<string, Map<string, any>>;
/**
* Attempts to load a DataStore from the virtual module.
* This only works in Vite.
*/
static fromModule(): Promise<ImmutableDataStore>;
static fromMap(data: Map<string, Map<string, any>>): Promise<ImmutableDataStore>;
}

75
node_modules/astro/dist/content/data-store.js generated vendored Normal file
View File

@@ -0,0 +1,75 @@
import * as devalue from "devalue";
class ImmutableDataStore {
_collections = /* @__PURE__ */ new Map();
constructor() {
this._collections = /* @__PURE__ */ new Map();
}
get(collectionName, key) {
return this._collections.get(collectionName)?.get(String(key));
}
entries(collectionName) {
const collection = this._collections.get(collectionName) ?? /* @__PURE__ */ new Map();
return [...collection.entries()];
}
values(collectionName) {
const collection = this._collections.get(collectionName) ?? /* @__PURE__ */ new Map();
return [...collection.values()];
}
keys(collectionName) {
const collection = this._collections.get(collectionName) ?? /* @__PURE__ */ new Map();
return [...collection.keys()];
}
has(collectionName, key) {
const collection = this._collections.get(collectionName);
if (collection) {
return collection.has(String(key));
}
return false;
}
hasCollection(collectionName) {
return this._collections.has(collectionName);
}
collections() {
return this._collections;
}
/**
* Attempts to load a DataStore from the virtual module.
* This only works in Vite.
*/
static async fromModule() {
try {
const data = await import("astro:data-layer-content");
if (data.default instanceof Map) {
return ImmutableDataStore.fromMap(data.default);
}
const map = devalue.unflatten(data.default);
return ImmutableDataStore.fromMap(map);
} catch {
}
return new ImmutableDataStore();
}
static async fromMap(data) {
const store = new ImmutableDataStore();
store._collections = data;
return store;
}
}
function dataStoreSingleton() {
let instance = void 0;
return {
get: async () => {
if (!instance) {
instance = ImmutableDataStore.fromModule();
}
return instance;
},
set: (store) => {
instance = store;
}
};
}
const globalDataStore = dataStoreSingleton();
export {
ImmutableDataStore,
globalDataStore
};

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

@@ -0,0 +1,6 @@
export { attachContentServerListeners } from './server-listeners.js';
export { createContentTypesGenerator } from './types-generator.js';
export { getContentPaths } 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';

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

@@ -0,0 +1,14 @@
import { attachContentServerListeners } from "./server-listeners.js";
import { createContentTypesGenerator } from "./types-generator.js";
import { getContentPaths } 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 {
astroContentAssetPropagationPlugin,
astroContentImportPlugin,
astroContentVirtualModPlugin,
attachContentServerListeners,
createContentTypesGenerator,
getContentPaths
};

17
node_modules/astro/dist/content/instance.d.ts generated vendored Normal file
View File

@@ -0,0 +1,17 @@
import type { FSWatcher } from 'vite';
import { ContentLayer } from './content-layer.js';
import type { AstroLogger } from '../core/logger/core.js';
import type { AstroSettings } from '../types/astro.js';
import type { MutableDataStore } from './mutable-data-store.js';
interface ContentLayerOptions {
store: MutableDataStore;
settings: AstroSettings;
logger: AstroLogger;
watcher?: FSWatcher;
}
export declare const globalContentLayer: {
init: (options: ContentLayerOptions) => ContentLayer;
get: () => ContentLayer | null;
dispose: () => void;
};
export {};

20
node_modules/astro/dist/content/instance.js generated vendored Normal file
View File

@@ -0,0 +1,20 @@
import { ContentLayer } from "./content-layer.js";
function contentLayerSingleton() {
let instance = null;
return {
init: (options) => {
instance?.dispose();
instance = new ContentLayer(options);
return instance;
},
get: () => instance,
dispose: () => {
instance?.dispose();
instance = null;
}
};
}
const globalContentLayer = contentLayerSingleton();
export {
globalContentLayer
};

20
node_modules/astro/dist/content/loaders/errors.d.ts generated vendored Normal file
View File

@@ -0,0 +1,20 @@
import type * as z from 'zod/v4/core';
export declare class LiveCollectionError extends Error {
readonly collection: string;
readonly message: string;
readonly cause?: Error;
constructor(collection: string, message: string, cause?: Error);
static is(error: unknown): error is LiveCollectionError;
}
export declare class LiveEntryNotFoundError extends LiveCollectionError {
constructor(collection: string, entryFilter: string | Record<string, unknown>);
static is(error: unknown): error is LiveEntryNotFoundError;
}
export declare class LiveCollectionValidationError extends LiveCollectionError {
constructor(collection: string, entryId: string, error: z.$ZodError);
static is(error: unknown): error is LiveCollectionValidationError;
}
export declare class LiveCollectionCacheHintError extends LiveCollectionError {
constructor(collection: string, entryId: string | undefined, error: z.$ZodError);
static is(error: unknown): error is LiveCollectionCacheHintError;
}

73
node_modules/astro/dist/content/loaders/errors.js generated vendored Normal file
View File

@@ -0,0 +1,73 @@
function formatZodError(error) {
return error.issues.map((issue) => ` **${issue.path.join(".")}**: ${issue.message}`);
}
class LiveCollectionError extends Error {
collection;
message;
cause;
constructor(collection, message, cause) {
super(message);
this.collection = collection;
this.message = message;
this.cause = cause;
this.name = "LiveCollectionError";
if (cause?.stack) {
this.stack = cause.stack;
}
}
static is(error) {
return error instanceof LiveCollectionError;
}
}
class LiveEntryNotFoundError extends LiveCollectionError {
constructor(collection, entryFilter) {
super(
collection,
`Entry ${collection} \u2192 ${typeof entryFilter === "string" ? entryFilter : JSON.stringify(entryFilter)} was not found.`
);
this.name = "LiveEntryNotFoundError";
}
static is(error) {
return error?.name === "LiveEntryNotFoundError";
}
}
class LiveCollectionValidationError extends LiveCollectionError {
constructor(collection, entryId, error) {
super(
collection,
[
`**${collection} \u2192 ${entryId}** data does not match the collection schema.
`,
...formatZodError(error),
""
].join("\n")
);
this.name = "LiveCollectionValidationError";
}
static is(error) {
return error?.name === "LiveCollectionValidationError";
}
}
class LiveCollectionCacheHintError extends LiveCollectionError {
constructor(collection, entryId, error) {
super(
collection,
[
`**${String(collection)}${entryId ? ` \u2192 ${String(entryId)}` : ""}** returned an invalid cache hint.
`,
...formatZodError(error),
""
].join("\n")
);
this.name = "LiveCollectionCacheHintError";
}
static is(error) {
return error?.name === "LiveCollectionCacheHintError";
}
}
export {
LiveCollectionCacheHintError,
LiveCollectionError,
LiveCollectionValidationError,
LiveEntryNotFoundError
};

16
node_modules/astro/dist/content/loaders/file.d.ts generated vendored Normal file
View File

@@ -0,0 +1,16 @@
import type { Loader } from './types.js';
type ParserOutput = Record<string, Record<string, unknown>> | Array<Record<string, unknown>>;
interface FileOptions {
/**
* the parsing function to use for this data
* @default JSON.parse or yaml.load, depending on the extension of the file
* */
parser?: (text: string) => Promise<ParserOutput> | ParserOutput;
}
/**
* Loads entries from a JSON file. The file must contain an array of objects that contain unique `id` fields, or an object with string keys.
* @param fileName The path to the JSON file to load, relative to the content directory.
* @param options Additional options for the file loader
*/
export declare function file(fileName: string, options?: FileOptions): Loader;
export {};

102
node_modules/astro/dist/content/loaders/file.js generated vendored Normal file
View File

@@ -0,0 +1,102 @@
import { existsSync, promises as fs } from "node:fs";
import { fileURLToPath } from "node:url";
import yaml from "js-yaml";
import toml from "smol-toml";
import { FileGlobNotSupported, FileParserNotFound } from "../../core/errors/errors-data.js";
import { AstroError } from "../../core/errors/index.js";
import { posixRelative } from "../utils.js";
function file(fileName, options) {
if (fileName.includes("*")) {
throw new AstroError(FileGlobNotSupported);
}
let parse = null;
const ext = fileName.split(".").at(-1);
if (ext === "json") {
parse = JSON.parse;
} else if (ext === "yml" || ext === "yaml") {
parse = (text) => yaml.load(text, {
filename: fileName
});
} else if (ext === "toml") {
parse = toml.parse;
}
if (options?.parser) parse = options.parser;
if (parse === null) {
throw new AstroError({
...FileParserNotFound,
message: FileParserNotFound.message(fileName)
});
}
async function syncData(filePath, { logger, parseData, store, config }) {
let data;
try {
const contents = await fs.readFile(filePath, "utf-8");
data = await parse(contents);
} catch (error) {
logger.error(`Error reading data from ${fileName}`);
logger.debug(error.message);
return;
}
const normalizedFilePath = posixRelative(fileURLToPath(config.root), filePath);
if (Array.isArray(data)) {
if (data.length === 0) {
logger.warn(`No items found in ${fileName}`);
}
logger.debug(`Found ${data.length} item array in ${fileName}`);
store.clear();
const idList = /* @__PURE__ */ new Set();
for (const rawItem of data) {
const id = (rawItem.id ?? rawItem.slug)?.toString();
if (!id) {
logger.error(`Item in ${fileName} is missing an id or slug field.`);
continue;
}
if (idList.has(id)) {
logger.warn(
`Duplicate id "${id}" found in ${fileName}. Later items with the same id will overwrite earlier ones.`
);
}
idList.add(id);
const parsedData = await parseData({ id, data: rawItem, filePath });
store.set({ id, data: parsedData, filePath: normalizedFilePath });
}
} else if (typeof data === "object") {
const entries = Object.entries(data);
logger.debug(`Found object with ${entries.length} entries in ${fileName}`);
store.clear();
for (const [id, rawItem] of entries) {
if (id === "$schema" && typeof rawItem === "string") {
continue;
}
const parsedData = await parseData({ id, data: rawItem, filePath });
store.set({ id, data: parsedData, filePath: normalizedFilePath });
}
} else {
logger.error(`Invalid data in ${fileName}. Must be an array or object.`);
}
}
return {
name: "file-loader",
load: async (context) => {
const { config, logger, watcher } = context;
logger.debug(`Loading data from ${fileName}`);
const url = new URL(fileName, config.root);
if (!existsSync(url)) {
logger.error(`File not found: ${fileName}`);
return;
}
const filePath = fileURLToPath(url);
await syncData(filePath, context);
watcher?.add(filePath);
watcher?.on("change", async (changedPath) => {
if (changedPath === filePath) {
logger.info(`Reloading data from ${fileName}`);
await syncData(filePath, context);
}
});
}
};
}
export {
file
};

35
node_modules/astro/dist/content/loaders/glob.d.ts generated vendored Normal file
View File

@@ -0,0 +1,35 @@
import type { Loader } from './types.js';
interface GenerateIdOptions {
/** The path to the entry file, relative to the base directory. */
entry: string;
/** The base directory URL. */
base: URL;
/** The parsed, unvalidated data of the entry. */
data: Record<string, unknown>;
}
interface GlobOptions {
/** The glob pattern to match files, relative to the base directory */
pattern: string | Array<string>;
/** The base directory to resolve the glob pattern from. Relative to the root directory, or an absolute file URL. Defaults to `.` */
base?: string | URL;
/**
* Function that generates an ID for an entry. Default implementation generates a slug from the entry path.
* @returns The ID of the entry. Must be unique per collection.
**/
generateId?: (options: GenerateIdOptions) => string;
/**
* Retains the unparsed body of the file in the data store, in addition to the rendered HTML.
* If `false`, `entry.body` will be undefined if the content type has a parser.
* Defaults to `true`.
*/
retainBody?: boolean;
}
export declare const secretLegacyFlag: unique symbol;
/**
* Loads multiple entries, using a glob pattern to match files.
* @param pattern A glob pattern to match files, relative to the content directory.
*/
export declare function glob(globOptions: GlobOptions & {
[secretLegacyFlag]?: boolean;
}): Loader;
export {};

271
node_modules/astro/dist/content/loaders/glob.js generated vendored Normal file
View File

@@ -0,0 +1,271 @@
import { existsSync, promises as fs } from "node:fs";
import { relative } from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import pLimit from "p-limit";
import colors from "piccolore";
import picomatch from "picomatch";
import { glob as tinyglobby } from "tinyglobby";
import { getContentEntryIdAndSlug, posixRelative } from "../utils.js";
function generateIdDefault({ entry, base, data }, isLegacy) {
if (data.slug) {
return data.slug;
}
const entryURL = new URL(encodeURI(entry), base);
if (isLegacy) {
const { id } = getContentEntryIdAndSlug({
entry: entryURL,
contentDir: base,
collection: ""
});
return id;
}
const { slug } = getContentEntryIdAndSlug({
entry: entryURL,
contentDir: base,
collection: ""
});
return slug;
}
function checkPrefix(pattern, prefix) {
if (Array.isArray(pattern)) {
return pattern.some((p) => p.startsWith(prefix));
}
return pattern.startsWith(prefix);
}
const secretLegacyFlag = /* @__PURE__ */ Symbol("astro.legacy-glob");
function glob(globOptions) {
if (checkPrefix(globOptions.pattern, "../")) {
throw new Error(
"Glob patterns cannot start with `../`. Set the `base` option to a parent directory instead."
);
}
if (checkPrefix(globOptions.pattern, "/")) {
throw new Error(
"Glob patterns cannot start with `/`. Set the `base` option to a parent directory or use a relative path instead."
);
}
const isLegacy = !!globOptions[secretLegacyFlag];
const generateId = globOptions?.generateId ?? ((opts) => generateIdDefault(opts, isLegacy));
const fileToIdMap = /* @__PURE__ */ new Map();
return {
name: "glob-loader",
load: async ({
config,
collection,
logger,
watcher,
parseData,
store,
generateDigest,
entryTypes
}) => {
const renderFunctionByContentType = /* @__PURE__ */ new WeakMap();
const untouchedEntries = new Set(store.keys());
async function syncData(entry, base, entryType, oldId) {
if (!entryType) {
logger.warn(`No entry type found for ${entry}`);
return;
}
const fileUrl = new URL(encodeURI(entry), base);
const contents = await fs.readFile(fileUrl, "utf-8").catch((err) => {
logger.error(`Error reading ${entry}: ${err.message}`);
return;
});
if (!contents && contents !== "") {
logger.warn(`No contents found for ${entry}`);
return;
}
const { body, data } = await entryType.getEntryInfo({
contents,
fileUrl
});
const id = generateId({ entry, base, data });
if (oldId && oldId !== id) {
store.delete(oldId);
}
untouchedEntries.delete(id);
const existingEntry = store.get(id);
const digest = generateDigest(contents);
const filePath2 = fileURLToPath(fileUrl);
if (existingEntry && existingEntry.digest === digest && existingEntry.filePath) {
if (existingEntry.deferredRender) {
store.addModuleImport(existingEntry.filePath);
}
if (existingEntry.assetImports?.length) {
store.addAssetImports(existingEntry.assetImports, existingEntry.filePath);
}
fileToIdMap.set(filePath2, id);
return;
}
const relativePath2 = posixRelative(fileURLToPath(config.root), filePath2);
const parsedData = await parseData({
id,
data,
filePath: filePath2
});
if (existingEntry && existingEntry.filePath && existingEntry.filePath !== relativePath2) {
const oldFilePath = new URL(existingEntry.filePath, config.root);
if (existsSync(oldFilePath)) {
logger.warn(
`Duplicate id "${id}" found in ${filePath2}. Later items with the same id will overwrite earlier ones.`
);
}
}
if (entryType.getRenderFunction) {
let render = renderFunctionByContentType.get(entryType);
if (!render) {
render = await entryType.getRenderFunction(config);
renderFunctionByContentType.set(entryType, render);
}
let rendered = void 0;
try {
rendered = await render?.({
id,
data,
body,
filePath: filePath2,
digest
});
} catch (error) {
logger.error(`Error rendering ${entry}: ${error.message}`);
}
store.set({
id,
data: parsedData,
body: globOptions.retainBody === false ? void 0 : body,
filePath: relativePath2,
digest,
rendered,
assetImports: rendered?.metadata?.imagePaths
});
} else if ("contentModuleTypes" in entryType) {
store.set({
id,
data: parsedData,
body: globOptions.retainBody === false ? void 0 : body,
filePath: relativePath2,
digest,
deferredRender: true
});
} else {
store.set({
id,
data: parsedData,
body: globOptions.retainBody === false ? void 0 : body,
filePath: relativePath2,
digest
});
}
fileToIdMap.set(filePath2, id);
}
let baseDir;
if (isLegacy && !globOptions.base) {
baseDir = new URL(`./src/content/${collection}`, config.root);
} else {
baseDir = globOptions.base ? new URL(globOptions.base, config.root) : config.root;
}
if (!baseDir.pathname.endsWith("/")) {
baseDir.pathname = `${baseDir.pathname}/`;
}
const filePath = fileURLToPath(baseDir);
const relativePath = relative(fileURLToPath(config.root), filePath);
const exists = existsSync(baseDir);
if (!exists) {
logger.warn(`The base directory "${fileURLToPath(baseDir)}" does not exist.`);
}
const files = await tinyglobby(globOptions.pattern, {
cwd: fileURLToPath(baseDir),
expandDirectories: false
});
if (exists && files.length === 0) {
logger.warn(
`No files found matching "${globOptions.pattern}" in directory "${relativePath}"`
);
return;
}
function configForFile(file) {
const ext = file.split(".").at(-1);
if (!ext) {
logger.warn(`No extension found for ${file}`);
return;
}
return entryTypes.get(`.${ext}`);
}
const limit = pLimit(10);
const skippedFiles = [];
const contentDir = new URL("content/", config.srcDir);
const configFiles = new Set(
["config.js", "config.ts", "config.mjs"].map((file) => new URL(file, contentDir).href)
);
function isConfigFile(file) {
const fileUrl = new URL(file, baseDir);
return configFiles.has(fileUrl.href);
}
await Promise.all(
files.map((entry) => {
if (isConfigFile(entry)) {
return;
}
return limit(async () => {
const entryType = configForFile(entry);
await syncData(entry, baseDir, entryType);
});
})
);
const skipCount = skippedFiles.length;
if (skipCount > 0) {
const patternList = Array.isArray(globOptions.pattern) ? globOptions.pattern.join(", ") : globOptions.pattern;
logger.warn(
`The glob() loader cannot be used for files in ${colors.bold("src/content")} when legacy mode is enabled.`
);
if (skipCount > 10) {
logger.warn(
`Skipped ${colors.green(skippedFiles.length)} files that matched ${colors.green(patternList)}.`
);
} else {
logger.warn(`Skipped the following files that matched ${colors.green(patternList)}:`);
skippedFiles.forEach((file) => logger.warn(`\u2022 ${colors.green(file)}`));
}
}
untouchedEntries.forEach((id) => store.delete(id));
if (!watcher) {
return;
}
watcher.add(filePath);
const matchesGlob = (entry) => !entry.startsWith("../") && picomatch.isMatch(entry, globOptions.pattern);
const basePath = fileURLToPath(baseDir);
async function onChange(changedPath) {
const entry = posixRelative(basePath, changedPath);
if (!matchesGlob(entry)) {
return;
}
const entryType = configForFile(changedPath);
const baseUrl = pathToFileURL(basePath);
const oldId = fileToIdMap.get(changedPath);
try {
await syncData(entry, baseUrl, entryType, oldId);
logger.info(`Reloaded data from ${colors.green(entry)}`);
} catch (e) {
logger.error(`Failed to reload ${entry}: ${e.message}`);
}
}
watcher.on("change", onChange);
watcher.on("add", onChange);
watcher.on("unlink", async (deletedPath) => {
const entry = posixRelative(basePath, deletedPath);
if (!matchesGlob(entry)) {
return;
}
const id = fileToIdMap.get(deletedPath);
if (id) {
store.delete(id);
fileToIdMap.delete(deletedPath);
}
});
}
};
}
export {
glob,
secretLegacyFlag
};

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

@@ -0,0 +1,3 @@
export { file } from './file.js';
export { glob } from './glob.js';
export * from './types.js';

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

@@ -0,0 +1,7 @@
import { file } from "./file.js";
import { glob } from "./glob.js";
export * from "./types.js";
export {
file,
glob
};

78
node_modules/astro/dist/content/loaders/types.d.ts generated vendored Normal file
View File

@@ -0,0 +1,78 @@
import type { FSWatcher } from 'vite';
import type * as z from 'zod/v4/core';
import type { AstroIntegrationLogger } from '../../core/logger/core.js';
import type { AstroConfig } from '../../types/public/config.js';
import type { LiveDataCollection, LiveDataEntry } from '../../types/public/content.js';
import type { RenderedContent } from '../data-store.js';
import type { DataStore, MetaStore } from '../mutable-data-store.js';
export type { DataStore, MetaStore };
export interface RenderMarkdownOptions {
/** The file URL of the markdown file being rendered */
fileURL?: URL;
}
export interface ParseDataOptions<TData extends Record<string, unknown>> {
/** The ID of the entry. Unique per collection */
id: string;
/** The raw, unvalidated data of the entry */
data: TData;
/** An optional file path, where the entry represents a local file. */
filePath?: string;
}
export interface LoaderContext {
/** The unique name of the collection */
collection: string;
/** A database to store the actual data */
store: DataStore;
/** A simple KV store, designed for things like sync tokens */
meta: MetaStore;
logger: AstroIntegrationLogger;
/** Astro config, with user config and merged defaults */
config: AstroConfig;
/** Validates and parses the data according to the collection schema */
parseData<TData extends Record<string, unknown>>(props: ParseDataOptions<TData>): Promise<TData>;
/** Renders markdown content to HTML and metadata */
renderMarkdown(content: string, options?: RenderMarkdownOptions): Promise<RenderedContent>;
/** Generates a non-cryptographic content digest. This can be used to check if the data has changed */
generateDigest(data: Record<string, unknown> | string): string;
/** When running in dev, this is a filesystem watcher that can be used to trigger updates */
watcher?: FSWatcher;
/** If the loader has been triggered by an integration, this may optionally contain extra data set by that integration */
refreshContextData?: Record<string, unknown>;
}
export type Loader = {
/** Unique name of the loader, e.g. the npm package name */
name: string;
/** Do the actual loading of the data */
load: (context: LoaderContext) => Promise<void>;
} & ({
/** Optionally, define the schema of the data. Will be overridden by user-defined schema */
schema?: z.$ZodType;
} | {
/** Optionally, provide a function to dynamically provide a schema. Will be overridden by user-defined schema */
createSchema?: () => Promise<{
schema: z.$ZodType;
types: string;
}>;
});
export interface LoadEntryContext<TEntryFilter = never> {
filter: TEntryFilter extends never ? {
id: string;
} : TEntryFilter;
collection: string;
}
export interface LoadCollectionContext<TCollectionFilter = unknown> {
filter?: TCollectionFilter;
collection: string;
}
export interface LiveLoader<TData extends Record<string, any> = Record<string, unknown>, TEntryFilter extends Record<string, any> | never = never, TCollectionFilter extends Record<string, any> | never = never, TError extends Error = Error> {
/** Unique name of the loader, e.g. the npm package name */
name: string;
/** Load a single entry */
loadEntry: (context: LoadEntryContext<TEntryFilter>) => Promise<LiveDataEntry<TData> | undefined | {
error: TError;
}>;
/** Load a collection of entries */
loadCollection: (context: LoadCollectionContext<TCollectionFilter>) => Promise<LiveDataCollection<TData> | {
error: TError;
}>;
}

0
node_modules/astro/dist/content/loaders/types.js generated vendored Normal file
View File

View File

@@ -0,0 +1,65 @@
import { type PathLike } from 'node:fs';
import { type DataEntry, ImmutableDataStore } from './data-store.js';
/**
* Extends the DataStore with the ability to change entries and write them to disk.
* This is kept as a separate class to avoid needing node builtins at runtime, when read-only access is all that is needed.
*/
export declare class MutableDataStore extends ImmutableDataStore {
#private;
set(collectionName: string, key: string, value: unknown): void;
delete(collectionName: string, key: string): void;
clear(collectionName: string): void;
clearAll(): void;
addAssetImport(assetImport: string, filePath?: string): void;
addAssetImports(assets: Array<string>, filePath?: string): void;
addModuleImport(fileName: string): void;
writeAssetImports(filePath: PathLike): Promise<void>;
writeModuleImports(filePath: PathLike): Promise<void>;
scopedStore(collectionName: string): DataStore;
/**
* Returns a MetaStore for a given collection, or if no collection is provided, the default meta collection.
*/
metaStore(collectionName?: string): MetaStore;
/**
* Returns a promise that resolves when all pending saves are complete.
* This includes any in-progress debounced saves for the data store, asset imports, and module imports.
*/
waitUntilSaveComplete(): Promise<void>;
toString(): string;
writeToDisk(): Promise<void>;
/**
* Attempts to load a MutableDataStore from the virtual module.
* This only works in Vite.
*/
static fromModule(): Promise<MutableDataStore>;
static fromMap(data: Map<string, Map<string, any>>): Promise<MutableDataStore>;
static fromString(data: string): Promise<MutableDataStore>;
static fromFile(filePath: string | URL): Promise<MutableDataStore>;
}
export interface DataStore {
get: <TData extends Record<string, unknown> = Record<string, unknown>>(key: string) => DataEntry<TData> | undefined;
entries: () => Array<[id: string, DataEntry]>;
set: <TData extends Record<string, unknown>>(opts: DataEntry<TData>) => boolean;
values: () => Array<DataEntry>;
keys: () => Array<string>;
delete: (key: string) => void;
clear: () => void;
has: (key: string) => boolean;
/**
* Adds a single asset to the store. This asset will be transformed
* by Vite, and the URL will be available in the final build.
* @param fileName
* @param specifier
* @returns
*/
addModuleImport: (fileName: string) => void;
}
/**
* A key-value store for metadata strings. Useful for storing things like sync tokens.
*/
export interface MetaStore {
get: (key: string) => string | undefined;
set: (key: string, value: string) => void;
has: (key: string) => boolean;
delete: (key: string) => void;
}

417
node_modules/astro/dist/content/mutable-data-store.js generated vendored Normal file
View File

@@ -0,0 +1,417 @@
import { existsSync, promises as fs } from "node:fs";
import * as devalue from "devalue";
import { Traverse } from "neotraverse/modern";
import { imageSrcToImportId, importIdToSymbolName } from "../assets/utils/resolveImports.js";
import { AstroError, AstroErrorData } from "../core/errors/index.js";
import { IMAGE_IMPORT_PREFIX } from "./consts.js";
import { ImmutableDataStore } from "./data-store.js";
import { contentModuleToId } from "./utils.js";
const SAVE_DEBOUNCE_MS = 500;
const MAX_DEPTH = 10;
class MutableDataStore extends ImmutableDataStore {
#file;
#assetsFile;
#modulesFile;
#saveTimeout;
#assetsSaveTimeout;
#modulesSaveTimeout;
#savePromise;
#savePromiseResolve;
#dirty = false;
#assetsDirty = false;
#modulesDirty = false;
#assetImports = /* @__PURE__ */ new Set();
#moduleImports = /* @__PURE__ */ new Map();
#writeInProgress = false;
#writeQueued = false;
set(collectionName, key, value) {
const collection = this._collections.get(collectionName) ?? /* @__PURE__ */ new Map();
collection.set(String(key), value);
this._collections.set(collectionName, collection);
this.#saveToDiskDebounced();
}
delete(collectionName, key) {
const collection = this._collections.get(collectionName);
if (collection) {
collection.delete(String(key));
this.#saveToDiskDebounced();
this.#writeAssetsImportsDebounced();
}
}
clear(collectionName) {
this._collections.delete(collectionName);
this.#saveToDiskDebounced();
this.#writeAssetsImportsDebounced();
}
clearAll() {
this._collections.clear();
this.#saveToDiskDebounced();
this.#writeAssetsImportsDebounced();
}
addAssetImport(assetImport, filePath) {
const id = imageSrcToImportId(assetImport, filePath);
if (id) {
this.#assetImports.add(id);
this.#writeAssetsImportsDebounced();
}
}
addAssetImports(assets, filePath) {
assets.forEach((asset) => this.addAssetImport(asset, filePath));
}
addModuleImport(fileName) {
const id = contentModuleToId(fileName);
if (id) {
this.#moduleImports.set(fileName, id);
this.#writeModulesImportsDebounced();
}
}
/**
* Rebuilds #assetImports from the current entries in _collections.
* This ensures stale import IDs are removed when entries are updated or deleted,
* preventing unrecoverable ImageNotFound errors in astro dev after a content entry's
* image path is temporarily set to an invalid value and then restored.
*/
#rebuildAssetImports() {
this.#assetImports.clear();
for (const collection of this._collections.values()) {
for (const entry of collection.values()) {
const typedEntry = entry;
if (typedEntry.assetImports?.length) {
for (const assetImport of typedEntry.assetImports) {
const id = imageSrcToImportId(assetImport, typedEntry.filePath);
if (id) {
this.#assetImports.add(id);
}
}
}
}
}
}
async writeAssetImports(filePath) {
this.#assetsFile = filePath;
this.#rebuildAssetImports();
if (this.#assetImports.size === 0) {
try {
await this.#writeFileAtomic(filePath, "export default new Map();");
} catch (err) {
throw new AstroError(AstroErrorData.UnknownFilesystemError, { cause: err });
}
}
if (!this.#assetsDirty && existsSync(filePath)) {
return;
}
const imports = [];
const exports = [];
const sortedAssetImports = [...this.#assetImports].sort();
sortedAssetImports.forEach((id) => {
const symbol = importIdToSymbolName(id);
imports.push(`import ${symbol} from ${JSON.stringify(id)};`);
exports.push(`[${JSON.stringify(id)}, ${symbol}]`);
});
const code = (
/* js */
`
${imports.join("\n")}
export default new Map([${exports.join(", ")}]);
`
);
try {
await this.#writeFileAtomic(filePath, code);
} catch (err) {
throw new AstroError(AstroErrorData.UnknownFilesystemError, { cause: err });
}
this.#assetsDirty = false;
}
async writeModuleImports(filePath) {
this.#modulesFile = filePath;
if (this.#moduleImports.size === 0) {
try {
await this.#writeFileAtomic(filePath, "export default new Map();");
} catch (err) {
throw new AstroError(AstroErrorData.UnknownFilesystemError, { cause: err });
}
}
if (!this.#modulesDirty && existsSync(filePath)) {
return;
}
const lines = [];
const sortedModuleImports = [...this.#moduleImports.entries()].sort(
([a], [b]) => a.localeCompare(b)
);
for (const [fileName, specifier] of sortedModuleImports) {
lines.push(`[${JSON.stringify(fileName)}, () => import(${JSON.stringify(specifier)})]`);
}
const code = `
export default new Map([
${lines.join(",\n")}]);
`;
try {
await this.#writeFileAtomic(filePath, code);
} catch (err) {
throw new AstroError(AstroErrorData.UnknownFilesystemError, { cause: err });
}
this.#modulesDirty = false;
}
#maybeResolveSavePromise() {
if (!this.#saveTimeout && !this.#assetsSaveTimeout && !this.#modulesSaveTimeout && !this.#writeQueued && !this.#writeInProgress && this.#savePromiseResolve) {
this.#savePromiseResolve();
this.#savePromiseResolve = void 0;
this.#savePromise = void 0;
}
}
#writeAssetsImportsDebounced() {
this.#assetsDirty = true;
if (this.#assetsFile) {
if (this.#assetsSaveTimeout) {
clearTimeout(this.#assetsSaveTimeout);
}
if (!this.#savePromise) {
this.#savePromise = new Promise((resolve) => {
this.#savePromiseResolve = resolve;
});
}
this.#assetsSaveTimeout = setTimeout(async () => {
this.#assetsSaveTimeout = void 0;
await this.writeAssetImports(this.#assetsFile);
this.#maybeResolveSavePromise();
}, SAVE_DEBOUNCE_MS);
}
}
#writeModulesImportsDebounced() {
this.#modulesDirty = true;
if (this.#modulesFile) {
if (this.#modulesSaveTimeout) {
clearTimeout(this.#modulesSaveTimeout);
}
if (!this.#savePromise) {
this.#savePromise = new Promise((resolve) => {
this.#savePromiseResolve = resolve;
});
}
this.#modulesSaveTimeout = setTimeout(async () => {
this.#modulesSaveTimeout = void 0;
await this.writeModuleImports(this.#modulesFile);
this.#maybeResolveSavePromise();
}, SAVE_DEBOUNCE_MS);
}
}
// Skips the debounce and writes to disk immediately
async #saveToDiskNow() {
if (this.#saveTimeout) {
clearTimeout(this.#saveTimeout);
}
this.#saveTimeout = void 0;
if (this.#file) {
await this.writeToDisk();
}
this.#maybeResolveSavePromise();
}
#saveToDiskDebounced() {
this.#dirty = true;
if (this.#saveTimeout) {
clearTimeout(this.#saveTimeout);
}
if (!this.#savePromise) {
this.#savePromise = new Promise((resolve) => {
this.#savePromiseResolve = resolve;
});
}
this.#saveTimeout = setTimeout(async () => {
this.#saveTimeout = void 0;
if (this.#file) {
await this.writeToDisk();
}
this.#maybeResolveSavePromise();
}, SAVE_DEBOUNCE_MS);
}
#writing = /* @__PURE__ */ new Set();
#pending = /* @__PURE__ */ new Set();
async #writeFileAtomic(filePath, data, depth = 0) {
if (depth > MAX_DEPTH) {
return;
}
const fileKey = filePath.toString();
if (this.#writing.has(fileKey)) {
this.#pending.add(fileKey);
return;
}
this.#writing.add(fileKey);
const tempFile = filePath instanceof URL ? new URL(`${filePath.href}.tmp`) : `${filePath}.tmp`;
try {
const oldData = await fs.readFile(filePath, "utf-8").catch(() => "");
if (oldData === data) {
return;
}
await fs.writeFile(tempFile, data);
await fs.rename(tempFile, filePath);
} finally {
this.#writing.delete(fileKey);
if (this.#pending.has(fileKey)) {
this.#pending.delete(fileKey);
await this.#writeFileAtomic(filePath, data, depth + 1);
}
}
}
scopedStore(collectionName) {
return {
get: (key) => this.get(collectionName, key),
entries: () => this.entries(collectionName),
values: () => this.values(collectionName),
keys: () => this.keys(collectionName),
set: ({ id: key, data, body, filePath, deferredRender, digest, rendered, assetImports }) => {
if (!key) {
throw new Error(`ID must be a non-empty string`);
}
const id = String(key);
if (digest) {
const existing = this.get(collectionName, id);
if (existing && existing.digest === digest) {
return false;
}
}
const foundAssets = new Set(assetImports);
new Traverse(data).forEach((_, val) => {
if (typeof val === "string" && val.startsWith(IMAGE_IMPORT_PREFIX)) {
const src = val.replace(IMAGE_IMPORT_PREFIX, "");
foundAssets.add(src);
}
});
const entry = {
id,
data
};
if (body) {
entry.body = body;
}
if (filePath) {
if (filePath.startsWith("/")) {
throw new Error(`File path must be relative to the site root. Got: ${filePath}`);
}
entry.filePath = filePath;
}
if (foundAssets.size) {
entry.assetImports = Array.from(foundAssets);
this.addAssetImports(entry.assetImports, filePath);
}
if (digest) {
entry.digest = digest;
}
if (rendered) {
entry.rendered = rendered;
}
if (deferredRender) {
entry.deferredRender = deferredRender;
if (filePath) {
this.addModuleImport(filePath);
}
}
this.set(collectionName, id, entry);
return true;
},
delete: (key) => this.delete(collectionName, key),
clear: () => this.clear(collectionName),
has: (key) => this.has(collectionName, key),
addAssetImport: (assetImport, fileName) => this.addAssetImport(assetImport, fileName),
addAssetImports: (assets, fileName) => this.addAssetImports(assets, fileName),
addModuleImport: (fileName) => this.addModuleImport(fileName)
};
}
/**
* Returns a MetaStore for a given collection, or if no collection is provided, the default meta collection.
*/
metaStore(collectionName = ":meta") {
const collectionKey = `meta:${collectionName}`;
return {
get: (key) => this.get(collectionKey, key),
set: (key, data) => this.set(collectionKey, key, data),
delete: (key) => this.delete(collectionKey, key),
has: (key) => this.has(collectionKey, key)
};
}
/**
* Returns a promise that resolves when all pending saves are complete.
* This includes any in-progress debounced saves for the data store, asset imports, and module imports.
*/
async waitUntilSaveComplete() {
if (!this.#savePromise) {
return Promise.resolve();
}
await this.#saveToDiskNow();
return this.#savePromise;
}
toString() {
const sorted = new Map(
[...this._collections.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([key, collection]) => [
key,
new Map([...collection.entries()].sort(([a], [b]) => a.localeCompare(b)))
])
);
return devalue.stringify(sorted);
}
async writeToDisk() {
if (!this.#dirty) {
return;
}
if (!this.#file) {
throw new AstroError(AstroErrorData.UnknownFilesystemError);
}
if (this.#writeInProgress) {
this.#writeQueued = true;
return;
}
try {
this.#dirty = false;
this.#writeInProgress = true;
await this.#writeFileAtomic(this.#file, this.toString());
} catch (err) {
throw new AstroError(AstroErrorData.UnknownFilesystemError, { cause: err });
} finally {
this.#writeInProgress = false;
if (this.#writeQueued) {
this.#writeQueued = false;
await this.writeToDisk();
}
}
}
/**
* Attempts to load a MutableDataStore from the virtual module.
* This only works in Vite.
*/
static async fromModule() {
try {
const data = await import("astro:data-layer-content");
const map = devalue.unflatten(data.default);
return MutableDataStore.fromMap(map);
} catch {
}
return new MutableDataStore();
}
static async fromMap(data) {
const store = new MutableDataStore();
store._collections = data;
return store;
}
static async fromString(data) {
const map = devalue.parse(data);
return MutableDataStore.fromMap(map);
}
static async fromFile(filePath) {
try {
if (existsSync(filePath)) {
const data = await fs.readFile(filePath, "utf-8");
const store2 = await MutableDataStore.fromString(data);
store2.#file = filePath;
return store2;
} else {
await fs.mkdir(new URL("./", filePath), { recursive: true });
}
} catch {
}
const store = new MutableDataStore();
store.#file = filePath;
return store;
}
}
export {
MutableDataStore
};

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 * as z from 'zod/v4';
export declare function createImage(pluginContext: PluginContext, shouldEmitFile: boolean, entryFilePath: string): () => z.ZodPipe<z.ZodString, z.ZodTransform<z.ZodNever | {
ASTRO_ASSET: string;
format: import("../assets/types.js").ImageInputFormat;
src: string;
width: number;
height: number;
fsPath: string;
orientation?: number | undefined;
}, string>>;

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

@@ -0,0 +1,26 @@
import * as z from "zod/v4";
import { emitClientAsset } from "../assets/utils/assets.js";
import { emitImageMetadata } from "../assets/utils/node.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 emitImageMetadata(
resolvedFilePath,
shouldEmitFile ? (opts) => emitClientAsset(pluginContext, opts) : 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
};

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

@@ -0,0 +1,106 @@
import type { MarkdownHeading } from '@astrojs/markdown-remark';
import * as z from 'zod/v4';
import type * as zCore from 'zod/v4/core';
import type { ImageMetadata } from '../assets/types.js';
import { type AstroComponentFactory } from '../runtime/server/index.js';
import type { LiveDataCollectionResult, LiveDataEntryResult } from '../types/public/content.js';
import { type LIVE_CONTENT_TYPE } from './consts.js';
import { type DataEntry } from './data-store.js';
import { LiveCollectionCacheHintError, LiveCollectionError, LiveCollectionValidationError, LiveEntryNotFoundError } from './loaders/errors.js';
import type { LiveLoader } from './loaders/types.js';
export { LiveCollectionError, LiveCollectionCacheHintError, LiveEntryNotFoundError, LiveCollectionValidationError, };
type LiveCollectionConfigMap = Record<string, {
loader: LiveLoader;
type: typeof LIVE_CONTENT_TYPE;
schema?: zCore.$ZodType;
}>;
export declare function createGetCollection({ liveCollections, }: {
liveCollections: LiveCollectionConfigMap;
}): (collection: string, filter?: ((entry: any) => unknown) | Record<string, unknown>) => Promise<{
data: Record<string, unknown>;
collection: string;
id: string;
filePath?: string;
body?: string;
digest?: number | string;
rendered?: import("./data-store.js").RenderedContent;
deferredRender?: boolean;
assetImports?: Array<string>;
}[]>;
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({ liveCollections }: {
liveCollections: LiveCollectionConfigMap;
}): (collectionOrLookupObject: string | EntryLookupObject, lookup?: string | Record<string, unknown>) => 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)[]>;
export declare function createGetLiveCollection({ liveCollections, }: {
liveCollections: LiveCollectionConfigMap;
}): (collection: string, filter?: Record<string, unknown>) => Promise<LiveDataCollectionResult>;
export declare function createGetLiveEntry({ liveCollections, }: {
liveCollections: LiveCollectionConfigMap;
}): (collection: string, lookup: string | Record<string, unknown>) => Promise<LiveDataEntryResult>;
type RenderResult = {
Content: AstroComponentFactory;
headings: MarkdownHeading[];
remarkPluginFrontmatter: Record<string, any>;
};
export declare function updateImageReferencesInData<T extends Record<string, unknown>>(data: T, fileName?: string, imageAssetMap?: Map<string, ImageMetadata>): T;
export declare function renderEntry(entry: DataEntry): Promise<RenderResult>;
export declare function createReference(): (collection: string) => z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
id: z.ZodString;
collection: z.ZodString;
}, z.core.$strip>, z.ZodObject<{
slug: z.ZodString;
collection: z.ZodString;
}, z.core.$strip>]>, z.ZodTransform<{
id: string;
collection: string;
} | {
slug: string;
collection: string;
} | undefined, string | {
id: string;
collection: string;
} | {
slug: string;
collection: string;
}>>;
export declare function defineCollection(config: any): import("./config.js").CollectionConfig<import("./config.js").BaseSchema, import("./loaders/types.js").Loader | (() => {
id: string;
}[] | Promise<{
id: string;
}[]> | Record<string, Omit<{
id: string;
}, "id"> & {
id?: string;
}> | Promise<Record<string, Omit<{
id: string;
}, "id"> & {
id?: string;
}>>)>;
export declare function defineLiveCollection(): void;
export declare function createDeprecatedFunction(functionName: string): (collection: string) => never;

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

@@ -0,0 +1,559 @@
import { escape } from "html-escaper";
import { Traverse } from "neotraverse/modern";
import * as z from "zod/v4";
import { createSvgComponent } from "../assets/runtime.js";
import { imageSrcToImportId } from "../assets/utils/resolveImports.js";
import { AstroError, AstroErrorData } from "../core/errors/index.js";
import { isRemotePath, prependForwardSlash } from "../core/path.js";
import {
createComponent,
createHeadAndContent,
renderComponent,
renderScriptElement,
renderTemplate,
renderUniqueStylesheet,
render as serverRender,
unescapeHTML
} from "../runtime/server/index.js";
import { defineCollection as defineCollectionOrig } from "./config.js";
import { IMAGE_IMPORT_PREFIX } from "./consts.js";
import { globalDataStore } from "./data-store.js";
import {
LiveCollectionCacheHintError,
LiveCollectionError,
LiveCollectionValidationError,
LiveEntryNotFoundError
} from "./loaders/errors.js";
const cacheHintSchema = z.object({
tags: z.array(z.string()).optional(),
lastModified: z.date().optional()
});
async function parseLiveEntry(entry, schema, collection) {
try {
const parsed = await z.safeParseAsync(schema, entry.data);
if (!parsed.success) {
return {
error: new LiveCollectionValidationError(collection, entry.id, parsed.error)
};
}
if (entry.cacheHint) {
const cacheHint = cacheHintSchema.safeParse(entry.cacheHint);
if (!cacheHint.success) {
return {
error: new LiveCollectionCacheHintError(collection, entry.id, cacheHint.error)
};
}
entry.cacheHint = cacheHint.data;
}
return {
entry: {
...entry,
data: parsed.data
}
};
} catch (error) {
return {
error: new LiveCollectionError(
collection,
`Unexpected error parsing entry ${entry.id} in collection ${collection}`,
error
)
};
}
}
function createGetCollection({
liveCollections
}) {
return async function getCollection(collection, filter) {
if (collection in liveCollections) {
throw new AstroError({
...AstroErrorData.UnknownContentCollectionError,
message: `Collection "${collection}" is a live collection. Use getLiveCollection() instead of getCollection().`
});
}
const hasFilter = typeof filter === "function";
const store = await globalDataStore.get();
if (store.hasCollection(collection)) {
const { default: imageAssetMap } = await import("astro:asset-imports");
const result = [];
for (const rawEntry of store.values(collection)) {
const data = updateImageReferencesInData(rawEntry.data, rawEntry.filePath, imageAssetMap);
let entry = {
...rawEntry,
data,
collection
};
if (hasFilter && !filter(entry)) {
continue;
}
result.push(entry);
}
return result;
} else {
console.warn(
`The collection ${JSON.stringify(
collection
)} does not exist or is empty. Please check your content config file for errors.`
);
return [];
}
};
}
function createGetEntry({ liveCollections }) {
return async function getEntry(collectionOrLookupObject, lookup) {
let collection, lookupId;
if (typeof collectionOrLookupObject === "string") {
collection = collectionOrLookupObject;
if (!lookup)
throw new AstroError({
...AstroErrorData.UnknownContentCollectionError,
message: "`getEntry()` requires an entry identifier as the second argument."
});
lookupId = lookup;
} else {
collection = collectionOrLookupObject.collection;
lookupId = "id" in collectionOrLookupObject ? collectionOrLookupObject.id : collectionOrLookupObject.slug;
}
if (collection in liveCollections) {
throw new AstroError({
...AstroErrorData.UnknownContentCollectionError,
message: `Collection "${collection}" is a live collection. Use getLiveEntry() instead of getEntry().`
});
}
if (typeof lookupId === "object") {
throw new AstroError({
...AstroErrorData.UnknownContentCollectionError,
message: `The entry identifier must be a string. Received object.`
});
}
const store = await globalDataStore.get();
if (store.hasCollection(collection)) {
const entry = store.get(collection, lookupId);
if (!entry) {
console.warn(`Entry ${collection} \u2192 ${lookupId} was not found.`);
return;
}
const { default: imageAssetMap } = await import("astro:asset-imports");
entry.data = updateImageReferencesInData(entry.data, entry.filePath, imageAssetMap);
const result = {
...entry,
collection
};
warnForPropertyAccess(
result.data,
"slug",
`[content] Attempted to access deprecated property on "${collection}" entry.
The "slug" property is no longer automatically added to entries. Please use the "id" property instead.`
);
warnForPropertyAccess(
result,
"render",
`[content] Invalid attempt to access "render()" method on "${collection}" entry.
To render an entry, use "render(entry)" from "astro:content".`
);
return result;
}
return void 0;
};
}
function warnForPropertyAccess(entry, prop, message) {
if (!(prop in entry)) {
let _value = void 0;
Object.defineProperty(entry, prop, {
get() {
if (_value === void 0) {
console.error(message);
}
return _value;
},
set(v) {
_value = v;
},
enumerable: false
});
}
}
function createGetEntries(getEntry) {
return async function getEntries(entries) {
return Promise.all(entries.map((e) => getEntry(e)));
};
}
function createGetLiveCollection({
liveCollections
}) {
return async function getLiveCollection(collection, filter) {
if (!(collection in liveCollections)) {
return {
error: new LiveCollectionError(
collection,
`Collection "${collection}" is not a live collection. Use getCollection() instead of getLiveCollection() to load regular content collections.`
)
};
}
try {
const context = {
filter,
collection
};
const response = await liveCollections[collection].loader?.loadCollection?.(context);
if (response && "error" in response) {
return { error: response.error };
}
const { schema } = liveCollections[collection];
let processedEntries = response.entries;
if (schema) {
const entryResults = await Promise.all(
response.entries.map((entry) => parseLiveEntry(entry, schema, collection))
);
for (const result of entryResults) {
if (result.error) {
return { error: result.error };
}
}
processedEntries = entryResults.map((result) => result.entry);
}
let cacheHint = response.cacheHint;
if (cacheHint) {
const cacheHintResult = cacheHintSchema.safeParse(cacheHint);
if (!cacheHintResult.success) {
return {
error: new LiveCollectionCacheHintError(collection, void 0, cacheHintResult.error)
};
}
cacheHint = cacheHintResult.data;
}
if (processedEntries.length > 0) {
const entryTags = /* @__PURE__ */ new Set();
let latestModified;
for (const entry of processedEntries) {
if (entry.cacheHint) {
if (entry.cacheHint.tags) {
entry.cacheHint.tags.forEach((tag) => entryTags.add(tag));
}
if (entry.cacheHint.lastModified instanceof Date) {
if (latestModified === void 0 || entry.cacheHint.lastModified > latestModified) {
latestModified = entry.cacheHint.lastModified;
}
}
}
}
if (entryTags.size > 0 || latestModified || cacheHint) {
const mergedCacheHint = {};
if (cacheHint?.tags || entryTags.size > 0) {
mergedCacheHint.tags = [.../* @__PURE__ */ new Set([...cacheHint?.tags || [], ...entryTags])];
}
if (cacheHint?.lastModified && latestModified) {
mergedCacheHint.lastModified = cacheHint.lastModified > latestModified ? cacheHint.lastModified : latestModified;
} else if (cacheHint?.lastModified || latestModified) {
mergedCacheHint.lastModified = cacheHint?.lastModified ?? latestModified;
}
cacheHint = mergedCacheHint;
}
}
return {
entries: processedEntries,
cacheHint
};
} catch (error) {
return {
error: new LiveCollectionError(
collection,
`Unexpected error loading collection ${collection}${error instanceof Error ? `: ${error.message}` : ""}`,
error
)
};
}
};
}
function createGetLiveEntry({
liveCollections
}) {
return async function getLiveEntry(collection, lookup) {
if (!(collection in liveCollections)) {
return {
error: new LiveCollectionError(
collection,
`Collection "${collection}" is not a live collection. Use getCollection() instead of getLiveEntry() to load regular content collections.`
)
};
}
try {
const lookupObject = {
filter: typeof lookup === "string" ? { id: lookup } : lookup,
collection
};
let entry = await liveCollections[collection].loader?.loadEntry?.(lookupObject);
if (entry && "error" in entry) {
return { error: entry.error };
}
if (!entry) {
return {
error: new LiveEntryNotFoundError(collection, lookup)
};
}
const { schema } = liveCollections[collection];
if (schema) {
const result = await parseLiveEntry(entry, schema, collection);
if (result.error) {
return { error: result.error };
}
entry = result.entry;
}
return {
entry,
cacheHint: entry.cacheHint
};
} catch (error) {
return {
error: new LiveCollectionError(
collection,
`Unexpected error loading entry ${collection} \u2192 ${typeof lookup === "string" ? lookup : JSON.stringify(lookup)}`,
error
)
};
}
};
}
const CONTENT_LAYER_IMAGE_REGEX = /__ASTRO_IMAGE_="([^"]+)"/g;
async function updateImageReferencesInBody(html, fileName) {
const { default: imageAssetMap } = await import("astro:asset-imports");
const imageObjects = /* @__PURE__ */ new Map();
const { getImage } = await import("virtual:astro:get-image");
for (const [_full, imagePath] of html.matchAll(CONTENT_LAYER_IMAGE_REGEX)) {
try {
const decodedImagePath = JSON.parse(imagePath.replaceAll("&#x22;", '"'));
let image;
if (URL.canParse(decodedImagePath.src)) {
image = await getImage(decodedImagePath);
} else {
const id = imageSrcToImportId(decodedImagePath.src, fileName);
const imported = imageAssetMap.get(id);
if (!id || imageObjects.has(id) || !imported) {
continue;
}
image = await getImage({ ...decodedImagePath, src: imported });
}
imageObjects.set(imagePath, image);
} catch {
throw new Error(`Failed to parse image reference: ${imagePath}`);
}
}
return html.replaceAll(CONTENT_LAYER_IMAGE_REGEX, (full, imagePath) => {
const image = imageObjects.get(imagePath);
if (!image) {
return full;
}
const { index, ...attributes } = image.attributes;
return Object.entries({
...attributes,
src: image.src,
srcset: image.srcSet.attribute,
// This attribute is used by the toolbar audit
...import.meta.env.DEV ? { "data-image-component": "true" } : {}
}).map(([key, value]) => value ? `${key}="${escape(value)}"` : "").join(" ");
});
}
function updateImageReferencesInData(data, fileName, imageAssetMap) {
return new Traverse(data).map(function(ctx, val) {
if (typeof val === "string" && val.startsWith(IMAGE_IMPORT_PREFIX)) {
const src = val.replace(IMAGE_IMPORT_PREFIX, "");
const id = imageSrcToImportId(src, fileName);
if (!id) {
ctx.update(src);
return;
}
const imported = imageAssetMap?.get(id);
if (imported) {
if (imported.__svgData) {
const { __svgData: svgData, ...meta } = imported;
ctx.update(createSvgComponent({ meta, ...svgData }));
} else {
ctx.update(imported);
}
} else {
ctx.update(src);
}
}
});
}
async function renderEntry(entry) {
if (!entry) {
throw new AstroError(AstroErrorData.RenderUndefinedEntryError);
}
if (entry.deferredRender) {
try {
const { default: contentModules } = await import("astro:content-module-imports");
const renderEntryImport = contentModules.get(entry.filePath);
return render({
collection: "",
id: entry.id,
renderEntryImport
});
} catch (e) {
console.error(e);
}
}
const html = entry?.rendered?.metadata?.imagePaths?.length && entry.filePath ? await updateImageReferencesInBody(entry.rendered.html, entry.filePath) : entry?.rendered?.html;
const Content = createComponent(() => serverRender`${unescapeHTML(html)}`);
return {
Content,
headings: entry?.rendered?.metadata?.headings ?? [],
remarkPluginFrontmatter: entry?.rendered?.metadata?.frontmatter ?? {}
};
}
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: isRemotePath(link) ? link : 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() {
return function reference(collection) {
return z.union([
z.string(),
z.object({
id: z.string(),
collection: z.string()
}),
z.object({
slug: z.string(),
collection: z.string()
})
]).transform((lookup, ctx) => {
if (typeof lookup === "object") {
if (lookup.collection !== collection) {
const flattenedErrorPath = ctx.issues[0]?.path?.join(".");
ctx.addIssue({
code: "custom",
message: `**${flattenedErrorPath}**: Reference to ${collection} invalid. Expected ${collection}. Received ${lookup.collection}.`
});
return;
}
return lookup;
}
return { id: lookup, collection };
});
};
}
function isPropagatedAssetsModule(module) {
return typeof module === "object" && module != null && "__astroPropagation" in module;
}
function defineCollection(config) {
if (config.type === "live") {
throw new AstroError({
...AstroErrorData.LiveContentConfigError,
message: AstroErrorData.LiveContentConfigError.message(
"Collections with type `live` must be defined in a `src/live.config.ts` file."
)
});
}
return defineCollectionOrig(config);
}
function defineLiveCollection() {
throw new AstroError({
...AstroErrorData.LiveContentConfigError,
message: AstroErrorData.LiveContentConfigError.message(
"Live collections must be defined in a `src/live.config.ts` file."
)
});
}
function createDeprecatedFunction(functionName) {
return (collection) => {
const error = new AstroError({
...AstroErrorData.GetEntryDeprecationError,
message: AstroErrorData.GetEntryDeprecationError.message(collection, functionName)
});
const stackLines = error.stack?.split("\n");
if (stackLines && stackLines.length > 1) {
stackLines.splice(1, 1);
error.stack = stackLines.join("\n");
}
throw error;
};
}
export {
LiveCollectionCacheHintError,
LiveCollectionError,
LiveCollectionValidationError,
LiveEntryNotFoundError,
createDeprecatedFunction,
createGetCollection,
createGetEntries,
createGetEntry,
createGetLiveCollection,
createGetLiveEntry,
createReference,
defineCollection,
defineLiveCollection,
renderEntry,
updateImageReferencesInData
};

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

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

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

@@ -0,0 +1,42 @@
import { createContentTypesGenerator } from "./types-generator.js";
import { globalContentConfigObserver } from "./utils.js";
async function attachContentServerListeners({
viteServer,
fs,
logger,
settings
}) {
const maxListeners = viteServer.watcher.getMaxListeners();
if (maxListeners !== 0 && maxListeners < 50) {
viteServer.watcher.setMaxListeners(50);
}
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 })
);
}
export {
attachContentServerListeners
};

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

@@ -0,0 +1,23 @@
import type fsMod from 'node:fs';
import { type ViteDevServer } from 'vite';
import type { AstroLogger } from '../core/logger/core.js';
import type { AstroSettings } from '../types/astro.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: AstroLogger;
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<void>;
queueEvent: (rawEvent: RawContentEvent) => void;
}>;
export {};

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

@@ -0,0 +1,490 @@
import * as path from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import colors from "piccolore";
import {
isRunnableDevEnvironment,
normalizePath
} from "vite";
import * as z from "zod/v4";
import { AstroError } from "../core/errors/errors.js";
import { AstroErrorData } from "../core/errors/index.js";
import { isRelativePath } from "../core/path.js";
import {
COLLECTIONS_DIR,
CONTENT_LAYER_TYPE,
CONTENT_TYPES_FILE,
VIRTUAL_MODULE_ID
} from "./consts.js";
import {
getContentEntryIdAndSlug,
getContentPaths,
getDataEntryExts,
getDataEntryId,
getEntryCollectionName,
getEntryConfigByExtMap,
getEntrySlug,
getEntryType,
reloadContentConfigObserver
} from "./utils.js";
import { ASTRO_VITE_ENVIRONMENT_NAMES } from "../core/constants.js";
async function createContentTypesGenerator({
contentConfigObserver,
fs,
logger,
settings,
viteServer
}) {
const collectionEntryMap = {};
const contentPaths = getContentPaths(
settings.config,
fs,
settings.config.legacy?.collectionsBackwardsCompat
);
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() {
events.push({ name: "add", entry: contentPaths.config.url });
await runEvents();
}
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[collectionKey2] = {
type: "unknown",
entries: {}
};
logger.debug("content", `${colors.cyan(collection2)} collection added`);
break;
case "unlinkDir":
delete collectionEntryMap[collectionKey2];
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,
environment: viteServer.environments[ASTRO_VITE_ENVIRONMENT_NAMES.astro]
});
return { shouldGenerateTypes: true };
}
const { entry } = event;
const { contentDir } = contentPaths;
const collection = getEntryCollectionName({ entry, contentDir });
if (collection === void 0) {
logger.warn(
"content",
`${colors.bold(
normalizePath(
path.relative(fileURLToPath(contentPaths.contentDir), fileURLToPath(event.entry))
)
)} must live in a ${colors.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.environments.client.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.environments.client.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 (contentPaths.config.url.pathname !== event.entry.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
});
if (!isRunnableDevEnvironment(viteServer.environments[ASTRO_VITE_ENVIRONMENT_NAMES.ssr])) {
return;
}
invalidateVirtualMod(viteServer.environments[ASTRO_VITE_ENVIRONMENT_NAMES.ssr]);
}
}
return { init, queueEvent };
}
function invalidateVirtualMod(environment) {
const virtualMod = environment.moduleGraph.getModuleById("\0" + VIRTUAL_MODULE_ID);
if (!virtualMod) return;
environment.moduleGraph.invalidateModule(virtualMod);
}
function normalizeConfigPath(from, to) {
const configPath = path.relative(from, to).replace(/\.ts$/, ".js");
const normalizedPath = configPath.replaceAll("\\", "/");
return `"${isRelativePath(configPath) ? "" : "./"}${normalizedPath}"`;
}
const createSchemaResultCache = /* @__PURE__ */ new Map();
async function getCreateSchemaResult(collection, collectionKey) {
const cached = createSchemaResultCache.get(collectionKey);
if (cached) {
return cached;
}
if (collection?.type === CONTENT_LAYER_TYPE && typeof collection.loader === "object" && !collection.loader.schema && collection.loader.createSchema) {
const result = await collection.loader.createSchema();
createSchemaResultCache.set(collectionKey, result);
return result;
}
}
async function getContentLayerSchema(collection, collectionKey) {
if (collection?.type !== CONTENT_LAYER_TYPE || typeof collection.loader === "function") {
return;
}
if (collection.loader.schema) {
return collection.loader.schema;
}
const result = await getCreateSchemaResult(collection, collectionKey);
return result?.schema;
}
async function typeForCollection(collection, collectionKey) {
if (collection?.schema) {
return { type: `InferEntrySchema<${collectionKey}>` };
}
if (!collection?.type || typeof collection.loader === "function" || !collection.loader) {
return { type: "any" };
}
if (typeof collection.loader === "object" && collection.loader.schema) {
return { type: `InferLoaderSchema<${collectionKey}>` };
}
const result = await getCreateSchemaResult(collection, collectionKey);
if (!result) {
return { type: "any" };
}
const base = `loaders/${collectionKey.slice(1, -1)}`;
return {
type: `import("./${base}.js").Entry`,
injectedType: {
filename: `${base}.ts`,
content: result.types
}
};
}
async function writeContentFiles({
fs,
contentPaths,
collectionEntryMap,
typeTemplateContent,
contentEntryTypes,
contentConfig,
viteServer,
logger,
settings
}) {
let dataTypesStr = "";
const collectionSchemasDir = new URL(COLLECTIONS_DIR, settings.dotAstroDir);
fs.mkdirSync(collectionSchemasDir, { recursive: true });
for (const [collection, config] of Object.entries(contentConfig?.collections ?? {})) {
collectionEntryMap[JSON.stringify(collection)] ??= {
type: config.type ?? "unknown",
entries: {}
};
}
let contentCollectionsMap = {};
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" && collectionConfig.type !== CONTENT_LAYER_TYPE && collection.type !== collectionConfig.type) {
viteServer.environments.client.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 { type: dataType, injectedType } = await typeForCollection(
collectionConfig,
collectionKey
);
if (injectedType) {
if (settings.injectedTypes.some((t) => t.filename === CONTENT_TYPES_FILE)) {
const url = new URL(injectedType.filename, settings.dotAstroDir);
await fs.promises.mkdir(path.dirname(fileURLToPath(url)), { recursive: true });
await fs.promises.writeFile(url, injectedType.content, "utf-8");
} else {
settings.injectedTypes.push(injectedType);
}
}
dataTypesStr += `${collectionKey}: Record<string, {
id: string;
body?: string;
collection: ${collectionKey};
data: ${dataType};
rendered?: RenderedContent;
filePath?: string;
}>;
`;
if (collectionConfig && (collectionConfig.schema || await getContentLayerSchema(collectionConfig, collectionKey))) {
await generateJSONSchema(fs, collectionConfig, collectionKey, collectionSchemasDir, logger);
contentCollectionsMap[collectionKey] = collection;
}
}
if (settings.config.experimental.contentIntellisense) {
let contentCollectionManifest = {
collections: [],
entries: {}
};
Object.entries(contentCollectionsMap).forEach(([collectionKey, collection]) => {
const collectionConfig = contentConfig?.collections[JSON.parse(collectionKey)];
const key = JSON.parse(collectionKey);
contentCollectionManifest.collections.push({
hasSchema: Boolean(
// Is there a user provided schema or
collectionConfig?.schema || // Is it a loader object and
typeof collectionConfig?.loader === "object" && // Is it a loader static schema or
(collectionConfig.loader.schema || // is it a loader dynamic schema
createSchemaResultCache.has(collectionKey))
),
name: key
});
Object.keys(collection.entries).forEach((entryKey) => {
const entryPath = new URL(
JSON.parse(entryKey),
contentPaths.contentDir + `${key}/`
).toString();
contentCollectionManifest.entries[entryPath.toLowerCase()] = key;
});
});
await fs.promises.writeFile(
new URL("./collections.json", collectionSchemasDir),
JSON.stringify(contentCollectionManifest, null, 2)
);
}
const configPathRelativeToCacheDir = normalizeConfigPath(
settings.dotAstroDir.pathname,
contentPaths.config.url.pathname
);
const liveConfigPathRelativeToCacheDir = contentPaths.liveConfig?.exists ? normalizeConfigPath(settings.dotAstroDir.pathname, contentPaths.liveConfig.url.pathname) : void 0;
for (const contentEntryType of contentEntryTypes) {
if (contentEntryType.contentModuleTypes) {
typeTemplateContent = contentEntryType.contentModuleTypes + "\n" + typeTemplateContent;
}
}
typeTemplateContent = typeTemplateContent.replace("// @@DATA_ENTRY_MAP@@", dataTypesStr).replace(
"'@@CONTENT_CONFIG_TYPE@@'",
contentConfig ? `typeof import(${configPathRelativeToCacheDir})` : "never"
).replace(
"'@@LIVE_CONTENT_CONFIG_TYPE@@'",
liveConfigPathRelativeToCacheDir ? `typeof import(${liveConfigPathRelativeToCacheDir})` : "never"
);
if (settings.injectedTypes.some((t) => t.filename === CONTENT_TYPES_FILE)) {
await fs.promises.writeFile(
new URL(CONTENT_TYPES_FILE, settings.dotAstroDir),
typeTemplateContent,
"utf-8"
);
} else {
settings.injectedTypes.push({
filename: CONTENT_TYPES_FILE,
content: typeTemplateContent
});
}
}
async function generateJSONSchema(fsMod, collectionConfig, collectionKey, collectionSchemasDir, logger) {
let zodSchemaForJson = typeof collectionConfig.schema === "function" ? collectionConfig.schema({ image: () => z.string() }) : collectionConfig.schema;
if (!zodSchemaForJson && collectionConfig.type === CONTENT_LAYER_TYPE) {
zodSchemaForJson = await getContentLayerSchema(collectionConfig, collectionKey);
}
if (collectionConfig.type === CONTENT_LAYER_TYPE && collectionConfig.loader.name === "file-loader") {
zodSchemaForJson = z.object({}).catchall(zodSchemaForJson);
}
if (zodSchemaForJson instanceof z.ZodObject) {
const existingMeta = z.globalRegistry.get(zodSchemaForJson);
zodSchemaForJson = zodSchemaForJson.extend({
$schema: z.string().optional()
});
if (existingMeta) {
z.globalRegistry.add(zodSchemaForJson, existingMeta);
}
}
try {
const schema = z.toJSONSchema(zodSchemaForJson, {
unrepresentable: "any",
override: (ctx) => {
const def = ctx.zodSchema._zod.def;
if (def.type === "date") {
ctx.jsonSchema.type = "string";
ctx.jsonSchema.format = "date-time";
}
},
// Collection schemas are used for parsing collection input, so we need to tell Zod to use the
// input shape when generating a JSON schema.
io: "input"
});
const schemaStr = JSON.stringify(schema, null, 2);
const schemaJsonPath = new URL(
`./${collectionKey.replace(/"/g, "")}.schema.json`,
collectionSchemasDir
);
await fsMod.promises.writeFile(schemaJsonPath, schemaStr);
} catch (err) {
logger.warn(
"content",
`An error was encountered while creating the JSON schema for the ${collectionKey} collection. Proceeding without it. Error: ${err}`
);
}
}
export {
createContentTypesGenerator
};

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

@@ -0,0 +1,196 @@
import fsMod from 'node:fs';
import type { PluginContext } from 'rollup';
import type { RunnableDevEnvironment } from 'vite';
import * as z from 'zod/v4';
import type { AstroLogger } from '../core/logger/core.js';
import type { AstroSettings } from '../types/astro.js';
import type { AstroConfig } from '../types/public/config.js';
import type { ContentEntryType, DataEntryType } from '../types/public/content.js';
import { type CONTENT_FLAGS } from './consts.js';
import type { LoaderContext } from './loaders/types.js';
export declare const loaderReturnSchema: z.ZodUnion<readonly [z.ZodArray<z.ZodObject<{
id: z.ZodString;
}, z.core.$loose>>, z.ZodRecord<z.ZodString, z.ZodObject<{
id: z.ZodOptional<z.ZodString>;
}, z.core.$loose>>]>;
declare const collectionConfigParser: z.ZodUnion<readonly [z.ZodObject<{
type: z.ZodOptional<z.ZodLiteral<"content">>;
schema: z.ZodOptional<z.ZodAny>;
loader: z.ZodOptional<z.ZodNever>;
}, z.core.$strip>, z.ZodObject<{
type: z.ZodOptional<z.ZodLiteral<"data">>;
schema: z.ZodOptional<z.ZodAny>;
loader: z.ZodOptional<z.ZodNever>;
}, z.core.$strip>, z.ZodObject<{
type: z.ZodLiteral<"content_layer">;
schema: z.ZodOptional<z.ZodAny>;
loader: z.ZodUnion<readonly [z.ZodFunction<z.core.$ZodFunctionArgs, z.core.$ZodFunctionOut>, z.ZodObject<{
name: z.ZodString;
load: z.ZodFunction<z.ZodTuple<readonly [z.ZodCustom<LoaderContext, LoaderContext>], null>, z.ZodCustom<void | {
schema?: any;
types?: string;
}, void | {
schema?: any;
types?: string;
}>>;
schema: z.ZodOptional<z.ZodPipe<z.ZodAny, z.ZodTransform<any, any>>>;
createSchema: z.ZodOptional<z.ZodFunction<z.ZodTuple<readonly [], null>, z.ZodPromise<z.ZodObject<{
schema: z.ZodCustom<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
types: z.ZodString;
}, z.core.$strip>>>>;
}, z.core.$strip>]>;
}, z.core.$strip>, z.ZodObject<{
type: z.ZodDefault<z.ZodOptional<z.ZodLiteral<"live">>>;
schema: z.ZodOptional<z.ZodAny>;
loader: z.ZodFunction<z.core.$ZodFunctionArgs, z.core.$ZodFunctionOut>;
}, z.core.$strip>]>;
declare const contentConfigParser: z.ZodObject<{
collections: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodObject<{
type: z.ZodOptional<z.ZodLiteral<"content">>;
schema: z.ZodOptional<z.ZodAny>;
loader: z.ZodOptional<z.ZodNever>;
}, z.core.$strip>, z.ZodObject<{
type: z.ZodOptional<z.ZodLiteral<"data">>;
schema: z.ZodOptional<z.ZodAny>;
loader: z.ZodOptional<z.ZodNever>;
}, z.core.$strip>, z.ZodObject<{
type: z.ZodLiteral<"content_layer">;
schema: z.ZodOptional<z.ZodAny>;
loader: z.ZodUnion<readonly [z.ZodFunction<z.core.$ZodFunctionArgs, z.core.$ZodFunctionOut>, z.ZodObject<{
name: z.ZodString;
load: z.ZodFunction<z.ZodTuple<readonly [z.ZodCustom<LoaderContext, LoaderContext>], null>, z.ZodCustom<void | {
schema?: any;
types?: string;
}, void | {
schema?: any;
types?: string;
}>>;
schema: z.ZodOptional<z.ZodPipe<z.ZodAny, z.ZodTransform<any, any>>>;
createSchema: z.ZodOptional<z.ZodFunction<z.ZodTuple<readonly [], null>, z.ZodPromise<z.ZodObject<{
schema: z.ZodCustom<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
types: z.ZodString;
}, z.core.$strip>>>>;
}, z.core.$strip>]>;
}, z.core.$strip>, z.ZodObject<{
type: z.ZodDefault<z.ZodOptional<z.ZodLiteral<"live">>>;
schema: z.ZodOptional<z.ZodAny>;
loader: z.ZodFunction<z.core.$ZodFunctionArgs, z.core.$ZodFunctionOut>;
}, z.core.$strip>]>>;
}, z.core.$strip>;
export type CollectionConfig = z.infer<typeof collectionConfigParser>;
export type ContentConfig = z.infer<typeof contentConfigParser> & {
digest?: string;
};
type EntryInternal = {
rawData: string | undefined;
filePath: string;
};
export declare function parseEntrySlug({ id, collection, generatedSlug, frontmatterSlug, }: {
id: string;
collection: string;
generatedSlug: string;
frontmatterSlug?: unknown;
}): string;
export declare function getEntryData<TInputData extends Record<string, unknown> = Record<string, unknown>, TOutputData extends TInputData = TInputData>(entry: {
id: string;
collection: string;
unvalidatedData: TInputData;
_internal: EntryInternal;
}, collectionConfig: CollectionConfig, shouldEmitFile: boolean, pluginContext?: PluginContext): Promise<TOutputData>;
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 getSymlinkedContentCollections({ contentDir, logger, fs, }: {
contentDir: URL;
logger: AstroLogger;
fs: typeof fsMod;
}): Promise<Map<string, string>>;
export declare function reverseSymlink({ entry, symlinks, contentDir, }: {
entry: string | URL;
contentDir: string | URL;
symlinks?: Map<string, string>;
}): string;
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' | 'root'>, contentFileExts: string[], dataFileExts: string[]): 'content' | 'data' | 'config' | 'ignored';
export declare function safeParseFrontmatter(source: string, id?: string): import("@astrojs/markdown-remark").ParseFrontmatterResult;
/**
* 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 hasContentFlag(viteId: string, flag: (typeof CONTENT_FLAGS)[number]): boolean;
export declare function isDeferredModule(viteId: string): boolean;
export declare function reloadContentConfigObserver({ observer, ...loadContentConfigOpts }: {
fs: typeof fsMod;
settings: AstroSettings;
environment: RunnableDevEnvironment;
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 type ContentPaths = {
root: URL;
contentDir: URL;
assetsDir: URL;
typesTemplate: URL;
virtualModTemplate: URL;
config: {
exists: boolean;
url: URL;
};
liveConfig: {
exists: boolean;
url: URL;
};
};
export declare function getContentPaths({ srcDir, root }: Pick<AstroConfig, 'root' | 'srcDir'>, fs?: typeof fsMod, legacyCollectionsBackwardsCompat?: boolean): 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>;
/**
* Unlike `path.posix.relative`, this function will accept a platform path and return a posix path.
*/
export declare function posixRelative(from: string, to: string): string;
export declare function contentModuleToId(fileName: string): string;
export declare function safeStringify(value: unknown): string;
export {};

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

@@ -0,0 +1,678 @@
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 colors from "piccolore";
import xxhash from "xxhash-wasm";
import * as z from "zod/v4";
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_LAYER_TYPE,
CONTENT_MODULE_FLAG,
DEFERRED_MODULE,
IMAGE_IMPORT_PREFIX,
LIVE_CONTENT_TYPE
} from "./consts.js";
import { glob, secretLegacyFlag } from "./loaders/glob.js";
import { createImage } from "./runtime-assets.js";
const entryTypeSchema = z.object({
id: z.string({
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({
error: "Content entry `id` must be a string"
}).optional()
}).passthrough()
)
]);
const collectionConfigParser = z.union([
z.object({
type: z.literal("content").optional(),
schema: z.any().optional(),
loader: z.never().optional()
}),
z.object({
type: z.literal("data").optional(),
schema: z.any().optional(),
loader: z.never().optional()
}),
z.object({
type: z.literal(CONTENT_LAYER_TYPE),
schema: z.any().optional(),
loader: z.union([
z.function(),
z.object({
name: z.string(),
load: z.function({
input: [z.custom()],
output: z.custom()
}),
schema: z.any().transform((v) => {
if (typeof v === "function") {
console.warn(
`Your loader's schema is defined using a function. This is no longer supported and the schema will be ignored. Please update your loader to use the \`createSchema()\` utility instead, or report this to the loader author. In a future major version, this will cause the loader to break entirely.`
);
return void 0;
}
return v;
}).superRefine((v, ctx) => {
if (v !== void 0 && !("_zod" in v)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Invalid Zod schema"
});
return z.NEVER;
}
}).optional(),
createSchema: z.function({
input: [],
output: z.promise(
z.object({
schema: z.custom((v) => "_zod" in v),
types: z.string()
})
)
}).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({
collections: z.record(z.string(), collectionConfigParser)
});
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 = entry.unvalidatedData;
let schema = collectionConfig.schema;
if (typeof schema === "function") {
if (pluginContext) {
schema = schema({
image: createImage(pluginContext, shouldEmitFile, entry._internal.filePath)
});
} else if (collectionConfig.type === CONTENT_LAYER_TYPE) {
schema = schema({
image: () => z.string().transform((val) => {
let normalizedPath = val;
const isUrl = val.includes("://");
const isAbsolute = val.startsWith("/");
const isRelative = val.startsWith(".");
if (val && !isUrl && !isAbsolute && !isRelative) {
const entryDir = path.dirname(entry._internal.filePath);
const resolvedPath = path.resolve(entryDir, val);
if (fsMod.existsSync(resolvedPath)) {
normalizedPath = `./${val}`;
}
}
return `${IMAGE_IMPORT_PREFIX}${normalizedPath}`;
})
});
}
}
if (schema) {
let formattedError;
const parsed = await schema.safeParseAsync(data, {
error(issue) {
if (issue.code === "custom" && issue.params?.isHoistedAstroError) {
formattedError = issue.params?.astroError;
}
return errorMap(issue);
}
});
if (parsed.success) {
data = parsed.data;
} else {
if (!formattedError) {
formattedError = new AstroError({
...AstroErrorData.InvalidContentEntryDataError,
message: AstroErrorData.InvalidContentEntryDataError.message(
entry.collection,
entry.id,
parsed.error
),
location: {
file: entry._internal?.filePath,
line: getYAMLErrorLine(
entry._internal?.rawData,
String(parsed.error.issues[0].path[0])
),
column: 0
}
});
}
throw formattedError;
}
}
return data;
}
function getContentEntryExts(settings) {
return settings.contentEntryTypes.flatMap((t) => t.extensions);
}
function getDataEntryExts(settings) {
return settings.dataEntryTypes.flatMap((t) => t.extensions);
}
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;
}
async function getSymlinkedContentCollections({
contentDir,
logger,
fs
}) {
const contentPaths = /* @__PURE__ */ new Map();
const contentDirPath = fileURLToPath(contentDir);
try {
if (!fs.existsSync(contentDirPath) || !fs.lstatSync(contentDirPath).isDirectory()) {
return contentPaths;
}
} catch {
return contentPaths;
}
try {
const contentDirEntries = await fs.promises.readdir(contentDir, { withFileTypes: true });
for (const entry of contentDirEntries) {
if (entry.isSymbolicLink()) {
const entryPath = path.join(contentDirPath, entry.name);
const realPath = await fs.promises.realpath(entryPath);
contentPaths.set(normalizePath(realPath), entry.name);
}
}
} catch (e) {
logger.warn("content", `Error when reading content directory "${contentDir}"`);
logger.debug("content", e);
return /* @__PURE__ */ new Map();
}
return contentPaths;
}
function reverseSymlink({
entry,
symlinks,
contentDir
}) {
const entryPath = normalizePath(typeof entry === "string" ? entry : fileURLToPath(entry));
const contentDirPath = typeof contentDir === "string" ? contentDir : fileURLToPath(contentDir);
if (!symlinks || symlinks.size === 0) {
return entryPath;
}
for (const [realPath, symlinkName] of symlinks) {
if (entryPath.startsWith(realPath)) {
return normalizePath(path.join(contentDirPath, symlinkName, entryPath.replace(realPath, "")));
}
}
return entryPath;
}
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 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);
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 {
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 parseFrontmatter(source, { frontmatter: "empty-with-spaces" });
} 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 hasContentFlag(viteId, flag) {
const flags = new URLSearchParams(viteId.split("?")[1] ?? "");
return flags.has(flag);
}
function isDeferredModule(viteId) {
const flags = new URLSearchParams(viteId.split("?")[1] ?? "");
return flags.has(CONTENT_MODULE_FLAG);
}
async function loadContentConfig({
fs,
settings,
environment
}) {
const contentPaths = getContentPaths(
settings.config,
fs,
settings.config.legacy?.collectionsBackwardsCompat
);
if (!contentPaths.config.exists) {
return void 0;
}
const configPathname = fileURLToPath(contentPaths.config.url);
const unparsedConfig = await environment.runner.import(configPathname);
const config = contentConfigParser.safeParse(unparsedConfig);
if (config.success) {
const hasher = await xxhash();
const digest = hasher.h64ToString(await fs.promises.readFile(configPathname, "utf-8"));
return { ...config.data, digest };
} else {
const message = config.error.issues.map((issue) => ` \u2192 ${colors.green(issue.path.join("."))}: ${colors.red(issue.message)}`).join("\n");
console.error(
`${colors.green("[content]")} There was a problem with your content config:
${message}
`
);
const liveCollections = Object.entries(unparsedConfig.collections ?? {}).filter(
([, collection]) => collection?.type === LIVE_CONTENT_TYPE
);
if (liveCollections.length > 0) {
throw new AstroError({
...AstroErrorData.LiveContentConfigError,
message: AstroErrorData.LiveContentConfigError.message(
"Live collections must be defined in a `src/live.config.ts` file.",
path.relative(fileURLToPath(settings.config.root), configPathname)
)
});
}
return void 0;
}
}
async function autogenerateCollections({
config,
settings,
fs
}) {
if (!config) {
return config;
}
if (!settings.config.legacy?.collectionsBackwardsCompat) {
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)) {
const collection = collections[collectionName];
if (collection?.type === CONTENT_LAYER_TYPE || collection?.type === LIVE_CONTENT_TYPE) {
usesContentLayer = true;
continue;
}
const isDataCollection = collection?.type === "data";
const base = new URL(`${collectionName}/`, contentDir);
collections[collectionName] = {
...collection,
type: CONTENT_LAYER_TYPE,
loader: glob({
base,
pattern: isDataCollection ? dataPattern : contentPattern,
[secretLegacyFlag]: true
})
};
}
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_TYPE,
loader: glob({
base,
pattern: contentPattern,
[secretLegacyFlag]: 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) => colors.green(name)).join(", ")}
`
);
}
}
return { ...config, collections };
}
async function reloadContentConfigObserver({
observer = globalContentConfigObserver,
...loadContentConfigOpts
}) {
observer.set({ status: "loading" });
try {
let config = await loadContentConfig(loadContentConfigOpts);
config = await autogenerateCollections({
config,
...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, legacyCollectionsBackwardsCompat = false) {
const pkgBase = new URL("../../", import.meta.url);
const configStats = searchConfig(fs, srcDir);
if (!configStats.exists) {
const legacyConfigStats = searchLegacyConfig(fs, srcDir);
if (legacyConfigStats.exists) {
if (!legacyCollectionsBackwardsCompat) {
const relativePath = path.relative(
fileURLToPath(root),
fileURLToPath(legacyConfigStats.url)
);
throw new AstroError({
...AstroErrorData.LegacyContentConfigError,
message: AstroErrorData.LegacyContentConfigError.message(relativePath)
});
}
return getContentPathsWithConfig(root, srcDir, pkgBase, legacyConfigStats, fs);
}
}
const liveConfigStats = searchLiveConfig(fs, srcDir);
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,
liveConfig: liveConfigStats
};
}
function getContentPathsWithConfig(root, srcDir, pkgBase, configStats, fs) {
const liveConfigStats = searchLiveConfig(fs, srcDir);
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,
liveConfig: liveConfigStats
};
}
function searchConfig(fs, srcDir) {
const paths = [
"content.config.mjs",
"content.config.js",
"content.config.mts",
"content.config.ts"
];
return search(fs, srcDir, paths);
}
function searchLegacyConfig(fs, srcDir) {
const paths = [
"content/config.ts",
"content/config.js",
"content/config.mjs",
"content/config.mts"
];
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: urls[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 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("/");
}
function posixRelative(from, to) {
return posixifyPath(path.relative(from, to));
}
function contentModuleToId(fileName) {
const params = new URLSearchParams(DEFERRED_MODULE);
params.set("fileName", 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,
getContentEntryExts,
getContentEntryIdAndSlug,
getContentPaths,
getDataEntryExts,
getDataEntryId,
getEntryCollectionName,
getEntryConfigByExtMap,
getEntryData,
getEntrySlug,
getEntryType,
getSymlinkedContentCollections,
globalContentConfigObserver,
hasContentFlag,
isDeferredModule,
loaderReturnSchema,
parseEntrySlug,
posixRelative,
reloadContentConfigObserver,
reverseSymlink,
safeParseFrontmatter,
safeStringify
};

View File

@@ -0,0 +1,17 @@
import type { AssetsPrefix } from '../core/app/types.js';
import { type Plugin } from 'vite';
import type { BuildInternals } from '../core/build/internal.js';
import type { ExtractedChunk } from '../core/build/static-build.js';
import type { AstroSettings } from '../types/astro.js';
export declare function astroContentAssetPropagationPlugin({ settings, }: {
settings: AstroSettings;
}): Plugin;
/**
* Post-build hook that injects propagated styles into content collection chunks.
* Finds chunks with LINKS_PLACEHOLDER and STYLES_PLACEHOLDER, and replaces them
* with actual styles from propagatedStylesMap.
*/
export declare function contentAssetsBuildPostHook(base: string, assetsPrefix: AssetsPrefix | undefined, internals: BuildInternals, { chunks, mutate, }: {
chunks: ExtractedChunk[];
mutate: (fileName: string, code: string, prerender: boolean) => void;
}): Promise<void>;

View File

@@ -0,0 +1,205 @@
import { extname } from "node:path";
import { getAssetsPrefix } from "../assets/utils/getAssetsPrefix.js";
import { fileExtension } from "../core/path.js";
import { fileURLToPath } from "node:url";
import { isRunnableDevEnvironment } from "vite";
import { AstroError, AstroErrorData } from "../core/errors/index.js";
import { wrapId } from "../core/util.js";
import { isBuildableCSSRequest } from "../vite-plugin-astro-server/util.js";
import { crawlGraph } from "../vite-plugin-astro-server/vite.js";
import {
CONTENT_IMAGE_FLAG,
CONTENT_RENDER_FLAG,
LINKS_PLACEHOLDER,
PROPAGATED_ASSET_FLAG,
STYLES_PLACEHOLDER
} from "./consts.js";
import { hasContentFlag } from "./utils.js";
import { joinPaths, prependForwardSlash, slash } from "@astrojs/internal-helpers/path";
import { ASTRO_VITE_ENVIRONMENT_NAMES } from "../core/constants.js";
import { isAstroServerEnvironment } from "../environments.js";
function astroContentAssetPropagationPlugin({
settings
}) {
let environment = void 0;
return {
name: "astro:content-asset-propagation",
enforce: "pre",
resolveId: {
filter: {
id: new RegExp(`(?:\\?|&)(?:${CONTENT_IMAGE_FLAG}|${CONTENT_RENDER_FLAG})(?:&|=|$)`)
},
async handler(id, importer, opts) {
if (hasContentFlag(id, CONTENT_IMAGE_FLAG)) {
const [base, query] = id.split("?");
const params = new URLSearchParams(query);
const importerParam = params.get("importer");
const importerPath = importerParam ? fileURLToPath(new URL(importerParam, settings.config.root)) : importer;
const resolved = await this.resolve(base, importerPath, { skipSelf: true, ...opts });
if (!resolved) {
throw new AstroError({
...AstroErrorData.ImageNotFound,
message: AstroErrorData.ImageNotFound.message(base)
});
}
resolved.id = `${resolved.id}?${CONTENT_IMAGE_FLAG}`;
return resolved;
}
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) {
const ssrEnv = server.environments[ASTRO_VITE_ENVIRONMENT_NAMES.ssr];
if (isRunnableDevEnvironment(ssrEnv)) {
environment = ssrEnv;
} else if (isRunnableDevEnvironment(server.environments[ASTRO_VITE_ENVIRONMENT_NAMES.astro])) {
environment = server.environments[ASTRO_VITE_ENVIRONMENT_NAMES.astro];
}
},
transform: {
filter: {
id: new RegExp(`(?:\\?|&)${PROPAGATED_ASSET_FLAG}(?:&|=|$)`)
},
async handler(_, id) {
if (hasContentFlag(id, PROPAGATED_ASSET_FLAG)) {
const basePath = id.split("?")[0];
let stringifiedLinks, stringifiedStyles;
if (isAstroServerEnvironment(this.environment) && environment) {
if (!environment.moduleGraph.getModuleById(basePath)?.ssrModule) {
await environment.runner.import(basePath).catch(() => {
});
}
const {
styles,
urls,
crawledFiles: styleCrawledFiles
} = await getStylesForURL(basePath, environment);
for (const file of styleCrawledFiles) {
if (!file.includes("node_modules")) {
this.addWatchFile(file);
}
}
stringifiedLinks = JSON.stringify([...urls]);
stringifiedStyles = JSON.stringify(styles.map((s) => s.content));
} else {
stringifiedLinks = JSON.stringify(LINKS_PLACEHOLDER);
stringifiedStyles = JSON.stringify(STYLES_PLACEHOLDER);
}
const code = `
async function getMod() {
return import(${JSON.stringify(basePath)});
}
const collectedLinks = ${stringifiedLinks};
const collectedStyles = ${stringifiedStyles};
const defaultMod = { __astroPropagation: true, getMod, collectedLinks, collectedStyles, collectedScripts: [] };
export default defaultMod;
`;
return { code, map: { mappings: "" } };
}
}
}
};
}
const INLINE_QUERY_REGEX = /(?:\?|&)inline(?:$|&)/;
async function getStylesForURL(filePath, environment) {
const importedCssUrls = /* @__PURE__ */ new Set();
const importedStylesMap = /* @__PURE__ */ new Map();
const crawledFiles = /* @__PURE__ */ new Set();
for await (const importedModule of crawlGraph(environment, filePath, false)) {
if (importedModule.file) {
crawledFiles.add(importedModule.file);
}
if (isBuildableCSSRequest(importedModule.url)) {
let css = "";
if (typeof importedModule.ssrModule?.default === "string") {
css = importedModule.ssrModule.default;
} else {
let modId = importedModule.url;
if (!INLINE_QUERY_REGEX.test(importedModule.url)) {
if (importedModule.url.includes("?")) {
modId = importedModule.url.replace("?", "?inline&");
} else {
modId += "?inline";
}
}
try {
const ssrModule = await environment.runner.import(modId);
css = ssrModule.default;
} catch {
continue;
}
}
importedStylesMap.set(importedModule.url, {
id: wrapId(importedModule.id ?? importedModule.url),
url: wrapId(importedModule.url),
content: css
});
}
}
return {
urls: importedCssUrls,
styles: [...importedStylesMap.values()],
crawledFiles
};
}
async function contentAssetsBuildPostHook(base, assetsPrefix, internals, {
chunks,
mutate
}) {
for (const chunk of chunks) {
if (!chunk.code.includes(LINKS_PLACEHOLDER)) continue;
const entryStyles = /* @__PURE__ */ new Set();
const entryLinks = /* @__PURE__ */ new Set();
for (const id of chunk.moduleIds) {
const entryCss = internals.propagatedStylesMap.get(id);
if (entryCss) {
for (const value of entryCss) {
if (value.type === "inline") entryStyles.add(value.content);
if (value.type === "external") {
let href;
if (assetsPrefix) {
const pf = getAssetsPrefix(fileExtension(value.src), assetsPrefix);
href = joinPaths(pf, slash(value.src));
} else {
href = prependForwardSlash(joinPaths(base, slash(value.src)));
}
entryLinks.add(href);
}
}
}
}
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))
);
} else {
newCode = newCode.replace(JSON.stringify(LINKS_PLACEHOLDER), "[]");
}
mutate(chunk.fileName, newCode, chunk.prerender);
}
}
export {
astroContentAssetPropagationPlugin,
contentAssetsBuildPostHook
};

View File

@@ -0,0 +1,9 @@
import type fsMod from 'node:fs';
import type { Plugin } from 'vite';
import type { AstroLogger } from '../core/logger/core.js';
import type { AstroSettings } from '../types/astro.js';
export declare function astroContentImportPlugin({ fs, settings, logger, }: {
fs: typeof fsMod;
settings: AstroSettings;
logger: AstroLogger;
}): Plugin[];

View File

@@ -0,0 +1,328 @@
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 { CONTENT_FLAG, DATA_FLAG } from "./consts.js";
import {
getContentEntryExts,
getContentEntryIdAndSlug,
getContentPaths,
getDataEntryExts,
getDataEntryId,
getEntryCollectionName,
getEntryConfigByExtMap,
getEntryData,
getEntryType,
getSymlinkedContentCollections,
globalContentConfigObserver,
hasContentFlag,
parseEntrySlug,
reloadContentConfigObserver,
reverseSymlink
} from "./utils.js";
import { ASTRO_VITE_ENVIRONMENT_NAMES } from "../core/constants.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,
logger
}) {
const contentPaths = getContentPaths(
settings.config,
fs,
settings.config.legacy?.collectionsBackwardsCompat
);
const contentEntryExts = getContentEntryExts(settings);
const dataEntryExts = getDataEntryExts(settings);
const contentEntryConfigByExt = getEntryConfigByExtMap(settings.contentEntryTypes);
const dataEntryConfigByExt = getEntryConfigByExtMap(settings.dataEntryTypes);
const { contentDir } = contentPaths;
let shouldEmitFile = false;
let symlinks;
const plugins = [
{
name: "astro:content-imports",
config(_config, env) {
shouldEmitFile = env.command === "build";
},
async buildStart() {
symlinks = await getSymlinkedContentCollections({ contentDir, logger, fs });
},
transform: {
filter: {
id: new RegExp(`(?:\\?|&)(?:${DATA_FLAG}|${CONTENT_FLAG})(?:&|=|$)`)
},
async handler(_, viteId) {
if (hasContentFlag(viteId, DATA_FLAG)) {
const fileId = reverseSymlink({
entry: viteId.split("?")[0] ?? viteId,
contentDir,
symlinks
});
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, settings.buildOutput === "server")};
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 = reverseSymlink({ entry: viteId.split("?")[0], contentDir, symlinks });
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, settings.buildOutput === "server")};
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 environment = viteServer.environments[ASTRO_VITE_ENVIRONMENT_NAMES.ssr];
const timestamp = Date.now();
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,
environment: viteServer.environments[ASTRO_VITE_ENVIRONMENT_NAMES.astro]
});
}
for (const modUrl of environment.moduleGraph.urlToModuleMap.keys()) {
if (hasContentFlag(modUrl, CONTENT_FLAG) || hasContentFlag(modUrl, DATA_FLAG) || Boolean(getContentRendererByViteId(modUrl, settings))) {
try {
const mod = await environment.moduleGraph.getModuleByUrl(modUrl);
if (mod) {
environment.moduleGraph.invalidateModule(mod, void 0, timestamp, true);
}
} 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",
transform: {
filter: {
id: {
include: settings.contentEntryTypes.filter((t) => t.getRenderModule).map((t) => new RegExp(`\\.(${t.extensions.map((e) => e.slice(1)).join("|")})$`))
}
},
async handler(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,9 @@
import nodeFs from 'node:fs';
import { type Plugin } from 'vite';
import type { AstroSettings } from '../types/astro.js';
interface AstroContentVirtualModPluginParams {
settings: AstroSettings;
fs: typeof nodeFs;
}
export declare function astroContentVirtualModPlugin({ settings, fs, }: AstroContentVirtualModPluginParams): Plugin;
export {};

View File

@@ -0,0 +1,212 @@
import nodeFs from "node:fs";
import { fileURLToPath } from "node:url";
import { dataToEsm } from "@rollup/pluginutils";
import { normalizePath } from "vite";
import { ASTRO_VITE_ENVIRONMENT_NAMES } from "../core/constants.js";
import { AstroError, AstroErrorData } from "../core/errors/index.js";
import { rootRelativePath } from "../core/viteUtils.js";
import { isAstroClientEnvironment } from "../environments.js";
import { createDefaultAstroMetadata } from "../vite-plugin-astro/metadata.js";
import {
ASSET_IMPORTS_FILE,
ASSET_IMPORTS_RESOLVED_STUB_ID,
ASSET_IMPORTS_VIRTUAL_ID,
CONTENT_MODULE_FLAG,
CONTENT_RENDER_FLAG,
DATA_STORE_VIRTUAL_ID,
MODULES_IMPORTS_FILE,
MODULES_MJS_ID,
MODULES_MJS_VIRTUAL_ID,
RESOLVED_DATA_STORE_VIRTUAL_ID,
RESOLVED_VIRTUAL_MODULE_ID,
VIRTUAL_MODULE_ID
} from "./consts.js";
import { getDataStoreFile } from "./content-layer.js";
import { getContentPaths, isDeferredModule } from "./utils.js";
function invalidateDataStore(viteServer) {
const environment = viteServer.environments[ASTRO_VITE_ENVIRONMENT_NAMES.ssr];
const module = environment.moduleGraph.getModuleById(RESOLVED_DATA_STORE_VIRTUAL_ID);
if (module) {
const timestamp = Date.now();
environment.moduleGraph.invalidateModule(module, void 0, timestamp, true);
}
environment.hot.send("astro:content-changed", {});
viteServer.environments.client.hot.send({
type: "full-reload",
path: "*"
});
}
function astroContentVirtualModPlugin({
settings,
fs
}) {
let dataStoreFile;
let devServer;
let liveConfig;
return {
name: "astro-content-virtual-mod-plugin",
enforce: "pre",
config(_, env) {
dataStoreFile = getDataStoreFile(settings, env.command === "serve");
const contentPaths = getContentPaths(
settings.config,
void 0,
settings.config.legacy?.collectionsBackwardsCompat
);
if (contentPaths.liveConfig.exists) {
liveConfig = normalizePath(fileURLToPath(contentPaths.liveConfig.url));
}
},
buildStart() {
if (devServer) {
devServer.watcher.add(fileURLToPath(dataStoreFile));
invalidateDataStore(devServer);
}
},
resolveId: {
filter: {
id: new RegExp(
`^(${VIRTUAL_MODULE_ID}|${DATA_STORE_VIRTUAL_ID}|${MODULES_MJS_ID}|${ASSET_IMPORTS_VIRTUAL_ID})$|(?:\\?|&)${CONTENT_MODULE_FLAG}(?:&|=|$)`
)
},
async handler(id, importer) {
if (id === VIRTUAL_MODULE_ID) {
if (liveConfig && importer && liveConfig === normalizePath(importer)) {
return this.resolve("astro/virtual-modules/live-config", importer, {
skipSelf: true
});
}
return RESOLVED_VIRTUAL_MODULE_ID;
}
if (id === DATA_STORE_VIRTUAL_ID) {
return RESOLVED_DATA_STORE_VIRTUAL_ID;
}
if (isDeferredModule(id)) {
const [, query] = id.split("?");
const params = new URLSearchParams(query);
const fileName = params.get("fileName");
let importPath = void 0;
if (fileName && URL.canParse(fileName, settings.config.root.toString())) {
importPath = fileURLToPath(new URL(fileName, settings.config.root));
}
if (importPath) {
return await this.resolve(`${importPath}?${CONTENT_RENDER_FLAG}`);
}
}
if (id === MODULES_MJS_ID) {
const modules = new URL(MODULES_IMPORTS_FILE, settings.dotAstroDir);
if (fs.existsSync(modules)) {
return fileURLToPath(modules);
}
return MODULES_MJS_VIRTUAL_ID;
}
if (id === ASSET_IMPORTS_VIRTUAL_ID) {
const assetImportsFile = new URL(ASSET_IMPORTS_FILE, settings.dotAstroDir);
if (fs.existsSync(assetImportsFile)) {
return fileURLToPath(assetImportsFile);
}
return ASSET_IMPORTS_RESOLVED_STUB_ID;
}
}
},
load: {
filter: {
id: new RegExp(
`^(${RESOLVED_VIRTUAL_MODULE_ID}|${RESOLVED_DATA_STORE_VIRTUAL_ID}|${ASSET_IMPORTS_RESOLVED_STUB_ID}|${MODULES_MJS_VIRTUAL_ID})$`
)
},
async handler(id) {
if (id === RESOLVED_VIRTUAL_MODULE_ID) {
const isClient = isAstroClientEnvironment(this.environment);
const code = await generateContentEntryFile({
settings,
fs,
isClient
});
const astro = createDefaultAstroMetadata();
astro.propagation = "in-tree";
return {
code,
meta: {
astro
}
};
}
if (id === RESOLVED_DATA_STORE_VIRTUAL_ID) {
if (!fs.existsSync(dataStoreFile)) {
return { code: "export default new Map()" };
}
const jsonData = await fs.promises.readFile(dataStoreFile, "utf-8");
try {
const parsed = JSON.parse(jsonData);
return {
code: dataToEsm(parsed, {
compact: true
}),
map: { mappings: "" }
};
} catch (err) {
const message = "Could not parse JSON file";
this.error({ message, id, cause: err });
}
}
if (id === ASSET_IMPORTS_RESOLVED_STUB_ID) {
const assetImportsFile = new URL(ASSET_IMPORTS_FILE, settings.dotAstroDir);
return {
code: fs.existsSync(assetImportsFile) ? fs.readFileSync(assetImportsFile, "utf-8") : "export default new Map()"
};
}
if (id === MODULES_MJS_VIRTUAL_ID) {
const modules = new URL(MODULES_IMPORTS_FILE, settings.dotAstroDir);
return {
code: fs.existsSync(modules) ? fs.readFileSync(modules, "utf-8") : "export default new Map()"
};
}
}
},
configureServer(server) {
devServer = server;
const dataStorePath = fileURLToPath(dataStoreFile);
server.watcher.on("add", (addedPath) => {
if (addedPath === dataStorePath) {
invalidateDataStore(server);
}
});
server.watcher.on("change", (changedPath) => {
if (changedPath === dataStorePath) {
invalidateDataStore(server);
}
});
}
};
}
async function generateContentEntryFile({
settings,
isClient
}) {
const contentPaths = getContentPaths(
settings.config,
void 0,
settings.config.legacy?.collectionsBackwardsCompat
);
const relContentDir = rootRelativePath(settings.config.root, contentPaths.contentDir);
let virtualModContents;
if (isClient) {
throw new AstroError({
...AstroErrorData.ServerOnlyModule,
message: AstroErrorData.ServerOnlyModule.message("astro:content")
});
} else {
virtualModContents = nodeFs.readFileSync(contentPaths.virtualModTemplate, "utf-8").replace("@@CONTENT_DIR@@", relContentDir).replace(
"/* @@LIVE_CONTENT_CONFIG@@ */",
contentPaths.liveConfig.exists ? (
// Dynamic import so it extracts the chunk and avoids a circular import
`const liveCollections = (await import(${JSON.stringify(fileURLToPath(contentPaths.liveConfig.url))})).collections;`
) : "const liveCollections = {};"
);
}
return virtualModContents;
}
export {
astroContentVirtualModPlugin
};

5
node_modules/astro/dist/content/watcher.d.ts generated vendored Normal file
View File

@@ -0,0 +1,5 @@
import type { FSWatcher } from 'vite';
export type WrappedWatcher = FSWatcher & {
removeAllTrackedListeners(): void;
};
export declare function createWatcherWrapper(watcher: FSWatcher): WrappedWatcher;

38
node_modules/astro/dist/content/watcher.js generated vendored Normal file
View File

@@ -0,0 +1,38 @@
function createWatcherWrapper(watcher) {
const listeners = /* @__PURE__ */ new Map();
const handler = {
get(target, prop, receiver) {
if (prop === "on") {
return function(event, callback) {
if (!listeners.has(event)) {
listeners.set(event, /* @__PURE__ */ new Set());
}
listeners.get(event).add(callback);
return Reflect.get(target, prop, receiver).call(target, event, callback);
};
}
if (prop === "off") {
return function(event, callback) {
listeners.get(event)?.delete(callback);
return Reflect.get(target, prop, receiver).call(target, event, callback);
};
}
if (prop === "removeAllTrackedListeners") {
return function() {
for (const [event, callbacks] of listeners.entries()) {
for (const callback of callbacks) {
target.off(event, callback);
}
callbacks.clear();
}
listeners.clear();
};
}
return Reflect.get(target, prop, receiver);
}
};
return new Proxy(watcher, handler);
}
export {
createWatcherWrapper
};