Initial commit
This commit is contained in:
74
node_modules/astro/dist/content/config.d.ts
generated
vendored
Normal file
74
node_modules/astro/dist/content/config.d.ts
generated
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
import type { ZodLiteral, ZodNumber, ZodObject, ZodString, ZodType, ZodUnion } from 'zod';
|
||||
import type { LiveLoader, Loader } from './loaders/types.js';
|
||||
export type ImageFunction = () => ZodObject<{
|
||||
src: ZodString;
|
||||
width: ZodNumber;
|
||||
height: ZodNumber;
|
||||
format: ZodUnion<[
|
||||
ZodLiteral<'png'>,
|
||||
ZodLiteral<'jpg'>,
|
||||
ZodLiteral<'jpeg'>,
|
||||
ZodLiteral<'tiff'>,
|
||||
ZodLiteral<'webp'>,
|
||||
ZodLiteral<'gif'>,
|
||||
ZodLiteral<'svg'>,
|
||||
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 = ZodType;
|
||||
export type SchemaContext = {
|
||||
image: ImageFunction;
|
||||
};
|
||||
type ContentLayerConfig<S extends BaseSchema, TData extends {
|
||||
id: string;
|
||||
} = {
|
||||
id: string;
|
||||
}> = {
|
||||
type?: 'content_layer';
|
||||
schema?: S | ((context: SchemaContext) => S);
|
||||
loader: Loader | (() => Array<TData> | Promise<Array<TData>> | Record<string, Omit<TData, 'id'> & {
|
||||
id?: string;
|
||||
}> | Promise<Record<string, Omit<TData, 'id'> & {
|
||||
id?: string;
|
||||
}>>);
|
||||
};
|
||||
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> = ContentCollectionConfig<S> | DataCollectionConfig<S> | ContentLayerConfig<S>;
|
||||
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>(config: CollectionConfig<S>): CollectionConfig<S>;
|
||||
export {};
|
||||
93
node_modules/astro/dist/content/config.js
generated
vendored
Normal file
93
node_modules/astro/dist/content/config.js
generated
vendored
Normal 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(
|
||||
`Collections that use the Content Layer API must have a \`loader\` defined and no \`type\` set. Check your collection definitions in ${importerFilename ?? "your content config file"}.`
|
||||
);
|
||||
}
|
||||
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 your collection definitions in "${importerFilename ?? "your content config file"}" to ensure you are not using a live loader.`
|
||||
);
|
||||
}
|
||||
config.type = CONTENT_LAYER_TYPE;
|
||||
}
|
||||
if (!config.type) config.type = "content";
|
||||
return config;
|
||||
}
|
||||
export {
|
||||
defineCollection,
|
||||
defineLiveCollection
|
||||
};
|
||||
27
node_modules/astro/dist/content/consts.d.ts
generated
vendored
Normal file
27
node_modules/astro/dist/content/consts.d.ts
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
export declare const PROPAGATED_ASSET_FLAG = "astroPropagatedAssets";
|
||||
export declare const CONTENT_RENDER_FLAG = "astroRenderContent";
|
||||
export declare const CONTENT_FLAG = "astroContentCollectionEntry";
|
||||
export declare const DATA_FLAG = "astroDataCollectionEntry";
|
||||
export declare const 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";
|
||||
63
node_modules/astro/dist/content/consts.js
generated
vendored
Normal file
63
node_modules/astro/dist/content/consts.js
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
const PROPAGATED_ASSET_FLAG = "astroPropagatedAssets";
|
||||
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,
|
||||
RESOLVED_DATA_STORE_VIRTUAL_ID,
|
||||
RESOLVED_VIRTUAL_MODULE_ID,
|
||||
STYLES_PLACEHOLDER,
|
||||
VIRTUAL_MODULE_ID
|
||||
};
|
||||
45
node_modules/astro/dist/content/content-layer.d.ts
generated
vendored
Normal file
45
node_modules/astro/dist/content/content-layer.d.ts
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
import type { FSWatcher } from 'vite';
|
||||
import type { Logger } 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';
|
||||
interface ContentLayerOptions {
|
||||
store: MutableDataStore;
|
||||
settings: AstroSettings;
|
||||
logger: Logger;
|
||||
watcher?: FSWatcher;
|
||||
}
|
||||
declare class ContentLayer {
|
||||
#private;
|
||||
constructor({ settings, logger, store, watcher }: 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;
|
||||
export declare const globalContentLayer: {
|
||||
init: (options: ContentLayerOptions) => ContentLayer;
|
||||
get: () => ContentLayer | null;
|
||||
dispose: () => void;
|
||||
};
|
||||
export {};
|
||||
359
node_modules/astro/dist/content/content-layer.js
generated
vendored
Normal file
359
node_modules/astro/dist/content/content-layer.js
generated
vendored
Normal file
@@ -0,0 +1,359 @@
|
||||
import { existsSync, promises as fs } from "node:fs";
|
||||
import { createMarkdownProcessor } 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,
|
||||
getEntryDataAndImages,
|
||||
globalContentConfigObserver,
|
||||
loaderReturnSchema,
|
||||
safeStringify
|
||||
} from "./utils.js";
|
||||
import { createWatcherWrapper } from "./watcher.js";
|
||||
class ContentLayer {
|
||||
#logger;
|
||||
#store;
|
||||
#settings;
|
||||
#watcher;
|
||||
#lastConfigDigest;
|
||||
#unsubscribe;
|
||||
#markdownProcessor;
|
||||
#generateDigest;
|
||||
#queue;
|
||||
constructor({ settings, logger, store, watcher }) {
|
||||
watcher?.setMaxListeners(50);
|
||||
this.#logger = logger;
|
||||
this.#store = store;
|
||||
this.#settings = settings;
|
||||
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 = globalContentConfigObserver.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) {
|
||||
this.#markdownProcessor ??= await createMarkdownProcessor(this.#settings.config.markdown);
|
||||
const { code, metadata } = await this.#markdownProcessor.render(content);
|
||||
return {
|
||||
html: code,
|
||||
metadata
|
||||
};
|
||||
}
|
||||
/**
|
||||
* 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 = globalContentConfigObserver.get();
|
||||
const logger = this.#logger.forkIntegrationLogger("content");
|
||||
if (contentConfig?.status === "loading") {
|
||||
contentConfig = await Promise.race([
|
||||
new Promise((resolve) => {
|
||||
const unsub = globalContentConfigObserver.subscribe((ctx) => {
|
||||
unsub();
|
||||
resolve(ctx);
|
||||
});
|
||||
}),
|
||||
new Promise(
|
||||
(resolve) => setTimeout(
|
||||
() => resolve({ status: "error", error: new Error("Content config loading timed out") }),
|
||||
5e3
|
||||
)
|
||||
)
|
||||
]);
|
||||
}
|
||||
if (contentConfig?.status === "error") {
|
||||
logger.error(`Error loading content config. Skipping sync.
|
||||
${contentConfig.error.message}`);
|
||||
return;
|
||||
}
|
||||
if (contentConfig?.status !== "loaded") {
|
||||
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 = await this.#store.metaStore().get("content-config-digest");
|
||||
const previousAstroConfigDigest = await this.#store.metaStore().get("astro-config-digest");
|
||||
const previousAstroVersion = await 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 !== "5.17.1") {
|
||||
logger.info("Astro version changed");
|
||||
shouldClear = true;
|
||||
}
|
||||
if (shouldClear) {
|
||||
logger.info("Clearing content store");
|
||||
this.#store.clearAll();
|
||||
}
|
||||
if ("5.17.1") {
|
||||
await this.#store.metaStore().set("astro-version", "5.17.1");
|
||||
}
|
||||
if (currentConfigDigest) {
|
||||
await this.#store.metaStore().set("content-config-digest", currentConfigDigest);
|
||||
}
|
||||
if (astroConfigDigest) {
|
||||
await this.#store.metaStore().set("astro-config-digest", astroConfigDigest);
|
||||
}
|
||||
if (!options?.loaders?.length) {
|
||||
this.#watcher?.removeAllTrackedListeners();
|
||||
}
|
||||
await Promise.all(
|
||||
Object.entries(contentConfig.config.collections).map(async ([name, collection]) => {
|
||||
if (collection.type !== CONTENT_LAYER_TYPE) {
|
||||
return;
|
||||
}
|
||||
let { schema } = collection;
|
||||
if (!schema && typeof collection.loader === "object") {
|
||||
schema = collection.loader.schema;
|
||||
if (typeof schema === "function") {
|
||||
schema = await schema();
|
||||
}
|
||||
}
|
||||
if (options?.loaders && (typeof collection.loader !== "object" || !options.loaders.includes(collection.loader.name))) {
|
||||
return;
|
||||
}
|
||||
const collectionWithResolvedSchema = { ...collection, schema };
|
||||
const parseData = async ({ id, data, filePath = "" }) => {
|
||||
const { data: parsedData } = await getEntryDataAndImages(
|
||||
{
|
||||
id,
|
||||
collection: name,
|
||||
unvalidatedData: data,
|
||||
_internal: {
|
||||
rawData: void 0,
|
||||
filePath
|
||||
}
|
||||
},
|
||||
collectionWithResolvedSchema,
|
||||
false,
|
||||
// FUTURE: Remove in this in v6
|
||||
id.endsWith(".svg")
|
||||
);
|
||||
return parsedData;
|
||||
};
|
||||
const context = await this.#getLoaderContext({
|
||||
collectionName: name,
|
||||
parseData,
|
||||
loaderName: collection.loader.name,
|
||||
refreshContextData: options?.context
|
||||
});
|
||||
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.unionErrors[0] : issue.unionErrors[1];
|
||||
const error = parseIssue.errors[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);
|
||||
}
|
||||
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 {
|
||||
getDataStoreFile,
|
||||
globalContentLayer
|
||||
};
|
||||
57
node_modules/astro/dist/content/data-store.d.ts
generated
vendored
Normal file
57
node_modules/astro/dist/content/data-store.d.ts
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
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 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>;
|
||||
/** @deprecated */
|
||||
legacyId?: 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
75
node_modules/astro/dist/content/data-store.js
generated
vendored
Normal 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
6
node_modules/astro/dist/content/index.d.ts
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
export { attachContentServerListeners } from './server-listeners.js';
|
||||
export { createContentTypesGenerator } from './types-generator.js';
|
||||
export { getContentPaths, hasAssetPropagationFlag } from './utils.js';
|
||||
export { astroContentAssetPropagationPlugin } from './vite-plugin-content-assets.js';
|
||||
export { astroContentImportPlugin } from './vite-plugin-content-imports.js';
|
||||
export { astroContentVirtualModPlugin } from './vite-plugin-content-virtual-mod.js';
|
||||
15
node_modules/astro/dist/content/index.js
generated
vendored
Normal file
15
node_modules/astro/dist/content/index.js
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
import { attachContentServerListeners } from "./server-listeners.js";
|
||||
import { createContentTypesGenerator } from "./types-generator.js";
|
||||
import { getContentPaths, hasAssetPropagationFlag } from "./utils.js";
|
||||
import { astroContentAssetPropagationPlugin } from "./vite-plugin-content-assets.js";
|
||||
import { astroContentImportPlugin } from "./vite-plugin-content-imports.js";
|
||||
import { astroContentVirtualModPlugin } from "./vite-plugin-content-virtual-mod.js";
|
||||
export {
|
||||
astroContentAssetPropagationPlugin,
|
||||
astroContentImportPlugin,
|
||||
astroContentVirtualModPlugin,
|
||||
attachContentServerListeners,
|
||||
createContentTypesGenerator,
|
||||
getContentPaths,
|
||||
hasAssetPropagationFlag
|
||||
};
|
||||
20
node_modules/astro/dist/content/loaders/errors.d.ts
generated
vendored
Normal file
20
node_modules/astro/dist/content/loaders/errors.d.ts
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
import type { ZodError } from 'zod';
|
||||
export declare class LiveCollectionError extends Error {
|
||||
readonly collection: string;
|
||||
readonly message: string;
|
||||
readonly cause?: Error | undefined;
|
||||
constructor(collection: string, message: string, cause?: Error | undefined);
|
||||
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: ZodError);
|
||||
static is(error: unknown): error is LiveCollectionValidationError;
|
||||
}
|
||||
export declare class LiveCollectionCacheHintError extends LiveCollectionError {
|
||||
constructor(collection: string, entryId: string | undefined, error: ZodError);
|
||||
static is(error: unknown): error is LiveCollectionCacheHintError;
|
||||
}
|
||||
67
node_modules/astro/dist/content/loaders/errors.js
generated
vendored
Normal file
67
node_modules/astro/dist/content/loaders/errors.js
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
class LiveCollectionError extends Error {
|
||||
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.
|
||||
`,
|
||||
...error.errors.map((zodError) => ` **${zodError.path.join(".")}**: ${zodError.message}`),
|
||||
""
|
||||
].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.
|
||||
`,
|
||||
...error.errors.map((zodError) => ` **${zodError.path.join(".")}**: ${zodError.message}`),
|
||||
""
|
||||
].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
16
node_modules/astro/dist/content/loaders/file.d.ts
generated
vendored
Normal 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
102
node_modules/astro/dist/content/loaders/file.js
generated
vendored
Normal 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
|
||||
};
|
||||
37
node_modules/astro/dist/content/loaders/glob.d.ts
generated
vendored
Normal file
37
node_modules/astro/dist/content/loaders/glob.d.ts
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
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;
|
||||
}
|
||||
/**
|
||||
* 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): Loader;
|
||||
/** @private */
|
||||
export declare function glob(globOptions: GlobOptions & {
|
||||
/** @deprecated */
|
||||
_legacy?: true;
|
||||
}): Loader;
|
||||
export {};
|
||||
267
node_modules/astro/dist/content/loaders/glob.js
generated
vendored
Normal file
267
node_modules/astro/dist/content/loaders/glob.js
generated
vendored
Normal file
@@ -0,0 +1,267 @@
|
||||
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 }) {
|
||||
if (data.slug) {
|
||||
return data.slug;
|
||||
}
|
||||
const entryURL = new URL(encodeURI(entry), base);
|
||||
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);
|
||||
}
|
||||
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 generateId = globOptions?.generateId ?? generateIdDefault;
|
||||
const fileToIdMap = /* @__PURE__ */ new Map();
|
||||
return {
|
||||
name: "glob-loader",
|
||||
load: async ({ config, logger, watcher, parseData, store, generateDigest, entryTypes }) => {
|
||||
const renderFunctionByContentType = /* @__PURE__ */ new WeakMap();
|
||||
const untouchedEntries = new Set(store.keys());
|
||||
const isLegacy = globOptions._legacy;
|
||||
const emulateLegacyCollections = !config.legacy.collections;
|
||||
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);
|
||||
}
|
||||
let legacyId;
|
||||
if (isLegacy) {
|
||||
const entryURL = new URL(encodeURI(entry), base);
|
||||
const legacyOptions = getContentEntryIdAndSlug({
|
||||
entry: entryURL,
|
||||
contentDir: base,
|
||||
collection: ""
|
||||
});
|
||||
legacyId = legacyOptions.id;
|
||||
}
|
||||
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 (entryType.getRenderFunction) {
|
||||
if (isLegacy && data.layout) {
|
||||
logger.error(
|
||||
`The Markdown "layout" field is not supported in content collections in Astro 5. Ignoring layout for ${JSON.stringify(entry)}. Enable "legacy.collections" if you need to use the layout field.`
|
||||
);
|
||||
}
|
||||
let render = renderFunctionByContentType.get(entryType);
|
||||
if (store.has(id)) {
|
||||
logger.warn(
|
||||
`Duplicate id "${id}" found in ${filePath2}. Later items with the same id will overwrite earlier ones.`
|
||||
);
|
||||
}
|
||||
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,
|
||||
legacyId
|
||||
});
|
||||
} else if ("contentModuleTypes" in entryType) {
|
||||
store.set({
|
||||
id,
|
||||
data: parsedData,
|
||||
body: globOptions.retainBody === false ? void 0 : body,
|
||||
filePath: relativePath2,
|
||||
digest,
|
||||
deferredRender: true,
|
||||
legacyId
|
||||
});
|
||||
} else {
|
||||
store.set({
|
||||
id,
|
||||
data: parsedData,
|
||||
body: globOptions.retainBody === false ? void 0 : body,
|
||||
filePath: relativePath2,
|
||||
digest,
|
||||
legacyId
|
||||
});
|
||||
}
|
||||
fileToIdMap.set(filePath2, id);
|
||||
}
|
||||
const 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);
|
||||
function isInContentDir(file) {
|
||||
const fileUrl = new URL(file, baseDir);
|
||||
return fileUrl.href.startsWith(contentDir.href);
|
||||
}
|
||||
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;
|
||||
}
|
||||
if (!emulateLegacyCollections && isInContentDir(entry)) {
|
||||
skippedFiles.push(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);
|
||||
await syncData(entry, baseUrl, entryType, oldId);
|
||||
logger.info(`Reloaded data from ${colors.green(entry)}`);
|
||||
}
|
||||
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
|
||||
};
|
||||
3
node_modules/astro/dist/content/loaders/index.d.ts
generated
vendored
Normal file
3
node_modules/astro/dist/content/loaders/index.d.ts
generated
vendored
Normal 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
7
node_modules/astro/dist/content/loaders/index.js
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
import { file } from "./file.js";
|
||||
import { glob } from "./glob.js";
|
||||
export * from "./types.js";
|
||||
export {
|
||||
file,
|
||||
glob
|
||||
};
|
||||
65
node_modules/astro/dist/content/loaders/types.d.ts
generated
vendored
Normal file
65
node_modules/astro/dist/content/loaders/types.d.ts
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
import type { FSWatcher } from 'vite';
|
||||
import type { ZodSchema } from 'zod';
|
||||
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 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): 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 interface 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?: ZodSchema | Promise<ZodSchema> | (() => ZodSchema | Promise<ZodSchema>);
|
||||
}
|
||||
export interface LoadEntryContext<TEntryFilter = never> {
|
||||
filter: TEntryFilter extends never ? {
|
||||
id: string;
|
||||
} : TEntryFilter;
|
||||
}
|
||||
export interface LoadCollectionContext<TCollectionFilter = unknown> {
|
||||
filter?: TCollectionFilter;
|
||||
}
|
||||
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
0
node_modules/astro/dist/content/loaders/types.js
generated
vendored
Normal file
65
node_modules/astro/dist/content/mutable-data-store.d.ts
generated
vendored
Normal file
65
node_modules/astro/dist/content/mutable-data-store.d.ts
generated
vendored
Normal 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;
|
||||
}
|
||||
381
node_modules/astro/dist/content/mutable-data-store.js
generated
vendored
Normal file
381
node_modules/astro/dist/content/mutable-data-store.js
generated
vendored
Normal file
@@ -0,0 +1,381 @@
|
||||
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();
|
||||
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();
|
||||
}
|
||||
}
|
||||
clear(collectionName) {
|
||||
this._collections.delete(collectionName);
|
||||
this.#saveToDiskDebounced();
|
||||
}
|
||||
clearAll() {
|
||||
this._collections.clear();
|
||||
this.#saveToDiskDebounced();
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
async writeAssetImports(filePath) {
|
||||
this.#assetsFile = filePath;
|
||||
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 = [];
|
||||
this.#assetImports.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 = [];
|
||||
for (const [fileName, specifier] of this.#moduleImports) {
|
||||
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.#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,
|
||||
legacyId
|
||||
}) => {
|
||||
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 (legacyId) {
|
||||
entry.legacyId = legacyId;
|
||||
}
|
||||
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() {
|
||||
return devalue.stringify(this._collections);
|
||||
}
|
||||
async writeToDisk() {
|
||||
if (!this.#dirty) {
|
||||
return;
|
||||
}
|
||||
if (!this.#file) {
|
||||
throw new AstroError(AstroErrorData.UnknownFilesystemError);
|
||||
}
|
||||
try {
|
||||
this.#dirty = false;
|
||||
await this.#writeFileAtomic(this.#file, this.toString());
|
||||
} catch (err) {
|
||||
throw new AstroError(AstroErrorData.UnknownFilesystemError, { cause: err });
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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
11
node_modules/astro/dist/content/runtime-assets.d.ts
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
import type { PluginContext } from 'rollup';
|
||||
import { z } from 'zod';
|
||||
export declare function createImage(pluginContext: PluginContext, shouldEmitFile: boolean, entryFilePath: string, experimentalSvgEnabled: boolean): () => z.ZodEffects<z.ZodString, z.ZodNever | {
|
||||
ASTRO_ASSET: string;
|
||||
src: string;
|
||||
format: import("../assets/types.js").ImageInputFormat;
|
||||
width: number;
|
||||
height: number;
|
||||
fsPath: string;
|
||||
orientation?: number | undefined;
|
||||
}, string>;
|
||||
28
node_modules/astro/dist/content/runtime-assets.js
generated
vendored
Normal file
28
node_modules/astro/dist/content/runtime-assets.js
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
import { z } from "zod";
|
||||
import { emitESMImage } from "../assets/utils/node/emitAsset.js";
|
||||
function createImage(pluginContext, shouldEmitFile, entryFilePath, experimentalSvgEnabled) {
|
||||
return () => {
|
||||
return z.string().transform(async (imagePath, ctx) => {
|
||||
const resolvedFilePath = (await pluginContext.resolve(imagePath, entryFilePath))?.id;
|
||||
const metadata = await emitESMImage(
|
||||
resolvedFilePath,
|
||||
pluginContext.meta.watchMode,
|
||||
// FUTURE: Remove in this in v6
|
||||
experimentalSvgEnabled,
|
||||
shouldEmitFile ? pluginContext.emitFile : void 0
|
||||
);
|
||||
if (!metadata) {
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
message: `Image ${imagePath} does not exist. Is the path correct?`,
|
||||
fatal: true
|
||||
});
|
||||
return z.never();
|
||||
}
|
||||
return { ...metadata, ASTRO_ASSET: metadata.fsPath };
|
||||
});
|
||||
};
|
||||
}
|
||||
export {
|
||||
createImage
|
||||
};
|
||||
142
node_modules/astro/dist/content/runtime.d.ts
generated
vendored
Normal file
142
node_modules/astro/dist/content/runtime.d.ts
generated
vendored
Normal file
@@ -0,0 +1,142 @@
|
||||
import type { MarkdownHeading } from '@astrojs/markdown-remark';
|
||||
import { z } from 'zod';
|
||||
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';
|
||||
import type { ContentLookupMap } from './utils.js';
|
||||
export { LiveCollectionError, LiveCollectionCacheHintError, LiveEntryNotFoundError, LiveCollectionValidationError, };
|
||||
type LazyImport = () => Promise<any>;
|
||||
type GlobResult = Record<string, LazyImport>;
|
||||
type CollectionToEntryMap = Record<string, GlobResult>;
|
||||
type GetEntryImport = (collection: string, lookupId: string) => Promise<LazyImport>;
|
||||
type LiveCollectionConfigMap = Record<string, {
|
||||
loader: LiveLoader;
|
||||
type: typeof LIVE_CONTENT_TYPE;
|
||||
schema?: z.ZodType;
|
||||
}>;
|
||||
export declare function createCollectionToGlobResultMap({ globResult, contentDir, }: {
|
||||
globResult: GlobResult;
|
||||
contentDir: string;
|
||||
}): CollectionToEntryMap;
|
||||
export declare function createGetCollection({ contentCollectionToEntryMap, dataCollectionToEntryMap, getRenderEntryImport, cacheEntriesByCollection, liveCollections, }: {
|
||||
contentCollectionToEntryMap: CollectionToEntryMap;
|
||||
dataCollectionToEntryMap: CollectionToEntryMap;
|
||||
getRenderEntryImport: GetEntryImport;
|
||||
cacheEntriesByCollection: Map<string, any[]>;
|
||||
liveCollections: LiveCollectionConfigMap;
|
||||
}): (collection: string, filter?: ((entry: any) => unknown) | Record<string, unknown>) => Promise<any[]>;
|
||||
export declare function createGetEntryBySlug({ getEntryImport, getRenderEntryImport, collectionNames, getEntry, }: {
|
||||
getEntryImport: GetEntryImport;
|
||||
getRenderEntryImport: GetEntryImport;
|
||||
collectionNames: Set<string>;
|
||||
getEntry: ReturnType<typeof createGetEntry>;
|
||||
}): (collection: string, slug: string) => Promise<{
|
||||
id: any;
|
||||
slug: any;
|
||||
body: any;
|
||||
collection: any;
|
||||
data: any;
|
||||
render(): Promise<RenderResult>;
|
||||
} | undefined>;
|
||||
export declare function createGetDataEntryById({ getEntryImport, collectionNames, getEntry, }: {
|
||||
getEntryImport: GetEntryImport;
|
||||
collectionNames: Set<string>;
|
||||
getEntry: ReturnType<typeof createGetEntry>;
|
||||
}): (collection: string, id: string) => Promise<ContentEntryResult | {
|
||||
id: any;
|
||||
collection: any;
|
||||
data: any;
|
||||
} | undefined>;
|
||||
type ContentEntryResult = {
|
||||
id: string;
|
||||
slug: string;
|
||||
body: string;
|
||||
collection: string;
|
||||
data: Record<string, any>;
|
||||
render(): Promise<RenderResult>;
|
||||
};
|
||||
type DataEntryResult = {
|
||||
id: string;
|
||||
collection: string;
|
||||
data: Record<string, any>;
|
||||
};
|
||||
type EntryLookupObject = {
|
||||
collection: string;
|
||||
id: string;
|
||||
} | {
|
||||
collection: string;
|
||||
slug: string;
|
||||
};
|
||||
export declare function createGetEntry({ getEntryImport, getRenderEntryImport, collectionNames, liveCollections, }: {
|
||||
getEntryImport: GetEntryImport;
|
||||
getRenderEntryImport: GetEntryImport;
|
||||
collectionNames: Set<string>;
|
||||
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 renderEntry(entry: DataEntry | {
|
||||
render: () => Promise<{
|
||||
Content: AstroComponentFactory;
|
||||
}>;
|
||||
} | (DataEntry & {
|
||||
render: () => Promise<{
|
||||
Content: AstroComponentFactory;
|
||||
}>;
|
||||
})): Promise<{
|
||||
Content: AstroComponentFactory;
|
||||
}>;
|
||||
export declare function createReference({ lookupMap }: {
|
||||
lookupMap: ContentLookupMap;
|
||||
}): (collection: string) => z.ZodEffects<z.ZodUnion<[z.ZodString, z.ZodObject<{
|
||||
id: z.ZodString;
|
||||
collection: z.ZodString;
|
||||
}, "strip", z.ZodTypeAny, {
|
||||
id: string;
|
||||
collection: string;
|
||||
}, {
|
||||
id: string;
|
||||
collection: string;
|
||||
}>, z.ZodObject<{
|
||||
slug: z.ZodString;
|
||||
collection: z.ZodString;
|
||||
}, "strip", z.ZodTypeAny, {
|
||||
slug: string;
|
||||
collection: string;
|
||||
}, {
|
||||
slug: string;
|
||||
collection: string;
|
||||
}>]>, {
|
||||
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>;
|
||||
export declare function defineLiveCollection(): void;
|
||||
718
node_modules/astro/dist/content/runtime.js
generated
vendored
Normal file
718
node_modules/astro/dist/content/runtime.js
generated
vendored
Normal file
@@ -0,0 +1,718 @@
|
||||
import { escape } from "html-escaper";
|
||||
import { Traverse } from "neotraverse/modern";
|
||||
import pLimit from "p-limit";
|
||||
import { ZodIssueCode, z } from "zod";
|
||||
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";
|
||||
function createCollectionToGlobResultMap({
|
||||
globResult,
|
||||
contentDir
|
||||
}) {
|
||||
const collectionToGlobResultMap = {};
|
||||
for (const key in globResult) {
|
||||
const keyRelativeToContentDir = key.replace(new RegExp(`^${contentDir}`), "");
|
||||
const segments = keyRelativeToContentDir.split("/");
|
||||
if (segments.length <= 1) continue;
|
||||
const collection = segments[0];
|
||||
collectionToGlobResultMap[collection] ??= {};
|
||||
collectionToGlobResultMap[collection][key] = globResult[key];
|
||||
}
|
||||
return collectionToGlobResultMap;
|
||||
}
|
||||
const cacheHintSchema = z.object({
|
||||
tags: z.array(z.string()).optional(),
|
||||
lastModified: z.date().optional()
|
||||
});
|
||||
async function parseLiveEntry(entry, schema, collection) {
|
||||
try {
|
||||
const parsed = await schema.safeParseAsync(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({
|
||||
contentCollectionToEntryMap,
|
||||
dataCollectionToEntryMap,
|
||||
getRenderEntryImport,
|
||||
cacheEntriesByCollection,
|
||||
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();
|
||||
let type;
|
||||
if (collection in contentCollectionToEntryMap) {
|
||||
type = "content";
|
||||
} else if (collection in dataCollectionToEntryMap) {
|
||||
type = "data";
|
||||
} else 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 (entry.legacyId) {
|
||||
entry = emulateLegacyEntry(entry);
|
||||
}
|
||||
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 [];
|
||||
}
|
||||
const lazyImports = Object.values(
|
||||
type === "content" ? contentCollectionToEntryMap[collection] : dataCollectionToEntryMap[collection]
|
||||
);
|
||||
let entries = [];
|
||||
if (!import.meta.env?.DEV && cacheEntriesByCollection.has(collection)) {
|
||||
entries = cacheEntriesByCollection.get(collection);
|
||||
} else {
|
||||
const limit = pLimit(10);
|
||||
entries = await Promise.all(
|
||||
lazyImports.map(
|
||||
(lazyImport) => limit(async () => {
|
||||
const entry = await lazyImport();
|
||||
return type === "content" ? {
|
||||
id: entry.id,
|
||||
slug: entry.slug,
|
||||
body: entry.body,
|
||||
collection: entry.collection,
|
||||
data: entry.data,
|
||||
async render() {
|
||||
return render({
|
||||
collection: entry.collection,
|
||||
id: entry.id,
|
||||
renderEntryImport: await getRenderEntryImport(collection, entry.slug)
|
||||
});
|
||||
}
|
||||
} : {
|
||||
id: entry.id,
|
||||
collection: entry.collection,
|
||||
data: entry.data
|
||||
};
|
||||
})
|
||||
)
|
||||
);
|
||||
cacheEntriesByCollection.set(collection, entries);
|
||||
}
|
||||
if (hasFilter) {
|
||||
return entries.filter(filter);
|
||||
} else {
|
||||
return entries.slice();
|
||||
}
|
||||
};
|
||||
}
|
||||
function createGetEntryBySlug({
|
||||
getEntryImport,
|
||||
getRenderEntryImport,
|
||||
collectionNames,
|
||||
getEntry
|
||||
}) {
|
||||
return async function getEntryBySlug(collection, slug) {
|
||||
const store = await globalDataStore.get();
|
||||
if (!collectionNames.has(collection)) {
|
||||
if (store.hasCollection(collection)) {
|
||||
const entry2 = await getEntry(collection, slug);
|
||||
if (entry2 && "slug" in entry2) {
|
||||
return entry2;
|
||||
}
|
||||
throw new AstroError({
|
||||
...AstroErrorData.GetEntryDeprecationError,
|
||||
message: AstroErrorData.GetEntryDeprecationError.message(collection, "getEntryBySlug")
|
||||
});
|
||||
}
|
||||
console.warn(
|
||||
`The collection ${JSON.stringify(collection)} does not exist. Please ensure it is defined in your content config.`
|
||||
);
|
||||
return void 0;
|
||||
}
|
||||
const entryImport = await getEntryImport(collection, slug);
|
||||
if (typeof entryImport !== "function") return void 0;
|
||||
const entry = await entryImport();
|
||||
return {
|
||||
id: entry.id,
|
||||
slug: entry.slug,
|
||||
body: entry.body,
|
||||
collection: entry.collection,
|
||||
data: entry.data,
|
||||
async render() {
|
||||
return render({
|
||||
collection: entry.collection,
|
||||
id: entry.id,
|
||||
renderEntryImport: await getRenderEntryImport(collection, slug)
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
function createGetDataEntryById({
|
||||
getEntryImport,
|
||||
collectionNames,
|
||||
getEntry
|
||||
}) {
|
||||
return async function getDataEntryById(collection, id) {
|
||||
const store = await globalDataStore.get();
|
||||
if (!collectionNames.has(collection)) {
|
||||
if (store.hasCollection(collection)) {
|
||||
return getEntry(collection, id);
|
||||
}
|
||||
console.warn(
|
||||
`The collection ${JSON.stringify(collection)} does not exist. Please ensure it is defined in your content config.`
|
||||
);
|
||||
return void 0;
|
||||
}
|
||||
const lazyImport = await getEntryImport(collection, id);
|
||||
if (!lazyImport) throw new Error(`Entry ${collection} \u2192 ${id} was not found.`);
|
||||
const entry = await lazyImport();
|
||||
return {
|
||||
id: entry.id,
|
||||
collection: entry.collection,
|
||||
data: entry.data
|
||||
};
|
||||
};
|
||||
}
|
||||
function emulateLegacyEntry({ legacyId, ...entry }) {
|
||||
const legacyEntry = {
|
||||
...entry,
|
||||
id: legacyId,
|
||||
slug: entry.id
|
||||
};
|
||||
return {
|
||||
...legacyEntry,
|
||||
// Define separately so the render function isn't included in the object passed to `renderEntry()`
|
||||
render: () => renderEntry(legacyEntry)
|
||||
};
|
||||
}
|
||||
function createGetEntry({
|
||||
getEntryImport,
|
||||
getRenderEntryImport,
|
||||
collectionNames,
|
||||
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 entry2 = store.get(collection, lookupId);
|
||||
if (!entry2) {
|
||||
console.warn(`Entry ${collection} \u2192 ${lookupId} was not found.`);
|
||||
return;
|
||||
}
|
||||
const { default: imageAssetMap } = await import("astro:asset-imports");
|
||||
entry2.data = updateImageReferencesInData(entry2.data, entry2.filePath, imageAssetMap);
|
||||
if (entry2.legacyId) {
|
||||
return emulateLegacyEntry({ ...entry2, collection });
|
||||
}
|
||||
return {
|
||||
...entry2,
|
||||
collection
|
||||
};
|
||||
}
|
||||
if (!collectionNames.has(collection)) {
|
||||
console.warn(
|
||||
`The collection ${JSON.stringify(collection)} does not exist. Please ensure it is defined in your content config.`
|
||||
);
|
||||
return void 0;
|
||||
}
|
||||
const entryImport = await getEntryImport(collection, lookupId);
|
||||
if (typeof entryImport !== "function") return void 0;
|
||||
const entry = await entryImport();
|
||||
if (entry._internal.type === "content") {
|
||||
return {
|
||||
id: entry.id,
|
||||
slug: entry.slug,
|
||||
body: entry.body,
|
||||
collection: entry.collection,
|
||||
data: entry.data,
|
||||
async render() {
|
||||
return render({
|
||||
collection: entry.collection,
|
||||
id: entry.id,
|
||||
renderEntryImport: await getRenderEntryImport(collection, lookupId)
|
||||
});
|
||||
}
|
||||
};
|
||||
} else if (entry._internal.type === "data") {
|
||||
return {
|
||||
id: entry.id,
|
||||
collection: entry.collection,
|
||||
data: entry.data
|
||||
};
|
||||
}
|
||||
return void 0;
|
||||
};
|
||||
}
|
||||
function createGetEntries(getEntry) {
|
||||
return async function getEntries(entries) {
|
||||
return Promise.all(entries.map((e) => getEntry(e)));
|
||||
};
|
||||
}
|
||||
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
|
||||
};
|
||||
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
|
||||
};
|
||||
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("astro:assets");
|
||||
for (const [_full, imagePath] of html.matchAll(CONTENT_LAYER_IMAGE_REGEX)) {
|
||||
try {
|
||||
const decodedImagePath = JSON.parse(imagePath.replaceAll(""", '"'));
|
||||
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) {
|
||||
ctx.update(imported);
|
||||
} else {
|
||||
ctx.update(src);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
async function renderEntry(entry) {
|
||||
if (!entry) {
|
||||
throw new AstroError(AstroErrorData.RenderUndefinedEntryError);
|
||||
}
|
||||
if ("render" in entry && !("legacyId" in entry)) {
|
||||
return entry.render();
|
||||
}
|
||||
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({ lookupMap }) {
|
||||
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) => {
|
||||
const flattenedErrorPath = ctx.path.join(".");
|
||||
if (typeof lookup === "object") {
|
||||
if (lookup.collection !== collection) {
|
||||
ctx.addIssue({
|
||||
code: ZodIssueCode.custom,
|
||||
message: `**${flattenedErrorPath}**: Reference to ${collection} invalid. Expected ${collection}. Received ${lookup.collection}.`
|
||||
});
|
||||
return;
|
||||
}
|
||||
return lookup;
|
||||
}
|
||||
if (!lookupMap[collection]) {
|
||||
return { id: lookup, collection };
|
||||
}
|
||||
const { type, entries } = lookupMap[collection];
|
||||
const entry = entries[lookup];
|
||||
if (!entry) {
|
||||
ctx.addIssue({
|
||||
code: ZodIssueCode.custom,
|
||||
message: `**${flattenedErrorPath}**: Reference to ${collection} invalid. Expected ${Object.keys(
|
||||
entries
|
||||
).map((c) => JSON.stringify(c)).join(" | ")}. Received ${JSON.stringify(lookup)}.`
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (type === "content") {
|
||||
return { slug: lookup, collection };
|
||||
}
|
||||
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."
|
||||
)
|
||||
});
|
||||
}
|
||||
export {
|
||||
LiveCollectionCacheHintError,
|
||||
LiveCollectionError,
|
||||
LiveCollectionValidationError,
|
||||
LiveEntryNotFoundError,
|
||||
createCollectionToGlobResultMap,
|
||||
createGetCollection,
|
||||
createGetDataEntryById,
|
||||
createGetEntries,
|
||||
createGetEntry,
|
||||
createGetEntryBySlug,
|
||||
createGetLiveCollection,
|
||||
createGetLiveEntry,
|
||||
createReference,
|
||||
defineCollection,
|
||||
defineLiveCollection,
|
||||
renderEntry
|
||||
};
|
||||
12
node_modules/astro/dist/content/server-listeners.d.ts
generated
vendored
Normal file
12
node_modules/astro/dist/content/server-listeners.d.ts
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
import type fsMod from 'node:fs';
|
||||
import type { ViteDevServer } from 'vite';
|
||||
import type { Logger } from '../core/logger/core.js';
|
||||
import type { AstroSettings } from '../types/astro.js';
|
||||
interface ContentServerListenerParams {
|
||||
fs: typeof fsMod;
|
||||
logger: Logger;
|
||||
settings: AstroSettings;
|
||||
viteServer: ViteDevServer;
|
||||
}
|
||||
export declare function attachContentServerListeners({ viteServer, fs, logger, settings, }: ContentServerListenerParams): Promise<void>;
|
||||
export {};
|
||||
101
node_modules/astro/dist/content/server-listeners.js
generated
vendored
Normal file
101
node_modules/astro/dist/content/server-listeners.js
generated
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
import path from "node:path";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
import colors from "piccolore";
|
||||
import { loadTSConfig } from "../core/config/tsconfig.js";
|
||||
import { appendForwardSlash } from "../core/path.js";
|
||||
import { createContentTypesGenerator } from "./types-generator.js";
|
||||
import { getContentPaths, globalContentConfigObserver } from "./utils.js";
|
||||
async function attachContentServerListeners({
|
||||
viteServer,
|
||||
fs,
|
||||
logger,
|
||||
settings
|
||||
}) {
|
||||
const contentPaths = getContentPaths(settings.config, fs);
|
||||
if (!settings.config.legacy?.collections) {
|
||||
await attachListeners();
|
||||
} else if (fs.existsSync(contentPaths.contentDir)) {
|
||||
logger.debug(
|
||||
"content",
|
||||
`Watching ${colors.cyan(
|
||||
contentPaths.contentDir.href.replace(settings.config.root.href, "")
|
||||
)} for changes`
|
||||
);
|
||||
const maybeTsConfigStats = await getTSConfigStatsWhenAllowJsFalse({ contentPaths, settings });
|
||||
if (maybeTsConfigStats) warnAllowJsIsFalse({ ...maybeTsConfigStats, logger });
|
||||
await attachListeners();
|
||||
} else {
|
||||
viteServer.watcher.on("addDir", contentDirListener);
|
||||
async function contentDirListener(dir) {
|
||||
if (appendForwardSlash(pathToFileURL(dir).href) === contentPaths.contentDir.href) {
|
||||
logger.debug("content", `Content directory found. Watching for changes`);
|
||||
await attachListeners();
|
||||
viteServer.watcher.removeListener("addDir", contentDirListener);
|
||||
}
|
||||
}
|
||||
}
|
||||
async function attachListeners() {
|
||||
const contentGenerator = await createContentTypesGenerator({
|
||||
fs,
|
||||
settings,
|
||||
logger,
|
||||
viteServer,
|
||||
contentConfigObserver: globalContentConfigObserver
|
||||
});
|
||||
await contentGenerator.init();
|
||||
logger.debug("content", "Types generated");
|
||||
viteServer.watcher.on("add", (entry) => {
|
||||
contentGenerator.queueEvent({ name: "add", entry });
|
||||
});
|
||||
viteServer.watcher.on(
|
||||
"addDir",
|
||||
(entry) => contentGenerator.queueEvent({ name: "addDir", entry })
|
||||
);
|
||||
viteServer.watcher.on("change", (entry) => {
|
||||
contentGenerator.queueEvent({ name: "change", entry });
|
||||
});
|
||||
viteServer.watcher.on("unlink", (entry) => {
|
||||
contentGenerator.queueEvent({ name: "unlink", entry });
|
||||
});
|
||||
viteServer.watcher.on(
|
||||
"unlinkDir",
|
||||
(entry) => contentGenerator.queueEvent({ name: "unlinkDir", entry })
|
||||
);
|
||||
}
|
||||
}
|
||||
function warnAllowJsIsFalse({
|
||||
logger,
|
||||
tsConfigFileName,
|
||||
contentConfigFileName
|
||||
}) {
|
||||
logger.warn(
|
||||
"content",
|
||||
`Make sure you have the ${colors.bold("allowJs")} compiler option set to ${colors.bold(
|
||||
"true"
|
||||
)} in your ${colors.bold(tsConfigFileName)} file to have autocompletion in your ${colors.bold(
|
||||
contentConfigFileName
|
||||
)} file. See ${colors.underline(
|
||||
colors.cyan("https://www.typescriptlang.org/tsconfig#allowJs")
|
||||
)} for more information.`
|
||||
);
|
||||
}
|
||||
async function getTSConfigStatsWhenAllowJsFalse({
|
||||
contentPaths,
|
||||
settings
|
||||
}) {
|
||||
const isContentConfigJsFile = [".js", ".mjs"].some(
|
||||
(ext) => contentPaths.config.url.pathname.endsWith(ext)
|
||||
);
|
||||
if (!isContentConfigJsFile) return;
|
||||
const inputConfig = await loadTSConfig(fileURLToPath(settings.config.root));
|
||||
if (typeof inputConfig === "string") return;
|
||||
const tsConfigFileName = inputConfig.tsconfigFile.split(path.sep).pop();
|
||||
if (!tsConfigFileName) return;
|
||||
const contentConfigFileName = contentPaths.config.url.pathname.split(path.sep).pop();
|
||||
const allowJSOption = inputConfig.tsconfig.compilerOptions?.allowJs;
|
||||
if (allowJSOption) return;
|
||||
return { tsConfigFileName, contentConfigFileName };
|
||||
}
|
||||
export {
|
||||
attachContentServerListeners
|
||||
};
|
||||
28
node_modules/astro/dist/content/types-generator.d.ts
generated
vendored
Normal file
28
node_modules/astro/dist/content/types-generator.d.ts
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
import type fsMod from 'node:fs';
|
||||
import { type ViteDevServer } from 'vite';
|
||||
import type { Logger } 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: Logger;
|
||||
settings: AstroSettings;
|
||||
/** This is required for loading the content config */
|
||||
viteServer: ViteDevServer;
|
||||
fs: typeof fsMod;
|
||||
};
|
||||
export declare function createContentTypesGenerator({ contentConfigObserver, fs, logger, settings, viteServer, }: CreateContentGeneratorParams): Promise<{
|
||||
init: () => Promise<{
|
||||
typesGenerated: true;
|
||||
} | {
|
||||
typesGenerated: false;
|
||||
reason: "no-content-dir";
|
||||
}>;
|
||||
queueEvent: (rawEvent: RawContentEvent) => void;
|
||||
}>;
|
||||
export {};
|
||||
539
node_modules/astro/dist/content/types-generator.js
generated
vendored
Normal file
539
node_modules/astro/dist/content/types-generator.js
generated
vendored
Normal file
@@ -0,0 +1,539 @@
|
||||
import * as path from "node:path";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
import colors from "piccolore";
|
||||
import { glob } from "tinyglobby";
|
||||
import { normalizePath } from "vite";
|
||||
import { z } from "zod";
|
||||
import { zodToJsonSchema } from "zod-to-json-schema";
|
||||
import { AstroError } from "../core/errors/errors.js";
|
||||
import { AstroErrorData, AstroUserError } from "../core/errors/index.js";
|
||||
import { isRelativePath } from "../core/path.js";
|
||||
import {
|
||||
COLLECTIONS_DIR,
|
||||
CONTENT_LAYER_TYPE,
|
||||
CONTENT_TYPES_FILE,
|
||||
LIVE_CONTENT_TYPE,
|
||||
VIRTUAL_MODULE_ID
|
||||
} from "./consts.js";
|
||||
import {
|
||||
getContentEntryIdAndSlug,
|
||||
getContentPaths,
|
||||
getDataEntryExts,
|
||||
getDataEntryId,
|
||||
getEntryCollectionName,
|
||||
getEntryConfigByExtMap,
|
||||
getEntrySlug,
|
||||
getEntryType,
|
||||
reloadContentConfigObserver
|
||||
} from "./utils.js";
|
||||
async function createContentTypesGenerator({
|
||||
contentConfigObserver,
|
||||
fs,
|
||||
logger,
|
||||
settings,
|
||||
viteServer
|
||||
}) {
|
||||
const collectionEntryMap = {};
|
||||
const contentPaths = getContentPaths(settings.config, fs);
|
||||
const contentEntryConfigByExt = getEntryConfigByExtMap(settings.contentEntryTypes);
|
||||
const contentEntryExts = [...contentEntryConfigByExt.keys()];
|
||||
const dataEntryExts = getDataEntryExts(settings);
|
||||
let events = [];
|
||||
let debounceTimeout;
|
||||
const typeTemplateContent = await fs.promises.readFile(contentPaths.typesTemplate, "utf-8");
|
||||
async function init() {
|
||||
events.push({ name: "add", entry: contentPaths.config.url });
|
||||
if (settings.config.legacy.collections) {
|
||||
if (!fs.existsSync(contentPaths.contentDir)) {
|
||||
return { typesGenerated: false, reason: "no-content-dir" };
|
||||
}
|
||||
const globResult = await glob("**", {
|
||||
cwd: fileURLToPath(contentPaths.contentDir),
|
||||
absolute: true
|
||||
});
|
||||
for (const fullPath of globResult) {
|
||||
const entryURL = pathToFileURL(fullPath);
|
||||
if (entryURL.href.startsWith(contentPaths.config.url.href)) continue;
|
||||
const stat = fs.statSync(fullPath);
|
||||
if (stat.isFile()) {
|
||||
events.push({ name: "add", entry: entryURL });
|
||||
} else if (stat.isDirectory()) {
|
||||
events.push({ name: "addDir", entry: entryURL });
|
||||
}
|
||||
}
|
||||
}
|
||||
await runEvents();
|
||||
return { typesGenerated: true };
|
||||
}
|
||||
async function handleEvent(event) {
|
||||
if (event.name === "addDir" || event.name === "unlinkDir") {
|
||||
const collection2 = normalizePath(
|
||||
path.relative(fileURLToPath(contentPaths.contentDir), fileURLToPath(event.entry))
|
||||
);
|
||||
const collectionKey2 = JSON.stringify(collection2);
|
||||
const isCollectionEvent = collection2.split("/").length === 1;
|
||||
if (!isCollectionEvent) return { shouldGenerateTypes: false };
|
||||
switch (event.name) {
|
||||
case "addDir":
|
||||
collectionEntryMap[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, viteServer });
|
||||
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.hot.send({
|
||||
type: "error",
|
||||
err: new AstroError({
|
||||
...AstroErrorData.MixedContentDataCollectionError,
|
||||
message: AstroErrorData.MixedContentDataCollectionError.message(collectionKey2),
|
||||
location: { file: entry.pathname }
|
||||
})
|
||||
});
|
||||
return { shouldGenerateTypes: false };
|
||||
}
|
||||
if (!(entryKey2 in collectionEntryMap[collectionKey2])) {
|
||||
collectionEntryMap[collectionKey2] = {
|
||||
type: "data",
|
||||
entries: { ...collectionInfo2.entries, [entryKey2]: {} }
|
||||
};
|
||||
}
|
||||
return { shouldGenerateTypes: true };
|
||||
case "unlink":
|
||||
if (collectionKey2 in collectionEntryMap && entryKey2 in collectionEntryMap[collectionKey2].entries) {
|
||||
delete collectionEntryMap[collectionKey2].entries[entryKey2];
|
||||
}
|
||||
return { shouldGenerateTypes: true };
|
||||
case "change":
|
||||
return { shouldGenerateTypes: false };
|
||||
}
|
||||
}
|
||||
const contentEntryType = contentEntryConfigByExt.get(path.extname(event.entry.pathname));
|
||||
if (!contentEntryType) return { shouldGenerateTypes: false };
|
||||
const { id, slug: generatedSlug } = getContentEntryIdAndSlug({
|
||||
entry,
|
||||
contentDir,
|
||||
collection
|
||||
});
|
||||
const collectionKey = JSON.stringify(collection);
|
||||
if (!(collectionKey in collectionEntryMap)) {
|
||||
collectionEntryMap[collectionKey] = { type: "content", entries: {} };
|
||||
}
|
||||
const collectionInfo = collectionEntryMap[collectionKey];
|
||||
if (collectionInfo.type === "data") {
|
||||
viteServer.hot.send({
|
||||
type: "error",
|
||||
err: new AstroError({
|
||||
...AstroErrorData.MixedContentDataCollectionError,
|
||||
message: AstroErrorData.MixedContentDataCollectionError.message(collectionKey),
|
||||
location: { file: entry.pathname }
|
||||
})
|
||||
});
|
||||
return { shouldGenerateTypes: false };
|
||||
}
|
||||
const entryKey = JSON.stringify(id);
|
||||
switch (event.name) {
|
||||
case "add":
|
||||
const addedSlug = await getEntrySlug({
|
||||
generatedSlug,
|
||||
id,
|
||||
collection,
|
||||
fileUrl: event.entry,
|
||||
contentEntryType,
|
||||
fs
|
||||
});
|
||||
if (!(entryKey in collectionEntryMap[collectionKey].entries)) {
|
||||
collectionEntryMap[collectionKey] = {
|
||||
type: "content",
|
||||
entries: {
|
||||
...collectionInfo.entries,
|
||||
[entryKey]: { slug: addedSlug }
|
||||
}
|
||||
};
|
||||
}
|
||||
return { shouldGenerateTypes: true };
|
||||
case "unlink":
|
||||
if (collectionKey in collectionEntryMap && entryKey in collectionEntryMap[collectionKey].entries) {
|
||||
delete collectionEntryMap[collectionKey].entries[entryKey];
|
||||
}
|
||||
return { shouldGenerateTypes: true };
|
||||
case "change":
|
||||
const changedSlug = await getEntrySlug({
|
||||
generatedSlug,
|
||||
id,
|
||||
collection,
|
||||
fileUrl: event.entry,
|
||||
contentEntryType,
|
||||
fs
|
||||
});
|
||||
const entryMetadata = collectionInfo.entries[entryKey];
|
||||
if (entryMetadata?.slug !== changedSlug) {
|
||||
collectionInfo.entries[entryKey].slug = changedSlug;
|
||||
return { shouldGenerateTypes: true };
|
||||
}
|
||||
return { shouldGenerateTypes: false };
|
||||
}
|
||||
}
|
||||
function queueEvent(rawEvent) {
|
||||
const event = {
|
||||
entry: pathToFileURL(rawEvent.entry),
|
||||
name: rawEvent.name
|
||||
};
|
||||
if (settings.config.legacy.collections) {
|
||||
if (!event.entry.pathname.startsWith(contentPaths.contentDir.pathname)) {
|
||||
return;
|
||||
}
|
||||
} else 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
|
||||
});
|
||||
invalidateVirtualMod(viteServer);
|
||||
}
|
||||
}
|
||||
return { init, queueEvent };
|
||||
}
|
||||
function invalidateVirtualMod(viteServer) {
|
||||
const virtualMod = viteServer.moduleGraph.getModuleById("\0" + VIRTUAL_MODULE_ID);
|
||||
if (!virtualMod) return;
|
||||
viteServer.moduleGraph.invalidateModule(virtualMod);
|
||||
}
|
||||
function normalizeConfigPath(from, to) {
|
||||
const configPath = path.relative(from, to).replace(/\.ts$/, ".js");
|
||||
const normalizedPath = configPath.replaceAll("\\", "/");
|
||||
return `"${isRelativePath(configPath) ? "" : "./"}${normalizedPath}"`;
|
||||
}
|
||||
const schemaCache = /* @__PURE__ */ new Map();
|
||||
async function getContentLayerSchema(collection, collectionKey) {
|
||||
const cached = schemaCache.get(collectionKey);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
if (collection?.type === CONTENT_LAYER_TYPE && typeof collection.loader === "object" && collection.loader.schema) {
|
||||
let schema = collection.loader.schema;
|
||||
if (typeof schema === "function") {
|
||||
schema = await schema();
|
||||
}
|
||||
if (schema) {
|
||||
schemaCache.set(collectionKey, await schema);
|
||||
return schema;
|
||||
}
|
||||
}
|
||||
}
|
||||
async function typeForCollection(collection, collectionKey) {
|
||||
if (collection?.schema) {
|
||||
return `InferEntrySchema<${collectionKey}>`;
|
||||
}
|
||||
if (collection?.type === CONTENT_LAYER_TYPE) {
|
||||
const schema = await getContentLayerSchema(collection, collectionKey);
|
||||
if (schema) {
|
||||
try {
|
||||
const zodToTs = await import("zod-to-ts");
|
||||
const ast = zodToTs.zodToTs(schema);
|
||||
return zodToTs.printNode(ast.node);
|
||||
} catch (err) {
|
||||
if (err.message.includes("Cannot find package 'typescript'")) {
|
||||
return "any";
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
return "any";
|
||||
}
|
||||
async function writeContentFiles({
|
||||
fs,
|
||||
contentPaths,
|
||||
collectionEntryMap,
|
||||
typeTemplateContent,
|
||||
contentEntryTypes,
|
||||
contentConfig,
|
||||
viteServer,
|
||||
logger,
|
||||
settings
|
||||
}) {
|
||||
let contentTypesStr = "";
|
||||
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,
|
||||
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.hot.send({
|
||||
type: "error",
|
||||
err: new AstroError({
|
||||
...AstroErrorData.ContentCollectionTypeMismatchError,
|
||||
message: AstroErrorData.ContentCollectionTypeMismatchError.message(
|
||||
collectionKey,
|
||||
collection.type,
|
||||
collectionConfig.type
|
||||
),
|
||||
hint: collection.type === "data" ? "Try adding `type: 'data'` to your collection config." : void 0,
|
||||
location: {
|
||||
file: ""
|
||||
}
|
||||
})
|
||||
});
|
||||
return;
|
||||
}
|
||||
const resolvedType = collection.type === "unknown" ? (
|
||||
// Add empty / unknown collections to the data type map by default
|
||||
// This ensures `getCollection('empty-collection')` doesn't raise a type error
|
||||
collectionConfig?.type ?? "data"
|
||||
) : collection.type;
|
||||
const collectionEntryKeys = Object.keys(collection.entries).sort();
|
||||
const dataType = await typeForCollection(collectionConfig, collectionKey);
|
||||
switch (resolvedType) {
|
||||
case LIVE_CONTENT_TYPE:
|
||||
throw new AstroUserError(
|
||||
`Invalid definition for collection ${collectionKey}: Live content collections must be defined in "src/live.config.ts"`
|
||||
);
|
||||
case "content":
|
||||
if (collectionEntryKeys.length === 0) {
|
||||
contentTypesStr += `${collectionKey}: Record<string, {
|
||||
id: string;
|
||||
slug: string;
|
||||
body: string;
|
||||
collection: ${collectionKey};
|
||||
data: ${dataType};
|
||||
render(): Render[".md"];
|
||||
}>;
|
||||
`;
|
||||
break;
|
||||
}
|
||||
contentTypesStr += `${collectionKey}: {
|
||||
`;
|
||||
for (const entryKey of collectionEntryKeys) {
|
||||
const entryMetadata = collection.entries[entryKey];
|
||||
const renderType = `{ render(): Render[${JSON.stringify(
|
||||
path.extname(JSON.parse(entryKey))
|
||||
)}] }`;
|
||||
const slugType = JSON.stringify(entryMetadata.slug);
|
||||
contentTypesStr += `${entryKey}: {
|
||||
id: ${entryKey};
|
||||
slug: ${slugType};
|
||||
body: string;
|
||||
collection: ${collectionKey};
|
||||
data: ${dataType}
|
||||
} & ${renderType};
|
||||
`;
|
||||
}
|
||||
contentTypesStr += `};
|
||||
`;
|
||||
break;
|
||||
case CONTENT_LAYER_TYPE:
|
||||
const legacyTypes = collectionConfig?._legacy ? 'render(): Render[".md"];\n slug: string;\n body: string;\n' : "body?: string;\n";
|
||||
dataTypesStr += `${collectionKey}: Record<string, {
|
||||
id: string;
|
||||
${legacyTypes} collection: ${collectionKey};
|
||||
data: ${dataType};
|
||||
rendered?: RenderedContent;
|
||||
filePath?: string;
|
||||
}>;
|
||||
`;
|
||||
break;
|
||||
case "data":
|
||||
if (collectionEntryKeys.length === 0) {
|
||||
dataTypesStr += `${collectionKey}: Record<string, {
|
||||
id: string;
|
||||
collection: ${collectionKey};
|
||||
data: ${dataType};
|
||||
}>;
|
||||
`;
|
||||
} else {
|
||||
dataTypesStr += `${collectionKey}: {
|
||||
`;
|
||||
for (const entryKey of collectionEntryKeys) {
|
||||
dataTypesStr += `${entryKey}: {
|
||||
id: ${entryKey};
|
||||
collection: ${collectionKey};
|
||||
data: ${dataType}
|
||||
};
|
||||
`;
|
||||
}
|
||||
dataTypesStr += `};
|
||||
`;
|
||||
}
|
||||
break;
|
||||
}
|
||||
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(collectionConfig?.schema || schemaCache.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("// @@CONTENT_ENTRY_MAP@@", contentTypesStr).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) {
|
||||
zodSchemaForJson = zodSchemaForJson.extend({
|
||||
$schema: z.string().optional()
|
||||
});
|
||||
}
|
||||
try {
|
||||
await fsMod.promises.writeFile(
|
||||
new URL(`./${collectionKey.replace(/"/g, "")}.schema.json`, collectionSchemasDir),
|
||||
JSON.stringify(
|
||||
zodToJsonSchema(zodSchemaForJson, {
|
||||
name: collectionKey.replace(/"/g, ""),
|
||||
markdownDescription: true,
|
||||
errorMessages: true,
|
||||
// Fix for https://github.com/StefanTerdell/zod-to-json-schema/issues/110
|
||||
dateStrategy: ["format:date-time", "format:date", "integer"]
|
||||
}),
|
||||
null,
|
||||
2
|
||||
)
|
||||
);
|
||||
} 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
|
||||
};
|
||||
546
node_modules/astro/dist/content/utils.d.ts
generated
vendored
Normal file
546
node_modules/astro/dist/content/utils.d.ts
generated
vendored
Normal file
@@ -0,0 +1,546 @@
|
||||
import fsMod from 'node:fs';
|
||||
import type { PluginContext } from 'rollup';
|
||||
import type { ViteDevServer } from 'vite';
|
||||
import { z } from 'zod';
|
||||
import type { Logger } 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';
|
||||
/**
|
||||
* A map from a collection + slug to the local file path.
|
||||
* This is used internally to resolve entry imports when using `getEntry()`.
|
||||
* @see `templates/content/module.mjs`
|
||||
*/
|
||||
export type ContentLookupMap = {
|
||||
[collectionName: string]: {
|
||||
type: 'content' | 'data';
|
||||
entries: {
|
||||
[lookupId: string]: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
export declare const loaderReturnSchema: z.ZodUnion<[z.ZodArray<z.ZodObject<{
|
||||
id: z.ZodString;
|
||||
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
||||
id: z.ZodString;
|
||||
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
||||
id: z.ZodString;
|
||||
}, z.ZodTypeAny, "passthrough">>, "many">, z.ZodRecord<z.ZodString, z.ZodObject<{
|
||||
id: z.ZodOptional<z.ZodString>;
|
||||
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
||||
id: z.ZodOptional<z.ZodString>;
|
||||
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
||||
id: z.ZodOptional<z.ZodString>;
|
||||
}, z.ZodTypeAny, "passthrough">>>]>;
|
||||
declare const collectionConfigParser: z.ZodUnion<[z.ZodObject<{
|
||||
type: z.ZodDefault<z.ZodOptional<z.ZodLiteral<"content">>>;
|
||||
schema: z.ZodOptional<z.ZodAny>;
|
||||
}, "strip", z.ZodTypeAny, {
|
||||
type: "content";
|
||||
schema?: any;
|
||||
}, {
|
||||
type?: "content" | undefined;
|
||||
schema?: any;
|
||||
}>, z.ZodObject<{
|
||||
type: z.ZodLiteral<"data">;
|
||||
schema: z.ZodOptional<z.ZodAny>;
|
||||
}, "strip", z.ZodTypeAny, {
|
||||
type: "data";
|
||||
schema?: any;
|
||||
}, {
|
||||
type: "data";
|
||||
schema?: any;
|
||||
}>, z.ZodObject<{
|
||||
type: z.ZodLiteral<"content_layer">;
|
||||
schema: z.ZodOptional<z.ZodAny>;
|
||||
loader: z.ZodUnion<[z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodUnknown>, z.ZodObject<{
|
||||
name: z.ZodString;
|
||||
load: z.ZodFunction<z.ZodTuple<[z.ZodObject<{
|
||||
collection: z.ZodString;
|
||||
store: z.ZodAny;
|
||||
meta: z.ZodAny;
|
||||
logger: z.ZodAny;
|
||||
config: z.ZodAny;
|
||||
entryTypes: z.ZodAny;
|
||||
parseData: z.ZodAny;
|
||||
renderMarkdown: z.ZodAny;
|
||||
generateDigest: z.ZodFunction<z.ZodTuple<[z.ZodAny], null>, z.ZodUnknown>;
|
||||
watcher: z.ZodOptional<z.ZodAny>;
|
||||
refreshContextData: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
||||
}, "strip", z.ZodTypeAny, {
|
||||
collection: string;
|
||||
generateDigest: (args_0: any) => unknown;
|
||||
config?: any;
|
||||
meta?: any;
|
||||
logger?: any;
|
||||
watcher?: any;
|
||||
parseData?: any;
|
||||
store?: any;
|
||||
entryTypes?: any;
|
||||
renderMarkdown?: any;
|
||||
refreshContextData?: Record<string, unknown> | undefined;
|
||||
}, {
|
||||
collection: string;
|
||||
generateDigest: (args_0: any) => unknown;
|
||||
config?: any;
|
||||
meta?: any;
|
||||
logger?: any;
|
||||
watcher?: any;
|
||||
parseData?: any;
|
||||
store?: any;
|
||||
entryTypes?: any;
|
||||
renderMarkdown?: any;
|
||||
refreshContextData?: Record<string, unknown> | undefined;
|
||||
}>], null>, z.ZodUnknown>;
|
||||
schema: z.ZodOptional<z.ZodAny>;
|
||||
render: z.ZodOptional<z.ZodFunction<z.ZodTuple<[z.ZodAny], null>, z.ZodUnknown>>;
|
||||
}, "strip", z.ZodTypeAny, {
|
||||
name: string;
|
||||
load: (args_0: {
|
||||
collection: string;
|
||||
generateDigest: (args_0: any) => unknown;
|
||||
config?: any;
|
||||
meta?: any;
|
||||
logger?: any;
|
||||
watcher?: any;
|
||||
parseData?: any;
|
||||
store?: any;
|
||||
entryTypes?: any;
|
||||
renderMarkdown?: any;
|
||||
refreshContextData?: Record<string, unknown> | undefined;
|
||||
}) => unknown;
|
||||
schema?: any;
|
||||
render?: ((args_0: any) => unknown) | undefined;
|
||||
}, {
|
||||
name: string;
|
||||
load: (args_0: {
|
||||
collection: string;
|
||||
generateDigest: (args_0: any) => unknown;
|
||||
config?: any;
|
||||
meta?: any;
|
||||
logger?: any;
|
||||
watcher?: any;
|
||||
parseData?: any;
|
||||
store?: any;
|
||||
entryTypes?: any;
|
||||
renderMarkdown?: any;
|
||||
refreshContextData?: Record<string, unknown> | undefined;
|
||||
}) => unknown;
|
||||
schema?: any;
|
||||
render?: ((args_0: any) => unknown) | undefined;
|
||||
}>]>;
|
||||
/** deprecated */
|
||||
_legacy: z.ZodOptional<z.ZodBoolean>;
|
||||
}, "strip", z.ZodTypeAny, {
|
||||
type: "content_layer";
|
||||
loader: ((...args: unknown[]) => unknown) | {
|
||||
name: string;
|
||||
load: (args_0: {
|
||||
collection: string;
|
||||
generateDigest: (args_0: any) => unknown;
|
||||
config?: any;
|
||||
meta?: any;
|
||||
logger?: any;
|
||||
watcher?: any;
|
||||
parseData?: any;
|
||||
store?: any;
|
||||
entryTypes?: any;
|
||||
renderMarkdown?: any;
|
||||
refreshContextData?: Record<string, unknown> | undefined;
|
||||
}) => unknown;
|
||||
schema?: any;
|
||||
render?: ((args_0: any) => unknown) | undefined;
|
||||
};
|
||||
schema?: any;
|
||||
_legacy?: boolean | undefined;
|
||||
}, {
|
||||
type: "content_layer";
|
||||
loader: ((...args: unknown[]) => unknown) | {
|
||||
name: string;
|
||||
load: (args_0: {
|
||||
collection: string;
|
||||
generateDigest: (args_0: any) => unknown;
|
||||
config?: any;
|
||||
meta?: any;
|
||||
logger?: any;
|
||||
watcher?: any;
|
||||
parseData?: any;
|
||||
store?: any;
|
||||
entryTypes?: any;
|
||||
renderMarkdown?: any;
|
||||
refreshContextData?: Record<string, unknown> | undefined;
|
||||
}) => unknown;
|
||||
schema?: any;
|
||||
render?: ((args_0: any) => unknown) | undefined;
|
||||
};
|
||||
schema?: any;
|
||||
_legacy?: boolean | undefined;
|
||||
}>, z.ZodObject<{
|
||||
type: z.ZodDefault<z.ZodOptional<z.ZodLiteral<"live">>>;
|
||||
schema: z.ZodOptional<z.ZodAny>;
|
||||
loader: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodUnknown>;
|
||||
}, "strip", z.ZodTypeAny, {
|
||||
type: "live";
|
||||
loader: (...args: unknown[]) => unknown;
|
||||
schema?: any;
|
||||
}, {
|
||||
loader: (...args: unknown[]) => unknown;
|
||||
type?: "live" | undefined;
|
||||
schema?: any;
|
||||
}>]>;
|
||||
declare const contentConfigParser: z.ZodObject<{
|
||||
collections: z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodObject<{
|
||||
type: z.ZodDefault<z.ZodOptional<z.ZodLiteral<"content">>>;
|
||||
schema: z.ZodOptional<z.ZodAny>;
|
||||
}, "strip", z.ZodTypeAny, {
|
||||
type: "content";
|
||||
schema?: any;
|
||||
}, {
|
||||
type?: "content" | undefined;
|
||||
schema?: any;
|
||||
}>, z.ZodObject<{
|
||||
type: z.ZodLiteral<"data">;
|
||||
schema: z.ZodOptional<z.ZodAny>;
|
||||
}, "strip", z.ZodTypeAny, {
|
||||
type: "data";
|
||||
schema?: any;
|
||||
}, {
|
||||
type: "data";
|
||||
schema?: any;
|
||||
}>, z.ZodObject<{
|
||||
type: z.ZodLiteral<"content_layer">;
|
||||
schema: z.ZodOptional<z.ZodAny>;
|
||||
loader: z.ZodUnion<[z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodUnknown>, z.ZodObject<{
|
||||
name: z.ZodString;
|
||||
load: z.ZodFunction<z.ZodTuple<[z.ZodObject<{
|
||||
collection: z.ZodString;
|
||||
store: z.ZodAny;
|
||||
meta: z.ZodAny;
|
||||
logger: z.ZodAny;
|
||||
config: z.ZodAny;
|
||||
entryTypes: z.ZodAny;
|
||||
parseData: z.ZodAny;
|
||||
renderMarkdown: z.ZodAny;
|
||||
generateDigest: z.ZodFunction<z.ZodTuple<[z.ZodAny], null>, z.ZodUnknown>;
|
||||
watcher: z.ZodOptional<z.ZodAny>;
|
||||
refreshContextData: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
||||
}, "strip", z.ZodTypeAny, {
|
||||
collection: string;
|
||||
generateDigest: (args_0: any) => unknown;
|
||||
config?: any;
|
||||
meta?: any;
|
||||
logger?: any;
|
||||
watcher?: any;
|
||||
parseData?: any;
|
||||
store?: any;
|
||||
entryTypes?: any;
|
||||
renderMarkdown?: any;
|
||||
refreshContextData?: Record<string, unknown> | undefined;
|
||||
}, {
|
||||
collection: string;
|
||||
generateDigest: (args_0: any) => unknown;
|
||||
config?: any;
|
||||
meta?: any;
|
||||
logger?: any;
|
||||
watcher?: any;
|
||||
parseData?: any;
|
||||
store?: any;
|
||||
entryTypes?: any;
|
||||
renderMarkdown?: any;
|
||||
refreshContextData?: Record<string, unknown> | undefined;
|
||||
}>], null>, z.ZodUnknown>;
|
||||
schema: z.ZodOptional<z.ZodAny>;
|
||||
render: z.ZodOptional<z.ZodFunction<z.ZodTuple<[z.ZodAny], null>, z.ZodUnknown>>;
|
||||
}, "strip", z.ZodTypeAny, {
|
||||
name: string;
|
||||
load: (args_0: {
|
||||
collection: string;
|
||||
generateDigest: (args_0: any) => unknown;
|
||||
config?: any;
|
||||
meta?: any;
|
||||
logger?: any;
|
||||
watcher?: any;
|
||||
parseData?: any;
|
||||
store?: any;
|
||||
entryTypes?: any;
|
||||
renderMarkdown?: any;
|
||||
refreshContextData?: Record<string, unknown> | undefined;
|
||||
}) => unknown;
|
||||
schema?: any;
|
||||
render?: ((args_0: any) => unknown) | undefined;
|
||||
}, {
|
||||
name: string;
|
||||
load: (args_0: {
|
||||
collection: string;
|
||||
generateDigest: (args_0: any) => unknown;
|
||||
config?: any;
|
||||
meta?: any;
|
||||
logger?: any;
|
||||
watcher?: any;
|
||||
parseData?: any;
|
||||
store?: any;
|
||||
entryTypes?: any;
|
||||
renderMarkdown?: any;
|
||||
refreshContextData?: Record<string, unknown> | undefined;
|
||||
}) => unknown;
|
||||
schema?: any;
|
||||
render?: ((args_0: any) => unknown) | undefined;
|
||||
}>]>;
|
||||
/** deprecated */
|
||||
_legacy: z.ZodOptional<z.ZodBoolean>;
|
||||
}, "strip", z.ZodTypeAny, {
|
||||
type: "content_layer";
|
||||
loader: ((...args: unknown[]) => unknown) | {
|
||||
name: string;
|
||||
load: (args_0: {
|
||||
collection: string;
|
||||
generateDigest: (args_0: any) => unknown;
|
||||
config?: any;
|
||||
meta?: any;
|
||||
logger?: any;
|
||||
watcher?: any;
|
||||
parseData?: any;
|
||||
store?: any;
|
||||
entryTypes?: any;
|
||||
renderMarkdown?: any;
|
||||
refreshContextData?: Record<string, unknown> | undefined;
|
||||
}) => unknown;
|
||||
schema?: any;
|
||||
render?: ((args_0: any) => unknown) | undefined;
|
||||
};
|
||||
schema?: any;
|
||||
_legacy?: boolean | undefined;
|
||||
}, {
|
||||
type: "content_layer";
|
||||
loader: ((...args: unknown[]) => unknown) | {
|
||||
name: string;
|
||||
load: (args_0: {
|
||||
collection: string;
|
||||
generateDigest: (args_0: any) => unknown;
|
||||
config?: any;
|
||||
meta?: any;
|
||||
logger?: any;
|
||||
watcher?: any;
|
||||
parseData?: any;
|
||||
store?: any;
|
||||
entryTypes?: any;
|
||||
renderMarkdown?: any;
|
||||
refreshContextData?: Record<string, unknown> | undefined;
|
||||
}) => unknown;
|
||||
schema?: any;
|
||||
render?: ((args_0: any) => unknown) | undefined;
|
||||
};
|
||||
schema?: any;
|
||||
_legacy?: boolean | undefined;
|
||||
}>, z.ZodObject<{
|
||||
type: z.ZodDefault<z.ZodOptional<z.ZodLiteral<"live">>>;
|
||||
schema: z.ZodOptional<z.ZodAny>;
|
||||
loader: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodUnknown>;
|
||||
}, "strip", z.ZodTypeAny, {
|
||||
type: "live";
|
||||
loader: (...args: unknown[]) => unknown;
|
||||
schema?: any;
|
||||
}, {
|
||||
loader: (...args: unknown[]) => unknown;
|
||||
type?: "live" | undefined;
|
||||
schema?: any;
|
||||
}>]>>;
|
||||
}, "strip", z.ZodTypeAny, {
|
||||
collections: Record<string, {
|
||||
type: "content";
|
||||
schema?: any;
|
||||
} | {
|
||||
type: "data";
|
||||
schema?: any;
|
||||
} | {
|
||||
type: "content_layer";
|
||||
loader: ((...args: unknown[]) => unknown) | {
|
||||
name: string;
|
||||
load: (args_0: {
|
||||
collection: string;
|
||||
generateDigest: (args_0: any) => unknown;
|
||||
config?: any;
|
||||
meta?: any;
|
||||
logger?: any;
|
||||
watcher?: any;
|
||||
parseData?: any;
|
||||
store?: any;
|
||||
entryTypes?: any;
|
||||
renderMarkdown?: any;
|
||||
refreshContextData?: Record<string, unknown> | undefined;
|
||||
}) => unknown;
|
||||
schema?: any;
|
||||
render?: ((args_0: any) => unknown) | undefined;
|
||||
};
|
||||
schema?: any;
|
||||
_legacy?: boolean | undefined;
|
||||
} | {
|
||||
type: "live";
|
||||
loader: (...args: unknown[]) => unknown;
|
||||
schema?: any;
|
||||
}>;
|
||||
}, {
|
||||
collections: Record<string, {
|
||||
type?: "content" | undefined;
|
||||
schema?: any;
|
||||
} | {
|
||||
type: "data";
|
||||
schema?: any;
|
||||
} | {
|
||||
type: "content_layer";
|
||||
loader: ((...args: unknown[]) => unknown) | {
|
||||
name: string;
|
||||
load: (args_0: {
|
||||
collection: string;
|
||||
generateDigest: (args_0: any) => unknown;
|
||||
config?: any;
|
||||
meta?: any;
|
||||
logger?: any;
|
||||
watcher?: any;
|
||||
parseData?: any;
|
||||
store?: any;
|
||||
entryTypes?: any;
|
||||
renderMarkdown?: any;
|
||||
refreshContextData?: Record<string, unknown> | undefined;
|
||||
}) => unknown;
|
||||
schema?: any;
|
||||
render?: ((args_0: any) => unknown) | undefined;
|
||||
};
|
||||
schema?: any;
|
||||
_legacy?: boolean | undefined;
|
||||
} | {
|
||||
loader: (...args: unknown[]) => unknown;
|
||||
type?: "live" | undefined;
|
||||
schema?: any;
|
||||
}>;
|
||||
}>;
|
||||
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 getEntryDataAndImages<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, experimentalSvgEnabled: boolean, pluginContext?: PluginContext): Promise<{
|
||||
data: TOutputData;
|
||||
imageImports: Array<string>;
|
||||
}>;
|
||||
export declare function getEntryData(entry: {
|
||||
id: string;
|
||||
collection: string;
|
||||
unvalidatedData: Record<string, unknown>;
|
||||
_internal: EntryInternal;
|
||||
}, collectionConfig: CollectionConfig, shouldEmitFile: boolean, experimentalSvgEnabled: boolean, pluginContext?: PluginContext): Promise<Record<string, unknown>>;
|
||||
export declare function getContentEntryExts(settings: Pick<AstroSettings, 'contentEntryTypes'>): string[];
|
||||
export declare function getDataEntryExts(settings: Pick<AstroSettings, 'dataEntryTypes'>): string[];
|
||||
export declare function getEntryConfigByExtMap<TEntryType extends ContentEntryType | DataEntryType>(entryTypes: TEntryType[]): Map<string, TEntryType>;
|
||||
export declare function getSymlinkedContentCollections({ contentDir, logger, fs, }: {
|
||||
contentDir: URL;
|
||||
logger: Logger;
|
||||
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;
|
||||
viteServer: ViteDevServer;
|
||||
observer?: ContentObservable;
|
||||
}): Promise<void>;
|
||||
type ContentCtx = {
|
||||
status: 'init';
|
||||
} | {
|
||||
status: 'loading';
|
||||
} | {
|
||||
status: 'does-not-exist';
|
||||
} | {
|
||||
status: 'loaded';
|
||||
config: ContentConfig;
|
||||
} | {
|
||||
status: 'error';
|
||||
error: Error;
|
||||
};
|
||||
type Observable<C> = {
|
||||
get: () => C;
|
||||
set: (ctx: C) => void;
|
||||
subscribe: (fn: (ctx: C) => void) => () => void;
|
||||
};
|
||||
export type ContentObservable = Observable<ContentCtx>;
|
||||
export 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, legacy, root, experimental, }: Pick<AstroConfig, 'root' | 'srcDir' | 'legacy' | 'experimental'>, fs?: typeof fsMod): ContentPaths;
|
||||
/**
|
||||
* Check for slug in content entry frontmatter and validate the type,
|
||||
* falling back to the `generatedSlug` if none is found.
|
||||
*/
|
||||
export declare function getEntrySlug({ id, collection, generatedSlug, contentEntryType, fileUrl, fs, }: {
|
||||
fs: typeof fsMod;
|
||||
id: string;
|
||||
collection: string;
|
||||
generatedSlug: string;
|
||||
fileUrl: URL;
|
||||
contentEntryType: Pick<ContentEntryType, 'getEntryInfo'>;
|
||||
}): Promise<string>;
|
||||
export declare function getExtGlob(exts: string[]): string;
|
||||
export declare function hasAssetPropagationFlag(id: string): boolean;
|
||||
export declare function globWithUnderscoresIgnored(relContentDir: string, exts: string[]): 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 {};
|
||||
681
node_modules/astro/dist/content/utils.js
generated
vendored
Normal file
681
node_modules/astro/dist/content/utils.js
generated
vendored
Normal file
@@ -0,0 +1,681 @@
|
||||
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 { z } from "zod";
|
||||
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,
|
||||
PROPAGATED_ASSET_FLAG
|
||||
} from "./consts.js";
|
||||
import { glob } from "./loaders/glob.js";
|
||||
import { createImage } from "./runtime-assets.js";
|
||||
const entryTypeSchema = z.object({
|
||||
id: z.string({
|
||||
invalid_type_error: "Content entry `id` must be a string"
|
||||
// Default to empty string so we can validate properly in the loader
|
||||
})
|
||||
}).passthrough();
|
||||
const loaderReturnSchema = z.union([
|
||||
z.array(entryTypeSchema),
|
||||
z.record(
|
||||
z.string(),
|
||||
z.object({
|
||||
id: z.string({
|
||||
invalid_type_error: "Content entry `id` must be a string"
|
||||
}).optional()
|
||||
}).passthrough()
|
||||
)
|
||||
]);
|
||||
const collectionConfigParser = z.union([
|
||||
z.object({
|
||||
type: z.literal("content").optional().default("content"),
|
||||
schema: z.any().optional()
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal("data"),
|
||||
schema: z.any().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(
|
||||
z.tuple(
|
||||
[
|
||||
z.object({
|
||||
collection: z.string(),
|
||||
store: z.any(),
|
||||
meta: z.any(),
|
||||
logger: z.any(),
|
||||
config: z.any(),
|
||||
entryTypes: z.any(),
|
||||
parseData: z.any(),
|
||||
renderMarkdown: z.any(),
|
||||
generateDigest: z.function(z.tuple([z.any()], z.string())),
|
||||
watcher: z.any().optional(),
|
||||
refreshContextData: z.record(z.unknown()).optional()
|
||||
})
|
||||
],
|
||||
z.unknown()
|
||||
)
|
||||
),
|
||||
schema: z.any().optional(),
|
||||
render: z.function(z.tuple([z.any()], z.unknown())).optional()
|
||||
})
|
||||
]),
|
||||
/** deprecated */
|
||||
_legacy: z.boolean().optional()
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal(LIVE_CONTENT_TYPE).optional().default(LIVE_CONTENT_TYPE),
|
||||
schema: z.any().optional(),
|
||||
loader: z.function()
|
||||
})
|
||||
]);
|
||||
const contentConfigParser = z.object({
|
||||
collections: z.record(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 getEntryDataAndImages(entry, collectionConfig, shouldEmitFile, experimentalSvgEnabled, pluginContext) {
|
||||
let data;
|
||||
if (collectionConfig.type === "content" || collectionConfig._legacy) {
|
||||
const { slug, ...unvalidatedData } = entry.unvalidatedData;
|
||||
data = unvalidatedData;
|
||||
} else {
|
||||
data = entry.unvalidatedData;
|
||||
}
|
||||
let schema = collectionConfig.schema;
|
||||
const imageImports = /* @__PURE__ */ new Set();
|
||||
if (typeof schema === "function") {
|
||||
if (pluginContext) {
|
||||
schema = schema({
|
||||
image: createImage(
|
||||
pluginContext,
|
||||
shouldEmitFile,
|
||||
entry._internal.filePath,
|
||||
experimentalSvgEnabled
|
||||
)
|
||||
});
|
||||
} 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}`;
|
||||
}
|
||||
}
|
||||
imageImports.add(normalizedPath);
|
||||
return `${IMAGE_IMPORT_PREFIX}${normalizedPath}`;
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
if (schema) {
|
||||
if (collectionConfig.type === "content" && typeof schema === "object" && "shape" in schema && schema.shape.slug) {
|
||||
throw new AstroError({
|
||||
...AstroErrorData.ContentSchemaContainsSlugError,
|
||||
message: AstroErrorData.ContentSchemaContainsSlugError.message(entry.collection)
|
||||
});
|
||||
}
|
||||
let formattedError;
|
||||
const parsed = await schema.safeParseAsync(data, {
|
||||
errorMap(error, ctx) {
|
||||
if (error.code === "custom" && error.params?.isHoistedAstroError) {
|
||||
formattedError = error.params?.astroError;
|
||||
}
|
||||
return errorMap(error, ctx);
|
||||
}
|
||||
});
|
||||
if (parsed.success) {
|
||||
data = parsed.data;
|
||||
} else {
|
||||
if (!formattedError) {
|
||||
const errorType = collectionConfig.type === "content" ? AstroErrorData.InvalidContentEntryFrontmatterError : AstroErrorData.InvalidContentEntryDataError;
|
||||
formattedError = new AstroError({
|
||||
...errorType,
|
||||
message: errorType.message(entry.collection, entry.id, parsed.error),
|
||||
location: {
|
||||
file: entry._internal?.filePath,
|
||||
line: getYAMLErrorLine(
|
||||
entry._internal?.rawData,
|
||||
String(parsed.error.errors[0].path[0])
|
||||
),
|
||||
column: 0
|
||||
}
|
||||
});
|
||||
}
|
||||
throw formattedError;
|
||||
}
|
||||
}
|
||||
return { data, imageImports: Array.from(imageImports) };
|
||||
}
|
||||
async function getEntryData(entry, collectionConfig, shouldEmitFile, experimentalSvgEnabled, pluginContext) {
|
||||
const { data } = await getEntryDataAndImages(
|
||||
entry,
|
||||
collectionConfig,
|
||||
shouldEmitFile,
|
||||
experimentalSvgEnabled,
|
||||
pluginContext
|
||||
);
|
||||
return data;
|
||||
}
|
||||
function getContentEntryExts(settings) {
|
||||
return settings.contentEntryTypes.map((t) => t.extensions).flat();
|
||||
}
|
||||
function getDataEntryExts(settings) {
|
||||
return settings.dataEntryTypes.map((t) => t.extensions).flat();
|
||||
}
|
||||
function getEntryConfigByExtMap(entryTypes) {
|
||||
const map = /* @__PURE__ */ new Map();
|
||||
for (const entryType of entryTypes) {
|
||||
for (const ext of entryType.extensions) {
|
||||
map.set(ext, entryType);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
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,
|
||||
viteServer
|
||||
}) {
|
||||
const contentPaths = getContentPaths(settings.config, fs);
|
||||
let unparsedConfig;
|
||||
if (!contentPaths.config.exists) {
|
||||
return void 0;
|
||||
}
|
||||
const configPathname = fileURLToPath(contentPaths.config.url);
|
||||
unparsedConfig = await viteServer.ssrLoadModule(configPathname);
|
||||
const config = contentConfigParser.safeParse(unparsedConfig);
|
||||
if (config.success) {
|
||||
const hasher = await xxhash();
|
||||
const digest = await 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}
|
||||
`
|
||||
);
|
||||
if (settings.config.experimental.liveContentCollections) {
|
||||
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 (settings.config.legacy.collections) {
|
||||
return config;
|
||||
}
|
||||
const contentDir = new URL("./content/", settings.config.srcDir);
|
||||
const collections = config?.collections ?? {};
|
||||
const contentExts = getContentEntryExts(settings);
|
||||
const dataExts = getDataEntryExts(settings);
|
||||
const contentPattern = globWithUnderscoresIgnored("", contentExts);
|
||||
const dataPattern = globWithUnderscoresIgnored("", dataExts);
|
||||
let usesContentLayer = false;
|
||||
for (const collectionName of Object.keys(collections)) {
|
||||
if (collections[collectionName]?.type === "content_layer" || collections[collectionName]?.type === "live") {
|
||||
usesContentLayer = true;
|
||||
continue;
|
||||
}
|
||||
const isDataCollection = collections[collectionName]?.type === "data";
|
||||
const base = new URL(`${collectionName}/`, contentDir);
|
||||
const _legacy = !isDataCollection || void 0;
|
||||
collections[collectionName] = {
|
||||
...collections[collectionName],
|
||||
type: "content_layer",
|
||||
_legacy,
|
||||
loader: glob({
|
||||
base,
|
||||
pattern: isDataCollection ? dataPattern : contentPattern,
|
||||
_legacy,
|
||||
// Legacy data collections IDs aren't slugified
|
||||
generateId: isDataCollection ? ({ entry }) => getDataEntryId({
|
||||
entry: new URL(entry, base),
|
||||
collection: collectionName,
|
||||
contentDir
|
||||
}) : void 0
|
||||
// Zod weirdness has trouble with typing the args to the load function
|
||||
})
|
||||
};
|
||||
}
|
||||
if (!usesContentLayer && fs.existsSync(contentDir)) {
|
||||
const orphanedCollections = [];
|
||||
for (const entry of await fs.promises.readdir(contentDir, { withFileTypes: true })) {
|
||||
const collectionName = entry.name;
|
||||
if (["_", "."].includes(collectionName.at(0) ?? "")) {
|
||||
continue;
|
||||
}
|
||||
if (entry.isDirectory() && !(collectionName in collections)) {
|
||||
orphanedCollections.push(collectionName);
|
||||
const base = new URL(`${collectionName}/`, contentDir);
|
||||
collections[collectionName] = {
|
||||
type: "content_layer",
|
||||
loader: glob({
|
||||
base,
|
||||
pattern: contentPattern,
|
||||
_legacy: true
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
||||
if (orphanedCollections.length > 0) {
|
||||
console.warn(
|
||||
`
|
||||
Auto-generating collections for folders in "src/content/" that are not defined as collections.
|
||||
This is deprecated, so you should define these collections yourself in "src/content.config.ts".
|
||||
The following collections have been auto-generated: ${orphanedCollections.map((name) => 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,
|
||||
legacy,
|
||||
root,
|
||||
experimental
|
||||
}, fs = fsMod) {
|
||||
const configStats = searchConfig(fs, srcDir, legacy?.collections);
|
||||
const liveConfigStats = experimental?.liveContentCollections ? searchLiveConfig(fs, srcDir) : { exists: false, url: new URL("./", srcDir) };
|
||||
const pkgBase = new URL("../../", import.meta.url);
|
||||
return {
|
||||
root: new URL("./", root),
|
||||
contentDir: new URL("./content/", srcDir),
|
||||
assetsDir: new URL("./assets/", srcDir),
|
||||
typesTemplate: new URL("templates/content/types.d.ts", pkgBase),
|
||||
virtualModTemplate: new URL("templates/content/module.mjs", pkgBase),
|
||||
config: configStats,
|
||||
liveConfig: liveConfigStats
|
||||
};
|
||||
}
|
||||
function searchConfig(fs, srcDir, legacy) {
|
||||
const paths = [
|
||||
...legacy ? [] : ["content.config.mjs", "content.config.js", "content.config.mts", "content.config.ts"],
|
||||
"content/config.mjs",
|
||||
"content/config.js",
|
||||
"content/config.mts",
|
||||
"content/config.ts"
|
||||
];
|
||||
return search(fs, srcDir, paths);
|
||||
}
|
||||
function searchLiveConfig(fs, srcDir) {
|
||||
const paths = ["live.config.mjs", "live.config.js", "live.config.mts", "live.config.ts"];
|
||||
return search(fs, srcDir, paths);
|
||||
}
|
||||
function search(fs, srcDir, paths) {
|
||||
const urls = paths.map((p) => new URL(`./${p}`, srcDir));
|
||||
for (const file of urls) {
|
||||
if (fs.existsSync(file)) {
|
||||
return { exists: true, url: file };
|
||||
}
|
||||
}
|
||||
return { exists: false, url: 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 hasAssetPropagationFlag(id) {
|
||||
try {
|
||||
return new URL(id, "file://").searchParams.has(PROPAGATED_ASSET_FLAG);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
function globWithUnderscoresIgnored(relContentDir, exts) {
|
||||
const extGlob = getExtGlob(exts);
|
||||
const contentDir = relContentDir.length > 0 ? appendForwardSlash(relContentDir) : relContentDir;
|
||||
return [
|
||||
`${contentDir}**/*${extGlob}`,
|
||||
`!${contentDir}**/_*/**/*${extGlob}`,
|
||||
`!${contentDir}**/_*${extGlob}`
|
||||
];
|
||||
}
|
||||
function posixifyPath(filePath) {
|
||||
return filePath.split(path.sep).join("/");
|
||||
}
|
||||
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,
|
||||
getEntryDataAndImages,
|
||||
getEntrySlug,
|
||||
getEntryType,
|
||||
getExtGlob,
|
||||
getSymlinkedContentCollections,
|
||||
globWithUnderscoresIgnored,
|
||||
globalContentConfigObserver,
|
||||
hasAssetPropagationFlag,
|
||||
hasContentFlag,
|
||||
isDeferredModule,
|
||||
loaderReturnSchema,
|
||||
parseEntrySlug,
|
||||
posixRelative,
|
||||
reloadContentConfigObserver,
|
||||
reverseSymlink,
|
||||
safeParseFrontmatter,
|
||||
safeStringify
|
||||
};
|
||||
9
node_modules/astro/dist/content/vite-plugin-content-assets.d.ts
generated
vendored
Normal file
9
node_modules/astro/dist/content/vite-plugin-content-assets.d.ts
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { Plugin } from 'vite';
|
||||
import type { BuildInternals } from '../core/build/internal.js';
|
||||
import type { AstroBuildPlugin } from '../core/build/plugin.js';
|
||||
import type { StaticBuildOptions } from '../core/build/types.js';
|
||||
import type { AstroSettings } from '../types/astro.js';
|
||||
export declare function astroContentAssetPropagationPlugin({ settings, }: {
|
||||
settings: AstroSettings;
|
||||
}): Plugin;
|
||||
export declare function astroConfigBuildPlugin(options: StaticBuildOptions, internals: BuildInternals): AstroBuildPlugin;
|
||||
148
node_modules/astro/dist/content/vite-plugin-content-assets.js
generated
vendored
Normal file
148
node_modules/astro/dist/content/vite-plugin-content-assets.js
generated
vendored
Normal file
@@ -0,0 +1,148 @@
|
||||
import { extname } from "node:path";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
import { getAssetsPrefix } from "../assets/utils/getAssetsPrefix.js";
|
||||
import { AstroError, AstroErrorData } from "../core/errors/index.js";
|
||||
import { createViteLoader } from "../core/module-loader/vite.js";
|
||||
import { joinPaths, prependForwardSlash } from "../core/path.js";
|
||||
import { getStylesForURL } from "../vite-plugin-astro-server/css.js";
|
||||
import {
|
||||
CONTENT_IMAGE_FLAG,
|
||||
CONTENT_RENDER_FLAG,
|
||||
LINKS_PLACEHOLDER,
|
||||
PROPAGATED_ASSET_FLAG,
|
||||
STYLES_PLACEHOLDER
|
||||
} from "./consts.js";
|
||||
import { hasContentFlag } from "./utils.js";
|
||||
function astroContentAssetPropagationPlugin({
|
||||
settings
|
||||
}) {
|
||||
let devModuleLoader;
|
||||
return {
|
||||
name: "astro:content-asset-propagation",
|
||||
enforce: "pre",
|
||||
async resolveId(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)
|
||||
});
|
||||
}
|
||||
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) {
|
||||
devModuleLoader = createViteLoader(server);
|
||||
},
|
||||
async transform(_, id, options) {
|
||||
if (hasContentFlag(id, PROPAGATED_ASSET_FLAG)) {
|
||||
const basePath = id.split("?")[0];
|
||||
let stringifiedLinks, stringifiedStyles;
|
||||
if (options?.ssr && devModuleLoader) {
|
||||
if (!devModuleLoader.getModuleById(basePath)?.ssrModule) {
|
||||
await devModuleLoader.import(basePath);
|
||||
}
|
||||
const {
|
||||
styles,
|
||||
urls,
|
||||
crawledFiles: styleCrawledFiles
|
||||
} = await getStylesForURL(pathToFileURL(basePath), devModuleLoader);
|
||||
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: "" } };
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
function astroConfigBuildPlugin(options, internals) {
|
||||
return {
|
||||
targets: ["server"],
|
||||
hooks: {
|
||||
"build:post": ({ ssrOutputs, mutate }) => {
|
||||
const outputs = ssrOutputs.flatMap((o) => o.output);
|
||||
const prependBase = (src) => {
|
||||
const { assetsPrefix } = options.settings.config.build;
|
||||
if (assetsPrefix) {
|
||||
const fileExtension = extname(src);
|
||||
const pf = getAssetsPrefix(fileExtension, assetsPrefix);
|
||||
return joinPaths(pf, src);
|
||||
} else {
|
||||
return prependForwardSlash(joinPaths(options.settings.config.base, src));
|
||||
}
|
||||
};
|
||||
for (const chunk of outputs) {
|
||||
if (chunk.type === "chunk" && chunk.code.includes(LINKS_PLACEHOLDER)) {
|
||||
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") entryLinks.add(value.src);
|
||||
}
|
||||
}
|
||||
}
|
||||
let newCode = chunk.code;
|
||||
if (entryStyles.size) {
|
||||
newCode = newCode.replace(
|
||||
JSON.stringify(STYLES_PLACEHOLDER),
|
||||
JSON.stringify(Array.from(entryStyles))
|
||||
);
|
||||
} else {
|
||||
newCode = newCode.replace(JSON.stringify(STYLES_PLACEHOLDER), "[]");
|
||||
}
|
||||
if (entryLinks.size) {
|
||||
newCode = newCode.replace(
|
||||
JSON.stringify(LINKS_PLACEHOLDER),
|
||||
JSON.stringify(Array.from(entryLinks).map(prependBase))
|
||||
);
|
||||
} else {
|
||||
newCode = newCode.replace(JSON.stringify(LINKS_PLACEHOLDER), "[]");
|
||||
}
|
||||
mutate(chunk, ["server"], newCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
export {
|
||||
astroConfigBuildPlugin,
|
||||
astroContentAssetPropagationPlugin
|
||||
};
|
||||
9
node_modules/astro/dist/content/vite-plugin-content-imports.d.ts
generated
vendored
Normal file
9
node_modules/astro/dist/content/vite-plugin-content-imports.d.ts
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import type fsMod from 'node:fs';
|
||||
import type { Plugin } from 'vite';
|
||||
import type { Logger } 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: Logger;
|
||||
}): Plugin[];
|
||||
309
node_modules/astro/dist/content/vite-plugin-content-imports.js
generated
vendored
Normal file
309
node_modules/astro/dist/content/vite-plugin-content-imports.js
generated
vendored
Normal file
@@ -0,0 +1,309 @@
|
||||
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";
|
||||
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);
|
||||
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 });
|
||||
},
|
||||
async transform(_, 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 entryType = getEntryType(entry, contentPaths, contentEntryExts, dataEntryExts);
|
||||
if (!COLLECTION_TYPES_TO_INVALIDATE_ON.includes(entryType)) return;
|
||||
if (entryType === "content" || entryType === "data") {
|
||||
await reloadContentConfigObserver({ fs, settings, viteServer });
|
||||
}
|
||||
for (const modUrl of viteServer.moduleGraph.urlToModuleMap.keys()) {
|
||||
if (hasContentFlag(modUrl, CONTENT_FLAG) || hasContentFlag(modUrl, DATA_FLAG) || Boolean(getContentRendererByViteId(modUrl, settings))) {
|
||||
try {
|
||||
const mod = await viteServer.moduleGraph.getModuleByUrl(modUrl);
|
||||
if (mod) {
|
||||
viteServer.moduleGraph.invalidateModule(mod);
|
||||
}
|
||||
} catch (e) {
|
||||
if (e.code === "ERR_CLOSED_SERVER") break;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
if (settings.contentEntryTypes.some((t) => t.getRenderModule)) {
|
||||
plugins.push({
|
||||
name: "astro:content-render-imports",
|
||||
async transform(contents, viteId) {
|
||||
const contentRenderer = getContentRendererByViteId(viteId, settings);
|
||||
if (!contentRenderer) return;
|
||||
const fileId = viteId.split("?")[0];
|
||||
return contentRenderer.bind(this)({ viteId, contents, fileUrl: pathToFileURL(fileId) });
|
||||
}
|
||||
});
|
||||
}
|
||||
return plugins;
|
||||
}
|
||||
async function getContentEntryModule(params) {
|
||||
const { fileId, contentDir, pluginContext } = params;
|
||||
const { collectionConfig, entryConfig, entry, rawContents, collection } = await getEntryModuleBaseInfo(params);
|
||||
const {
|
||||
rawData,
|
||||
data: unvalidatedData,
|
||||
body,
|
||||
slug: frontmatterSlug
|
||||
} = await entryConfig.getEntryInfo({
|
||||
fileUrl: pathToFileURL(fileId),
|
||||
contents: rawContents
|
||||
});
|
||||
const _internal = { filePath: fileId, rawData };
|
||||
const { id, slug: generatedSlug } = getContentEntryIdAndSlug({ entry, contentDir, collection });
|
||||
const slug = parseEntrySlug({
|
||||
id,
|
||||
collection,
|
||||
generatedSlug,
|
||||
frontmatterSlug
|
||||
});
|
||||
const data = collectionConfig ? await getEntryData(
|
||||
{ id, collection, _internal, unvalidatedData },
|
||||
collectionConfig,
|
||||
params.shouldEmitFile,
|
||||
// FUTURE: Remove in this in v6
|
||||
id.endsWith(".svg"),
|
||||
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,
|
||||
// FUTURE: Remove in this in v6
|
||||
id.endsWith(".svg"),
|
||||
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
|
||||
};
|
||||
9
node_modules/astro/dist/content/vite-plugin-content-virtual-mod.d.ts
generated
vendored
Normal file
9
node_modules/astro/dist/content/vite-plugin-content-virtual-mod.d.ts
generated
vendored
Normal 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 {};
|
||||
305
node_modules/astro/dist/content/vite-plugin-content-virtual-mod.js
generated
vendored
Normal file
305
node_modules/astro/dist/content/vite-plugin-content-virtual-mod.js
generated
vendored
Normal file
@@ -0,0 +1,305 @@
|
||||
import nodeFs from "node:fs";
|
||||
import { extname } from "node:path";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
import { dataToEsm } from "@rollup/pluginutils";
|
||||
import pLimit from "p-limit";
|
||||
import { glob } from "tinyglobby";
|
||||
import { normalizePath } from "vite";
|
||||
import { AstroError, AstroErrorData } from "../core/errors/index.js";
|
||||
import { rootRelativePath } from "../core/viteUtils.js";
|
||||
import { createDefaultAstroMetadata } from "../vite-plugin-astro/metadata.js";
|
||||
import {
|
||||
ASSET_IMPORTS_FILE,
|
||||
ASSET_IMPORTS_RESOLVED_STUB_ID,
|
||||
ASSET_IMPORTS_VIRTUAL_ID,
|
||||
CONTENT_FLAG,
|
||||
CONTENT_RENDER_FLAG,
|
||||
DATA_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 {
|
||||
getContentEntryIdAndSlug,
|
||||
getContentPaths,
|
||||
getDataEntryExts,
|
||||
getDataEntryId,
|
||||
getEntryCollectionName,
|
||||
getEntryConfigByExtMap,
|
||||
getEntrySlug,
|
||||
getEntryType,
|
||||
getExtGlob,
|
||||
globWithUnderscoresIgnored,
|
||||
isDeferredModule
|
||||
} from "./utils.js";
|
||||
function invalidateDataStore(server) {
|
||||
const module = server.moduleGraph.getModuleById(RESOLVED_DATA_STORE_VIRTUAL_ID);
|
||||
if (module) {
|
||||
server.moduleGraph.invalidateModule(module);
|
||||
}
|
||||
server.ws.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);
|
||||
if (contentPaths.liveConfig.exists) {
|
||||
liveConfig = normalizePath(fileURLToPath(contentPaths.liveConfig.url));
|
||||
}
|
||||
},
|
||||
buildStart() {
|
||||
if (devServer) {
|
||||
devServer.watcher.add(fileURLToPath(dataStoreFile));
|
||||
invalidateDataStore(devServer);
|
||||
}
|
||||
},
|
||||
async resolveId(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;
|
||||
}
|
||||
},
|
||||
async load(id, args) {
|
||||
if (id === RESOLVED_VIRTUAL_MODULE_ID) {
|
||||
const lookupMap = settings.config.legacy.collections ? await generateLookupMap({
|
||||
settings,
|
||||
fs
|
||||
}) : {};
|
||||
const isClient = !args?.ssr;
|
||||
const code = await generateContentEntryFile({
|
||||
settings,
|
||||
fs,
|
||||
lookupMap,
|
||||
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,
|
||||
lookupMap,
|
||||
isClient
|
||||
}) {
|
||||
const contentPaths = getContentPaths(settings.config);
|
||||
const relContentDir = rootRelativePath(settings.config.root, contentPaths.contentDir);
|
||||
let contentEntryGlobResult = '""';
|
||||
let dataEntryGlobResult = '""';
|
||||
let renderEntryGlobResult = '""';
|
||||
if (settings.config.legacy.collections) {
|
||||
const contentEntryConfigByExt = getEntryConfigByExtMap(settings.contentEntryTypes);
|
||||
const contentEntryExts = [...contentEntryConfigByExt.keys()];
|
||||
const dataEntryExts = getDataEntryExts(settings);
|
||||
const createGlob = (value, flag) => `import.meta.glob(${JSON.stringify(value)}, { query: { ${flag}: true } })`;
|
||||
contentEntryGlobResult = createGlob(
|
||||
globWithUnderscoresIgnored(relContentDir, contentEntryExts),
|
||||
CONTENT_FLAG
|
||||
);
|
||||
dataEntryGlobResult = createGlob(
|
||||
globWithUnderscoresIgnored(relContentDir, dataEntryExts),
|
||||
DATA_FLAG
|
||||
);
|
||||
renderEntryGlobResult = createGlob(
|
||||
globWithUnderscoresIgnored(relContentDir, contentEntryExts),
|
||||
CONTENT_RENDER_FLAG
|
||||
);
|
||||
}
|
||||
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("'@@CONTENT_ENTRY_GLOB_PATH@@'", contentEntryGlobResult).replace("'@@DATA_ENTRY_GLOB_PATH@@'", dataEntryGlobResult).replace("'@@RENDER_ENTRY_GLOB_PATH@@'", renderEntryGlobResult).replace("/* @@LOOKUP_MAP_ASSIGNMENT@@ */", `lookupMap = ${JSON.stringify(lookupMap)};`).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;
|
||||
}
|
||||
async function generateLookupMap({ settings, fs }) {
|
||||
const { root } = settings.config;
|
||||
const contentPaths = getContentPaths(settings.config);
|
||||
const relContentDir = rootRelativePath(root, contentPaths.contentDir, false);
|
||||
const contentEntryConfigByExt = getEntryConfigByExtMap(settings.contentEntryTypes);
|
||||
const dataEntryExts = getDataEntryExts(settings);
|
||||
const { contentDir } = contentPaths;
|
||||
const contentEntryExts = [...contentEntryConfigByExt.keys()];
|
||||
let lookupMap = {};
|
||||
const contentGlob = await glob(
|
||||
`${relContentDir}**/*${getExtGlob([...dataEntryExts, ...contentEntryExts])}`,
|
||||
{
|
||||
absolute: true,
|
||||
cwd: fileURLToPath(root),
|
||||
expandDirectories: false
|
||||
}
|
||||
);
|
||||
const limit = pLimit(10);
|
||||
const promises = [];
|
||||
for (const filePath of contentGlob) {
|
||||
promises.push(
|
||||
limit(async () => {
|
||||
const entryType = getEntryType(filePath, contentPaths, contentEntryExts, dataEntryExts);
|
||||
if (entryType !== "content" && entryType !== "data") return;
|
||||
const collection = getEntryCollectionName({ contentDir, entry: pathToFileURL(filePath) });
|
||||
if (!collection) throw UnexpectedLookupMapError;
|
||||
if (lookupMap[collection]?.type && lookupMap[collection].type !== entryType) {
|
||||
throw new AstroError({
|
||||
...AstroErrorData.MixedContentDataCollectionError,
|
||||
message: AstroErrorData.MixedContentDataCollectionError.message(collection)
|
||||
});
|
||||
}
|
||||
if (entryType === "content") {
|
||||
const contentEntryType = contentEntryConfigByExt.get(extname(filePath));
|
||||
if (!contentEntryType) throw UnexpectedLookupMapError;
|
||||
const { id, slug: generatedSlug } = getContentEntryIdAndSlug({
|
||||
entry: pathToFileURL(filePath),
|
||||
contentDir,
|
||||
collection
|
||||
});
|
||||
const slug = await getEntrySlug({
|
||||
id,
|
||||
collection,
|
||||
generatedSlug,
|
||||
fs,
|
||||
fileUrl: pathToFileURL(filePath),
|
||||
contentEntryType
|
||||
});
|
||||
if (lookupMap[collection]?.entries?.[slug]) {
|
||||
throw new AstroError({
|
||||
...AstroErrorData.DuplicateContentEntrySlugError,
|
||||
message: AstroErrorData.DuplicateContentEntrySlugError.message(
|
||||
collection,
|
||||
slug,
|
||||
lookupMap[collection].entries[slug],
|
||||
rootRelativePath(root, filePath)
|
||||
),
|
||||
hint: slug !== generatedSlug ? `Check the \`slug\` frontmatter property in **${id}**.` : void 0
|
||||
});
|
||||
}
|
||||
lookupMap[collection] ??= { type: "content", entries: {} };
|
||||
lookupMap[collection].entries[slug] = rootRelativePath(root, filePath);
|
||||
} else {
|
||||
const id = getDataEntryId({ entry: pathToFileURL(filePath), contentDir, collection });
|
||||
lookupMap[collection] ??= { type: "data", entries: {} };
|
||||
lookupMap[collection].entries[id] = rootRelativePath(root, filePath);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
await Promise.all(promises);
|
||||
return lookupMap;
|
||||
}
|
||||
const UnexpectedLookupMapError = new AstroError({
|
||||
...AstroErrorData.UnknownContentCollectionError,
|
||||
message: `Unexpected error while parsing content entry IDs and slugs.`
|
||||
});
|
||||
export {
|
||||
astroContentVirtualModPlugin
|
||||
};
|
||||
5
node_modules/astro/dist/content/watcher.d.ts
generated
vendored
Normal file
5
node_modules/astro/dist/content/watcher.d.ts
generated
vendored
Normal 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
38
node_modules/astro/dist/content/watcher.js
generated
vendored
Normal 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
|
||||
};
|
||||
Reference in New Issue
Block a user