@toptal/davinci-graphql-codegen 0.1.1-alpha-feature-comm-620-graphql-codegen.1420

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/LICENSE.md ADDED
@@ -0,0 +1,5 @@
1
+ /\* Copyright (C) Toptal LLC - All Rights Reserved
2
+
3
+ - Unauthorized copying of this file, via any medium is strictly prohibited
4
+ - Proprietary and confidential
5
+ \*/
package/README.md ADDED
@@ -0,0 +1,73 @@
1
+ # 🛠 Generating Operation Types
2
+
3
+ In order to generate operation types for your GraphQL Gateway, you'd need to add this library as a dependency on your app/host/lib and the GraphQL schemas lib dependency too:
4
+
5
+ ```
6
+ yarn workspace @toptal/modularity-template-my-lib add @toptal/modularity-template-codegen@0.0.1 @toptal/modularity-template-graphql@0.0.1 @graphql-typed-document-node/core
7
+ ```
8
+
9
+ _PS: Note that we specify the package version, otherwise, yarn will throw you an error saying that it cannot find the module._
10
+
11
+ Then add this `script` to your `package.json`.
12
+
13
+ `libs/my-lib/package.json`
14
+ ```
15
+ "codegen:operations": "codegen generate:operations"
16
+ ```
17
+
18
+ Then create a `codegen.json` file at the root folder of your package that contains the following data/structure:
19
+
20
+ ```json
21
+ {
22
+ "schema": "@toptal/modularity-template-graphql/talent",
23
+ "documents": "src/**/*.gql"
24
+ }
25
+ ```
26
+
27
+ This `schema` property in the `codegen.json` file is where this utility is going to go fetch your schema to download it, parse it and then generate the appropriate typescript types for all your GraphQL operations. In this example we're telling the utility to fetch the schema from `@toptal/modularity-tempalte-graphql` which is the dependency that we have just installed and also telling it that the `schema` we would like to use is the `talent` schema. This is because we store our schemas within `lib/graphl` like so:
28
+
29
+ ![Captura de pantalla 2021-10-11 a las 11 03 00](https://user-images.githubusercontent.com/9496960/136763291-7ab3f50e-17e0-4fc8-a4b5-965cc15ac0fd.png)
30
+
31
+ `@toptal/modularity-template-graphql` will internally resolve to `lib/graphql`, this is a more convenient and comfortable way of working with the `lib/graphql` package and won't break implementations.
32
+
33
+ **ℹ️ Heads-up:** The `documents` property is a `glob pattern` that tells this utility where your GraphQL operations are located. Please note that this `glob pattern` does not start with `./` as we use `process.env.INIT_CWD` to resolve where this utility was launched from and therefore, using `./src/**/*.gql` will break the implementation and won't be able to resolve your files.
34
+
35
+ # 🛠 Generating Schema Types
36
+
37
+ In order to generate schema types for your GraphQL Gateway, you'd have to go to `lib/graphql` and modify the `codegen.json`. This file contains a structure like the following<sup>1</sup>:
38
+
39
+ ```json
40
+ [
41
+ {
42
+ "schema": "https://staging.toptal.net/gateway/graphql/talent/graphql",
43
+ "target": "talent"
44
+ },
45
+ {
46
+ "schema": "https://staging.toptal.net/gateway/graphql/gateway/graphql",
47
+ "target": "gateway"
48
+ },
49
+ {
50
+ "schema": "https://staging.toptal.net/gateway/graphql/lens/graphql",
51
+ "target": "lens"
52
+ },
53
+ {
54
+ "schema": "https://staging.toptal.net/gateway/graphql/chronicles/graphql",
55
+ "target": "chronicles"
56
+ },
57
+ {
58
+ "schema": "https://staging.toptal.net/gateway/graphql/staff/graphql",
59
+ "target": "staff"
60
+ }
61
+ ]
62
+ ```
63
+
64
+ The array contains many objects with the following shape:
65
+
66
+ ```ts
67
+ schema: string;
68
+ target: string;
69
+ ```
70
+
71
+ The `schema` property will point to the URL where the schema will be fetched from. The `target` property will indicate where the schema is going to be store. As per the example above, your should name it the same way as your gateway, for instance: the talent gateway would have a target of "talent". Later on, as you have already read on the "Generate Operation Types" section, you'd use this target to retrieve the schemas from like so: `@toptal/modularity-template-graphql/talent`.
72
+
73
+ _<sup>1</sup> *This structure might be subject to change in a future iteration of this project*_
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env node
2
+
3
+ const cliEngine = require('@toptal/davinci-cli-shared')
4
+
5
+ const codegenGenerateSchemaCreator = require('../src/commands/generate-schema')
6
+ const codegenGenerateOperationsCreator = require('../src/commands/generate-operations')
7
+
8
+ cliEngine.loadCommands([
9
+ codegenGenerateSchemaCreator,
10
+ codegenGenerateOperationsCreator
11
+ ])
12
+
13
+ cliEngine.help(
14
+ () => `
15
+ Commands:
16
+
17
+ help [command...] Provides help for a given command
18
+ exit Exits application
19
+ generate:operations generate graphql operation types based off generated schemas
20
+ generate:schema generate graphql schemas
21
+ `
22
+ )
23
+
24
+ const args = cliEngine.bootstrap({ use: 'minimist' })
25
+
26
+ // eslint-disable-next-line @typescript-eslint/no-shadow
27
+ cliEngine.catchErrorCommand((args, cb) => {
28
+ cliEngine.execCommand(`${args.commands.join(' ')}`)
29
+ cb()
30
+ })
31
+
32
+ if (args._ && args._.length) {
33
+ cliEngine.execCommand(`${args._.join(' ')}`)
34
+ }
package/esbuild.js ADDED
@@ -0,0 +1,16 @@
1
+ const esbuild = require('esbuild')
2
+ // Automatically exclude all node_modules from the bundled version
3
+ const { nodeExternalsPlugin } = require('esbuild-node-externals')
4
+
5
+ esbuild
6
+ .build({
7
+ entryPoints: ['./bin/davinci-graphql-codegen.ts'],
8
+ outdir: 'dist-package',
9
+ bundle: true,
10
+ minify: false,
11
+ platform: 'node',
12
+ sourcemap: true,
13
+ target: 'node14',
14
+ plugins: [nodeExternalsPlugin()]
15
+ })
16
+ .catch(() => process.exit(1))
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@toptal/davinci-graphql-codegen",
3
+ "version": "0.1.1-alpha-feature-comm-620-graphql-codegen.1420+772235c2",
4
+ "description": "Codegen",
5
+ "author": "Toptal",
6
+ "license": "ISC",
7
+ "homepage": "https://github.com/toptal/davinci/tree/master/packages/graphql-codegen#readme",
8
+ "publishConfig": {
9
+ "access": "public"
10
+ },
11
+ "bin": {
12
+ "davinci-graphql-codegen": "./bin/davinci-graphql-codegen.js"
13
+ },
14
+ "main": "./src/index.js",
15
+ "scripts": {
16
+ "build:package": "../../bin/build-package.js",
17
+ "prepublishOnly": "../../bin/prepublish.js",
18
+ "test": "echo \"Error: no test specified\" && exit 1"
19
+ },
20
+ "sideEffects": false,
21
+ "dependencies": {
22
+ "@google-cloud/storage": "^5.16.1",
23
+ "@graphql-codegen/add": "^3.1.0",
24
+ "@graphql-codegen/cli": "^2.2.0",
25
+ "@graphql-codegen/introspection": "^2.1.0",
26
+ "@graphql-codegen/typed-document-node": "^2.1.4",
27
+ "@graphql-codegen/typescript": "^2.2.2",
28
+ "@graphql-codegen/typescript-operations": "^2.1.4",
29
+ "@graphql-codegen/typescript-resolvers": "^2.2.1",
30
+ "@graphql-typed-document-node/core": "^3.1.0",
31
+ "@toptal/davinci-cli-shared": "1.5.1-alpha-feature-comm-620-graphql-codegen.83+772235c2",
32
+ "chalk": "^4.1.2",
33
+ "graphql": "^15.7.1"
34
+ },
35
+ "gitHead": "772235c2a3e93c6d06cd233dc7c77a532831c83e"
36
+ }
@@ -0,0 +1,34 @@
1
+ const { generateOperations } = require('../generate')
2
+ const readConfig = require('../read-config')
3
+ const processArgs = require('../services/process-args')
4
+
5
+ const args = processArgs(process.argv.slice(2))
6
+ const operation = args.operation
7
+
8
+ // eslint-disable-next-line func-style
9
+ async function codegenGenerateOperations() {
10
+ const codegen = await readConfig()
11
+
12
+ switch (operation) {
13
+ case 'generate:operations':
14
+ for await (const { schema, documents } of codegen) {
15
+ await generateOperations({
16
+ schema,
17
+ documents
18
+ })
19
+ }
20
+ break
21
+ default:
22
+ throw new Error(`❌ Unrecognized operation`)
23
+ }
24
+ }
25
+
26
+ const codegenGenerateOperationsCreator = {
27
+ command: 'generate:operations',
28
+ description: 'generate graphql operation types based off generated schemas',
29
+ action: () => {
30
+ codegenGenerateOperations()
31
+ }
32
+ }
33
+
34
+ module.exports = codegenGenerateOperationsCreator
@@ -0,0 +1,38 @@
1
+ const { generateSchema } = require('../generate')
2
+ const readConfig = require('../read-config')
3
+ const processArgs = require('../services/process-args')
4
+
5
+ const args = processArgs(process.argv.slice(2))
6
+ const operation = args.operation
7
+ const projectId = args.projectId || 'toptal-hub'
8
+
9
+ // eslint-disable-next-line func-style
10
+ async function codegenGenerateSchema() {
11
+ const codegen = await readConfig()
12
+
13
+ switch (operation) {
14
+ case 'generate:schema': {
15
+ for await (const { schema, documents, target } of codegen) {
16
+ await generateSchema({
17
+ schema,
18
+ documents,
19
+ target,
20
+ projectId
21
+ })
22
+ }
23
+ break
24
+ }
25
+ default:
26
+ throw new Error(`❌ Unrecognized operation`)
27
+ }
28
+ }
29
+
30
+ const codegenGenerateSchemaCreator = {
31
+ command: 'generate:schema',
32
+ description: 'generate graphql schemas',
33
+ action: () => {
34
+ codegenGenerateSchema()
35
+ }
36
+ }
37
+
38
+ module.exports = codegenGenerateSchemaCreator
@@ -0,0 +1,4 @@
1
+ const generateOperations = require('./operations')
2
+ const generateSchema = require('./schema')
3
+
4
+ module.exports = { generateOperations, generateSchema }
@@ -0,0 +1,50 @@
1
+ const { generate } = require('@graphql-codegen/cli')
2
+
3
+ const {
4
+ commonOperationTypesConfig,
5
+ autoGenerationComments
6
+ } = require('../shared-config/shared-config')
7
+ const getRelativeFilePath = require('../services/get-relative-file-path')
8
+
9
+ const generateOperations = async ({ schema, documents }) => {
10
+ const docsPath = getRelativeFilePath(documents)
11
+
12
+ // @toptal/modularity-template-graphql/talent -> talent
13
+ const target = schema.split('/').pop()
14
+ // @toptal/modularity-template-graphql/talent -> @toptal/modularity-template-graphql/talent
15
+ const schemaPath = schema.split('/').slice(0, 2).join('/')
16
+
17
+ await generate(
18
+ {
19
+ documents: docsPath,
20
+ config: {
21
+ ...commonOperationTypesConfig
22
+ },
23
+ generates: {
24
+ [schemaPath]: {
25
+ schema: require.resolve(`${schemaPath}/${target}/schema.graphql`),
26
+ preset: 'near-operation-file',
27
+ presetConfig: {
28
+ extension: '.ts',
29
+ baseTypesPath: `~${schema}/schema`
30
+ },
31
+ plugins: [
32
+ 'typescript-operations',
33
+ 'typed-document-node',
34
+ {
35
+ add: {
36
+ content: autoGenerationComments
37
+ }
38
+ }
39
+ ]
40
+ }
41
+ },
42
+ hooks: {
43
+ afterAllFileWrite: 'prettier --write'
44
+ }
45
+ },
46
+ true
47
+ )
48
+ }
49
+
50
+ module.exports = generateOperations
@@ -0,0 +1,51 @@
1
+ const { generate } = require('@graphql-codegen/cli')
2
+ const chalk = require('chalk')
3
+
4
+ const {
5
+ commonSchemaTypesConfig,
6
+ autoGenerationComments
7
+ } = require('../shared-config/shared-config')
8
+ const schemaLoader = require('../services/schema-loader')
9
+
10
+ const generateSchema = async ({ schema, target, projectId }) => {
11
+ const {
12
+ schema: schemaPath,
13
+ destination: destinationPathTs
14
+ } = await schemaLoader(schema, target, projectId)
15
+
16
+ console.log(`ℹ️ Generating [${chalk.green(target)}] schema`)
17
+
18
+ await generate(
19
+ {
20
+ schema: schemaPath,
21
+ generates: {
22
+ // [destinationPath]: {
23
+ // plugins: ['introspection']
24
+ // },
25
+ [destinationPathTs]: {
26
+ schema: schema.startsWith('https://')
27
+ ? schema
28
+ : require.resolve(schemaPath),
29
+ plugins: [
30
+ 'typescript',
31
+ 'typescript-resolvers',
32
+ {
33
+ add: {
34
+ content: autoGenerationComments
35
+ }
36
+ }
37
+ ]
38
+ }
39
+ },
40
+ hooks: {
41
+ afterAllFileWrite: 'prettier --write'
42
+ },
43
+ config: {
44
+ ...commonSchemaTypesConfig
45
+ }
46
+ },
47
+ true
48
+ )
49
+ }
50
+
51
+ module.exports = generateSchema
package/src/index.js ADDED
@@ -0,0 +1,6 @@
1
+ const codegenGenerateSchemaCreator = require('./commands/generate-schema')
2
+ const codegenGenerateOperationsCreator = require('./commands/generate-operations')
3
+
4
+ module.exports = {
5
+ commands: [codegenGenerateSchemaCreator, codegenGenerateOperationsCreator]
6
+ }
@@ -0,0 +1,3 @@
1
+ const readConfig = require('./read-config')
2
+
3
+ module.exports = readConfig
@@ -0,0 +1,21 @@
1
+ const chalk = require('chalk')
2
+
3
+ const getRelativeFilePath = require('../services/get-relative-file-path')
4
+ const toArray = require('../services/to-array')
5
+
6
+ const readConfig = async () => {
7
+ console.log(`🔍 Searching for ${chalk.bold('codegen.json')} file`)
8
+ let codegenConfig
9
+
10
+ const codegenPath = getRelativeFilePath('codegen.json')
11
+
12
+ try {
13
+ codegenConfig = require(codegenPath)
14
+ } catch (error) {
15
+ throw new Error(`❌ There is no ${chalk.bold('codegen.json')} file`)
16
+ }
17
+
18
+ return toArray(codegenConfig)
19
+ }
20
+
21
+ module.exports = readConfig
@@ -0,0 +1,17 @@
1
+ const isSchemaFromBucket = schema => {
2
+ return schema.startsWith('gs://')
3
+ }
4
+
5
+ const isSchemaFromHttp = schema => {
6
+ return schema.startsWith('https://')
7
+ }
8
+
9
+ const isLocalSchema = schema => {
10
+ return !isSchemaFromBucket(schema) && !isSchemaFromHttp(schema)
11
+ }
12
+
13
+ module.exports = {
14
+ isSchemaFromBucket,
15
+ isSchemaFromHttp,
16
+ isLocalSchema
17
+ }
@@ -0,0 +1,5 @@
1
+ const getRelativeFilePath = file => {
2
+ return `${process.cwd()}/${file}`
3
+ }
4
+
5
+ module.exports = getRelativeFilePath
@@ -0,0 +1,20 @@
1
+ const { Storage } = require('@google-cloud/storage')
2
+
3
+ const getRelativeFilePath = require('../services/get-relative-file-path')
4
+ const parseGSURI = require('./parse-gs-uri')
5
+
6
+ const gsSchemaLoader = async (projectId, source, destination) => {
7
+ // Creates a Google Storage Client
8
+ const storage = new Storage({ projectId })
9
+
10
+ const { bucketName, fileName } = parseGSURI(source)
11
+
12
+ await storage
13
+ .bucket(bucketName)
14
+ .file(fileName)
15
+ .download({
16
+ destination: getRelativeFilePath(destination)
17
+ })
18
+ }
19
+
20
+ module.exports = gsSchemaLoader
@@ -0,0 +1,16 @@
1
+ const parseGSURI = source => {
2
+ // Eg: Source Parts -> ['gqlgw-introspection', 'staging_talent_schema', '.graphql']
3
+ const sourceParts = source
4
+ .split(/(?:gs:\/\/)([a-zA-Z-]*)(?:\/)([a-zA-Z-_]*)([.a-zA-Z]*)/)
5
+ .filter(Boolean)
6
+
7
+ const bucketName = sourceParts[0]
8
+ const fileName = `${sourceParts.slice(-2).join('')}`
9
+
10
+ return {
11
+ bucketName,
12
+ fileName
13
+ }
14
+ }
15
+
16
+ module.exports = parseGSURI
@@ -0,0 +1,25 @@
1
+ const processArgs = args => {
2
+ const obj = {}
3
+
4
+ args.forEach(arg => {
5
+ if (arg.includes('generate:')) {
6
+ obj.operation = arg
7
+
8
+ return
9
+ }
10
+
11
+ // Eg: Parts -> ['projectId', 'toptal-hub']
12
+ const parts = arg
13
+ .split(/(?:--)([a-zA-Z]*)(?:=)([a-zA-Z-]*)/i)
14
+ .filter(Boolean)
15
+
16
+ const key = parts[0]
17
+ const value = parts[1]
18
+
19
+ obj[key] = value
20
+ })
21
+
22
+ return obj
23
+ }
24
+
25
+ module.exports = processArgs
@@ -0,0 +1,35 @@
1
+ const { existsSync, copyFileSync } = require('fs')
2
+
3
+ const gsSchemaLoader = require('./gs-schema-loader')
4
+ const getRelativeFilePath = require('./get-relative-file-path')
5
+ const {
6
+ isSchemaFromBucket,
7
+ isLocalSchema,
8
+ isSchemaFromHttp
9
+ } = require('./detect-schema-source')
10
+
11
+ module.exports = async (schema, target, projectId) => {
12
+ const schemaPath = isSchemaFromHttp(schema)
13
+ ? schema
14
+ : getRelativeFilePath(`${target}/schema.graphql`)
15
+
16
+ if (isSchemaFromBucket(schema)) {
17
+ await gsSchemaLoader(projectId, schema, `${target}/schema.graphql`)
18
+ }
19
+
20
+ if (isLocalSchema(schema)) {
21
+ if (!existsSync(schema)) {
22
+ console.log(`Couldn't find a source schema file ${schema}`)
23
+ process.exit(1)
24
+ }
25
+ copyFileSync(schema, `${target}/schema.graphql`)
26
+ }
27
+
28
+ // const destinationPathJson = getRelativeFilePath(`${target}/schema.json`)
29
+ const destinationPathTs = getRelativeFilePath(`${target}/schema.ts`)
30
+
31
+ return {
32
+ schema: schemaPath,
33
+ destination: destinationPathTs
34
+ }
35
+ }
@@ -0,0 +1,9 @@
1
+ const toArray = value => {
2
+ if (Array.isArray(value)) {
3
+ return value
4
+ }
5
+
6
+ return [value]
7
+ }
8
+
9
+ module.exports = toArray
@@ -0,0 +1,3 @@
1
+ const sharedConfig = require('./shared-config')
2
+
3
+ module.exports = sharedConfig
@@ -0,0 +1,50 @@
1
+ const commonTypesConfig = {
2
+ skipTypename: true,
3
+ exportFragmentSpreadSubTypes: true,
4
+ avoidOptionals: {
5
+ field: true,
6
+ inputValue: false,
7
+ object: true
8
+ },
9
+ namingConvention: {
10
+ enumValues: 'upper-case#upperCase'
11
+ },
12
+ scalars: {
13
+ BigDecimal: 'string',
14
+ Date: 'string',
15
+ ISO8601Date: 'string',
16
+ ISO8601DateTime: 'string',
17
+ JobTypeEnum: 'string',
18
+ JSON: 'string',
19
+ PageSize: 'number',
20
+ Time: 'string',
21
+ TimeOfDay: 'string',
22
+ Upload: 'unknown'
23
+ }
24
+ }
25
+
26
+ const commonSchemaTypesConfig = {
27
+ ...commonTypesConfig,
28
+ resolverTypeWrapperSignature:
29
+ '(parent: unknown, args: any) => ({ [K in keyof Partial<T>]: TypeOrMockList<T[K]> }) | null',
30
+ allowParentTypeOverride: false,
31
+ noSchemaStitching: true,
32
+ optionalInfoArgument: true,
33
+ optionalResolveType: true
34
+ }
35
+
36
+ const commonOperationTypesConfig = {
37
+ ...commonTypesConfig,
38
+ dedupeFragments: true
39
+ }
40
+
41
+ const autoGenerationComments = [
42
+ '/* eslint-disable */',
43
+ '/* ⚠️ THIS IS AN AUTOGENERATED FILE, DO NOT EDIT ⚠️ */'
44
+ ]
45
+
46
+ module.exports = {
47
+ commonOperationTypesConfig,
48
+ commonSchemaTypesConfig,
49
+ autoGenerationComments
50
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist-package",
5
+ "noEmit": false,
6
+ "paths": {}
7
+ }
8
+ }