@khanacademy/graphql-flow 3.0.1 → 3.1.1
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/.eslintrc.js +15 -4
- package/.github/workflows/changeset-release.yml +3 -5
- package/.github/workflows/pr-checks.yml +17 -17
- package/.prettierrc +2 -2
- package/.vscode/settings.json +6 -0
- package/CHANGELOG.md +12 -0
- package/dist/cli/config.js +6 -15
- package/dist/cli/run.js +26 -49
- package/dist/enums.js +9 -9
- package/dist/generateResponseType.js +36 -40
- package/dist/generateTypeFiles.js +10 -10
- package/dist/generateVariablesType.js +12 -13
- package/dist/index.js +4 -6
- package/dist/parser/parse.js +48 -56
- package/dist/parser/resolve.js +20 -16
- package/dist/parser/utils.js +29 -16
- package/dist/schemaFromIntrospectionData.js +5 -5
- package/dist/utils.js +8 -8
- package/package.json +12 -13
- package/schema.json +18 -2
- package/src/__test__/generateTypeFileContents.test.ts +26 -25
- package/src/__test__/graphql-flow.test.ts +32 -33
- package/src/__test__/processPragmas.test.ts +14 -13
- package/src/cli/__test__/config.test.ts +51 -56
- package/src/cli/config.ts +23 -20
- package/src/cli/run.ts +38 -52
- package/src/enums.ts +17 -17
- package/src/generateResponseType.ts +120 -91
- package/src/generateTypeFiles.ts +24 -22
- package/src/generateVariablesType.ts +20 -20
- package/src/index.ts +28 -29
- package/src/parser/__test__/parse.test.ts +114 -23
- package/src/parser/__test__/utils.test.ts +80 -0
- package/src/parser/parse.ts +126 -109
- package/src/parser/resolve.ts +64 -34
- package/src/parser/utils.ts +25 -15
- package/src/schemaFromIntrospectionData.ts +10 -8
- package/src/types.ts +57 -53
- package/src/utils.ts +30 -16
- package/tools/find-files-with-gql.ts +7 -11
|
@@ -1,40 +1,44 @@
|
|
|
1
1
|
/* eslint-disable no-console */
|
|
2
|
-
import generate from
|
|
3
|
-
import * as babelTypes from
|
|
4
|
-
import {TSType} from
|
|
2
|
+
import generate from "@babel/generator";
|
|
3
|
+
import * as babelTypes from "@babel/types";
|
|
4
|
+
import {TSType} from "@babel/types";
|
|
5
5
|
import type {
|
|
6
6
|
FieldNode,
|
|
7
7
|
IntrospectionOutputTypeRef,
|
|
8
8
|
OperationDefinitionNode,
|
|
9
9
|
FragmentDefinitionNode,
|
|
10
10
|
SelectionNode,
|
|
11
|
-
} from
|
|
12
|
-
import type {Context, Schema, Selections} from
|
|
11
|
+
} from "graphql";
|
|
12
|
+
import type {Context, Schema, Selections} from "./types";
|
|
13
13
|
import {
|
|
14
14
|
liftLeadingPropertyComments,
|
|
15
15
|
maybeAddDescriptionComment,
|
|
16
16
|
transferLeadingComments,
|
|
17
17
|
nullableType,
|
|
18
18
|
objectTypeFromProperties,
|
|
19
|
-
} from
|
|
20
|
-
import {enumTypeToFlow, scalarTypeToFlow} from
|
|
19
|
+
} from "./utils";
|
|
20
|
+
import {enumTypeToFlow, scalarTypeToFlow} from "./enums";
|
|
21
21
|
import type {
|
|
22
22
|
IntrospectionField,
|
|
23
23
|
IntrospectionInterfaceType,
|
|
24
24
|
IntrospectionObjectType,
|
|
25
25
|
IntrospectionUnionType,
|
|
26
|
-
} from
|
|
26
|
+
} from "graphql";
|
|
27
27
|
|
|
28
|
-
export const generateResponseType = (
|
|
28
|
+
export const generateResponseType = (
|
|
29
|
+
schema: Schema,
|
|
30
|
+
query: OperationDefinitionNode,
|
|
31
|
+
ctx: Context,
|
|
32
|
+
): string => {
|
|
29
33
|
const ast = querySelectionToObjectType(
|
|
30
34
|
ctx,
|
|
31
35
|
query.selectionSet.selections,
|
|
32
|
-
query.operation ===
|
|
36
|
+
query.operation === "mutation"
|
|
33
37
|
? schema.typesByName.Mutation
|
|
34
38
|
: schema.typesByName.Query,
|
|
35
|
-
query.operation ===
|
|
39
|
+
query.operation === "mutation" ? "mutation" : "query",
|
|
36
40
|
);
|
|
37
|
-
|
|
41
|
+
|
|
38
42
|
return generate(ast).code;
|
|
39
43
|
};
|
|
40
44
|
|
|
@@ -45,17 +49,17 @@ const sortedObjectTypeAnnotation = (
|
|
|
45
49
|
const obj = objectTypeFromProperties(
|
|
46
50
|
properties.sort((a, b) => {
|
|
47
51
|
if (
|
|
48
|
-
a.type ===
|
|
49
|
-
b.type ===
|
|
52
|
+
a.type === "TSPropertySignature" &&
|
|
53
|
+
b.type === "TSPropertySignature"
|
|
50
54
|
) {
|
|
51
|
-
const aName = a.key.type ===
|
|
52
|
-
const bName = b.key.type ===
|
|
55
|
+
const aName = a.key.type === "Identifier" ? a.key.name : "";
|
|
56
|
+
const bName = b.key.type === "Identifier" ? b.key.name : "";
|
|
53
57
|
return aName < bName ? -1 : 1;
|
|
54
58
|
}
|
|
55
59
|
return 0;
|
|
56
60
|
}),
|
|
57
61
|
);
|
|
58
|
-
const name = ctx.path.join(
|
|
62
|
+
const name = ctx.path.join("_");
|
|
59
63
|
const isTopLevelType = ctx.path.length <= 1;
|
|
60
64
|
if (ctx.allObjectTypes != null && !isTopLevelType) {
|
|
61
65
|
ctx.allObjectTypes[name] = obj;
|
|
@@ -65,7 +69,11 @@ const sortedObjectTypeAnnotation = (
|
|
|
65
69
|
}
|
|
66
70
|
};
|
|
67
71
|
|
|
68
|
-
export const generateFragmentType = (
|
|
72
|
+
export const generateFragmentType = (
|
|
73
|
+
schema: Schema,
|
|
74
|
+
fragment: FragmentDefinitionNode,
|
|
75
|
+
ctx: Context,
|
|
76
|
+
): string => {
|
|
69
77
|
const onType = fragment.typeCondition.name.value;
|
|
70
78
|
let ast;
|
|
71
79
|
|
|
@@ -95,28 +103,31 @@ export const generateFragmentType = (schema: Schema, fragment: FragmentDefinitio
|
|
|
95
103
|
throw new Error(`Unknown ${onType}`);
|
|
96
104
|
}
|
|
97
105
|
|
|
98
|
-
// eslint-disable-next-line flowtype-errors/uncovered
|
|
99
106
|
return generate(ast).code;
|
|
100
107
|
};
|
|
101
108
|
|
|
102
|
-
const _typeToFlow = (
|
|
103
|
-
|
|
109
|
+
const _typeToFlow = (
|
|
110
|
+
ctx: Context,
|
|
111
|
+
type: any,
|
|
112
|
+
selection: FieldNode,
|
|
113
|
+
): babelTypes.TSType => {
|
|
114
|
+
if (type.kind === "SCALAR") {
|
|
104
115
|
return scalarTypeToFlow(ctx, type.name);
|
|
105
116
|
}
|
|
106
|
-
if (type.kind ===
|
|
117
|
+
if (type.kind === "LIST") {
|
|
107
118
|
return babelTypes.tsTypeReference(
|
|
108
119
|
ctx.readOnlyArray
|
|
109
|
-
? babelTypes.identifier(
|
|
110
|
-
: babelTypes.identifier(
|
|
120
|
+
? babelTypes.identifier("ReadonlyArray")
|
|
121
|
+
: babelTypes.identifier("Array"),
|
|
111
122
|
babelTypes.tsTypeParameterInstantiation([
|
|
112
123
|
typeToFlow(ctx, type.ofType, selection),
|
|
113
124
|
]),
|
|
114
125
|
);
|
|
115
126
|
}
|
|
116
|
-
if (type.kind ===
|
|
127
|
+
if (type.kind === "UNION") {
|
|
117
128
|
const union = ctx.schema.unionsByName[type.name];
|
|
118
129
|
if (!selection.selectionSet) {
|
|
119
|
-
console.log(
|
|
130
|
+
console.log("no selection set", selection);
|
|
120
131
|
return babelTypes.tsAnyKeyword();
|
|
121
132
|
}
|
|
122
133
|
return unionOrInterfaceToFlow(
|
|
@@ -126,9 +137,9 @@ const _typeToFlow = (ctx: Context, type: any, selection: FieldNode): babelTypes.
|
|
|
126
137
|
);
|
|
127
138
|
}
|
|
128
139
|
|
|
129
|
-
if (type.kind ===
|
|
140
|
+
if (type.kind === "INTERFACE") {
|
|
130
141
|
if (!selection.selectionSet) {
|
|
131
|
-
console.log(
|
|
142
|
+
console.log("no selection set", selection);
|
|
132
143
|
return babelTypes.tsAnyKeyword();
|
|
133
144
|
}
|
|
134
145
|
return unionOrInterfaceToFlow(
|
|
@@ -137,22 +148,22 @@ const _typeToFlow = (ctx: Context, type: any, selection: FieldNode): babelTypes.
|
|
|
137
148
|
selection.selectionSet.selections,
|
|
138
149
|
);
|
|
139
150
|
}
|
|
140
|
-
if (type.kind ===
|
|
151
|
+
if (type.kind === "ENUM") {
|
|
141
152
|
return enumTypeToFlow(ctx, type.name);
|
|
142
153
|
}
|
|
143
|
-
if (type.kind !==
|
|
144
|
-
console.log(
|
|
154
|
+
if (type.kind !== "OBJECT") {
|
|
155
|
+
console.log("not object", type);
|
|
145
156
|
return babelTypes.tsAnyKeyword();
|
|
146
157
|
}
|
|
147
158
|
|
|
148
159
|
const tname = type.name;
|
|
149
160
|
if (!ctx.schema.typesByName[tname]) {
|
|
150
|
-
console.log(
|
|
161
|
+
console.log("unknown referenced type", tname);
|
|
151
162
|
return babelTypes.tsAnyKeyword();
|
|
152
163
|
}
|
|
153
164
|
const childType = ctx.schema.typesByName[tname];
|
|
154
165
|
if (!selection.selectionSet) {
|
|
155
|
-
console.log(
|
|
166
|
+
console.log("no selection set", selection);
|
|
156
167
|
return babelTypes.tsAnyKeyword();
|
|
157
168
|
}
|
|
158
169
|
return maybeAddDescriptionComment(
|
|
@@ -166,9 +177,13 @@ const _typeToFlow = (ctx: Context, type: any, selection: FieldNode): babelTypes.
|
|
|
166
177
|
);
|
|
167
178
|
};
|
|
168
179
|
|
|
169
|
-
export const typeToFlow = (
|
|
180
|
+
export const typeToFlow = (
|
|
181
|
+
ctx: Context,
|
|
182
|
+
type: IntrospectionOutputTypeRef,
|
|
183
|
+
selection: FieldNode,
|
|
184
|
+
): babelTypes.TSType => {
|
|
170
185
|
// throw new Error('npoe');
|
|
171
|
-
if (type.kind ===
|
|
186
|
+
if (type.kind === "NON_NULL") {
|
|
172
187
|
return _typeToFlow(ctx, type.ofType, selection);
|
|
173
188
|
}
|
|
174
189
|
// If we don'babelTypes care about strict nullability checking, then pretend everything is non-null
|
|
@@ -180,22 +195,25 @@ export const typeToFlow = (ctx: Context, type: IntrospectionOutputTypeRef, selec
|
|
|
180
195
|
return transferLeadingComments(inner, result);
|
|
181
196
|
};
|
|
182
197
|
|
|
183
|
-
const ensureOnlyOneTypenameProperty = (
|
|
198
|
+
const ensureOnlyOneTypenameProperty = (
|
|
199
|
+
properties: Array<babelTypes.TSPropertySignature>,
|
|
200
|
+
) => {
|
|
184
201
|
let seenTypeName: false | string = false;
|
|
185
202
|
return properties.filter((type) => {
|
|
186
203
|
// The apollo-utilities "addTypeName" utility will add it
|
|
187
204
|
// even if it's already specified :( so we have to filter out
|
|
188
205
|
// the extra one here.
|
|
189
206
|
if (
|
|
190
|
-
type.type ===
|
|
207
|
+
type.type === "TSPropertySignature" &&
|
|
191
208
|
type.key.type === "Identifier" &&
|
|
192
|
-
type.key.name ===
|
|
209
|
+
type.key.name === "__typename"
|
|
193
210
|
) {
|
|
194
211
|
const name =
|
|
195
|
-
type.typeAnnotation?.typeAnnotation.type ===
|
|
196
|
-
type.typeAnnotation.typeAnnotation.literal.type ===
|
|
212
|
+
type.typeAnnotation?.typeAnnotation.type === "TSLiteralType" &&
|
|
213
|
+
type.typeAnnotation.typeAnnotation.literal.type ===
|
|
214
|
+
"StringLiteral"
|
|
197
215
|
? type.typeAnnotation.typeAnnotation.literal.value
|
|
198
|
-
:
|
|
216
|
+
: "INVALID";
|
|
199
217
|
if (seenTypeName) {
|
|
200
218
|
if (name !== seenTypeName) {
|
|
201
219
|
throw new Error(
|
|
@@ -210,7 +228,12 @@ const ensureOnlyOneTypenameProperty = (properties: Array<babelTypes.TSPropertySi
|
|
|
210
228
|
});
|
|
211
229
|
};
|
|
212
230
|
|
|
213
|
-
const querySelectionToObjectType = (
|
|
231
|
+
const querySelectionToObjectType = (
|
|
232
|
+
ctx: Context,
|
|
233
|
+
selections: any,
|
|
234
|
+
type: any,
|
|
235
|
+
typeName: string,
|
|
236
|
+
): babelTypes.TSType => {
|
|
214
237
|
return sortedObjectTypeAnnotation(
|
|
215
238
|
ctx,
|
|
216
239
|
ensureOnlyOneTypenameProperty(
|
|
@@ -223,15 +246,15 @@ export const objectPropertiesToFlow = (
|
|
|
223
246
|
ctx: Context,
|
|
224
247
|
type: IntrospectionObjectType & {
|
|
225
248
|
fieldsByName: {
|
|
226
|
-
[name: string]: IntrospectionField
|
|
227
|
-
}
|
|
249
|
+
[name: string]: IntrospectionField;
|
|
250
|
+
};
|
|
228
251
|
},
|
|
229
252
|
typeName: string,
|
|
230
253
|
selections: Selections,
|
|
231
254
|
): Array<babelTypes.TSPropertySignature> => {
|
|
232
255
|
return selections.flatMap((selection) => {
|
|
233
256
|
switch (selection.kind) {
|
|
234
|
-
case
|
|
257
|
+
case "InlineFragment": {
|
|
235
258
|
const newTypeName =
|
|
236
259
|
selection.typeCondition?.name.value ?? typeName;
|
|
237
260
|
if (newTypeName !== typeName) {
|
|
@@ -244,7 +267,7 @@ export const objectPropertiesToFlow = (
|
|
|
244
267
|
selection.selectionSet.selections,
|
|
245
268
|
);
|
|
246
269
|
}
|
|
247
|
-
case
|
|
270
|
+
case "FragmentSpread":
|
|
248
271
|
if (!ctx.fragments[selection.name.value]) {
|
|
249
272
|
ctx.errors.push(
|
|
250
273
|
`No fragment named '${selection.name.value}'. Did you forget to include it in the template literal?`,
|
|
@@ -255,7 +278,7 @@ export const objectPropertiesToFlow = (
|
|
|
255
278
|
babelTypes.tsTypeAnnotation(
|
|
256
279
|
babelTypes.tsTypeReference(
|
|
257
280
|
babelTypes.identifier(`UNKNOWN_FRAGMENT`),
|
|
258
|
-
)
|
|
281
|
+
),
|
|
259
282
|
),
|
|
260
283
|
),
|
|
261
284
|
];
|
|
@@ -265,16 +288,15 @@ export const objectPropertiesToFlow = (
|
|
|
265
288
|
ctx,
|
|
266
289
|
type,
|
|
267
290
|
typeName,
|
|
268
|
-
ctx.fragments[selection.name.value].selectionSet
|
|
269
|
-
.selections,
|
|
291
|
+
ctx.fragments[selection.name.value].selectionSet.selections,
|
|
270
292
|
);
|
|
271
293
|
|
|
272
|
-
case
|
|
294
|
+
case "Field":
|
|
273
295
|
const name = selection.name.value;
|
|
274
296
|
const alias: string = selection.alias
|
|
275
297
|
? selection.alias.value
|
|
276
298
|
: name;
|
|
277
|
-
if (name ===
|
|
299
|
+
if (name === "__typename") {
|
|
278
300
|
return [
|
|
279
301
|
babelTypes.tsPropertySignature(
|
|
280
302
|
babelTypes.identifier(alias),
|
|
@@ -298,9 +320,9 @@ export const objectPropertiesToFlow = (
|
|
|
298
320
|
babelTypes.identifier(
|
|
299
321
|
`UNKNOWN_FIELD["${name}"]`,
|
|
300
322
|
),
|
|
301
|
-
)
|
|
323
|
+
),
|
|
302
324
|
),
|
|
303
|
-
)
|
|
325
|
+
),
|
|
304
326
|
];
|
|
305
327
|
}
|
|
306
328
|
const typeField = type.fieldsByName[name];
|
|
@@ -320,7 +342,7 @@ export const objectPropertiesToFlow = (
|
|
|
320
342
|
typeField.type,
|
|
321
343
|
selection,
|
|
322
344
|
),
|
|
323
|
-
)
|
|
345
|
+
),
|
|
324
346
|
),
|
|
325
347
|
),
|
|
326
348
|
),
|
|
@@ -328,7 +350,6 @@ export const objectPropertiesToFlow = (
|
|
|
328
350
|
|
|
329
351
|
default:
|
|
330
352
|
ctx.errors.push(
|
|
331
|
-
// @ts-expect-error: `selection` is `never` here
|
|
332
353
|
`Unsupported selection kind '${selection.kind}'`,
|
|
333
354
|
);
|
|
334
355
|
return [];
|
|
@@ -338,19 +359,21 @@ export const objectPropertiesToFlow = (
|
|
|
338
359
|
|
|
339
360
|
export const unionOrInterfaceToFlow = (
|
|
340
361
|
ctx: Context,
|
|
341
|
-
type:
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
362
|
+
type:
|
|
363
|
+
| IntrospectionUnionType
|
|
364
|
+
| (IntrospectionInterfaceType & {
|
|
365
|
+
fieldsByName: {
|
|
366
|
+
[key: string]: IntrospectionField;
|
|
367
|
+
};
|
|
368
|
+
}),
|
|
346
369
|
selections: Selections,
|
|
347
370
|
): TSType => {
|
|
348
371
|
const allFields = selections.every(
|
|
349
|
-
(selection) => selection.kind ===
|
|
372
|
+
(selection) => selection.kind === "Field",
|
|
350
373
|
);
|
|
351
374
|
const selectedAttributes: Array<{
|
|
352
|
-
attributes: Array<babelTypes.TSPropertySignature
|
|
353
|
-
typeName: string
|
|
375
|
+
attributes: Array<babelTypes.TSPropertySignature>;
|
|
376
|
+
typeName: string;
|
|
354
377
|
}> = type.possibleTypes
|
|
355
378
|
.slice()
|
|
356
379
|
.sort((a, b) => {
|
|
@@ -382,23 +405,25 @@ export const unionOrInterfaceToFlow = (
|
|
|
382
405
|
const sharedAttributes = selectedAttributes[0].attributes.slice();
|
|
383
406
|
const typeNameIndex = selectedAttributes[0].attributes.findIndex(
|
|
384
407
|
(x) =>
|
|
385
|
-
x.type ===
|
|
386
|
-
x.key.type ===
|
|
387
|
-
x.key.name ===
|
|
408
|
+
x.type === "TSPropertySignature" &&
|
|
409
|
+
x.key.type === "Identifier" &&
|
|
410
|
+
x.key.name === "__typename",
|
|
388
411
|
);
|
|
389
412
|
if (typeNameIndex !== -1) {
|
|
390
413
|
sharedAttributes[typeNameIndex] = babelTypes.tsPropertySignature(
|
|
391
|
-
babelTypes.identifier(
|
|
414
|
+
babelTypes.identifier("__typename"),
|
|
392
415
|
babelTypes.tsTypeAnnotation(
|
|
393
416
|
babelTypes.tsUnionType(
|
|
394
417
|
selectedAttributes.map(
|
|
395
418
|
(attrs) =>
|
|
396
|
-
(
|
|
397
|
-
|
|
398
|
-
|
|
419
|
+
(
|
|
420
|
+
attrs.attributes[
|
|
421
|
+
typeNameIndex
|
|
422
|
+
] as babelTypes.TSPropertySignature
|
|
423
|
+
).typeAnnotation!.typeAnnotation,
|
|
399
424
|
),
|
|
400
425
|
),
|
|
401
|
-
)
|
|
426
|
+
),
|
|
402
427
|
);
|
|
403
428
|
}
|
|
404
429
|
return sortedObjectTypeAnnotation(ctx, sharedAttributes);
|
|
@@ -447,17 +472,20 @@ export const unionOrInterfaceToFlow = (
|
|
|
447
472
|
),
|
|
448
473
|
),
|
|
449
474
|
);
|
|
450
|
-
const name = ctx.path.join(
|
|
475
|
+
const name = ctx.path.join("_");
|
|
451
476
|
if (ctx.allObjectTypes && ctx.path.length > 1) {
|
|
452
477
|
ctx.allObjectTypes[name] = result;
|
|
453
|
-
return babelTypes.tsTypeReference(
|
|
454
|
-
babelTypes.identifier(name),
|
|
455
|
-
);
|
|
478
|
+
return babelTypes.tsTypeReference(babelTypes.identifier(name));
|
|
456
479
|
}
|
|
457
480
|
return result;
|
|
458
481
|
};
|
|
459
|
-
const unionOrInterfaceSelection = (
|
|
460
|
-
|
|
482
|
+
const unionOrInterfaceSelection = (
|
|
483
|
+
config: Context,
|
|
484
|
+
type: any,
|
|
485
|
+
possible: any,
|
|
486
|
+
selection: SelectionNode,
|
|
487
|
+
): Array<babelTypes.TSPropertySignature> => {
|
|
488
|
+
if (selection.kind === "Field" && selection.name.value === "__typename") {
|
|
461
489
|
const alias = selection.alias
|
|
462
490
|
? selection.alias.value
|
|
463
491
|
: selection.name.value;
|
|
@@ -472,17 +500,17 @@ const unionOrInterfaceSelection = (config: Context, type: any, possible: any, se
|
|
|
472
500
|
),
|
|
473
501
|
];
|
|
474
502
|
}
|
|
475
|
-
if (selection.kind ===
|
|
503
|
+
if (selection.kind === "Field" && type.kind !== "UNION") {
|
|
476
504
|
// this is an interface
|
|
477
505
|
const name = selection.name.value;
|
|
478
506
|
const alias = selection.alias ? selection.alias.value : name;
|
|
479
507
|
if (!type.fieldsByName[name]) {
|
|
480
508
|
config.errors.push(
|
|
481
|
-
|
|
509
|
+
"Unknown field: " +
|
|
482
510
|
name +
|
|
483
|
-
|
|
511
|
+
" on type " +
|
|
484
512
|
type.name +
|
|
485
|
-
|
|
513
|
+
" for possible " +
|
|
486
514
|
possible.name,
|
|
487
515
|
);
|
|
488
516
|
return [
|
|
@@ -512,7 +540,7 @@ const unionOrInterfaceSelection = (config: Context, type: any, possible: any, se
|
|
|
512
540
|
),
|
|
513
541
|
];
|
|
514
542
|
}
|
|
515
|
-
if (selection.kind ===
|
|
543
|
+
if (selection.kind === "FragmentSpread") {
|
|
516
544
|
const fragment = config.fragments[selection.name.value];
|
|
517
545
|
if (!fragment) {
|
|
518
546
|
throw new Error(`Unknown fragment ${selection.name.value}`);
|
|
@@ -525,23 +553,24 @@ const unionOrInterfaceSelection = (config: Context, type: any, possible: any, se
|
|
|
525
553
|
]) ||
|
|
526
554
|
typeName === possible.name
|
|
527
555
|
) {
|
|
528
|
-
return fragment.selectionSet.selections.flatMap(
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
556
|
+
return fragment.selectionSet.selections.flatMap(
|
|
557
|
+
(selection: SelectionNode) =>
|
|
558
|
+
unionOrInterfaceSelection(
|
|
559
|
+
config,
|
|
560
|
+
config.schema.typesByName[possible.name],
|
|
561
|
+
possible,
|
|
562
|
+
selection,
|
|
563
|
+
),
|
|
535
564
|
);
|
|
536
565
|
} else {
|
|
537
566
|
return [];
|
|
538
567
|
}
|
|
539
568
|
}
|
|
540
|
-
if (selection.kind !==
|
|
569
|
+
if (selection.kind !== "InlineFragment") {
|
|
541
570
|
config.errors.push(
|
|
542
571
|
`union selectors must be inline fragment: found ${selection.kind}`,
|
|
543
572
|
);
|
|
544
|
-
if (type.kind ===
|
|
573
|
+
if (type.kind === "UNION") {
|
|
545
574
|
config.errors
|
|
546
575
|
.push(`You're trying to select a field from the union ${type.name},
|
|
547
576
|
but the only field you're allowed to select is "__typename".
|
package/src/generateTypeFiles.ts
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import type {DocumentNode} from
|
|
2
|
-
import type {GenerateConfig, CrawlConfig, Schema} from
|
|
3
|
-
import fs from
|
|
4
|
-
import path from
|
|
5
|
-
import {documentToFlowTypes} from
|
|
6
|
-
|
|
7
|
-
export const indexPrelude = (
|
|
1
|
+
import type {DocumentNode} from "graphql";
|
|
2
|
+
import type {GenerateConfig, CrawlConfig, Schema} from "./types";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import {documentToFlowTypes} from ".";
|
|
6
|
+
|
|
7
|
+
export const indexPrelude = (
|
|
8
|
+
regenerateCommand?: string,
|
|
9
|
+
): string => `// AUTOGENERATED
|
|
8
10
|
// NOTE: New response types are added to this file automatically.
|
|
9
11
|
// Outdated response types can be removed manually as they are deprecated.
|
|
10
|
-
//${regenerateCommand ?
|
|
12
|
+
//${regenerateCommand ? " To regenerate, run " + regenerateCommand : ""}
|
|
11
13
|
//
|
|
12
14
|
|
|
13
15
|
`;
|
|
@@ -20,10 +22,10 @@ export const generateTypeFileContents = (
|
|
|
20
22
|
generatedDir: string,
|
|
21
23
|
indexContents: string,
|
|
22
24
|
): {
|
|
23
|
-
indexContents: string
|
|
25
|
+
indexContents: string;
|
|
24
26
|
files: {
|
|
25
|
-
[key: string]: string
|
|
26
|
-
}
|
|
27
|
+
[key: string]: string;
|
|
28
|
+
};
|
|
27
29
|
} => {
|
|
28
30
|
const files: Record<string, any> = {};
|
|
29
31
|
|
|
@@ -31,7 +33,7 @@ export const generateTypeFileContents = (
|
|
|
31
33
|
const addToIndex = (filePath: string, typeName: unknown) => {
|
|
32
34
|
if (options.typeScript || options.omitFileExtensions) {
|
|
33
35
|
// Typescript doesn't like file extensions
|
|
34
|
-
filePath = filePath.replace(/\.ts$/,
|
|
36
|
+
filePath = filePath.replace(/\.ts$/, "");
|
|
35
37
|
}
|
|
36
38
|
const newLine = `export type {${typeName}} from './${path.basename(
|
|
37
39
|
filePath,
|
|
@@ -39,7 +41,7 @@ export const generateTypeFileContents = (
|
|
|
39
41
|
// We match the entire new line to avoid issues that can arise from
|
|
40
42
|
// prefix matches.
|
|
41
43
|
if (indexContents.indexOf(newLine) === -1) {
|
|
42
|
-
indexContents += newLine +
|
|
44
|
+
indexContents += newLine + "\n";
|
|
43
45
|
}
|
|
44
46
|
};
|
|
45
47
|
|
|
@@ -49,7 +51,7 @@ export const generateTypeFileContents = (
|
|
|
49
51
|
// We write all generated files to a `__generated__` subdir to keep
|
|
50
52
|
// things tidy.
|
|
51
53
|
const targetFileName = options.typeFileName
|
|
52
|
-
? options.typeFileName.replace(
|
|
54
|
+
? options.typeFileName.replace("[operationName]", name)
|
|
53
55
|
: `${name}.ts`;
|
|
54
56
|
const targetPath = path.join(generatedDir, targetFileName);
|
|
55
57
|
|
|
@@ -60,7 +62,7 @@ export const generateTypeFileContents = (
|
|
|
60
62
|
)}'\n` +
|
|
61
63
|
(options.regenerateCommand
|
|
62
64
|
? `// To regenerate, run '${options.regenerateCommand}'.\n`
|
|
63
|
-
:
|
|
65
|
+
: "") +
|
|
64
66
|
code;
|
|
65
67
|
if (options.splitTypes && !isFragment) {
|
|
66
68
|
fileContents +=
|
|
@@ -88,7 +90,7 @@ export const generateTypeFileContents = (
|
|
|
88
90
|
fileContents
|
|
89
91
|
// Remove whitespace from the ends of lines; babel's generate sometimes
|
|
90
92
|
// leaves them hanging around.
|
|
91
|
-
.replace(/\s+$/gm,
|
|
93
|
+
.replace(/\s+$/gm, "") + "\n";
|
|
92
94
|
},
|
|
93
95
|
);
|
|
94
96
|
|
|
@@ -96,7 +98,7 @@ export const generateTypeFileContents = (
|
|
|
96
98
|
};
|
|
97
99
|
|
|
98
100
|
const getGeneratedDir = (fileName: string, options: GenerateConfig) => {
|
|
99
|
-
const generatedDirectory = options.generatedDirectory ??
|
|
101
|
+
const generatedDirectory = options.generatedDirectory ?? "__generated__";
|
|
100
102
|
if (path.isAbsolute(generatedDirectory)) {
|
|
101
103
|
// fileName is absolute here, so we make it relative to cwd
|
|
102
104
|
// for more reasonable filenames. We convert leading ..'s
|
|
@@ -105,7 +107,7 @@ const getGeneratedDir = (fileName: string, options: GenerateConfig) => {
|
|
|
105
107
|
generatedDirectory,
|
|
106
108
|
path
|
|
107
109
|
.relative(process.cwd(), path.dirname(fileName))
|
|
108
|
-
.replace(/\.\.\//g,
|
|
110
|
+
.replace(/\.\.\//g, "__/"),
|
|
109
111
|
);
|
|
110
112
|
} else {
|
|
111
113
|
return path.join(path.dirname(fileName), generatedDirectory);
|
|
@@ -119,7 +121,7 @@ export const generateTypeFiles = (
|
|
|
119
121
|
options: GenerateConfig,
|
|
120
122
|
) => {
|
|
121
123
|
const generatedDir = getGeneratedDir(fileName, options);
|
|
122
|
-
const indexFile = path.join(generatedDir,
|
|
124
|
+
const indexFile = path.join(generatedDir, "index.ts");
|
|
123
125
|
|
|
124
126
|
if (!fs.existsSync(generatedDir)) {
|
|
125
127
|
fs.mkdirSync(generatedDir, {recursive: true});
|
|
@@ -134,7 +136,7 @@ export const generateTypeFiles = (
|
|
|
134
136
|
document,
|
|
135
137
|
options,
|
|
136
138
|
generatedDir,
|
|
137
|
-
fs.readFileSync(indexFile,
|
|
139
|
+
fs.readFileSync(indexFile, "utf8"),
|
|
138
140
|
);
|
|
139
141
|
|
|
140
142
|
fs.writeFileSync(indexFile, indexContents);
|
|
@@ -150,8 +152,8 @@ export const processPragmas = (
|
|
|
150
152
|
crawlConfig: CrawlConfig,
|
|
151
153
|
rawSource: string,
|
|
152
154
|
): {
|
|
153
|
-
generate: boolean
|
|
154
|
-
strict?: boolean
|
|
155
|
+
generate: boolean;
|
|
156
|
+
strict?: boolean;
|
|
155
157
|
} => {
|
|
156
158
|
if (
|
|
157
159
|
crawlConfig.ignorePragma &&
|