From ae184609c8c0db82f712bfb687524a794a1452da Mon Sep 17 00:00:00 2001 From: bymyself Date: Tue, 12 Aug 2025 15:16:51 -0700 Subject: [PATCH] fix: Correct schema version from v2 to v1 and add schema generation docs - Fix version number in ComfyWorkflow1BaseType interface (was 2, should be 1) - Fix version literal in zComfyWorkflow1 schema (was 2, should be 1) - Update all comments referencing v2 to correctly state v1 - Fix validateComfyWorkflow to only check for version 1, not 2 - Add comprehensive schema generation documentation in docs/SCHEMA_GENERATION.md - Document SOP for schema updates and version bumping The PR was incorrectly trying to create a v2 schema when only documentation changes were made. Schema version should only increment for breaking changes. --- docs/SCHEMA_GENERATION.md | 160 +++++++++++++++++++++++++++++ src/schemas/comfyWorkflowSchema.ts | 108 ++++++++++++------- 2 files changed, 231 insertions(+), 37 deletions(-) create mode 100644 docs/SCHEMA_GENERATION.md diff --git a/docs/SCHEMA_GENERATION.md b/docs/SCHEMA_GENERATION.md new file mode 100644 index 000000000..bd5998d62 --- /dev/null +++ b/docs/SCHEMA_GENERATION.md @@ -0,0 +1,160 @@ +# ComfyUI Workflow Schema Generation + +This document describes the process for generating and maintaining JSON Schema definitions for ComfyUI workflows. + +## Overview + +ComfyUI uses **Zod schemas** in TypeScript to define workflow structure, which are converted to **JSON Schema** format for external consumption, documentation, and validation. + +### Schema Versions + +- **Version 0.4** (Legacy): Original workflow format with array-based links +- **Version 1.0** (Current): Modern format with object-based links, subgraphs, and reroutes + +## Schema Generation Process + +### Prerequisites + +1. Ensure all dependencies are installed: `npm install` +2. Verify TypeScript compilation: `npm run typecheck` + +### Command + +```bash +npm run json-schema +``` + +This runs the generation script: `scripts/generate-json-schema.ts` + +### Output + +Generated schemas are written to `./schemas/` directory: + +- `workflow-0_4.json` (~80KB) - Legacy workflow format +- `workflow-1_0.json` (~82KB) - Current workflow format +- `node-def-v1.json` - Node definition schema v1 +- `node-def-v2.json` - Node definition schema v2 + +**Note**: The `./schemas/` directory is gitignored and not committed to the repository. + +## When to Regenerate Schemas + +### Required Regeneration + +Run schema generation when: + +1. **Schema Changes**: Any modifications to files in `/src/schemas/` +2. **Breaking Changes**: Changes that affect data structure or validation +3. **New Features**: Adding new workflow capabilities (subgraphs, reroutes, etc.) +4. **Documentation Updates**: When schema descriptions change + +### Schema Version Bumping + +Only increment schema version for **breaking changes**: + +- Data structure changes (field renames, type changes) +- Required field additions +- Format changes that break backward compatibility + +**Non-breaking changes** (documentation, optional fields) should NOT bump the version. + +## Schema Sources + +### Primary Schemas (Zod-based) + +#### Workflow Schemas +- **File**: `src/schemas/comfyWorkflowSchema.ts` +- **Exports**: `zComfyWorkflow` (v0.4), `zComfyWorkflow1` (v1.0) +- **Purpose**: Defines workflow JSON structure for both legacy and modern formats + +#### Node Definition Schemas +- **Files**: + - `src/schemas/nodeDefSchema.ts` (v1) + - `src/schemas/nodeDef/nodeDefSchemaV2.ts` (v2) +- **Purpose**: Defines node definition structure for different versions + +### Generation Configuration + +The generation script uses: +- **Library**: `zod-to-json-schema` +- **Strategy**: `$refStrategy: 'none'` (inlines all references) +- **Output**: Formatted JSON with proper naming + +## Standard Operating Procedure (SOP) + +### For Schema Updates + +1. **Make Changes**: Modify Zod schemas in `/src/schemas/` +2. **Test Locally**: Ensure changes work with existing workflows +3. **Run Generation**: `npm run json-schema` +4. **Validate Output**: Check generated schemas for correctness +5. **Test Integration**: Verify schemas work with external tools +6. **Document Changes**: Update this document if process changes + +### For Version Bumps + +1. **Assess Breaking Changes**: Determine if changes are truly breaking +2. **Update Version**: Increment version number in schema definition +3. **Update Validation**: Modify `validateComfyWorkflow()` function +4. **Update Serialization**: Update LGraph serialization if needed +5. **Test Backward Compatibility**: Ensure old workflows still load +6. **Document Migration**: Provide migration guide if needed + +### For External Publishing + +**Current Status**: No automated publishing to docs.comfy.org exists yet. + +**Recommended Process**: +1. Generate schemas locally +2. Copy to appropriate documentation repository +3. Update documentation with new schema versions +4. Coordinate with docs team for publication + +## Troubleshooting + +### Common Issues + +#### Lodash Import Errors +**Error**: `SyntaxError: The requested module 'lodash' does not provide an export named 'clamp'` + +**Solution**: Use the simplified generation script that avoids litegraph imports: +```bash +npx tsx scripts/generate-json-schema-simple.ts +``` + +#### Recursive Reference Warnings +**Warning**: `Recursive reference detected... Defaulting to any` + +**Explanation**: This is expected for subgraph definitions and doesn't affect functionality. + +### Schema Validation Issues + +1. **Check Source Schema**: Verify Zod schema is valid +2. **Test TypeScript**: Run `npm run typecheck` +3. **Check Dependencies**: Ensure all imports resolve correctly +4. **Validate Output**: Test generated JSON Schema with online validators + +## Integration Points + +### External Documentation +- **docs.comfy.org**: Official ComfyUI documentation site +- **Workflow Spec**: https://docs.comfy.org/specs/workflow_json + +### Development Tools +- **IDE Support**: Generated schemas provide autocomplete and validation +- **API Validation**: External tools can validate workflow JSON +- **Documentation Generation**: Schemas document workflow structure + +## Future Improvements + +1. **Automated Publishing**: Set up CI/CD to publish schemas to docs.comfy.org +2. **Version Management**: Implement semantic versioning for schema changes +3. **Migration Tools**: Create utilities to migrate between schema versions +4. **Testing**: Add automated tests for schema generation and validation +5. **Documentation**: Generate human-readable documentation from schemas + +## References + +- **Zod Documentation**: https://zod.dev/ +- **JSON Schema Specification**: https://json-schema.org/ +- **ComfyUI Workflow Specification**: https://docs.comfy.org/specs/workflow_json \ No newline at end of file diff --git a/src/schemas/comfyWorkflowSchema.ts b/src/schemas/comfyWorkflowSchema.ts index 5171d41ec..ccd67c30a 100644 --- a/src/schemas/comfyWorkflowSchema.ts +++ b/src/schemas/comfyWorkflowSchema.ts @@ -4,7 +4,7 @@ import { fromZodError } from 'zod-validation-error' // GroupNode is hacking node id to be a string, so we need to allow that. // innerNode.id = `${this.node.id}:${i}` // Remove it after GroupNode is redesigned. -/** +/** * Node identifier that can be either a number or string. * Numeric IDs are standard, string IDs are used for GroupNodes. */ @@ -19,7 +19,7 @@ export const zNodeInputName = z export type NodeId = z.infer -/** +/** * Index of a slot on a node (input or output). * Can be number or string that parses to a number. */ @@ -38,7 +38,7 @@ export const zSlotIndex = z // TODO: Investigate usage of array and number as data type usage in custom nodes. // Known usage: // - https://github.com/rgthree/rgthree-comfy Context Big node is using array as type. -/** +/** * Data type for node inputs/outputs. Can be string, array of strings, or number. * Most common types are strings like 'IMAGE', 'LATENT', 'MODEL', etc. */ @@ -46,7 +46,7 @@ export const zDataType = z .union([z.string(), z.array(z.string()), z.number()]) .describe('Data type specification for node connections') -/** +/** * 2D position or size vector [x, y]. * Can be array tuple or object with numeric indices. */ @@ -71,7 +71,10 @@ const zModelFile = z /** Download URL for the model */ url: z.string().url().describe('Download URL for the model'), /** File hash for integrity verification */ - hash: z.string().optional().describe('File hash for integrity verification'), + hash: z + .string() + .optional() + .describe('File hash for integrity verification'), /** Hash algorithm type (e.g., 'sha256') */ hash_type: z.string().optional().describe('Hash algorithm type'), /** Directory where model should be stored */ @@ -336,7 +339,7 @@ export const zBaseExportableGraph = z.object({ subgraphs: z.array(zSubgraphInstance).optional() }) -/** +/** * ComfyUI Workflow JSON Schema version 0.4 (legacy). * This is the original workflow format used by ComfyUI. */ @@ -353,15 +356,26 @@ export const zComfyWorkflow = zBaseExportableGraph /** All nodes in the workflow */ nodes: z.array(zComfyNode).describe('All nodes in the workflow'), /** Node connections (legacy tuple format) */ - links: z.array(zComfyLink).describe('Node connections in legacy tuple format'), + links: z + .array(zComfyLink) + .describe('Node connections in legacy tuple format'), /** Floating links (unconnected endpoints) */ - floatingLinks: z.array(zComfyLinkObject).optional().describe('Floating links with unconnected endpoints'), + floatingLinks: z + .array(zComfyLinkObject) + .optional() + .describe('Floating links with unconnected endpoints'), /** Visual groupings of nodes */ groups: z.array(zGroup).optional().describe('Visual groupings of nodes'), /** Workflow configuration settings */ - config: zConfig.optional().nullable().describe('Workflow configuration settings'), + config: zConfig + .optional() + .nullable() + .describe('Workflow configuration settings'), /** Extra metadata and extensions */ - extra: zExtra.optional().nullable().describe('Extra metadata and extensions'), + extra: zExtra + .optional() + .nullable() + .describe('Extra metadata and extensions'), /** Schema version number */ version: z.number().describe('Schema version number (0.4)'), /** Required model files */ @@ -376,7 +390,7 @@ export const zComfyWorkflow = zBaseExportableGraph interface ComfyWorkflow1BaseType { id?: string revision?: number - version: 2 + version: 1 models?: z.infer[] state: z.infer } @@ -405,8 +419,8 @@ interface ComfyWorkflow1BaseOutput extends ComfyWorkflow1BaseType { } } -/** - * ComfyUI Workflow JSON Schema version 2 (current). +/** + * ComfyUI Workflow JSON Schema version 1 (current). * This is the modern workflow format with improved structure and features. */ export const zComfyWorkflow1 = zBaseExportableGraph @@ -414,11 +428,17 @@ export const zComfyWorkflow1 = zBaseExportableGraph /** Unique workflow identifier */ id: z.string().uuid().optional().describe('Unique workflow identifier'), /** Workflow revision number for tracking changes */ - revision: z.number().optional().describe('Workflow revision number for tracking changes'), - /** Schema version (always 2 for this format) */ - version: z.literal(2).describe('Schema version number (2)'), + revision: z + .number() + .optional() + .describe('Workflow revision number for tracking changes'), + /** Schema version (always 1 for this format) */ + version: z.literal(1).describe('Schema version number (1)'), /** Workflow configuration settings */ - config: zConfig.optional().nullable().describe('Workflow configuration settings'), + config: zConfig + .optional() + .nullable() + .describe('Workflow configuration settings'), /** Graph state for ID tracking and generation */ state: zGraphState.describe('Graph state for ID tracking and generation'), /** Visual groupings of nodes */ @@ -426,35 +446,49 @@ export const zComfyWorkflow1 = zBaseExportableGraph /** All nodes in the workflow */ nodes: z.array(zComfyNode).describe('All nodes in the workflow'), /** Node connections (modern object format) */ - links: z.array(zComfyLinkObject).optional().describe('Node connections in modern object format'), + links: z + .array(zComfyLinkObject) + .optional() + .describe('Node connections in modern object format'), /** Floating links (unconnected endpoints) */ - floatingLinks: z.array(zComfyLinkObject).optional().describe('Floating links with unconnected endpoints'), + floatingLinks: z + .array(zComfyLinkObject) + .optional() + .describe('Floating links with unconnected endpoints'), /** Reroute nodes for organizing connections */ - reroutes: z.array(zReroute).optional().describe('Reroute nodes for organizing connections'), + reroutes: z + .array(zReroute) + .optional() + .describe('Reroute nodes for organizing connections'), /** Extra metadata and extensions */ - extra: zExtra.optional().nullable().describe('Extra metadata and extensions'), + extra: zExtra + .optional() + .nullable() + .describe('Extra metadata and extensions'), /** Required AI model files */ models: z.array(zModelFile).optional().describe('Required AI model files'), /** Subgraph definitions */ definitions: z .object({ /** Nested subgraph definitions */ - subgraphs: z.lazy( - (): z.ZodArray< - z.ZodType< - SubgraphDefinitionBase, - z.ZodTypeDef, - SubgraphDefinitionBase - >, - 'many' - > => z.array(zSubgraphDefinition) - ).describe('Nested subgraph definitions') + subgraphs: z + .lazy( + (): z.ZodArray< + z.ZodType< + SubgraphDefinitionBase, + z.ZodTypeDef, + SubgraphDefinitionBase + >, + 'many' + > => z.array(zSubgraphDefinition) + ) + .describe('Nested subgraph definitions') }) .optional() .describe('Subgraph definitions') }) .passthrough() - .describe('ComfyUI Workflow JSON Schema v2') + .describe('ComfyUI Workflow JSON Schema v1') export const zExportedSubgraphIONode = z.object({ id: zNodeId, @@ -568,8 +602,8 @@ const zWorkflowVersion = z.object({ /** * Validates a ComfyUI workflow JSON against the appropriate schema version. - * Supports both legacy (v0.4) and modern (v2) workflow formats. - * + * Supports both legacy (v0.4) and modern (v1) workflow formats. + * * @param data - The workflow data to validate * @param onError - Error callback function for validation failures * @returns Parsed and validated workflow data or null if invalid @@ -586,14 +620,14 @@ export async function validateComfyWorkflow( const error = fromZodError(versionResult.error) onError(`Workflow does not contain a valid version. Zod error:\n${error}`) return null - } else if (versionResult.data.version === 1 || versionResult.data.version === 2) { - // Modern schema versions 1 or 2 (v2 is current) + } else if (versionResult.data.version === 1) { + // Modern schema version 1 (current) result = await zComfyWorkflow1.safeParseAsync(data) } else { // Legacy or unknown version: defaults to 0.4 format result = await zComfyWorkflow.safeParseAsync(data) } - + if (result.success) return result.data const error = fromZodError(result.error)