@khanacademy/graphql-flow 3.1.3 → 3.3.0
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/CHANGELOG.md +14 -0
- package/dist/cli/config.js +59 -7
- package/dist/cli/run.js +8 -21
- package/dist/enums.js +2 -2
- package/dist/generateResponseType.js +6 -2
- package/dist/generateVariablesType.js +3 -1
- package/dist/index.js +10 -7
- package/package.json +3 -2
- package/schema.json +3 -0
- package/src/__test__/example-schema.graphql +1 -0
- package/src/__test__/graphql-flow.test.ts +33 -0
- package/src/cli/__test__/config.test.ts +40 -1
- package/src/cli/config.ts +83 -6
- package/src/cli/run.ts +16 -42
- package/src/enums.ts +2 -2
- package/src/generateResponseType.ts +2 -2
- package/src/generateVariablesType.ts +3 -1
- package/src/index.ts +12 -4
- package/src/types.ts +8 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @khanacademy/graphql-flow
|
|
2
2
|
|
|
3
|
+
## 3.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- b9fc899: Adds support for marking input fields as @deprecated
|
|
8
|
+
|
|
9
|
+
## 3.2.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 96c201e: Add a noComments config option to exclude all comments in the output
|
|
14
|
+
- 7c01b6a: Add a --schema-file graphql argument, and allow exporting a function from the config.js file
|
|
15
|
+
- 96c201e: Add a `noComments` config option to exclude all comments
|
|
16
|
+
|
|
3
17
|
## 3.1.3
|
|
4
18
|
|
|
5
19
|
### Patch Changes
|
package/dist/cli/config.js
CHANGED
|
@@ -3,12 +3,15 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.validateOrThrow = exports.loadConfigFile = exports.getSchemas = exports.findApplicableConfig = void 0;
|
|
7
|
-
var _schemaFromIntrospectionData = require("../schemaFromIntrospectionData");
|
|
6
|
+
exports.validateOrThrow = exports.parseCliOptions = exports.makeAbsPath = exports.loadConfigFile = exports.getSchemas = exports.getInputFiles = exports.findApplicableConfig = void 0;
|
|
8
7
|
var _schema = _interopRequireDefault(require("../../schema.json"));
|
|
8
|
+
var _schemaFromIntrospectionData = require("../schemaFromIntrospectionData");
|
|
9
9
|
var _fs = _interopRequireDefault(require("fs"));
|
|
10
10
|
var _graphql = require("graphql");
|
|
11
11
|
var _jsonschema = require("jsonschema");
|
|
12
|
+
var _minimist = _interopRequireDefault(require("minimist"));
|
|
13
|
+
var _child_process = require("child_process");
|
|
14
|
+
var _path = _interopRequireDefault(require("path"));
|
|
12
15
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
13
16
|
const validateOrThrow = (value, jsonSchema) => {
|
|
14
17
|
const result = (0, _jsonschema.validate)(value, jsonSchema);
|
|
@@ -17,16 +20,47 @@ const validateOrThrow = (value, jsonSchema) => {
|
|
|
17
20
|
}
|
|
18
21
|
};
|
|
19
22
|
exports.validateOrThrow = validateOrThrow;
|
|
20
|
-
const
|
|
21
|
-
|
|
23
|
+
const makeAbsPath = (maybeRelativePath, basePath) => {
|
|
24
|
+
return _path.default.isAbsolute(maybeRelativePath) ? maybeRelativePath : _path.default.join(basePath, maybeRelativePath);
|
|
25
|
+
};
|
|
26
|
+
exports.makeAbsPath = makeAbsPath;
|
|
27
|
+
const loadConfigFile = options => {
|
|
28
|
+
let data = require(options.configFilePath);
|
|
29
|
+
if (typeof data === "function") {
|
|
30
|
+
data = data(options);
|
|
31
|
+
} else {
|
|
32
|
+
if (options.schemaFile) {
|
|
33
|
+
if (Array.isArray(data.generate)) {
|
|
34
|
+
data.generate.forEach(item => {
|
|
35
|
+
item.schemaFilePath = options.schemaFile;
|
|
36
|
+
});
|
|
37
|
+
} else {
|
|
38
|
+
data.generate.schemaFilePath = options.schemaFile;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
22
42
|
validateOrThrow(data, _schema.default);
|
|
23
43
|
return data;
|
|
24
44
|
};
|
|
45
|
+
exports.loadConfigFile = loadConfigFile;
|
|
46
|
+
const findGraphqlTagReferences = root => {
|
|
47
|
+
// NOTE(john): We want to include untracked files here so that we can
|
|
48
|
+
// generate types for them. This is useful for when we have a new file
|
|
49
|
+
// that we want to generate types for, but we haven't committed it yet.
|
|
50
|
+
const response = (0, _child_process.execSync)("git grep -I --word-regexp --name-only --fixed-strings --untracked 'graphql-tag' -- '*.js' '*.jsx' '*.ts' '*.tsx'", {
|
|
51
|
+
encoding: "utf8",
|
|
52
|
+
cwd: root
|
|
53
|
+
});
|
|
54
|
+
return response.trim().split("\n").map(relative => _path.default.join(root, relative));
|
|
55
|
+
};
|
|
56
|
+
const getInputFiles = (options, config) => {
|
|
57
|
+
return options.cliFiles.length ? options.cliFiles : findGraphqlTagReferences(makeAbsPath(config.crawl.root, _path.default.dirname(options.configFilePath)));
|
|
58
|
+
};
|
|
25
59
|
|
|
26
60
|
/**
|
|
27
61
|
* Loads a .json 'introspection query response', or a .graphql schema definition.
|
|
28
62
|
*/
|
|
29
|
-
exports.
|
|
63
|
+
exports.getInputFiles = getInputFiles;
|
|
30
64
|
const getSchemas = schemaFilePath => {
|
|
31
65
|
const raw = _fs.default.readFileSync(schemaFilePath, "utf8");
|
|
32
66
|
if (schemaFilePath.endsWith(".graphql")) {
|
|
@@ -34,7 +68,8 @@ const getSchemas = schemaFilePath => {
|
|
|
34
68
|
const queryResponse = (0, _graphql.graphqlSync)({
|
|
35
69
|
schema: schemaForValidation,
|
|
36
70
|
source: (0, _graphql.getIntrospectionQuery)({
|
|
37
|
-
descriptions: true
|
|
71
|
+
descriptions: true,
|
|
72
|
+
inputValueDeprecation: true
|
|
38
73
|
})
|
|
39
74
|
});
|
|
40
75
|
const schemaForTypeGeneration = (0, _schemaFromIntrospectionData.schemaFromIntrospectionData)(queryResponse.data);
|
|
@@ -46,13 +81,30 @@ const getSchemas = schemaFilePath => {
|
|
|
46
81
|
return [schemaForValidation, schemaForTypeGeneration];
|
|
47
82
|
}
|
|
48
83
|
};
|
|
84
|
+
exports.getSchemas = getSchemas;
|
|
85
|
+
const parseCliOptions = (argv, relativeBase) => {
|
|
86
|
+
const args = (0, _minimist.default)(argv);
|
|
87
|
+
let [configFilePath, ...cliFiles] = args._;
|
|
88
|
+
if (args.h || args.help || !configFilePath) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
configFilePath = makeAbsPath(configFilePath, relativeBase);
|
|
92
|
+
const options = {
|
|
93
|
+
configFilePath,
|
|
94
|
+
cliFiles
|
|
95
|
+
};
|
|
96
|
+
if (args["schema-file"]) {
|
|
97
|
+
options.schemaFile = args["schema-file"];
|
|
98
|
+
}
|
|
99
|
+
return options;
|
|
100
|
+
};
|
|
49
101
|
|
|
50
102
|
/**
|
|
51
103
|
* Find the first item of the `config.generate` array where both:
|
|
52
104
|
* - no item of `exclude` matches
|
|
53
105
|
* - at least one item of `match` matches
|
|
54
106
|
*/
|
|
55
|
-
exports.
|
|
107
|
+
exports.parseCliOptions = parseCliOptions;
|
|
56
108
|
const findApplicableConfig = (path, configs) => {
|
|
57
109
|
if (!Array.isArray(configs)) {
|
|
58
110
|
configs = [configs];
|
package/dist/cli/run.js
CHANGED
|
@@ -8,7 +8,6 @@ var _resolve = require("../parser/resolve");
|
|
|
8
8
|
var _utils = require("../parser/utils");
|
|
9
9
|
var _config = require("./config");
|
|
10
10
|
var _apolloUtilities = require("apollo-utilities");
|
|
11
|
-
var _child_process = require("child_process");
|
|
12
11
|
var _fs = require("fs");
|
|
13
12
|
var _printer = require("graphql/language/printer");
|
|
14
13
|
var _validation = require("graphql/validation");
|
|
@@ -26,29 +25,17 @@ function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e;
|
|
|
26
25
|
* 4) generate types for all resolved Queries & Mutations
|
|
27
26
|
*/
|
|
28
27
|
/** Step (1) */
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
// generate types for them. This is useful for when we have a new file
|
|
32
|
-
// that we want to generate types for, but we haven't committed it yet.
|
|
33
|
-
const response = (0, _child_process.execSync)("git grep -I --word-regexp --name-only --fixed-strings --untracked 'graphql-tag' -- '*.js' '*.jsx' '*.ts' '*.tsx'", {
|
|
34
|
-
encoding: "utf8",
|
|
35
|
-
cwd: root
|
|
36
|
-
});
|
|
37
|
-
return response.trim().split("\n").map(relative => _path.default.join(root, relative));
|
|
38
|
-
};
|
|
39
|
-
const [_, __, configFilePath, ...cliFiles] = process.argv;
|
|
40
|
-
if (configFilePath === "-h" || configFilePath === "--help" || configFilePath === "help" || !configFilePath) {
|
|
28
|
+
const options = (0, _config.parseCliOptions)(process.argv.slice(2), process.cwd());
|
|
29
|
+
if (options === false) {
|
|
41
30
|
console.log(`graphql-flow
|
|
42
31
|
|
|
43
|
-
Usage: graphql-flow [configFile.json] [filesToCrawl...]
|
|
32
|
+
Usage: graphql-flow [configFile.json] [filesToCrawl...]
|
|
33
|
+
Options:
|
|
34
|
+
--schema-file: provide a schema file to override the one defined in config`);
|
|
44
35
|
process.exit(1);
|
|
45
36
|
}
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
};
|
|
49
|
-
const absConfigPath = makeAbsPath(configFilePath, process.cwd());
|
|
50
|
-
const config = (0, _config.loadConfigFile)(absConfigPath);
|
|
51
|
-
const inputFiles = cliFiles.length ? cliFiles : findGraphqlTagReferences(makeAbsPath(config.crawl.root, _path.default.dirname(absConfigPath)));
|
|
37
|
+
const config = (0, _config.loadConfigFile)(options);
|
|
38
|
+
const inputFiles = (0, _config.getInputFiles)(options, config);
|
|
52
39
|
|
|
53
40
|
/** Step (2) */
|
|
54
41
|
|
|
@@ -101,7 +88,7 @@ console.log(Object.keys(resolved).length, "resolved queries");
|
|
|
101
88
|
const schemaCache = {};
|
|
102
89
|
const getCachedSchemas = schemaFilePath => {
|
|
103
90
|
if (!schemaCache[schemaFilePath]) {
|
|
104
|
-
schemaCache[schemaFilePath] = (0, _config.getSchemas)(makeAbsPath(schemaFilePath, _path.default.dirname(
|
|
91
|
+
schemaCache[schemaFilePath] = (0, _config.getSchemas)((0, _config.makeAbsPath)(schemaFilePath, _path.default.dirname(options.configFilePath)));
|
|
105
92
|
}
|
|
106
93
|
return schemaCache[schemaFilePath];
|
|
107
94
|
};
|
package/dist/enums.js
CHANGED
|
@@ -19,7 +19,7 @@ const experimentalEnumTypeToFlow = (ctx, enumConfig, description) => {
|
|
|
19
19
|
if (ctx.experimentalEnumsMap) {
|
|
20
20
|
ctx.experimentalEnumsMap[enumConfig.name] = enumDeclaration;
|
|
21
21
|
}
|
|
22
|
-
return (0, _utils.maybeAddDescriptionComment)(description, babelTypes.tsTypeReference(enumDeclaration.id));
|
|
22
|
+
return (0, _utils.maybeAddDescriptionComment)(ctx.noComments ? null : description, babelTypes.tsTypeReference(enumDeclaration.id));
|
|
23
23
|
};
|
|
24
24
|
exports.experimentalEnumTypeToFlow = experimentalEnumTypeToFlow;
|
|
25
25
|
const enumTypeToFlow = (ctx, name) => {
|
|
@@ -28,7 +28,7 @@ const enumTypeToFlow = (ctx, name) => {
|
|
|
28
28
|
if (enumConfig.description) {
|
|
29
29
|
combinedDescription = enumConfig.description + "\n\n" + combinedDescription;
|
|
30
30
|
}
|
|
31
|
-
return ctx.experimentalEnumsMap ? experimentalEnumTypeToFlow(ctx, enumConfig, combinedDescription) : (0, _utils.maybeAddDescriptionComment)(combinedDescription, babelTypes.tsUnionType(enumConfig.enumValues.map(n => babelTypes.tsLiteralType(babelTypes.stringLiteral(n.name)))));
|
|
31
|
+
return ctx.experimentalEnumsMap ? experimentalEnumTypeToFlow(ctx, enumConfig, combinedDescription) : (0, _utils.maybeAddDescriptionComment)(ctx.noComments ? null : combinedDescription, babelTypes.tsUnionType(enumConfig.enumValues.map(n => babelTypes.tsLiteralType(babelTypes.stringLiteral(n.name)))));
|
|
32
32
|
};
|
|
33
33
|
exports.enumTypeToFlow = enumTypeToFlow;
|
|
34
34
|
const builtinScalars = exports.builtinScalars = {
|
|
@@ -15,7 +15,9 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
|
|
|
15
15
|
|
|
16
16
|
const generateResponseType = (schema, query, ctx) => {
|
|
17
17
|
const ast = querySelectionToObjectType(ctx, query.selectionSet.selections, query.operation === "mutation" ? schema.typesByName.Mutation : schema.typesByName.Query, query.operation === "mutation" ? "mutation" : "query");
|
|
18
|
-
return (0, _generator.default)(ast
|
|
18
|
+
return (0, _generator.default)(ast, {
|
|
19
|
+
comments: ctx.noComments ? false : true
|
|
20
|
+
}).code;
|
|
19
21
|
};
|
|
20
22
|
exports.generateResponseType = generateResponseType;
|
|
21
23
|
const sortedObjectTypeAnnotation = (ctx, properties) => {
|
|
@@ -48,7 +50,9 @@ const generateFragmentType = (schema, fragment, ctx) => {
|
|
|
48
50
|
} else {
|
|
49
51
|
throw new Error(`Unknown ${onType}`);
|
|
50
52
|
}
|
|
51
|
-
return (0, _generator.default)(ast
|
|
53
|
+
return (0, _generator.default)(ast, {
|
|
54
|
+
comments: ctx.noComments ? false : true
|
|
55
|
+
}).code;
|
|
52
56
|
};
|
|
53
57
|
exports.generateFragmentType = generateFragmentType;
|
|
54
58
|
const _typeToFlow = (ctx, type, selection) => {
|
|
@@ -82,6 +82,8 @@ const generateVariablesType = (schema, item, ctx) => {
|
|
|
82
82
|
const variableObject = (0, _utils.objectTypeFromProperties)((item.variableDefinitions || []).map(vbl => {
|
|
83
83
|
return maybeOptionalObjectTypeProperty(vbl.variable.name.value, variableToFlow(ctx, vbl.type));
|
|
84
84
|
}));
|
|
85
|
-
return (0, _generator.default)(variableObject
|
|
85
|
+
return (0, _generator.default)(variableObject, {
|
|
86
|
+
comments: ctx.noComments ? false : true
|
|
87
|
+
}).code;
|
|
86
88
|
};
|
|
87
89
|
exports.generateVariablesType = generateVariablesType;
|
package/dist/index.js
CHANGED
|
@@ -25,7 +25,8 @@ const optionsToConfig = (schema, definitions, options, errors = []) => {
|
|
|
25
25
|
readOnlyArray: (_options$readOnlyArra = options === null || options === void 0 ? void 0 : options.readOnlyArray) !== null && _options$readOnlyArra !== void 0 ? _options$readOnlyArra : true,
|
|
26
26
|
scalars: (_options$scalars = options === null || options === void 0 ? void 0 : options.scalars) !== null && _options$scalars !== void 0 ? _options$scalars : {},
|
|
27
27
|
typeScript: (_options$typeScript = options === null || options === void 0 ? void 0 : options.typeScript) !== null && _options$typeScript !== void 0 ? _options$typeScript : false,
|
|
28
|
-
omitFileExtensions: (_options$omitFileExte = options === null || options === void 0 ? void 0 : options.omitFileExtensions) !== null && _options$omitFileExte !== void 0 ? _options$omitFileExte : false
|
|
28
|
+
omitFileExtensions: (_options$omitFileExte = options === null || options === void 0 ? void 0 : options.omitFileExtensions) !== null && _options$omitFileExte !== void 0 ? _options$omitFileExte : false,
|
|
29
|
+
noComments: options === null || options === void 0 ? void 0 : options.noComments
|
|
29
30
|
};
|
|
30
31
|
const fragments = {};
|
|
31
32
|
definitions.forEach(def => {
|
|
@@ -66,8 +67,8 @@ const documentToFlowTypes = (document, schema, options) => {
|
|
|
66
67
|
path: [name],
|
|
67
68
|
allObjectTypes: options !== null && options !== void 0 && options.exportAllObjectTypes ? types : null
|
|
68
69
|
})};`;
|
|
69
|
-
const extraTypes = codegenExtraTypes(types);
|
|
70
|
-
const experimentalEnums = codegenExtraTypes(config.experimentalEnumsMap || {});
|
|
70
|
+
const extraTypes = codegenExtraTypes(types, config);
|
|
71
|
+
const experimentalEnums = codegenExtraTypes(config.experimentalEnumsMap || {}, config);
|
|
71
72
|
return {
|
|
72
73
|
name,
|
|
73
74
|
typeName: name,
|
|
@@ -93,8 +94,8 @@ const documentToFlowTypes = (document, schema, options) => {
|
|
|
93
94
|
// TODO(jared): Maybe make this template configurable?
|
|
94
95
|
// We'll see what's required to get webapp on board.
|
|
95
96
|
const code = `export type ${typeName} = {\n variables: ${variables},\n response: ${response}\n};`;
|
|
96
|
-
const extraTypes = codegenExtraTypes(types);
|
|
97
|
-
const experimentalEnums = codegenExtraTypes(config.experimentalEnumsMap || {});
|
|
97
|
+
const extraTypes = codegenExtraTypes(types, config);
|
|
98
|
+
const experimentalEnums = codegenExtraTypes(config.experimentalEnumsMap || {}, config);
|
|
98
99
|
return {
|
|
99
100
|
name,
|
|
100
101
|
typeName,
|
|
@@ -110,10 +111,12 @@ const documentToFlowTypes = (document, schema, options) => {
|
|
|
110
111
|
return result;
|
|
111
112
|
};
|
|
112
113
|
exports.documentToFlowTypes = documentToFlowTypes;
|
|
113
|
-
function codegenExtraTypes(types) {
|
|
114
|
+
function codegenExtraTypes(types, ctx) {
|
|
114
115
|
const extraTypes = {};
|
|
115
116
|
Object.keys(types).forEach(k => {
|
|
116
|
-
extraTypes[k] = (0, _generator.default)(types[k]
|
|
117
|
+
extraTypes[k] = (0, _generator.default)(types[k], {
|
|
118
|
+
comments: ctx.noComments ? false : true
|
|
119
|
+
}).code;
|
|
117
120
|
});
|
|
118
121
|
return extraTypes;
|
|
119
122
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@khanacademy/graphql-flow",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"bin": {
|
|
5
5
|
"graphql-flow": "./dist/cli/run.js"
|
|
6
6
|
},
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
"@khanacademy/wonder-stuff-core": "^1.5.1",
|
|
49
49
|
"apollo-utilities": "^1.3.4",
|
|
50
50
|
"graphql": "^16.9.0",
|
|
51
|
-
"jsonschema": "^1.4.1"
|
|
51
|
+
"jsonschema": "^1.4.1",
|
|
52
|
+
"minimist": "^1.2.8"
|
|
52
53
|
}
|
|
53
54
|
}
|
package/schema.json
CHANGED
|
@@ -579,6 +579,39 @@ describe("graphql-flow generation", () => {
|
|
|
579
579
|
"NEW_HOPE" | "EMPIRE" | "JEDI"> | null | undefined;
|
|
580
580
|
candies: number;
|
|
581
581
|
friendly?: boolean | null | undefined;
|
|
582
|
+
toBeRemoved?: string | null | undefined;
|
|
583
|
+
};
|
|
584
|
+
},
|
|
585
|
+
response: {
|
|
586
|
+
addCharacter: {
|
|
587
|
+
id: string;
|
|
588
|
+
} | null | undefined;
|
|
589
|
+
}
|
|
590
|
+
};
|
|
591
|
+
`);
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
it("should strip comments", () => {
|
|
595
|
+
const result = rawQueryToFlowTypes(
|
|
596
|
+
`mutation addCharacter($character: CharacterInput!) {
|
|
597
|
+
addCharacter(character: $character) {
|
|
598
|
+
id
|
|
599
|
+
}
|
|
600
|
+
}`,
|
|
601
|
+
{readOnlyArray: false, noComments: true},
|
|
602
|
+
);
|
|
603
|
+
|
|
604
|
+
expect(result).toMatchInlineSnapshot(`
|
|
605
|
+
// addCharacterType.js
|
|
606
|
+
export type addCharacterType = {
|
|
607
|
+
variables: {
|
|
608
|
+
character: {
|
|
609
|
+
name: string;
|
|
610
|
+
friends?: ReadonlyArray<string> | null | undefined;
|
|
611
|
+
appearsIn?: ReadonlyArray<"NEW_HOPE" | "EMPIRE" | "JEDI"> | null | undefined;
|
|
612
|
+
candies: number;
|
|
613
|
+
friendly?: boolean | null | undefined;
|
|
614
|
+
toBeRemoved?: string | null | undefined;
|
|
582
615
|
};
|
|
583
616
|
},
|
|
584
617
|
response: {
|
|
@@ -1,9 +1,48 @@
|
|
|
1
1
|
import {describe, it, expect} from "@jest/globals";
|
|
2
2
|
|
|
3
3
|
import type {Config} from "../../types";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
findApplicableConfig,
|
|
6
|
+
parseCliOptions,
|
|
7
|
+
validateOrThrow,
|
|
8
|
+
} from "../config";
|
|
5
9
|
import configSchema from "../../../schema.json";
|
|
6
10
|
|
|
11
|
+
describe("parseCliOptions", () => {
|
|
12
|
+
it("should handle basic invocation", () => {
|
|
13
|
+
expect(parseCliOptions(["config.json"], "./")).toEqual({
|
|
14
|
+
configFilePath: "config.json",
|
|
15
|
+
cliFiles: [],
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("should handle cli files", () => {
|
|
20
|
+
expect(
|
|
21
|
+
parseCliOptions(["config.json", "one.js", "two.ts"], "./"),
|
|
22
|
+
).toEqual({
|
|
23
|
+
configFilePath: "config.json",
|
|
24
|
+
cliFiles: ["one.js", "two.ts"],
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("should handle -h", () => {
|
|
29
|
+
expect(
|
|
30
|
+
parseCliOptions(["config.json", "-h", "one.js", "two.ts"], "./"),
|
|
31
|
+
).toEqual(false);
|
|
32
|
+
expect(parseCliOptions([], "./")).toEqual(false);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("should pick up schema-file", () => {
|
|
36
|
+
expect(
|
|
37
|
+
parseCliOptions(["config.json", "--schema-file=some.schema"], "./"),
|
|
38
|
+
).toEqual({
|
|
39
|
+
configFilePath: "config.json",
|
|
40
|
+
cliFiles: [],
|
|
41
|
+
schemaFile: "some.schema",
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
7
46
|
describe("findApplicableConfig", () => {
|
|
8
47
|
it("should work with one that matches", () => {
|
|
9
48
|
const config = {
|
package/src/cli/config.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type {Schema} from "../types";
|
|
2
1
|
import type {GraphQLSchema} from "graphql/type/schema";
|
|
2
|
+
import type {CliOptions, Schema} from "../types";
|
|
3
3
|
|
|
4
|
-
import {schemaFromIntrospectionData} from "../schemaFromIntrospectionData";
|
|
5
4
|
import configSchema from "../../schema.json";
|
|
5
|
+
import {schemaFromIntrospectionData} from "../schemaFromIntrospectionData";
|
|
6
6
|
|
|
7
7
|
import fs from "fs";
|
|
8
8
|
import {
|
|
@@ -12,8 +12,11 @@ import {
|
|
|
12
12
|
graphqlSync,
|
|
13
13
|
IntrospectionQuery,
|
|
14
14
|
} from "graphql";
|
|
15
|
-
import type {Config, GenerateConfig} from "../types";
|
|
16
15
|
import {validate} from "jsonschema";
|
|
16
|
+
import processArgs from "minimist";
|
|
17
|
+
import type {Config, GenerateConfig} from "../types";
|
|
18
|
+
import {execSync} from "child_process";
|
|
19
|
+
import path from "path";
|
|
17
20
|
|
|
18
21
|
export const validateOrThrow = (value: unknown, jsonSchema: unknown) => {
|
|
19
22
|
const result = validate(value, jsonSchema);
|
|
@@ -24,12 +27,61 @@ export const validateOrThrow = (value: unknown, jsonSchema: unknown) => {
|
|
|
24
27
|
}
|
|
25
28
|
};
|
|
26
29
|
|
|
27
|
-
export const
|
|
28
|
-
|
|
30
|
+
export const makeAbsPath = (maybeRelativePath: string, basePath: string) => {
|
|
31
|
+
return path.isAbsolute(maybeRelativePath)
|
|
32
|
+
? maybeRelativePath
|
|
33
|
+
: path.join(basePath, maybeRelativePath);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const loadConfigFile = (options: CliOptions): Config => {
|
|
37
|
+
let data:
|
|
38
|
+
| Config
|
|
39
|
+
| ((c: CliOptions) => Config) = require(options.configFilePath);
|
|
40
|
+
if (typeof data === "function") {
|
|
41
|
+
data = data(options);
|
|
42
|
+
} else {
|
|
43
|
+
if (options.schemaFile) {
|
|
44
|
+
if (Array.isArray(data.generate)) {
|
|
45
|
+
data.generate.forEach((item) => {
|
|
46
|
+
item.schemaFilePath = options.schemaFile!;
|
|
47
|
+
});
|
|
48
|
+
} else {
|
|
49
|
+
data.generate.schemaFilePath = options.schemaFile;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
29
53
|
validateOrThrow(data, configSchema);
|
|
30
54
|
return data;
|
|
31
55
|
};
|
|
32
56
|
|
|
57
|
+
const findGraphqlTagReferences = (root: string): Array<string> => {
|
|
58
|
+
// NOTE(john): We want to include untracked files here so that we can
|
|
59
|
+
// generate types for them. This is useful for when we have a new file
|
|
60
|
+
// that we want to generate types for, but we haven't committed it yet.
|
|
61
|
+
const response = execSync(
|
|
62
|
+
"git grep -I --word-regexp --name-only --fixed-strings --untracked 'graphql-tag' -- '*.js' '*.jsx' '*.ts' '*.tsx'",
|
|
63
|
+
{
|
|
64
|
+
encoding: "utf8",
|
|
65
|
+
cwd: root,
|
|
66
|
+
},
|
|
67
|
+
);
|
|
68
|
+
return response
|
|
69
|
+
.trim()
|
|
70
|
+
.split("\n")
|
|
71
|
+
.map((relative) => path.join(root, relative));
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const getInputFiles = (options: CliOptions, config: Config) => {
|
|
75
|
+
return options.cliFiles.length
|
|
76
|
+
? options.cliFiles
|
|
77
|
+
: findGraphqlTagReferences(
|
|
78
|
+
makeAbsPath(
|
|
79
|
+
config.crawl.root,
|
|
80
|
+
path.dirname(options.configFilePath),
|
|
81
|
+
),
|
|
82
|
+
);
|
|
83
|
+
};
|
|
84
|
+
|
|
33
85
|
/**
|
|
34
86
|
* Loads a .json 'introspection query response', or a .graphql schema definition.
|
|
35
87
|
*/
|
|
@@ -39,7 +91,10 @@ export const getSchemas = (schemaFilePath: string): [GraphQLSchema, Schema] => {
|
|
|
39
91
|
const schemaForValidation = buildSchema(raw);
|
|
40
92
|
const queryResponse = graphqlSync({
|
|
41
93
|
schema: schemaForValidation,
|
|
42
|
-
source: getIntrospectionQuery({
|
|
94
|
+
source: getIntrospectionQuery({
|
|
95
|
+
descriptions: true,
|
|
96
|
+
inputValueDeprecation: true,
|
|
97
|
+
}),
|
|
43
98
|
});
|
|
44
99
|
const schemaForTypeGeneration = schemaFromIntrospectionData(
|
|
45
100
|
queryResponse.data as any as IntrospectionQuery,
|
|
@@ -54,6 +109,28 @@ export const getSchemas = (schemaFilePath: string): [GraphQLSchema, Schema] => {
|
|
|
54
109
|
}
|
|
55
110
|
};
|
|
56
111
|
|
|
112
|
+
export const parseCliOptions = (
|
|
113
|
+
argv: string[],
|
|
114
|
+
relativeBase: string,
|
|
115
|
+
): CliOptions | false => {
|
|
116
|
+
const args = processArgs(argv);
|
|
117
|
+
let [configFilePath, ...cliFiles] = args._;
|
|
118
|
+
|
|
119
|
+
if (args.h || args.help || !configFilePath) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
configFilePath = makeAbsPath(configFilePath, relativeBase);
|
|
124
|
+
|
|
125
|
+
const options: CliOptions = {configFilePath, cliFiles};
|
|
126
|
+
|
|
127
|
+
if (args["schema-file"]) {
|
|
128
|
+
options.schemaFile = args["schema-file"];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return options;
|
|
132
|
+
};
|
|
133
|
+
|
|
57
134
|
/**
|
|
58
135
|
* Find the first item of the `config.generate` array where both:
|
|
59
136
|
* - no item of `exclude` matches
|
package/src/cli/run.ts
CHANGED
|
@@ -7,7 +7,14 @@ import {generateTypeFiles, processPragmas} from "../generateTypeFiles";
|
|
|
7
7
|
import {processFiles} from "../parser/parse";
|
|
8
8
|
import {resolveDocuments} from "../parser/resolve";
|
|
9
9
|
import {getPathWithExtension} from "../parser/utils";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
findApplicableConfig,
|
|
12
|
+
getInputFiles,
|
|
13
|
+
getSchemas,
|
|
14
|
+
loadConfigFile,
|
|
15
|
+
makeAbsPath,
|
|
16
|
+
parseCliOptions,
|
|
17
|
+
} from "./config";
|
|
11
18
|
|
|
12
19
|
import {addTypenameToDocument} from "apollo-utilities";
|
|
13
20
|
|
|
@@ -32,52 +39,19 @@ import {dirname} from "path";
|
|
|
32
39
|
|
|
33
40
|
/** Step (1) */
|
|
34
41
|
|
|
35
|
-
const
|
|
36
|
-
// NOTE(john): We want to include untracked files here so that we can
|
|
37
|
-
// generate types for them. This is useful for when we have a new file
|
|
38
|
-
// that we want to generate types for, but we haven't committed it yet.
|
|
39
|
-
const response = execSync(
|
|
40
|
-
"git grep -I --word-regexp --name-only --fixed-strings --untracked 'graphql-tag' -- '*.js' '*.jsx' '*.ts' '*.tsx'",
|
|
41
|
-
{
|
|
42
|
-
encoding: "utf8",
|
|
43
|
-
cwd: root,
|
|
44
|
-
},
|
|
45
|
-
);
|
|
46
|
-
return response
|
|
47
|
-
.trim()
|
|
48
|
-
.split("\n")
|
|
49
|
-
.map((relative) => path.join(root, relative));
|
|
50
|
-
};
|
|
42
|
+
const options = parseCliOptions(process.argv.slice(2), process.cwd());
|
|
51
43
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (
|
|
55
|
-
configFilePath === "-h" ||
|
|
56
|
-
configFilePath === "--help" ||
|
|
57
|
-
configFilePath === "help" ||
|
|
58
|
-
!configFilePath
|
|
59
|
-
) {
|
|
44
|
+
if (options === false) {
|
|
60
45
|
console.log(`graphql-flow
|
|
61
46
|
|
|
62
|
-
Usage: graphql-flow [configFile.json] [filesToCrawl...]
|
|
47
|
+
Usage: graphql-flow [configFile.json] [filesToCrawl...]
|
|
48
|
+
Options:
|
|
49
|
+
--schema-file: provide a schema file to override the one defined in config`);
|
|
63
50
|
process.exit(1);
|
|
64
51
|
}
|
|
65
52
|
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
? maybeRelativePath
|
|
69
|
-
: path.join(basePath, maybeRelativePath);
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
const absConfigPath = makeAbsPath(configFilePath, process.cwd());
|
|
73
|
-
|
|
74
|
-
const config = loadConfigFile(absConfigPath);
|
|
75
|
-
|
|
76
|
-
const inputFiles = cliFiles.length
|
|
77
|
-
? cliFiles
|
|
78
|
-
: findGraphqlTagReferences(
|
|
79
|
-
makeAbsPath(config.crawl.root, path.dirname(absConfigPath)),
|
|
80
|
-
);
|
|
53
|
+
const config = loadConfigFile(options);
|
|
54
|
+
const inputFiles = getInputFiles(options, config);
|
|
81
55
|
|
|
82
56
|
/** Step (2) */
|
|
83
57
|
|
|
@@ -130,7 +104,7 @@ const schemaCache: {
|
|
|
130
104
|
const getCachedSchemas = (schemaFilePath: string) => {
|
|
131
105
|
if (!schemaCache[schemaFilePath]) {
|
|
132
106
|
schemaCache[schemaFilePath] = getSchemas(
|
|
133
|
-
makeAbsPath(schemaFilePath, path.dirname(
|
|
107
|
+
makeAbsPath(schemaFilePath, path.dirname(options.configFilePath)),
|
|
134
108
|
);
|
|
135
109
|
}
|
|
136
110
|
|
package/src/enums.ts
CHANGED
|
@@ -28,7 +28,7 @@ export const experimentalEnumTypeToFlow = (
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
return maybeAddDescriptionComment(
|
|
31
|
-
description,
|
|
31
|
+
ctx.noComments ? null : description,
|
|
32
32
|
babelTypes.tsTypeReference(enumDeclaration.id),
|
|
33
33
|
);
|
|
34
34
|
};
|
|
@@ -52,7 +52,7 @@ export const enumTypeToFlow = (ctx: Context, name: string): TSType => {
|
|
|
52
52
|
return ctx.experimentalEnumsMap
|
|
53
53
|
? experimentalEnumTypeToFlow(ctx, enumConfig, combinedDescription)
|
|
54
54
|
: maybeAddDescriptionComment(
|
|
55
|
-
combinedDescription,
|
|
55
|
+
ctx.noComments ? null : combinedDescription,
|
|
56
56
|
babelTypes.tsUnionType(
|
|
57
57
|
enumConfig.enumValues.map((n) =>
|
|
58
58
|
babelTypes.tsLiteralType(
|
|
@@ -39,7 +39,7 @@ export const generateResponseType = (
|
|
|
39
39
|
query.operation === "mutation" ? "mutation" : "query",
|
|
40
40
|
);
|
|
41
41
|
|
|
42
|
-
return generate(ast as any).code;
|
|
42
|
+
return generate(ast as any, {comments: ctx.noComments ? false : true}).code;
|
|
43
43
|
};
|
|
44
44
|
|
|
45
45
|
const sortedObjectTypeAnnotation = (
|
|
@@ -103,7 +103,7 @@ export const generateFragmentType = (
|
|
|
103
103
|
throw new Error(`Unknown ${onType}`);
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
return generate(ast as any).code;
|
|
106
|
+
return generate(ast as any, {comments: ctx.noComments ? false : true}).code;
|
|
107
107
|
};
|
|
108
108
|
|
|
109
109
|
const _typeToFlow = (
|
package/src/index.ts
CHANGED
|
@@ -31,6 +31,7 @@ const optionsToConfig = (
|
|
|
31
31
|
scalars: options?.scalars ?? {},
|
|
32
32
|
typeScript: options?.typeScript ?? false,
|
|
33
33
|
omitFileExtensions: options?.omitFileExtensions ?? false,
|
|
34
|
+
noComments: options?.noComments,
|
|
34
35
|
} as const;
|
|
35
36
|
const fragments: Record<string, any> = {};
|
|
36
37
|
definitions.forEach((def) => {
|
|
@@ -101,9 +102,10 @@ export const documentToFlowTypes = (
|
|
|
101
102
|
},
|
|
102
103
|
)};`;
|
|
103
104
|
|
|
104
|
-
const extraTypes = codegenExtraTypes(types);
|
|
105
|
+
const extraTypes = codegenExtraTypes(types, config);
|
|
105
106
|
const experimentalEnums = codegenExtraTypes(
|
|
106
107
|
config.experimentalEnumsMap || {},
|
|
108
|
+
config,
|
|
107
109
|
);
|
|
108
110
|
|
|
109
111
|
return {
|
|
@@ -139,9 +141,10 @@ export const documentToFlowTypes = (
|
|
|
139
141
|
// We'll see what's required to get webapp on board.
|
|
140
142
|
const code = `export type ${typeName} = {\n variables: ${variables},\n response: ${response}\n};`;
|
|
141
143
|
|
|
142
|
-
const extraTypes = codegenExtraTypes(types);
|
|
144
|
+
const extraTypes = codegenExtraTypes(types, config);
|
|
143
145
|
const experimentalEnums = codegenExtraTypes(
|
|
144
146
|
config.experimentalEnumsMap || {},
|
|
147
|
+
config,
|
|
145
148
|
);
|
|
146
149
|
|
|
147
150
|
return {name, typeName, code, extraTypes, experimentalEnums};
|
|
@@ -154,14 +157,19 @@ export const documentToFlowTypes = (
|
|
|
154
157
|
return result;
|
|
155
158
|
};
|
|
156
159
|
|
|
157
|
-
function codegenExtraTypes(
|
|
160
|
+
function codegenExtraTypes(
|
|
161
|
+
types: {[key: string]: Node},
|
|
162
|
+
ctx: Context,
|
|
163
|
+
): {
|
|
158
164
|
[key: string]: string;
|
|
159
165
|
} {
|
|
160
166
|
const extraTypes: {
|
|
161
167
|
[key: string]: string;
|
|
162
168
|
} = {};
|
|
163
169
|
Object.keys(types).forEach((k: string) => {
|
|
164
|
-
extraTypes[k] = generate(types[k] as any
|
|
170
|
+
extraTypes[k] = generate(types[k] as any, {
|
|
171
|
+
comments: ctx.noComments ? false : true,
|
|
172
|
+
}).code;
|
|
165
173
|
});
|
|
166
174
|
return extraTypes;
|
|
167
175
|
}
|
package/src/types.ts
CHANGED
|
@@ -10,6 +10,12 @@ import type {
|
|
|
10
10
|
SelectionNode,
|
|
11
11
|
} from "graphql";
|
|
12
12
|
|
|
13
|
+
export type CliOptions = {
|
|
14
|
+
schemaFile?: string;
|
|
15
|
+
configFilePath: string;
|
|
16
|
+
cliFiles: string[];
|
|
17
|
+
};
|
|
18
|
+
|
|
13
19
|
export type Selections = ReadonlyArray<SelectionNode>;
|
|
14
20
|
|
|
15
21
|
export type GenerateConfig = {
|
|
@@ -18,6 +24,7 @@ export type GenerateConfig = {
|
|
|
18
24
|
exclude?: ReadonlyArray<RegExp | string>;
|
|
19
25
|
typeScript?: boolean;
|
|
20
26
|
scalars?: Scalars;
|
|
27
|
+
noComments?: boolean;
|
|
21
28
|
strictNullability?: boolean;
|
|
22
29
|
/**
|
|
23
30
|
* The command that users should run to regenerate the types files.
|
|
@@ -81,6 +88,7 @@ export type Schema = {
|
|
|
81
88
|
export type Context = {
|
|
82
89
|
path: Array<string>;
|
|
83
90
|
strictNullability: boolean;
|
|
91
|
+
noComments: boolean;
|
|
84
92
|
readOnlyArray: boolean;
|
|
85
93
|
fragments: {
|
|
86
94
|
[key: string]: FragmentDefinitionNode;
|