@khanacademy/graphql-flow 0.2.4 → 1.0.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 (57) hide show
  1. package/.flowconfig +1 -0
  2. package/.github/workflows/changeset-release.yml +3 -17
  3. package/.github/workflows/pr-checks.yml +13 -10
  4. package/CHANGELOG.md +35 -0
  5. package/Readme.md +41 -132
  6. package/dist/__test__/example-schema.graphql +67 -0
  7. package/dist/__test__/generateTypeFileContents.test.js +157 -0
  8. package/dist/__test__/graphql-flow.test.js +639 -0
  9. package/dist/__test__/processPragmas.test.js +76 -0
  10. package/dist/cli/__test__/config.test.js +120 -0
  11. package/dist/cli/config.js +51 -26
  12. package/dist/cli/config.js.flow +40 -68
  13. package/dist/cli/config.js.map +1 -1
  14. package/dist/cli/run.js +41 -19
  15. package/dist/cli/run.js.flow +58 -22
  16. package/dist/cli/run.js.map +1 -1
  17. package/dist/cli/schema.json +91 -0
  18. package/dist/enums.js +20 -7
  19. package/dist/enums.js.flow +44 -15
  20. package/dist/enums.js.map +1 -1
  21. package/dist/generateResponseType.js +47 -47
  22. package/dist/generateResponseType.js.flow +55 -57
  23. package/dist/generateResponseType.js.map +1 -1
  24. package/dist/generateTypeFiles.js +41 -22
  25. package/dist/generateTypeFiles.js.flow +86 -78
  26. package/dist/generateTypeFiles.js.map +1 -1
  27. package/dist/generateVariablesType.js +24 -24
  28. package/dist/generateVariablesType.js.flow +25 -28
  29. package/dist/generateVariablesType.js.map +1 -1
  30. package/dist/index.js +18 -20
  31. package/dist/index.js.flow +31 -16
  32. package/dist/index.js.map +1 -1
  33. package/dist/parser/__test__/parse.test.js +247 -0
  34. package/dist/types.js.flow +28 -5
  35. package/flow-typed/npm/@babel/types_vx.x.x.js +17 -3
  36. package/package.json +3 -2
  37. package/src/__test__/generateTypeFileContents.test.js +55 -1
  38. package/src/__test__/graphql-flow.test.js +7 -7
  39. package/src/__test__/processPragmas.test.js +28 -15
  40. package/src/cli/__test__/config.test.js +120 -0
  41. package/src/cli/config.js +40 -68
  42. package/src/cli/run.js +58 -22
  43. package/src/cli/schema.json +91 -0
  44. package/src/enums.js +44 -15
  45. package/src/generateResponseType.js +55 -57
  46. package/src/generateTypeFiles.js +86 -78
  47. package/src/generateVariablesType.js +25 -28
  48. package/src/index.js +31 -16
  49. package/src/types.js +28 -5
  50. package/.github/actions/filter-files/action.yml +0 -37
  51. package/.github/actions/full-or-limited/action.yml +0 -27
  52. package/.github/actions/json-args/action.yml +0 -32
  53. package/.github/actions/setup/action.yml +0 -28
  54. package/dist/jest-mock-graphql-tag.js +0 -88
  55. package/dist/jest-mock-graphql-tag.js.flow +0 -96
  56. package/dist/jest-mock-graphql-tag.js.map +0 -1
  57. package/src/jest-mock-graphql-tag.js +0 -96
@@ -0,0 +1,120 @@
1
+ // @flow
2
+ import type {Config} from '../../types';
3
+
4
+ import {findApplicableConfig, validateOrThrow} from '../config';
5
+ import configSchema from '../schema.json'; // eslint-disable-line flowtype-errors/uncovered
6
+
7
+ describe('findApplicableConfig', () => {
8
+ it('should work with one that matches', () => {
9
+ const config = {
10
+ schemaFilePath: 'ok.graphql',
11
+ };
12
+ expect(findApplicableConfig('/hello', config)).toBe(config);
13
+ });
14
+
15
+ it('should be falsy if nothing matches', () => {
16
+ const config = {
17
+ schemaFilePath: 'ok.graphql',
18
+ exclude: [/hello$/],
19
+ };
20
+ expect(findApplicableConfig('/hello', config)).toBeUndefined();
21
+ });
22
+
23
+ it('should match & exclude with multiple configs', () => {
24
+ const configs = [
25
+ {schemaFilePath: 'one', match: [/\.jsx$/], exclude: [/^test/]},
26
+ {schemaFilePath: 'two', exclude: [/^hello/]},
27
+ {schemaFilePath: 'three'},
28
+ ];
29
+ expect(findApplicableConfig('hello.js', configs)).toBe(configs[2]);
30
+ expect(findApplicableConfig('goodbye.js', configs)).toBe(configs[1]);
31
+ expect(findApplicableConfig('hello.jsx', configs)).toBe(configs[0]);
32
+ expect(findApplicableConfig('test.jsx', configs)).toBe(configs[1]);
33
+ });
34
+ });
35
+
36
+ describe('jsonschema validation', () => {
37
+ it('should accept valid schema', () => {
38
+ const config: Config = {
39
+ crawl: {
40
+ root: '/here/we/crawl',
41
+ },
42
+ generate: {
43
+ match: [/\.fixture\.js$/],
44
+ exclude: [
45
+ '_test\\.js$',
46
+ '\\bcourse-editor-package\\b',
47
+ '\\.fixture\\.js$',
48
+ '\\b__flowtests__\\b',
49
+ '\\bcourse-editor\\b',
50
+ ],
51
+ readOnlyArray: false,
52
+ regenerateCommand: 'make gqlflow',
53
+ scalars: {
54
+ JSONString: 'string',
55
+ KALocale: 'string',
56
+ NaiveDateTime: 'string',
57
+ },
58
+ splitTypes: true,
59
+ generatedDirectory: '__graphql-types__',
60
+ exportAllObjectTypes: true,
61
+ schemaFilePath: './composed_schema.graphql',
62
+ },
63
+ };
64
+ validateOrThrow(
65
+ config,
66
+ configSchema, // eslint-disable-line flowtype-errors/uncovered
67
+ );
68
+ });
69
+
70
+ it('should accept a schema with multiple generate configs', () => {
71
+ const generate = {
72
+ match: [/\.fixture\.js$/],
73
+ exclude: [
74
+ '_test\\.js$',
75
+ '\\bcourse-editor-package\\b',
76
+ '\\.fixture\\.js$',
77
+ '\\b__flowtests__\\b',
78
+ '\\bcourse-editor\\b',
79
+ ],
80
+ readOnlyArray: false,
81
+ regenerateCommand: 'make gqlflow',
82
+ scalars: {
83
+ JSONString: 'string',
84
+ KALocale: 'string',
85
+ NaiveDateTime: 'string',
86
+ },
87
+ splitTypes: true,
88
+ generatedDirectory: '__graphql-types__',
89
+ exportAllObjectTypes: true,
90
+ schemaFilePath: './composed_schema.graphql',
91
+ };
92
+ const config: Config = {
93
+ crawl: {
94
+ root: '/here/we/crawl',
95
+ },
96
+ generate: [
97
+ {...generate, match: [/^static/], exportAllObjectTypes: false},
98
+ generate,
99
+ ],
100
+ };
101
+ validateOrThrow(
102
+ config,
103
+ configSchema, // eslint-disable-line flowtype-errors/uncovered
104
+ );
105
+ });
106
+
107
+ it('should reject invalid schema', () => {
108
+ expect(() =>
109
+ validateOrThrow(
110
+ {schemaFilePath: 10, options: {extraOption: 'hello'}},
111
+ configSchema, // eslint-disable-line flowtype-errors/uncovered
112
+ ),
113
+ ).toThrowErrorMatchingInlineSnapshot(`
114
+ "instance is not allowed to have the additional property \\"schemaFilePath\\"
115
+ instance is not allowed to have the additional property \\"options\\"
116
+ instance requires property \\"crawl\\"
117
+ instance requires property \\"generate\\""
118
+ `);
119
+ });
120
+ });
@@ -3,45 +3,42 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.loadConfigFile = exports.getSchemas = void 0;
6
+ exports.validateOrThrow = exports.loadConfigFile = exports.getSchemas = exports.findApplicableConfig = void 0;
7
7
 
8
8
  var _schemaFromIntrospectionData = require("../schemaFromIntrospectionData");
9
9
 
10
+ var _schema = _interopRequireDefault(require("./schema.json"));
11
+
10
12
  var _fs = _interopRequireDefault(require("fs"));
11
13
 
12
14
  var _graphql = require("graphql");
13
15
 
14
- var _path = _interopRequireDefault(require("path"));
16
+ var _jsonschema = require("jsonschema");
15
17
 
16
18
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
17
19
 
18
- const loadConfigFile = configFile => {
19
- var _data$options, _data$excludes$map, _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', 'dumpOperations'];
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
- });
20
+ // eslint-disable-line flowtype-errors/uncovered
21
+ // eslint-disable-line flowtype-errors/uncovered
22
+ const validateOrThrow = (value, jsonSchema) => {
23
+ /* eslint-disable flowtype-errors/uncovered */
24
+ const result = (0, _jsonschema.validate)(value, jsonSchema);
29
25
 
30
- if (data.options) {
31
- const externalOptionsKeys = ['pragma', 'loosePragma', 'ignorePragma', 'scalars', 'strictNullability', 'regenerateCommand', 'readOnlyArray', 'splitTypes', 'generatedDirectory', 'exportAllObjectTypes', 'typeFileName'];
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
- });
26
+ if (!result.valid) {
27
+ throw new Error(result.errors.map(error => error.toString()).join('\n'));
37
28
  }
29
+ /* eslint-enable flowtype-errors/uncovered */
30
+
31
+ };
38
32
 
39
- return {
40
- options: (_data$options = data.options) !== null && _data$options !== void 0 ? _data$options : {},
41
- excludes: (_data$excludes$map = (_data$excludes = data.excludes) === null || _data$excludes === void 0 ? void 0 : _data$excludes.map(string => new RegExp(string))) !== null && _data$excludes$map !== void 0 ? _data$excludes$map : [],
42
- schemaFilePath: _path.default.join(_path.default.dirname(configFile), data.schemaFilePath),
43
- dumpOperations: data.dumpOperations
44
- };
33
+ exports.validateOrThrow = validateOrThrow;
34
+
35
+ const loadConfigFile = configFile => {
36
+ // $FlowIgnore // eslint-disable-next-line flowtype-errors/uncovered
37
+ const data = require(configFile); // eslint-disable-next-line flowtype-errors/uncovered
38
+
39
+
40
+ validateOrThrow(data, _schema.default);
41
+ return data;
45
42
  };
46
43
  /**
47
44
  * Loads a .json 'introspection query response', or a .graphql schema definition.
@@ -69,6 +66,34 @@ const getSchemas = schemaFilePath => {
69
66
  return [schemaForValidation, schemaForTypeGeneration];
70
67
  }
71
68
  };
69
+ /**
70
+ * Find the first item of the `config.generate` array where both:
71
+ * - no item of `exclude` matches
72
+ * - at least one item of `match` matches
73
+ */
74
+
72
75
 
73
76
  exports.getSchemas = getSchemas;
77
+
78
+ const findApplicableConfig = (path, configs) => {
79
+ if (!Array.isArray(configs)) {
80
+ configs = [configs];
81
+ }
82
+
83
+ return configs.find(config => {
84
+ var _config$exclude;
85
+
86
+ if ((_config$exclude = config.exclude) !== null && _config$exclude !== void 0 && _config$exclude.some(exclude => new RegExp(exclude).test(path))) {
87
+ return false;
88
+ }
89
+
90
+ if (!config.match) {
91
+ return true;
92
+ }
93
+
94
+ return config.match.some(matcher => new RegExp(matcher).test(path));
95
+ });
96
+ };
97
+
98
+ exports.findApplicableConfig = findApplicableConfig;
74
99
  //# sourceMappingURL=config.js.map
@@ -1,9 +1,9 @@
1
1
  // @flow
2
- import type {ExternalOptions} from '../generateTypeFiles';
3
2
  import type {Schema} from '../types';
4
3
  import type {GraphQLSchema} from 'graphql/type/schema';
5
4
 
6
5
  import {schemaFromIntrospectionData} from '../schemaFromIntrospectionData';
6
+ import configSchema from './schema.json'; // eslint-disable-line flowtype-errors/uncovered
7
7
 
8
8
  import fs from 'fs';
9
9
  import {
@@ -13,77 +13,26 @@ import {
13
13
  graphqlSync,
14
14
  type IntrospectionQuery,
15
15
  } from 'graphql';
16
- import path from 'path';
16
+ import type {Config, GenerateConfig} from '../types';
17
+ import {validate} from 'jsonschema'; // eslint-disable-line flowtype-errors/uncovered
17
18
 
18
- export type CliConfig = {
19
- excludes: Array<RegExp>,
20
- schemaFilePath: string,
21
- dumpOperations?: string,
22
- options: ExternalOptions,
23
- };
24
-
25
- /**
26
- * This is the json-compatible form of the config
27
- * object.
28
- */
29
- type JSONConfig = {
30
- excludes?: Array<string>,
31
- schemaFilePath: string,
32
- options?: ExternalOptions,
33
- dumpOperations?: string,
19
+ export const validateOrThrow = (value: mixed, jsonSchema: mixed) => {
20
+ /* eslint-disable flowtype-errors/uncovered */
21
+ const result = validate(value, jsonSchema);
22
+ if (!result.valid) {
23
+ throw new Error(
24
+ result.errors.map((error) => error.toString()).join('\n'),
25
+ );
26
+ }
27
+ /* eslint-enable flowtype-errors/uncovered */
34
28
  };
35
29
 
36
- export const loadConfigFile = (configFile: string): CliConfig => {
30
+ export const loadConfigFile = (configFile: string): Config => {
31
+ // $FlowIgnore // eslint-disable-next-line flowtype-errors/uncovered
32
+ const data: Config = require(configFile);
37
33
  // eslint-disable-next-line flowtype-errors/uncovered
38
- const data: JSONConfig = JSON.parse(fs.readFileSync(configFile, 'utf8'));
39
- const toplevelKeys = [
40
- 'excludes',
41
- 'schemaFilePath',
42
- 'options',
43
- 'dumpOperations',
44
- ];
45
- Object.keys(data).forEach((k) => {
46
- if (!toplevelKeys.includes(k)) {
47
- throw new Error(
48
- `Invalid attribute in config file ${configFile}: ${k}. Allowed attributes: ${toplevelKeys.join(
49
- ', ',
50
- )}`,
51
- );
52
- }
53
- });
54
- if (data.options) {
55
- const externalOptionsKeys = [
56
- 'pragma',
57
- 'loosePragma',
58
- 'ignorePragma',
59
- 'scalars',
60
- 'strictNullability',
61
- 'regenerateCommand',
62
- 'readOnlyArray',
63
- 'splitTypes',
64
- 'generatedDirectory',
65
- 'exportAllObjectTypes',
66
- 'typeFileName',
67
- ];
68
- Object.keys(data.options).forEach((k) => {
69
- if (!externalOptionsKeys.includes(k)) {
70
- throw new Error(
71
- `Invalid option in config file ${configFile}: ${k}. Allowed options: ${externalOptionsKeys.join(
72
- ', ',
73
- )}`,
74
- );
75
- }
76
- });
77
- }
78
- return {
79
- options: data.options ?? {},
80
- excludes: data.excludes?.map((string) => new RegExp(string)) ?? [],
81
- schemaFilePath: path.join(
82
- path.dirname(configFile),
83
- data.schemaFilePath,
84
- ),
85
- dumpOperations: data.dumpOperations,
86
- };
34
+ validateOrThrow(data, configSchema);
35
+ return data;
87
36
  };
88
37
 
89
38
  /**
@@ -111,3 +60,26 @@ export const getSchemas = (schemaFilePath: string): [GraphQLSchema, Schema] => {
111
60
  return [schemaForValidation, schemaForTypeGeneration];
112
61
  }
113
62
  };
63
+
64
+ /**
65
+ * Find the first item of the `config.generate` array where both:
66
+ * - no item of `exclude` matches
67
+ * - at least one item of `match` matches
68
+ */
69
+ export const findApplicableConfig = (
70
+ path: string,
71
+ configs: Array<GenerateConfig> | GenerateConfig,
72
+ ): ?GenerateConfig => {
73
+ if (!Array.isArray(configs)) {
74
+ configs = [configs];
75
+ }
76
+ return configs.find((config) => {
77
+ if (config.exclude?.some((exclude) => new RegExp(exclude).test(path))) {
78
+ return false;
79
+ }
80
+ if (!config.match) {
81
+ return true;
82
+ }
83
+ return config.match.some((matcher) => new RegExp(matcher).test(path));
84
+ });
85
+ };
@@ -1 +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","dumpOperations","getSchemas","raw","endsWith","schemaForValidation","queryResponse","descriptions","schemaForTypeGeneration","introspectionData"],"mappings":";;;;;;;AAKA;;AAEA;;AACA;;AAOA;;;;AAoBO,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,CACjB,UADiB,EAEjB,gBAFiB,EAGjB,SAHiB,EAIjB,gBAJiB,CAArB;AAMAC,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,EAWxB,cAXwB,CAA5B;AAaAR,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,mBAAEb,IAAI,CAACa,OAAP,yDAAkB,EADtB;AAEHE,IAAAA,QAAQ,0CAAEf,IAAI,CAACe,QAAP,mDAAE,eAAeC,GAAf,CAAoBC,MAAD,IAAY,IAAIC,MAAJ,CAAWD,MAAX,CAA/B,CAAF,mEAAwD,EAF7D;AAGHE,IAAAA,cAAc,EAAEC,cAAKR,IAAL,CACZQ,cAAKC,OAAL,CAAatB,UAAb,CADY,EAEZC,IAAI,CAACmB,cAFO,CAHb;AAOHG,IAAAA,cAAc,EAAEtB,IAAI,CAACsB;AAPlB,GAAP;AASH,CAnDM;AAqDP;AACA;AACA;;;;;AACO,MAAMC,UAAU,GAAIJ,cAAD,IAAqD;AAC3E,QAAMK,GAAG,GAAGrB,YAAGC,YAAH,CAAgBe,cAAhB,EAAgC,MAAhC,CAAZ;;AACA,MAAIA,cAAc,CAACM,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,CAAC3B,IAFY,CAAhC;AAIA,WAAO,CAAC0B,mBAAD,EAAsBG,uBAAtB,CAAP;AACH,GAXD,MAWO;AACH;AACA,UAAMC,iBAAqC,GAAG7B,IAAI,CAACC,KAAL,CAAWsB,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 dumpOperations?: 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 dumpOperations?: string,\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 = [\n 'excludes',\n 'schemaFilePath',\n 'options',\n 'dumpOperations',\n ];\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 'typeFileName',\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 dumpOperations: data.dumpOperations,\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"}
1
+ {"version":3,"sources":["../../src/cli/config.js"],"names":["validateOrThrow","value","jsonSchema","result","valid","Error","errors","map","error","toString","join","loadConfigFile","configFile","data","require","configSchema","getSchemas","schemaFilePath","raw","fs","readFileSync","endsWith","schemaForValidation","queryResponse","descriptions","schemaForTypeGeneration","introspectionData","JSON","parse","findApplicableConfig","path","configs","Array","isArray","find","config","exclude","some","RegExp","test","match","matcher"],"mappings":";;;;;;;AAIA;;AACA;;AAEA;;AACA;;AAQA;;;;AAX0C;AAWL;AAE9B,MAAMA,eAAe,GAAG,CAACC,KAAD,EAAeC,UAAf,KAAqC;AAChE;AACA,QAAMC,MAAM,GAAG,0BAASF,KAAT,EAAgBC,UAAhB,CAAf;;AACA,MAAI,CAACC,MAAM,CAACC,KAAZ,EAAmB;AACf,UAAM,IAAIC,KAAJ,CACFF,MAAM,CAACG,MAAP,CAAcC,GAAd,CAAmBC,KAAD,IAAWA,KAAK,CAACC,QAAN,EAA7B,EAA+CC,IAA/C,CAAoD,IAApD,CADE,CAAN;AAGH;AACD;;AACH,CATM;;;;AAWA,MAAMC,cAAc,GAAIC,UAAD,IAAgC;AAC1D;AACA,QAAMC,IAAY,GAAGC,OAAO,CAACF,UAAD,CAA5B,CAF0D,CAG1D;;;AACAZ,EAAAA,eAAe,CAACa,IAAD,EAAOE,eAAP,CAAf;AACA,SAAOF,IAAP;AACH,CANM;AAQP;AACA;AACA;;;;;AACO,MAAMG,UAAU,GAAIC,cAAD,IAAqD;AAC3E,QAAMC,GAAG,GAAGC,YAAGC,YAAH,CAAgBH,cAAhB,EAAgC,MAAhC,CAAZ;;AACA,MAAIA,cAAc,CAACI,QAAf,CAAwB,UAAxB,CAAJ,EAAyC;AACrC,UAAMC,mBAAmB,GAAG,0BAAYJ,GAAZ,CAA5B;AACA,UAAMK,aAAa,GAAG,0BAClBD,mBADkB,EAElB,oCAAsB;AAACE,MAAAA,YAAY,EAAE;AAAf,KAAtB,CAFkB,CAAtB;AAIA,UAAMC,uBAAuB,GAAG,+DAC5B;AACEF,IAAAA,aAAa,CAACV,IAFY,CAAhC;AAIA,WAAO,CAACS,mBAAD,EAAsBG,uBAAtB,CAAP;AACH,GAXD,MAWO;AACH;AACA,UAAMC,iBAAqC,GAAGC,IAAI,CAACC,KAAL,CAAWV,GAAX,CAA9C;AACA,UAAMI,mBAAmB,GAAG,gCAAkBI,iBAAlB,CAA5B;AACA,UAAMD,uBAAuB,GACzB,8DAA4BC,iBAA5B,CADJ;AAEA,WAAO,CAACJ,mBAAD,EAAsBG,uBAAtB,CAAP;AACH;AACJ,CArBM;AAuBP;AACA;AACA;AACA;AACA;;;;;AACO,MAAMI,oBAAoB,GAAG,CAChCC,IADgC,EAEhCC,OAFgC,KAGd;AAClB,MAAI,CAACC,KAAK,CAACC,OAAN,CAAcF,OAAd,CAAL,EAA6B;AACzBA,IAAAA,OAAO,GAAG,CAACA,OAAD,CAAV;AACH;;AACD,SAAOA,OAAO,CAACG,IAAR,CAAcC,MAAD,IAAY;AAAA;;AAC5B,2BAAIA,MAAM,CAACC,OAAX,4CAAI,gBAAgBC,IAAhB,CAAsBD,OAAD,IAAa,IAAIE,MAAJ,CAAWF,OAAX,EAAoBG,IAApB,CAAyBT,IAAzB,CAAlC,CAAJ,EAAuE;AACnE,aAAO,KAAP;AACH;;AACD,QAAI,CAACK,MAAM,CAACK,KAAZ,EAAmB;AACf,aAAO,IAAP;AACH;;AACD,WAAOL,MAAM,CAACK,KAAP,CAAaH,IAAb,CAAmBI,OAAD,IAAa,IAAIH,MAAJ,CAAWG,OAAX,EAAoBF,IAApB,CAAyBT,IAAzB,CAA/B,CAAP;AACH,GARM,CAAP;AASH,CAhBM","sourcesContent":["// @flow\nimport type {Schema} from '../types';\nimport type {GraphQLSchema} from 'graphql/type/schema';\n\nimport {schemaFromIntrospectionData} from '../schemaFromIntrospectionData';\nimport configSchema from './schema.json'; // eslint-disable-line flowtype-errors/uncovered\n\nimport fs from 'fs';\nimport {\n buildClientSchema,\n buildSchema,\n getIntrospectionQuery,\n graphqlSync,\n type IntrospectionQuery,\n} from 'graphql';\nimport type {Config, GenerateConfig} from '../types';\nimport {validate} from 'jsonschema'; // eslint-disable-line flowtype-errors/uncovered\n\nexport const validateOrThrow = (value: mixed, jsonSchema: mixed) => {\n /* eslint-disable flowtype-errors/uncovered */\n const result = validate(value, jsonSchema);\n if (!result.valid) {\n throw new Error(\n result.errors.map((error) => error.toString()).join('\\n'),\n );\n }\n /* eslint-enable flowtype-errors/uncovered */\n};\n\nexport const loadConfigFile = (configFile: string): Config => {\n // $FlowIgnore // eslint-disable-next-line flowtype-errors/uncovered\n const data: Config = require(configFile);\n // eslint-disable-next-line flowtype-errors/uncovered\n validateOrThrow(data, configSchema);\n return data;\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\n/**\n * Find the first item of the `config.generate` array where both:\n * - no item of `exclude` matches\n * - at least one item of `match` matches\n */\nexport const findApplicableConfig = (\n path: string,\n configs: Array<GenerateConfig> | GenerateConfig,\n): ?GenerateConfig => {\n if (!Array.isArray(configs)) {\n configs = [configs];\n }\n return configs.find((config) => {\n if (config.exclude?.some((exclude) => new RegExp(exclude).test(path))) {\n return false;\n }\n if (!config.match) {\n return true;\n }\n return config.match.some((matcher) => new RegExp(matcher).test(path));\n });\n};\n"],"file":"config.js"}
package/dist/cli/run.js CHANGED
@@ -31,7 +31,7 @@ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj &&
31
31
 
32
32
  /**
33
33
  * This CLI tool executes the following steps:
34
- * 1) process options
34
+ * 1) parse & validate config file
35
35
  * 2) crawl files to find all operations and fragments, with
36
36
  * tagged template literals and expressions.
37
37
  * 3) resolve the found operations, passing the literals and
@@ -49,18 +49,22 @@ const findGraphqlTagReferences = root => {
49
49
  return response.trim().split('\n').map(relative => _path.default.join(root, relative));
50
50
  };
51
51
 
52
- const [_, __, configFile, ...cliFiles] = process.argv;
52
+ const [_, __, configFilePath, ...cliFiles] = process.argv;
53
53
 
54
- if (configFile === '-h' || configFile === '--help' || configFile === 'help' || !configFile) {
54
+ if (configFilePath === '-h' || configFilePath === '--help' || configFilePath === 'help' || !configFilePath) {
55
55
  console.log(`graphql-flow
56
56
 
57
57
  Usage: graphql-flow [configFile.json] [filesToCrawl...]`);
58
58
  process.exit(1); // eslint-disable-line flowtype-errors/uncovered
59
59
  }
60
60
 
61
- const config = (0, _config.loadConfigFile)(configFile);
62
- const [schemaForValidation, schemaForTypeGeneration] = (0, _config.getSchemas)(config.schemaFilePath);
63
- const inputFiles = cliFiles.length ? cliFiles : findGraphqlTagReferences(process.cwd());
61
+ const makeAbsPath = (maybeRelativePath, basePath) => {
62
+ return _path.default.isAbsolute(maybeRelativePath) ? maybeRelativePath : _path.default.join(basePath, maybeRelativePath);
63
+ };
64
+
65
+ const absConfigPath = makeAbsPath(configFilePath, process.cwd());
66
+ const config = (0, _config.loadConfigFile)(absConfigPath);
67
+ const inputFiles = cliFiles.length ? cliFiles : findGraphqlTagReferences(makeAbsPath(config.crawl.root, _path.default.dirname(absConfigPath)));
64
68
  /** Step (2) */
65
69
 
66
70
  const files = (0, _parse.processFiles)(inputFiles, f => {
@@ -110,22 +114,34 @@ if (errors.length) {
110
114
  console.log(Object.keys(resolved).length, 'resolved queries');
111
115
  /** Step (4) */
112
116
 
117
+ const schemaCache = {};
118
+
119
+ const getCachedSchemas = schemaFilePath => {
120
+ if (!schemaCache[schemaFilePath]) {
121
+ schemaCache[schemaFilePath] = (0, _config.getSchemas)(makeAbsPath(schemaFilePath, _path.default.dirname(absConfigPath)));
122
+ }
123
+
124
+ return schemaCache[schemaFilePath];
125
+ };
126
+
113
127
  let validationFailures = 0;
114
128
  const printedOperations = [];
115
- Object.keys(resolved).forEach(k => {
129
+ Object.keys(resolved).forEach(filePathAndLine => {
116
130
  const {
117
131
  document,
118
132
  raw
119
- } = resolved[k];
120
-
121
- if (config.excludes.some(rx => rx.test(raw.loc.path))) {
122
- return; // skip
123
- }
124
-
133
+ } = resolved[filePathAndLine];
125
134
  const hasNonFragments = document.definitions.some(({
126
135
  kind
127
136
  }) => kind !== 'FragmentDefinition');
128
- const rawSource = raw.literals[0]; // eslint-disable-next-line flowtype-errors/uncovered
137
+ const rawSource = raw.literals[0];
138
+ const generateConfig = (0, _config.findApplicableConfig)( // strip off the trailing line number, e.g. `:23`
139
+ filePathAndLine.split(':')[0], config.generate);
140
+
141
+ if (!generateConfig) {
142
+ return; // no generate config matches, bail
143
+ } // eslint-disable-next-line flowtype-errors/uncovered
144
+
129
145
 
130
146
  const withTypeNames = (0, _apolloUtilities.addTypenameToDocument)(document);
131
147
  const printed = (0, _printer.print)(withTypeNames);
@@ -134,12 +150,18 @@ Object.keys(resolved).forEach(k => {
134
150
  printedOperations.push(printed);
135
151
  }
136
152
 
137
- const processedOptions = (0, _generateTypeFiles.processPragmas)(config.options, rawSource);
153
+ const pragmaResult = (0, _generateTypeFiles.processPragmas)(generateConfig, config.crawl, rawSource);
138
154
 
139
- if (!processedOptions) {
155
+ if (!pragmaResult.generate) {
140
156
  return;
141
157
  }
142
158
 
159
+ if (pragmaResult.strict != null) {
160
+ generateConfig.strictNullability = pragmaResult.strict;
161
+ }
162
+
163
+ const [schemaForValidation, schemaForTypeGeneration] = getCachedSchemas(generateConfig.schemaFilePath);
164
+
143
165
  if (hasNonFragments) {
144
166
  /* eslint-disable flowtype-errors/uncovered */
145
167
  const errors = (0, _validation.validate)(schemaForValidation, withTypeNames);
@@ -158,7 +180,7 @@ Object.keys(resolved).forEach(k => {
158
180
  }
159
181
 
160
182
  try {
161
- (0, _generateTypeFiles.generateTypeFiles)(raw.loc.path, schemaForTypeGeneration, withTypeNames, processedOptions); // eslint-disable-next-line flowtype-errors/uncovered
183
+ (0, _generateTypeFiles.generateTypeFiles)(raw.loc.path, schemaForTypeGeneration, withTypeNames, generateConfig); // eslint-disable-next-line flowtype-errors/uncovered
162
184
  } catch (err) {
163
185
  console.error(`Error while generating operation from ${raw.loc.path}`);
164
186
  console.error(printed); // eslint-disable-next-line flowtype-errors/uncovered
@@ -174,8 +196,8 @@ if (validationFailures) {
174
196
  process.exit(1);
175
197
  }
176
198
 
177
- if (config.dumpOperations) {
178
- const dumpOperations = config.dumpOperations;
199
+ if (config.crawl.dumpOperations) {
200
+ const dumpOperations = config.crawl.dumpOperations;
179
201
  const parent = (0, _path.dirname)(dumpOperations);
180
202
  (0, _fs.mkdirSync)(parent, {
181
203
  recursive: true
@@ -4,7 +4,7 @@
4
4
  import {generateTypeFiles, processPragmas} from '../generateTypeFiles';
5
5
  import {processFiles} from '../parser/parse';
6
6
  import {resolveDocuments} from '../parser/resolve';
7
- import {getSchemas, loadConfigFile} from './config';
7
+ import {findApplicableConfig, getSchemas, loadConfigFile} from './config';
8
8
 
9
9
  import {addTypenameToDocument} from 'apollo-utilities'; // eslint-disable-line flowtype-errors/uncovered
10
10
 
@@ -15,10 +15,11 @@ import {print} from 'graphql/language/printer';
15
15
  import {validate} from 'graphql/validation';
16
16
  import path from 'path';
17
17
  import {dirname} from 'path';
18
+ import type {GenerateConfig} from '../types';
18
19
 
19
20
  /**
20
21
  * This CLI tool executes the following steps:
21
- * 1) process options
22
+ * 1) parse & validate config file
22
23
  * 2) crawl files to find all operations and fragments, with
23
24
  * tagged template literals and expressions.
24
25
  * 3) resolve the found operations, passing the literals and
@@ -43,13 +44,13 @@ const findGraphqlTagReferences = (root: string): Array<string> => {
43
44
  .map((relative) => path.join(root, relative));
44
45
  };
45
46
 
46
- const [_, __, configFile, ...cliFiles] = process.argv;
47
+ const [_, __, configFilePath, ...cliFiles] = process.argv;
47
48
 
48
49
  if (
49
- configFile === '-h' ||
50
- configFile === '--help' ||
51
- configFile === 'help' ||
52
- !configFile
50
+ configFilePath === '-h' ||
51
+ configFilePath === '--help' ||
52
+ configFilePath === 'help' ||
53
+ !configFilePath
53
54
  ) {
54
55
  console.log(`graphql-flow
55
56
 
@@ -57,15 +58,21 @@ Usage: graphql-flow [configFile.json] [filesToCrawl...]`);
57
58
  process.exit(1); // eslint-disable-line flowtype-errors/uncovered
58
59
  }
59
60
 
60
- const config = loadConfigFile(configFile);
61
+ const makeAbsPath = (maybeRelativePath: string, basePath: string) => {
62
+ return path.isAbsolute(maybeRelativePath)
63
+ ? maybeRelativePath
64
+ : path.join(basePath, maybeRelativePath);
65
+ };
66
+
67
+ const absConfigPath = makeAbsPath(configFilePath, process.cwd());
61
68
 
62
- const [schemaForValidation, schemaForTypeGeneration] = getSchemas(
63
- config.schemaFilePath,
64
- );
69
+ const config = loadConfigFile(absConfigPath);
65
70
 
66
71
  const inputFiles = cliFiles.length
67
72
  ? cliFiles
68
- : findGraphqlTagReferences(process.cwd());
73
+ : findGraphqlTagReferences(
74
+ makeAbsPath(config.crawl.root, path.dirname(absConfigPath)),
75
+ );
69
76
 
70
77
  /** Step (2) */
71
78
 
@@ -111,19 +118,37 @@ console.log(Object.keys(resolved).length, 'resolved queries');
111
118
 
112
119
  /** Step (4) */
113
120
 
121
+ const schemaCache = {};
122
+ const getCachedSchemas = (schemaFilePath: string) => {
123
+ if (!schemaCache[schemaFilePath]) {
124
+ schemaCache[schemaFilePath] = getSchemas(
125
+ makeAbsPath(schemaFilePath, path.dirname(absConfigPath)),
126
+ );
127
+ }
128
+
129
+ return schemaCache[schemaFilePath];
130
+ };
131
+
114
132
  let validationFailures: number = 0;
115
133
  const printedOperations: Array<string> = [];
116
134
 
117
- Object.keys(resolved).forEach((k) => {
118
- const {document, raw} = resolved[k];
119
- if (config.excludes.some((rx) => rx.test(raw.loc.path))) {
120
- return; // skip
121
- }
135
+ Object.keys(resolved).forEach((filePathAndLine) => {
136
+ const {document, raw} = resolved[filePathAndLine];
137
+
122
138
  const hasNonFragments = document.definitions.some(
123
139
  ({kind}) => kind !== 'FragmentDefinition',
124
140
  );
125
141
  const rawSource: string = raw.literals[0];
126
142
 
143
+ const generateConfig = findApplicableConfig(
144
+ // strip off the trailing line number, e.g. `:23`
145
+ filePathAndLine.split(':')[0],
146
+ config.generate,
147
+ );
148
+ if (!generateConfig) {
149
+ return; // no generate config matches, bail
150
+ }
151
+
127
152
  // eslint-disable-next-line flowtype-errors/uncovered
128
153
  const withTypeNames: DocumentNode = addTypenameToDocument(document);
129
154
  const printed = print(withTypeNames);
@@ -131,10 +156,21 @@ Object.keys(resolved).forEach((k) => {
131
156
  printedOperations.push(printed);
132
157
  }
133
158
 
134
- const processedOptions = processPragmas(config.options, rawSource);
135
- if (!processedOptions) {
159
+ const pragmaResult = processPragmas(
160
+ generateConfig,
161
+ config.crawl,
162
+ rawSource,
163
+ );
164
+ if (!pragmaResult.generate) {
136
165
  return;
137
166
  }
167
+ if (pragmaResult.strict != null) {
168
+ generateConfig.strictNullability = pragmaResult.strict;
169
+ }
170
+
171
+ const [schemaForValidation, schemaForTypeGeneration] = getCachedSchemas(
172
+ generateConfig.schemaFilePath,
173
+ );
138
174
 
139
175
  if (hasNonFragments) {
140
176
  /* eslint-disable flowtype-errors/uncovered */
@@ -158,7 +194,7 @@ Object.keys(resolved).forEach((k) => {
158
194
  raw.loc.path,
159
195
  schemaForTypeGeneration,
160
196
  withTypeNames,
161
- processedOptions,
197
+ generateConfig,
162
198
  );
163
199
  // eslint-disable-next-line flowtype-errors/uncovered
164
200
  } catch (err) {
@@ -178,8 +214,8 @@ if (validationFailures) {
178
214
  process.exit(1);
179
215
  }
180
216
 
181
- if (config.dumpOperations) {
182
- const dumpOperations = config.dumpOperations;
217
+ if (config.crawl.dumpOperations) {
218
+ const dumpOperations = config.crawl.dumpOperations;
183
219
  const parent = dirname(dumpOperations);
184
220
  mkdirSync(parent, {recursive: true});
185
221
  writeFileSync(