@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
package/.flowconfig CHANGED
@@ -9,5 +9,6 @@ flow-typed/
9
9
  [lints]
10
10
 
11
11
  [options]
12
+ enums=true
12
13
 
13
14
  [strict]
@@ -34,7 +34,7 @@ jobs:
34
34
  - uses: actions/checkout@v2
35
35
  with:
36
36
  fetch-depth: 0
37
- - uses: ./.github/actions/setup
37
+ - uses: Khan/actions@shared-node-cache-v0.0.2
38
38
  with:
39
39
  node-version: 12.x
40
40
 
@@ -55,8 +55,8 @@ jobs:
55
55
  if: steps.changesets.outputs.published == 'true'
56
56
  uses: rtCamp/action-slack-notify@v2
57
57
  env:
58
- SLACK_WEBHOOK: ${{ secrets.SLACK_FEIWEB_WEBHOOK }}
59
- SLACK_CHANNEL: frontend-infra-web
58
+ SLACK_WEBHOOK: ${{ secrets.SLACK_FEI_FIREHOSE }}
59
+ SLACK_CHANNEL: fei-firehose
60
60
  SLACK_MSG_AUTHOR: ${{ github.event.pull_request.user.login }}
61
61
  SLACK_USERNAME: GithubGoose
62
62
  SLACK_ICON_EMOJI: ":goose:"
@@ -64,17 +64,3 @@ jobs:
64
64
  SLACK_TITLE: "New Graphql-Flow release!"
65
65
  SLACK_FOOTER: Graphql-Flow Slack Notification
66
66
  MSG_MINIMAL: true
67
-
68
- - name: Send a Slack notification for mobile if a publish happens
69
- if: steps.changesets.outputs.published == 'true'
70
- uses: rtCamp/action-slack-notify@v2
71
- env:
72
- SLACK_WEBHOOK: ${{ secrets.SLACK_FEIMOBILE_WEBHOOK }}
73
- SLACK_CHANNEL: frontend-infra-mobile
74
- SLACK_MSG_AUTHOR: ${{ github.event.pull_request.user.login }}
75
- SLACK_USERNAME: GithubGoose
76
- SLACK_ICON_EMOJI: ":goose:"
77
- SLACK_MESSAGE: "A new version of ${{ github.event.repository.name }} was published! 🎉 \nRelease notes → https://github.com/Khan/${{ github.event.repository.name }}/releases/"
78
- SLACK_TITLE: "New Graphql-Flow release!"
79
- SLACK_FOOTER: Graphql-Flow Slack Notification
80
- MSG_MINIMAL: true
@@ -17,16 +17,19 @@ jobs:
17
17
  node-version: [16.x]
18
18
  steps:
19
19
  - uses: actions/checkout@v2
20
- - uses: ./.github/actions/setup
21
- id: setup
20
+ - uses: Khan/actions@shared-node-cache-v0
22
21
  with:
23
22
  node-version: ${{ matrix.node-version }}
24
23
 
24
+ - name: Get All Changed Files
25
+ uses: Khan/actions@get-changed-files-v1
26
+ id: changed
27
+
25
28
  - id: js-files
26
29
  name: Find .js changed files
27
- uses: ./.github/actions/filter-files
30
+ uses: Khan/actions@filter-files-v0
28
31
  with:
29
- changed-files: ${{ steps.setup.outputs.changed_files }}
32
+ changed-files: ${{ steps.changed.outputs.files }}
30
33
  extensions: '.js'
31
34
 
32
35
  - name: Run Flow
@@ -34,14 +37,14 @@ jobs:
34
37
  run: yarn flow
35
38
 
36
39
  - id: eslint-reset
37
- uses: ./.github/actions/filter-files
40
+ uses: Khan/actions@filter-files-v0
38
41
  name: Files that would trigger a full eslint run
39
42
  with:
40
- changed-files: ${{ steps.setup.outputs.changed_files }}
43
+ changed-files: ${{ steps.changed.outputs.files }}
41
44
  files: '.eslintrc.js,package.json,.eslintignore'
42
45
 
43
46
  - name: Eslint
44
- uses: ./.github/actions/full-or-limited
47
+ uses: Khan/actions@full-or-limited-v0
45
48
  with:
46
49
  full-trigger: ${{ steps.eslint-reset.outputs.filtered }}
47
50
  full: yarn eslint
@@ -49,14 +52,14 @@ jobs:
49
52
  limited: yarn eslint {}
50
53
 
51
54
  - id: jest-reset
52
- uses: ./.github/actions/filter-files
55
+ uses: Khan/actions@filter-files-v0
53
56
  name: Files that would trigger a full jest run
54
57
  with:
55
- changed-files: ${{ steps.setup.outputs.changed_files }}
58
+ changed-files: ${{ steps.changed.outputs.files }}
56
59
  files: 'package.json,package-lock.json'
57
60
 
58
61
  - name: Jest
59
- uses: ./.github/actions/full-or-limited
62
+ uses: Khan/actions@full-or-limited-v0
60
63
  with:
61
64
  full-trigger: ${{ steps.jest-reset.outputs.filtered }}
62
65
  full: yarn jest
package/CHANGELOG.md CHANGED
@@ -1,5 +1,40 @@
1
1
  # @khanacademy/graphql-flow
2
2
 
3
+ ## 1.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - 1e1de13: BREAKING: Change config file format, split into 'crawl' and 'generate' sections
8
+ See schema.json for new organization.
9
+ - 284be55: [breaking] remove support for running graphql-flow via jest
10
+
11
+ Now the cli tool is the only way to invoke graphql-flow.
12
+
13
+ - 9f7f2d1: Breaking: removed support for multiple config files (to be replaced with override support in the central config file)
14
+
15
+ ### Minor Changes
16
+
17
+ - a99fc38: Support multiple 'generate' configs, allowing projects to have different settings for different directories or files
18
+
19
+ `config.generate` can now be an object or an array of objects, with `match` and `exclude` arrays (either a RegExp or a string that will be passed to `new RegExp()`) to fine-tune which files they apply to.
20
+
21
+ ### Patch Changes
22
+
23
+ - bcc06d0: Use jsonschema for validating the config file
24
+
25
+ ## 0.3.0
26
+
27
+ ### Minor Changes
28
+
29
+ - 093fa5f: Enable `experimentalEnums` in a config or subconfig file in to enable the export of flow enum types, which replace the default string union literals. The type currently comes with eslint decorators to skirt a bug in eslint and flow.
30
+ - 5078624: Users can add files with the name ending in `graphql-flow.config.js` with a subset of the config fields (`options`, `excludes`) in order to have more granular control of the behavior. Another field, `extends`, takes the path of another config file to use as a base and extends/overrides fields. If no `extends` is provided, the file completely overwrites any other config files (as far as `options` and `excludes`).
31
+
32
+ ## 0.2.5
33
+
34
+ ### Patch Changes
35
+
36
+ - 0df32ac: Allow generatedDir to be an absolute path
37
+
3
38
  ## 0.2.4
4
39
 
5
40
  ### Patch Changes
package/Readme.md CHANGED
@@ -4,24 +4,7 @@ This is a tool for generating flow types from graphql queries in javascript fron
4
4
 
5
5
  ## Using as a CLI tool
6
6
 
7
- Write a config file, with the following options:
8
-
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"
21
- }
22
- }
23
- }
24
- ```
7
+ Write a config file, following the schema defined in [src/cli/schema.json](src/cli/schema.json), either as a `.json` file, or a `.js` file that `module.exports` an object adhering to the schema.
25
8
 
26
9
  Then run from the CLI, like so:
27
10
 
@@ -29,128 +12,54 @@ Then run from the CLI, like so:
29
12
  $ graphql-flow path/to/config.json
30
13
  ```
31
14
 
32
- Files will be discovered relative to the current working directory.
33
-
34
- To specify what file should be checked, pass them in as subsequent cli arguments.
35
-
36
- ## Options (for the cli 'options' config item, or when running from jest):
37
-
38
- ```ts
39
- type Options = {
40
- // These are from the `documentToFlowTypes` options object above
41
- strictNullability: boolean = true,
42
- readOnlyArray: boolean = true,
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,
73
-
74
- // A template for the name of generated files
75
- // default: [operationName].js
76
- typeFileName?: string,
77
- }
78
- ```
79
-
80
- ## Using from jest
81
-
82
- You can also use jest to do the heavy lifting, running all of your code and collecting queries
83
- by mocking out the `graphql-tag` function itself. This requires that all graphql operations are
84
- defined at the top level (no queries defined in functions or components, for example), but does
85
- support complicated things like returning a fragment from a function (which is probably
86
- not a great idea code-style-wise anyway).
15
+ Files will be discovered relative to the `crawl.root`.
87
16
 
88
- ### jest-setup.js
89
-
90
- Add the following snippet to your jest-setup.js
91
-
92
- ```js
93
- if (process.env.GRAPHQL_FLOW) {
94
- jest.mock('graphql-tag', () => {
95
- const introspectionData = jest.requireActual(
96
- './server-introspection-response.json',
97
- );
98
-
99
- return jest.requireActual('../tools/graphql-flow/jest-mock-graphql-tag.js')(
100
- introspectionData,
101
- // See "Options" type above
102
- {
103
- pragma: '# @autogen\n',
104
- loosePragma: '# @autogen-loose\n',
105
- }
106
- );
107
- });
108
- }
109
- ```
17
+ ### Multiple generate configs
110
18
 
111
- That will mock out the `graphql-tag` function, so that everywhere you call 'gql`some-query`', we'll pick it up and
112
- generate a type for it! As written, the mocking only happens if the `GRAPHQL_FLOW` env variable is set, so that it won't run every time.
19
+ To customize type generation for certain directories or files, you can provide multiple
20
+ `generate` configs as an array, using `match` and `exclude` to customize behavior.
113
21
 
114
- By default, all queries are processed. To have them 'opt-in', use the `pragma` and `loosePragma` options. Queries with `loosePragma` in the source will have types generated that ignore nullability.
22
+ For a given file containing operations, the first `generate` config that matches that path
23
+ (and doesn't exclude it) will be used to generate types for those operations. If a `generate`
24
+ config doesn't have a `match` attribute, it will match all files (but might exclude some via the
25
+ `exclude` attribute).
115
26
 
116
- ### Ensure all files with queries get processed by jest
27
+ For example:
117
28
 
118
- Then you'll want to make a pseudo-'test' that makes sure to 'require' all of the files that define queries, so that
119
- jest will process them and our mock will see them.
120
29
  ```js
121
- // generate-types.test.js
122
- import {findGraphqlTagReferences} from '../tools/graphql-flow/find-files-with-gql';
123
- import path from 'path';
124
-
125
- if (process.env.GRAPHQL_FLOW) {
126
- findGraphqlTagReferences(path.join(__dirname, '..')).forEach(name => {
127
- require(name);
128
- });
129
-
130
- it('should have found queries', () => {
131
- const gql = require('graphql-tag')
132
- expect(gql.collectedQueries.length).toBeGreaterThan(0)
133
- })
134
- } else {
135
- it(`not generating graphql types because the env flag isn't set`, () => {
136
- // not doing anything because the env flag isn't set.
137
- })
138
- }
139
- ```
140
-
141
- ### Run that test to generate the types!
142
-
143
- You can add a script to package.json, like so:
144
- ```json
145
- "scripts": {
146
- "generate-types": "env GRAPHQL_FLOW=1 jest generate-types.test.js"
147
- }
30
+ // dev/graphql-flow/config.js
31
+
32
+ const options = {
33
+ schemaFilePath: "../../gengraphql/composed-schema.graphql",
34
+ regenerateCommand: "make gqlflow",
35
+ generatedDirectory: "__graphql-types__",
36
+ exclude: [
37
+ /_test.js$/,
38
+ /.fixture.js$/,
39
+ /\b__flowtests__\b/,
40
+ ],
41
+ };
42
+
43
+ module.exports = {
44
+ crawl: {
45
+ root: "../../",
46
+ },
47
+ generate: [
48
+ {
49
+ ...options,
50
+ schemaFilePath: "../../gengraphql/course-editor-schema.graphql",
51
+ match: [/\bcourse-editor-package\b/, /\bcourse-editor\b/],
52
+ },
53
+ {
54
+ ...options,
55
+ match: [/\bdiscussion-package\b/]
56
+ experimentalEnums: true,
57
+ },
58
+ options,
59
+ ],
60
+ };
148
61
  ```
149
62
 
150
- And then `yarn generate-types` or `npm run generate-types` gets your types generated!
151
-
152
- 🚀
153
-
154
63
  ## Introspecting your backend's graphql schema
155
64
  Here's how to get your backend's schema in the way that this tool expects, using the builtin 'graphql introspection query':
156
65
 
@@ -0,0 +1,67 @@
1
+ scalar PositiveNumber
2
+
3
+ enum Episode { NEW_HOPE, EMPIRE, JEDI }
4
+
5
+ """
6
+ A movie character
7
+ """
8
+ interface Character {
9
+ id: String!
10
+ name: String
11
+ friends: [Character]
12
+ appearsIn: [Episode]
13
+ }
14
+
15
+ """
16
+ A human character
17
+ """
18
+ type Human implements Character {
19
+ id: String!
20
+ """The person's name"""
21
+ name: String
22
+ friends: [Character]
23
+ appearsIn: [Episode]
24
+ homePlanet: String
25
+ alive: Boolean
26
+ hands: PositiveNumber
27
+ }
28
+
29
+ """
30
+ A robot character
31
+ """
32
+ type Droid implements Character {
33
+ id: String!
34
+ name: String
35
+ friends: [Character]
36
+ appearsIn: [Episode]
37
+ """The robot's primary function"""
38
+ primaryFunction: String!
39
+ }
40
+
41
+ type Animal {
42
+ name: String
43
+ }
44
+
45
+ union Friendable = Human | Droid | Animal
46
+
47
+ type Query {
48
+ hero(episode: Episode): Character
49
+ human(id: String!): Human
50
+ droid(id: String!): Droid
51
+ friend(id: String!): Friendable
52
+ candies(number: PositiveNumber!): String
53
+ }
54
+
55
+ """A character to add"""
56
+ input CharacterInput {
57
+ """The new character's name"""
58
+ name: String!
59
+ """The character's friends"""
60
+ friends: [ID!]
61
+ appearsIn: [Episode!]
62
+ candies: PositiveNumber!
63
+ }
64
+
65
+ type Mutation {
66
+ addCharacter(character: CharacterInput!): Character
67
+ }
@@ -0,0 +1,157 @@
1
+ // @flow
2
+ // Test the generate the type file contents
3
+
4
+ import {getSchemas} from '../cli/config';
5
+ import {generateTypeFileContents, indexPrelude} from '../generateTypeFiles';
6
+ import gql from 'graphql-tag';
7
+
8
+ const [_, exampleSchema] = getSchemas(__dirname + '/example-schema.graphql');
9
+
10
+ describe('generateTypeFileContents', () => {
11
+ it('split types should export response & variables types', () => {
12
+ const {indexContents, files} = generateTypeFileContents(
13
+ 'hello.js',
14
+ exampleSchema,
15
+ gql`
16
+ query Hello {
17
+ human(id: "Han") {
18
+ id
19
+ }
20
+ }
21
+ `,
22
+ {splitTypes: true, schemaFilePath: ''},
23
+ '__generated__',
24
+ indexPrelude('yarn queries'),
25
+ );
26
+ expect(indexContents).toMatchInlineSnapshot(`
27
+ "// @flow
28
+ //
29
+ // AUTOGENERATED
30
+ // NOTE: New response types are added to this file automatically.
31
+ // Outdated response types can be removed manually as they are deprecated.
32
+ // To regenerate, run yarn queries
33
+ //
34
+
35
+ export type {HelloType} from './Hello.js';
36
+ "
37
+ `);
38
+ expect(
39
+ Object.keys(files)
40
+ .map((k) => `// ${k}\n${files[k]}`)
41
+ .join('\n\n'),
42
+ ).toMatchInlineSnapshot(`
43
+ "// __generated__/Hello.js
44
+ // @flow
45
+ // AUTOGENERATED -- DO NOT EDIT
46
+ // Generated for operation 'Hello' in file '../hello.js'
47
+ export type HelloType = {|
48
+ variables: {||},
49
+ response: {|
50
+ /** A human character*/
51
+ human: ?{|
52
+ id: string
53
+ |}
54
+ |}
55
+ |};
56
+ export type Hello = HelloType['response'];
57
+ export type HelloVariables = HelloType['variables'];
58
+ "
59
+ `);
60
+ });
61
+
62
+ it('should respect the typeFileName option', () => {
63
+ const {files} = generateTypeFileContents(
64
+ 'hello.js',
65
+ exampleSchema,
66
+ gql`
67
+ query Hello {
68
+ human(id: "Han") {
69
+ id
70
+ }
71
+ }
72
+ `,
73
+ {
74
+ splitTypes: true,
75
+ typeFileName: 'prefix-[operationName]-suffix.js',
76
+ schemaFilePath: '',
77
+ },
78
+ '__generated__',
79
+ indexPrelude('yarn queries'),
80
+ );
81
+ expect(
82
+ Object.keys(files)
83
+ .map((k) => `// ${k}\n${files[k]}`)
84
+ .join('\n\n'),
85
+ ).toMatchInlineSnapshot(`
86
+ "// __generated__/prefix-Hello-suffix.js
87
+ // @flow
88
+ // AUTOGENERATED -- DO NOT EDIT
89
+ // Generated for operation 'Hello' in file '../hello.js'
90
+ export type HelloType = {|
91
+ variables: {||},
92
+ response: {|
93
+ /** A human character*/
94
+ human: ?{|
95
+ id: string
96
+ |}
97
+ |}
98
+ |};
99
+ export type Hello = HelloType['response'];
100
+ export type HelloVariables = HelloType['variables'];
101
+ "
102
+ `);
103
+ });
104
+
105
+ describe('experimentalEnums', () => {
106
+ it('should generate the expected values', () => {
107
+ const {files} = generateTypeFileContents(
108
+ 'hello.js',
109
+ exampleSchema,
110
+ gql`
111
+ query Hello {
112
+ human(id: "Han") {
113
+ appearsIn
114
+ }
115
+ }
116
+ `,
117
+ {
118
+ experimentalEnums: true,
119
+ schemaFilePath: '',
120
+ },
121
+ '__generated__',
122
+ indexPrelude('yarn queries'),
123
+ );
124
+ expect(
125
+ Object.keys(files)
126
+ .map((k) => `// ${k}\n${files[k]}`)
127
+ .join('\n\n'),
128
+ ).toMatchInlineSnapshot(`
129
+ "// __generated__/Hello.js
130
+ // @flow
131
+ // AUTOGENERATED -- DO NOT EDIT
132
+ // Generated for operation 'Hello' in file '../hello.js'
133
+ export type HelloType = {|
134
+ variables: {||},
135
+ response: {|
136
+ /** A human character*/
137
+ human: ?{|
138
+ appearsIn: ?$ReadOnlyArray<
139
+ /** - NEW_HOPE
140
+ - EMPIRE
141
+ - JEDI*/
142
+ ?Episode>
143
+ |}
144
+ |}
145
+ |};
146
+ /* eslint-disable no-undef */
147
+ export enum Episode {
148
+ NEW_HOPE,
149
+ EMPIRE,
150
+ JEDI,
151
+ };
152
+ /* eslint-enable no-undef */
153
+ "
154
+ `);
155
+ });
156
+ });
157
+ });