@stepzen/transpiler 0.0.33 → 0.0.36

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.
Files changed (66) hide show
  1. package/LICENSE +1 -1
  2. package/lib/actions/configure.js +15 -14
  3. package/lib/actions/configure.js.map +1 -0
  4. package/lib/actions/lint.js +6 -5
  5. package/lib/actions/lint.js.map +1 -0
  6. package/lib/actions/merge.d.ts +2 -0
  7. package/lib/actions/merge.js +80 -70
  8. package/lib/actions/merge.js.map +1 -0
  9. package/lib/actions/print.d.ts +1 -1
  10. package/lib/actions/print.js +2 -1
  11. package/lib/actions/print.js.map +1 -0
  12. package/lib/actions/stitch.js +14 -13
  13. package/lib/actions/stitch.js.map +1 -0
  14. package/lib/actions/transpile.js +7 -6
  15. package/lib/actions/transpile.js.map +1 -0
  16. package/lib/actions/validate.js +4 -3
  17. package/lib/actions/validate.js.map +1 -0
  18. package/lib/index.d.ts +7 -1
  19. package/lib/index.js +16 -16
  20. package/lib/index.js.map +1 -0
  21. package/lib/mutations/config/envvars.js +3 -2
  22. package/lib/mutations/config/envvars.js.map +1 -0
  23. package/lib/mutations/config/index.js +1 -0
  24. package/lib/mutations/config/index.js.map +1 -0
  25. package/lib/utils/constants.js +2 -1
  26. package/lib/utils/constants.js.map +1 -0
  27. package/lib/utils/dedupe-query-and-mutation-types.js +7 -6
  28. package/lib/utils/dedupe-query-and-mutation-types.js.map +1 -0
  29. package/lib/utils/ensure-query-and-mutation-types.d.ts +1 -1
  30. package/lib/utils/ensure-query-and-mutation-types.js +7 -6
  31. package/lib/utils/ensure-query-and-mutation-types.js.map +1 -0
  32. package/lib/utils/graphql-helpers.js +1 -0
  33. package/lib/utils/graphql-helpers.js.map +1 -0
  34. package/lib/utils/merge-helpers.d.ts +2 -1
  35. package/lib/utils/merge-helpers.js +16 -5
  36. package/lib/utils/merge-helpers.js.map +1 -0
  37. package/lib/utils/set-files-in-sdl.js +9 -8
  38. package/lib/utils/set-files-in-sdl.js.map +1 -0
  39. package/lib/utils/strip-empty-queries-and-mutations.d.ts +1 -1
  40. package/lib/utils/strip-empty-queries-and-mutations.js +7 -6
  41. package/lib/utils/strip-empty-queries-and-mutations.js.map +1 -0
  42. package/lib/validators/config-exists/index.d.ts +1 -1
  43. package/lib/validators/config-exists/index.js +11 -10
  44. package/lib/validators/config-exists/index.js.map +1 -0
  45. package/lib/validators/index.js +1 -0
  46. package/lib/validators/index.js.map +1 -0
  47. package/package.json +24 -16
  48. package/src/actions/configure.ts +120 -0
  49. package/src/actions/lint.ts +45 -0
  50. package/src/actions/merge.ts +202 -0
  51. package/src/actions/print.ts +8 -0
  52. package/src/actions/stitch.ts +105 -0
  53. package/src/actions/transpile.ts +70 -0
  54. package/src/actions/validate.ts +54 -0
  55. package/src/index.ts +7 -0
  56. package/src/mutations/config/envvars.ts +17 -0
  57. package/src/mutations/config/index.ts +3 -0
  58. package/src/utils/constants.ts +16 -0
  59. package/src/utils/dedupe-query-and-mutation-types.ts +74 -0
  60. package/src/utils/ensure-query-and-mutation-types.ts +52 -0
  61. package/src/utils/graphql-helpers.ts +3 -0
  62. package/src/utils/merge-helpers.ts +187 -0
  63. package/src/utils/set-files-in-sdl.ts +40 -0
  64. package/src/utils/strip-empty-queries-and-mutations.ts +43 -0
  65. package/src/validators/config-exists/index.ts +54 -0
  66. package/src/validators/index.ts +3 -0
@@ -0,0 +1,202 @@
1
+ import * as fs from 'fs-extra'
2
+ import * as glob from 'glob'
3
+ import {mergeTypeDefs} from '@graphql-tools/merge'
4
+ import {parse, visit} from 'graphql'
5
+ import * as os from 'os'
6
+ import * as path from 'path'
7
+
8
+ import {
9
+ dedupeTempFolder,
10
+ findQueryMutationInSchema,
11
+ findTypeInSchema,
12
+ getSchema,
13
+ mergeQueryMutationIntoSchema,
14
+ removeQueryMutationFromSchema,
15
+ mergeTypeIntoSchema,
16
+ removeTypeFromSchema,
17
+ getConfiguration,
18
+ findNextAvailableSubfolder,
19
+ } from '../utils/merge-helpers'
20
+ import print from './print'
21
+ import setFilesInSDL from '../utils/set-files-in-sdl'
22
+ import stripEmptyQueriesAndMutations from '../utils/strip-empty-queries-and-mutations'
23
+
24
+ const BLANK_INDEX_TEMPLATE = `
25
+ schema @sdl(files: []) {
26
+ query: Query
27
+ }
28
+
29
+ type Query {
30
+ __query: String
31
+ }
32
+ `.trim()
33
+
34
+ export default async (
35
+ original: string,
36
+ imported: {
37
+ name: string
38
+ source: string
39
+ },
40
+ options: {
41
+ answers: any
42
+ output: string | null
43
+ silent: boolean
44
+ /** when `false` merge only covers config files and updates @sdl directrive */
45
+ mergeTypes?: boolean
46
+ } = {
47
+ answers: {},
48
+ output: null,
49
+ silent: false,
50
+ mergeTypes: true,
51
+ },
52
+ ) => {
53
+ // Make sure there is an output directory
54
+ if (!options.answers) options.answers = {}
55
+ if (!options.output)
56
+ options.output = path.join(os.tmpdir(), `stepzen-tmp-${Date.now()}`)
57
+ if (!options.silent) options.silent = false
58
+ if (options.mergeTypes === undefined) options.mergeTypes = true
59
+
60
+ // To stop things like
61
+ // C:\Users\Darren\AppData\Local\Temp\stepzen-tmp-config-1638293497187\C:\Users\Darren\AppData\Local\Temp\stepzen-tmp-1638293496286
62
+ options.output = dedupeTempFolder(options.output)
63
+
64
+ // Ensure original, importing and output directories exist
65
+ if (!fs.existsSync(original))
66
+ throw new Error(`Cannot find original directory ${original}`)
67
+ if (!fs.existsSync(imported.source))
68
+ throw new Error(`Cannot find imported source directory ${imported.source}`)
69
+ fs.ensureDirSync(options.output)
70
+
71
+ // Copy the original into the output.
72
+ fs.copySync(original, options.output)
73
+
74
+ // Ensure an index.graphql exists
75
+ const outputIndex = path.join(options.output, 'index.graphql')
76
+ if (!fs.existsSync(outputIndex)) {
77
+ fs.writeFileSync(outputIndex, BLANK_INDEX_TEMPLATE)
78
+ }
79
+
80
+ const targetSubfolder = findNextAvailableSubfolder(original, imported.name)
81
+
82
+ if (options.mergeTypes) {
83
+ const merged = mergeTypeDefs([
84
+ await getSchema(options.output),
85
+ await getSchema(imported.source),
86
+ ])
87
+
88
+ let queries: any[] = []
89
+ let mutations: any[] = []
90
+ let types: any[] = []
91
+
92
+ visit(merged, {
93
+ ObjectTypeDefinition(node) {
94
+ if (node.name.value === 'Query') queries = queries.concat(node.fields)
95
+ else if (node.name.value === 'Mutation')
96
+ mutations = mutations.concat(node.fields)
97
+ else types = types.concat(node)
98
+ },
99
+ })
100
+
101
+ const details = {
102
+ original: glob.sync('**/*.graphql', {cwd: options.output}).map(file => {
103
+ const content = fs.readFileSync(`${options.output}/${file}`, 'utf8')
104
+ return {ast: parse(content), file}
105
+ }),
106
+ imported: glob.sync('**/*.graphql', {cwd: imported.source}).map(file => {
107
+ const content = fs.readFileSync(`${imported.source}/${file}`, 'utf8')
108
+ return {ast: parse(content), file}
109
+ }),
110
+ }
111
+
112
+ // Merge Queries and Mutations
113
+ for (const type of [...queries, ...mutations]) {
114
+ const isInOriginal = findQueryMutationInSchema(
115
+ type.name.value,
116
+ details.original,
117
+ )
118
+ const isInImported = findQueryMutationInSchema(
119
+ type.name.value,
120
+ details.imported,
121
+ )
122
+
123
+ if (isInOriginal && isInImported) {
124
+ details.original = mergeQueryMutationIntoSchema(type, details.original)
125
+ details.imported = removeQueryMutationFromSchema(
126
+ type.name.value,
127
+ details.imported,
128
+ )
129
+ }
130
+ }
131
+
132
+ // Merge Types
133
+ for (const type of types) {
134
+ const isInOriginal = findTypeInSchema(type.name.value, details.original)
135
+ const isInImported = findTypeInSchema(type.name.value, details.imported)
136
+
137
+ if (isInOriginal && isInImported) {
138
+ details.original = mergeTypeIntoSchema(type, details.original)
139
+ details.imported = removeTypeFromSchema(
140
+ type.name.value,
141
+ details.imported,
142
+ )
143
+ }
144
+ }
145
+
146
+ // Clean up the files. Remove empty Query / Mutation types, filter out now-empty files
147
+ const cleaned = {
148
+ original: details.original
149
+ .map(item => ({
150
+ ...item,
151
+ ast: stripEmptyQueriesAndMutations(item.ast),
152
+ }))
153
+ .filter(item => item.ast.definitions.length > 0),
154
+ imported: details.imported
155
+ .map(item => ({
156
+ ...item,
157
+ ast: stripEmptyQueriesAndMutations(item.ast),
158
+ }))
159
+ .filter(item => item.ast.definitions.length > 0),
160
+ }
161
+
162
+ // Write the files to the output directory
163
+ cleaned.original.forEach((a: any) => {
164
+ // eslint-disable-next-line
165
+ const file = path.join(options.output as any, a.file)
166
+ const deduped = dedupeTempFolder(file)
167
+
168
+ fs.ensureFileSync(deduped)
169
+ fs.writeFileSync(deduped, print(a.ast))
170
+ })
171
+
172
+ cleaned.imported.forEach(a => {
173
+ // eslint-disable-next-line
174
+ const file = path.join(options.output as any, targetSubfolder, a.file)
175
+ const deduped = dedupeTempFolder(file)
176
+
177
+ fs.ensureFileSync(deduped)
178
+ fs.writeFileSync(deduped, print(a.ast))
179
+ })
180
+ } else {
181
+ // Write the files to the output directory
182
+ fs.copySync(original, options.output)
183
+ fs.copySync(imported.source, path.join(options.output, targetSubfolder))
184
+ }
185
+
186
+ // Make sure all files are referenced in @sdl
187
+ setFilesInSDL(options.output)
188
+
189
+ // Generate configuration
190
+ const config = await getConfiguration(
191
+ [options.output, imported.source],
192
+ options.silent,
193
+ options.answers,
194
+ )
195
+ if (config) {
196
+ const configFile = path.join(options.output, 'config.yaml')
197
+ fs.writeFileSync(configFile, config as string)
198
+ }
199
+
200
+ // Return a merged schema!
201
+ return options.output
202
+ }
@@ -0,0 +1,8 @@
1
+ import {DocumentNode, print} from 'graphql'
2
+ import * as prettier from 'prettier'
3
+
4
+ export default (ast: DocumentNode): string => {
5
+ const printed = print(ast)
6
+ const formatted = prettier.format(printed, {parser: 'graphql'})
7
+ return formatted
8
+ }
@@ -0,0 +1,105 @@
1
+ import {clone, cloneDeep} from 'lodash'
2
+ import {parse, print, visit} from 'graphql'
3
+ import * as fs from 'fs-extra'
4
+ import * as glob from 'glob'
5
+ import * as os from 'os'
6
+ import * as path from 'path'
7
+ import * as prettier from 'prettier'
8
+
9
+ import dedupeQueryAndMutationTypes from '../utils/dedupe-query-and-mutation-types'
10
+
11
+ export default (
12
+ source: string,
13
+ output: string = path.join(os.tmpdir(), `stepzen-tmp-${Date.now()}`),
14
+ ) => {
15
+ // Ensure source and output directories exist
16
+ if (!fs.existsSync(source))
17
+ throw new Error(`Cannot find source directory ${source}`)
18
+ fs.ensureDirSync(output)
19
+
20
+ // Get a list of files.
21
+ let ast: any
22
+ let files: string[] = []
23
+
24
+ // If there's an index.graphQL - get files argument in @sdl directive
25
+ const sourceIndex = path.join(source, 'index.graphql')
26
+
27
+ if (fs.existsSync(sourceIndex)) {
28
+ const index = fs.readFileSync(sourceIndex, 'utf8')
29
+ ast = parse(index)
30
+ ast = visit(ast, {
31
+ Directive(node) {
32
+ if (node.name.value === 'sdl') {
33
+ const list = node.arguments?.find(
34
+ (arg: any) => arg.name.value === 'files',
35
+ )
36
+ // If we find a 'files' argument, we copy the list, and remove the argument
37
+ if (list) {
38
+ const copy: any = clone(list.value)
39
+ for (const file of copy.values) {
40
+ files = files.concat(file.value)
41
+ }
42
+ const mutated: any = cloneDeep(node)
43
+ mutated.arguments = mutated.arguments.filter(
44
+ (arg: any) => arg.name.value !== 'files',
45
+ )
46
+ return mutated
47
+ }
48
+ }
49
+ },
50
+ })
51
+ } else {
52
+ const content = glob
53
+ .sync('**/*.graphql', {cwd: source})
54
+ .map((file: string) => {
55
+ const graphql = fs.readFileSync(`${source}/${file}`, 'utf8')
56
+ return graphql
57
+ })
58
+ ast = parse(content.join(os.EOL))
59
+ }
60
+
61
+ // Strip @sdl directive
62
+ ast = visit(ast, {
63
+ Directive(node) {
64
+ if (node.name.value === 'sdl') return null
65
+ },
66
+ })
67
+
68
+ // Check all the files exist
69
+ for (const file of files) {
70
+ const find = path.join(source, file)
71
+ if (!fs.existsSync(find)) {
72
+ throw new Error(`Cannot find file ${find}`)
73
+ }
74
+ }
75
+
76
+ // Get all the files and stitch
77
+ const printed = print(ast)
78
+ let stitched = `${printed}${os.EOL}`
79
+ for (const file of files) {
80
+ const find = path.join(source, file)
81
+ const content = fs.readFileSync(find, 'utf8')
82
+ stitched += `${content}${os.EOL}`
83
+ }
84
+
85
+ // Dedupe Query and Mutation types
86
+ stitched = dedupeQueryAndMutationTypes(stitched)
87
+
88
+ // Format
89
+ stitched = prettier.format(stitched, {parser: 'graphql'})
90
+
91
+ // Write to output folder
92
+ const outputIndex = path.join(output, 'index.graphql')
93
+ fs.writeFileSync(outputIndex, stitched)
94
+
95
+ // Copy config if exists, too
96
+ const sourceConfig = path.join(source, 'config.yaml')
97
+ const outputConfig = path.join(output, 'config.yaml')
98
+
99
+ if (fs.existsSync(sourceConfig)) {
100
+ fs.copyFileSync(sourceConfig, outputConfig)
101
+ }
102
+
103
+ // Return output folder
104
+ return output
105
+ }
@@ -0,0 +1,70 @@
1
+ import {DocumentNode, parse} from 'graphql'
2
+ import * as dotenv from 'dotenv'
3
+ import * as fs from 'fs'
4
+ import * as glob from 'glob'
5
+ import * as path from 'path'
6
+
7
+ import print from '../actions/print'
8
+ import stitch from '../actions/stitch'
9
+ import configure from './configure'
10
+
11
+ import configMutations from '../mutations/config'
12
+
13
+ export default async (source: string) => {
14
+ // Ensure source and output directories exist
15
+ if (!fs.existsSync(source)) {
16
+ throw new Error(`Cannot find source directory ${source}`)
17
+ }
18
+
19
+ // Load env vars, from working and source directories
20
+ dotenv.config()
21
+ dotenv.config({path: path.resolve(source, '.env')})
22
+
23
+ // State of whether we've transpiled or not
24
+ let transpiledConfig = false
25
+ let transpiledGraphQL = false
26
+
27
+ // See whether to transpile config
28
+ let config: any = await configure(source, true)
29
+ const configCopy = config
30
+
31
+ if (config) {
32
+ for await (const mutation of configMutations) {
33
+ config = await mutation(config)
34
+ }
35
+ }
36
+
37
+ if (configCopy !== config) {
38
+ transpiledConfig = true
39
+ }
40
+
41
+ // See whether to transpile GraphQL
42
+ const graphqlFiles = glob.sync('**/*.graphql', {cwd: source})
43
+ let original: any = ''
44
+ let mutated: any = ''
45
+
46
+ if (graphqlFiles.length > 0) {
47
+ const stitched = await stitch(source)
48
+
49
+ const index = path.join(stitched, 'index.graphql')
50
+
51
+ const graphql = fs.readFileSync(index, 'utf8')
52
+
53
+ let ast: DocumentNode = parse(graphql)
54
+ original = parse(graphql)
55
+
56
+ // you can transpile schema ast here
57
+
58
+ mutated = print(ast)
59
+
60
+ if (print(original) !== mutated) {
61
+ transpiledGraphQL = true
62
+ }
63
+ }
64
+
65
+ return {
66
+ config,
67
+ schema: mutated,
68
+ transpiled: transpiledConfig || transpiledGraphQL,
69
+ }
70
+ }
@@ -0,0 +1,54 @@
1
+ import {buildASTSchema, DocumentNode, parse, print} from 'graphql'
2
+ import * as fs from 'fs-extra'
3
+ import * as os from 'os'
4
+ import * as path from 'path'
5
+
6
+ import ensureQueryAndMutationTypes from '../utils/ensure-query-and-mutation-types'
7
+ import {EXPERIMENTAL_EXTENSIONS} from '../utils/constants'
8
+ import validators from '../validators'
9
+
10
+ export default (
11
+ source: string,
12
+ options: {
13
+ extensions: string
14
+ } = {
15
+ extensions: '',
16
+ },
17
+ ) => {
18
+ // Ensure source and output directories exist
19
+ if (!fs.existsSync(source)) {
20
+ throw new Error(`Cannot find source directory ${source}`)
21
+ }
22
+
23
+ // Ensure index.graphql exists
24
+ if (!fs.existsSync(`${source}/index.graphql`)) {
25
+ throw new Error(`Cannot find index.graphql in ${source}`)
26
+ }
27
+
28
+ // Get the index.graphql file
29
+ const file = path.join(source, 'index.graphql')
30
+ const index = fs.readFileSync(file, 'utf8')
31
+
32
+ // Parse
33
+ let ast: DocumentNode = parse(
34
+ `${EXPERIMENTAL_EXTENSIONS}${os.EOL}${options.extensions}${os.EOL}${index}`,
35
+ {
36
+ noLocation: true,
37
+ },
38
+ )
39
+
40
+ // We make an exception for 'no type Query' or 'no type Mutation'
41
+ ast = ensureQueryAndMutationTypes(ast)
42
+
43
+ // Build and print, to validate
44
+ buildASTSchema(ast)
45
+ print(ast)
46
+
47
+ // Custom validators
48
+ for (const validator of validators) {
49
+ validator(ast, source)
50
+ }
51
+
52
+ // Return true
53
+ return true
54
+ }
package/src/index.ts ADDED
@@ -0,0 +1,7 @@
1
+ export {default as configure} from './actions/configure'
2
+ export {default as lint} from './actions/lint'
3
+ export {default as merge} from './actions/merge'
4
+ export {default as print} from './actions/print'
5
+ export {default as stitch} from './actions/stitch'
6
+ export {default as transpile} from './actions/transpile'
7
+ export {default as validate} from './actions/validate'
@@ -0,0 +1,17 @@
1
+ export default async (config: string): Promise<string> => {
2
+ const envvars = Object.keys(process.env)
3
+ .filter(key => {
4
+ return key.startsWith(`STEPZEN_`)
5
+ })
6
+ .reduce((obj: any, key) => {
7
+ obj[key] = process.env[key]
8
+ return obj
9
+ }, {})
10
+
11
+ for (const [key, value] of Object.entries(envvars)) {
12
+ const regex = new RegExp(key, 'g')
13
+ config = config.replace(regex, `${value}`)
14
+ }
15
+
16
+ return config
17
+ }
@@ -0,0 +1,3 @@
1
+ import envvars from './envvars'
2
+
3
+ export default [envvars]
@@ -0,0 +1,16 @@
1
+ import * as dotenv from 'dotenv'
2
+
3
+ dotenv.config()
4
+
5
+ const {STEPZEN_DOMAIN: ENV_VAR_STEPZEN_DOMAIN} = process.env
6
+
7
+ export const EXPERIMENTAL_EXTENSIONS = `
8
+ directive @experimental(
9
+ debug: Boolean
10
+ function: String
11
+ mock: String
12
+ setter: String
13
+ ) on OBJECT | FIELD_DEFINITION
14
+ `
15
+
16
+ export const STEPZEN_DOMAIN = ENV_VAR_STEPZEN_DOMAIN || 'stepzen.io'
@@ -0,0 +1,74 @@
1
+ import {DocumentNode, parse, print, visit} from 'graphql'
2
+
3
+ import {cloneDeep} from '../utils/graphql-helpers'
4
+
5
+ export default (schema: string): string => {
6
+ let ast: DocumentNode = parse(schema)
7
+
8
+ let queries: any = []
9
+ let mutations: any = []
10
+
11
+ // Loop through the AST and find Query and Mutation types.
12
+ // Store their fields.
13
+ // Return null to remove it from the tree
14
+ ast = visit(ast, {
15
+ ObjectTypeDefinition: {
16
+ enter(node) {
17
+ if (node.name.kind === 'Name' && node.name.value === 'Query') {
18
+ queries = queries.concat(node.fields)
19
+ return null
20
+ }
21
+ if (node.name.kind === 'Name' && node.name.value === 'Mutation') {
22
+ mutations = mutations.concat(node.fields)
23
+ return null
24
+ }
25
+ },
26
+ },
27
+ })
28
+
29
+ // This is the template for the new Query type
30
+ const queryTemplate = {
31
+ kind: 'ObjectTypeDefinition',
32
+ description: undefined,
33
+ name: {kind: 'Name', value: 'Query'},
34
+ interfaces: [],
35
+ directives: [],
36
+ fields: [],
37
+ }
38
+
39
+ // This is the template for the new Mutation type
40
+ const mutationTemplate = {
41
+ kind: 'ObjectTypeDefinition',
42
+ description: undefined,
43
+ name: {kind: 'Name', value: 'Mutation'},
44
+ interfaces: [],
45
+ directives: [],
46
+ fields: [],
47
+ }
48
+
49
+ // If there are queries, add a single Query type
50
+ // with all the concatenated queries
51
+ if (queries.length > 0) {
52
+ const mutated: any = cloneDeep(ast)
53
+ const query = {
54
+ ...queryTemplate,
55
+ fields: queries,
56
+ }
57
+ mutated.definitions.push(query)
58
+ ast = mutated
59
+ }
60
+
61
+ // If there are mutations, add a single Query type
62
+ // with all the concatenated mutations
63
+ if (mutations.length > 0) {
64
+ const mutated: any = cloneDeep(ast)
65
+ const query = {
66
+ ...mutationTemplate,
67
+ fields: mutations,
68
+ }
69
+ mutated.definitions.push(query)
70
+ ast = mutated
71
+ }
72
+
73
+ return print(ast)
74
+ }
@@ -0,0 +1,52 @@
1
+ import {DocumentNode, visit} from 'graphql'
2
+
3
+ import {cloneDeep} from '../utils/graphql-helpers'
4
+
5
+ export default (ast: DocumentNode): DocumentNode => {
6
+ let queries: any = []
7
+ let mutations: any = []
8
+
9
+ // Loop through the AST and find Query and Mutation types.
10
+ ast = visit(ast, {
11
+ ObjectTypeDefinition: {
12
+ enter(node) {
13
+ if (node.name.kind === 'Name' && node.name.value === 'Query') {
14
+ queries = queries.concat(node.fields)
15
+ }
16
+ if (node.name.kind === 'Name' && node.name.value === 'Mutation') {
17
+ mutations = mutations.concat(node.fields)
18
+ }
19
+ },
20
+ },
21
+ })
22
+
23
+ // If there are no queries, add a single empty Query type
24
+ if (queries.length === 0) {
25
+ const mutated: any = cloneDeep(ast)
26
+ mutated.definitions.push({
27
+ kind: 'ObjectTypeDefinition',
28
+ description: undefined,
29
+ name: {kind: 'Name', value: 'Query'},
30
+ interfaces: [],
31
+ directives: [],
32
+ fields: [],
33
+ })
34
+ ast = mutated
35
+ }
36
+
37
+ // If there are mutations, add a single empty Mutation type
38
+ if (mutations.length === 0) {
39
+ const mutated: any = cloneDeep(ast)
40
+ mutated.definitions.push({
41
+ kind: 'ObjectTypeDefinition',
42
+ description: undefined,
43
+ name: {kind: 'Name', value: 'Mutation'},
44
+ interfaces: [],
45
+ directives: [],
46
+ fields: [],
47
+ })
48
+ ast = mutated
49
+ }
50
+
51
+ return ast
52
+ }
@@ -0,0 +1,3 @@
1
+ export const cloneDeep = (obj: any) => {
2
+ return JSON.parse(JSON.stringify(obj))
3
+ }