relay-compiler 9.0.0 → 10.1.0

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