@graphql-codegen/typescript-oclif
Version:
GraphQL Code Generator plugin for generating a CLI tool with oclif
82 lines (79 loc) • 3.9 kB
JavaScript
import autoBind from 'auto-bind';
import { print } from 'graphql';
import { ClientSideBaseVisitor, indentMultiline, } from '@graphql-codegen/visitor-plugin-common';
import { getFlagConfigForVariableDefinition, omitOclifDirectives } from './utils.js';
export class GraphQLRequestVisitor extends ClientSideBaseVisitor {
constructor(schema, fragments, rawConfig, info) {
super(schema, fragments, rawConfig, {});
this._operationsToInclude = [];
this._info = info;
const { handlerPath = '../../handler' } = rawConfig;
// FIXME: This is taken in part from
// presets/near-operation-file/src/index.ts:139. How do I build a path relative to the outputFile in the same way?
// A plugin doesn't appear to have access to the same "options.baseOutputDir" that the preset does.
// const absClientPath = resolve(info.outputFile, join(options.baseOutputDir, options.presetConfig.baseTypesPath));
autoBind(this);
this._additionalImports.push(`import { Command, flags } from '@oclif/command'`);
this._additionalImports.push(`import handler from '${handlerPath}'`);
}
buildOperation(node, documentVariableName, operationType, operationResultType, operationVariablesTypes) {
this._operationsToInclude.push({
node,
documentVariableName,
operationType,
operationResultType,
operationVariablesTypes,
});
return null;
}
// Clean client-side content (ie directives) out of the GraphQL document prior to sending to the server
get definition() {
const operation = this._operationsToInclude[0];
const clientOperation = print(omitOclifDirectives(operation.node));
return `const ${operation.documentVariableName} = \`\n${clientOperation}\``;
}
// Generate the code required for this CLI operation
get cliContent() {
if (this._operationsToInclude.length !== 1) {
throw new Error(`Each graphql document should have exactly one operation; found ${this._operationsToInclude.length} while generating ${this._info.outputFile}.`);
}
const operation = this._operationsToInclude[0];
// Find the @oclif directive in the client document, if it's there
const directive = operation.node.directives.find(directive => directive.name.value === 'oclif');
// Remap the directive's fields ie @oclif(description: "a name") to a more usable format
const directiveValues = {};
if (directive) {
directiveValues.examples = [];
directive.arguments.forEach(arg => {
const value = 'value' in arg.value ? arg.value.value.toString() : null;
const { value: name } = arg.name;
if (name === 'description') {
directiveValues.description = value;
}
else if (name === 'example') {
directiveValues.examples.push(value);
}
else {
throw new Error(`Invalid field supplied to @oclif directive: ${name}`);
}
});
}
const { description, examples } = directiveValues;
const flags = operation.node.variableDefinitions.map(getFlagConfigForVariableDefinition);
return `
${this.definition}
export default class ${operation.node.name.value} extends Command {
${description ? `\nstatic description = "${description}";\n` : ''}
${examples ? `\nstatic examples: string[] = ${JSON.stringify(examples)};\n` : ''}
static flags = {
help: flags.help({ char: 'h' }),
${indentMultiline(flags.join(',\n'), 2)}
};
async run() {
const { flags } = this.parse(${operation.node.name.value});
await handler({ command: this, query: ${operation.documentVariableName}, variables: flags });
}
}
`;
}
}