@khanacademy/graphql-flow 0.0.1 → 0.2.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.
Files changed (54) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/Readme.md +67 -67
  3. package/dist/cli/config.js +73 -0
  4. package/dist/cli/config.js.flow +104 -0
  5. package/dist/cli/config.js.map +1 -0
  6. package/dist/cli/run.js +157 -0
  7. package/dist/cli/run.js.flow +164 -0
  8. package/dist/cli/run.js.map +1 -0
  9. package/dist/enums.js +2 -1
  10. package/dist/enums.js.map +1 -0
  11. package/dist/generateResponseType.js +170 -60
  12. package/dist/generateResponseType.js.flow +248 -82
  13. package/dist/generateResponseType.js.map +1 -0
  14. package/dist/generateTypeFiles.js +141 -0
  15. package/dist/generateTypeFiles.js.flow +167 -0
  16. package/dist/generateTypeFiles.js.map +1 -0
  17. package/dist/generateVariablesType.js +8 -1
  18. package/dist/generateVariablesType.js.flow +6 -0
  19. package/dist/generateVariablesType.js.map +1 -0
  20. package/dist/index.js +45 -4
  21. package/dist/index.js.flow +54 -4
  22. package/dist/index.js.map +1 -0
  23. package/dist/jest-mock-graphql-tag.js +42 -123
  24. package/dist/jest-mock-graphql-tag.js.flow +50 -145
  25. package/dist/jest-mock-graphql-tag.js.map +1 -0
  26. package/dist/parser/parse.js +349 -0
  27. package/dist/parser/parse.js.flow +403 -0
  28. package/dist/parser/parse.js.map +1 -0
  29. package/dist/parser/resolve.js +111 -0
  30. package/dist/parser/resolve.js.flow +117 -0
  31. package/dist/parser/resolve.js.map +1 -0
  32. package/dist/schemaFromIntrospectionData.js +2 -1
  33. package/dist/schemaFromIntrospectionData.js.map +1 -0
  34. package/dist/types.js +2 -1
  35. package/dist/types.js.flow +7 -0
  36. package/dist/types.js.map +1 -0
  37. package/dist/utils.js +2 -1
  38. package/dist/utils.js.map +1 -0
  39. package/package.json +13 -9
  40. package/src/__test__/example-schema.graphql +3 -1
  41. package/src/__test__/generateTypeFileContents.test.js +61 -0
  42. package/src/__test__/graphql-flow.test.js +329 -54
  43. package/src/__test__/{jest-mock-graphql-tag.test.js → processPragmas.test.js} +13 -1
  44. package/src/cli/config.js +104 -0
  45. package/src/cli/run.js +164 -0
  46. package/src/generateResponseType.js +248 -82
  47. package/src/generateTypeFiles.js +167 -0
  48. package/src/generateVariablesType.js +6 -0
  49. package/src/index.js +54 -4
  50. package/src/jest-mock-graphql-tag.js +50 -145
  51. package/src/parser/__test__/parse.test.js +247 -0
  52. package/src/parser/parse.js +403 -0
  53. package/src/parser/resolve.js +117 -0
  54. package/src/types.js +7 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # @khanacademy/graphql-flow
2
2
 
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - d9a8229: Generate types for fragments!
8
+
9
+ ### Patch Changes
10
+
11
+ - 7497164: Fix a bug in index file generation that resulted in duplicate entries
12
+ - 26abf9b: Add options for specifying the name of the generated directory, and for exporting the response and variables types.
13
+
14
+ ## 0.1.0
15
+
16
+ ### Minor Changes
17
+
18
+ - b08ed1b: Build out a cli tool that does our own parsing of javascript files, dramatically speeding things up!
19
+
20
+ ### Patch Changes
21
+
22
+ - 8cdcdc2: Support inline fragments on objects
23
+ - fd5c6b7: Allow schema to be provided as a .graphql file, not just .json
24
+ - 6869203: Add 'ignorePragma' option, to allow skipping of documents
25
+
26
+ ## 0.0.2
27
+
28
+ ### Patch Changes
29
+
30
+ - 9810bfe: Allow customization of the "regenerate command" that's in the file docstrings
31
+ - 7d27337: Support custom scalars as variables in an operation
32
+
3
33
  ## 0.0.1
4
34
 
5
35
  ### Patch Changes
package/Readme.md CHANGED
@@ -2,64 +2,84 @@
2
2
 
3
3
  This is a tool for generating flow types from graphql queries in javascript frontends.
4
4
 
5
- The core of this tool is the `documentToFlowTypes` function, which takes a `DocumentNode` (such as is returned by the `graphql-tag` package) as well as your backend's graphql schema (see below for instructions on how to produce this), and produces a list of stringified flow types, one for each query and mutation defined in that graphql block.
5
+ ## Using as a CLI tool
6
6
 
7
- It looks something like this:
7
+ Write a config file, with the following options:
8
8
 
9
- ```js
10
- import gql from 'graphql-tag';
11
- import {documentToFlowTypes, schemaFromIntrospectionData} from 'graphql-flow';
12
- import myIntrospectionData from './server-introspection-response.json';
13
-
14
- const schema = schemaFromIntrospectionData(myIntrospectionData);
15
-
16
- const MyQuery = gql`
17
- query SomeQuery {
18
- human(id: "Han Solo") {
19
- id
20
- name
21
- homePlanet
22
- friends {
23
- name
24
- }
9
+ ```json
10
+ {
11
+ // Response to the "introspection query" (see below), or a .graphql schema file.
12
+ // The file extension indicates the format, .json or .graphql (default .json).
13
+ // This path is resolved relative to the config file location.
14
+ "schemaFilePath": "../some/schema-file.json",
15
+ // List of regexes
16
+ "excludes": ["\\bsome-thing", "_test.jsx?$"],
17
+ // Options for type generation (see below)
18
+ "options": {
19
+ "scalars": {
20
+ "JSONString": "string"
25
21
  }
26
22
  }
27
- `;
28
-
29
- console.log(documentToFlowTypes(MyQuery, schema))
30
- /*
31
- export type SomeQueryResponseType = {|
32
- human: ?{|
33
- id: string,
34
- name: ?string,
35
- homePlanet: ?string,
36
- friends: ?$ReadOnlyArray<?{|
37
- name: ?string
38
- |}>,
39
- |}
40
- |};
41
- */
23
+ }
24
+ ```
25
+
26
+ Then run from the CLI, like so:
27
+
28
+ ```bash
29
+ $ graphql-flow path/to/config.json
42
30
  ```
43
31
 
44
- If you already have a setup whereby you collect all of your graphql literals, that may be all you need!
32
+ Files will be discovered relative to the current working directory.
45
33
 
46
- Otherwise, we provide a way to hook into jest to automatically collect your queries and generate the types.
34
+ To specify what file should be checked, pass them in as subsequent cli arguments.
47
35
 
48
- ## Options for `documentToFlowTypes`
36
+ ## Options (for the cli 'options' config item, or when running from jest):
49
37
 
50
- ```js
51
- {
52
- // Use nullable types where the graphql type is nullable. Included for legacy compatability,
53
- // will probably remove once the mobile repo no longer needs it.
38
+ ```ts
39
+ type Options = {
40
+ // These are from the `documentToFlowTypes` options object above
54
41
  strictNullability: boolean = true,
55
- // Output `$ReadOnlyArray<>` instead of `Array<>`, for stricter flow typing. On by default.
56
42
  readOnlyArray: boolean = true,
57
- // A mapping of custom scalar names to the underlying json representation.
58
43
  scalars: {[key: string]: 'string' | 'boolean' | 'number'}
44
+
45
+ // Specify the name of the generated types directory
46
+ generatedDirectory: string = '__generated__',
47
+
48
+ // The default generated type contains both the types of the response
49
+ // and the variables, combined as [operatioName]Type. Setting
50
+ // `splitTypes = true` adds the additional exports [operationName]
51
+ // (for the response) and [operationName]Variables (for the variables).
52
+ splitTypes?: boolean,
53
+
54
+ // Specify an opt-in pragma that must be present in a graphql string source
55
+ // in order for it to be picked up and processed
56
+ // e.g. set this to `"# @autogen\n"` to only generate types for queries that
57
+ // have the comment `# @autogen` in them.
58
+ pragma?: string,
59
+ // Specify a pragma that will turn off `strictNullability` for that
60
+ // source file. e.g. `"# @autogen-loose\n"`.
61
+ loosePragma?: string,
62
+ // If neither pragma nor loosePragma are specified, all graphql documents
63
+ // that contain a query or mutation will be processed.
64
+
65
+ // Any graphql operations containing ignorePragma will be skipped
66
+ ignorePragma?: string,
67
+
68
+ // Set to true to mirror gqlgen's behavior of exporting all
69
+ // nested object types within the response type.
70
+ // Names are generated by concatenating the attribute names
71
+ // of the path to the object type, separated by underscores.
72
+ exportAllObjectTypes?: boolean,
59
73
  }
60
74
  ```
61
75
 
62
- ## Using jest to do the heavy lifting:
76
+ ## Using from jest
77
+
78
+ You can also use jest to do the heavy lifting, running all of your code and collecting queries
79
+ by mocking out the `graphql-tag` function itself. This requires that all graphql operations are
80
+ defined at the top level (no queries defined in functions or components, for example), but does
81
+ support complicated things like returning a fragment from a function (which is probably
82
+ not a great idea code-style-wise anyway).
63
83
 
64
84
  ### jest-setup.js
65
85
 
@@ -74,6 +94,7 @@ if (process.env.GRAPHQL_FLOW) {
74
94
 
75
95
  return jest.requireActual('../tools/graphql-flow/jest-mock-graphql-tag.js')(
76
96
  introspectionData,
97
+ // See "Options" type above
77
98
  {
78
99
  pragma: '# @autogen\n',
79
100
  loosePragma: '# @autogen-loose\n',
@@ -94,10 +115,11 @@ Then you'll want to make a pseudo-'test' that makes sure to 'require' all of the
94
115
  jest will process them and our mock will see them.
95
116
  ```js
96
117
  // generate-types.test.js
97
- import {findFilesWithQueries} from '../tools/graphql-flow/find-files-with-gql';
118
+ import {findGraphqlTagReferences} from '../tools/graphql-flow/find-files-with-gql';
119
+ import path from 'path';
98
120
 
99
121
  if (process.env.GRAPHQL_FLOW) {
100
- findFilesWithQueries(path.join(__dirname, '..')).forEach(name => {
122
+ findGraphqlTagReferences(path.join(__dirname, '..')).forEach(name => {
101
123
  require(name);
102
124
  });
103
125
 
@@ -125,28 +147,6 @@ And then `yarn generate-types` or `npm run generate-types` gets your types gener
125
147
 
126
148
  🚀
127
149
 
128
- ### Options for the `jest-mock-graphql-tag.js` helper:
129
-
130
- ```js
131
- {
132
- // These are from the `documentToFlowTypes` options object above
133
- strictNullability: boolean = true,
134
- readOnlyArray: boolean = true,
135
- scalars: {[key: string]: 'string' | 'boolean' | 'number'}
136
-
137
- // Specify an opt-in pragma that must be present in a graphql string source
138
- // in order for it to be picked up and processed
139
- // e.g. set this to `"# @autogen\n"` to only generate types for queries that
140
- // have the comment `# @autogen` in them.
141
- pragma?: string,
142
- // Specify a pragma that will turn off `strictNullability` for that
143
- // source file. e.g. `"# @autogen-loose\n"`.
144
- loosePragma?: string,
145
- // If neither pragma nor loosePragma are specified, all graphql documents
146
- // that contain a query or mutation will be processed.
147
- }
148
- ```
149
-
150
150
  ## Introspecting your backend's graphql schema
151
151
  Here's how to get your backend's schema in the way that this tool expects, using the builtin 'graphql introspection query':
152
152
 
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.loadConfigFile = exports.getSchemas = void 0;
7
+
8
+ var _schemaFromIntrospectionData = require("../schemaFromIntrospectionData");
9
+
10
+ var _fs = _interopRequireDefault(require("fs"));
11
+
12
+ var _graphql = require("graphql");
13
+
14
+ var _path = _interopRequireDefault(require("path"));
15
+
16
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
17
+
18
+ const loadConfigFile = configFile => {
19
+ var _data$excludes;
20
+
21
+ // eslint-disable-next-line flowtype-errors/uncovered
22
+ const data = JSON.parse(_fs.default.readFileSync(configFile, 'utf8'));
23
+ const toplevelKeys = ['excludes', 'schemaFilePath', 'options'];
24
+ Object.keys(data).forEach(k => {
25
+ if (!toplevelKeys.includes(k)) {
26
+ throw new Error(`Invalid attribute in config file ${configFile}: ${k}. Allowed attributes: ${toplevelKeys.join(', ')}`);
27
+ }
28
+ });
29
+
30
+ if (data.options) {
31
+ const externalOptionsKeys = ['pragma', 'loosePragma', 'ignorePragma', 'scalars', 'strictNullability', 'regenerateCommand', 'readOnlyArray', 'splitTypes', 'generatedDirectory', 'exportAllObjectTypes'];
32
+ Object.keys(data.options).forEach(k => {
33
+ if (!externalOptionsKeys.includes(k)) {
34
+ throw new Error(`Invalid option in config file ${configFile}: ${k}. Allowed options: ${externalOptionsKeys.join(', ')}`);
35
+ }
36
+ });
37
+ }
38
+
39
+ return {
40
+ options: data.options ?? {},
41
+ excludes: ((_data$excludes = data.excludes) === null || _data$excludes === void 0 ? void 0 : _data$excludes.map(string => new RegExp(string))) ?? [],
42
+ schemaFilePath: _path.default.join(_path.default.dirname(configFile), data.schemaFilePath)
43
+ };
44
+ };
45
+ /**
46
+ * Loads a .json 'introspection query response', or a .graphql schema definition.
47
+ */
48
+
49
+
50
+ exports.loadConfigFile = loadConfigFile;
51
+
52
+ const getSchemas = schemaFilePath => {
53
+ const raw = _fs.default.readFileSync(schemaFilePath, 'utf8');
54
+
55
+ if (schemaFilePath.endsWith('.graphql')) {
56
+ const schemaForValidation = (0, _graphql.buildSchema)(raw);
57
+ const queryResponse = (0, _graphql.graphqlSync)(schemaForValidation, (0, _graphql.getIntrospectionQuery)({
58
+ descriptions: true
59
+ }));
60
+ const schemaForTypeGeneration = (0, _schemaFromIntrospectionData.schemaFromIntrospectionData)( // eslint-disable-next-line flowtype-errors/uncovered
61
+ queryResponse.data);
62
+ return [schemaForValidation, schemaForTypeGeneration];
63
+ } else {
64
+ // eslint-disable-next-line flowtype-errors/uncovered
65
+ const introspectionData = JSON.parse(raw);
66
+ const schemaForValidation = (0, _graphql.buildClientSchema)(introspectionData);
67
+ const schemaForTypeGeneration = (0, _schemaFromIntrospectionData.schemaFromIntrospectionData)(introspectionData);
68
+ return [schemaForValidation, schemaForTypeGeneration];
69
+ }
70
+ };
71
+
72
+ exports.getSchemas = getSchemas;
73
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1,104 @@
1
+ // @flow
2
+ import type {ExternalOptions} from '../generateTypeFiles';
3
+ import type {Schema} from '../types';
4
+ import type {GraphQLSchema} from 'graphql/type/schema';
5
+
6
+ import {schemaFromIntrospectionData} from '../schemaFromIntrospectionData';
7
+
8
+ import fs from 'fs';
9
+ import {
10
+ buildClientSchema,
11
+ buildSchema,
12
+ getIntrospectionQuery,
13
+ graphqlSync,
14
+ type IntrospectionQuery,
15
+ } from 'graphql';
16
+ import path from 'path';
17
+
18
+ export type CliConfig = {
19
+ excludes: Array<RegExp>,
20
+ schemaFilePath: string,
21
+ options: ExternalOptions,
22
+ };
23
+
24
+ /**
25
+ * This is the json-compatible form of the config
26
+ * object.
27
+ */
28
+ type JSONConfig = {
29
+ excludes?: Array<string>,
30
+ schemaFilePath: string,
31
+ options?: ExternalOptions,
32
+ };
33
+
34
+ export const loadConfigFile = (configFile: string): CliConfig => {
35
+ // eslint-disable-next-line flowtype-errors/uncovered
36
+ const data: JSONConfig = JSON.parse(fs.readFileSync(configFile, 'utf8'));
37
+ const toplevelKeys = ['excludes', 'schemaFilePath', 'options'];
38
+ Object.keys(data).forEach((k) => {
39
+ if (!toplevelKeys.includes(k)) {
40
+ throw new Error(
41
+ `Invalid attribute in config file ${configFile}: ${k}. Allowed attributes: ${toplevelKeys.join(
42
+ ', ',
43
+ )}`,
44
+ );
45
+ }
46
+ });
47
+ if (data.options) {
48
+ const externalOptionsKeys = [
49
+ 'pragma',
50
+ 'loosePragma',
51
+ 'ignorePragma',
52
+ 'scalars',
53
+ 'strictNullability',
54
+ 'regenerateCommand',
55
+ 'readOnlyArray',
56
+ 'splitTypes',
57
+ 'generatedDirectory',
58
+ 'exportAllObjectTypes',
59
+ ];
60
+ Object.keys(data.options).forEach((k) => {
61
+ if (!externalOptionsKeys.includes(k)) {
62
+ throw new Error(
63
+ `Invalid option in config file ${configFile}: ${k}. Allowed options: ${externalOptionsKeys.join(
64
+ ', ',
65
+ )}`,
66
+ );
67
+ }
68
+ });
69
+ }
70
+ return {
71
+ options: data.options ?? {},
72
+ excludes: data.excludes?.map((string) => new RegExp(string)) ?? [],
73
+ schemaFilePath: path.join(
74
+ path.dirname(configFile),
75
+ data.schemaFilePath,
76
+ ),
77
+ };
78
+ };
79
+
80
+ /**
81
+ * Loads a .json 'introspection query response', or a .graphql schema definition.
82
+ */
83
+ export const getSchemas = (schemaFilePath: string): [GraphQLSchema, Schema] => {
84
+ const raw = fs.readFileSync(schemaFilePath, 'utf8');
85
+ if (schemaFilePath.endsWith('.graphql')) {
86
+ const schemaForValidation = buildSchema(raw);
87
+ const queryResponse = graphqlSync(
88
+ schemaForValidation,
89
+ getIntrospectionQuery({descriptions: true}),
90
+ );
91
+ const schemaForTypeGeneration = schemaFromIntrospectionData(
92
+ // eslint-disable-next-line flowtype-errors/uncovered
93
+ ((queryResponse.data: any): IntrospectionQuery),
94
+ );
95
+ return [schemaForValidation, schemaForTypeGeneration];
96
+ } else {
97
+ // eslint-disable-next-line flowtype-errors/uncovered
98
+ const introspectionData: IntrospectionQuery = JSON.parse(raw);
99
+ const schemaForValidation = buildClientSchema(introspectionData);
100
+ const schemaForTypeGeneration =
101
+ schemaFromIntrospectionData(introspectionData);
102
+ return [schemaForValidation, schemaForTypeGeneration];
103
+ }
104
+ };
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/cli/config.js"],"names":["loadConfigFile","configFile","data","JSON","parse","fs","readFileSync","toplevelKeys","Object","keys","forEach","k","includes","Error","join","options","externalOptionsKeys","excludes","map","string","RegExp","schemaFilePath","path","dirname","getSchemas","raw","endsWith","schemaForValidation","queryResponse","descriptions","schemaForTypeGeneration","introspectionData"],"mappings":";;;;;;;AAKA;;AAEA;;AACA;;AAOA;;;;AAkBO,MAAMA,cAAc,GAAIC,UAAD,IAAmC;AAAA;;AAC7D;AACA,QAAMC,IAAgB,GAAGC,IAAI,CAACC,KAAL,CAAWC,YAAGC,YAAH,CAAgBL,UAAhB,EAA4B,MAA5B,CAAX,CAAzB;AACA,QAAMM,YAAY,GAAG,CAAC,UAAD,EAAa,gBAAb,EAA+B,SAA/B,CAArB;AACAC,EAAAA,MAAM,CAACC,IAAP,CAAYP,IAAZ,EAAkBQ,OAAlB,CAA2BC,CAAD,IAAO;AAC7B,QAAI,CAACJ,YAAY,CAACK,QAAb,CAAsBD,CAAtB,CAAL,EAA+B;AAC3B,YAAM,IAAIE,KAAJ,CACD,oCAAmCZ,UAAW,KAAIU,CAAE,yBAAwBJ,YAAY,CAACO,IAAb,CACzE,IADyE,CAE3E,EAHA,CAAN;AAKH;AACJ,GARD;;AASA,MAAIZ,IAAI,CAACa,OAAT,EAAkB;AACd,UAAMC,mBAAmB,GAAG,CACxB,QADwB,EAExB,aAFwB,EAGxB,cAHwB,EAIxB,SAJwB,EAKxB,mBALwB,EAMxB,mBANwB,EAOxB,eAPwB,EAQxB,YARwB,EASxB,oBATwB,EAUxB,sBAVwB,CAA5B;AAYAR,IAAAA,MAAM,CAACC,IAAP,CAAYP,IAAI,CAACa,OAAjB,EAA0BL,OAA1B,CAAmCC,CAAD,IAAO;AACrC,UAAI,CAACK,mBAAmB,CAACJ,QAApB,CAA6BD,CAA7B,CAAL,EAAsC;AAClC,cAAM,IAAIE,KAAJ,CACD,iCAAgCZ,UAAW,KAAIU,CAAE,sBAAqBK,mBAAmB,CAACF,IAApB,CACnE,IADmE,CAErE,EAHA,CAAN;AAKH;AACJ,KARD;AASH;;AACD,SAAO;AACHC,IAAAA,OAAO,EAAEb,IAAI,CAACa,OAAL,IAAgB,EADtB;AAEHE,IAAAA,QAAQ,EAAE,mBAAAf,IAAI,CAACe,QAAL,kEAAeC,GAAf,CAAoBC,MAAD,IAAY,IAAIC,MAAJ,CAAWD,MAAX,CAA/B,MAAsD,EAF7D;AAGHE,IAAAA,cAAc,EAAEC,cAAKR,IAAL,CACZQ,cAAKC,OAAL,CAAatB,UAAb,CADY,EAEZC,IAAI,CAACmB,cAFO;AAHb,GAAP;AAQH,CA5CM;AA8CP;AACA;AACA;;;;;AACO,MAAMG,UAAU,GAAIH,cAAD,IAAqD;AAC3E,QAAMI,GAAG,GAAGpB,YAAGC,YAAH,CAAgBe,cAAhB,EAAgC,MAAhC,CAAZ;;AACA,MAAIA,cAAc,CAACK,QAAf,CAAwB,UAAxB,CAAJ,EAAyC;AACrC,UAAMC,mBAAmB,GAAG,0BAAYF,GAAZ,CAA5B;AACA,UAAMG,aAAa,GAAG,0BAClBD,mBADkB,EAElB,oCAAsB;AAACE,MAAAA,YAAY,EAAE;AAAf,KAAtB,CAFkB,CAAtB;AAIA,UAAMC,uBAAuB,GAAG,+DAC5B;AACEF,IAAAA,aAAa,CAAC1B,IAFY,CAAhC;AAIA,WAAO,CAACyB,mBAAD,EAAsBG,uBAAtB,CAAP;AACH,GAXD,MAWO;AACH;AACA,UAAMC,iBAAqC,GAAG5B,IAAI,CAACC,KAAL,CAAWqB,GAAX,CAA9C;AACA,UAAME,mBAAmB,GAAG,gCAAkBI,iBAAlB,CAA5B;AACA,UAAMD,uBAAuB,GACzB,8DAA4BC,iBAA5B,CADJ;AAEA,WAAO,CAACJ,mBAAD,EAAsBG,uBAAtB,CAAP;AACH;AACJ,CArBM","sourcesContent":["// @flow\nimport type {ExternalOptions} from '../generateTypeFiles';\nimport type {Schema} from '../types';\nimport type {GraphQLSchema} from 'graphql/type/schema';\n\nimport {schemaFromIntrospectionData} from '../schemaFromIntrospectionData';\n\nimport fs from 'fs';\nimport {\n buildClientSchema,\n buildSchema,\n getIntrospectionQuery,\n graphqlSync,\n type IntrospectionQuery,\n} from 'graphql';\nimport path from 'path';\n\nexport type CliConfig = {\n excludes: Array<RegExp>,\n schemaFilePath: string,\n options: ExternalOptions,\n};\n\n/**\n * This is the json-compatible form of the config\n * object.\n */\ntype JSONConfig = {\n excludes?: Array<string>,\n schemaFilePath: string,\n options?: ExternalOptions,\n};\n\nexport const loadConfigFile = (configFile: string): CliConfig => {\n // eslint-disable-next-line flowtype-errors/uncovered\n const data: JSONConfig = JSON.parse(fs.readFileSync(configFile, 'utf8'));\n const toplevelKeys = ['excludes', 'schemaFilePath', 'options'];\n Object.keys(data).forEach((k) => {\n if (!toplevelKeys.includes(k)) {\n throw new Error(\n `Invalid attribute in config file ${configFile}: ${k}. Allowed attributes: ${toplevelKeys.join(\n ', ',\n )}`,\n );\n }\n });\n if (data.options) {\n const externalOptionsKeys = [\n 'pragma',\n 'loosePragma',\n 'ignorePragma',\n 'scalars',\n 'strictNullability',\n 'regenerateCommand',\n 'readOnlyArray',\n 'splitTypes',\n 'generatedDirectory',\n 'exportAllObjectTypes',\n ];\n Object.keys(data.options).forEach((k) => {\n if (!externalOptionsKeys.includes(k)) {\n throw new Error(\n `Invalid option in config file ${configFile}: ${k}. Allowed options: ${externalOptionsKeys.join(\n ', ',\n )}`,\n );\n }\n });\n }\n return {\n options: data.options ?? {},\n excludes: data.excludes?.map((string) => new RegExp(string)) ?? [],\n schemaFilePath: path.join(\n path.dirname(configFile),\n data.schemaFilePath,\n ),\n };\n};\n\n/**\n * Loads a .json 'introspection query response', or a .graphql schema definition.\n */\nexport const getSchemas = (schemaFilePath: string): [GraphQLSchema, Schema] => {\n const raw = fs.readFileSync(schemaFilePath, 'utf8');\n if (schemaFilePath.endsWith('.graphql')) {\n const schemaForValidation = buildSchema(raw);\n const queryResponse = graphqlSync(\n schemaForValidation,\n getIntrospectionQuery({descriptions: true}),\n );\n const schemaForTypeGeneration = schemaFromIntrospectionData(\n // eslint-disable-next-line flowtype-errors/uncovered\n ((queryResponse.data: any): IntrospectionQuery),\n );\n return [schemaForValidation, schemaForTypeGeneration];\n } else {\n // eslint-disable-next-line flowtype-errors/uncovered\n const introspectionData: IntrospectionQuery = JSON.parse(raw);\n const schemaForValidation = buildClientSchema(introspectionData);\n const schemaForTypeGeneration =\n schemaFromIntrospectionData(introspectionData);\n return [schemaForValidation, schemaForTypeGeneration];\n }\n};\n"],"file":"config.js"}
@@ -0,0 +1,157 @@
1
+ "use strict";
2
+
3
+ var _generateTypeFiles = require("../generateTypeFiles");
4
+
5
+ var _parse = require("../parser/parse");
6
+
7
+ var _resolve = require("../parser/resolve");
8
+
9
+ var _config = require("./config");
10
+
11
+ var _apolloUtilities = require("apollo-utilities");
12
+
13
+ var _child_process = require("child_process");
14
+
15
+ var _fs = require("fs");
16
+
17
+ var _printer = require("graphql/language/printer");
18
+
19
+ var _validation = require("graphql/validation");
20
+
21
+ var _path = _interopRequireDefault(require("path"));
22
+
23
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
24
+
25
+ /* eslint-disable no-console */
26
+ // eslint-disable-line flowtype-errors/uncovered
27
+
28
+ /**
29
+ * This CLI tool executes the following steps:
30
+ * 1) process options
31
+ * 2) crawl files to find all operations and fragments, with
32
+ * tagged template literals and expressions.
33
+ * 3) resolve the found operations, passing the literals and
34
+ * fragments into the `graphql-tag` function to produce
35
+ * the DocumentNodes.
36
+ * 4) generate types for all resolved Queries & Mutations
37
+ */
38
+
39
+ /** Step (1) */
40
+ const findGraphqlTagReferences = root => {
41
+ const response = (0, _child_process.execSync)("git grep -I --word-regexp --name-only --fixed-strings 'graphql-tag' -- '*.js' '*.jsx'", {
42
+ encoding: 'utf8',
43
+ cwd: root
44
+ });
45
+ return response.trim().split('\n').map(relative => _path.default.join(root, relative));
46
+ };
47
+
48
+ const [_, __, configFile, ...cliFiles] = process.argv;
49
+
50
+ if (configFile === '-h' || configFile === '--help' || configFile === 'help' || !configFile) {
51
+ console.log(`graphql-flow
52
+
53
+ Usage: graphql-flow [configFile.json] [filesToCrawl...]`);
54
+ process.exit(1); // eslint-disable-line flowtype-errors/uncovered
55
+ }
56
+
57
+ const config = (0, _config.loadConfigFile)(configFile);
58
+ const [schemaForValidation, schemaForTypeGeneration] = (0, _config.getSchemas)(config.schemaFilePath);
59
+ const inputFiles = cliFiles.length ? cliFiles : findGraphqlTagReferences(process.cwd());
60
+ /** Step (2) */
61
+
62
+ const files = (0, _parse.processFiles)(inputFiles, f => (0, _fs.readFileSync)(f, 'utf8'));
63
+ let filesHadErrors = false;
64
+ Object.keys(files).forEach(key => {
65
+ const file = files[key];
66
+
67
+ if (file.errors.length) {
68
+ filesHadErrors = true;
69
+ console.error(`Errors in ${file.path}`);
70
+ file.errors.forEach(error => {
71
+ console.error(` - ${error.message}`);
72
+ });
73
+ }
74
+ });
75
+
76
+ if (filesHadErrors) {
77
+ console.error('Aborting');
78
+ process.exit(1); // eslint-disable-line flowtype-errors/uncovered
79
+ }
80
+ /** Step (3) */
81
+
82
+
83
+ const {
84
+ resolved,
85
+ errors
86
+ } = (0, _resolve.resolveDocuments)(files);
87
+
88
+ if (errors.length) {
89
+ errors.forEach(error => {
90
+ console.error(`Resolution error ${error.message} in ${error.loc.path}`);
91
+ });
92
+ console.error('Aborting');
93
+ process.exit(1); // eslint-disable-line flowtype-errors/uncovered
94
+ }
95
+
96
+ console.log(Object.keys(resolved).length, 'resolved queries');
97
+ /** Step (4) */
98
+
99
+ let validationFailures = 0;
100
+ Object.keys(resolved).forEach(k => {
101
+ const {
102
+ document,
103
+ raw
104
+ } = resolved[k];
105
+
106
+ if (config.excludes.some(rx => rx.test(raw.loc.path))) {
107
+ return; // skip
108
+ }
109
+
110
+ const hasNonFragments = document.definitions.some(({
111
+ kind
112
+ }) => kind !== 'FragmentDefinition');
113
+ const rawSource = raw.literals[0];
114
+ const processedOptions = (0, _generateTypeFiles.processPragmas)(config.options, rawSource);
115
+
116
+ if (!processedOptions) {
117
+ return;
118
+ } // eslint-disable-next-line flowtype-errors/uncovered
119
+
120
+
121
+ const withTypeNames = (0, _apolloUtilities.addTypenameToDocument)(document);
122
+ const printed = (0, _printer.print)(withTypeNames);
123
+
124
+ if (hasNonFragments) {
125
+ /* eslint-disable flowtype-errors/uncovered */
126
+ const errors = (0, _validation.validate)(schemaForValidation, withTypeNames);
127
+ /* eslint-disable flowtype-errors/uncovered */
128
+
129
+ if (errors.length) {
130
+ errors.forEach(error => {
131
+ console.error(`Schema validation found errors for ${raw.loc.path}!`);
132
+ console.error(printed);
133
+ console.error(error);
134
+ validationFailures++;
135
+ });
136
+ }
137
+ /* eslint-enable flowtype-errors/uncovered */
138
+
139
+ }
140
+
141
+ try {
142
+ (0, _generateTypeFiles.generateTypeFiles)(raw.loc.path, schemaForTypeGeneration, withTypeNames, processedOptions); // eslint-disable-next-line flowtype-errors/uncovered
143
+ } catch (err) {
144
+ console.error(`Error while generating operation from ${raw.loc.path}`);
145
+ console.error(printed); // eslint-disable-next-line flowtype-errors/uncovered
146
+
147
+ console.error(err);
148
+ validationFailures++;
149
+ }
150
+ });
151
+
152
+ if (validationFailures) {
153
+ console.error(`Encountered ${validationFailures} validation failures while printing types.`); // eslint-disable-next-line flowtype-errors/uncovered
154
+
155
+ process.exit(1);
156
+ }
157
+ //# sourceMappingURL=run.js.map