generate-graphql-types.ts 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. import { spawn } from 'child_process';
  2. import * as fs from 'fs';
  3. import { execute, GraphQLSchema, IntrospectionQuery, IntrospectionSchema, parse } from 'graphql';
  4. import { makeExecutableSchema } from 'graphql-tools';
  5. import { buildClientSchema, introspectionQuery, printSchema } from 'graphql/utilities';
  6. import { fileLoader, mergeTypes } from 'merge-graphql-schemas';
  7. import { API_PATH, API_PORT } from '../shared/shared-constants';
  8. // tslint:disable:no-console
  9. const API_URL = `http://localhost:${API_PORT}/${API_PATH}`;
  10. const SCHEMA_JSON_FILE = '../schema.json';
  11. const CLIENT_SCHEMA_FILES = './src/app/data/types/**/*.graphql';
  12. const CLIENT_QUERY_FILES = '"./src/app/data/(queries|mutations|fragments)/**/*.ts"';
  13. const TYPESCRIPT_DEFINITIONS_FILE = './src/app/data/types/gql-generated-types.ts';
  14. main().catch(e => {
  15. console.log('Could not generate types!', e);
  16. process.exitCode = 1;
  17. });
  18. /**
  19. * This script uses apollo-codegen to generate TypeScript interfaces for all
  20. * GraphQL queries defined in the admin-ui app. Run it via the package.json
  21. * script "generate-gql-types".
  22. */
  23. async function main(): Promise<void> {
  24. const introspectionQueryFromApi = await downloadSchemaFromApi(API_URL);
  25. const combinedSchema = await combineSchemas(introspectionQueryFromApi, CLIENT_SCHEMA_FILES);
  26. fs.writeFileSync(SCHEMA_JSON_FILE, JSON.stringify(combinedSchema));
  27. console.log(`Generated schema file: ${SCHEMA_JSON_FILE}`);
  28. await generateTypeScriptTypesFromSchema(SCHEMA_JSON_FILE, CLIENT_QUERY_FILES, TYPESCRIPT_DEFINITIONS_FILE);
  29. console.log('Generated TypeScript definitions!');
  30. }
  31. /**
  32. * Downloads the schema from the provided GraphQL endpoint using the `apollo schema:download`
  33. * cli command and returns the result as an IntrospectionQuery object.
  34. */
  35. async function downloadSchemaFromApi(apiEndpoint: string): Promise<IntrospectionQuery> {
  36. const TEMP_API_SCHEMA = '../schema.temp.json';
  37. await runCommand('yarn', [
  38. 'apollo',
  39. 'schema:download',
  40. TEMP_API_SCHEMA,
  41. `--endpoint=${API_URL}`,
  42. ]);
  43. console.log(`Downloaded schema from ${API_URL}`);
  44. const schemaFromApi = fs.readFileSync(TEMP_API_SCHEMA, { encoding: 'utf8' });
  45. fs.unlinkSync(TEMP_API_SCHEMA);
  46. const introspectionSchema: IntrospectionSchema = JSON.parse(schemaFromApi);
  47. return {
  48. __schema: introspectionSchema,
  49. };
  50. }
  51. async function introspectionFromSchema(schema: GraphQLSchema): Promise<IntrospectionQuery> {
  52. const queryAST = parse(introspectionQuery);
  53. const result = await execute(schema, queryAST);
  54. return result.data as IntrospectionQuery;
  55. }
  56. /**
  57. * Combines the IntrospectionQuery from the GraphQL API with any client-side schemas as defined by the
  58. * clientSchemaFiles glob.
  59. */
  60. async function combineSchemas(introspectionQueryFromApi: IntrospectionQuery, clientSchemaFiles: string): Promise<IntrospectionQuery> {
  61. const schemaFromApi = buildClientSchema(introspectionQueryFromApi);
  62. const clientSchemas = fileLoader(clientSchemaFiles);
  63. const remoteSchema = printSchema(schemaFromApi);
  64. const typeDefs = mergeTypes([...clientSchemas, remoteSchema], {
  65. all: true,
  66. });
  67. const executableSchema = makeExecutableSchema({ typeDefs, resolverValidationOptions: { requireResolversForResolveType: false } });
  68. const introspection = await introspectionFromSchema(executableSchema);
  69. return introspection;
  70. }
  71. /**
  72. * Generates TypeScript definitions from the provided schema json file sing the `apollo codegen:generate` cli command.
  73. */
  74. async function generateTypeScriptTypesFromSchema(schemaFile: string, queryFiles: string, outputFile: string): Promise<number> {
  75. return runCommand('yarn', [
  76. 'apollo',
  77. 'codegen:generate',
  78. outputFile,
  79. '--addTypename',
  80. `--queries=${queryFiles}`,
  81. `--schema ${schemaFile}`,
  82. ]);
  83. }
  84. /**
  85. * Runs a command-line command and resolves when completed.
  86. */
  87. function runCommand(command: string, args: string[]): Promise<number> {
  88. return new Promise((resolve, reject) => {
  89. const cp = spawn(command, args, { shell: true });
  90. cp.on('error', reject);
  91. cp.stdout.on('data', (data) => {
  92. if (4 < data.length) {
  93. console.log(`${data}`);
  94. }
  95. });
  96. cp.stderr.on('data', data => {
  97. if (4 < data.length) {
  98. console.log(`${data}`);
  99. }
  100. });
  101. cp.on('close', code => {
  102. if (code !== 0) {
  103. reject(code);
  104. }
  105. resolve(code);
  106. });
  107. });
  108. }