@veloxts/cli 0.7.2 → 0.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,16 +1,25 @@
1
1
  # @veloxts/cli
2
2
 
3
+ ## 0.7.3
4
+
5
+ ### Patch Changes
6
+
7
+ - feat(cli): auto-populate Zod schemas from Prisma model fields
8
+ - Updated dependencies
9
+ - @veloxts/auth@0.7.3
10
+ - @veloxts/core@0.7.3
11
+ - @veloxts/orm@0.7.3
12
+ - @veloxts/router@0.7.3
13
+ - @veloxts/validation@0.7.3
14
+
3
15
  ## 0.7.2
4
16
 
5
17
  ### Patch Changes
6
18
 
7
- - chore(auth,core,create,cli,client,orm,mcp,router,validation,web): simplify code for clarity and maintainability
19
+ - fix(cli): replace require() with readFileSync in MCP tests
20
+ - simplify code for clarity and maintainability
8
21
  - Updated dependencies
9
- - @veloxts/auth@0.7.2
10
22
  - @veloxts/core@0.7.2
11
- - @veloxts/orm@0.7.2
12
- - @veloxts/router@0.7.2
13
- - @veloxts/validation@0.7.2
14
23
 
15
24
  ## 0.7.1
16
25
 
@@ -88,6 +88,13 @@ export declare function validateEnumName(name: string): string | undefined;
88
88
  * Validate enum values format (UPPER_CASE)
89
89
  */
90
90
  export declare function validateEnumValues(values: string[]): string | undefined;
91
+ /**
92
+ * Map a Prisma schema type string to a FieldType
93
+ *
94
+ * Used when reading fields from an existing Prisma model to convert them
95
+ * into FieldDefinition objects for code generation.
96
+ */
97
+ export declare function prismaTypeToFieldType(prismaType: string): FieldType;
91
98
  /**
92
99
  * Parse comma-separated enum values
93
100
  */
@@ -138,6 +138,35 @@ export function validateEnumValues(values) {
138
138
  }
139
139
  return undefined;
140
140
  }
141
+ // ============================================================================
142
+ // Prisma Type Mapping
143
+ // ============================================================================
144
+ /**
145
+ * Map a Prisma schema type string to a FieldType
146
+ *
147
+ * Used when reading fields from an existing Prisma model to convert them
148
+ * into FieldDefinition objects for code generation.
149
+ */
150
+ export function prismaTypeToFieldType(prismaType) {
151
+ switch (prismaType) {
152
+ case 'String':
153
+ return 'string';
154
+ case 'Int':
155
+ return 'int';
156
+ case 'Float':
157
+ case 'Decimal':
158
+ case 'BigInt':
159
+ return 'float';
160
+ case 'Boolean':
161
+ return 'boolean';
162
+ case 'DateTime':
163
+ return 'datetime';
164
+ case 'Json':
165
+ return 'json';
166
+ default:
167
+ return 'string';
168
+ }
169
+ }
141
170
  /**
142
171
  * Parse comma-separated enum values
143
172
  */
@@ -14,7 +14,7 @@
14
14
  import { BaseGenerator } from '../base.js';
15
15
  import { getNamespaceInstructions, getNamespaceProcedurePath, getNamespaceSchemaPath, getNamespaceTestPath, namespaceSchemaTemplate, namespaceTemplate, namespaceTestTemplate, } from '../templates/namespace.js';
16
16
  import { deriveEntityNames } from '../utils/naming.js';
17
- import { analyzePrismaSchema, findPrismaSchema, getModelRelations, hasModel, } from '../utils/prisma-schema.js';
17
+ import { analyzePrismaSchema, findPrismaSchema, getModelFields, getModelRelations, hasModel, } from '../utils/prisma-schema.js';
18
18
  import { detectRouterPattern, isProcedureRegistered, registerProcedures, } from '../utils/router-integration.js';
19
19
  // ============================================================================
20
20
  // Generator Implementation
@@ -113,10 +113,15 @@ Examples:
113
113
  hasMany: [...modelRelations.hasMany],
114
114
  };
115
115
  }
116
+ // Extract scalar fields for Zod schema generation
117
+ const scalarFields = getModelFields(schemaAnalysis, entity.pascal);
118
+ if (scalarFields.length > 0) {
119
+ enrichedOptions.fields = scalarFields;
120
+ }
116
121
  }
117
122
  }
118
123
  catch {
119
- // Schema parsing failed — proceed without relations
124
+ // Schema parsing failed — proceed without relations or fields
120
125
  }
121
126
  }
122
127
  const enrichedConfig = { ...config, options: enrichedOptions };
@@ -5,6 +5,7 @@
5
5
  * Unlike the procedure generator, this creates a minimal scaffold ready for
6
6
  * custom procedures rather than pre-defined CRUD operations.
7
7
  */
8
+ import type { FieldDefinition } from '../fields/types.js';
8
9
  import type { TemplateContext, TemplateFunction } from '../types.js';
9
10
  /**
10
11
  * Relation info for code generation (shared with resource template)
@@ -24,6 +25,8 @@ export interface NamespaceOptions {
24
25
  withTests: boolean;
25
26
  /** Detected relations from Prisma schema */
26
27
  relations?: RelationInfo;
28
+ /** Scalar fields extracted from Prisma model */
29
+ fields?: FieldDefinition[];
27
30
  }
28
31
  /**
29
32
  * Generate namespace procedure file
@@ -5,6 +5,8 @@
5
5
  * Unlike the procedure generator, this creates a minimal scaffold ready for
6
6
  * custom procedures rather than pre-defined CRUD operations.
7
7
  */
8
+ import { FIELD_TYPES } from '../fields/types.js';
9
+ import { fieldToZod } from '../templates/resource.js';
8
10
  // ============================================================================
9
11
  // Relation Helpers
10
12
  // ============================================================================
@@ -170,7 +172,89 @@ export const ${entity.camel}Procedures = procedures('${entity.plural}', {
170
172
  * Generate schema file for the namespace
171
173
  */
172
174
  export function namespaceSchemaTemplate(ctx) {
173
- const { entity } = ctx;
175
+ const { entity, options } = ctx;
176
+ const fields = options.fields;
177
+ if (fields && fields.length > 0) {
178
+ return generateSchemaWithFields(entity, fields);
179
+ }
180
+ return generateSchemaPlaceholder(entity);
181
+ }
182
+ /**
183
+ * Generate schema with actual fields from Prisma model
184
+ */
185
+ function generateSchemaWithFields(entity, fields) {
186
+ // Base schema: all fields
187
+ const baseFields = fields.map(fieldToZod).join('\n');
188
+ // Create input: non-auto-generated fields (those without @default or @updatedAt)
189
+ const createFields = fields.filter((f) => !f.attributes.hasDefault);
190
+ const createFieldLines = createFields.map((f) => {
191
+ // In create input, optional fields use .optional() instead of .nullable()
192
+ return fieldToZodForInput(f);
193
+ });
194
+ const createContent = createFieldLines.join('\n');
195
+ return `/**
196
+ * ${entity.pascal} Schemas
197
+ *
198
+ * Zod validation schemas for ${entity.humanReadable} entities.
199
+ */
200
+
201
+ import { z } from 'zod';
202
+
203
+ // ============================================================================
204
+ // Base Schema
205
+ // ============================================================================
206
+
207
+ /**
208
+ * ${entity.pascal} entity schema
209
+ */
210
+ export const ${entity.pascal}Schema = z.object({
211
+ id: z.string().uuid(),
212
+ ${baseFields}
213
+ createdAt: z.coerce.date(),
214
+ updatedAt: z.coerce.date(),
215
+ });
216
+
217
+ export type ${entity.pascal} = z.infer<typeof ${entity.pascal}Schema>;
218
+
219
+ // ============================================================================
220
+ // Input Schemas
221
+ // ============================================================================
222
+
223
+ /**
224
+ * Create ${entity.pascal} input
225
+ */
226
+ export const Create${entity.pascal}Input = z.object({
227
+ ${createContent}
228
+ });
229
+
230
+ export type Create${entity.pascal}InputType = z.infer<typeof Create${entity.pascal}Input>;
231
+
232
+ /**
233
+ * Update ${entity.pascal} input
234
+ */
235
+ export const Update${entity.pascal}Input = z.object({
236
+ ${createContent}
237
+ }).partial();
238
+
239
+ export type Update${entity.pascal}InputType = z.infer<typeof Update${entity.pascal}Input>;
240
+ `;
241
+ }
242
+ /**
243
+ * Convert a field to Zod for input schemas (optional → .optional() instead of .nullable())
244
+ */
245
+ function fieldToZodForInput(field) {
246
+ // Create a copy with optional mapped to .optional() suffix
247
+ const typeInfo = FIELD_TYPES.find((t) => t.type === field.type);
248
+ let zodType = typeInfo?.zodSchema ?? 'z.string()';
249
+ if (field.attributes.optional) {
250
+ zodType += '.optional()';
251
+ }
252
+ return ` ${field.name}: ${zodType},`;
253
+ }
254
+ /**
255
+ * Generate schema with TODO placeholders (no Prisma model found)
256
+ */
257
+ function generateSchemaPlaceholder(entity) {
174
258
  return `/**
175
259
  * ${entity.pascal} Schemas
176
260
  *
@@ -4,6 +4,7 @@
4
4
  * Parses Prisma schema files to find models/enums and enables safe injection
5
5
  * of new definitions without breaking existing code.
6
6
  */
7
+ import type { FieldDefinition } from '../fields/types.js';
7
8
  /**
8
9
  * Parsed information about a single field in a Prisma model
9
10
  */
@@ -18,6 +19,10 @@ export interface PrismaFieldInfo {
18
19
  readonly isArray: boolean;
19
20
  /** The related model name, if this is a relation field */
20
21
  readonly relatedModel: string | undefined;
22
+ /** Whether this field has a `?` modifier (nullable/optional) */
23
+ readonly isOptional: boolean;
24
+ /** Whether this field has `@default(...)` or `@updatedAt` */
25
+ readonly hasDefault: boolean;
21
26
  }
22
27
  /**
23
28
  * Detailed information about a Prisma model including its fields
@@ -129,6 +134,19 @@ export declare function injectIntoSchema(analysis: PrismaSchemaAnalysis, models:
129
134
  * Throws if any model or enum already exists (prevents accidental overwrites)
130
135
  */
131
136
  export declare function validateNoConflicts(analysis: PrismaSchemaAnalysis, models: PrismaModelDefinition[], enums?: PrismaEnumDefinition[]): void;
137
+ /**
138
+ * Convert a PrismaFieldInfo to a FieldDefinition for code generation
139
+ *
140
+ * Returns null for fields that should be skipped (relations, reserved names).
141
+ */
142
+ export declare function prismaFieldToFieldDefinition(field: PrismaFieldInfo): FieldDefinition | null;
143
+ /**
144
+ * Get scalar, non-auto-generated fields from a Prisma model as FieldDefinitions
145
+ *
146
+ * Filters out relation fields, reserved fields (id, createdAt, updatedAt, deletedAt),
147
+ * and fields with @id annotation.
148
+ */
149
+ export declare function getModelFields(analysis: PrismaSchemaAnalysis, modelName: string): FieldDefinition[];
132
150
  /**
133
151
  * Generate a minimal Prisma model string
134
152
  */
@@ -5,6 +5,7 @@
5
5
  * of new definitions without breaking existing code.
6
6
  */
7
7
  import { existsSync, readFileSync } from 'node:fs';
8
+ import { prismaTypeToFieldType, RESERVED_FIELD_NAMES } from '../fields/types.js';
8
9
  import { GeneratorError, GeneratorErrorCode } from '../types.js';
9
10
  // ============================================================================
10
11
  // Schema Parsing
@@ -156,13 +157,15 @@ function parseModelFields(body, modelNames) {
156
157
  continue;
157
158
  }
158
159
  // Parse field: name Type[?][] [@annotations...]
159
- // Match: fieldName TypeName optional modifiers
160
- const fieldMatch = trimmed.match(/^(\w+)\s+(\w+)(\[\])?\??/);
160
+ // Match: fieldName TypeName optional array and optional modifiers
161
+ const fieldMatch = trimmed.match(/^(\w+)\s+(\w+)(\[\])?(\?)?/);
161
162
  if (!fieldMatch)
162
163
  continue;
163
164
  const name = fieldMatch[1];
164
165
  const baseType = fieldMatch[2];
165
166
  const isArray = fieldMatch[3] === '[]';
167
+ const isOptional = fieldMatch[4] === '?';
168
+ const hasDefault = /@default\(/.test(trimmed) || /@updatedAt/.test(trimmed);
166
169
  // Check if this is a relation field (type matches a known model)
167
170
  const isRelation = modelNames.has(baseType);
168
171
  if (!isRelation) {
@@ -172,6 +175,8 @@ function parseModelFields(body, modelNames) {
172
175
  isRelation: false,
173
176
  isArray: false,
174
177
  relatedModel: undefined,
178
+ isOptional,
179
+ hasDefault,
175
180
  });
176
181
  continue;
177
182
  }
@@ -188,6 +193,8 @@ function parseModelFields(body, modelNames) {
188
193
  isRelation: true,
189
194
  isArray,
190
195
  relatedModel: baseType,
196
+ isOptional,
197
+ hasDefault,
191
198
  });
192
199
  }
193
200
  return fields;
@@ -311,6 +318,50 @@ export function validateNoConflicts(analysis, models, enums = []) {
311
318
  }
312
319
  }
313
320
  // ============================================================================
321
+ // Field Conversion
322
+ // ============================================================================
323
+ /**
324
+ * Convert a PrismaFieldInfo to a FieldDefinition for code generation
325
+ *
326
+ * Returns null for fields that should be skipped (relations, reserved names).
327
+ */
328
+ export function prismaFieldToFieldDefinition(field) {
329
+ // Skip relation fields
330
+ if (field.isRelation)
331
+ return null;
332
+ // Skip reserved fields (id, createdAt, updatedAt, deletedAt)
333
+ if (RESERVED_FIELD_NAMES.includes(field.name))
334
+ return null;
335
+ return {
336
+ name: field.name,
337
+ type: prismaTypeToFieldType(field.type),
338
+ attributes: {
339
+ optional: field.isOptional,
340
+ unique: false, // Not tracked in PrismaFieldInfo
341
+ hasDefault: field.hasDefault,
342
+ },
343
+ };
344
+ }
345
+ /**
346
+ * Get scalar, non-auto-generated fields from a Prisma model as FieldDefinitions
347
+ *
348
+ * Filters out relation fields, reserved fields (id, createdAt, updatedAt, deletedAt),
349
+ * and fields with @id annotation.
350
+ */
351
+ export function getModelFields(analysis, modelName) {
352
+ const modelInfo = analysis.modelDetails.get(modelName);
353
+ if (!modelInfo)
354
+ return [];
355
+ const fields = [];
356
+ for (const field of modelInfo.fields) {
357
+ const converted = prismaFieldToFieldDefinition(field);
358
+ if (converted) {
359
+ fields.push(converted);
360
+ }
361
+ }
362
+ return fields;
363
+ }
364
+ // ============================================================================
314
365
  // Helpers
315
366
  // ============================================================================
316
367
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@veloxts/cli",
3
- "version": "0.7.2",
3
+ "version": "0.7.3",
4
4
  "description": "Developer tooling and CLI commands for VeloxTS framework",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -41,11 +41,11 @@
41
41
  "pluralize": "8.0.0",
42
42
  "tsx": "4.21.0",
43
43
  "yaml": "2.8.2",
44
- "@veloxts/core": "0.7.2",
45
- "@veloxts/auth": "0.7.2",
46
- "@veloxts/orm": "0.7.2",
47
- "@veloxts/router": "0.7.2",
48
- "@veloxts/validation": "0.7.2"
44
+ "@veloxts/auth": "0.7.3",
45
+ "@veloxts/orm": "0.7.3",
46
+ "@veloxts/core": "0.7.3",
47
+ "@veloxts/router": "0.7.3",
48
+ "@veloxts/validation": "0.7.3"
49
49
  },
50
50
  "peerDependencies": {
51
51
  "@prisma/client": ">=7.0.0"