Skip to content
This repository has been archived by the owner on Jun 29, 2021. It is now read-only.

Commit

Permalink
Basic CLI
Browse files Browse the repository at this point in the history
  • Loading branch information
lemonmade committed May 19, 2017
1 parent 3e890b1 commit a64e86e
Show file tree
Hide file tree
Showing 11 changed files with 1,033 additions and 71 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/usr/bin/env node

require('../lib/cli');
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# import './SimpleCard.graphql'
# import './TableCard.graphql'

query Home($someVariable: ProductInput, $another: Int!) {
query Home($someVariable: ProductInput, $another: String) {
shop {
onlineStoreChannel: channelByHandle(handle: "online_store") {
name
Expand Down
15 changes: 0 additions & 15 deletions packages/graphql-typescript-definitions/example/build.ts

This file was deleted.

7 changes: 6 additions & 1 deletion packages/graphql-typescript-definitions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,20 @@
"author": "Chris Sauve <chrismsauve@gmail.com>",
"license": "MIT",
"scripts": {
"build": "tsc",
"pretry": "find example -type f -name '*.d.ts' -delete",
"try": "ts-node --disableWarnings --project example example/build.ts"
"try": "bin/graphql-typescript-definitions 'example/**/*.graphql' --schema-path './example/schema.json'"
},
"devDependencies": {
"@types/chalk": "^0.4.31",
"ts-node": "^3.0.4",
"typescript": "^2.3.2"
},
"dependencies": {
"@types/chokidar": "^1.6.0",
"@types/graphql": "^0.9.1",
"chalk": "^1.1.3",
"chokidar": "^1.7.0",
"fs-extra": "^3.0.1",
"glob": "^7.1.1",
"graphql": "^0.9.1",
Expand Down
28 changes: 0 additions & 28 deletions packages/graphql-typescript-definitions/src/ast.ts

This file was deleted.

52 changes: 52 additions & 0 deletions packages/graphql-typescript-definitions/src/cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import * as yargs from 'yargs';
import * as chalk from 'chalk';

import {Builder} from '.';

const argv = yargs
.usage('Usage: $0 <graphql-files> [options]')
.option('schema-path', {
required: true,
normalize: true,
type: 'string',
describe: 'The path to the JSON file containing a schema instrospection query result',
})
.option('watch', {
required: false,
default: false,
type: 'boolean',
describe: 'Watch the GraphQL files for changes and re-run the generation',
})
.help()
.argv;

const builder = new Builder({
graphQLFiles: argv._[0],
schemaPath: argv.schemaPath,
});

const BUILT = chalk.inverse.bold.green(' BUILT ');
const ERROR = chalk.inverse.bold.red(' ERROR ');

builder.on('start', () => {
console.log();
});

builder.on('build', ({documentPath, definitionPath}) => {
console.log(`${BUILT} ${chalk.dim(documentPath)}${definitionPath}`);
});

builder.on('end', () => {
console.log();
});

builder.on('error', (error) => {
console.log(`${ERROR} ${error.message}`);
if (error.stack) {
console.log(chalk.dim(error.stack));
}
console.log();
});

builder.run({watch: argv.watch})
.catch((error) => console.log(error));
182 changes: 168 additions & 14 deletions packages/graphql-typescript-definitions/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,178 @@
import {writeFileSync} from 'fs-extra';
import {Operation, Fragment, AST} from 'graphql-tool-utilities/ast';
import {EventEmitter} from 'events';
import {buildClientSchema, GraphQLSchema, DocumentNode, parse, Source, concatAST} from 'graphql';
import {readJSON, readFile, writeFile} from 'fs-extra';
import {watch} from 'chokidar';
import * as glob from 'glob';
import {compile, Operation, Fragment, AST} from 'graphql-tool-utilities/ast';

import buildAST, {Options} from './ast';
import buildAST from './ast';
import {printFile} from './print';

export default function graphQLToTypeScriptDefinitions(options: Options) {
const ast = buildAST(options);
const fileMap = groupQueriesAndFragmentsByFile(ast);
export interface Options {
graphQLFiles: string,
schemaPath: string,
}

export interface RunOptions {
watch?: boolean,
}

export interface Build {
documentPath: string,
definitionPath: string,
operation?: Operation,
fragment?: Fragment,
}

export class Builder extends EventEmitter {
watching: boolean;
private globs: string;
private schemaPath: string;
private documentCache = new Map<string, DocumentNode>();

constructor({
graphQLFiles,
schemaPath,
}: Options) {
super();
this.globs = graphQLFiles;
this.schemaPath = schemaPath;
}

on(event: 'error', handler: (error: Error) => void): this
on(event: 'build', handler: (built: Build) => void): this
on(event: 'start', handler: () => void): this
on(event: 'end', handler: () => void): this
on(event: string, handler: (...args: any[]) => void): this {
return super.on(event, handler);
}

emit(event: 'error', error: Error): boolean
emit(event: 'build', built: Build): boolean
emit(event: 'start'): boolean
emit(event: 'end'): boolean
emit(event: string, ...args: any[]): boolean {
return super.emit(event, ...args);
}

async run({watch: watchGlobs = false} = {}) {
const {schemaPath, globs, documentCache} = this;
let schema: GraphQLSchema;

const generate = async () => {
this.emit('start');
let ast: AST;

try {
ast = compile(schema, concatAST(Array.from(documentCache.values())));
} catch (error) {
this.emit(error);
return;
}

const fileMap = groupOperationsAndFragmentsByFile(ast);
await Promise.all(
Object
.keys(fileMap)
.map(async (key) => {
const file = fileMap[key];
const definition = printFile(file, ast);
const definitionPath = `${file.path}.d.ts`;
await writeFile(definitionPath, definition);

Object.keys(fileMap).forEach((path) => {
const file = fileMap[path];
const build = {
documentPath: file.path,
definitionPath,
operation: file.operation,
fragment: file.fragment,
};

const content = printFile(file, ast);
if (!content) { return; }
this.emit('build', build);
})
);

const newFile = `${file.path}.d.ts`;
writeFileSync(newFile, content);
});
this.emit('end');
};

const update = async (file: string) => {
try {
await this.updateDocumentForFile(file);
} catch (error) {
return;
}

await generate();
};

const remove = async (file: string) => {
this.removeDocumentForFile(file);
await generate();
};

if (watchGlobs) {
const watcher = watch(globs);
watcher.on('ready', () => {
watcher.on('add', update);
watcher.on('change', update);
watcher.on('unlink', remove);
});
}

try {
const schemaJSON = await readJSON(schemaPath, 'utf8');
schema = buildClientSchema(schemaJSON.data);
} catch (error) {
const parseError = new Error(`Error parsing '${schemaPath}':\n\n${error.message.replace(/Syntax Error GraphQL \(.*?\) /, '')}`);
this.emit('error', parseError);
return;
}

try {
await Promise.all(
glob
.sync(globs)
.map(this.updateDocumentForFile.bind(this))
);
} catch (error) {
return;
}

await generate();
}

private async updateDocumentForFile(file: string) {
try {
const contents = await readFile(file, 'utf8');
if (contents.trim().length === 0) { return; }

const document = parse(new Source(contents, file));
this.documentCache.set(file, document);
} catch (error) {
this.emit('error', error);
throw error;
}
}

private removeDocumentForFile(file: string) {
this.documentCache.delete(file);
}
}

// export default function graphQLToTypeScriptDefinitions(options: Options) {
// const ast = buildAST(options);
// const fileMap = groupQueriesAndFragmentsByFile(ast);

// Object.keys(fileMap).forEach((path) => {
// const file = fileMap[path];

// const content = printFile(file, ast);
// if (!content) { return; }

// const newFile = `${file.path}.d.ts`;
// writeFileSync(newFile, content);
// });
// }

interface File {
path: string,
operation?: Operation,
Expand All @@ -29,7 +183,7 @@ interface FileMap {
[key: string]: File,
}

function groupQueriesAndFragmentsByFile({operations, fragments}: AST): FileMap {
function groupOperationsAndFragmentsByFile({operations, fragments}: AST): FileMap {
const map: FileMap = {};

Object
Expand Down
11 changes: 11 additions & 0 deletions packages/graphql-typescript-definitions/test/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"compileOnSave": false,
"compilerOptions": {
"moduleResolution": "node",
"strict": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"noUnusedParameters": true,
"noUnusedLocals": true
}
}
5 changes: 4 additions & 1 deletion packages/graphql-typescript-definitions/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,8 @@
"allowSyntheticDefaultImports": true,
"noUnusedParameters": true,
"noUnusedLocals": true
}
},
"include": [
"src/*"
]
}
Loading

0 comments on commit a64e86e

Please sign in to comment.