@constructive-io/graphql-codegen 4.1.1 → 4.1.2
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/core/codegen/cli/index.d.ts +1 -0
- package/core/codegen/cli/index.js +10 -3
- package/core/codegen/cli/infra-generator.js +1 -1
- package/core/codegen/cli/table-command-generator.js +64 -8
- package/core/codegen/cli/utils-generator.d.ts +10 -0
- package/core/codegen/cli/utils-generator.js +78 -0
- package/core/codegen/templates/cli-utils.ts +144 -0
- package/esm/core/codegen/cli/index.d.ts +1 -0
- package/esm/core/codegen/cli/index.js +8 -2
- package/esm/core/codegen/cli/infra-generator.js +1 -1
- package/esm/core/codegen/cli/table-command-generator.js +64 -8
- package/esm/core/codegen/cli/utils-generator.d.ts +10 -0
- package/esm/core/codegen/cli/utils-generator.js +42 -0
- package/package.json +2 -2
|
@@ -50,4 +50,5 @@ export { generateReadme, generateAgentsDocs, getCliMcpTools, generateSkills, gen
|
|
|
50
50
|
export type { MultiTargetDocsInput } from './docs-generator';
|
|
51
51
|
export { resolveDocsConfig } from '../docs-utils';
|
|
52
52
|
export type { GeneratedDocFile, McpTool } from '../docs-utils';
|
|
53
|
+
export { generateUtilsFile } from './utils-generator';
|
|
53
54
|
export type { GeneratedFile, MultiTargetExecutorInput } from './executor-generator';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.resolveDocsConfig = exports.generateMultiTargetSkills = exports.getMultiTargetCliMcpTools = exports.generateMultiTargetAgentsDocs = exports.generateMultiTargetReadme = exports.generateSkills = exports.getCliMcpTools = exports.generateAgentsDocs = exports.generateReadme = exports.generateAuthCommandWithName = exports.generateMultiTargetContextCommand = exports.generateAuthCommand = exports.generateContextCommand = exports.generateMultiTargetCommandMap = exports.generateCommandMap = exports.generateCustomCommand = exports.generateTableCommand = exports.generateMultiTargetExecutorFile = exports.generateExecutorFile = void 0;
|
|
3
|
+
exports.generateUtilsFile = exports.resolveDocsConfig = exports.generateMultiTargetSkills = exports.getMultiTargetCliMcpTools = exports.generateMultiTargetAgentsDocs = exports.generateMultiTargetReadme = exports.generateSkills = exports.getCliMcpTools = exports.generateAgentsDocs = exports.generateReadme = exports.generateAuthCommandWithName = exports.generateMultiTargetContextCommand = exports.generateAuthCommand = exports.generateContextCommand = exports.generateMultiTargetCommandMap = exports.generateCommandMap = exports.generateCustomCommand = exports.generateTableCommand = exports.generateMultiTargetExecutorFile = exports.generateExecutorFile = void 0;
|
|
4
4
|
exports.generateCli = generateCli;
|
|
5
5
|
exports.resolveBuiltinNames = resolveBuiltinNames;
|
|
6
6
|
exports.generateMultiTargetCli = generateMultiTargetCli;
|
|
@@ -9,6 +9,7 @@ const custom_command_generator_1 = require("./custom-command-generator");
|
|
|
9
9
|
const executor_generator_1 = require("./executor-generator");
|
|
10
10
|
const infra_generator_1 = require("./infra-generator");
|
|
11
11
|
const table_command_generator_1 = require("./table-command-generator");
|
|
12
|
+
const utils_generator_1 = require("./utils-generator");
|
|
12
13
|
function generateCli(options) {
|
|
13
14
|
const { tables, customOperations, config } = options;
|
|
14
15
|
const files = [];
|
|
@@ -18,6 +19,8 @@ function generateCli(options) {
|
|
|
18
19
|
: 'app';
|
|
19
20
|
const executorFile = (0, executor_generator_1.generateExecutorFile)(toolName);
|
|
20
21
|
files.push(executorFile);
|
|
22
|
+
const utilsFile = (0, utils_generator_1.generateUtilsFile)();
|
|
23
|
+
files.push(utilsFile);
|
|
21
24
|
const contextFile = (0, infra_generator_1.generateContextCommand)(toolName);
|
|
22
25
|
files.push(contextFile);
|
|
23
26
|
const authFile = (0, infra_generator_1.generateAuthCommand)(toolName);
|
|
@@ -42,7 +45,7 @@ function generateCli(options) {
|
|
|
42
45
|
tables: tables.length,
|
|
43
46
|
customQueries: customOperations?.queries.length ?? 0,
|
|
44
47
|
customMutations: customOperations?.mutations.length ?? 0,
|
|
45
|
-
infraFiles:
|
|
48
|
+
infraFiles: 4,
|
|
46
49
|
totalFiles: files.length,
|
|
47
50
|
},
|
|
48
51
|
};
|
|
@@ -70,6 +73,8 @@ function generateMultiTargetCli(options) {
|
|
|
70
73
|
}));
|
|
71
74
|
const executorFile = (0, executor_generator_1.generateMultiTargetExecutorFile)(toolName, executorInputs);
|
|
72
75
|
files.push(executorFile);
|
|
76
|
+
const utilsFile = (0, utils_generator_1.generateUtilsFile)();
|
|
77
|
+
files.push(utilsFile);
|
|
73
78
|
const contextFile = (0, infra_generator_1.generateMultiTargetContextCommand)(toolName, builtinNames.context, targets.map((t) => ({ name: t.name, endpoint: t.endpoint })));
|
|
74
79
|
files.push(contextFile);
|
|
75
80
|
const authFile = (0, infra_generator_1.generateAuthCommandWithName)(toolName, builtinNames.auth);
|
|
@@ -120,7 +125,7 @@ function generateMultiTargetCli(options) {
|
|
|
120
125
|
tables: totalTables,
|
|
121
126
|
customQueries: totalQueries,
|
|
122
127
|
customMutations: totalMutations,
|
|
123
|
-
infraFiles:
|
|
128
|
+
infraFiles: 4,
|
|
124
129
|
totalFiles: files.length,
|
|
125
130
|
},
|
|
126
131
|
};
|
|
@@ -151,3 +156,5 @@ Object.defineProperty(exports, "getMultiTargetCliMcpTools", { enumerable: true,
|
|
|
151
156
|
Object.defineProperty(exports, "generateMultiTargetSkills", { enumerable: true, get: function () { return docs_generator_1.generateMultiTargetSkills; } });
|
|
152
157
|
var docs_utils_1 = require("../docs-utils");
|
|
153
158
|
Object.defineProperty(exports, "resolveDocsConfig", { enumerable: true, get: function () { return docs_utils_1.resolveDocsConfig; } });
|
|
159
|
+
var utils_generator_2 = require("./utils-generator");
|
|
160
|
+
Object.defineProperty(exports, "generateUtilsFile", { enumerable: true, get: function () { return utils_generator_2.generateUtilsFile; } });
|
|
@@ -695,7 +695,7 @@ function buildSetTokenHandler() {
|
|
|
695
695
|
t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('store'), t.identifier('setCredentials')), [
|
|
696
696
|
t.memberExpression(t.identifier('current'), t.identifier('name')),
|
|
697
697
|
t.objectExpression([
|
|
698
|
-
t.objectProperty(t.identifier('token'), t.callExpression(t.memberExpression(t.callExpression(t.
|
|
698
|
+
t.objectProperty(t.identifier('token'), t.callExpression(t.memberExpression(t.callExpression(t.identifier('String'), [
|
|
699
699
|
t.logicalExpression('||', t.identifier('tokenValue'), t.stringLiteral('')),
|
|
700
700
|
]), t.identifier('trim')), [])),
|
|
701
701
|
]),
|
|
@@ -44,6 +44,40 @@ function createImportDeclaration(moduleSpecifier, namedImports, typeOnly = false
|
|
|
44
44
|
decl.importKind = typeOnly ? 'type' : 'value';
|
|
45
45
|
return decl;
|
|
46
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Build a field schema object that maps field names to their GraphQL types.
|
|
49
|
+
* This is used at runtime for type coercion (string CLI args → proper types).
|
|
50
|
+
* e.g., { name: 'string', isActive: 'boolean', position: 'int', status: 'enum' }
|
|
51
|
+
*/
|
|
52
|
+
function buildFieldSchemaObject(table) {
|
|
53
|
+
const fields = (0, utils_1.getScalarFields)(table);
|
|
54
|
+
return t.objectExpression(fields.map((f) => {
|
|
55
|
+
const gqlType = f.type.gqlType.replace(/!/g, '');
|
|
56
|
+
let schemaType;
|
|
57
|
+
switch (gqlType) {
|
|
58
|
+
case 'Boolean':
|
|
59
|
+
schemaType = 'boolean';
|
|
60
|
+
break;
|
|
61
|
+
case 'Int':
|
|
62
|
+
case 'BigInt':
|
|
63
|
+
schemaType = 'int';
|
|
64
|
+
break;
|
|
65
|
+
case 'Float':
|
|
66
|
+
schemaType = 'float';
|
|
67
|
+
break;
|
|
68
|
+
case 'JSON':
|
|
69
|
+
case 'GeoJSON':
|
|
70
|
+
schemaType = 'json';
|
|
71
|
+
break;
|
|
72
|
+
case 'UUID':
|
|
73
|
+
schemaType = 'uuid';
|
|
74
|
+
break;
|
|
75
|
+
default:
|
|
76
|
+
schemaType = 'string';
|
|
77
|
+
}
|
|
78
|
+
return t.objectProperty(t.identifier(f.name), t.stringLiteral(schemaType));
|
|
79
|
+
}));
|
|
80
|
+
}
|
|
47
81
|
function buildSelectObject(table) {
|
|
48
82
|
const fields = (0, utils_1.getScalarFields)(table);
|
|
49
83
|
return t.objectExpression(fields.map((f) => t.objectProperty(t.identifier(f.name), t.booleanLiteral(true))));
|
|
@@ -183,36 +217,52 @@ function buildMutationHandler(table, operation, targetName) {
|
|
|
183
217
|
: buildSelectObject(table);
|
|
184
218
|
let ormArgs;
|
|
185
219
|
if (operation === 'create') {
|
|
186
|
-
const dataProps = editableFields.map((f) => t.objectProperty(t.identifier(f.name), t.memberExpression(t.identifier('
|
|
220
|
+
const dataProps = editableFields.map((f) => t.objectProperty(t.identifier(f.name), t.memberExpression(t.identifier('cleanedData'), t.identifier(f.name)), false, true));
|
|
187
221
|
ormArgs = t.objectExpression([
|
|
188
222
|
t.objectProperty(t.identifier('data'), t.objectExpression(dataProps)),
|
|
189
223
|
t.objectProperty(t.identifier('select'), selectObj),
|
|
190
224
|
]);
|
|
191
225
|
}
|
|
192
226
|
else if (operation === 'update') {
|
|
193
|
-
const dataProps = editableFields.map((f) => t.objectProperty(t.identifier(f.name), t.memberExpression(t.identifier('
|
|
227
|
+
const dataProps = editableFields.map((f) => t.objectProperty(t.identifier(f.name), t.memberExpression(t.identifier('cleanedData'), t.identifier(f.name)), false, true));
|
|
194
228
|
ormArgs = t.objectExpression([
|
|
195
|
-
t.objectProperty(t.identifier(
|
|
229
|
+
t.objectProperty(t.identifier('where'), t.objectExpression([
|
|
230
|
+
t.objectProperty(t.identifier(pk.name), t.tsAsExpression(t.memberExpression(t.identifier('answers'), t.identifier(pk.name)), t.tsStringKeyword())),
|
|
231
|
+
])),
|
|
196
232
|
t.objectProperty(t.identifier('data'), t.objectExpression(dataProps)),
|
|
197
233
|
t.objectProperty(t.identifier('select'), selectObj),
|
|
198
234
|
]);
|
|
199
235
|
}
|
|
200
236
|
else {
|
|
201
237
|
ormArgs = t.objectExpression([
|
|
202
|
-
t.objectProperty(t.identifier(
|
|
238
|
+
t.objectProperty(t.identifier('where'), t.objectExpression([
|
|
239
|
+
t.objectProperty(t.identifier(pk.name), t.tsAsExpression(t.memberExpression(t.identifier('answers'), t.identifier(pk.name)), t.tsStringKeyword())),
|
|
240
|
+
])),
|
|
203
241
|
t.objectProperty(t.identifier('select'), selectObj),
|
|
204
242
|
]);
|
|
205
243
|
}
|
|
206
244
|
const tryBody = [
|
|
207
245
|
t.variableDeclaration('const', [
|
|
208
|
-
t.variableDeclarator(t.identifier('
|
|
246
|
+
t.variableDeclarator(t.identifier('rawAnswers'), t.awaitExpression(t.callExpression(t.memberExpression(t.identifier('prompter'), t.identifier('prompt')), [t.identifier('argv'), t.arrayExpression(questions)]))),
|
|
209
247
|
]),
|
|
210
|
-
buildGetClientStatement(targetName),
|
|
211
248
|
t.variableDeclaration('const', [
|
|
212
|
-
t.variableDeclarator(t.identifier('
|
|
249
|
+
t.variableDeclarator(t.identifier('answers'), t.callExpression(t.identifier('coerceAnswers'), [
|
|
250
|
+
t.identifier('rawAnswers'),
|
|
251
|
+
t.identifier('fieldSchema'),
|
|
252
|
+
])),
|
|
213
253
|
]),
|
|
214
|
-
buildJsonLog(t.identifier('result')),
|
|
215
254
|
];
|
|
255
|
+
if (operation !== 'delete') {
|
|
256
|
+
tryBody.push(t.variableDeclaration('const', [
|
|
257
|
+
t.variableDeclarator(t.identifier('cleanedData'), t.callExpression(t.identifier('stripUndefined'), [
|
|
258
|
+
t.identifier('answers'),
|
|
259
|
+
t.identifier('fieldSchema'),
|
|
260
|
+
])),
|
|
261
|
+
]));
|
|
262
|
+
}
|
|
263
|
+
tryBody.push(buildGetClientStatement(targetName), t.variableDeclaration('const', [
|
|
264
|
+
t.variableDeclarator(t.identifier('result'), t.awaitExpression(buildOrmCall(singularName, operation, ormArgs))),
|
|
265
|
+
]), buildJsonLog(t.identifier('result')));
|
|
216
266
|
const argvParam = t.identifier('argv');
|
|
217
267
|
argvParam.typeAnnotation = buildArgvType();
|
|
218
268
|
const prompterParam = t.identifier('prompter');
|
|
@@ -233,6 +283,12 @@ function generateTableCommand(table, options) {
|
|
|
233
283
|
'extractFirst',
|
|
234
284
|
]));
|
|
235
285
|
statements.push(createImportDeclaration(executorPath, ['getClient']));
|
|
286
|
+
const utilsPath = options?.targetName ? '../../utils' : '../utils';
|
|
287
|
+
statements.push(createImportDeclaration(utilsPath, ['coerceAnswers', 'stripUndefined']));
|
|
288
|
+
// Generate field schema for type coercion
|
|
289
|
+
statements.push(t.variableDeclaration('const', [
|
|
290
|
+
t.variableDeclarator(t.identifier('fieldSchema'), buildFieldSchemaObject(table)),
|
|
291
|
+
]));
|
|
236
292
|
const subcommands = ['list', 'get', 'create', 'update', 'delete'];
|
|
237
293
|
const usageLines = [
|
|
238
294
|
'',
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { GeneratedFile } from './executor-generator';
|
|
2
|
+
/**
|
|
3
|
+
* Generate a utils.ts file with runtime helpers for CLI commands.
|
|
4
|
+
* Reads from the templates directory (cli-utils.ts) for proper type checking.
|
|
5
|
+
*
|
|
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 declare function generateUtilsFile(): GeneratedFile;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.generateUtilsFile = generateUtilsFile;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const utils_1 = require("../utils");
|
|
40
|
+
/**
|
|
41
|
+
* Find the cli-utils template file path.
|
|
42
|
+
* Templates are at ../templates/ relative to this file in both src/ and dist/.
|
|
43
|
+
*/
|
|
44
|
+
function findTemplateFile(templateName) {
|
|
45
|
+
const templatePath = path.join(__dirname, '../templates', templateName);
|
|
46
|
+
if (fs.existsSync(templatePath)) {
|
|
47
|
+
return templatePath;
|
|
48
|
+
}
|
|
49
|
+
throw new Error(`Could not find template file: ${templateName}. ` +
|
|
50
|
+
`Searched in: ${templatePath}`);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Read a template file and replace the header with generated file header.
|
|
54
|
+
* Follows the same pattern as ORM client-generator.ts readTemplateFile().
|
|
55
|
+
*/
|
|
56
|
+
function readTemplateFile(templateName, description) {
|
|
57
|
+
const templatePath = findTemplateFile(templateName);
|
|
58
|
+
let content = fs.readFileSync(templatePath, 'utf-8');
|
|
59
|
+
// Replace the source file header comment with the generated file header
|
|
60
|
+
// Match the header pattern used in template files
|
|
61
|
+
const headerPattern = /\/\*\*[\s\S]*?\* NOTE: This file is read at codegen time and written to output\.[\s\S]*?\*\/\n*/;
|
|
62
|
+
content = content.replace(headerPattern, (0, utils_1.getGeneratedFileHeader)(description) + '\n');
|
|
63
|
+
return content;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Generate a utils.ts file with runtime helpers for CLI commands.
|
|
67
|
+
* Reads from the templates directory (cli-utils.ts) for proper type checking.
|
|
68
|
+
*
|
|
69
|
+
* Includes type coercion (string CLI args -> proper GraphQL types),
|
|
70
|
+
* field filtering (strip extra minimist fields like _ and tty),
|
|
71
|
+
* and mutation input parsing.
|
|
72
|
+
*/
|
|
73
|
+
function generateUtilsFile() {
|
|
74
|
+
return {
|
|
75
|
+
fileName: 'utils.ts',
|
|
76
|
+
content: readTemplateFile('cli-utils.ts', 'CLI utility functions for type coercion and input handling'),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI utility functions for type coercion and input handling
|
|
3
|
+
*
|
|
4
|
+
* This is the RUNTIME code that gets copied to generated output.
|
|
5
|
+
* Provides helpers for CLI commands: type coercion (string CLI args -> proper
|
|
6
|
+
* GraphQL types), field filtering (strip extra minimist fields), and
|
|
7
|
+
* mutation input parsing.
|
|
8
|
+
*
|
|
9
|
+
* NOTE: This file is read at codegen time and written to output.
|
|
10
|
+
* Any changes here will affect all generated CLI utils.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export type FieldType =
|
|
14
|
+
| 'string'
|
|
15
|
+
| 'boolean'
|
|
16
|
+
| 'int'
|
|
17
|
+
| 'float'
|
|
18
|
+
| 'json'
|
|
19
|
+
| 'uuid'
|
|
20
|
+
| 'enum';
|
|
21
|
+
|
|
22
|
+
export interface FieldSchema {
|
|
23
|
+
[fieldName: string]: FieldType;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Coerce CLI string arguments to their proper GraphQL types based on a field schema.
|
|
28
|
+
* CLI args always arrive as strings from minimist, but GraphQL expects
|
|
29
|
+
* Boolean, Int, Float, JSON, etc.
|
|
30
|
+
*/
|
|
31
|
+
export function coerceAnswers(
|
|
32
|
+
answers: Record<string, unknown>,
|
|
33
|
+
schema: FieldSchema,
|
|
34
|
+
): Record<string, unknown> {
|
|
35
|
+
const result: Record<string, unknown> = { ...answers };
|
|
36
|
+
|
|
37
|
+
for (const [key, value] of Object.entries(result)) {
|
|
38
|
+
const fieldType = schema[key];
|
|
39
|
+
if (!fieldType || value === undefined || value === null) continue;
|
|
40
|
+
|
|
41
|
+
const strValue = String(value);
|
|
42
|
+
|
|
43
|
+
// Empty strings become undefined for non-string types
|
|
44
|
+
if (strValue === '' && fieldType !== 'string') {
|
|
45
|
+
result[key] = undefined;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
switch (fieldType) {
|
|
50
|
+
case 'boolean':
|
|
51
|
+
if (typeof value === 'boolean') break;
|
|
52
|
+
result[key] =
|
|
53
|
+
strValue === 'true' || strValue === '1' || strValue === 'yes';
|
|
54
|
+
break;
|
|
55
|
+
case 'int':
|
|
56
|
+
if (typeof value === 'number') break;
|
|
57
|
+
{
|
|
58
|
+
const parsed = parseInt(strValue, 10);
|
|
59
|
+
result[key] = isNaN(parsed) ? undefined : parsed;
|
|
60
|
+
}
|
|
61
|
+
break;
|
|
62
|
+
case 'float':
|
|
63
|
+
if (typeof value === 'number') break;
|
|
64
|
+
{
|
|
65
|
+
const parsed = parseFloat(strValue);
|
|
66
|
+
result[key] = isNaN(parsed) ? undefined : parsed;
|
|
67
|
+
}
|
|
68
|
+
break;
|
|
69
|
+
case 'json':
|
|
70
|
+
if (typeof value === 'object') break;
|
|
71
|
+
if (strValue === '') {
|
|
72
|
+
result[key] = undefined;
|
|
73
|
+
} else {
|
|
74
|
+
try {
|
|
75
|
+
result[key] = JSON.parse(strValue);
|
|
76
|
+
} catch {
|
|
77
|
+
result[key] = undefined;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
break;
|
|
81
|
+
case 'uuid':
|
|
82
|
+
// Empty UUIDs become undefined
|
|
83
|
+
if (strValue === '') {
|
|
84
|
+
result[key] = undefined;
|
|
85
|
+
}
|
|
86
|
+
break;
|
|
87
|
+
case 'enum':
|
|
88
|
+
// Enums stay as strings but empty ones become undefined
|
|
89
|
+
if (strValue === '') {
|
|
90
|
+
result[key] = undefined;
|
|
91
|
+
}
|
|
92
|
+
break;
|
|
93
|
+
default:
|
|
94
|
+
// String type: empty strings also become undefined to avoid
|
|
95
|
+
// sending empty strings for optional fields
|
|
96
|
+
if (strValue === '') {
|
|
97
|
+
result[key] = undefined;
|
|
98
|
+
}
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return result;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Strip undefined values and filter to only schema-defined keys.
|
|
108
|
+
* This removes extra fields injected by minimist (like _, tty, etc.)
|
|
109
|
+
* and any fields that were coerced to undefined.
|
|
110
|
+
*/
|
|
111
|
+
export function stripUndefined(
|
|
112
|
+
obj: Record<string, unknown>,
|
|
113
|
+
schema?: FieldSchema,
|
|
114
|
+
): Record<string, unknown> {
|
|
115
|
+
const result: Record<string, unknown> = {};
|
|
116
|
+
const allowedKeys = schema ? new Set(Object.keys(schema)) : null;
|
|
117
|
+
|
|
118
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
119
|
+
if (value === undefined) continue;
|
|
120
|
+
if (allowedKeys && !allowedKeys.has(key)) continue;
|
|
121
|
+
result[key] = value;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return result;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Parse mutation input from CLI.
|
|
129
|
+
* Custom mutation commands receive an `input` field as a JSON string
|
|
130
|
+
* from the CLI prompt. This parses it into a proper object.
|
|
131
|
+
*/
|
|
132
|
+
export function parseMutationInput(
|
|
133
|
+
answers: Record<string, unknown>,
|
|
134
|
+
): Record<string, unknown> {
|
|
135
|
+
if (typeof answers.input === 'string') {
|
|
136
|
+
try {
|
|
137
|
+
const parsed = JSON.parse(answers.input);
|
|
138
|
+
return { ...answers, input: parsed };
|
|
139
|
+
} catch {
|
|
140
|
+
return answers;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return answers;
|
|
144
|
+
}
|
|
@@ -50,4 +50,5 @@ export { generateReadme, generateAgentsDocs, getCliMcpTools, generateSkills, gen
|
|
|
50
50
|
export type { MultiTargetDocsInput } from './docs-generator';
|
|
51
51
|
export { resolveDocsConfig } from '../docs-utils';
|
|
52
52
|
export type { GeneratedDocFile, McpTool } from '../docs-utils';
|
|
53
|
+
export { generateUtilsFile } from './utils-generator';
|
|
53
54
|
export type { GeneratedFile, MultiTargetExecutorInput } from './executor-generator';
|
|
@@ -3,6 +3,7 @@ import { generateCustomCommand } from './custom-command-generator';
|
|
|
3
3
|
import { generateExecutorFile, generateMultiTargetExecutorFile } from './executor-generator';
|
|
4
4
|
import { generateAuthCommand, generateAuthCommandWithName, generateContextCommand, generateMultiTargetContextCommand, } from './infra-generator';
|
|
5
5
|
import { generateTableCommand } from './table-command-generator';
|
|
6
|
+
import { generateUtilsFile } from './utils-generator';
|
|
6
7
|
export function generateCli(options) {
|
|
7
8
|
const { tables, customOperations, config } = options;
|
|
8
9
|
const files = [];
|
|
@@ -12,6 +13,8 @@ export function generateCli(options) {
|
|
|
12
13
|
: 'app';
|
|
13
14
|
const executorFile = generateExecutorFile(toolName);
|
|
14
15
|
files.push(executorFile);
|
|
16
|
+
const utilsFile = generateUtilsFile();
|
|
17
|
+
files.push(utilsFile);
|
|
15
18
|
const contextFile = generateContextCommand(toolName);
|
|
16
19
|
files.push(contextFile);
|
|
17
20
|
const authFile = generateAuthCommand(toolName);
|
|
@@ -36,7 +39,7 @@ export function generateCli(options) {
|
|
|
36
39
|
tables: tables.length,
|
|
37
40
|
customQueries: customOperations?.queries.length ?? 0,
|
|
38
41
|
customMutations: customOperations?.mutations.length ?? 0,
|
|
39
|
-
infraFiles:
|
|
42
|
+
infraFiles: 4,
|
|
40
43
|
totalFiles: files.length,
|
|
41
44
|
},
|
|
42
45
|
};
|
|
@@ -64,6 +67,8 @@ export function generateMultiTargetCli(options) {
|
|
|
64
67
|
}));
|
|
65
68
|
const executorFile = generateMultiTargetExecutorFile(toolName, executorInputs);
|
|
66
69
|
files.push(executorFile);
|
|
70
|
+
const utilsFile = generateUtilsFile();
|
|
71
|
+
files.push(utilsFile);
|
|
67
72
|
const contextFile = generateMultiTargetContextCommand(toolName, builtinNames.context, targets.map((t) => ({ name: t.name, endpoint: t.endpoint })));
|
|
68
73
|
files.push(contextFile);
|
|
69
74
|
const authFile = generateAuthCommandWithName(toolName, builtinNames.auth);
|
|
@@ -114,7 +119,7 @@ export function generateMultiTargetCli(options) {
|
|
|
114
119
|
tables: totalTables,
|
|
115
120
|
customQueries: totalQueries,
|
|
116
121
|
customMutations: totalMutations,
|
|
117
|
-
infraFiles:
|
|
122
|
+
infraFiles: 4,
|
|
118
123
|
totalFiles: files.length,
|
|
119
124
|
},
|
|
120
125
|
};
|
|
@@ -126,3 +131,4 @@ export { generateCommandMap, generateMultiTargetCommandMap } from './command-map
|
|
|
126
131
|
export { generateContextCommand, generateAuthCommand, generateMultiTargetContextCommand, generateAuthCommandWithName, } from './infra-generator';
|
|
127
132
|
export { generateReadme, generateAgentsDocs, getCliMcpTools, generateSkills, generateMultiTargetReadme, generateMultiTargetAgentsDocs, getMultiTargetCliMcpTools, generateMultiTargetSkills, } from './docs-generator';
|
|
128
133
|
export { resolveDocsConfig } from '../docs-utils';
|
|
134
|
+
export { generateUtilsFile } from './utils-generator';
|
|
@@ -656,7 +656,7 @@ function buildSetTokenHandler() {
|
|
|
656
656
|
t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('store'), t.identifier('setCredentials')), [
|
|
657
657
|
t.memberExpression(t.identifier('current'), t.identifier('name')),
|
|
658
658
|
t.objectExpression([
|
|
659
|
-
t.objectProperty(t.identifier('token'), t.callExpression(t.memberExpression(t.callExpression(t.
|
|
659
|
+
t.objectProperty(t.identifier('token'), t.callExpression(t.memberExpression(t.callExpression(t.identifier('String'), [
|
|
660
660
|
t.logicalExpression('||', t.identifier('tokenValue'), t.stringLiteral('')),
|
|
661
661
|
]), t.identifier('trim')), [])),
|
|
662
662
|
]),
|
|
@@ -8,6 +8,40 @@ function createImportDeclaration(moduleSpecifier, namedImports, typeOnly = false
|
|
|
8
8
|
decl.importKind = typeOnly ? 'type' : 'value';
|
|
9
9
|
return decl;
|
|
10
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* Build a field schema object that maps field names to their GraphQL types.
|
|
13
|
+
* This is used at runtime for type coercion (string CLI args → proper types).
|
|
14
|
+
* e.g., { name: 'string', isActive: 'boolean', position: 'int', status: 'enum' }
|
|
15
|
+
*/
|
|
16
|
+
function buildFieldSchemaObject(table) {
|
|
17
|
+
const fields = getScalarFields(table);
|
|
18
|
+
return t.objectExpression(fields.map((f) => {
|
|
19
|
+
const gqlType = f.type.gqlType.replace(/!/g, '');
|
|
20
|
+
let schemaType;
|
|
21
|
+
switch (gqlType) {
|
|
22
|
+
case 'Boolean':
|
|
23
|
+
schemaType = 'boolean';
|
|
24
|
+
break;
|
|
25
|
+
case 'Int':
|
|
26
|
+
case 'BigInt':
|
|
27
|
+
schemaType = 'int';
|
|
28
|
+
break;
|
|
29
|
+
case 'Float':
|
|
30
|
+
schemaType = 'float';
|
|
31
|
+
break;
|
|
32
|
+
case 'JSON':
|
|
33
|
+
case 'GeoJSON':
|
|
34
|
+
schemaType = 'json';
|
|
35
|
+
break;
|
|
36
|
+
case 'UUID':
|
|
37
|
+
schemaType = 'uuid';
|
|
38
|
+
break;
|
|
39
|
+
default:
|
|
40
|
+
schemaType = 'string';
|
|
41
|
+
}
|
|
42
|
+
return t.objectProperty(t.identifier(f.name), t.stringLiteral(schemaType));
|
|
43
|
+
}));
|
|
44
|
+
}
|
|
11
45
|
function buildSelectObject(table) {
|
|
12
46
|
const fields = getScalarFields(table);
|
|
13
47
|
return t.objectExpression(fields.map((f) => t.objectProperty(t.identifier(f.name), t.booleanLiteral(true))));
|
|
@@ -147,36 +181,52 @@ function buildMutationHandler(table, operation, targetName) {
|
|
|
147
181
|
: buildSelectObject(table);
|
|
148
182
|
let ormArgs;
|
|
149
183
|
if (operation === 'create') {
|
|
150
|
-
const dataProps = editableFields.map((f) => t.objectProperty(t.identifier(f.name), t.memberExpression(t.identifier('
|
|
184
|
+
const dataProps = editableFields.map((f) => t.objectProperty(t.identifier(f.name), t.memberExpression(t.identifier('cleanedData'), t.identifier(f.name)), false, true));
|
|
151
185
|
ormArgs = t.objectExpression([
|
|
152
186
|
t.objectProperty(t.identifier('data'), t.objectExpression(dataProps)),
|
|
153
187
|
t.objectProperty(t.identifier('select'), selectObj),
|
|
154
188
|
]);
|
|
155
189
|
}
|
|
156
190
|
else if (operation === 'update') {
|
|
157
|
-
const dataProps = editableFields.map((f) => t.objectProperty(t.identifier(f.name), t.memberExpression(t.identifier('
|
|
191
|
+
const dataProps = editableFields.map((f) => t.objectProperty(t.identifier(f.name), t.memberExpression(t.identifier('cleanedData'), t.identifier(f.name)), false, true));
|
|
158
192
|
ormArgs = t.objectExpression([
|
|
159
|
-
t.objectProperty(t.identifier(
|
|
193
|
+
t.objectProperty(t.identifier('where'), t.objectExpression([
|
|
194
|
+
t.objectProperty(t.identifier(pk.name), t.tsAsExpression(t.memberExpression(t.identifier('answers'), t.identifier(pk.name)), t.tsStringKeyword())),
|
|
195
|
+
])),
|
|
160
196
|
t.objectProperty(t.identifier('data'), t.objectExpression(dataProps)),
|
|
161
197
|
t.objectProperty(t.identifier('select'), selectObj),
|
|
162
198
|
]);
|
|
163
199
|
}
|
|
164
200
|
else {
|
|
165
201
|
ormArgs = t.objectExpression([
|
|
166
|
-
t.objectProperty(t.identifier(
|
|
202
|
+
t.objectProperty(t.identifier('where'), t.objectExpression([
|
|
203
|
+
t.objectProperty(t.identifier(pk.name), t.tsAsExpression(t.memberExpression(t.identifier('answers'), t.identifier(pk.name)), t.tsStringKeyword())),
|
|
204
|
+
])),
|
|
167
205
|
t.objectProperty(t.identifier('select'), selectObj),
|
|
168
206
|
]);
|
|
169
207
|
}
|
|
170
208
|
const tryBody = [
|
|
171
209
|
t.variableDeclaration('const', [
|
|
172
|
-
t.variableDeclarator(t.identifier('
|
|
210
|
+
t.variableDeclarator(t.identifier('rawAnswers'), t.awaitExpression(t.callExpression(t.memberExpression(t.identifier('prompter'), t.identifier('prompt')), [t.identifier('argv'), t.arrayExpression(questions)]))),
|
|
173
211
|
]),
|
|
174
|
-
buildGetClientStatement(targetName),
|
|
175
212
|
t.variableDeclaration('const', [
|
|
176
|
-
t.variableDeclarator(t.identifier('
|
|
213
|
+
t.variableDeclarator(t.identifier('answers'), t.callExpression(t.identifier('coerceAnswers'), [
|
|
214
|
+
t.identifier('rawAnswers'),
|
|
215
|
+
t.identifier('fieldSchema'),
|
|
216
|
+
])),
|
|
177
217
|
]),
|
|
178
|
-
buildJsonLog(t.identifier('result')),
|
|
179
218
|
];
|
|
219
|
+
if (operation !== 'delete') {
|
|
220
|
+
tryBody.push(t.variableDeclaration('const', [
|
|
221
|
+
t.variableDeclarator(t.identifier('cleanedData'), t.callExpression(t.identifier('stripUndefined'), [
|
|
222
|
+
t.identifier('answers'),
|
|
223
|
+
t.identifier('fieldSchema'),
|
|
224
|
+
])),
|
|
225
|
+
]));
|
|
226
|
+
}
|
|
227
|
+
tryBody.push(buildGetClientStatement(targetName), t.variableDeclaration('const', [
|
|
228
|
+
t.variableDeclarator(t.identifier('result'), t.awaitExpression(buildOrmCall(singularName, operation, ormArgs))),
|
|
229
|
+
]), buildJsonLog(t.identifier('result')));
|
|
180
230
|
const argvParam = t.identifier('argv');
|
|
181
231
|
argvParam.typeAnnotation = buildArgvType();
|
|
182
232
|
const prompterParam = t.identifier('prompter');
|
|
@@ -197,6 +247,12 @@ export function generateTableCommand(table, options) {
|
|
|
197
247
|
'extractFirst',
|
|
198
248
|
]));
|
|
199
249
|
statements.push(createImportDeclaration(executorPath, ['getClient']));
|
|
250
|
+
const utilsPath = options?.targetName ? '../../utils' : '../utils';
|
|
251
|
+
statements.push(createImportDeclaration(utilsPath, ['coerceAnswers', 'stripUndefined']));
|
|
252
|
+
// Generate field schema for type coercion
|
|
253
|
+
statements.push(t.variableDeclaration('const', [
|
|
254
|
+
t.variableDeclarator(t.identifier('fieldSchema'), buildFieldSchemaObject(table)),
|
|
255
|
+
]));
|
|
200
256
|
const subcommands = ['list', 'get', 'create', 'update', 'delete'];
|
|
201
257
|
const usageLines = [
|
|
202
258
|
'',
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { GeneratedFile } from './executor-generator';
|
|
2
|
+
/**
|
|
3
|
+
* Generate a utils.ts file with runtime helpers for CLI commands.
|
|
4
|
+
* Reads from the templates directory (cli-utils.ts) for proper type checking.
|
|
5
|
+
*
|
|
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 declare function generateUtilsFile(): GeneratedFile;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { getGeneratedFileHeader } from '../utils';
|
|
4
|
+
/**
|
|
5
|
+
* Find the cli-utils template file path.
|
|
6
|
+
* Templates are at ../templates/ relative to this file in both src/ and dist/.
|
|
7
|
+
*/
|
|
8
|
+
function findTemplateFile(templateName) {
|
|
9
|
+
const templatePath = path.join(__dirname, '../templates', templateName);
|
|
10
|
+
if (fs.existsSync(templatePath)) {
|
|
11
|
+
return templatePath;
|
|
12
|
+
}
|
|
13
|
+
throw new Error(`Could not find template file: ${templateName}. ` +
|
|
14
|
+
`Searched in: ${templatePath}`);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Read a template file and replace the header with generated file header.
|
|
18
|
+
* Follows the same pattern as ORM client-generator.ts readTemplateFile().
|
|
19
|
+
*/
|
|
20
|
+
function readTemplateFile(templateName, description) {
|
|
21
|
+
const templatePath = findTemplateFile(templateName);
|
|
22
|
+
let content = fs.readFileSync(templatePath, 'utf-8');
|
|
23
|
+
// Replace the source file header comment with the generated file header
|
|
24
|
+
// Match the header pattern used in template files
|
|
25
|
+
const headerPattern = /\/\*\*[\s\S]*?\* NOTE: This file is read at codegen time and written to output\.[\s\S]*?\*\/\n*/;
|
|
26
|
+
content = content.replace(headerPattern, getGeneratedFileHeader(description) + '\n');
|
|
27
|
+
return content;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Generate a utils.ts file with runtime helpers for CLI commands.
|
|
31
|
+
* Reads from the templates directory (cli-utils.ts) for proper type checking.
|
|
32
|
+
*
|
|
33
|
+
* Includes type coercion (string CLI args -> proper GraphQL types),
|
|
34
|
+
* field filtering (strip extra minimist fields like _ and tty),
|
|
35
|
+
* and mutation input parsing.
|
|
36
|
+
*/
|
|
37
|
+
export function generateUtilsFile() {
|
|
38
|
+
return {
|
|
39
|
+
fileName: 'utils.ts',
|
|
40
|
+
content: readTemplateFile('cli-utils.ts', 'CLI utility functions for type coercion and input handling'),
|
|
41
|
+
};
|
|
42
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@constructive-io/graphql-codegen",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.2",
|
|
4
4
|
"description": "GraphQL SDK generator for Constructive databases with React Query hooks",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"graphql",
|
|
@@ -100,5 +100,5 @@
|
|
|
100
100
|
"tsx": "^4.21.0",
|
|
101
101
|
"typescript": "^5.9.3"
|
|
102
102
|
},
|
|
103
|
-
"gitHead": "
|
|
103
|
+
"gitHead": "f6fa9972bcbf6ffaa6340c514462e7a6d0cb4426"
|
|
104
104
|
}
|