├── .eslint.json ├── .flowconfig ├── .gitignore ├── LICENSE ├── README.md ├── lerna.json ├── package.json └── packages ├── gql-cli ├── README.md ├── dist │ └── index.js ├── package.json ├── src │ └── index.js └── test │ └── index.js ├── gql-format ├── .npmignore ├── README.md ├── dist │ └── index.js ├── package.json ├── src │ └── index.js └── test │ └── index.js ├── gql-lint ├── .npmignore ├── README.md ├── dist │ └── index.js ├── package.json ├── src │ └── index.js └── test │ └── index.js ├── gql-merge ├── .npmignore ├── README.md ├── dist │ └── index.js ├── package.json ├── src │ └── index.js └── test │ └── index.js └── gql-utils ├── .npmignore ├── README.md ├── dist └── index.js ├── package.json └── src └── index.js /.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "ecmaVersion": 8, 3 | "env": { 4 | "browser": true, 5 | "node": true, 6 | "commonjs": true, 7 | "es6": true 8 | }, 9 | "extends": "eslint:recommended", 10 | "parser": "babel-eslint", 11 | "parserOptions": { 12 | "sourceType": "module" 13 | }, 14 | "rules": { 15 | "indent": [ 16 | "error", 17 | 2 18 | ], 19 | "linebreak-style": [ 20 | "error", 21 | "unix" 22 | ], 23 | "quotes": [ 24 | "error", 25 | "single" 26 | ], 27 | "semi": [ 28 | "error", 29 | "never" 30 | ], 31 | "comma-dangle": [ 32 | "error", 33 | "always" 34 | ] 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | .*/packages/.*/lib 3 | .*/packages/.*/test 4 | 5 | [include] 6 | packages/*/src 7 | 8 | [libs] 9 | 10 | [options] 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *-debug.log 3 | .DS_Store 4 | conflicted copy 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016-2017 Liam Curry 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gql 2 | 3 | [![standard-readme compliant](https://img.shields.io/badge/standard--readme-OK-green.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme) 4 | [![Travis](https://img.shields.io/travis/liamcurry/gql.svg?style=flat-square)]() 5 | 6 | > Tools for working with GraphQL documents 7 | 8 | **Warning: these tools are still in an early phase and there will be issues. Bug reports and pull-requests are welcome!** 9 | 10 | ## Table of Contents 11 | 12 | - [Background](#background) 13 | - [Install](#getting-started) 14 | - [Packages](#packages) 15 | - [Contribute](#contribute) 16 | - [License](#license) 17 | 18 | ## Background 19 | 20 | Building GraphQL schemas in the GraphQL native language (i.e. `.graphql` files) 21 | has several benefits over using a programming language: 22 | 23 | - Your data structures are language agnostic so only the resolvers need to be 24 | programmed. 25 | - The GraphQL language is terser than most programming languages. Schemas are simpler 26 | to write and understand. 27 | - Schemas rarely change after compile time; most people won't take advantage 28 | of progammatic schemas. 29 | - Schemas can be accessed without a GraphQL server. 30 | 31 | Hopefully the tools in this repository make it easier to work with GraphQL files. 32 | 33 | ## Install 34 | 35 | The quickest way to get started is by globally installing the `gql` command-line interface: 36 | 37 | ``` 38 | $ npm i -g gql-cli 39 | ``` 40 | 41 | ### Packages 42 | 43 | Package | Description | Version | Dependencies 44 | ------- | ----------- | ------- | ------------ 45 | [`gql-cli`](packages/gql-cli) | Command-line interface for `gql` GraphQL tools | [![npm](https://img.shields.io/npm/v/gql-cli.svg?style=flat-square)](https://www.npmjs.com/package/gql-cli) |[![Dependency Status](https://david-dm.org/liamcurry/gql.svg?path=packages/gql-cli)](https://david-dm.org/liamcurry/gql?path=packages/gql-cli) 46 | [`gql-format`](packages/gql-format) | Tools for formatting GraphQL documents | [![npm](https://img.shields.io/npm/v/gql-format.svg?style=flat-square)](https://www.npmjs.com/package/gql-format) |[![Dependency Status](https://david-dm.org/liamcurry/gql.svg?path=packages/gql-format)](https://david-dm.org/liamcurry/gql?path=packages/gql-format) 47 | [`gql-merge`](packages/gql-merge) | Tools for merging GraphQL documents | [![npm](https://img.shields.io/npm/v/gql-merge.svg?style=flat-square)](https://www.npmjs.com/package/gql-merge) |[![Dependency Status](https://david-dm.org/liamcurry/gql.svg?path=packages/gql-merge)](https://david-dm.org/liamcurry/gql?path=packages/gql-merge) 48 | [`gql-lint`](packages/gql-lint) | Tools for linting GraphQL documents | [![npm](https://img.shields.io/npm/v/gql-lint.svg?style=flat-square)](https://www.npmjs.com/package/gql-lint) |[![Dependency Status](https://david-dm.org/liamcurry/gql.svg?path=packages/gql-lint)](https://david-dm.org/liamcurry/gql?path=packages/gql-lint) 49 | 50 | ## Contribute 51 | 52 | Feel free to dive in! [Open an issue](/liamcurry/gql/issues/new) or submit PRs. 53 | 54 | `gql` follows the [Contributor Covenant Code of Conduct](http://contributor-covenant.org/version/1/3/0/). 55 | 56 | ## License 57 | 58 | MIT (c) Liam Curry 59 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "lerna": "2.0.0-beta.30", 3 | "version": "0.0.3" 4 | } 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gql", 3 | "version": "0.0.1", 4 | "description": "Tools for working with GraphQL documents", 5 | "main": "packages/gql-cli/index.js", 6 | "babel": { 7 | "presets": [ 8 | "latest", 9 | "stage-0" 10 | ], 11 | "plugins": [ 12 | "transform-runtime", 13 | "transform-flow-strip-types" 14 | ] 15 | }, 16 | "ava": { 17 | "require": "babel-register", 18 | "babel": "inherit" 19 | }, 20 | "devDependencies": { 21 | "ava": "^0.16.0", 22 | "babel-cli": "^6.18.0", 23 | "babel-eslint": "^7.1.0", 24 | "babel-plugin-transform-flow-strip-types": "^6.18.0", 25 | "babel-plugin-transform-runtime": "^6.15.0", 26 | "babel-preset-latest": "^6.16.0", 27 | "babel-preset-stage-0": "^6.16.0", 28 | "concurrently": "^3.1.0", 29 | "eslint": "^3.9.1", 30 | "flow-bin": "^0.34.0", 31 | "lerna": "2.0.0-beta.30", 32 | "nodemon": "^1.11.0" 33 | }, 34 | "scripts": { 35 | "build": "lerna exec -- babel --out-dir dist src && chmod +x packages/**/dist/index.js", 36 | "publish": "lerna publish", 37 | "postinstall": "lerna bootstrap", 38 | "flow": "flow check", 39 | "lint": "lerna exec -- eslint --ignore-path ../../.gitignore --config ../../.eslint.json 'src/**/*.js' 'test/**/*.js'", 40 | "test": "lerna exec -- ava", 41 | "watch-build": "npm run build -- -w", 42 | "watch-test": "npm run test -- -w" 43 | }, 44 | "repository": { 45 | "type": "git", 46 | "url": "git+https://github.com/liamcurry/gql.git" 47 | }, 48 | "author": "", 49 | "license": "MIT", 50 | "bugs": { 51 | "url": "https://github.com/liamcurry/gql/issues" 52 | }, 53 | "homepage": "https://github.com/liamcurry/gql#readme" 54 | } 55 | -------------------------------------------------------------------------------- /packages/gql-cli/README.md: -------------------------------------------------------------------------------- 1 | # gql-cli 2 | 3 | [![standard-readme compliant](https://img.shields.io/badge/standard--readme-OK-green.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme) 4 | [![npm](https://img.shields.io/npm/v/gql-cli.svg?style=flat-square)](https://www.npmjs.com/package/gql-cli) 5 | [![npm](https://img.shields.io/npm/dm/gql-cli.svg?style=flat-square)](https://www.npmjs.com/package/gql-cli) 6 | [![npm](https://img.shields.io/npm/l/gql-cli.svg?style=flat-square)](https://www.npmjs.com/package/gql-cli) 7 | 8 | > Tools for formatting GraphQL documents 9 | 10 | ## Table of Contents 11 | 12 | - [Install](#install) 13 | - [CLI](#cli) 14 | 15 | ## Install 16 | 17 | ``` 18 | $ npm i -g gql-cli 19 | ``` 20 | 21 | ## CLI 22 | 23 | ``` 24 | $ gql -h 25 | 26 | Usage: gql [options] [command] 27 | 28 | Commands: 29 | 30 | merge [options] Tools for merging GraphQL documents 31 | format Tools for formatting GraphQL documents 32 | 33 | Command-line interface for gql GraphQL tools 34 | 35 | Options: 36 | 37 | -h, --help output usage information 38 | -V, --version output the version number 39 | 40 | ``` 41 | -------------------------------------------------------------------------------- /packages/gql-cli/dist/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | var _commander = require('commander'); 5 | 6 | var _commander2 = _interopRequireDefault(_commander); 7 | 8 | var _gqlFormat = require('gql-format'); 9 | 10 | var _gqlMerge = require('gql-merge'); 11 | 12 | var _package = require('../package.json'); 13 | 14 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 15 | 16 | _commander2.default.version(_package.version).description(_package.description); 17 | 18 | (0, _gqlMerge.cli)(_commander2.default); 19 | (0, _gqlFormat.cli)(_commander2.default); 20 | 21 | _commander2.default.parse(process.argv); -------------------------------------------------------------------------------- /packages/gql-cli/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gql-cli", 3 | "version": "0.0.4", 4 | "description": "Command-line interface for gql GraphQL tools", 5 | "main": "dist/index.js", 6 | "bin": { 7 | "gql": "./dist/index.js" 8 | }, 9 | "directories": { 10 | "test": "test" 11 | }, 12 | "author": "", 13 | "license": "MIT", 14 | "dependencies": { 15 | "commander": "^2.9.0", 16 | "gql-format": "^0.0.4", 17 | "gql-lint": "0.0.2", 18 | "gql-merge": "^0.0.4" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/gql-cli/src/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* @flow */ 3 | import program from 'commander' 4 | import {cli as formatCli} from 'gql-format' 5 | import {cli as mergeCli} from 'gql-merge' 6 | import {version, description,} from '../package.json' 7 | 8 | program 9 | .version(version) 10 | .description(description) 11 | 12 | mergeCli(program) 13 | formatCli(program) 14 | 15 | program.parse(process.argv) 16 | -------------------------------------------------------------------------------- /packages/gql-cli/test/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liamcurry/gql/0a28f69cc80084d648a95d1ea24a834d737235f6/packages/gql-cli/test/index.js -------------------------------------------------------------------------------- /packages/gql-format/.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | src 3 | -------------------------------------------------------------------------------- /packages/gql-format/README.md: -------------------------------------------------------------------------------- 1 | # gql-format 2 | 3 | [![standard-readme compliant](https://img.shields.io/badge/standard--readme-OK-green.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme) 4 | [![npm](https://img.shields.io/npm/v/gql-format.svg?style=flat-square)](https://www.npmjs.com/package/gql-format) 5 | [![npm](https://img.shields.io/npm/dm/gql-format.svg?style=flat-square)](https://www.npmjs.com/package/gql-format) 6 | [![npm](https://img.shields.io/npm/l/gql-format.svg?style=flat-square)](https://www.npmjs.com/package/gql-format) 7 | 8 | > Tools for formatting GraphQL documents 9 | 10 | ## Table of Contents 11 | 12 | - [Installation](#installation) 13 | - [CLI](#cli) 14 | - [API](#api) 15 | 16 | ## Installation 17 | 18 | ``` 19 | $ npm i -g gql-format 20 | ``` 21 | 22 | ## CLI 23 | 24 | ``` 25 | $ gql-format -h 26 | 27 | Usage: gql-format [options] 28 | 29 | Tools for formatting GraphQL documents 30 | 31 | Options: 32 | 33 | -h, --help output usage information 34 | -V, --version output the version number 35 | 36 | ``` 37 | 38 | ## API 39 | 40 | More detailed docs coming soon. 41 | 42 | ### `formatFileGlob` 43 | 44 | ### `formatFilePaths` 45 | 46 | ### `formatString` 47 | -------------------------------------------------------------------------------- /packages/gql-format/dist/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | Object.defineProperty(exports, "__esModule", { 5 | value: true 6 | }); 7 | exports.cli = exports.formatFileObjects = exports.formatFilePaths = exports.formatFileGlob = undefined; 8 | 9 | var _promise = require('babel-runtime/core-js/promise'); 10 | 11 | var _promise2 = _interopRequireDefault(_promise); 12 | 13 | var _keys = require('babel-runtime/core-js/object/keys'); 14 | 15 | var _keys2 = _interopRequireDefault(_keys); 16 | 17 | var _stringify = require('babel-runtime/core-js/json/stringify'); 18 | 19 | var _stringify2 = _interopRequireDefault(_stringify); 20 | 21 | var _regenerator = require('babel-runtime/regenerator'); 22 | 23 | var _regenerator2 = _interopRequireDefault(_regenerator); 24 | 25 | var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); 26 | 27 | var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); 28 | 29 | var _defineProperty2 = require('babel-runtime/helpers/defineProperty'); 30 | 31 | var _defineProperty3 = _interopRequireDefault(_defineProperty2); 32 | 33 | /** 34 | * Find files matching the input glob, format them, and overwrite the originals. 35 | * @param {string} fileGlob - A glob pattern to find files, e.g. '*.graphql' 36 | * @return {Promise} The write files promise 37 | */ 38 | var formatFileGlob = exports.formatFileGlob = function () { 39 | var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(fileGlob) { 40 | var fileObjects; 41 | return _regenerator2.default.wrap(function _callee$(_context) { 42 | while (1) { 43 | switch (_context.prev = _context.next) { 44 | case 0: 45 | _context.next = 2; 46 | return (0, _gqlUtils.readFileGlob)(fileGlob); 47 | 48 | case 2: 49 | fileObjects = _context.sent; 50 | _context.next = 5; 51 | return formatFileObjects(fileObjects); 52 | 53 | case 5: 54 | return _context.abrupt('return', _context.sent); 55 | 56 | case 6: 57 | case 'end': 58 | return _context.stop(); 59 | } 60 | } 61 | }, _callee, this); 62 | })); 63 | 64 | return function formatFileGlob(_x) { 65 | return _ref.apply(this, arguments); 66 | }; 67 | }(); 68 | 69 | /** 70 | * Find files based on paths, format them, and overwrite the originals. 71 | * @param {string[]} filePaths - An array of file paths to look for. 72 | * @return {Promise} The write files promise 73 | */ 74 | 75 | 76 | var formatFilePaths = exports.formatFilePaths = function () { 77 | var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(filePaths) { 78 | var fileObjects; 79 | return _regenerator2.default.wrap(function _callee2$(_context2) { 80 | while (1) { 81 | switch (_context2.prev = _context2.next) { 82 | case 0: 83 | _context2.next = 2; 84 | return (0, _gqlUtils.readFilePaths)(filePaths); 85 | 86 | case 2: 87 | fileObjects = _context2.sent; 88 | _context2.next = 5; 89 | return formatFileObjects(fileObjects); 90 | 91 | case 5: 92 | return _context2.abrupt('return', _context2.sent); 93 | 94 | case 6: 95 | case 'end': 96 | return _context2.stop(); 97 | } 98 | } 99 | }, _callee2, this); 100 | })); 101 | 102 | return function formatFilePaths(_x2) { 103 | return _ref2.apply(this, arguments); 104 | }; 105 | }(); 106 | 107 | /** 108 | * Formats file contents and saves the result to the file path. 109 | * @param {{filePath: string, fileContents: string}[]} - An array of file paths 110 | * and content. 111 | * @return {Promise} The write files promise 112 | */ 113 | 114 | 115 | var formatFileObjects = exports.formatFileObjects = function () { 116 | var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(fileObjects) { 117 | var fileObjectsFormatted; 118 | return _regenerator2.default.wrap(function _callee3$(_context3) { 119 | while (1) { 120 | switch (_context3.prev = _context3.next) { 121 | case 0: 122 | fileObjectsFormatted = fileObjects.map(function (_ref4) { 123 | var filePath = _ref4.filePath, 124 | fileContents = _ref4.fileContents; 125 | return { 126 | filePath: filePath, 127 | fileContents: formatString(fileContents) 128 | }; 129 | }); 130 | _context3.next = 3; 131 | return (0, _gqlUtils.writeFileObjects)(fileObjectsFormatted); 132 | 133 | case 3: 134 | return _context3.abrupt('return', _context3.sent); 135 | 136 | case 4: 137 | case 'end': 138 | return _context3.stop(); 139 | } 140 | } 141 | }, _callee3, this); 142 | })); 143 | 144 | return function formatFileObjects(_x3) { 145 | return _ref3.apply(this, arguments); 146 | }; 147 | }(); 148 | 149 | /** 150 | * Format a GraphQL schema string. 151 | * @param {string} schemaStr - The raw GraphQL string to format. 152 | * @return {string} The formatted string. 153 | */ 154 | 155 | 156 | /** 157 | * The command-line interface for formatting GraphQL files. If this module is 158 | * being imported, it will register itself as a commander command 'format'. 159 | * Otherwise, it will run the CLI. 160 | * @param program - The commander object to modify. 161 | */ 162 | var cli = exports.cli = function () { 163 | var _ref37 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5() { 164 | var _this = this; 165 | 166 | var program = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _commander2.default; 167 | var command; 168 | return _regenerator2.default.wrap(function _callee5$(_context5) { 169 | while (1) { 170 | switch (_context5.prev = _context5.next) { 171 | case 0: 172 | if (module.parent) { 173 | _context5.next = 8; 174 | break; 175 | } 176 | 177 | program.version(_package.version).usage('[options] '); 178 | 179 | cliAddHelp(cliAddBasics(program)); 180 | 181 | program.parse(process.argv); 182 | _context5.next = 6; 183 | return cliAction(program, program.args); 184 | 185 | case 6: 186 | _context5.next = 11; 187 | break; 188 | 189 | case 8: 190 | command = program.command('format '); 191 | 192 | cliAddHelp(cliAddBasics(command)); 193 | command.action(function () { 194 | var _ref38 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(inputGlob, options) { 195 | return _regenerator2.default.wrap(function _callee4$(_context4) { 196 | while (1) { 197 | switch (_context4.prev = _context4.next) { 198 | case 0: 199 | _context4.next = 2; 200 | return cliAction(command, inputGlob.split(' ')); 201 | 202 | case 2: 203 | case 'end': 204 | return _context4.stop(); 205 | } 206 | } 207 | }, _callee4, _this); 208 | })); 209 | 210 | return function (_x5, _x6) { 211 | return _ref38.apply(this, arguments); 212 | }; 213 | }()); 214 | 215 | case 11: 216 | case 'end': 217 | return _context5.stop(); 218 | } 219 | } 220 | }, _callee5, this); 221 | })); 222 | 223 | return function cli() { 224 | return _ref37.apply(this, arguments); 225 | }; 226 | }(); 227 | 228 | var cliAction = function () { 229 | var _ref39 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(program) { 230 | var fileGlobs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; 231 | var formatFilePromises; 232 | return _regenerator2.default.wrap(function _callee6$(_context6) { 233 | while (1) { 234 | switch (_context6.prev = _context6.next) { 235 | case 0: 236 | if (fileGlobs.length) { 237 | _context6.next = 2; 238 | break; 239 | } 240 | 241 | return _context6.abrupt('return', program.help()); 242 | 243 | case 2: 244 | formatFilePromises = fileGlobs.map(formatFileGlob); 245 | _context6.next = 5; 246 | return _promise2.default.all(formatFilePromises); 247 | 248 | case 5: 249 | return _context6.abrupt('return', _context6.sent); 250 | 251 | case 6: 252 | case 'end': 253 | return _context6.stop(); 254 | } 255 | } 256 | }, _callee6, this); 257 | })); 258 | 259 | return function cliAction(_x7) { 260 | return _ref39.apply(this, arguments); 261 | }; 262 | }(); 263 | 264 | exports.formatString = formatString; 265 | exports.formatAst = formatAst; 266 | 267 | var _commander = require('commander'); 268 | 269 | var _commander2 = _interopRequireDefault(_commander); 270 | 271 | var _buildASTSchema = require('graphql/utilities/buildASTSchema'); 272 | 273 | var _language = require('graphql/language'); 274 | 275 | var _gqlUtils = require('gql-utils'); 276 | 277 | var _package = require('../package.json'); 278 | 279 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 280 | 281 | exports.default = (0, _defineProperty3.default)({ 282 | formatString: formatString, 283 | formatFileGlob: formatFileGlob, 284 | formatFileObjects: formatFileObjects 285 | }, 'formatString', formatString); 286 | function formatString(schemaStr) { 287 | var schemaAst = (0, _language.parse)(schemaStr); 288 | return formatAst(schemaAst); 289 | } 290 | /** 291 | * Converts an AST into a string, using one set of reasonable 292 | * formatting rules. 293 | */ 294 | function formatAst(ast) { 295 | return (0, _language.visit)(ast, { leave: printDocASTReducer }); 296 | } 297 | 298 | var printDocASTReducer = { 299 | Name: function Name(node) { 300 | return node.value; 301 | }, 302 | Variable: function Variable(node) { 303 | return '$' + node.name; 304 | }, 305 | 306 | // Document 307 | 308 | Document: function Document(node) { 309 | return join(node.definitions, '\n\n') + '\n'; 310 | }, 311 | 312 | OperationDefinition: function OperationDefinition(node) { 313 | var op = node.operation; 314 | var name = node.name; 315 | var varDefs = wrap('(', join(node.variableDefinitions, ', '), ')'); 316 | var directives = join(node.directives, ' '); 317 | var selectionSet = node.selectionSet; 318 | // Anonymous queries with no directives or variable definitions can use 319 | // the query short form. 320 | return !name && !directives && !varDefs && op === 'query' ? selectionSet : join([op, join([name, varDefs]), directives, selectionSet], ' '); 321 | }, 322 | 323 | 324 | VariableDefinition: function VariableDefinition(_ref5) { 325 | var variable = _ref5.variable, 326 | type = _ref5.type, 327 | defaultValue = _ref5.defaultValue; 328 | return variable + ': ' + type + wrap(' = ', defaultValue); 329 | }, 330 | 331 | SelectionSet: function SelectionSet(_ref6) { 332 | var selections = _ref6.selections; 333 | return block(selections); 334 | }, 335 | 336 | Field: function Field(_ref7) { 337 | var alias = _ref7.alias, 338 | name = _ref7.name, 339 | args = _ref7.arguments, 340 | directives = _ref7.directives, 341 | selectionSet = _ref7.selectionSet; 342 | return join([wrap('', alias, ': ') + name + wrap('(', join(args, ', '), ')'), join(directives, ' '), selectionSet], ' '); 343 | }, 344 | 345 | Argument: function Argument(_ref8) { 346 | var name = _ref8.name, 347 | value = _ref8.value; 348 | return name + ': ' + value; 349 | }, 350 | 351 | // Fragments 352 | 353 | FragmentSpread: function FragmentSpread(_ref9) { 354 | var name = _ref9.name, 355 | directives = _ref9.directives; 356 | return '...' + name + wrap(' ', join(directives, ' ')); 357 | }, 358 | 359 | InlineFragment: function InlineFragment(_ref10) { 360 | var typeCondition = _ref10.typeCondition, 361 | directives = _ref10.directives, 362 | selectionSet = _ref10.selectionSet; 363 | return join(['...', wrap('on ', typeCondition), join(directives, ' '), selectionSet], ' '); 364 | }, 365 | 366 | FragmentDefinition: function FragmentDefinition(_ref11) { 367 | var name = _ref11.name, 368 | typeCondition = _ref11.typeCondition, 369 | directives = _ref11.directives, 370 | selectionSet = _ref11.selectionSet; 371 | return 'fragment ' + name + ' on ' + typeCondition + ' ' + wrap('', join(directives, ' '), ' ') + selectionSet; 372 | }, 373 | 374 | // Value 375 | 376 | IntValue: function IntValue(_ref12) { 377 | var value = _ref12.value; 378 | return value; 379 | }, 380 | FloatValue: function FloatValue(_ref13) { 381 | var value = _ref13.value; 382 | return value; 383 | }, 384 | StringValue: function StringValue(_ref14) { 385 | var value = _ref14.value; 386 | return (0, _stringify2.default)(value); 387 | }, 388 | BooleanValue: function BooleanValue(_ref15) { 389 | var value = _ref15.value; 390 | return (0, _stringify2.default)(value); 391 | }, 392 | NullValue: function NullValue() { 393 | return 'null'; 394 | }, 395 | EnumValue: function EnumValue(_ref16) { 396 | var value = _ref16.value; 397 | return value; 398 | }, 399 | ListValue: function ListValue(_ref17) { 400 | var values = _ref17.values; 401 | return '[' + join(values, ', ') + ']'; 402 | }, 403 | ObjectValue: function ObjectValue(_ref18) { 404 | var fields = _ref18.fields; 405 | return '{' + join(fields, ', ') + '}'; 406 | }, 407 | ObjectField: function ObjectField(_ref19) { 408 | var name = _ref19.name, 409 | value = _ref19.value; 410 | return name + ': ' + value; 411 | }, 412 | 413 | // Directive 414 | 415 | Directive: function Directive(_ref20) { 416 | var name = _ref20.name, 417 | args = _ref20.arguments; 418 | return '@' + name + wrap('(', join(args, ', '), ')'); 419 | }, 420 | 421 | // Type 422 | 423 | NamedType: function NamedType(_ref21) { 424 | var name = _ref21.name; 425 | return name; 426 | }, 427 | ListType: function ListType(_ref22) { 428 | var type = _ref22.type; 429 | return '[' + type + ']'; 430 | }, 431 | NonNullType: function NonNullType(_ref23) { 432 | var type = _ref23.type; 433 | return type + '!'; 434 | }, 435 | 436 | // Type System Definitions 437 | 438 | SchemaDefinition: function SchemaDefinition(_ref24) { 439 | var directives = _ref24.directives, 440 | operationTypes = _ref24.operationTypes; 441 | return join(['schema', join(directives, ' '), block(operationTypes)], ' '); 442 | }, 443 | 444 | OperationTypeDefinition: function OperationTypeDefinition(_ref25) { 445 | var operation = _ref25.operation, 446 | type = _ref25.type; 447 | return operation + ': ' + type; 448 | }, 449 | 450 | ScalarTypeDefinition: function ScalarTypeDefinition(_ref26) { 451 | var name = _ref26.name, 452 | directives = _ref26.directives; 453 | return join(['scalar', name, join(directives, ' ')], ' '); 454 | }, 455 | 456 | ObjectTypeDefinition: function ObjectTypeDefinition(_ref27) { 457 | var name = _ref27.name, 458 | interfaces = _ref27.interfaces, 459 | directives = _ref27.directives, 460 | fields = _ref27.fields; 461 | return join(['type', name, wrap('implements ', join(interfaces, ', ')), join(directives, ' '), block(fields)], ' '); 462 | }, 463 | 464 | FieldDefinition: function FieldDefinition(_ref28) { 465 | var name = _ref28.name, 466 | args = _ref28.arguments, 467 | type = _ref28.type, 468 | directives = _ref28.directives; 469 | return name + wrap('(', join(args, ', '), ')') + ': ' + type + wrap(' ', join(directives, ' ')); 470 | }, 471 | 472 | InputValueDefinition: function InputValueDefinition(_ref29) { 473 | var name = _ref29.name, 474 | type = _ref29.type, 475 | defaultValue = _ref29.defaultValue, 476 | directives = _ref29.directives; 477 | return join([name + ': ' + type, wrap('= ', defaultValue), join(directives, ' ')], ' '); 478 | }, 479 | 480 | InterfaceTypeDefinition: function InterfaceTypeDefinition(_ref30) { 481 | var name = _ref30.name, 482 | directives = _ref30.directives, 483 | fields = _ref30.fields; 484 | return join(['interface', name, join(directives, ' '), block(fields)], ' '); 485 | }, 486 | 487 | UnionTypeDefinition: function UnionTypeDefinition(_ref31) { 488 | var name = _ref31.name, 489 | directives = _ref31.directives, 490 | types = _ref31.types; 491 | return join(['union', name, join(directives, ' '), '= ' + join(types, ' | ')], ' '); 492 | }, 493 | 494 | EnumTypeDefinition: function EnumTypeDefinition(_ref32) { 495 | var name = _ref32.name, 496 | directives = _ref32.directives, 497 | values = _ref32.values; 498 | return join(['enum', name, join(directives, ' '), block(values)], ' '); 499 | }, 500 | 501 | EnumValueDefinition: function EnumValueDefinition(_ref33) { 502 | var name = _ref33.name, 503 | directives = _ref33.directives; 504 | return join([name, join(directives, ' ')], ' '); 505 | }, 506 | 507 | InputObjectTypeDefinition: function InputObjectTypeDefinition(_ref34) { 508 | var name = _ref34.name, 509 | directives = _ref34.directives, 510 | fields = _ref34.fields; 511 | return join(['input', name, join(directives, ' '), block(fields)], ' '); 512 | }, 513 | 514 | TypeExtensionDefinition: function TypeExtensionDefinition(_ref35) { 515 | var definition = _ref35.definition; 516 | return 'extend ' + definition; 517 | }, 518 | 519 | DirectiveDefinition: function DirectiveDefinition(_ref36) { 520 | var name = _ref36.name, 521 | args = _ref36.arguments, 522 | locations = _ref36.locations; 523 | return 'directive @' + name + wrap('(', join(args, ', '), ')') + ' on ' + join(locations, ' | '); 524 | } 525 | }; 526 | 527 | (0, _keys2.default)(printDocASTReducer).filter(function (key) { 528 | return key !== 'Name'; 529 | }).forEach(function (key) { 530 | var fn = printDocASTReducer[key]; 531 | printDocASTReducer[key] = withDescription(fn); 532 | }); 533 | 534 | function withDescription(fn) { 535 | return function (node) { 536 | var desc = (0, _buildASTSchema.getDescription)(node); 537 | var descText = desc ? '# ' + desc + '\n' : ''; 538 | return descText + fn(node); 539 | }; 540 | } 541 | 542 | /** 543 | * Given maybeArray, print an empty string if it is null or empty, otherwise 544 | * print all items together separated by separator if provided 545 | */ 546 | function join(maybeArray, separator) { 547 | return maybeArray ? maybeArray.filter(function (x) { 548 | return x; 549 | }).join(separator || '') : ''; 550 | } 551 | 552 | /** 553 | * Given array, print each item on its own line, wrapped in an 554 | * indented "{ }" block. 555 | */ 556 | function block(array) { 557 | return array && array.length !== 0 ? indent('{\n' + join(array, '\n')) + '\n}' : '{}'; 558 | } 559 | 560 | /** 561 | * If maybeString is not null or empty, then wrap with start and end, otherwise 562 | * print an empty string. 563 | */ 564 | function wrap(start, maybeString, end) { 565 | return maybeString ? start + maybeString + (end || '') : ''; 566 | } 567 | 568 | function indent(maybeString) { 569 | return maybeString && maybeString.replace(/\n/g, '\n '); 570 | } 571 | 572 | function cliAddBasics(command) { 573 | return command.description(_package.description); 574 | } 575 | 576 | function cliAddHelp(command) { 577 | var commandName = !module.parent ? 'gql-format' : 'gql format'; 578 | return command.on('--help', function () { 579 | return console.log(' Examples:\n\n $ ' + commandName + ' **/*.graphql\n $ ' + commandName + ' dir1/*.graphql dir2/*.graphql\n '); 580 | }); 581 | } 582 | 583 | if (!module.parent) { 584 | cli(); 585 | } -------------------------------------------------------------------------------- /packages/gql-format/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gql-format", 3 | "version": "0.0.5", 4 | "description": "Tools for formatting GraphQL documents", 5 | "main": "dist/index.js", 6 | "bin": { 7 | "gql-format": "dist/index.js" 8 | }, 9 | "scripts": { 10 | "test": "ava" 11 | }, 12 | "repository": "https://github.com/liamcurry/gql/tree/master/packages/gql-format", 13 | "author": "Liam Curry ", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/liamcurry/gql/issues" 17 | }, 18 | "homepage": "https://github.com/liamcurry/gql/tree/master/packages/gql-format#readme", 19 | "dependencies": { 20 | "commander": "^2.9.0", 21 | "graphql": "0.9.2" 22 | }, 23 | "devDependencies": { 24 | "ava": "^0.19.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/gql-format/src/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* @flow */ 3 | import commander from 'commander' 4 | import { getDescription } from 'graphql/utilities/buildASTSchema' 5 | import { parse, visit } from 'graphql/language' 6 | import { readFileGlob, readFilePaths, writeFileObjects, } from 'gql-utils' 7 | import { version, description, } from '../package.json' 8 | 9 | export default { 10 | formatString, 11 | formatFileGlob, 12 | formatFileObjects, 13 | formatString, 14 | } 15 | 16 | /** 17 | * Find files matching the input glob, format them, and overwrite the originals. 18 | * @param {string} fileGlob - A glob pattern to find files, e.g. '*.graphql' 19 | * @return {Promise} The write files promise 20 | */ 21 | export async function formatFileGlob(fileGlob: string) { 22 | const fileObjects = await readFileGlob(fileGlob) 23 | return await formatFileObjects(fileObjects) 24 | } 25 | 26 | /** 27 | * Find files based on paths, format them, and overwrite the originals. 28 | * @param {string[]} filePaths - An array of file paths to look for. 29 | * @return {Promise} The write files promise 30 | */ 31 | export async function formatFilePaths(filePaths: string[]) { 32 | const fileObjects = await readFilePaths(filePaths) 33 | return await formatFileObjects(fileObjects) 34 | } 35 | 36 | /** 37 | * Formats file contents and saves the result to the file path. 38 | * @param {{filePath: string, fileContents: string}[]} - An array of file paths 39 | * and content. 40 | * @return {Promise} The write files promise 41 | */ 42 | export async function formatFileObjects(fileObjects : {filePath : string, fileContents : string}[]) { 43 | const fileObjectsFormatted = fileObjects.map(({filePath, fileContents}) => ({ 44 | filePath, 45 | fileContents: formatString(fileContents), 46 | })) 47 | return await writeFileObjects(fileObjectsFormatted) 48 | } 49 | 50 | /** 51 | * Format a GraphQL schema string. 52 | * @param {string} schemaStr - The raw GraphQL string to format. 53 | * @return {string} The formatted string. 54 | */ 55 | export function formatString(schemaStr: string): string { 56 | const schemaAst: Document = parse(schemaStr) 57 | return formatAst(schemaAst) 58 | } 59 | /** 60 | * Converts an AST into a string, using one set of reasonable 61 | * formatting rules. 62 | */ 63 | export function formatAst(ast) { 64 | return visit(ast, { leave: printDocASTReducer }); 65 | } 66 | 67 | const printDocASTReducer = { 68 | Name: node => node.value, 69 | Variable: node => '$' + node.name, 70 | 71 | // Document 72 | 73 | Document: node => join(node.definitions, '\n\n') + '\n', 74 | 75 | OperationDefinition(node) { 76 | const op = node.operation; 77 | const name = node.name; 78 | const varDefs = wrap('(', join(node.variableDefinitions, ', '), ')'); 79 | const directives = join(node.directives, ' '); 80 | const selectionSet = node.selectionSet; 81 | // Anonymous queries with no directives or variable definitions can use 82 | // the query short form. 83 | return !name && !directives && !varDefs && op === 'query' ? 84 | selectionSet : 85 | join([ op, join([ name, varDefs ]), directives, selectionSet ], ' '); 86 | }, 87 | 88 | VariableDefinition: ({ variable, type, defaultValue }) => 89 | variable + ': ' + type + wrap(' = ', defaultValue), 90 | 91 | SelectionSet: ({ selections }) => block(selections), 92 | 93 | Field: ({ alias, name, arguments: args, directives, selectionSet }) => 94 | join([ 95 | wrap('', alias, ': ') + name + wrap('(', join(args, ', '), ')'), 96 | join(directives, ' '), 97 | selectionSet 98 | ], ' '), 99 | 100 | Argument: ({ name, value }) => name + ': ' + value, 101 | 102 | // Fragments 103 | 104 | FragmentSpread: ({ name, directives }) => 105 | '...' + name + wrap(' ', join(directives, ' ')), 106 | 107 | InlineFragment: ({ typeCondition, directives, selectionSet }) => 108 | join([ 109 | '...', 110 | wrap('on ', typeCondition), 111 | join(directives, ' '), 112 | selectionSet 113 | ], ' '), 114 | 115 | FragmentDefinition: ({ name, typeCondition, directives, selectionSet }) => 116 | `fragment ${name} on ${typeCondition} ` + 117 | wrap('', join(directives, ' '), ' ') + 118 | selectionSet, 119 | 120 | // Value 121 | 122 | IntValue: ({ value }) => value, 123 | FloatValue: ({ value }) => value, 124 | StringValue: ({ value }) => JSON.stringify(value), 125 | BooleanValue: ({ value }) => JSON.stringify(value), 126 | NullValue: () => 'null', 127 | EnumValue: ({ value }) => value, 128 | ListValue: ({ values }) => '[' + join(values, ', ') + ']', 129 | ObjectValue: ({ fields }) => '{' + join(fields, ', ') + '}', 130 | ObjectField: ({ name, value }) => name + ': ' + value, 131 | 132 | // Directive 133 | 134 | Directive: ({ name, arguments: args }) => 135 | '@' + name + wrap('(', join(args, ', '), ')'), 136 | 137 | // Type 138 | 139 | NamedType: ({ name }) => name, 140 | ListType: ({ type }) => '[' + type + ']', 141 | NonNullType: ({ type }) => type + '!', 142 | 143 | // Type System Definitions 144 | 145 | SchemaDefinition: ({ directives, operationTypes }) => 146 | join([ 147 | 'schema', 148 | join(directives, ' '), 149 | block(operationTypes), 150 | ], ' '), 151 | 152 | OperationTypeDefinition: ({ operation, type }) => 153 | operation + ': ' + type, 154 | 155 | ScalarTypeDefinition: ({ name, directives }) => 156 | join([ 'scalar', name, join(directives, ' ') ], ' '), 157 | 158 | ObjectTypeDefinition: ({ name, interfaces, directives, fields }) => 159 | join([ 160 | 'type', 161 | name, 162 | wrap('implements ', join(interfaces, ', ')), 163 | join(directives, ' '), 164 | block(fields) 165 | ], ' '), 166 | 167 | FieldDefinition: ({ name, arguments: args, type, directives }) => 168 | name + 169 | wrap('(', join(args, ', '), ')') + 170 | ': ' + type + 171 | wrap(' ', join(directives, ' ')), 172 | 173 | InputValueDefinition: ({ name, type, defaultValue, directives }) => 174 | join([ 175 | name + ': ' + type, 176 | wrap('= ', defaultValue), 177 | join(directives, ' ') 178 | ], ' '), 179 | 180 | InterfaceTypeDefinition: ({ name, directives, fields }) => 181 | join([ 182 | 'interface', 183 | name, 184 | join(directives, ' '), 185 | block(fields) 186 | ], ' '), 187 | 188 | UnionTypeDefinition: ({ name, directives, types }) => 189 | join([ 190 | 'union', 191 | name, 192 | join(directives, ' '), 193 | '= ' + join(types, ' | ') 194 | ], ' '), 195 | 196 | EnumTypeDefinition: ({ name, directives, values }) => 197 | join([ 198 | 'enum', 199 | name, 200 | join(directives, ' '), 201 | block(values) 202 | ], ' '), 203 | 204 | EnumValueDefinition: ({ name, directives }) => 205 | join([ name, join(directives, ' ') ], ' '), 206 | 207 | InputObjectTypeDefinition: ({ name, directives, fields }) => 208 | join([ 209 | 'input', 210 | name, 211 | join(directives, ' '), 212 | block(fields) 213 | ], ' '), 214 | 215 | TypeExtensionDefinition: ({ definition }) => `extend ${definition}`, 216 | 217 | DirectiveDefinition: ({ name, arguments: args, locations }) => 218 | 'directive @' + name + wrap('(', join(args, ', '), ')') + 219 | ' on ' + join(locations, ' | '), 220 | }; 221 | 222 | 223 | Object 224 | .keys(printDocASTReducer) 225 | .filter(key => key !== 'Name') 226 | .forEach(key => { 227 | const fn = printDocASTReducer[key]; 228 | printDocASTReducer[key] = withDescription(fn); 229 | }); 230 | 231 | 232 | function withDescription(fn) { 233 | return function (node) { 234 | const desc = getDescription(node); 235 | const descText = desc ? '# ' + desc + '\n' : ''; 236 | return descText + fn(node); 237 | } 238 | } 239 | 240 | /** 241 | * Given maybeArray, print an empty string if it is null or empty, otherwise 242 | * print all items together separated by separator if provided 243 | */ 244 | function join(maybeArray, separator) { 245 | return maybeArray ? maybeArray.filter(x => x).join(separator || '') : ''; 246 | } 247 | 248 | /** 249 | * Given array, print each item on its own line, wrapped in an 250 | * indented "{ }" block. 251 | */ 252 | function block(array) { 253 | return array && array.length !== 0 ? 254 | indent('{\n' + join(array, '\n')) + '\n}' : 255 | '{}'; 256 | } 257 | 258 | /** 259 | * If maybeString is not null or empty, then wrap with start and end, otherwise 260 | * print an empty string. 261 | */ 262 | function wrap(start, maybeString, end) { 263 | return maybeString ? 264 | start + maybeString + (end || '') : 265 | ''; 266 | } 267 | 268 | function indent(maybeString) { 269 | return maybeString && maybeString.replace(/\n/g, '\n '); 270 | } 271 | 272 | /** 273 | * The command-line interface for formatting GraphQL files. If this module is 274 | * being imported, it will register itself as a commander command 'format'. 275 | * Otherwise, it will run the CLI. 276 | * @param program - The commander object to modify. 277 | */ 278 | export async function cli(program=commander) { 279 | if (!module.parent) { 280 | program 281 | .version(version) 282 | .usage('[options] ') 283 | 284 | cliAddHelp(cliAddBasics(program)) 285 | 286 | program.parse(process.argv) 287 | await cliAction(program, program.args) 288 | } else { 289 | const command = program.command('format ') 290 | cliAddHelp(cliAddBasics(command)) 291 | command.action(async (inputGlob, options) => { 292 | await cliAction(command, inputGlob.split(' ')) 293 | }) 294 | } 295 | } 296 | 297 | function cliAddBasics(command) { 298 | return command.description(description) 299 | } 300 | 301 | function cliAddHelp(command) { 302 | const commandName = 303 | !module.parent 304 | ? 'gql-format' 305 | : 'gql format' 306 | return command.on('--help', () => console.log(` Examples: 307 | 308 | $ ${commandName} **/*.graphql 309 | $ ${commandName} dir1/*.graphql dir2/*.graphql 310 | `)) 311 | } 312 | 313 | async function cliAction(program, fileGlobs=[]) { 314 | if (!fileGlobs.length) { 315 | return program.help() 316 | } 317 | const formatFilePromises = fileGlobs.map(formatFileGlob) 318 | return await Promise.all(formatFilePromises) 319 | } 320 | 321 | if (!module.parent) { 322 | cli() 323 | } 324 | -------------------------------------------------------------------------------- /packages/gql-format/test/index.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import {formatString} from '../dist' 3 | 4 | 5 | test('formatString', async t => { 6 | const input = `type Foo { 7 | bar: String 8 | } 9 | 10 | 11 | 12 | # Foo is a thing 13 | type Foo { 14 | 15 | # Baz is a string 16 | baz: String 17 | 18 | }` 19 | const result = formatString(input) 20 | 21 | const expected = `type Foo { 22 | bar: String 23 | } 24 | 25 | # Foo is a thing 26 | type Foo { 27 | # Baz is a string 28 | baz: String 29 | } 30 | `; 31 | t.is(result, expected) 32 | }) 33 | -------------------------------------------------------------------------------- /packages/gql-lint/.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | src 3 | -------------------------------------------------------------------------------- /packages/gql-lint/README.md: -------------------------------------------------------------------------------- 1 | Coming soon. 2 | -------------------------------------------------------------------------------- /packages/gql-lint/dist/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; -------------------------------------------------------------------------------- /packages/gql-lint/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gql-lint", 3 | "version": "0.0.2", 4 | "description": "Lint GraphQL documents for errors", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC" 11 | } 12 | -------------------------------------------------------------------------------- /packages/gql-lint/src/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liamcurry/gql/0a28f69cc80084d648a95d1ea24a834d737235f6/packages/gql-lint/src/index.js -------------------------------------------------------------------------------- /packages/gql-lint/test/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liamcurry/gql/0a28f69cc80084d648a95d1ea24a834d737235f6/packages/gql-lint/test/index.js -------------------------------------------------------------------------------- /packages/gql-merge/.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | src 3 | -------------------------------------------------------------------------------- /packages/gql-merge/README.md: -------------------------------------------------------------------------------- 1 | # gql-merge 2 | 3 | [![standard-readme compliant](https://img.shields.io/badge/standard--readme-OK-green.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme) 4 | [![npm](https://img.shields.io/npm/v/gql-merge.svg?style=flat-square)](https://www.npmjs.com/package/gql-merge) 5 | [![npm](https://img.shields.io/npm/dm/gql-merge.svg?style=flat-square)](https://www.npmjs.com/package/gql-merge) 6 | [![npm](https://img.shields.io/npm/l/gql-merge.svg?style=flat-square)](https://www.npmjs.com/package/gql-merge) 7 | 8 | > Tools for merging GraphQL documents 9 | 10 | ## Table of Contents 11 | 12 | - [Background](#background) 13 | - [Installation](#installation) 14 | - [CLI](#cli) 15 | - [API](#api) 16 | 17 | ## Background 18 | 19 | 20 | This repo contains tools for merging definitions into multiple GraphQL documents 21 | into one. For example, say you have these two files GraphQL files: 22 | 23 | ```graphql 24 | type Post { 25 | id: ID! 26 | content: String 27 | } 28 | 29 | type Query { 30 | postById(id: ID!): Post 31 | } 32 | ``` 33 | 34 | ```graphql 35 | type Author { 36 | id: ID! 37 | name: String 38 | } 39 | 40 | type Query { 41 | postsByAuthorId(id: ID!): [Post] 42 | } 43 | ``` 44 | 45 | You can use the `gql-merge` CLI to combine these files into one: 46 | 47 | ``` 48 | $ gql-merge --out-file schema.graphql testdata/readme* 49 | ``` 50 | 51 | The resulting file would look like this: 52 | 53 | ```graphql 54 | type Post { 55 | id: ID! 56 | content: String 57 | } 58 | 59 | type Query { 60 | postById(id: ID!): Post 61 | postsByAuthorId(id: ID!): [Post] 62 | } 63 | 64 | type Author { 65 | id: ID! 66 | name: String 67 | } 68 | ``` 69 | 70 | ## Installation 71 | 72 | ``` 73 | $ npm i -g gql-merge 74 | ``` 75 | 76 | ## CLI 77 | 78 | ``` 79 | $ gql-merge -h 80 | 81 | Usage: gql-merge [options] 82 | 83 | Tools for merging GraphQL documents 84 | 85 | Options: 86 | 87 | -h, --help output usage information 88 | -V, --version output the version number 89 | -o, --out-file Output GraphQL file, otherwise use stdout 90 | -v, --verbose Enable verbose logging 91 | 92 | Examples: 93 | 94 | $ gql-merge **/*.graphql > schema.graphql 95 | $ gql-merge -o schema.graphql **/*.graphql 96 | $ gql-merge dir1/*.graphql dir2/*.graphql > schema.graphql 97 | 98 | ``` 99 | 100 | ## API 101 | 102 | You can also import the package. The following example merges all files living in the `data/types` folder. 103 | 104 | ``` 105 | import fs from 'fs'; 106 | import path from 'path'; 107 | import { mergeStrings } from 'gql-merge'; 108 | 109 | const typesDir = path.resolve(__dirname, 'data/types'); 110 | const typeFiles = fs.readdirSync(typesDir); 111 | const types = typeFiles.map(file => fs.readFileSync(path.join(typesDir, file), 'utf-8')); 112 | const typeDefs = mergeStrings(types); 113 | ``` 114 | 115 | More detailed docs coming soon. 116 | 117 | ### `mergeFileGlob` 118 | 119 | ### `mergeFilePaths` 120 | 121 | ### `mergeStrings` 122 | 123 | ### `mergeString` 124 | 125 | ### `mergeAst` 126 | -------------------------------------------------------------------------------- /packages/gql-merge/dist/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | Object.defineProperty(exports, "__esModule", { 5 | value: true 6 | }); 7 | exports.cliAction = exports.cli = exports.mergeFilePaths = exports.mergeFileGlob = undefined; 8 | 9 | var _promise = require('babel-runtime/core-js/promise'); 10 | 11 | var _promise2 = _interopRequireDefault(_promise); 12 | 13 | var _values = require('babel-runtime/core-js/object/values'); 14 | 15 | var _values2 = _interopRequireDefault(_values); 16 | 17 | var _regenerator = require('babel-runtime/regenerator'); 18 | 19 | var _regenerator2 = _interopRequireDefault(_regenerator); 20 | 21 | var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); 22 | 23 | var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); 24 | 25 | /** 26 | * Find GraphQL files based on a glob pattern and merge the results. 27 | * @param {string} fileGlob - A glob pattern to find files, e.g. '*.graphql' 28 | * @return {Promise} A promise of the resulting string. 29 | */ 30 | var mergeFileGlob = exports.mergeFileGlob = function () { 31 | var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(fileGlob) { 32 | var fileDetails, fileContents; 33 | return _regenerator2.default.wrap(function _callee$(_context) { 34 | while (1) { 35 | switch (_context.prev = _context.next) { 36 | case 0: 37 | _context.next = 2; 38 | return (0, _gqlUtils.readFileGlob)(fileGlob); 39 | 40 | case 2: 41 | fileDetails = _context.sent; 42 | fileContents = fileDetails.map(function (f) { 43 | return f.fileContents; 44 | }); 45 | return _context.abrupt('return', mergeStrings(fileContents)); 46 | 47 | case 5: 48 | case 'end': 49 | return _context.stop(); 50 | } 51 | } 52 | }, _callee, this); 53 | })); 54 | 55 | return function mergeFileGlob(_x) { 56 | return _ref.apply(this, arguments); 57 | }; 58 | }(); 59 | 60 | /** 61 | * Find GraphQL files based on a glob pattern and merge the results. 62 | * @param {string} fileGlob - A glob pattern to find files, e.g. '*.graphql' 63 | * @return {Promise} A promise of the resulting string. 64 | */ 65 | 66 | 67 | var mergeFilePaths = exports.mergeFilePaths = function () { 68 | var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(filePaths) { 69 | var fileDetails, fileContents; 70 | return _regenerator2.default.wrap(function _callee2$(_context2) { 71 | while (1) { 72 | switch (_context2.prev = _context2.next) { 73 | case 0: 74 | _context2.next = 2; 75 | return (0, _gqlUtils.readFilePaths)(filePaths); 76 | 77 | case 2: 78 | fileDetails = _context2.sent; 79 | fileContents = fileDetails.map(function (f) { 80 | return f.fileContents; 81 | }); 82 | return _context2.abrupt('return', mergeStrings(fileContents)); 83 | 84 | case 5: 85 | case 'end': 86 | return _context2.stop(); 87 | } 88 | } 89 | }, _callee2, this); 90 | })); 91 | 92 | return function mergeFilePaths(_x2) { 93 | return _ref2.apply(this, arguments); 94 | }; 95 | }(); 96 | 97 | /** 98 | * Merges an array of GraphQL strings into one 99 | * @param {string[]} schemaStrs - An array of GraphQL strings. 100 | * @return {string} The resulting merged GraphQL string. 101 | */ 102 | 103 | 104 | var cli = exports.cli = function () { 105 | var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4() { 106 | var _this = this; 107 | 108 | var program = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _commander2.default; 109 | var command; 110 | return _regenerator2.default.wrap(function _callee4$(_context4) { 111 | while (1) { 112 | switch (_context4.prev = _context4.next) { 113 | case 0: 114 | if (module.parent) { 115 | _context4.next = 8; 116 | break; 117 | } 118 | 119 | program.version(_package.version).usage('[options] '); 120 | 121 | cliAddHelp(cliAddBasics(program)); 122 | 123 | program.parse(process.argv); 124 | _context4.next = 6; 125 | return cliAction(program, program.args, program); 126 | 127 | case 6: 128 | _context4.next = 11; 129 | break; 130 | 131 | case 8: 132 | command = program.command('merge '); 133 | 134 | cliAddHelp(cliAddBasics(command)); 135 | command.action(function () { 136 | var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(inputGlob, options) { 137 | return _regenerator2.default.wrap(function _callee3$(_context3) { 138 | while (1) { 139 | switch (_context3.prev = _context3.next) { 140 | case 0: 141 | _context3.next = 2; 142 | return cliAction(command, inputGlob.split(' '), options); 143 | 144 | case 2: 145 | case 'end': 146 | return _context3.stop(); 147 | } 148 | } 149 | }, _callee3, _this); 150 | })); 151 | 152 | return function (_x4, _x5) { 153 | return _ref4.apply(this, arguments); 154 | }; 155 | }()); 156 | 157 | case 11: 158 | case 'end': 159 | return _context4.stop(); 160 | } 161 | } 162 | }, _callee4, this); 163 | })); 164 | 165 | return function cli() { 166 | return _ref3.apply(this, arguments); 167 | }; 168 | }(); 169 | 170 | var cliAction = exports.cliAction = function () { 171 | var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(program) { 172 | var fileGlobs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; 173 | var _ref6 = arguments[2]; 174 | var outFile = _ref6.outFile; 175 | var mergeGlobsPromises, schemaStrs, schemaStr; 176 | return _regenerator2.default.wrap(function _callee5$(_context5) { 177 | while (1) { 178 | switch (_context5.prev = _context5.next) { 179 | case 0: 180 | if (fileGlobs.length) { 181 | _context5.next = 2; 182 | break; 183 | } 184 | 185 | return _context5.abrupt('return', program.help()); 186 | 187 | case 2: 188 | mergeGlobsPromises = fileGlobs.map(mergeFileGlob); 189 | _context5.next = 5; 190 | return _promise2.default.all(mergeGlobsPromises); 191 | 192 | case 5: 193 | schemaStrs = _context5.sent; 194 | schemaStr = mergeStrings(schemaStrs); 195 | 196 | if (!outFile) { 197 | _context5.next = 12; 198 | break; 199 | } 200 | 201 | _context5.next = 10; 202 | return (0, _gqlUtils.writeFileObject)({ 203 | filePath: outFile, 204 | fileContents: schemaStr 205 | }); 206 | 207 | case 10: 208 | _context5.next = 13; 209 | break; 210 | 211 | case 12: 212 | console.log(schemaStr); 213 | 214 | case 13: 215 | case 'end': 216 | return _context5.stop(); 217 | } 218 | } 219 | }, _callee5, this); 220 | })); 221 | 222 | return function cliAction(_x6) { 223 | return _ref5.apply(this, arguments); 224 | }; 225 | }(); 226 | 227 | exports.mergeStrings = mergeStrings; 228 | exports.mergeString = mergeString; 229 | exports.mergeAst = mergeAst; 230 | 231 | var _commander = require('commander'); 232 | 233 | var _commander2 = _interopRequireDefault(_commander); 234 | 235 | var _language = require('graphql/language'); 236 | 237 | var _gqlFormat = require('gql-format'); 238 | 239 | var _gqlUtils = require('gql-utils'); 240 | 241 | var _package = require('../package.json'); 242 | 243 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 244 | 245 | exports.default = { 246 | mergeFileGlob: mergeFileGlob, 247 | mergeFilePaths: mergeFilePaths, 248 | mergeStrings: mergeStrings, 249 | mergeString: mergeString, 250 | mergeAst: mergeAst 251 | }; 252 | function mergeStrings(schemaStrs) { 253 | var schemaStr = schemaStrs.join('\n\n'); 254 | return mergeString(schemaStr); 255 | } 256 | 257 | /** 258 | * Merges duplicate definitions in a single GraphQL string 259 | * @param {string} schemaStr - The GraphQL String. 260 | * @return {string} The resulting merged GraphQL string. 261 | */ 262 | function mergeString(schemaStr) { 263 | var schemaAst = (0, _language.parse)(schemaStr); 264 | return mergeAst(schemaAst); 265 | } 266 | 267 | /** 268 | * Merges duplicate definitions in a single GraphQL abstract-syntax tree 269 | * @param {Document} schemaAst - The GraphQL AST. 270 | * @return {string} The resulting merged GraphQL string. 271 | */ 272 | function mergeAst(schemaAst) { 273 | var typeDefs = {}; 274 | 275 | // Go through the AST and extract/merge type definitions. 276 | var editedAst = (0, _language.visit)(schemaAst, { 277 | enter: function enter(node) { 278 | var nodeName = node.name ? node.name.value : null; 279 | 280 | // Don't transform TypeDefinitions directly 281 | if (!nodeName || !node.kind.endsWith('TypeDefinition')) { 282 | return; 283 | } 284 | 285 | var oldNode = typeDefs[nodeName]; 286 | 287 | if (!oldNode) { 288 | // First time seeing this type so just store the value. 289 | typeDefs[nodeName] = node; 290 | return null; 291 | } 292 | 293 | // This type is defined multiple times, so merge the fields and values. 294 | var concatProps = ['fields', 'values', 'types']; 295 | concatProps.forEach(function (propName) { 296 | if (node[propName] && oldNode[propName]) { 297 | node[propName] = oldNode[propName].concat(node[propName]); 298 | } 299 | }); 300 | 301 | typeDefs[nodeName] = node; 302 | return null; 303 | } 304 | }); 305 | 306 | var remainingNodesStr = (0, _gqlFormat.formatAst)(editedAst); 307 | var typeDefsStr = (0, _values2.default)(typeDefs).map(_gqlFormat.formatAst).join('\n'); 308 | var fullSchemaStr = remainingNodesStr + '\n\n' + typeDefsStr; 309 | 310 | return (0, _gqlFormat.formatString)(fullSchemaStr); 311 | } 312 | 313 | function cliAddBasics(command) { 314 | return command.description(_package.description).option('-o, --out-file ', 'Output GraphQL file, otherwise use stdout'); 315 | } 316 | 317 | function cliAddHelp(command) { 318 | var commandName = !module.parent ? 'gql-merge' : 'gql merge'; 319 | return command.on('--help', function () { 320 | return console.log(' Examples:\n $ ' + commandName + ' **/*.graphql > schema.graphql\n $ ' + commandName + ' -o schema.graphql **/*.graphql\n $ ' + commandName + ' dir1/*.graphql dir2/*.graphql > schema.graphql\n '); 321 | }); 322 | } 323 | 324 | if (!module.parent) { 325 | cli(); 326 | } -------------------------------------------------------------------------------- /packages/gql-merge/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gql-merge", 3 | "version": "0.0.6", 4 | "description": "Tools for merging GraphQL documents", 5 | "main": "dist/index.js", 6 | "bin": { 7 | "gql-merge": "dist/index.js" 8 | }, 9 | "repository": "https://github.com/liamcurry/gql/tree/master/packages/gql-merge", 10 | "author": "Liam Curry ", 11 | "license": "MIT", 12 | "bugs": { 13 | "url": "https://github.com/liamcurry/gql/issues" 14 | }, 15 | "scripts": { 16 | "test": "ava" 17 | }, 18 | "homepage": "https://github.com/liamcurry/gql/tree/master/packages/gql-merge#readme", 19 | "dependencies": { 20 | "commander": "^2.9.0", 21 | "gql-format": "^0.0.5", 22 | "gql-utils": "^0.0.2", 23 | "graphql": "0.9.2" 24 | }, 25 | "devDependencies": { 26 | "ava": "^0.19.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/gql-merge/src/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* @flow */ 3 | import commander from 'commander' 4 | import {parse,visit,} from 'graphql/language' 5 | import {formatString, formatAst} from 'gql-format' 6 | import {readFileGlob, readFilePaths, writeFileObject,} from 'gql-utils' 7 | import {version, description,} from '../package.json' 8 | 9 | export default { 10 | mergeFileGlob, 11 | mergeFilePaths, 12 | mergeStrings, 13 | mergeString, 14 | mergeAst, 15 | } 16 | 17 | /** 18 | * Find GraphQL files based on a glob pattern and merge the results. 19 | * @param {string} fileGlob - A glob pattern to find files, e.g. '*.graphql' 20 | * @return {Promise} A promise of the resulting string. 21 | */ 22 | export async function mergeFileGlob(fileGlob: string): Promise { 23 | const fileDetails = await readFileGlob(fileGlob) 24 | const fileContents = fileDetails.map(f => f.fileContents) 25 | return mergeStrings(fileContents) 26 | } 27 | 28 | /** 29 | * Find GraphQL files based on a glob pattern and merge the results. 30 | * @param {string} fileGlob - A glob pattern to find files, e.g. '*.graphql' 31 | * @return {Promise} A promise of the resulting string. 32 | */ 33 | export async function mergeFilePaths(filePaths: string[]): Promise { 34 | const fileDetails = await readFilePaths(filePaths) 35 | const fileContents = fileDetails.map(f => f.fileContents) 36 | return mergeStrings(fileContents) 37 | } 38 | 39 | /** 40 | * Merges an array of GraphQL strings into one 41 | * @param {string[]} schemaStrs - An array of GraphQL strings. 42 | * @return {string} The resulting merged GraphQL string. 43 | */ 44 | export function mergeStrings(schemaStrs: string[]): string { 45 | const schemaStr: string = schemaStrs.join('\n\n') 46 | return mergeString(schemaStr) 47 | } 48 | 49 | /** 50 | * Merges duplicate definitions in a single GraphQL string 51 | * @param {string} schemaStr - The GraphQL String. 52 | * @return {string} The resulting merged GraphQL string. 53 | */ 54 | export function mergeString(schemaStr: string): string { 55 | const schemaAst: Document = parse(schemaStr) 56 | return mergeAst(schemaAst) 57 | } 58 | 59 | /** 60 | * Merges duplicate definitions in a single GraphQL abstract-syntax tree 61 | * @param {Document} schemaAst - The GraphQL AST. 62 | * @return {string} The resulting merged GraphQL string. 63 | */ 64 | export function mergeAst(schemaAst: Document): string { 65 | const typeDefs = {}; 66 | 67 | // Go through the AST and extract/merge type definitions. 68 | const editedAst: Document = visit(schemaAst, { 69 | enter(node) { 70 | const nodeName = node.name ? node.name.value : null 71 | 72 | // Don't transform TypeDefinitions directly 73 | if (!nodeName || !node.kind.endsWith('TypeDefinition')) { 74 | return 75 | } 76 | 77 | const oldNode = typeDefs[nodeName] 78 | 79 | if (!oldNode) { 80 | // First time seeing this type so just store the value. 81 | typeDefs[nodeName] = node 82 | return null 83 | } 84 | 85 | // This type is defined multiple times, so merge the fields and values. 86 | const concatProps = ['fields', 'values', 'types'] 87 | concatProps.forEach(propName => { 88 | if (node[propName] && oldNode[propName]) { 89 | node[propName] = oldNode[propName].concat(node[propName]) 90 | } 91 | }) 92 | 93 | typeDefs[nodeName] = node 94 | return null 95 | } 96 | }) 97 | 98 | const remainingNodesStr = formatAst(editedAst) 99 | const typeDefsStr = Object.values(typeDefs).map(formatAst).join('\n') 100 | const fullSchemaStr = `${remainingNodesStr}\n\n${typeDefsStr}` 101 | 102 | return formatString(fullSchemaStr) 103 | } 104 | 105 | export async function cli(program=commander) { 106 | if (!module.parent) { 107 | program 108 | .version(version) 109 | .usage('[options] ') 110 | 111 | cliAddHelp(cliAddBasics(program)) 112 | 113 | program.parse(process.argv) 114 | await cliAction(program, program.args, program) 115 | } else { 116 | const command = program.command('merge ') 117 | cliAddHelp(cliAddBasics(command)) 118 | command.action(async (inputGlob, options) => { 119 | await cliAction(command, inputGlob.split(' '), options) 120 | }) 121 | } 122 | } 123 | 124 | function cliAddBasics(command) { 125 | return command 126 | .description(description) 127 | .option('-o, --out-file ', 'Output GraphQL file, otherwise use stdout') 128 | } 129 | 130 | function cliAddHelp(command) { 131 | const commandName = 132 | !module.parent 133 | ? 'gql-merge' 134 | : 'gql merge' 135 | return command.on('--help', () => console.log(` Examples: 136 | $ ${commandName} **/*.graphql > schema.graphql 137 | $ ${commandName} -o schema.graphql **/*.graphql 138 | $ ${commandName} dir1/*.graphql dir2/*.graphql > schema.graphql 139 | `)) 140 | } 141 | 142 | export async function cliAction(program, fileGlobs=[], {outFile}) { 143 | if (!fileGlobs.length) { 144 | return program.help() 145 | } 146 | 147 | const mergeGlobsPromises = fileGlobs.map(mergeFileGlob) 148 | const schemaStrs = await Promise.all(mergeGlobsPromises) 149 | const schemaStr = mergeStrings(schemaStrs) 150 | 151 | if (outFile) { 152 | await writeFileObject({ 153 | filePath: outFile, 154 | fileContents: schemaStr, 155 | }) 156 | } else { 157 | console.log(schemaStr) 158 | } 159 | } 160 | 161 | if (!module.parent) { 162 | cli() 163 | } 164 | -------------------------------------------------------------------------------- /packages/gql-merge/test/index.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import {formatString} from 'gql-format' 3 | import { 4 | mergeString, 5 | mergeStrings, 6 | } from '../dist' 7 | 8 | // test('mergeGlob', async t => { 9 | // try { 10 | // await mergeGlob('test/**/*.graphql') 11 | // } catch(e) { 12 | // t.fail('failed') 13 | // } 14 | // }) 15 | 16 | test('mergeString', async t => { 17 | const input = `type Foo { 18 | bar: String 19 | } 20 | 21 | type Foo { 22 | baz: String 23 | }` 24 | const result = await mergeString(input) 25 | const expected = formatString(`type Foo { 26 | bar: String 27 | baz: String 28 | }`) 29 | t.is(result, formatString(expected)) 30 | }) 31 | 32 | 33 | test('mergeStrings', async t => { 34 | const schema1 = `type Foo { 35 | bar: String 36 | }` 37 | 38 | const schema2 = `type Foo { 39 | baz: String 40 | }` 41 | 42 | const schema3 = `type Query { 43 | hello(input: String): String 44 | }` 45 | const result = await mergeStrings([schema1, schema2, schema3]) 46 | const expected = formatString(`type Foo { 47 | bar: String 48 | baz: String 49 | } 50 | 51 | type Query { 52 | hello(input: String): String 53 | }`) 54 | t.is(result, expected) 55 | }) 56 | 57 | 58 | test('commentDescriptions', async t => { 59 | const schema1 = ` 60 | # This should have a comment description 61 | type ThingWithDesc { 62 | # Bar is a string 63 | bar: String 64 | }` 65 | 66 | const schema2 = ` 67 | # This should have a comment description 68 | type ThingWithDesc { 69 | # Foo is an int 70 | foo: Int 71 | }` 72 | 73 | const result = await mergeStrings([schema1, schema2]) 74 | const expected = formatString(` 75 | # This should have a comment description 76 | type ThingWithDesc { 77 | # Bar is a string 78 | bar: String 79 | # Foo is an int 80 | foo: Int 81 | }`) 82 | t.is(result, expected) 83 | }) 84 | 85 | 86 | test('enums', async t => { 87 | const schema1 = ` 88 | # This should have a comment description 89 | enum Thing { 90 | # Thing1 is a thing 91 | Thing1 92 | }` 93 | 94 | const schema2 = ` 95 | # This should have a comment description 96 | enum Thing { 97 | # Thing2 is a thing 98 | Thing2 99 | }` 100 | 101 | const result = await mergeStrings([schema1, schema2]) 102 | const expected = formatString(` 103 | # This should have a comment description 104 | enum Thing { 105 | # Thing1 is a thing 106 | Thing1 107 | # Thing2 is a thing 108 | Thing2 109 | }`) 110 | t.is(result, expected) 111 | }) 112 | 113 | 114 | test('unions', async t => { 115 | const schema1 = ` 116 | # This should have a comment description 117 | union Thing 118 | = Thing1` 119 | 120 | const schema2 = ` 121 | # This should have a comment description 122 | union Thing 123 | = Thing2` 124 | 125 | const result = await mergeStrings([schema1, schema2]) 126 | const expected = formatString(` 127 | # This should have a comment description 128 | union Thing = Thing1 | Thing2`) 129 | t.is(result, expected) 130 | }) 131 | -------------------------------------------------------------------------------- /packages/gql-utils/.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | src 3 | -------------------------------------------------------------------------------- /packages/gql-utils/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liamcurry/gql/0a28f69cc80084d648a95d1ea24a834d737235f6/packages/gql-utils/README.md -------------------------------------------------------------------------------- /packages/gql-utils/dist/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.writeFileObject = exports.writeFileObjects = exports.readFilePath = exports.readFilePaths = exports.readFileGlob = undefined; 7 | 8 | var _promise = require('babel-runtime/core-js/promise'); 9 | 10 | var _promise2 = _interopRequireDefault(_promise); 11 | 12 | var _regenerator = require('babel-runtime/regenerator'); 13 | 14 | var _regenerator2 = _interopRequireDefault(_regenerator); 15 | 16 | var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); 17 | 18 | var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); 19 | 20 | var readFileGlob = exports.readFileGlob = function () { 21 | var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(fileGlob) { 22 | var filePaths; 23 | return _regenerator2.default.wrap(function _callee$(_context) { 24 | while (1) { 25 | switch (_context.prev = _context.next) { 26 | case 0: 27 | _context.next = 2; 28 | return globAsync(fileGlob); 29 | 30 | case 2: 31 | filePaths = _context.sent; 32 | return _context.abrupt('return', readFilePaths(filePaths)); 33 | 34 | case 4: 35 | case 'end': 36 | return _context.stop(); 37 | } 38 | } 39 | }, _callee, this); 40 | })); 41 | 42 | return function readFileGlob(_x) { 43 | return _ref.apply(this, arguments); 44 | }; 45 | }(); 46 | 47 | var readFilePaths = exports.readFilePaths = function () { 48 | var _ref2 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(filePaths) { 49 | var fileReads; 50 | return _regenerator2.default.wrap(function _callee2$(_context2) { 51 | while (1) { 52 | switch (_context2.prev = _context2.next) { 53 | case 0: 54 | fileReads = filePaths.map(readFilePath); 55 | _context2.next = 3; 56 | return _promise2.default.all(fileReads); 57 | 58 | case 3: 59 | return _context2.abrupt('return', _context2.sent); 60 | 61 | case 4: 62 | case 'end': 63 | return _context2.stop(); 64 | } 65 | } 66 | }, _callee2, this); 67 | })); 68 | 69 | return function readFilePaths(_x2) { 70 | return _ref2.apply(this, arguments); 71 | }; 72 | }(); 73 | 74 | var readFilePath = exports.readFilePath = function () { 75 | var _ref3 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(filePath) { 76 | var fileContents; 77 | return _regenerator2.default.wrap(function _callee3$(_context3) { 78 | while (1) { 79 | switch (_context3.prev = _context3.next) { 80 | case 0: 81 | _context3.next = 2; 82 | return readFileAsync(filePath); 83 | 84 | case 2: 85 | fileContents = _context3.sent; 86 | return _context3.abrupt('return', { filePath: filePath, fileContents: fileContents.toString() }); 87 | 88 | case 4: 89 | case 'end': 90 | return _context3.stop(); 91 | } 92 | } 93 | }, _callee3, this); 94 | })); 95 | 96 | return function readFilePath(_x3) { 97 | return _ref3.apply(this, arguments); 98 | }; 99 | }(); 100 | 101 | var writeFileObjects = exports.writeFileObjects = function () { 102 | var _ref4 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(fileDetails) { 103 | var fileWrites; 104 | return _regenerator2.default.wrap(function _callee4$(_context4) { 105 | while (1) { 106 | switch (_context4.prev = _context4.next) { 107 | case 0: 108 | fileWrites = fileDetails.map(writeFileObject); 109 | _context4.next = 3; 110 | return _promise2.default.all(fileWrites); 111 | 112 | case 3: 113 | return _context4.abrupt('return', _context4.sent); 114 | 115 | case 4: 116 | case 'end': 117 | return _context4.stop(); 118 | } 119 | } 120 | }, _callee4, this); 121 | })); 122 | 123 | return function writeFileObjects(_x4) { 124 | return _ref4.apply(this, arguments); 125 | }; 126 | }(); 127 | 128 | var writeFileObject = exports.writeFileObject = function () { 129 | var _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(_ref6) { 130 | var filePath = _ref6.filePath, 131 | fileContents = _ref6.fileContents; 132 | return _regenerator2.default.wrap(function _callee5$(_context5) { 133 | while (1) { 134 | switch (_context5.prev = _context5.next) { 135 | case 0: 136 | _context5.next = 2; 137 | return writeFileAsync(filePath, fileContents); 138 | 139 | case 2: 140 | return _context5.abrupt('return', _context5.sent); 141 | 142 | case 3: 143 | case 'end': 144 | return _context5.stop(); 145 | } 146 | } 147 | }, _callee5, this); 148 | })); 149 | 150 | return function writeFileObject(_x5) { 151 | return _ref5.apply(this, arguments); 152 | }; 153 | }(); 154 | 155 | var _glob = require('glob'); 156 | 157 | var _glob2 = _interopRequireDefault(_glob); 158 | 159 | var _bluebird = require('bluebird'); 160 | 161 | var _fs = require('fs'); 162 | 163 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 164 | 165 | var readFileAsync = (0, _bluebird.promisify)(_fs.readFile); 166 | 167 | var writeFileAsync = (0, _bluebird.promisify)(_fs.writeFile); 168 | var globAsync = (0, _bluebird.promisify)(_glob2.default); 169 | 170 | exports.default = { 171 | readFileGlob: readFileGlob, 172 | readFilePaths: readFilePaths, 173 | readFilePath: readFilePath, 174 | writeFileObjects: writeFileObjects, 175 | writeFileObject: writeFileObject 176 | }; -------------------------------------------------------------------------------- /packages/gql-utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gql-utils", 3 | "version": "0.0.2", 4 | "description": "Utility functions used by gql", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Liam Curry ", 10 | "license": "MIT", 11 | "dependencies": { 12 | "bluebird": "^3.4.6", 13 | "glob": "^7.1.1" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/gql-utils/src/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import glob from 'glob' 3 | import {promisify} from 'bluebird' 4 | import {readFile, writeFile} from 'fs' 5 | 6 | const readFileAsync = promisify(readFile) 7 | const writeFileAsync = promisify(writeFile) 8 | const globAsync = promisify(glob) 9 | 10 | export default { 11 | readFileGlob, 12 | readFilePaths, 13 | readFilePath, 14 | writeFileObjects, 15 | writeFileObject, 16 | } 17 | 18 | export async function readFileGlob(fileGlob: string): Promise { 19 | const filePaths: string[] = await globAsync(fileGlob) 20 | return readFilePaths(filePaths) 21 | } 22 | 23 | export async function readFilePaths(filePaths: string[]): Promise { 24 | const fileReads = filePaths.map(readFilePath) 25 | return await Promise.all(fileReads) 26 | } 27 | 28 | export async function readFilePath(filePath: string): Promise { 29 | const fileContents = await readFileAsync(filePath) 30 | return {filePath, fileContents: fileContents.toString()} 31 | } 32 | 33 | export async function writeFileObjects(fileDetails): Promise { 34 | const fileWrites = fileDetails.map(writeFileObject) 35 | return await Promise.all(fileWrites) 36 | } 37 | 38 | export async function writeFileObject({filePath, fileContents}): Promise { 39 | return await writeFileAsync(filePath, fileContents) 40 | } 41 | --------------------------------------------------------------------------------