Skip to content

Commit 2a71222

Browse files
committed
feat: implement bundle, bindings, save config
1 parent 33101f2 commit 2a71222

File tree

2 files changed

+276
-0
lines changed

2 files changed

+276
-0
lines changed

src/index.ts

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { Context, CommandObject } from 'graphql-cli' // Types only
2+
import { CommandModule } from 'yargs' // Types only
3+
import { GraphQLProjectConfig } from 'graphql-config' // Types only
4+
5+
import chalk from 'chalk'
6+
import { has, merge } from 'lodash'
7+
import * as path from 'path'
8+
9+
import { getProjectConfig, processBundle, processBindings, saveConfig } from './prepare'
10+
11+
const command: CommandObject = {
12+
command: 'prepare',
13+
describe: 'Bundle schemas and generate bindings',
14+
15+
builder: argv => {
16+
return argv.options({
17+
output: {
18+
alias: 'o',
19+
describe: 'Output folder',
20+
type: 'string'
21+
},
22+
save: {
23+
alias: 's',
24+
describe: 'Save settings to config file',
25+
type: 'boolean',
26+
default: 'false'
27+
},
28+
bundle: {
29+
describe: 'Process schema imports',
30+
type: 'boolean',
31+
default: 'false'
32+
},
33+
bindings: {
34+
describe: 'Generate bindings',
35+
type: 'boolean',
36+
default: 'false'
37+
},
38+
generator: {
39+
alias: 'g',
40+
describe: 'Generator used to generate bindings',
41+
type: 'string'
42+
}
43+
})
44+
},
45+
46+
handler: (context: Context, argv) => {
47+
if (!argv.bundle && !argv.bindings) {
48+
argv.bundle = argv.bindings = true
49+
}
50+
51+
// Get projects
52+
const projects: { [name: string]: GraphQLProjectConfig } = getProjectConfig(argv.project, context)
53+
54+
// Process each project
55+
for (const projectName of Object.keys(projects)) {
56+
const project = projects[projectName]
57+
58+
let bundleExtensionConfig: { bundle: string } | undefined
59+
let bindingExtensionConfig: { binding: { output: string, generator: string } } | undefined
60+
61+
if (argv.bundle) {
62+
if (argv.project || (!argv.project && has(project.config, 'extensions.bundle'))) {
63+
context.spinner.start(`Processing schema imports for project ${chalk.green(projectName)}...`)
64+
bundleExtensionConfig = processBundle(projectName, project, { output: argv.output })
65+
merge(project.extensions, bundleExtensionConfig)
66+
context.spinner.succeed(
67+
`Bundled schema for project ${chalk.green(projectName)} written to ${chalk.green(
68+
bundleExtensionConfig.bundle
69+
)}`
70+
)
71+
} else {
72+
context.spinner.info(`Bundling not configured for project ${chalk.green(projectName)}. Skipping`)
73+
}
74+
}
75+
76+
if (argv.bindings) {
77+
if (argv.project || (!argv.project && has(project.config, 'extensions.binding'))) {
78+
context.spinner.start(`Generating bindings for project ${chalk.green(projectName)}...`)
79+
bindingExtensionConfig = processBindings(projectName, project, {
80+
output: argv.output,
81+
generator: argv.generator,
82+
schemaPath: bundleExtensionConfig ? bundleExtensionConfig.bundle : undefined
83+
})
84+
merge(project.extensions, bindingExtensionConfig)
85+
context.spinner.succeed(
86+
`Bindings for project ${chalk.green(projectName)} written to ${chalk.green(bindingExtensionConfig.binding.output)}`
87+
)
88+
} else {
89+
context.spinner.info(`Binding not configured for project ${chalk.green(projectName)}. Skipping`)
90+
}
91+
}
92+
93+
if (argv.save) {
94+
context.spinner.start(`Saving configuration for project ${chalk.green(projectName)} to ${chalk.green(path.basename(context.getConfig().configPath))}...`)
95+
saveConfig(context, project, projectName)
96+
context.spinner.succeed(`Configuration for project ${chalk.green(projectName)} saved to ${chalk.green(path.basename(context.getConfig().configPath))}`)
97+
}
98+
}
99+
}
100+
}
101+
102+
export = command

src/prepare.ts

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
import * as fs from 'fs'
2+
import { Context } from 'graphql-cli'
3+
import { GraphQLProjectConfig } from 'graphql-config'
4+
import { importSchema } from 'graphql-import'
5+
import { generateCode } from 'graphql-static-binding'
6+
import { get, has, merge } from 'lodash'
7+
import * as path from 'path'
8+
9+
export function getProjectConfig(
10+
project: string,
11+
context: Context
12+
): { [name: string]: GraphQLProjectConfig } {
13+
let projects: { [name: string]: GraphQLProjectConfig } | undefined
14+
if (project) {
15+
if (Array.isArray(project)) {
16+
projects = {}
17+
project.map((p: string) => merge(projects, { [p]: context.getConfig().getProjectConfig(p) }))
18+
} else {
19+
// Single project mode
20+
projects = { [project]: context.getProjectConfig() }
21+
}
22+
} else {
23+
// Process all projects
24+
projects = context.getConfig().getProjects()
25+
}
26+
27+
if (!projects) {
28+
throw new Error('No projects defined in config file')
29+
}
30+
31+
return projects
32+
}
33+
34+
export function processBundle(
35+
projectName: string,
36+
project: GraphQLProjectConfig,
37+
args: { output: string }
38+
): { bundle: string } {
39+
const outputPath: string = determineOutputPath(projectName, project, args.output, 'graphql', 'bundle')
40+
const schemaPath: string = determineSchemaPath(projectName, project)
41+
42+
const finalSchema = importSchema(schemaPath)
43+
44+
fs.writeFileSync(outputPath, finalSchema, { flag: 'w' })
45+
46+
return { bundle: outputPath }
47+
}
48+
49+
export function processBindings(
50+
projectName: string,
51+
project: GraphQLProjectConfig,
52+
args: { output: string; generator: string; schemaPath: string | undefined }
53+
): { binding: { output: string, generator: string } } {
54+
const generator: string = determineGenerator(project, args.generator)
55+
// TODO: This does not support custom generators
56+
const extension = generator.endsWith('ts') ? 'ts' : 'js'
57+
const outputPath: string = determineOutputPath(
58+
projectName,
59+
project,
60+
args.output,
61+
extension,
62+
'binding.output'
63+
)
64+
const schema: string = determineInputSchema(args.schemaPath, project)
65+
66+
const schemaContents: string = fs.readFileSync(schema, 'utf-8')
67+
const finalSchema: string = generateCode(schemaContents, generator)
68+
fs.writeFileSync(outputPath, finalSchema, { flag: 'w' })
69+
70+
return { binding: { output: outputPath, generator } }
71+
}
72+
73+
export function saveConfig(context, project, projectName) {
74+
const config = context.getConfig()
75+
config.saveConfig(project.config, projectName)
76+
}
77+
78+
/**
79+
* Determine input schema path for binding. It uses the resulting schema from bundling (if available),
80+
* then looks at bundle extension (in case bundling ran before), then takes the project schemaPath.
81+
* Also checks if the file exists, otherwise it throws and error.
82+
*
83+
* @param {(string | undefined)} schemaPath Schema path from bundling
84+
* @param {GraphQLProjectConfig} project Configuration object for current project
85+
* @returns {string} Input schema path to be used for binding generatio.
86+
*/
87+
function determineInputSchema(schemaPath: string | undefined, project: GraphQLProjectConfig): string {
88+
const bundleDefined = has(project.config, 'extensions.bundle.output')
89+
// schemaPath is only set when bundle ran
90+
if (!schemaPath) {
91+
if (bundleDefined) {
92+
// Otherwise, use bundle output schema if defined
93+
schemaPath = get(project.config, 'extensions.bundle.output')
94+
} else if (project.schemaPath) {
95+
// Otherwise, use project schemaPath
96+
schemaPath = project.schemaPath
97+
} else {
98+
throw new Error(`Input schema cannot be determined.`)
99+
}
100+
}
101+
102+
if (fs.existsSync(schemaPath!)) {
103+
return schemaPath!
104+
} else {
105+
throw new Error(
106+
`Schema '${schemaPath!}' not found.${bundleDefined ? ' Did you run bundle first?' : ''}`
107+
)
108+
}
109+
}
110+
111+
/**
112+
* Determine input schema path for bundling.
113+
*
114+
* @param {string} projectName Name of the current project
115+
* @param {GraphQLProjectConfig} project Configuration object for current project
116+
* @returns {string} Input schema path for bundling
117+
*/
118+
function determineSchemaPath(projectName: string, project: GraphQLProjectConfig): string {
119+
if (project.schemaPath) {
120+
return project.schemaPath
121+
}
122+
throw new Error(`No schemaPath defined for project '${projectName}' in config file.`)
123+
}
124+
125+
/**
126+
* Determine generator. Provided generator takes precedence over value from config
127+
*
128+
* @param {GraphQLProjectConfig} project Configuration object for current project
129+
* @param {string} generator Command line parameter for generator
130+
* @returns {string} Generator to be used
131+
*/
132+
function determineGenerator(project: GraphQLProjectConfig, generator: string): string {
133+
if (generator) {
134+
return generator
135+
}
136+
if (has(project.config, 'extensions.binding.generator')) {
137+
return get(project.config, 'extensions.binding.generator')
138+
}
139+
throw new Error(
140+
'Generator cannot be determined. No existing configuration found and no generator parameter specified.'
141+
)
142+
}
143+
144+
/**
145+
* Determine output path. Provided path takes precedence over value from config
146+
*
147+
* @param {GraphQLProjectConfig} project Configuration object for current project
148+
* @param {string} output Command line parameter for output path
149+
* @param {string} key Extension key containing current output setting
150+
* @returns Output path
151+
*/
152+
function determineOutputPath(
153+
projectName: string,
154+
project: GraphQLProjectConfig,
155+
output: string,
156+
extension: string,
157+
key: string
158+
) {
159+
let outputPath: string
160+
if (output) {
161+
outputPath = path.join(output, `${projectName}.${extension}`)
162+
} else if (has(project.config, `extensions.${key}`)) {
163+
outputPath = get(project.config, `extensions.${key}`)
164+
} else {
165+
throw new Error(
166+
'Output path cannot be determined. No existing configuration found and no output parameter specified.'
167+
)
168+
}
169+
170+
if (!fs.existsSync(path.dirname(outputPath))) {
171+
throw new Error(`Output path '${path.dirname(outputPath)}' does not exist.`)
172+
}
173+
return outputPath
174+
}

0 commit comments

Comments
 (0)