diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b3b258e..92a20606 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ - [#696](https://github.com/estruyf/vscode-front-matter/issues/696): Close the local server terminal on restart - [#699](https://github.com/estruyf/vscode-front-matter/issues/699): Changing border theme variable for the dashboard header - [#703](https://github.com/estruyf/vscode-front-matter/issues/703): Fix retrieval of Astro Collections for `pnpm` projects +- [#704](https://github.com/estruyf/vscode-front-matter/issues/704): Fix `zod` schema script for optional fields ## [9.3.1] - 2023-10-27 diff --git a/src/constants/ContentType.ts b/src/constants/ContentType.ts index 258feb09..c8880ecd 100644 --- a/src/constants/ContentType.ts +++ b/src/constants/ContentType.ts @@ -3,7 +3,7 @@ import { ContentType } from './../models/PanelSettings'; export const DEFAULT_CONTENT_TYPE_NAME = 'default'; export const DEFAULT_CONTENT_TYPE: ContentType = { - name: 'default', + name: DEFAULT_CONTENT_TYPE_NAME, pageBundle: false, previewPath: null, fields: [ diff --git a/src/dashboardWebView/components/Steps/SelectItem.tsx b/src/dashboardWebView/components/Steps/SelectItem.tsx index 0adbc032..05ae5d2b 100644 --- a/src/dashboardWebView/components/Steps/SelectItem.tsx +++ b/src/dashboardWebView/components/Steps/SelectItem.tsx @@ -4,7 +4,6 @@ import { CheckCircleIcon as CheckCircleIconSolid, PlusCircleIcon as PlusCircleIc export interface ISelectItemProps { title: string; - icon?: "add" | "select"; buttonTitle: string; isSelected: boolean; disabled?: boolean; @@ -13,7 +12,6 @@ export interface ISelectItemProps { export const SelectItem: React.FunctionComponent = ({ title, - icon = "select", buttonTitle, isSelected, disabled, @@ -30,17 +28,9 @@ export const SelectItem: React.FunctionComponent = ({ disabled={disabled} > {isSelected ? ( - icon === "add" ? ( - - ) : ( - - ) + ) : ( - icon === "add" ? ( - - ) : ( - - ) + )} {title} diff --git a/src/listeners/dashboard/SsgListener.ts b/src/listeners/dashboard/SsgListener.ts index 29a3ce1a..49d86b30 100644 --- a/src/listeners/dashboard/SsgListener.ts +++ b/src/listeners/dashboard/SsgListener.ts @@ -5,7 +5,11 @@ import { BaseListener } from './BaseListener'; import { exec } from 'child_process'; import { Extension, Logger, Settings } from '../../helpers'; import { Folders } from '../../commands'; -import { SETTING_TAXONOMY_CONTENT_TYPES, SsgScripts } from '../../constants'; +import { + DEFAULT_CONTENT_TYPE_NAME, + SETTING_TAXONOMY_CONTENT_TYPES, + SsgScripts +} from '../../constants'; import { SettingsListener } from './SettingsListener'; import { Terminal } from '../../services'; import { existsAsync, readFileAsync } from '../../utils'; @@ -58,7 +62,10 @@ export class SsgListener extends BaseListener { } } - const contentTypes = Settings.get(SETTING_TAXONOMY_CONTENT_TYPES) || []; + let contentTypes = Settings.get(SETTING_TAXONOMY_CONTENT_TYPES) || []; + + // Filter out the default content type + contentTypes = contentTypes.filter((ct) => ct.name !== DEFAULT_CONTENT_TYPE_NAME); if (contentTypes.find((ct) => ct.name === collection.name)) { SsgListener.sendRequest(command as any, requestId, {}); @@ -150,7 +157,7 @@ export class SsgListener extends BaseListener { // Update the vite reference, as it is not a direct dependency of the project let scriptContents = await readFileAsync(scriptPath.fsPath, 'utf8'); - scriptContents = scriptContents.replace(`"vite"`, `"${vitePath}"`); + scriptContents = scriptContents.replace(`'vite'`, `'${vitePath}'`); await workspace.fs.writeFile(tempScriptPath, Buffer.from(scriptContents, 'utf8')); } } else { @@ -209,16 +216,35 @@ export class SsgListener extends BaseListener { } as Field; break; case 'ZodBoolean': - ctField = { - name: field.name, - type: 'boolean' - } as Field; + if (field.name === 'published') { + ctField = { + name: field.name, + type: 'draft' + } as Field; + } else { + ctField = { + name: field.name, + type: 'boolean' + } as Field; + } break; case 'ZodArray': - ctField = { - name: field.name, - type: 'list' - } as Field; + if (field.name === 'tags') { + ctField = { + name: field.name, + type: 'tags' + } as Field; + } else if (field.name === 'categories') { + ctField = { + name: field.name, + type: 'categories' + } as Field; + } else { + ctField = { + name: field.name, + type: 'list' + } as Field; + } break; case 'ZodEnum': ctField = { @@ -227,6 +253,7 @@ export class SsgListener extends BaseListener { choices: field.options || [] } as Field; break; + case 'datetime': case 'ZodDate': ctField = { name: field.name, diff --git a/src/models/AstroCollections.ts b/src/models/AstroCollections.ts index f07b9c3f..b91fcc75 100644 --- a/src/models/AstroCollections.ts +++ b/src/models/AstroCollections.ts @@ -14,6 +14,7 @@ export interface AstroField { | 'ZodEnum' | 'ZodDate' | 'ZodObject' + | 'datetime' | 'email' | 'url' | 'image'; diff --git a/ssg-scripts/astro.collections.mjs b/ssg-scripts/astro.collections.mjs index f3ee8970..717270c6 100644 --- a/ssg-scripts/astro.collections.mjs +++ b/ssg-scripts/astro.collections.mjs @@ -1,32 +1,35 @@ -import { writeFileSync } from "fs"; -import { join } from "path"; -import { createServer } from "vite"; -import zod from "astro/zod"; +import { writeFileSync } from 'fs'; +import { join } from 'path'; +import { createServer } from 'vite'; +import zod from 'astro/zod'; -const { - ZodDefault, - ZodObject, - ZodOptional, - ZodString, - ZodEffects, - ZodEnum -} = zod; +const { ZodDefault, ZodObject, ZodOptional, ZodString, ZodEffects, ZodEnum, ZodUnion } = zod; /** * Process the Zod field - * @param {ZodTypeAny} field - * @param {string} defaultValue - * @returns + * @param {ZodTypeAny} field + * @param {boolean} isOptional + * @param {string} defaultValue + * @returns */ -function getField(field, defaultValue = undefined) { - let isOptional = false; - +function getField(field, isOptional = false, defaultValue = undefined) { // Handle various type transformations and assignments if (field instanceof ZodOptional) { isOptional = true; const type = field.unwrap(); - return getField(type, isOptional, defaultValue) + return getField(type, isOptional, defaultValue); + } + + if (field instanceof ZodEffects) { + const type = field.sourceType(); + return getField(type, isOptional, defaultValue); + } + + if (field instanceof ZodUnion) { + const types = field._def.options; + const type = types[0]; + return getField(type, isOptional, defaultValue); } if (field instanceof ZodDefault) { @@ -35,43 +38,43 @@ function getField(field, defaultValue = undefined) { defaultValue = field.parse(undefined); // https://github.com/sachinraja/zod-to-ts/blob/main/src/index.ts const type = field._def.innerType; - return getField(type, isOptional, defaultValue) + return getField(type, isOptional, defaultValue); } return { type: field, isOptional, defaultValue - } + }; } /** * Generate the field information - * @param {string} name - * @param {ZodTypeAny} type - * @returns + * @param {string} name + * @param {ZodTypeAny} type + * @returns */ function generateFieldInfo(name, type) { let description = type.description; let defaultValue = undefined; - const { - type: fieldType, - isOptional: isFieldOptional, - defaultValue: fieldDefaultValue - } = getField(type, defaultValue); + const { + type: fieldType, + isOptional: isFieldOptional, + defaultValue: fieldDefaultValue + } = getField(type, false, defaultValue); const fieldInfo = { name: name, description: description, defaultValue: fieldDefaultValue, type: fieldType._def.typeName, - required: !isFieldOptional, + required: !isFieldOptional }; if (fieldType instanceof ZodObject) { const subFields = extractFieldInfoFromShape(fieldType); - + fieldInfo.fields = subFields; } @@ -79,6 +82,10 @@ function generateFieldInfo(name, type) { fieldInfo.type = fieldType.sourceType().fmFieldType; } + if (fieldInfo.name.toLowerCase().includes('image')) { + fieldInfo.type = 'image'; + } + if (fieldType instanceof ZodEnum) { fieldInfo.options = fieldType.options; } @@ -96,8 +103,8 @@ function generateFieldInfo(name, type) { /** * Parse the scheme into an array of fields - * @param {ZodTypeAny} type - * @returns + * @param {ZodTypeAny} type + * @returns */ function extractFieldInfoFromShape(type) { if (type instanceof ZodOptional) { @@ -106,7 +113,7 @@ function extractFieldInfoFromShape(type) { if (!(type instanceof ZodObject)) { // Return an empty array if the type is not of the expected type - return []; + return []; } // Iterate through the shape properties @@ -121,8 +128,8 @@ function extractFieldInfoFromShape(type) { /** * Process each content collection - * @param {*} collections - * @returns + * @param {*} collections + * @returns */ function processCollection(collections) { if (!Array.isArray(collections)) { @@ -130,32 +137,35 @@ function processCollection(collections) { } return collections.map(([name, collection]) => { - const schema = typeof collection.schema === "function" ? collection.schema({ - image() { - const field = zod.string(); - field.fmFieldType = "image"; - return field; - } - }) : collection.schema; + const schema = + typeof collection.schema === 'function' + ? collection.schema({ + image() { + const field = zod.string(); + field.fmFieldType = 'image'; + return field; + } + }) + : collection.schema; return { name, - type: collection.type || "content", - fields: extractFieldInfoFromShape(schema), + type: collection.type || 'content', + fields: extractFieldInfoFromShape(schema) }; }); } /** * More info: https://vitejs.dev/guide/api-plugin.html#virtual-modules-convention - * @returns + * @returns */ const astroContentModulePlugin = () => { - const astroContent = "astro:content"; + const astroContent = 'astro:content'; const astroContentMarker = `\0${astroContent}`; return { - name: "astro-content-collections", + name: 'astro-content-collections', resolveId(importee) { if (importee === astroContent) { return astroContentMarker; @@ -174,7 +184,7 @@ const astroContentModulePlugin = () => { `; } } - } + }; }; /** @@ -182,8 +192,8 @@ const astroContentModulePlugin = () => { */ (async () => { const configPath = process.argv[2]; - if (!configPath || typeof configPath !== "string") { - console.log("No config path provided"); + if (!configPath || typeof configPath !== 'string') { + console.log('No config path provided'); process.exit(1); } @@ -195,22 +205,25 @@ const astroContentModulePlugin = () => { hmr: false }, optimizeDeps: { - disabled: true, + disabled: true }, - appType: "custom", + appType: 'custom', plugins: [astroContentModulePlugin()] }); try { // https://github.dev/withastro/astro/blob/defab70cb2a0c67d5e9153542490d2749046b151/packages/astro/src/content/utils.ts#L334 - const contentModule = await server.ssrLoadModule(configPath) + const contentModule = await server.ssrLoadModule(configPath); const collections = Object.entries(contentModule.collections ?? {}); const processedCollections = processCollection(collections); - writeFileSync(join(process.cwd(), `./.frontmatter/temp/astro.collections.json`), JSON.stringify(processedCollections)); - - console.log("Collections generated successfully"); + writeFileSync( + join(process.cwd(), `./.frontmatter/temp/astro.collections.json`), + JSON.stringify(processedCollections, null, 2) + ); + + console.log('Collections generated successfully'); process.exit(0); } catch (error) { console.log(error.message); @@ -218,4 +231,4 @@ const astroContentModulePlugin = () => { } finally { await server.close(); } -})(); \ No newline at end of file +})();