UNPKG

astro-sveltia-cms-loader

Version:
312 lines (306 loc) 9.18 kB
import { writeFileSync, readFileSync } from 'node:fs'; import yaml from 'yaml'; import { z } from 'astro/zod'; import { glob } from 'astro/loaders'; import { parse, resolve } from 'node:path'; // src/integration.ts var DEFAULT_CONFIG_PATH = "./public/admin/config.yaml"; var createPlugin = (globalOptions = {}) => ({ name: "astro-sveltia-cms-loader", hooks: { "astro:config:setup": ({ config, createCodegenDir }) => { const codegenDir = createCodegenDir(); const globalOptionsFile = new URL("global-options.json", codegenDir); const newGlobalOptions = { configPath: DEFAULT_CONFIG_PATH, repositoryRoot: config.root, ...globalOptions }; writeFileSync(globalOptionsFile, JSON.stringify(newGlobalOptions), "utf-8"); } } }); var integration_default = createPlugin; var getGlobalOptions = () => { try { const globalOptionsFile = resolve( process.cwd(), ".astro/integrations/astro-sveltia-cms-loader/global-options.json" ); const globalOptions = JSON.parse(readFileSync(globalOptionsFile, "utf-8")); return globalOptions; } catch (error) { throw new Error(`Failed to load global options file: ${error}`); } }; var getCollectionGlobLoad = async (collection, context) => { const globalOptions = getGlobalOptions(); if (collection.files) { const filePaths = collection.files.map(({ file }) => file); const pattern = filePaths.length > 1 ? `{${filePaths.join(",")}}` : filePaths[0]; const base = new URL(collection.folder ?? ".", globalOptions.repositoryRoot); const generateId = ({ entry }) => parse(entry).name; return glob({ pattern, base, generateId }).load(context); } if (collection.folder) { const extension = collection.extension ?? "md"; const wildcardPath = (collection.path ?? "{{slug}}").replace(/\{\{[^}]+\}\}/g, "*"); const pattern = `${wildcardPath}.${extension}`; const base = new URL(collection.folder, globalOptions.repositoryRoot); return glob({ pattern, base }).load(context); } throw new Error(`Collection "${collection.name}" is missing "files" or "folder" property.`); }; var generateCodePropSchema = (field) => { let schema = z.string(); if (field.required !== false) { schema = schema.min(1, { message: "This field is required" }); return schema; } if (field.required === false) { return schema.optional(); } return schema; }; var codeWidget = (field) => { if (field.output_code_only) return generateCodePropSchema(field); return z.object({ code: generateCodePropSchema(field), lang: z.string() }); }; var colorWidget = (field) => { let schema = z.string(); if (field.required !== false) { schema = schema.min(1, { message: "This field is required" }); return schema; } if (field.required === false) { return schema.optional(); } return schema; }; var dateTimeWidget = (field) => { const schema = z.coerce.string(); if (field.required === false) { return schema.optional(); } return schema; }; var fileWidget = (field) => { let schema = z.string(); if (field.required !== false) { schema = schema.min(1, { message: "This field is required" }); return schema; } if (field.required === false) { return schema.optional(); } return schema; }; var imageWidget = (field) => { let schema = z.string(); if (field.required !== false) { schema = schema.min(1, { message: "This field is required" }); return schema; } if (field.required === false) { return schema.optional(); } return schema; }; var listWidget = (field) => { let schema = z.string().array(); if (field.field) schema = generateFieldSchema(field.field).array(); if (field.fields) schema = generateSchema(field.fields).array(); if (field.min) schema = schema.min(field.min); if (field.max) schema = schema.max(field.max); return field.required === false ? schema.optional() : schema.nonempty(); }; var numberWidget = (field) => { let schema = z.number(); if (field.value_type === "int") schema = schema.int(); if (field.min) schema = schema.min(field.min); if (field.max) schema = schema.max(field.max); if (field.step) schema = schema.step(field.step); if (field.required === false) { return schema.optional().nullable(); } return schema; }; // src/factory/widgets/object.ts var objectWidget = (field) => { const schema = generateSchema(field.fields); if (field.required === false) { return schema.optional().nullable(); } return schema; }; var selectWidget = (field) => { let schema = field.multiple ? z.array(z.string()) : z.string(); if (field.required !== false) { schema = schema.min(1, { message: "This field is required" }); return schema; } if (field.required === false) { return schema.optional(); } return schema; }; var stringWidget = (field) => { let schema = z.string(); if (field.type === "email") schema = schema.email(); if (field.type === "url") schema = schema.url(); if (field.pattern) { const [regex, message] = field.pattern; schema = schema.regex(new RegExp(regex), { message }); } if (field.minlength) schema = schema.min(field.minlength); if (field.maxlength) schema = schema.max(field.maxlength); if (field.required !== false) { schema = schema.min(1, { message: "This field is required" }); return schema; } if (field.required === false) { return schema.optional(); } return schema; }; var textWidget = (field) => { let schema = z.string(); if (field.pattern) { const [regex, message] = field.pattern; schema = schema.regex(new RegExp(regex), { message }); } if (field.minlength) schema = schema.min(field.minlength); if (field.maxlength) schema = schema.max(field.maxlength); if (field.required !== false) { schema = schema.min(1, { message: "This field is required" }); return schema; } if (field.required === false) { return schema.optional(); } return schema; }; // src/factory/index.ts var generateFieldSchema = (field) => { let schema = z.any(); switch (field.widget) { case "boolean": { schema = z.boolean(); break; } case "code": { schema = codeWidget(field); break; } case "color": { schema = colorWidget(field); break; } case "datetime": { schema = dateTimeWidget(field); break; } case "file": { schema = fileWidget(field); break; } case "image": { schema = imageWidget(field); break; } case "list": { schema = listWidget(field); break; } case "number": { schema = numberWidget(field); break; } case "object": { schema = objectWidget(field); break; } case "select": { schema = selectWidget(field); break; } case "string": { schema = stringWidget(field); break; } case "text": { schema = textWidget(field); break; } } return schema; }; var generateSchema = (fields) => { const schemaEntries = []; for (const field of fields) { if (field.name === "_discriminator") continue; schemaEntries.push([field.name, generateFieldSchema(field)]); } return z.object(Object.fromEntries(schemaEntries)); }; // src/loader/index.ts var getCollection = (options) => { const globalOptions = getGlobalOptions(); const configPath = options.overrides?.configPath || globalOptions?.configPath; const configFile = readFileSync(configPath, "utf-8"); const { collections: parsedCollections } = yaml.parse(configFile); const collection = parsedCollections.find(({ name }) => name === options.collectionName); if (!collection) { throw new Error(`Collection "${options.collectionName}" not found in CMS config`); } return collection; }; var collectionLoader = (options) => { const collection = getCollection(options); return { name: "sveltia-cms-collection-loader", load: async (context) => { try { const globLoad = options.overrides?.glob ? glob({ ...options.overrides.glob }).load(context) : await getCollectionGlobLoad(collection, context); return globLoad; } catch (error) { const errorMessage = error.message; context.logger.error(errorMessage); } }, schema: () => { const { folder, fields, files } = collection; let schema = void 0; if (folder) { schema = generateSchema(fields); } if (files) { const filesUnion = files.map((file) => { return z.object({ _discriminator: z.literal(file.name), ...generateSchema(file.fields).shape }); }); if (filesUnion.length === 0) throw new Error("No files found in collection"); if (filesUnion.length > 1) { schema = z.discriminatedUnion( "_discriminator", filesUnion ); } else { schema = filesUnion[0]; } } if (!schema) throw new Error("No schema generated"); return schema; } }; }; export { collectionLoader, integration_default as default };