From 11354ad8e5c831e879b9f695ae331493bb805209 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Sat, 11 Nov 2023 10:12:21 -0800 Subject: [PATCH 1/3] #704 - Fix in zod optional check --- CHANGELOG.md | 1 + ssg-scripts/astro.collections.mjs | 114 +++++++++++++++--------------- 2 files changed, 57 insertions(+), 58 deletions(-) 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/ssg-scripts/astro.collections.mjs b/ssg-scripts/astro.collections.mjs index f3ee8970..947dd9dc 100644 --- a/ssg-scripts/astro.collections.mjs +++ b/ssg-scripts/astro.collections.mjs @@ -1,32 +1,24 @@ -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 } = 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 ZodDefault) { @@ -35,43 +27,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; } @@ -96,8 +88,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 +98,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 +113,8 @@ function extractFieldInfoFromShape(type) { /** * Process each content collection - * @param {*} collections - * @returns + * @param {*} collections + * @returns */ function processCollection(collections) { if (!Array.isArray(collections)) { @@ -130,32 +122,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 +169,7 @@ const astroContentModulePlugin = () => { `; } } - } + }; }; /** @@ -182,8 +177,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 +190,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) + ); + + console.log('Collections generated successfully'); process.exit(0); } catch (error) { console.log(error.message); @@ -218,4 +216,4 @@ const astroContentModulePlugin = () => { } finally { await server.close(); } -})(); \ No newline at end of file +})(); From e1f6c90fc0733b2eec9f400e2359fadc497904c1 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Sat, 11 Nov 2023 10:38:31 -0800 Subject: [PATCH 2/3] Enhancement: Better content type creation for Astro #704 --- ssg-scripts/astro.collections.mjs | 50 +++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/ssg-scripts/astro.collections.mjs b/ssg-scripts/astro.collections.mjs index 947dd9dc..b96d59a7 100644 --- a/ssg-scripts/astro.collections.mjs +++ b/ssg-scripts/astro.collections.mjs @@ -3,7 +3,8 @@ 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, ZodArray } = + zod; /** * Process the Zod field @@ -21,6 +22,17 @@ function getField(field, isOptional = false, defaultValue = undefined) { 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) { // https://github.com/colinhacks/zod/blob/master/README.md#default isOptional = true; @@ -61,6 +73,26 @@ function generateFieldInfo(name, type) { required: !isFieldOptional }; + switch (fieldInfo.type) { + case 'ZodString': + fieldInfo.type = 'string'; + break; + case 'ZodNumber': + fieldInfo.type = 'number'; + break; + case 'ZodBoolean': + fieldInfo.type = 'boolean'; + break; + case 'ZodDate': + fieldInfo.type = 'datetime'; + break; + case 'ZodArray': + fieldInfo.type = 'choice'; + break; + default: + break; + } + if (fieldType instanceof ZodObject) { const subFields = extractFieldInfoFromShape(fieldType); @@ -71,10 +103,24 @@ 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; } + if (fieldType instanceof ZodArray) { + if (fieldInfo.name === 'tags') { + fieldInfo.type = 'tags'; + } else if (fieldInfo.name === 'categories') { + fieldInfo.type = 'categories'; + } else { + fieldInfo.options = fieldType.options; + } + } + if (fieldType instanceof ZodString) { // https://github.com/StefanTerdell/zod-to-json-schema/blob/master/src/parsers/string.ts#L45 if (fieldType._def.checks && fieldType._def.checks.length > 0) { @@ -205,7 +251,7 @@ const astroContentModulePlugin = () => { writeFileSync( join(process.cwd(), `./.frontmatter/temp/astro.collections.json`), - JSON.stringify(processedCollections) + JSON.stringify(processedCollections, null, 2) ); console.log('Collections generated successfully'); From a8407920bc079e4e71a87b1bc093cc71f3734003 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Sat, 11 Nov 2023 11:19:11 -0800 Subject: [PATCH 3/3] Enhancement: Better content type creation for Astro #704 --- src/constants/ContentType.ts | 2 +- .../components/Steps/SelectItem.tsx | 14 +----- src/listeners/dashboard/SsgListener.ts | 49 ++++++++++++++----- src/models/AstroCollections.ts | 1 + ssg-scripts/astro.collections.mjs | 33 +------------ 5 files changed, 43 insertions(+), 56 deletions(-) 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 b96d59a7..717270c6 100644 --- a/ssg-scripts/astro.collections.mjs +++ b/ssg-scripts/astro.collections.mjs @@ -3,8 +3,7 @@ import { join } from 'path'; import { createServer } from 'vite'; import zod from 'astro/zod'; -const { ZodDefault, ZodObject, ZodOptional, ZodString, ZodEffects, ZodEnum, ZodUnion, ZodArray } = - zod; +const { ZodDefault, ZodObject, ZodOptional, ZodString, ZodEffects, ZodEnum, ZodUnion } = zod; /** * Process the Zod field @@ -73,26 +72,6 @@ function generateFieldInfo(name, type) { required: !isFieldOptional }; - switch (fieldInfo.type) { - case 'ZodString': - fieldInfo.type = 'string'; - break; - case 'ZodNumber': - fieldInfo.type = 'number'; - break; - case 'ZodBoolean': - fieldInfo.type = 'boolean'; - break; - case 'ZodDate': - fieldInfo.type = 'datetime'; - break; - case 'ZodArray': - fieldInfo.type = 'choice'; - break; - default: - break; - } - if (fieldType instanceof ZodObject) { const subFields = extractFieldInfoFromShape(fieldType); @@ -111,16 +90,6 @@ function generateFieldInfo(name, type) { fieldInfo.options = fieldType.options; } - if (fieldType instanceof ZodArray) { - if (fieldInfo.name === 'tags') { - fieldInfo.type = 'tags'; - } else if (fieldInfo.name === 'categories') { - fieldInfo.type = 'categories'; - } else { - fieldInfo.options = fieldType.options; - } - } - if (fieldType instanceof ZodString) { // https://github.com/StefanTerdell/zod-to-json-schema/blob/master/src/parsers/string.ts#L45 if (fieldType._def.checks && fieldType._def.checks.length > 0) {