Skip to content

Commit 4e525f2

Browse files
authored
Merge pull request #734 from constructive-io/devin/1771827273-fix-custom-command-select
fix(codegen): add select param and JSON input parsing to custom command generator
2 parents f6fa997 + 58e631e commit 4e525f2

2 files changed

Lines changed: 97 additions & 7 deletions

File tree

graphql/codegen/src/__tests__/codegen/__snapshots__/cli-generator.test.ts.snap

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1151,7 +1151,9 @@ export default async (argv: Partial<Record<string, unknown>>, prompter: Inquirer
11511151
process.exit(0);
11521152
}
11531153
const client = getClient();
1154-
const result = await client.query.currentUser({}).execute();
1154+
const result = await client.query.currentUser({}, {
1155+
select: {}
1156+
}).execute();
11551157
console.log(JSON.stringify(result, null, 2));
11561158
} catch (error) {
11571159
console.error("Failed: currentUser");
@@ -1395,7 +1397,11 @@ export default async (argv: Partial<Record<string, unknown>>, prompter: Inquirer
13951397
required: true
13961398
}]);
13971399
const client = getClient();
1398-
const result = await client.mutation.login(answers).execute();
1400+
const result = await client.mutation.login(answers, {
1401+
select: {
1402+
clientMutationId: true
1403+
}
1404+
}).execute();
13991405
console.log(JSON.stringify(result, null, 2));
14001406
} catch (error) {
14011407
console.error("Failed: login");
@@ -3555,7 +3561,11 @@ export default async (argv: Partial<Record<string, unknown>>, prompter: Inquirer
35553561
required: true
35563562
}]);
35573563
const client = getClient("auth");
3558-
const result = await client.mutation.login(answers).execute();
3564+
const result = await client.mutation.login(answers, {
3565+
select: {
3566+
clientMutationId: true
3567+
}
3568+
}).execute();
35593569
if (argv.saveToken && result) {
35603570
const tokenValue = result.token || result.jwtToken || result.accessToken;
35613571
if (tokenValue) {

graphql/codegen/src/core/codegen/cli/custom-command-generator.ts

Lines changed: 84 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { toKebabCase } from 'komoji';
33

44
import { generateCode } from '../babel-ast';
55
import { getGeneratedFileHeader } from '../utils';
6-
import type { CleanOperation } from '../../../types/schema';
6+
import type { CleanOperation, CleanTypeRef } from '../../../types/schema';
77
import type { GeneratedFile } from './executor-generator';
88
import { buildQuestionsArray } from './arg-mapper';
99

@@ -100,10 +100,50 @@ function buildErrorCatch(errorMessage: string): t.CatchClause {
100100
);
101101
}
102102

103+
/**
104+
* Unwrap NON_NULL / LIST wrappers to get the underlying named type.
105+
*/
106+
function unwrapType(ref: CleanTypeRef): CleanTypeRef {
107+
if ((ref.kind === 'NON_NULL' || ref.kind === 'LIST') && ref.ofType) {
108+
return unwrapType(ref.ofType);
109+
}
110+
return ref;
111+
}
112+
113+
/**
114+
* Build a select object expression from return-type fields.
115+
* If the return type has known fields, generates { field1: true, field2: true, ... }.
116+
* Falls back to { clientMutationId: true } for mutations without known fields.
117+
*/
118+
function buildSelectObject(
119+
returnType: CleanTypeRef,
120+
isMutation: boolean,
121+
): t.ObjectExpression {
122+
const base = unwrapType(returnType);
123+
if (base.fields && base.fields.length > 0) {
124+
return t.objectExpression(
125+
base.fields.map((f) =>
126+
t.objectProperty(t.identifier(f.name), t.booleanLiteral(true)),
127+
),
128+
);
129+
}
130+
// Fallback: all PostGraphile mutation payloads have clientMutationId
131+
if (isMutation) {
132+
return t.objectExpression([
133+
t.objectProperty(
134+
t.identifier('clientMutationId'),
135+
t.booleanLiteral(true),
136+
),
137+
]);
138+
}
139+
return t.objectExpression([]);
140+
}
141+
103142
function buildOrmCustomCall(
104143
opKind: 'query' | 'mutation',
105144
opName: string,
106145
argsExpr: t.Expression,
146+
selectExpr: t.ObjectExpression,
107147
): t.Expression {
108148
return t.callExpression(
109149
t.memberExpression(
@@ -115,7 +155,12 @@ function buildOrmCustomCall(
115155
),
116156
t.identifier(opName),
117157
),
118-
[argsExpr],
158+
[
159+
argsExpr,
160+
t.objectExpression([
161+
t.objectProperty(t.identifier('select'), selectExpr),
162+
]),
163+
],
119164
),
120165
t.identifier('execute'),
121166
),
@@ -139,13 +184,29 @@ export function generateCustomCommand(op: CleanOperation, options?: CustomComman
139184
imports.push('getStore');
140185
}
141186

187+
// Check if any argument is an INPUT_OBJECT (i.e. takes JSON input like { input: SomeInput })
188+
const hasInputObjectArg = op.args.some((arg) => {
189+
const base = unwrapType(arg.type);
190+
return base.kind === 'INPUT_OBJECT';
191+
});
192+
193+
const utilsPath = options?.executorImportPath
194+
? options.executorImportPath.replace(/\/executor$/, '/utils')
195+
: '../utils';
196+
142197
statements.push(
143198
createImportDeclaration('inquirerer', ['CLIOptions', 'Inquirerer']),
144199
);
145200
statements.push(
146201
createImportDeclaration(executorPath, imports),
147202
);
148203

204+
if (hasInputObjectArg) {
205+
statements.push(
206+
createImportDeclaration(utilsPath, ['parseMutationInput']),
207+
);
208+
}
209+
149210
const questionsArray =
150211
op.args.length > 0
151212
? buildQuestionsArray(op.args)
@@ -224,17 +285,36 @@ export function generateCustomCommand(op: CleanOperation, options?: CustomComman
224285
]),
225286
);
226287

288+
// For mutations with INPUT_OBJECT args (like `input: SignUpInput`),
289+
// parse JSON strings from CLI into proper objects
290+
if (hasInputObjectArg && op.args.length > 0) {
291+
bodyStatements.push(
292+
t.variableDeclaration('const', [
293+
t.variableDeclarator(
294+
t.identifier('parsedAnswers'),
295+
t.callExpression(t.identifier('parseMutationInput'), [
296+
t.identifier('answers'),
297+
]),
298+
),
299+
]),
300+
);
301+
}
302+
227303
const argsExpr =
228304
op.args.length > 0
229-
? t.identifier('answers')
305+
? (hasInputObjectArg
306+
? t.identifier('parsedAnswers')
307+
: t.identifier('answers'))
230308
: t.objectExpression([]);
231309

310+
const selectExpr = buildSelectObject(op.returnType, op.kind === 'mutation');
311+
232312
bodyStatements.push(
233313
t.variableDeclaration('const', [
234314
t.variableDeclarator(
235315
t.identifier('result'),
236316
t.awaitExpression(
237-
buildOrmCustomCall(opKind, op.name, argsExpr),
317+
buildOrmCustomCall(opKind, op.name, argsExpr, selectExpr),
238318
),
239319
),
240320
]),

0 commit comments

Comments
 (0)