Skip to content

Commit 97f9cf3

Browse files
committed
refactor(codegen): normalize cli source args
1 parent 5c0b123 commit 97f9cf3

6 files changed

Lines changed: 129 additions & 14 deletions

File tree

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import {
2+
buildGenerateOptions,
3+
seedArgvFromConfig,
4+
} from '../../cli/shared';
5+
6+
describe('CLI shared utilities', () => {
7+
it('preserves explicit false booleans from argv when merging with file config', () => {
8+
const fileConfig = {
9+
reactQuery: true,
10+
orm: true,
11+
dryRun: true,
12+
};
13+
14+
const seeded = seedArgvFromConfig(
15+
{ reactQuery: false, orm: false },
16+
fileConfig,
17+
);
18+
19+
expect(seeded).toMatchObject({
20+
reactQuery: false,
21+
orm: false,
22+
});
23+
});
24+
25+
it('normalizes list options through buildGenerateOptions in non-interactive flows', () => {
26+
const options = buildGenerateOptions(
27+
{
28+
schemas: 'public, app',
29+
apiNames: 'core, admin',
30+
},
31+
{},
32+
);
33+
34+
expect(options.db).toEqual({
35+
schemas: ['public', 'app'],
36+
apiNames: ['core', 'admin'],
37+
});
38+
});
39+
});

graphql/codegen/src/cli/index.ts

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import {
1515
buildGenerateOptions,
1616
camelizeArgv,
1717
codegenQuestions,
18+
hasResolvedCodegenSource,
19+
normalizeCodegenListOptions,
1820
printResult,
1921
seedArgvFromConfig,
2022
} from './shared';
@@ -105,7 +107,9 @@ export const commands = async (
105107
}
106108

107109
const cliOptions = buildDbConfig(
108-
camelizeArgv(argv as Record<string, any>),
110+
normalizeCodegenListOptions(
111+
camelizeArgv(argv as Record<string, any>),
112+
),
109113
);
110114
let hasError = false;
111115
for (const name of names) {
@@ -127,7 +131,9 @@ export const commands = async (
127131
}
128132

129133
const seeded = seedArgvFromConfig(argv, fileConfig);
130-
const answers = await prompter.prompt(seeded, codegenQuestions);
134+
const answers = hasResolvedCodegenSource(seeded)
135+
? seeded
136+
: await prompter.prompt(seeded, codegenQuestions);
131137
const options = buildGenerateOptions(answers, fileConfig);
132138
const result = await generate(options);
133139
printResult(result);
@@ -147,15 +153,6 @@ export const options: Partial<CLIOptions> = {
147153
a: 'authorization',
148154
v: 'verbose',
149155
},
150-
boolean: [
151-
'help',
152-
'version',
153-
'verbose',
154-
'dry-run',
155-
'react-query',
156-
'orm',
157-
'keep-db',
158-
],
159156
string: [
160157
'config',
161158
'endpoint',

graphql/codegen/src/cli/shared.ts

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,25 @@ export const splitCommas = (
2121
.filter(Boolean);
2222
};
2323

24+
function normalizeListOption(
25+
input: unknown,
26+
): string[] | undefined {
27+
if (Array.isArray(input)) {
28+
return input
29+
.flatMap((item) =>
30+
typeof item === 'string' ? (splitCommas(item) ?? []) : [String(item)],
31+
)
32+
.map((s) => s.trim())
33+
.filter(Boolean);
34+
}
35+
36+
if (typeof input === 'string') {
37+
return splitCommas(input);
38+
}
39+
40+
return undefined;
41+
}
42+
2443
export interface CodegenAnswers {
2544
endpoint?: string;
2645
schemaFile?: string;
@@ -188,6 +207,41 @@ export function buildDbConfig(
188207
return rest;
189208
}
190209

210+
/**
211+
* Normalizes top-level list-like CLI options to string arrays.
212+
* This keeps non-interactive paths equivalent to prompt sanitize behavior.
213+
*/
214+
export function normalizeCodegenListOptions(
215+
options: Record<string, unknown>,
216+
): Record<string, unknown> {
217+
return {
218+
...options,
219+
schemas: normalizeListOption(options.schemas),
220+
apiNames: normalizeListOption(options.apiNames),
221+
};
222+
}
223+
224+
/**
225+
* Returns true when source options are already available, so prompting can be skipped.
226+
*/
227+
export function hasResolvedCodegenSource(
228+
options: Record<string, unknown>,
229+
): boolean {
230+
const normalized = normalizeCodegenListOptions(camelizeArgv(options));
231+
const db = normalized.db as Record<string, unknown> | undefined;
232+
const dbSchemas = normalizeListOption(db?.schemas);
233+
const dbApiNames = normalizeListOption(db?.apiNames);
234+
235+
return Boolean(
236+
normalized.endpoint ||
237+
normalized.schemaFile ||
238+
(normalized.schemas as string[] | undefined)?.length ||
239+
(normalized.apiNames as string[] | undefined)?.length ||
240+
dbSchemas?.length ||
241+
dbApiNames?.length,
242+
);
243+
}
244+
191245
export function seedArgvFromConfig(
192246
argv: Record<string, unknown>,
193247
fileConfig: GraphQLSDKConfigTarget,
@@ -203,6 +257,7 @@ export function buildGenerateOptions(
203257
fileConfig: GraphQLSDKConfigTarget = {},
204258
): GraphQLSDKConfigTarget {
205259
const camelized = camelizeArgv(answers);
206-
const withDb = buildDbConfig(camelized);
260+
const normalized = normalizeCodegenListOptions(camelized);
261+
const withDb = buildDbConfig(normalized);
207262
return { ...fileConfig, ...withDb } as GraphQLSDKConfigTarget;
208263
}

graphql/codegen/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@ export {
3737
codegenQuestions,
3838
filterDefined,
3939
flattenDbFields,
40+
hasResolvedCodegenSource,
4041
hyphenateKeys,
42+
normalizeCodegenListOptions,
4143
printResult,
4244
seedArgvFromConfig,
4345
splitCommas,

packages/cli/__tests__/codegen.test.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,27 @@ jest.mock('@constructive-io/graphql-codegen', () => {
3939
}),
4040
camelizeArgv: jest.fn((argv: Record<string, any>) => argv),
4141
seedArgvFromConfig: jest.fn((argv: Record<string, unknown>, _fileConfig: any) => argv),
42+
hasResolvedCodegenSource: jest.fn((argv: Record<string, unknown>) => {
43+
const db = argv.db as Record<string, unknown> | undefined;
44+
return Boolean(
45+
argv.endpoint ||
46+
argv['schema-file'] ||
47+
argv.schemas ||
48+
argv['api-names'] ||
49+
db?.schemas ||
50+
db?.apiNames
51+
);
52+
}),
4253
buildGenerateOptions: jest.fn((answers: Record<string, unknown>, _fileConfig: any) => {
4354
const { schemas, apiNames, ...rest } = answers;
55+
const normalizedSchemas = Array.isArray(schemas)
56+
? schemas
57+
: splitCommasMock(schemas as string | undefined);
58+
const normalizedApiNames = Array.isArray(apiNames)
59+
? apiNames
60+
: splitCommasMock(apiNames as string | undefined);
4461
if (schemas || apiNames) {
45-
return { ...rest, db: { schemas, apiNames } };
62+
return { ...rest, db: { schemas: normalizedSchemas, apiNames: normalizedApiNames } };
4663
}
4764
return rest;
4865
}),
@@ -101,6 +118,7 @@ describe('codegen command', () => {
101118

102119
await codegenCommand(argv, mockPrompter as any, {} as any)
103120

121+
expect(mockPrompter.prompt).not.toHaveBeenCalled()
104122
expect(mockGenerate).toHaveBeenCalled()
105123
const call = mockGenerate.mock.calls[0][0]
106124
expect(call).toMatchObject({
@@ -125,6 +143,7 @@ describe('codegen command', () => {
125143

126144
await codegenCommand(argv, mockPrompter as any, {} as any)
127145

146+
expect(mockPrompter.prompt).not.toHaveBeenCalled()
128147
expect(mockGenerate).toHaveBeenCalled()
129148
const call = mockGenerate.mock.calls[0][0]
130149
expect(call.db).toEqual({ schemas: ['public', 'app'], apiNames: undefined })

packages/cli/src/commands/codegen.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
printResult,
88
buildGenerateOptions,
99
seedArgvFromConfig,
10+
hasResolvedCodegenSource,
1011
type GraphQLSDKConfigTarget,
1112
} from '@constructive-io/graphql-codegen';
1213

@@ -63,7 +64,9 @@ export default async (
6364
}
6465

6566
const seeded = seedArgvFromConfig(argv as Record<string, unknown>, fileConfig);
66-
const answers = await prompter.prompt(seeded, codegenQuestions);
67+
const answers = hasResolvedCodegenSource(seeded)
68+
? seeded
69+
: await prompter.prompt(seeded, codegenQuestions);
6770
const options = buildGenerateOptions(answers, fileConfig);
6871
const result = await generate(options);
6972
printResult(result);

0 commit comments

Comments
 (0)