relay-compiler 7.0.0 → 9.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (242) hide show
  1. package/bin/RelayCompilerBin.js.flow +169 -0
  2. package/bin/RelayCompilerMain.js.flow +508 -0
  3. package/bin/__fixtures__/plugin-module.js.flow +17 -0
  4. package/bin/relay-compiler +8554 -8142
  5. package/codegen/CodegenDirectory.js.flow +375 -0
  6. package/codegen/CodegenRunner.js.flow +431 -0
  7. package/codegen/CodegenTypes.js.flow +28 -0
  8. package/codegen/CodegenWatcher.js.flow +254 -0
  9. package/codegen/NormalizationCodeGenerator.js.flow +499 -0
  10. package/codegen/ReaderCodeGenerator.js.flow +453 -0
  11. package/codegen/RelayCodeGenerator.js.flow +76 -0
  12. package/codegen/RelayFileWriter.js.flow +366 -0
  13. package/codegen/SourceControl.js.flow +58 -0
  14. package/codegen/compileRelayArtifacts.js.flow +182 -0
  15. package/codegen/createPrintRequireModuleDependency.js.flow +21 -0
  16. package/codegen/writeRelayGeneratedFile.js.flow +194 -0
  17. package/core/ASTCache.js.flow +73 -0
  18. package/core/ASTConvert.js.flow +233 -0
  19. package/core/CompilerContext.js.flow +190 -0
  20. package/core/CompilerError.js.flow +250 -0
  21. package/core/DotGraphQLParser.js.flow +39 -0
  22. package/core/GraphQLCompilerProfiler.js.flow +341 -0
  23. package/core/GraphQLDerivedFromMetadata.js.flow +48 -0
  24. package/core/GraphQLWatchmanClient.js.flow +111 -0
  25. package/core/IR.js.flow +329 -0
  26. package/core/IRPrinter.js.flow +488 -0
  27. package/core/IRTransformer.js.flow +377 -0
  28. package/core/IRValidator.js.flow +260 -0
  29. package/core/IRVisitor.js.flow +150 -0
  30. package/core/JSModuleParser.js.flow +24 -0
  31. package/core/RelayCompilerScope.js.flow +199 -0
  32. package/core/RelayFindGraphQLTags.js.flow +119 -0
  33. package/core/RelayGraphQLEnumsGenerator.js.flow +55 -0
  34. package/core/RelayIRTransforms.js.flow +130 -0
  35. package/core/RelayParser.js.flow +1759 -0
  36. package/core/RelaySourceModuleParser.js.flow +135 -0
  37. package/core/Schema.js.flow +1985 -0
  38. package/core/SchemaUtils.js.flow +109 -0
  39. package/core/filterContextForNode.js.flow +50 -0
  40. package/core/getFieldDefinition.js.flow +156 -0
  41. package/core/getIdentifierForArgumentValue.js.flow +49 -0
  42. package/core/getIdentifierForSelection.js.flow +69 -0
  43. package/core/getLiteralArgumentValues.js.flow +32 -0
  44. package/core/getNormalizationOperationName.js.flow +19 -0
  45. package/core/inferRootArgumentDefinitions.js.flow +323 -0
  46. package/index.js +1 -1
  47. package/index.js.flow +202 -0
  48. package/language/RelayLanguagePluginInterface.js.flow +283 -0
  49. package/language/javascript/FindGraphQLTags.js.flow +233 -0
  50. package/language/javascript/RelayFlowBabelFactories.js.flow +180 -0
  51. package/language/javascript/RelayFlowGenerator.js.flow +1040 -0
  52. package/language/javascript/RelayFlowTypeTransformers.js.flow +184 -0
  53. package/language/javascript/RelayLanguagePluginJavaScript.js.flow +34 -0
  54. package/language/javascript/formatGeneratedModule.js.flow +65 -0
  55. package/lib/bin/RelayCompilerBin.js +25 -7
  56. package/lib/bin/RelayCompilerMain.js +134 -125
  57. package/lib/bin/__fixtures__/plugin-module.js +1 -0
  58. package/lib/codegen/CodegenDirectory.js +14 -8
  59. package/lib/codegen/CodegenRunner.js +35 -75
  60. package/lib/codegen/CodegenTypes.js +1 -0
  61. package/lib/codegen/CodegenWatcher.js +14 -21
  62. package/lib/codegen/NormalizationCodeGenerator.js +80 -127
  63. package/lib/codegen/ReaderCodeGenerator.js +85 -111
  64. package/lib/codegen/RelayCodeGenerator.js +9 -6
  65. package/lib/codegen/RelayFileWriter.js +22 -41
  66. package/lib/codegen/SourceControl.js +1 -0
  67. package/lib/codegen/compileRelayArtifacts.js +18 -31
  68. package/lib/codegen/createPrintRequireModuleDependency.js +1 -0
  69. package/lib/codegen/writeRelayGeneratedFile.js +62 -90
  70. package/lib/core/ASTCache.js +4 -4
  71. package/lib/core/ASTConvert.js +1 -0
  72. package/lib/core/{GraphQLCompilerContext.js → CompilerContext.js} +10 -11
  73. package/lib/core/{RelayCompilerError.js → CompilerError.js} +29 -55
  74. package/lib/core/DotGraphQLParser.js +1 -0
  75. package/lib/core/GraphQLCompilerProfiler.js +9 -12
  76. package/lib/core/GraphQLDerivedFromMetadata.js +1 -0
  77. package/lib/core/GraphQLWatchmanClient.js +5 -12
  78. package/lib/core/{GraphQLIR.js → IR.js} +1 -0
  79. package/lib/core/{GraphQLIRPrinter.js → IRPrinter.js} +39 -17
  80. package/lib/core/{GraphQLIRTransformer.js → IRTransformer.js} +21 -16
  81. package/lib/core/{GraphQLIRValidator.js → IRValidator.js} +18 -10
  82. package/lib/core/{GraphQLIRVisitor.js → IRVisitor.js} +1 -2
  83. package/lib/core/JSModuleParser.js +18 -0
  84. package/lib/core/RelayCompilerScope.js +6 -5
  85. package/lib/core/RelayFindGraphQLTags.js +1 -0
  86. package/lib/core/RelayGraphQLEnumsGenerator.js +26 -12
  87. package/lib/core/RelayIRTransforms.js +12 -9
  88. package/lib/core/RelayParser.js +113 -75
  89. package/lib/core/RelaySourceModuleParser.js +4 -3
  90. package/lib/core/Schema.js +808 -317
  91. package/lib/core/SchemaUtils.js +1 -0
  92. package/lib/core/filterContextForNode.js +5 -4
  93. package/lib/core/getFieldDefinition.js +14 -16
  94. package/lib/core/getIdentifierForArgumentValue.js +18 -0
  95. package/lib/core/getIdentifierForSelection.js +4 -5
  96. package/lib/core/getLiteralArgumentValues.js +1 -0
  97. package/lib/core/getNormalizationOperationName.js +1 -0
  98. package/lib/core/inferRootArgumentDefinitions.js +79 -99
  99. package/lib/index.js +69 -19
  100. package/lib/language/RelayLanguagePluginInterface.js +1 -0
  101. package/lib/language/javascript/FindGraphQLTags.js +1 -0
  102. package/lib/language/javascript/RelayFlowBabelFactories.js +15 -0
  103. package/lib/language/javascript/RelayFlowGenerator.js +94 -173
  104. package/lib/language/javascript/RelayFlowTypeTransformers.js +2 -3
  105. package/lib/language/javascript/RelayLanguagePluginJavaScript.js +7 -4
  106. package/lib/language/javascript/formatGeneratedModule.js +14 -4
  107. package/lib/reporters/ConsoleReporter.js +2 -3
  108. package/lib/reporters/MultiReporter.js +2 -3
  109. package/lib/reporters/Reporter.js +1 -0
  110. package/lib/runner/Artifacts.js +327 -0
  111. package/lib/runner/BufferedFilesystem.js +265 -0
  112. package/lib/runner/GraphQLASTNodeGroup.js +260 -0
  113. package/lib/runner/GraphQLASTUtils.js +23 -0
  114. package/lib/runner/GraphQLNodeMap.js +85 -0
  115. package/lib/runner/Sources.js +266 -0
  116. package/lib/runner/StrictMap.js +136 -0
  117. package/lib/runner/compileArtifacts.js +39 -0
  118. package/lib/runner/extractAST.js +77 -0
  119. package/lib/runner/getChangedNodeNames.js +84 -0
  120. package/lib/runner/getSchemaInstance.js +30 -0
  121. package/lib/runner/types.js +12 -0
  122. package/lib/transforms/ApplyFragmentArgumentTransform.js +49 -55
  123. package/lib/transforms/ClientExtensionsTransform.js +11 -17
  124. package/lib/transforms/ConnectionTransform.js +35 -28
  125. package/lib/transforms/DeferStreamTransform.js +26 -74
  126. package/lib/transforms/DisallowIdAsAlias.js +5 -4
  127. package/lib/transforms/DisallowTypenameOnRoot.js +55 -0
  128. package/lib/transforms/FieldHandleTransform.js +8 -3
  129. package/lib/transforms/FilterDirectivesTransform.js +4 -3
  130. package/lib/transforms/FlattenTransform.js +23 -47
  131. package/lib/transforms/GenerateIDFieldTransform.js +9 -4
  132. package/lib/transforms/GenerateTypeNameTransform.js +8 -3
  133. package/lib/transforms/InlineDataFragmentTransform.js +11 -6
  134. package/lib/transforms/InlineFragmentsTransform.js +3 -2
  135. package/lib/transforms/MaskTransform.js +20 -19
  136. package/lib/transforms/MatchTransform.js +113 -34
  137. package/lib/transforms/RefetchableFragmentTransform.js +25 -41
  138. package/lib/transforms/RelayDirectiveTransform.js +13 -4
  139. package/lib/transforms/SkipClientExtensionsTransform.js +11 -2
  140. package/lib/transforms/SkipHandleFieldTransform.js +8 -3
  141. package/lib/transforms/SkipRedundantNodesTransform.js +9 -6
  142. package/lib/transforms/SkipSplitOperationTransform.js +32 -0
  143. package/lib/transforms/SkipUnreachableNodeTransform.js +12 -12
  144. package/lib/transforms/SkipUnusedVariablesTransform.js +19 -17
  145. package/lib/transforms/SplitModuleImportTransform.js +4 -3
  146. package/lib/transforms/TestOperationTransform.js +9 -6
  147. package/lib/transforms/TransformUtils.js +1 -0
  148. package/lib/transforms/ValidateGlobalVariablesTransform.js +20 -31
  149. package/lib/transforms/ValidateRequiredArgumentsTransform.js +17 -20
  150. package/lib/transforms/ValidateServerOnlyDirectivesTransform.js +20 -33
  151. package/lib/transforms/ValidateUnusedVariablesTransform.js +20 -31
  152. package/lib/transforms/query-generators/FetchableQueryGenerator.js +161 -0
  153. package/lib/transforms/query-generators/NodeQueryGenerator.js +9 -3
  154. package/lib/transforms/query-generators/QueryQueryGenerator.js +2 -0
  155. package/lib/transforms/query-generators/ViewerQueryGenerator.js +6 -3
  156. package/lib/transforms/query-generators/index.js +25 -7
  157. package/lib/transforms/query-generators/utils.js +13 -15
  158. package/lib/util/CodeMarker.js +1 -0
  159. package/lib/util/DefaultHandleKey.js +1 -0
  160. package/lib/util/RelayCompilerCache.js +2 -3
  161. package/lib/util/Rollout.js +1 -0
  162. package/lib/util/TimeReporter.js +83 -0
  163. package/lib/util/areEqualOSS.js +1 -0
  164. package/lib/util/dedupeJSONStringify.js +16 -12
  165. package/lib/util/getDefinitionNodeHash.js +22 -0
  166. package/lib/util/getModuleName.js +4 -5
  167. package/lib/util/joinArgumentDefinitions.js +2 -1
  168. package/lib/util/md5.js +17 -0
  169. package/lib/util/murmurHash.js +1 -0
  170. package/lib/util/nullthrowsOSS.js +1 -0
  171. package/lib/util/orList.js +2 -1
  172. package/lib/util/partitionArray.js +1 -0
  173. package/package.json +4 -4
  174. package/relay-compiler.js +4 -4
  175. package/relay-compiler.min.js +4 -4
  176. package/reporters/ConsoleReporter.js.flow +81 -0
  177. package/reporters/MultiReporter.js.flow +43 -0
  178. package/reporters/Reporter.js.flow +19 -0
  179. package/runner/Artifacts.js.flow +219 -0
  180. package/runner/BufferedFilesystem.js.flow +194 -0
  181. package/runner/GraphQLASTNodeGroup.js.flow +176 -0
  182. package/runner/GraphQLASTUtils.js.flow +26 -0
  183. package/runner/GraphQLNodeMap.js.flow +55 -0
  184. package/runner/Sources.js.flow +218 -0
  185. package/runner/StrictMap.js.flow +96 -0
  186. package/runner/compileArtifacts.js.flow +76 -0
  187. package/runner/extractAST.js.flow +100 -0
  188. package/runner/getChangedNodeNames.js.flow +48 -0
  189. package/runner/getSchemaInstance.js.flow +36 -0
  190. package/runner/types.js.flow +37 -0
  191. package/transforms/ApplyFragmentArgumentTransform.js.flow +474 -0
  192. package/transforms/ClientExtensionsTransform.js.flow +220 -0
  193. package/transforms/ConnectionTransform.js.flow +869 -0
  194. package/transforms/DeferStreamTransform.js.flow +258 -0
  195. package/transforms/DisallowIdAsAlias.js.flow +47 -0
  196. package/transforms/DisallowTypenameOnRoot.js.flow +45 -0
  197. package/transforms/FieldHandleTransform.js.flow +80 -0
  198. package/transforms/FilterDirectivesTransform.js.flow +45 -0
  199. package/transforms/FlattenTransform.js.flow +456 -0
  200. package/transforms/GenerateIDFieldTransform.js.flow +134 -0
  201. package/transforms/GenerateTypeNameTransform.js.flow +81 -0
  202. package/transforms/InlineDataFragmentTransform.js.flow +124 -0
  203. package/transforms/InlineFragmentsTransform.js.flow +71 -0
  204. package/transforms/MaskTransform.js.flow +126 -0
  205. package/transforms/MatchTransform.js.flow +583 -0
  206. package/transforms/RefetchableFragmentTransform.js.flow +272 -0
  207. package/transforms/RelayDirectiveTransform.js.flow +99 -0
  208. package/transforms/SkipClientExtensionsTransform.js.flow +54 -0
  209. package/transforms/SkipHandleFieldTransform.js.flow +44 -0
  210. package/transforms/SkipRedundantNodesTransform.js.flow +253 -0
  211. package/transforms/SkipSplitOperationTransform.js.flow +37 -0
  212. package/transforms/SkipUnreachableNodeTransform.js.flow +149 -0
  213. package/transforms/SkipUnusedVariablesTransform.js.flow +59 -0
  214. package/transforms/SplitModuleImportTransform.js.flow +98 -0
  215. package/transforms/TestOperationTransform.js.flow +138 -0
  216. package/transforms/TransformUtils.js.flow +26 -0
  217. package/transforms/ValidateGlobalVariablesTransform.js.flow +81 -0
  218. package/transforms/ValidateRequiredArgumentsTransform.js.flow +127 -0
  219. package/transforms/ValidateServerOnlyDirectivesTransform.js.flow +112 -0
  220. package/transforms/ValidateUnusedVariablesTransform.js.flow +89 -0
  221. package/transforms/query-generators/FetchableQueryGenerator.js.flow +190 -0
  222. package/transforms/query-generators/NodeQueryGenerator.js.flow +206 -0
  223. package/transforms/query-generators/QueryQueryGenerator.js.flow +57 -0
  224. package/transforms/query-generators/ViewerQueryGenerator.js.flow +97 -0
  225. package/transforms/query-generators/index.js.flow +90 -0
  226. package/transforms/query-generators/utils.js.flow +72 -0
  227. package/util/CodeMarker.js.flow +79 -0
  228. package/util/DefaultHandleKey.js.flow +17 -0
  229. package/util/RelayCompilerCache.js.flow +88 -0
  230. package/util/Rollout.js.flow +39 -0
  231. package/util/TimeReporter.js.flow +79 -0
  232. package/util/areEqualOSS.js.flow +123 -0
  233. package/util/dedupeJSONStringify.js.flow +152 -0
  234. package/util/getDefinitionNodeHash.js.flow +25 -0
  235. package/util/getModuleName.js.flow +39 -0
  236. package/util/joinArgumentDefinitions.js.flow +99 -0
  237. package/util/md5.js.flow +22 -0
  238. package/util/murmurHash.js.flow +94 -0
  239. package/util/nullthrowsOSS.js.flow +25 -0
  240. package/util/orList.js.flow +37 -0
  241. package/util/partitionArray.js.flow +37 -0
  242. package/lib/transforms/ConnectionFieldTransform.js +0 -275
@@ -0,0 +1,431 @@
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow strict-local
8
+ * @format
9
+ */
10
+
11
+ // flowlint ambiguous-object-type:error
12
+
13
+ 'use strict';
14
+
15
+ const CodegenDirectory = require('./CodegenDirectory');
16
+ const CodegenWatcher = require('./CodegenWatcher');
17
+ const GraphQLWatchmanClient = require('../core/GraphQLWatchmanClient');
18
+ const Profiler = require('../core/GraphQLCompilerProfiler');
19
+
20
+ const invariant = require('invariant');
21
+ const path = require('path');
22
+
23
+ const {create: createSchema} = require('../core/Schema');
24
+ // $FlowFixMe - importing immutable, which is untyped (and flow is sad about it)
25
+ const {Map: ImmutableMap} = require('immutable');
26
+
27
+ import type ASTCache from '../core/ASTCache';
28
+ import type {Schema} from '../core/Schema';
29
+ import type {Reporter} from '../reporters/Reporter';
30
+ import type {CompileResult, File} from './CodegenTypes';
31
+ import type {FileFilter, WatchmanExpression} from './CodegenWatcher';
32
+ import type {SourceControl} from './SourceControl';
33
+ import type {DocumentNode, Source} from 'graphql';
34
+
35
+ export type ParserConfig = {|
36
+ baseDir: string,
37
+ getFileFilter?: (baseDir: string) => FileFilter,
38
+ getParser: (baseDir: string) => ASTCache,
39
+ getSchemaSource: () => Source,
40
+ schemaExtensions: $ReadOnlyArray<string>,
41
+ generatedDirectoriesWatchmanExpression?: ?WatchmanExpression,
42
+ watchmanExpression?: ?WatchmanExpression,
43
+ filepaths?: ?Array<string>,
44
+ |};
45
+
46
+ type ParserConfigs = {[parser: string]: ParserConfig, ...};
47
+ type Parsers = {[parser: string]: ASTCache, ...};
48
+
49
+ export type IsGeneratedFileFn = (filePath: string) => boolean;
50
+ export type KeepExtraFileFn = (filePath: string) => boolean;
51
+
52
+ export type WriterConfig = {|
53
+ parser: string,
54
+ baseParsers?: Array<string>,
55
+ isGeneratedFile: IsGeneratedFileFn,
56
+ writeFiles: WriteFiles,
57
+ |};
58
+
59
+ type WriterConfigs = {[writer: string]: WriterConfig, ...};
60
+
61
+ export type WriteFilesOptions = {|
62
+ onlyValidate: boolean,
63
+ schema: Schema,
64
+ documents: ImmutableMap<string, DocumentNode>,
65
+ baseDocuments: ImmutableMap<string, DocumentNode>,
66
+ sourceControl: ?SourceControl,
67
+ reporter: Reporter,
68
+ generatedDirectories?: Array<string>,
69
+ |};
70
+
71
+ export type WriteFiles = WriteFilesOptions => Promise<
72
+ Map<string, CodegenDirectory>,
73
+ >;
74
+
75
+ type OnCompleteCallback = (
76
+ codegenDirs: $ReadOnlyArray<CodegenDirectory>,
77
+ ) => void;
78
+
79
+ class CodegenRunner {
80
+ parserConfigs: ParserConfigs;
81
+ writerConfigs: WriterConfigs;
82
+ onlyValidate: boolean;
83
+ parsers: Parsers;
84
+ onComplete: ?OnCompleteCallback;
85
+
86
+ // parser => writers that are affected by it
87
+ parserWriters: {[parser: string]: Set<string>, ...};
88
+ _reporter: Reporter;
89
+ _sourceControl: ?SourceControl;
90
+
91
+ constructor(options: {
92
+ parserConfigs: ParserConfigs,
93
+ writerConfigs: WriterConfigs,
94
+ onlyValidate: boolean,
95
+ reporter: Reporter,
96
+ sourceControl: ?SourceControl,
97
+ onComplete?: OnCompleteCallback,
98
+ ...
99
+ }) {
100
+ this.parsers = {};
101
+ this.parserConfigs = options.parserConfigs;
102
+ this.writerConfigs = options.writerConfigs;
103
+ this.onlyValidate = options.onlyValidate;
104
+ this.onComplete = options.onComplete;
105
+ this._reporter = options.reporter;
106
+ this._sourceControl = options.sourceControl;
107
+
108
+ this.parserWriters = {};
109
+ for (const parser in options.parserConfigs) {
110
+ this.parserWriters[parser] = new Set();
111
+ }
112
+
113
+ for (const writer in options.writerConfigs) {
114
+ const config = options.writerConfigs[writer];
115
+ config.baseParsers &&
116
+ config.baseParsers.forEach(parser =>
117
+ this.parserWriters[parser].add(writer),
118
+ );
119
+ this.parserWriters[config.parser].add(writer);
120
+ }
121
+ }
122
+
123
+ async compileAll(): Promise<CompileResult> {
124
+ // reset the parsers
125
+ this.parsers = {};
126
+ for (const parserName in this.parserConfigs) {
127
+ try {
128
+ await this.parseEverything(parserName);
129
+ } catch (e) {
130
+ this._reporter.reportError('CodegenRunner.compileAll', e);
131
+ return 'ERROR';
132
+ }
133
+ }
134
+
135
+ let hasChanges = false;
136
+ for (const writerName in this.writerConfigs) {
137
+ const result = await this.write(writerName);
138
+ if (result === 'ERROR') {
139
+ return 'ERROR';
140
+ }
141
+ if (result === 'HAS_CHANGES') {
142
+ hasChanges = true;
143
+ }
144
+ }
145
+ return hasChanges ? 'HAS_CHANGES' : 'NO_CHANGES';
146
+ }
147
+
148
+ async compile(writerName: string): Promise<CompileResult> {
149
+ const writerConfig = this.writerConfigs[writerName];
150
+
151
+ const parsers = [writerConfig.parser];
152
+ if (writerConfig.baseParsers) {
153
+ writerConfig.baseParsers.forEach(parser => parsers.push(parser));
154
+ }
155
+ // Don't bother resetting the parsers
156
+ await Profiler.asyncContext('CodegenRunner:parseEverything', () =>
157
+ Promise.all(parsers.map(parser => this.parseEverything(parser))),
158
+ );
159
+
160
+ return await this.write(writerName);
161
+ }
162
+
163
+ getDirtyWriters(filePaths: Array<string>): Promise<Set<string>> {
164
+ return Profiler.asyncContext('CodegenRunner:getDirtyWriters', async () => {
165
+ const dirtyWriters = new Set();
166
+
167
+ // Check if any files are in the output
168
+ for (const configName in this.writerConfigs) {
169
+ const config = this.writerConfigs[configName];
170
+ for (const filePath of filePaths) {
171
+ if (config.isGeneratedFile(filePath)) {
172
+ dirtyWriters.add(configName);
173
+ }
174
+ }
175
+ }
176
+
177
+ // Check for files in the input
178
+ await Promise.all(
179
+ Object.keys(this.parserConfigs).map(parserConfigName =>
180
+ Profiler.waitFor('Watchman:query', async () => {
181
+ const client = new GraphQLWatchmanClient();
182
+ const config = this.parserConfigs[parserConfigName];
183
+ const dirs = await client.watchProject(config.baseDir);
184
+
185
+ const relativeFilePaths = filePaths.map(filePath =>
186
+ path.relative(config.baseDir, filePath),
187
+ );
188
+
189
+ const query = {
190
+ expression: [
191
+ 'allof',
192
+ config.watchmanExpression,
193
+ ['name', relativeFilePaths, 'wholename'],
194
+ ],
195
+ fields: ['exists'],
196
+ relative_root: dirs.relativePath,
197
+ };
198
+
199
+ const result = await client.command('query', dirs.root, query);
200
+ client.end();
201
+
202
+ if (result.files.length > 0) {
203
+ this.parserWriters[parserConfigName].forEach(writerName =>
204
+ dirtyWriters.add(writerName),
205
+ );
206
+ }
207
+ }),
208
+ ),
209
+ );
210
+
211
+ return dirtyWriters;
212
+ });
213
+ }
214
+
215
+ async parseEverything(parserName: string): Promise<void> {
216
+ if (this.parsers[parserName]) {
217
+ // no need to parse
218
+ return;
219
+ }
220
+
221
+ const parserConfig = this.parserConfigs[parserName];
222
+ this.parsers[parserName] = parserConfig.getParser(parserConfig.baseDir);
223
+ const filter = parserConfig.getFileFilter
224
+ ? parserConfig.getFileFilter(parserConfig.baseDir)
225
+ : anyFileFilter;
226
+
227
+ if (parserConfig.filepaths && parserConfig.watchmanExpression) {
228
+ throw new Error(
229
+ 'Provide either `watchmanExpression` or `filepaths` but not both.',
230
+ );
231
+ }
232
+
233
+ let files;
234
+ if (parserConfig.watchmanExpression) {
235
+ files = await CodegenWatcher.queryFiles(
236
+ parserConfig.baseDir,
237
+ parserConfig.watchmanExpression,
238
+ filter,
239
+ );
240
+ } else if (parserConfig.filepaths) {
241
+ files = await CodegenWatcher.queryFilepaths(
242
+ parserConfig.baseDir,
243
+ parserConfig.filepaths,
244
+ filter,
245
+ );
246
+ } else {
247
+ throw new Error(
248
+ 'Either `watchmanExpression` or `filepaths` is required to query files',
249
+ );
250
+ }
251
+ this.parseFileChanges(parserName, files);
252
+ }
253
+
254
+ parseFileChanges(parserName: string, files: Set<File>): void {
255
+ return Profiler.run('CodegenRunner.parseFileChanges', () => {
256
+ const parser = this.parsers[parserName];
257
+ // this maybe should be await parser.parseFiles(files);
258
+ parser.parseFiles(files);
259
+ });
260
+ }
261
+
262
+ // We cannot do incremental writes right now.
263
+ // When we can, this could be writeChanges(writerName, parserName, parsedDefinitions)
264
+ write(writerName: string): Promise<CompileResult> {
265
+ return Profiler.asyncContext('CodegenRunner.write', async () => {
266
+ try {
267
+ this._reporter.reportMessage(`\nWriting ${writerName}`);
268
+ const {
269
+ writeFiles,
270
+ parser,
271
+ baseParsers,
272
+ isGeneratedFile,
273
+ } = this.writerConfigs[writerName];
274
+
275
+ let baseDocuments = ImmutableMap();
276
+ if (baseParsers) {
277
+ baseParsers.forEach(baseParserName => {
278
+ invariant(
279
+ this.parsers[baseParserName] != null,
280
+ 'Trying to access an uncompiled base parser config: %s',
281
+ baseParserName,
282
+ );
283
+ baseDocuments = baseDocuments.merge(
284
+ this.parsers[baseParserName].documents(),
285
+ );
286
+ });
287
+ }
288
+
289
+ const {
290
+ baseDir,
291
+ generatedDirectoriesWatchmanExpression,
292
+ } = this.parserConfigs[parser];
293
+ let generatedDirectories = [];
294
+ if (generatedDirectoriesWatchmanExpression) {
295
+ const relativePaths = await CodegenWatcher.queryDirectories(
296
+ baseDir,
297
+ generatedDirectoriesWatchmanExpression,
298
+ );
299
+ generatedDirectories = relativePaths.map(x => path.join(baseDir, x));
300
+ }
301
+
302
+ // always create a new writer: we have to write everything anyways
303
+ const documents = this.parsers[parser].documents();
304
+ const schema = Profiler.run('getSchema', () =>
305
+ createSchema(
306
+ this.parserConfigs[parser].getSchemaSource(),
307
+ baseDocuments.toArray(),
308
+ this.parserConfigs[parser].schemaExtensions,
309
+ ),
310
+ );
311
+
312
+ const outputDirectories = await writeFiles({
313
+ onlyValidate: this.onlyValidate,
314
+ schema,
315
+ documents,
316
+ baseDocuments,
317
+ generatedDirectories,
318
+ sourceControl: this._sourceControl,
319
+ reporter: this._reporter,
320
+ });
321
+
322
+ for (const dir of outputDirectories.values()) {
323
+ const all = [
324
+ ...dir.changes.created,
325
+ ...dir.changes.updated,
326
+ ...dir.changes.deleted,
327
+ ...dir.changes.unchanged,
328
+ ];
329
+ for (const filename of all) {
330
+ const filePath = dir.getPath(filename);
331
+ invariant(
332
+ isGeneratedFile(filePath),
333
+ 'CodegenRunner: %s returned false for isGeneratedFile, ' +
334
+ 'but was in generated directory',
335
+ filePath,
336
+ );
337
+ }
338
+ }
339
+
340
+ const onCompleteCallback = this.onComplete;
341
+ if (onCompleteCallback != null) {
342
+ onCompleteCallback(Array.from(outputDirectories.values()));
343
+ }
344
+
345
+ const combinedChanges = CodegenDirectory.combineChanges(
346
+ Array.from(outputDirectories.values()),
347
+ );
348
+
349
+ this._reporter.reportMessage(
350
+ CodegenDirectory.formatChanges(combinedChanges, {
351
+ onlyValidate: this.onlyValidate,
352
+ }),
353
+ );
354
+
355
+ return CodegenDirectory.hasChanges(combinedChanges)
356
+ ? 'HAS_CHANGES'
357
+ : 'NO_CHANGES';
358
+ } catch (e) {
359
+ this._reporter.reportError('CodegenRunner.write', e);
360
+ return 'ERROR';
361
+ }
362
+ });
363
+ }
364
+
365
+ async watchAll(): Promise<void> {
366
+ // get everything set up for watching
367
+ await this.compileAll();
368
+
369
+ for (const parserName in this.parserConfigs) {
370
+ await this.watch(parserName);
371
+ }
372
+ }
373
+
374
+ async watch(parserName: string): Promise<void> {
375
+ const parserConfig = this.parserConfigs[parserName];
376
+
377
+ if (!parserConfig.watchmanExpression) {
378
+ throw new Error('`watchmanExpression` is required to watch files');
379
+ }
380
+
381
+ // watchCompile starts with a full set of files as the changes
382
+ // But as we need to set everything up due to potential parser dependencies,
383
+ // we should prevent the first watch callback from doing anything.
384
+ let firstChange = true;
385
+
386
+ await CodegenWatcher.watchCompile(
387
+ parserConfig.baseDir,
388
+ parserConfig.watchmanExpression,
389
+ parserConfig.getFileFilter
390
+ ? parserConfig.getFileFilter(parserConfig.baseDir)
391
+ : anyFileFilter,
392
+ async files => {
393
+ invariant(
394
+ this.parsers[parserName] != null,
395
+ 'Trying to watch an uncompiled parser config: %s',
396
+ parserName,
397
+ );
398
+ if (firstChange) {
399
+ firstChange = false;
400
+ return;
401
+ }
402
+ const dependentWriters = [];
403
+ this.parserWriters[parserName].forEach(writer =>
404
+ dependentWriters.push(writer),
405
+ );
406
+
407
+ try {
408
+ if (!this.parsers[parserName]) {
409
+ // have to load the parser and make sure all of its dependents are set
410
+ await this.parseEverything(parserName);
411
+ } else {
412
+ this.parseFileChanges(parserName, files);
413
+ }
414
+ await Promise.all(dependentWriters.map(writer => this.write(writer)));
415
+ } catch (error) {
416
+ this._reporter.reportError('CodegenRunner.watch', error);
417
+ }
418
+ this._reporter.reportMessage(
419
+ `Watching for changes to ${parserName}...`,
420
+ );
421
+ },
422
+ );
423
+ this._reporter.reportMessage(`Watching for changes to ${parserName}...`);
424
+ }
425
+ }
426
+
427
+ function anyFileFilter(file: File): boolean {
428
+ return true;
429
+ }
430
+
431
+ module.exports = CodegenRunner;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow strict
8
+ * @format
9
+ */
10
+
11
+ // flowlint ambiguous-object-type:error
12
+
13
+ 'use strict';
14
+
15
+ export type CompileResult = 'HAS_CHANGES' | 'NO_CHANGES' | 'ERROR';
16
+
17
+ export type File =
18
+ | {
19
+ exists: false,
20
+ relPath: string,
21
+ ...
22
+ }
23
+ | {
24
+ exists: true,
25
+ relPath: string,
26
+ hash: string,
27
+ ...
28
+ };
@@ -0,0 +1,254 @@
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ * @format
9
+ */
10
+
11
+ // flowlint ambiguous-object-type:error
12
+
13
+ 'use strict';
14
+
15
+ const GraphQLWatchmanClient = require('../core/GraphQLWatchmanClient');
16
+ const Profiler = require('../core/GraphQLCompilerProfiler');
17
+
18
+ const crypto = require('crypto');
19
+ const fs = require('fs');
20
+ const path = require('path');
21
+
22
+ import type {File} from './CodegenTypes';
23
+
24
+ const SUBSCRIPTION_NAME = 'graphql-codegen';
25
+ const QUERY_RETRIES = 3;
26
+
27
+ export type WatchmanExpression = $ReadOnlyArray<string | WatchmanExpression>;
28
+
29
+ export type FileFilter = (file: File) => boolean;
30
+
31
+ type WatchmanChange = {|
32
+ name: string,
33
+ exists: boolean,
34
+ 'content.sha1hex': ?string,
35
+ |};
36
+ type WatchmanChanges = {
37
+ files?: $ReadOnlyArray<WatchmanChange>,
38
+ ...
39
+ };
40
+
41
+ async function queryFiles(
42
+ baseDir: string,
43
+ expression: WatchmanExpression,
44
+ filter: FileFilter,
45
+ ): Promise<Set<File>> {
46
+ return await Profiler.waitFor('Watchman:query', async () => {
47
+ const client = new GraphQLWatchmanClient(QUERY_RETRIES);
48
+ const [watchResp, fields] = await Promise.all([
49
+ client.watchProject(baseDir),
50
+ getFields(client),
51
+ ]);
52
+ const resp = await client.command('query', watchResp.root, {
53
+ expression,
54
+ fields: fields,
55
+ relative_root: watchResp.relativePath,
56
+ });
57
+ client.end();
58
+ return updateFiles(new Set(), baseDir, filter, resp.files);
59
+ });
60
+ }
61
+
62
+ async function queryDirectories(
63
+ baseDir: string,
64
+ expression: WatchmanExpression,
65
+ ): Promise<$ReadOnlyArray<string>> {
66
+ return await Profiler.waitFor('Watchman:query', async () => {
67
+ const client = new GraphQLWatchmanClient();
68
+ const watchResp = await client.watchProject(baseDir);
69
+ const resp = await client.command('query', watchResp.root, {
70
+ expression,
71
+ fields: ['name'],
72
+ relative_root: watchResp.relativePath,
73
+ });
74
+ client.end();
75
+ return resp.files;
76
+ });
77
+ }
78
+
79
+ async function getFields(
80
+ client: GraphQLWatchmanClient,
81
+ ): Promise<$ReadOnlyArray<string>> {
82
+ const fields = ['name', 'exists'];
83
+ if (await client.hasCapability('field-content.sha1hex')) {
84
+ fields.push('content.sha1hex');
85
+ }
86
+ return fields;
87
+ }
88
+
89
+ // For use when not using Watchman.
90
+ async function queryFilepaths(
91
+ baseDir: string,
92
+ filepaths: $ReadOnlyArray<string>,
93
+ filter: FileFilter,
94
+ ): Promise<Set<File>> {
95
+ // Construct WatchmanChange objects as an intermediate step before
96
+ // calling updateFiles to produce file content.
97
+ const files = filepaths.map(filepath => ({
98
+ name: filepath,
99
+ exists: true,
100
+ 'content.sha1hex': null,
101
+ }));
102
+ return updateFiles(new Set(), baseDir, filter, files);
103
+ }
104
+
105
+ /**
106
+ * Provides a simplified API to the watchman API.
107
+ * Given some base directory and a list of subdirectories it calls the callback
108
+ * with watchman change events on file changes.
109
+ */
110
+ async function watch(
111
+ baseDir: string,
112
+ expression: WatchmanExpression,
113
+ callback: (changes: WatchmanChanges) => mixed,
114
+ ): Promise<void> {
115
+ return await Profiler.waitFor('Watchman:subscribe', async () => {
116
+ const client = new GraphQLWatchmanClient();
117
+ const watchResp = await client.watchProject(baseDir);
118
+
119
+ await makeSubscription(
120
+ client,
121
+ watchResp.root,
122
+ watchResp.relativePath,
123
+ expression,
124
+ callback,
125
+ );
126
+ });
127
+ }
128
+
129
+ async function makeSubscription(
130
+ client: GraphQLWatchmanClient,
131
+ root: string,
132
+ relativePath: string,
133
+ expression: WatchmanExpression,
134
+ callback,
135
+ ): Promise<void> {
136
+ client.on('subscription', resp => {
137
+ if (resp.subscription === SUBSCRIPTION_NAME) {
138
+ callback(resp);
139
+ }
140
+ });
141
+ const fields = await getFields(client);
142
+ await client.command('subscribe', root, SUBSCRIPTION_NAME, {
143
+ expression,
144
+ fields: fields,
145
+ relative_root: relativePath,
146
+ });
147
+ }
148
+
149
+ /**
150
+ * Further simplifies `watch` and calls the callback on every change with a
151
+ * full list of files that match the conditions.
152
+ */
153
+ async function watchFiles(
154
+ baseDir: string,
155
+ expression: WatchmanExpression,
156
+ filter: FileFilter,
157
+ callback: (files: Set<File>) => any,
158
+ ): Promise<void> {
159
+ let files = new Set();
160
+ await watch(baseDir, expression, changes => {
161
+ if (!changes.files) {
162
+ // Watchmen fires a change without files when a watchman state changes,
163
+ // for example during an hg update.
164
+ return;
165
+ }
166
+ files = updateFiles(files, baseDir, filter, changes.files);
167
+ callback(files);
168
+ });
169
+ }
170
+
171
+ /**
172
+ * Similar to watchFiles, but takes an async function. The `compile` function
173
+ * is awaited and not called in parallel. If multiple changes are triggered
174
+ * before a compile finishes, the latest version is called after the compile
175
+ * finished.
176
+ *
177
+ * TODO: Consider changing from a Promise to abortable, so we can abort mid
178
+ * compilation.
179
+ */
180
+ async function watchCompile(
181
+ baseDir: string,
182
+ expression: WatchmanExpression,
183
+ filter: FileFilter,
184
+ compile: (files: Set<File>) => Promise<any>,
185
+ ): Promise<void> {
186
+ let compiling = false;
187
+ let needsCompiling = false;
188
+ let latestFiles = null;
189
+
190
+ watchFiles(baseDir, expression, filter, async files => {
191
+ needsCompiling = true;
192
+ latestFiles = files;
193
+ if (compiling) {
194
+ return;
195
+ }
196
+ compiling = true;
197
+ while (needsCompiling) {
198
+ needsCompiling = false;
199
+ await compile(latestFiles);
200
+ }
201
+ compiling = false;
202
+ });
203
+ }
204
+
205
+ function updateFiles(
206
+ files: Set<File>,
207
+ baseDir: string,
208
+ filter: FileFilter,
209
+ fileChanges: $ReadOnlyArray<WatchmanChange>,
210
+ ): Set<File> {
211
+ const fileMap = new Map();
212
+ files.forEach(file => {
213
+ file.exists && fileMap.set(file.relPath, file);
214
+ });
215
+
216
+ fileChanges.forEach(({name, exists, 'content.sha1hex': hash}) => {
217
+ let shouldRemove = !exists;
218
+ if (!shouldRemove) {
219
+ const file = {
220
+ exists: true,
221
+ relPath: name,
222
+ hash: hash || hashFile(path.join(baseDir, name)),
223
+ };
224
+ if (filter(file)) {
225
+ fileMap.set(name, file);
226
+ } else {
227
+ shouldRemove = true;
228
+ }
229
+ }
230
+ shouldRemove &&
231
+ fileMap.set(name, {
232
+ exists: false,
233
+ relPath: name,
234
+ });
235
+ });
236
+ return new Set(fileMap.values());
237
+ }
238
+
239
+ function hashFile(filename: string): string {
240
+ const content = fs.readFileSync(filename);
241
+ return crypto
242
+ .createHash('sha1')
243
+ .update(content)
244
+ .digest('hex');
245
+ }
246
+
247
+ module.exports = {
248
+ queryDirectories,
249
+ queryFiles,
250
+ queryFilepaths,
251
+ watch,
252
+ watchFiles,
253
+ watchCompile,
254
+ };