|
| 1 | +import * as fs from 'fs'; |
| 2 | +import * as path from 'path'; |
| 3 | + |
1 | 4 | import { getGeneratedFileHeader } from '../utils'; |
2 | 5 | import type { GeneratedFile } from './executor-generator'; |
3 | 6 |
|
4 | 7 | /** |
5 | | - * Generate a utils.ts file with runtime helpers for CLI commands. |
6 | | - * Includes type coercion (string CLI args -> proper GraphQL types), |
7 | | - * field filtering (strip extra minimist fields like _ and tty), |
8 | | - * and mutation input parsing. |
9 | | - */ |
10 | | -export function generateUtilsFile(): GeneratedFile { |
11 | | - const header = getGeneratedFileHeader( |
12 | | - 'CLI utility functions for type coercion and input handling', |
13 | | - ); |
14 | | - |
15 | | - const code = ` |
16 | | -export type FieldType = 'string' | 'boolean' | 'int' | 'float' | 'json' | 'uuid' | 'enum'; |
17 | | -
|
18 | | -export interface FieldSchema { |
19 | | - [fieldName: string]: FieldType; |
20 | | -} |
21 | | -
|
22 | | -/** |
23 | | - * Coerce CLI string arguments to their proper GraphQL types based on a field schema. |
24 | | - * CLI args always arrive as strings from minimist, but GraphQL expects |
25 | | - * Boolean, Int, Float, JSON, etc. |
| 8 | + * Find the cli-utils template file path. |
| 9 | + * Templates are at ../templates/ relative to this file in both src/ and dist/. |
26 | 10 | */ |
27 | | -export function coerceAnswers( |
28 | | - answers: Record<string, unknown>, |
29 | | - schema: FieldSchema, |
30 | | -): Record<string, unknown> { |
31 | | - const result: Record<string, unknown> = { ...answers }; |
32 | | -
|
33 | | - for (const [key, value] of Object.entries(result)) { |
34 | | - const fieldType = schema[key]; |
35 | | - if (!fieldType || value === undefined || value === null) continue; |
| 11 | +function findTemplateFile(templateName: string): string { |
| 12 | + const templatePath = path.join(__dirname, '../templates', templateName); |
36 | 13 |
|
37 | | - const strValue = String(value); |
38 | | -
|
39 | | - // Empty strings become undefined for non-string types |
40 | | - if (strValue === '' && fieldType !== 'string') { |
41 | | - result[key] = undefined; |
42 | | - continue; |
43 | | - } |
44 | | -
|
45 | | - switch (fieldType) { |
46 | | - case 'boolean': |
47 | | - if (typeof value === 'boolean') break; |
48 | | - result[key] = strValue === 'true' || strValue === '1' || strValue === 'yes'; |
49 | | - break; |
50 | | - case 'int': |
51 | | - if (typeof value === 'number') break; |
52 | | - { |
53 | | - const parsed = parseInt(strValue, 10); |
54 | | - result[key] = isNaN(parsed) ? undefined : parsed; |
55 | | - } |
56 | | - break; |
57 | | - case 'float': |
58 | | - if (typeof value === 'number') break; |
59 | | - { |
60 | | - const parsed = parseFloat(strValue); |
61 | | - result[key] = isNaN(parsed) ? undefined : parsed; |
62 | | - } |
63 | | - break; |
64 | | - case 'json': |
65 | | - if (typeof value === 'object') break; |
66 | | - if (strValue === '') { |
67 | | - result[key] = undefined; |
68 | | - } else { |
69 | | - try { |
70 | | - result[key] = JSON.parse(strValue); |
71 | | - } catch { |
72 | | - result[key] = undefined; |
73 | | - } |
74 | | - } |
75 | | - break; |
76 | | - case 'uuid': |
77 | | - // Empty UUIDs become undefined |
78 | | - if (strValue === '') { |
79 | | - result[key] = undefined; |
80 | | - } |
81 | | - break; |
82 | | - case 'enum': |
83 | | - // Enums stay as strings but empty ones become undefined |
84 | | - if (strValue === '') { |
85 | | - result[key] = undefined; |
86 | | - } |
87 | | - break; |
88 | | - default: |
89 | | - // String type: empty strings also become undefined to avoid |
90 | | - // sending empty strings for optional fields |
91 | | - if (strValue === '') { |
92 | | - result[key] = undefined; |
93 | | - } |
94 | | - break; |
95 | | - } |
| 14 | + if (fs.existsSync(templatePath)) { |
| 15 | + return templatePath; |
96 | 16 | } |
97 | 17 |
|
98 | | - return result; |
| 18 | + throw new Error( |
| 19 | + `Could not find template file: ${templateName}. ` + |
| 20 | + `Searched in: ${templatePath}`, |
| 21 | + ); |
99 | 22 | } |
100 | 23 |
|
101 | 24 | /** |
102 | | - * Strip undefined values and filter to only schema-defined keys. |
103 | | - * This removes extra fields injected by minimist (like _, tty, etc.) |
104 | | - * and any fields that were coerced to undefined. |
| 25 | + * Read a template file and replace the header with generated file header. |
| 26 | + * Follows the same pattern as ORM client-generator.ts readTemplateFile(). |
105 | 27 | */ |
106 | | -export function stripUndefined( |
107 | | - obj: Record<string, unknown>, |
108 | | - schema?: FieldSchema, |
109 | | -): Record<string, unknown> { |
110 | | - const result: Record<string, unknown> = {}; |
111 | | - const allowedKeys = schema ? new Set(Object.keys(schema)) : null; |
112 | | -
|
113 | | - for (const [key, value] of Object.entries(obj)) { |
114 | | - if (value === undefined) continue; |
115 | | - if (allowedKeys && !allowedKeys.has(key)) continue; |
116 | | - result[key] = value; |
117 | | - } |
| 28 | +function readTemplateFile(templateName: string, description: string): string { |
| 29 | + const templatePath = findTemplateFile(templateName); |
| 30 | + let content = fs.readFileSync(templatePath, 'utf-8'); |
| 31 | + |
| 32 | + // Replace the source file header comment with the generated file header |
| 33 | + // Match the header pattern used in template files |
| 34 | + const headerPattern = |
| 35 | + /\/\*\*[\s\S]*?\* NOTE: This file is read at codegen time and written to output\.[\s\S]*?\*\/\n*/; |
| 36 | + |
| 37 | + content = content.replace( |
| 38 | + headerPattern, |
| 39 | + getGeneratedFileHeader(description) + '\n', |
| 40 | + ); |
118 | 41 |
|
119 | | - return result; |
| 42 | + return content; |
120 | 43 | } |
121 | 44 |
|
122 | 45 | /** |
123 | | - * Parse mutation input from CLI. |
124 | | - * Custom mutation commands receive an \`input\` field as a JSON string |
125 | | - * from the CLI prompt. This parses it into a proper object. |
| 46 | + * Generate a utils.ts file with runtime helpers for CLI commands. |
| 47 | + * Reads from the templates directory (cli-utils.ts) for proper type checking. |
| 48 | + * |
| 49 | + * Includes type coercion (string CLI args -> proper GraphQL types), |
| 50 | + * field filtering (strip extra minimist fields like _ and tty), |
| 51 | + * and mutation input parsing. |
126 | 52 | */ |
127 | | -export function parseMutationInput( |
128 | | - answers: Record<string, unknown>, |
129 | | -): Record<string, unknown> { |
130 | | - if (typeof answers.input === 'string') { |
131 | | - try { |
132 | | - const parsed = JSON.parse(answers.input); |
133 | | - return { ...answers, input: parsed }; |
134 | | - } catch { |
135 | | - return answers; |
136 | | - } |
137 | | - } |
138 | | - return answers; |
139 | | -} |
140 | | -`; |
141 | | - |
| 53 | +export function generateUtilsFile(): GeneratedFile { |
142 | 54 | return { |
143 | 55 | fileName: 'utils.ts', |
144 | | - content: header + '\n' + code, |
| 56 | + content: readTemplateFile( |
| 57 | + 'cli-utils.ts', |
| 58 | + 'CLI utility functions for type coercion and input handling', |
| 59 | + ), |
145 | 60 | }; |
146 | 61 | } |
0 commit comments