@graphql-codegen/cli 2.7.0-alpha-05066c85f.0 → 2.7.1-alpha-223b75f49.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.
- package/cjs/bin.js +13 -0
- package/cjs/cli.js +60 -0
- package/cjs/codegen.js +319 -0
- package/cjs/config.js +303 -0
- package/cjs/generate-and-save.js +110 -0
- package/cjs/graphql-config.js +69 -0
- package/cjs/hooks.js +87 -0
- package/cjs/index.js +13 -0
- package/cjs/init/helpers.js +64 -0
- package/cjs/init/index.js +60 -0
- package/cjs/init/plugins.js +163 -0
- package/cjs/init/questions.js +147 -0
- package/cjs/init/targets.js +44 -0
- package/cjs/init/types.js +14 -0
- package/cjs/load.js +93 -0
- package/cjs/package.json +1 -0
- package/cjs/plugins.js +45 -0
- package/cjs/presets.js +50 -0
- package/cjs/utils/cli-error.js +25 -0
- package/cjs/utils/debugging.js +27 -0
- package/cjs/utils/file-system.js +26 -0
- package/cjs/utils/logger.js +25 -0
- package/cjs/utils/watcher.js +140 -0
- package/esm/bin.js +11 -0
- package/esm/cli.js +32 -0
- package/esm/codegen.js +314 -0
- package/esm/config.js +290 -0
- package/esm/generate-and-save.js +105 -0
- package/esm/graphql-config.js +64 -0
- package/esm/hooks.js +83 -0
- package/esm/index.js +7 -0
- package/esm/init/helpers.js +55 -0
- package/esm/init/index.js +55 -0
- package/esm/init/plugins.js +160 -0
- package/esm/init/questions.js +140 -0
- package/esm/init/targets.js +40 -0
- package/esm/init/types.js +11 -0
- package/esm/load.js +88 -0
- package/esm/plugins.js +41 -0
- package/esm/presets.js +46 -0
- package/esm/utils/cli-error.js +20 -0
- package/esm/utils/debugging.js +21 -0
- package/esm/utils/file-system.js +19 -0
- package/esm/utils/logger.js +18 -0
- package/esm/utils/watcher.js +135 -0
- package/package.json +36 -32
- package/typings/bin.d.ts +2 -0
- package/{cli.d.ts → typings/cli.d.ts} +0 -0
- package/{codegen.d.ts → typings/codegen.d.ts} +1 -1
- package/{config.d.ts → typings/config.d.ts} +0 -0
- package/{generate-and-save.d.ts → typings/generate-and-save.d.ts} +1 -1
- package/{graphql-config.d.ts → typings/graphql-config.d.ts} +0 -0
- package/{hooks.d.ts → typings/hooks.d.ts} +0 -0
- package/typings/index.d.ts +7 -0
- package/{init → typings/init}/helpers.d.ts +1 -1
- package/{init → typings/init}/index.d.ts +0 -0
- package/{init → typings/init}/plugins.d.ts +1 -1
- package/{init → typings/init}/questions.d.ts +1 -1
- package/{init → typings/init}/targets.d.ts +1 -1
- package/{init → typings/init}/types.d.ts +0 -0
- package/{load.d.ts → typings/load.d.ts} +0 -0
- package/{plugins.d.ts → typings/plugins.d.ts} +0 -0
- package/{presets.d.ts → typings/presets.d.ts} +0 -0
- package/{utils → typings/utils}/cli-error.d.ts +0 -0
- package/{utils → typings/utils}/debugging.d.ts +0 -0
- package/{utils → typings/utils}/file-system.d.ts +0 -0
- package/{utils → typings/utils}/logger.d.ts +0 -0
- package/{utils → typings/utils}/watcher.d.ts +1 -1
- package/bin.d.ts +0 -1
- package/bin.js +0 -1888
- package/index.d.ts +0 -7
- package/index.js +0 -1899
- package/index.mjs +0 -1855
- package/utils/indentString.d.ts +0 -26
- package/utils/listr-renderer.d.ts +0 -18
- package/utils/try-to-build-schema.d.ts +0 -2
package/esm/config.js
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import { cosmiconfig, defaultLoaders } from 'cosmiconfig';
|
|
2
|
+
import { resolve } from 'path';
|
|
3
|
+
import { DetailedError, createProfiler, createNoopProfiler, getCachedDocumentNodeFromSchema, } from '@graphql-codegen/plugin-helpers';
|
|
4
|
+
import { env } from 'string-env-interpolation';
|
|
5
|
+
import yargs from 'yargs';
|
|
6
|
+
import { findAndLoadGraphQLConfig } from './graphql-config.js';
|
|
7
|
+
import { loadSchema, loadDocuments, defaultSchemaLoadOptions, defaultDocumentsLoadOptions } from './load.js';
|
|
8
|
+
import { print } from 'graphql';
|
|
9
|
+
import yaml from 'yaml';
|
|
10
|
+
import { createRequire } from 'module';
|
|
11
|
+
import { promises } from 'fs';
|
|
12
|
+
import { createHash } from 'crypto';
|
|
13
|
+
const { lstat } = promises;
|
|
14
|
+
export function generateSearchPlaces(moduleName) {
|
|
15
|
+
const extensions = ['json', 'yaml', 'yml', 'js', 'config.js'];
|
|
16
|
+
// gives codegen.json...
|
|
17
|
+
const regular = extensions.map(ext => `${moduleName}.${ext}`);
|
|
18
|
+
// gives .codegenrc.json... but no .codegenrc.config.js
|
|
19
|
+
const dot = extensions.filter(ext => ext !== 'config.js').map(ext => `.${moduleName}rc.${ext}`);
|
|
20
|
+
return [...regular.concat(dot), 'package.json'];
|
|
21
|
+
}
|
|
22
|
+
function customLoader(ext) {
|
|
23
|
+
function loader(filepath, content) {
|
|
24
|
+
if (typeof process !== 'undefined' && 'env' in process) {
|
|
25
|
+
content = env(content);
|
|
26
|
+
}
|
|
27
|
+
if (ext === 'json') {
|
|
28
|
+
return defaultLoaders['.json'](filepath, content);
|
|
29
|
+
}
|
|
30
|
+
if (ext === 'yaml') {
|
|
31
|
+
try {
|
|
32
|
+
const result = yaml.parse(content, { prettyErrors: true, merge: true });
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
error.message = `YAML Error in ${filepath}:\n${error.message}`;
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (ext === 'js') {
|
|
41
|
+
return defaultLoaders['.js'](filepath, content);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return loader;
|
|
45
|
+
}
|
|
46
|
+
export async function loadCodegenConfig({ configFilePath, moduleName, searchPlaces: additionalSearchPlaces, packageProp, loaders: customLoaders, }) {
|
|
47
|
+
configFilePath = configFilePath || process.cwd();
|
|
48
|
+
moduleName = moduleName || 'codegen';
|
|
49
|
+
packageProp = packageProp || moduleName;
|
|
50
|
+
const cosmi = cosmiconfig(moduleName, {
|
|
51
|
+
searchPlaces: generateSearchPlaces(moduleName).concat(additionalSearchPlaces || []),
|
|
52
|
+
packageProp,
|
|
53
|
+
loaders: {
|
|
54
|
+
'.json': customLoader('json'),
|
|
55
|
+
'.yaml': customLoader('yaml'),
|
|
56
|
+
'.yml': customLoader('yaml'),
|
|
57
|
+
'.js': customLoader('js'),
|
|
58
|
+
noExt: customLoader('yaml'),
|
|
59
|
+
...customLoaders,
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
const pathStats = await lstat(configFilePath);
|
|
63
|
+
return pathStats.isDirectory() ? cosmi.search(configFilePath) : cosmi.load(configFilePath);
|
|
64
|
+
}
|
|
65
|
+
export async function loadContext(configFilePath) {
|
|
66
|
+
const graphqlConfig = await findAndLoadGraphQLConfig(configFilePath);
|
|
67
|
+
if (graphqlConfig) {
|
|
68
|
+
return new CodegenContext({
|
|
69
|
+
graphqlConfig,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
const result = await loadCodegenConfig({ configFilePath });
|
|
73
|
+
if (!result) {
|
|
74
|
+
if (configFilePath) {
|
|
75
|
+
throw new DetailedError(`Config ${configFilePath} does not exist`, `
|
|
76
|
+
Config ${configFilePath} does not exist.
|
|
77
|
+
|
|
78
|
+
$ graphql-codegen --config ${configFilePath}
|
|
79
|
+
|
|
80
|
+
Please make sure the --config points to a correct file.
|
|
81
|
+
`);
|
|
82
|
+
}
|
|
83
|
+
throw new DetailedError(`Unable to find Codegen config file!`, `
|
|
84
|
+
Please make sure that you have a configuration file under the current directory!
|
|
85
|
+
`);
|
|
86
|
+
}
|
|
87
|
+
if (result.isEmpty) {
|
|
88
|
+
throw new DetailedError(`Found Codegen config file but it was empty!`, `
|
|
89
|
+
Please make sure that you have a valid configuration file under the current directory!
|
|
90
|
+
`);
|
|
91
|
+
}
|
|
92
|
+
return new CodegenContext({
|
|
93
|
+
filepath: result.filepath,
|
|
94
|
+
config: result.config,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
function getCustomConfigPath(cliFlags) {
|
|
98
|
+
const configFile = cliFlags.config;
|
|
99
|
+
return configFile ? resolve(process.cwd(), configFile) : null;
|
|
100
|
+
}
|
|
101
|
+
export function buildOptions() {
|
|
102
|
+
return {
|
|
103
|
+
c: {
|
|
104
|
+
alias: 'config',
|
|
105
|
+
type: 'string',
|
|
106
|
+
describe: 'Path to GraphQL codegen YAML config file, defaults to "codegen.yml" on the current directory',
|
|
107
|
+
},
|
|
108
|
+
w: {
|
|
109
|
+
alias: 'watch',
|
|
110
|
+
describe: 'Watch for changes and execute generation automatically. You can also specify a glob expreession for custom watch list.',
|
|
111
|
+
coerce: (watch) => {
|
|
112
|
+
if (watch === 'false') {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
if (typeof watch === 'string' || Array.isArray(watch)) {
|
|
116
|
+
return watch;
|
|
117
|
+
}
|
|
118
|
+
return !!watch;
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
r: {
|
|
122
|
+
alias: 'require',
|
|
123
|
+
describe: 'Loads specific require.extensions before running the codegen and reading the configuration',
|
|
124
|
+
type: 'array',
|
|
125
|
+
default: [],
|
|
126
|
+
},
|
|
127
|
+
o: {
|
|
128
|
+
alias: 'overwrite',
|
|
129
|
+
describe: 'Overwrites existing files',
|
|
130
|
+
type: 'boolean',
|
|
131
|
+
},
|
|
132
|
+
s: {
|
|
133
|
+
alias: 'silent',
|
|
134
|
+
describe: 'Suppresses printing errors',
|
|
135
|
+
type: 'boolean',
|
|
136
|
+
},
|
|
137
|
+
e: {
|
|
138
|
+
alias: 'errors-only',
|
|
139
|
+
describe: 'Only print errors',
|
|
140
|
+
type: 'boolean',
|
|
141
|
+
},
|
|
142
|
+
profile: {
|
|
143
|
+
describe: 'Use profiler to measure performance',
|
|
144
|
+
type: 'boolean',
|
|
145
|
+
},
|
|
146
|
+
p: {
|
|
147
|
+
alias: 'project',
|
|
148
|
+
describe: 'Name of a project in GraphQL Config',
|
|
149
|
+
type: 'string',
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
export function parseArgv(argv = process.argv) {
|
|
154
|
+
return yargs(argv).options(buildOptions()).parse(argv);
|
|
155
|
+
}
|
|
156
|
+
export async function createContext(cliFlags = parseArgv(process.argv)) {
|
|
157
|
+
if (cliFlags.require && cliFlags.require.length > 0) {
|
|
158
|
+
const relativeRequire = createRequire(process.cwd());
|
|
159
|
+
await Promise.all(cliFlags.require.map(mod => import(relativeRequire.resolve(mod, {
|
|
160
|
+
paths: [process.cwd()],
|
|
161
|
+
}))));
|
|
162
|
+
}
|
|
163
|
+
const customConfigPath = getCustomConfigPath(cliFlags);
|
|
164
|
+
const context = await loadContext(customConfigPath);
|
|
165
|
+
updateContextWithCliFlags(context, cliFlags);
|
|
166
|
+
return context;
|
|
167
|
+
}
|
|
168
|
+
export function updateContextWithCliFlags(context, cliFlags) {
|
|
169
|
+
const config = {
|
|
170
|
+
configFilePath: context.filepath,
|
|
171
|
+
};
|
|
172
|
+
if (cliFlags.watch) {
|
|
173
|
+
config.watch = cliFlags.watch;
|
|
174
|
+
}
|
|
175
|
+
if (cliFlags.overwrite === true) {
|
|
176
|
+
config.overwrite = cliFlags.overwrite;
|
|
177
|
+
}
|
|
178
|
+
if (cliFlags.silent === true) {
|
|
179
|
+
config.silent = cliFlags.silent;
|
|
180
|
+
}
|
|
181
|
+
if (cliFlags.errorsOnly === true) {
|
|
182
|
+
config.errorsOnly = cliFlags.errorsOnly;
|
|
183
|
+
}
|
|
184
|
+
if (cliFlags.project) {
|
|
185
|
+
context.useProject(cliFlags.project);
|
|
186
|
+
}
|
|
187
|
+
if (cliFlags.profile === true) {
|
|
188
|
+
context.useProfiler();
|
|
189
|
+
}
|
|
190
|
+
context.updateConfig(config);
|
|
191
|
+
}
|
|
192
|
+
export class CodegenContext {
|
|
193
|
+
constructor({ config, graphqlConfig, filepath, }) {
|
|
194
|
+
this._pluginContext = {};
|
|
195
|
+
this._config = config;
|
|
196
|
+
this._graphqlConfig = graphqlConfig;
|
|
197
|
+
this.filepath = this._graphqlConfig ? this._graphqlConfig.filepath : filepath;
|
|
198
|
+
this.cwd = this._graphqlConfig ? this._graphqlConfig.dirpath : process.cwd();
|
|
199
|
+
this.profiler = createNoopProfiler();
|
|
200
|
+
}
|
|
201
|
+
useProject(name) {
|
|
202
|
+
this._project = name;
|
|
203
|
+
}
|
|
204
|
+
getConfig(extraConfig) {
|
|
205
|
+
if (!this.config) {
|
|
206
|
+
if (this._graphqlConfig) {
|
|
207
|
+
const project = this._graphqlConfig.getProject(this._project);
|
|
208
|
+
this.config = {
|
|
209
|
+
...project.extension('codegen'),
|
|
210
|
+
schema: project.schema,
|
|
211
|
+
documents: project.documents,
|
|
212
|
+
pluginContext: this._pluginContext,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
this.config = { ...this._config, pluginContext: this._pluginContext };
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return {
|
|
220
|
+
...extraConfig,
|
|
221
|
+
...this.config,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
updateConfig(config) {
|
|
225
|
+
this.config = {
|
|
226
|
+
...this.getConfig(),
|
|
227
|
+
...config,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
useProfiler() {
|
|
231
|
+
this.profiler = createProfiler();
|
|
232
|
+
const now = new Date(); // 2011-10-05T14:48:00.000Z
|
|
233
|
+
const datetime = now.toISOString().split('.')[0]; // 2011-10-05T14:48:00
|
|
234
|
+
const datetimeNormalized = datetime.replace(/-|:/g, ''); // 20111005T144800
|
|
235
|
+
this.profilerOutput = `codegen-${datetimeNormalized}.json`;
|
|
236
|
+
}
|
|
237
|
+
getPluginContext() {
|
|
238
|
+
return this._pluginContext;
|
|
239
|
+
}
|
|
240
|
+
async loadSchema(pointer) {
|
|
241
|
+
const config = this.getConfig(defaultSchemaLoadOptions);
|
|
242
|
+
if (this._graphqlConfig) {
|
|
243
|
+
// TODO: SchemaWithLoader won't work here
|
|
244
|
+
return addHashToSchema(this._graphqlConfig.getProject(this._project).loadSchema(pointer, 'GraphQLSchema', config));
|
|
245
|
+
}
|
|
246
|
+
return addHashToSchema(loadSchema(pointer, config));
|
|
247
|
+
}
|
|
248
|
+
async loadDocuments(pointer) {
|
|
249
|
+
const config = this.getConfig(defaultDocumentsLoadOptions);
|
|
250
|
+
if (this._graphqlConfig) {
|
|
251
|
+
// TODO: pointer won't work here
|
|
252
|
+
return addHashToDocumentFiles(this._graphqlConfig.getProject(this._project).loadDocuments(pointer, config));
|
|
253
|
+
}
|
|
254
|
+
return addHashToDocumentFiles(loadDocuments(pointer, config));
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
export function ensureContext(input) {
|
|
258
|
+
return input instanceof CodegenContext ? input : new CodegenContext({ config: input });
|
|
259
|
+
}
|
|
260
|
+
function hashContent(content) {
|
|
261
|
+
return createHash('sha256').update(content).digest('hex');
|
|
262
|
+
}
|
|
263
|
+
function hashSchema(schema) {
|
|
264
|
+
return hashContent(print(getCachedDocumentNodeFromSchema(schema)));
|
|
265
|
+
}
|
|
266
|
+
function addHashToSchema(schemaPromise) {
|
|
267
|
+
return schemaPromise.then(schema => {
|
|
268
|
+
// It's consumed later on. The general purpose is to use it for caching.
|
|
269
|
+
if (!schema.extensions) {
|
|
270
|
+
schema.extensions = {};
|
|
271
|
+
}
|
|
272
|
+
schema.extensions['hash'] = hashSchema(schema);
|
|
273
|
+
return schema;
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
function hashDocument(doc) {
|
|
277
|
+
if (doc.rawSDL) {
|
|
278
|
+
return hashContent(doc.rawSDL);
|
|
279
|
+
}
|
|
280
|
+
if (doc.document) {
|
|
281
|
+
return hashContent(print(doc.document));
|
|
282
|
+
}
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
function addHashToDocumentFiles(documentFilesPromise) {
|
|
286
|
+
return documentFilesPromise.then(documentFiles => documentFiles.map(doc => {
|
|
287
|
+
doc.hash = hashDocument(doc);
|
|
288
|
+
return doc;
|
|
289
|
+
}));
|
|
290
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { lifecycleHooks } from './hooks.js';
|
|
2
|
+
import { executeCodegen } from './codegen.js';
|
|
3
|
+
import { createWatcher } from './utils/watcher.js';
|
|
4
|
+
import { fileExists, readFile, writeFile, unlinkFile } from './utils/file-system.js';
|
|
5
|
+
import mkdirp from 'mkdirp';
|
|
6
|
+
import { dirname, join, isAbsolute } from 'path';
|
|
7
|
+
import { debugLog } from './utils/debugging.js';
|
|
8
|
+
import { ensureContext } from './config.js';
|
|
9
|
+
import { createHash } from 'crypto';
|
|
10
|
+
const hash = (content) => createHash('sha1').update(content).digest('base64');
|
|
11
|
+
export async function generate(input, saveToFile = true) {
|
|
12
|
+
const context = ensureContext(input);
|
|
13
|
+
const config = context.getConfig();
|
|
14
|
+
await context.profiler.run(() => lifecycleHooks(config.hooks).afterStart(), 'Lifecycle: afterStart');
|
|
15
|
+
let previouslyGeneratedFilenames = [];
|
|
16
|
+
function removeStaleFiles(config, generationResult) {
|
|
17
|
+
const filenames = generationResult.map(o => o.filename);
|
|
18
|
+
// find stale files from previous build which are not present in current build
|
|
19
|
+
const staleFilenames = previouslyGeneratedFilenames.filter(f => !filenames.includes(f));
|
|
20
|
+
staleFilenames.forEach(filename => {
|
|
21
|
+
if (shouldOverwrite(config, filename)) {
|
|
22
|
+
return unlinkFile(filename, err => {
|
|
23
|
+
const prettyFilename = filename.replace(`${input.cwd || process.cwd()}/`, '');
|
|
24
|
+
if (err) {
|
|
25
|
+
debugLog(`Cannot remove stale file: ${prettyFilename}\n${err}`);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
debugLog(`Removed stale file: ${prettyFilename}`);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
previouslyGeneratedFilenames = filenames;
|
|
34
|
+
}
|
|
35
|
+
const recentOutputHash = new Map();
|
|
36
|
+
async function writeOutput(generationResult) {
|
|
37
|
+
if (!saveToFile) {
|
|
38
|
+
return generationResult;
|
|
39
|
+
}
|
|
40
|
+
if (config.watch) {
|
|
41
|
+
removeStaleFiles(config, generationResult);
|
|
42
|
+
}
|
|
43
|
+
await context.profiler.run(async () => {
|
|
44
|
+
await lifecycleHooks(config.hooks).beforeAllFileWrite(generationResult.map(r => r.filename));
|
|
45
|
+
}, 'Lifecycle: beforeAllFileWrite');
|
|
46
|
+
await context.profiler.run(() => Promise.all(generationResult.map(async (result) => {
|
|
47
|
+
const exists = await fileExists(result.filename);
|
|
48
|
+
if (!shouldOverwrite(config, result.filename) && exists) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const content = result.content || '';
|
|
52
|
+
const currentHash = hash(content);
|
|
53
|
+
let previousHash = recentOutputHash.get(result.filename);
|
|
54
|
+
if (!previousHash && exists) {
|
|
55
|
+
previousHash = hash(await readFile(result.filename));
|
|
56
|
+
}
|
|
57
|
+
if (previousHash && currentHash === previousHash) {
|
|
58
|
+
debugLog(`Skipping file (${result.filename}) writing due to indentical hash...`);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (content.length === 0) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
recentOutputHash.set(result.filename, currentHash);
|
|
65
|
+
const basedir = dirname(result.filename);
|
|
66
|
+
await lifecycleHooks(result.hooks).beforeOneFileWrite(result.filename);
|
|
67
|
+
await lifecycleHooks(config.hooks).beforeOneFileWrite(result.filename);
|
|
68
|
+
await mkdirp(basedir);
|
|
69
|
+
const absolutePath = isAbsolute(result.filename)
|
|
70
|
+
? result.filename
|
|
71
|
+
: join(input.cwd || process.cwd(), result.filename);
|
|
72
|
+
await writeFile(absolutePath, result.content);
|
|
73
|
+
await lifecycleHooks(result.hooks).afterOneFileWrite(result.filename);
|
|
74
|
+
await lifecycleHooks(config.hooks).afterOneFileWrite(result.filename);
|
|
75
|
+
})), 'Write files');
|
|
76
|
+
await context.profiler.run(() => lifecycleHooks(config.hooks).afterAllFileWrite(generationResult.map(r => r.filename)), 'Lifecycle: afterAllFileWrite');
|
|
77
|
+
return generationResult;
|
|
78
|
+
}
|
|
79
|
+
// watch mode
|
|
80
|
+
if (config.watch) {
|
|
81
|
+
return createWatcher(context, writeOutput);
|
|
82
|
+
}
|
|
83
|
+
const outputFiles = await context.profiler.run(() => executeCodegen(context), 'executeCodegen');
|
|
84
|
+
await context.profiler.run(() => writeOutput(outputFiles), 'writeOutput');
|
|
85
|
+
await context.profiler.run(() => lifecycleHooks(config.hooks).beforeDone(), 'Lifecycle: beforeDone');
|
|
86
|
+
if (context.profilerOutput) {
|
|
87
|
+
await writeFile(join(context.cwd, context.profilerOutput), JSON.stringify(context.profiler.collect()));
|
|
88
|
+
}
|
|
89
|
+
return outputFiles;
|
|
90
|
+
}
|
|
91
|
+
function shouldOverwrite(config, outputPath) {
|
|
92
|
+
const globalValue = config.overwrite === undefined ? true : !!config.overwrite;
|
|
93
|
+
const outputConfig = config.generates[outputPath];
|
|
94
|
+
if (!outputConfig) {
|
|
95
|
+
debugLog(`Couldn't find a config of ${outputPath}`);
|
|
96
|
+
return globalValue;
|
|
97
|
+
}
|
|
98
|
+
if (isConfiguredOutput(outputConfig) && typeof outputConfig.overwrite === 'boolean') {
|
|
99
|
+
return outputConfig.overwrite;
|
|
100
|
+
}
|
|
101
|
+
return globalValue;
|
|
102
|
+
}
|
|
103
|
+
function isConfiguredOutput(output) {
|
|
104
|
+
return typeof output.plugins !== 'undefined';
|
|
105
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { loadConfig } from 'graphql-config';
|
|
2
|
+
import { ApolloEngineLoader } from '@graphql-tools/apollo-engine-loader';
|
|
3
|
+
import { CodeFileLoader } from '@graphql-tools/code-file-loader';
|
|
4
|
+
import { GitLoader } from '@graphql-tools/git-loader';
|
|
5
|
+
import { GithubLoader } from '@graphql-tools/github-loader';
|
|
6
|
+
import { PrismaLoader } from '@graphql-tools/prisma-loader';
|
|
7
|
+
export const CodegenExtension = (api) => {
|
|
8
|
+
// Schema
|
|
9
|
+
api.loaders.schema.register(new CodeFileLoader({
|
|
10
|
+
pluckConfig: {
|
|
11
|
+
skipIndent: true,
|
|
12
|
+
},
|
|
13
|
+
}));
|
|
14
|
+
api.loaders.schema.register(new GitLoader());
|
|
15
|
+
api.loaders.schema.register(new GithubLoader());
|
|
16
|
+
api.loaders.schema.register(new ApolloEngineLoader());
|
|
17
|
+
api.loaders.schema.register(new PrismaLoader());
|
|
18
|
+
// Documents
|
|
19
|
+
api.loaders.documents.register(new CodeFileLoader({
|
|
20
|
+
pluckConfig: {
|
|
21
|
+
skipIndent: true,
|
|
22
|
+
},
|
|
23
|
+
}));
|
|
24
|
+
api.loaders.documents.register(new GitLoader());
|
|
25
|
+
api.loaders.documents.register(new GithubLoader());
|
|
26
|
+
return {
|
|
27
|
+
name: 'codegen',
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
export async function findAndLoadGraphQLConfig(filepath) {
|
|
31
|
+
const config = await loadConfig({
|
|
32
|
+
filepath,
|
|
33
|
+
rootDir: process.cwd(),
|
|
34
|
+
extensions: [CodegenExtension],
|
|
35
|
+
throwOnEmpty: false,
|
|
36
|
+
throwOnMissing: false,
|
|
37
|
+
});
|
|
38
|
+
if (isGraphQLConfig(config)) {
|
|
39
|
+
return config;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// Kamil: user might load a config that is not GraphQL Config
|
|
43
|
+
// so we need to check if it's a regular config or not
|
|
44
|
+
function isGraphQLConfig(config) {
|
|
45
|
+
if (!config) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
return config.getDefault().hasExtension('codegen');
|
|
50
|
+
}
|
|
51
|
+
catch (e) { }
|
|
52
|
+
try {
|
|
53
|
+
for (const projectName in config.projects) {
|
|
54
|
+
if (config.projects.hasOwnProperty(projectName)) {
|
|
55
|
+
const project = config.projects[projectName];
|
|
56
|
+
if (project.hasExtension('codegen')) {
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch (e) { }
|
|
63
|
+
return false;
|
|
64
|
+
}
|
package/esm/hooks.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { debugLog } from './utils/debugging.js';
|
|
2
|
+
import { exec } from 'child_process';
|
|
3
|
+
import { delimiter, sep } from 'path';
|
|
4
|
+
const DEFAULT_HOOKS = {
|
|
5
|
+
afterStart: [],
|
|
6
|
+
beforeDone: [],
|
|
7
|
+
onWatchTriggered: [],
|
|
8
|
+
onError: [],
|
|
9
|
+
afterOneFileWrite: [],
|
|
10
|
+
afterAllFileWrite: [],
|
|
11
|
+
beforeOneFileWrite: [],
|
|
12
|
+
beforeAllFileWrite: [],
|
|
13
|
+
};
|
|
14
|
+
function normalizeHooks(_hooks) {
|
|
15
|
+
const keys = Object.keys({
|
|
16
|
+
...DEFAULT_HOOKS,
|
|
17
|
+
..._hooks,
|
|
18
|
+
});
|
|
19
|
+
return keys.reduce((prev, hookName) => {
|
|
20
|
+
if (typeof _hooks[hookName] === 'string') {
|
|
21
|
+
return {
|
|
22
|
+
...prev,
|
|
23
|
+
[hookName]: [_hooks[hookName]],
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
if (typeof _hooks[hookName] === 'function') {
|
|
27
|
+
return {
|
|
28
|
+
...prev,
|
|
29
|
+
[hookName]: [_hooks[hookName]],
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
if (Array.isArray(_hooks[hookName])) {
|
|
33
|
+
return {
|
|
34
|
+
...prev,
|
|
35
|
+
[hookName]: _hooks[hookName],
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
return prev;
|
|
39
|
+
}, {});
|
|
40
|
+
}
|
|
41
|
+
function execShellCommand(cmd) {
|
|
42
|
+
return new Promise((resolve, reject) => {
|
|
43
|
+
exec(cmd, {
|
|
44
|
+
env: {
|
|
45
|
+
...process.env,
|
|
46
|
+
PATH: `${process.env.PATH}${delimiter}${process.cwd()}${sep}node_modules${sep}.bin`,
|
|
47
|
+
},
|
|
48
|
+
}, (error, stdout, stderr) => {
|
|
49
|
+
if (error) {
|
|
50
|
+
reject(error);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
resolve(stdout || stderr);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
async function executeHooks(hookName, scripts = [], args = []) {
|
|
59
|
+
debugLog(`Running lifecycle hook "${hookName}" scripts...`);
|
|
60
|
+
for (const script of scripts) {
|
|
61
|
+
if (typeof script === 'string') {
|
|
62
|
+
debugLog(`Running lifecycle hook "${hookName}" script: ${script} with args: ${args.join(' ')}...`);
|
|
63
|
+
await execShellCommand(`${script} ${args.join(' ')}`);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
debugLog(`Running lifecycle hook "${hookName}" script: ${script.name} with args: ${args.join(' ')}...`);
|
|
67
|
+
await script(...args);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
export const lifecycleHooks = (_hooks = {}) => {
|
|
72
|
+
const hooks = normalizeHooks(_hooks);
|
|
73
|
+
return {
|
|
74
|
+
afterStart: async () => executeHooks('afterStart', hooks.afterStart),
|
|
75
|
+
onWatchTriggered: async (event, path) => executeHooks('onWatchTriggered', hooks.onWatchTriggered, [event, path]),
|
|
76
|
+
onError: async (error) => executeHooks('onError', hooks.onError, [`"${error}"`]),
|
|
77
|
+
afterOneFileWrite: async (path) => executeHooks('afterOneFileWrite', hooks.afterOneFileWrite, [path]),
|
|
78
|
+
afterAllFileWrite: async (paths) => executeHooks('afterAllFileWrite', hooks.afterAllFileWrite, paths),
|
|
79
|
+
beforeOneFileWrite: async (path) => executeHooks('beforeOneFileWrite', hooks.beforeOneFileWrite, [path]),
|
|
80
|
+
beforeAllFileWrite: async (paths) => executeHooks('beforeAllFileWrite', hooks.beforeAllFileWrite, paths),
|
|
81
|
+
beforeDone: async () => executeHooks('beforeDone', hooks.beforeDone),
|
|
82
|
+
};
|
|
83
|
+
};
|
package/esm/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { executeCodegen } from './codegen.js';
|
|
2
|
+
export { generate } from './generate-and-save.js';
|
|
3
|
+
export * from './config.js';
|
|
4
|
+
export * from './init/index.js';
|
|
5
|
+
export * from './utils/cli-error.js';
|
|
6
|
+
export * from './cli.js';
|
|
7
|
+
export * from './graphql-config.js';
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { resolve, relative } from 'path';
|
|
3
|
+
import { writeFileSync, readFileSync } from 'fs';
|
|
4
|
+
import detectIndent from 'detect-indent';
|
|
5
|
+
import getLatestVersion from 'latest-version';
|
|
6
|
+
// Parses config and writes it to a file
|
|
7
|
+
export async function writeConfig(answers, config) {
|
|
8
|
+
const YAML = await import('json-to-pretty-yaml').then(m => ('default' in m ? m.default : m));
|
|
9
|
+
const ext = answers.config.toLocaleLowerCase().endsWith('.json') ? 'json' : 'yml';
|
|
10
|
+
const content = ext === 'json' ? JSON.stringify(config) : YAML.stringify(config);
|
|
11
|
+
const fullPath = resolve(process.cwd(), answers.config);
|
|
12
|
+
const relativePath = relative(process.cwd(), answers.config);
|
|
13
|
+
writeFileSync(fullPath, content, {
|
|
14
|
+
encoding: 'utf-8',
|
|
15
|
+
});
|
|
16
|
+
return {
|
|
17
|
+
relativePath,
|
|
18
|
+
fullPath,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
// Updates package.json (script and plugins as dependencies)
|
|
22
|
+
export async function writePackage(answers, configLocation) {
|
|
23
|
+
// script
|
|
24
|
+
const pkgPath = resolve(process.cwd(), 'package.json');
|
|
25
|
+
const pkgContent = readFileSync(pkgPath, {
|
|
26
|
+
encoding: 'utf-8',
|
|
27
|
+
});
|
|
28
|
+
const pkg = JSON.parse(pkgContent);
|
|
29
|
+
const { indent } = detectIndent(pkgContent);
|
|
30
|
+
if (!pkg.scripts) {
|
|
31
|
+
pkg.scripts = {};
|
|
32
|
+
}
|
|
33
|
+
pkg.scripts[answers.script] = `graphql-codegen --config ${configLocation}`;
|
|
34
|
+
// plugin
|
|
35
|
+
if (!pkg.devDependencies) {
|
|
36
|
+
pkg.devDependencies = {};
|
|
37
|
+
}
|
|
38
|
+
await Promise.all(answers.plugins.map(async (plugin) => {
|
|
39
|
+
pkg.devDependencies[plugin.package] = await getLatestVersion(plugin.package);
|
|
40
|
+
}));
|
|
41
|
+
if (answers.introspection) {
|
|
42
|
+
pkg.devDependencies['@graphql-codegen/introspection'] = await getLatestVersion('@graphql-codegen/introspection');
|
|
43
|
+
}
|
|
44
|
+
pkg.devDependencies['@graphql-codegen/cli'] = await getLatestVersion('@graphql-codegen/cli');
|
|
45
|
+
writeFileSync(pkgPath, JSON.stringify(pkg, null, indent));
|
|
46
|
+
}
|
|
47
|
+
export function bold(str) {
|
|
48
|
+
return chalk.bold(str);
|
|
49
|
+
}
|
|
50
|
+
export function grey(str) {
|
|
51
|
+
return chalk.grey(str);
|
|
52
|
+
}
|
|
53
|
+
export function italic(str) {
|
|
54
|
+
return chalk.italic(str);
|
|
55
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
import { getQuestions } from './questions.js';
|
|
3
|
+
import { guessTargets } from './targets.js';
|
|
4
|
+
import { Tags } from './types.js';
|
|
5
|
+
import { writeConfig, writePackage, bold } from './helpers.js';
|
|
6
|
+
function log(...msgs) {
|
|
7
|
+
// eslint-disable-next-line no-console
|
|
8
|
+
console.log(...msgs);
|
|
9
|
+
}
|
|
10
|
+
export async function init() {
|
|
11
|
+
log(`
|
|
12
|
+
Welcome to ${bold('GraphQL Code Generator')}!
|
|
13
|
+
Answer few questions and we will setup everything for you.
|
|
14
|
+
`);
|
|
15
|
+
const possibleTargets = await guessTargets();
|
|
16
|
+
const answers = await inquirer.prompt(getQuestions(possibleTargets));
|
|
17
|
+
// define config
|
|
18
|
+
const config = {
|
|
19
|
+
overwrite: true,
|
|
20
|
+
schema: answers.schema,
|
|
21
|
+
documents: answers.targets.includes(Tags.browser) ? answers.documents : null,
|
|
22
|
+
generates: {
|
|
23
|
+
[answers.output]: {
|
|
24
|
+
plugins: answers.plugins.map(p => p.value),
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
// introspection
|
|
29
|
+
if (answers.introspection) {
|
|
30
|
+
addIntrospection(config);
|
|
31
|
+
}
|
|
32
|
+
// config file
|
|
33
|
+
const { relativePath } = await writeConfig(answers, config);
|
|
34
|
+
log(`Fetching latest versions of selected plugins...`);
|
|
35
|
+
// write package.json
|
|
36
|
+
await writePackage(answers, relativePath);
|
|
37
|
+
// Emit status to the terminal
|
|
38
|
+
log(`
|
|
39
|
+
Config file generated at ${bold(relativePath)}
|
|
40
|
+
|
|
41
|
+
${bold('$ npm install')}
|
|
42
|
+
|
|
43
|
+
To install the plugins.
|
|
44
|
+
|
|
45
|
+
${bold(`$ npm run ${answers.script}`)}
|
|
46
|
+
|
|
47
|
+
To run GraphQL Code Generator.
|
|
48
|
+
`);
|
|
49
|
+
}
|
|
50
|
+
// adds an introspection to `generates`
|
|
51
|
+
function addIntrospection(config) {
|
|
52
|
+
config.generates['./graphql.schema.json'] = {
|
|
53
|
+
plugins: ['introspection'],
|
|
54
|
+
};
|
|
55
|
+
}
|