├── .github └── workflows │ ├── codesee-arch-diagram.yml │ ├── pull_request.yml │ └── release.yml ├── .gitignore ├── .npmignore ├── .prettierrc.json ├── LICENSE ├── README.handlebars ├── README.md ├── README.ts ├── bin └── graphdoc.js ├── example.github.json ├── example.pokemon.json ├── example.scaphold.json ├── example.shopify.json ├── example.starWars-js.json ├── example.starWars.json ├── lib ├── command.ts ├── interface.d.ts ├── schema-loader │ ├── http.ts │ ├── idl.ts │ ├── index.ts │ ├── js.ts │ └── json.ts ├── types │ └── word-wrap.d.ts └── utility │ ├── fs.ts │ ├── html.test.ts │ ├── html.ts │ ├── index.ts │ ├── introspection.test.ts │ ├── introspection.ts │ ├── output.test.ts │ ├── output.ts │ ├── plugin.ts │ ├── template.test.ts │ └── template.ts ├── package-lock.json ├── package.json ├── plugins ├── default.ts ├── document.require-by │ ├── index.ts │ └── require-by.css ├── document.schema │ ├── assets │ │ ├── code.css │ │ └── line-link.js │ └── index.ts ├── navigation.directive.test.ts ├── navigation.directive.ts ├── navigation.enum.test.ts ├── navigation.enum.ts ├── navigation.input.test.ts ├── navigation.input.ts ├── navigation.interface.test.ts ├── navigation.interface.ts ├── navigation.object.test.ts ├── navigation.object.ts ├── navigation.scalar.test.ts ├── navigation.scalar.ts ├── navigation.schema.test.ts ├── navigation.schema.ts ├── navigation.union.test.ts └── navigation.union.ts ├── template └── slds │ ├── fonts │ └── webfonts │ │ ├── SalesforceSans-Bold.eot │ │ ├── SalesforceSans-Bold.svg │ │ ├── SalesforceSans-Bold.woff │ │ ├── SalesforceSans-Bold.woff2 │ │ ├── SalesforceSans-BoldItalic.eot │ │ ├── SalesforceSans-BoldItalic.svg │ │ ├── SalesforceSans-BoldItalic.woff │ │ ├── SalesforceSans-BoldItalic.woff2 │ │ ├── SalesforceSans-Italic.eot │ │ ├── SalesforceSans-Italic.svg │ │ ├── SalesforceSans-Italic.woff │ │ ├── SalesforceSans-Italic.woff2 │ │ ├── SalesforceSans-Light.eot │ │ ├── SalesforceSans-Light.svg │ │ ├── SalesforceSans-Light.woff │ │ ├── SalesforceSans-Light.woff2 │ │ ├── SalesforceSans-LightItalic.eot │ │ ├── SalesforceSans-LightItalic.svg │ │ ├── SalesforceSans-LightItalic.woff │ │ ├── SalesforceSans-LightItalic.woff2 │ │ ├── SalesforceSans-Regular.eot │ │ ├── SalesforceSans-Regular.svg │ │ ├── SalesforceSans-Regular.woff │ │ ├── SalesforceSans-Regular.woff2 │ │ ├── SalesforceSans-Thin.eot │ │ ├── SalesforceSans-Thin.svg │ │ ├── SalesforceSans-Thin.woff │ │ ├── SalesforceSans-Thin.woff2 │ │ ├── SalesforceSans-ThinItalic.eot │ │ ├── SalesforceSans-ThinItalic.svg │ │ ├── SalesforceSans-ThinItalic.woff │ │ └── SalesforceSans-ThinItalic.woff2 │ ├── index.mustache │ ├── main.mustache │ ├── nav.mustache │ ├── scripts │ ├── filter-types.js │ ├── focus-active.js │ └── toggle-navigation.js │ └── styles │ └── graphdoc.css ├── test ├── dump-schema.ts ├── empty.package.json ├── empty.schema.json ├── empty.ts ├── github.json ├── schema.ts ├── shopify.json ├── starWars.ts └── starwars.graphql ├── tsconfig.json └── tslint.json /.github/workflows/codesee-arch-diagram.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | pull_request_target: 6 | types: [opened, synchronize, reopened] 7 | 8 | name: CodeSee Map 9 | 10 | jobs: 11 | test_map_action: 12 | runs-on: ubuntu-latest 13 | continue-on-error: true 14 | name: Run CodeSee Map Analysis 15 | steps: 16 | - name: checkout 17 | id: checkout 18 | uses: actions/checkout@v2 19 | with: 20 | repository: ${{ github.event.pull_request.head.repo.full_name }} 21 | ref: ${{ github.event.pull_request.head.ref }} 22 | fetch-depth: 0 23 | 24 | # codesee-detect-languages has an output with id languages. 25 | - name: Detect Languages 26 | id: detect-languages 27 | uses: Codesee-io/codesee-detect-languages-action@latest 28 | 29 | - name: Configure JDK 16 30 | uses: actions/setup-java@v2 31 | if: ${{ fromJSON(steps.detect-languages.outputs.languages).java }} 32 | with: 33 | java-version: '16' 34 | distribution: 'zulu' 35 | 36 | # CodeSee Maps Go support uses a static binary so there's no setup step required. 37 | 38 | - name: Configure Node.js 14 39 | uses: actions/setup-node@v2 40 | if: ${{ fromJSON(steps.detect-languages.outputs.languages).javascript }} 41 | with: 42 | node-version: '14' 43 | 44 | - name: Configure Python 3.x 45 | uses: actions/setup-python@v2 46 | if: ${{ fromJSON(steps.detect-languages.outputs.languages).python }} 47 | with: 48 | python-version: '3.x' 49 | architecture: 'x64' 50 | 51 | - name: Configure Ruby '3.x' 52 | uses: ruby/setup-ruby@v1 53 | if: ${{ fromJSON(steps.detect-languages.outputs.languages).ruby }} 54 | with: 55 | ruby-version: '3.0' 56 | 57 | # CodeSee Maps Rust support uses a static binary so there's no setup step required. 58 | 59 | - name: Generate Map 60 | id: generate-map 61 | uses: Codesee-io/codesee-map-action@latest 62 | with: 63 | step: map 64 | github_ref: ${{ github.ref }} 65 | languages: ${{ steps.detect-languages.outputs.languages }} 66 | 67 | - name: Upload Map 68 | id: upload-map 69 | uses: Codesee-io/codesee-map-action@latest 70 | with: 71 | step: mapUpload 72 | api_token: ${{ secrets.CODESEE_ARCH_DIAG_API_TOKEN }} 73 | github_ref: ${{ github.ref }} 74 | 75 | - name: Insights 76 | id: insights 77 | uses: Codesee-io/codesee-map-action@latest 78 | with: 79 | step: insights 80 | api_token: ${{ secrets.CODESEE_ARCH_DIAG_API_TOKEN }} 81 | github_ref: ${{ github.ref }} 82 | -------------------------------------------------------------------------------- /.github/workflows/pull_request.yml: -------------------------------------------------------------------------------- 1 | name: pull request workflow 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | support: 10 | runs-on: ubuntu-20.04 11 | strategy: 12 | matrix: 13 | node-version: [6.x, 8.x, 10.x, 12.x] 14 | 15 | steps: 16 | - uses: actions/checkout@v1 17 | - name: node.js ${{ matrix.node-version }} 18 | uses: actions/setup-node@v1 19 | with: 20 | node-version: ${{ matrix.node-version }} 21 | - name: updating npm 22 | run: npm i -g npm 23 | - name: installing 24 | run: npm ci 25 | - name: build 26 | run: npm run compile 27 | - name: testing 28 | run: npm test 29 | env: 30 | CI: true 31 | 32 | build: 33 | runs-on: ubuntu-20.04 34 | 35 | steps: 36 | - uses: actions/checkout@v1 37 | - name: node.js 14 38 | uses: actions/setup-node@v1 39 | with: 40 | node-version: 14 41 | - name: installing 42 | run: npm ci 43 | - name: build 44 | run: npm run compile 45 | - name: testing 46 | run: npm test 47 | env: 48 | CI: true 49 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | support: 10 | runs-on: ubuntu-20.04 11 | strategy: 12 | matrix: 13 | node-version: [6.x, 8.x, 10.x, 12.x] 14 | 15 | steps: 16 | - uses: actions/checkout@v1 17 | - name: node.js ${{ matrix.node-version }} 18 | uses: actions/setup-node@v1 19 | with: 20 | node-version: ${{ matrix.node-version }} 21 | - name: updating npm 22 | run: npm i -g npm 23 | - name: installing 24 | run: npm ci 25 | - name: testing 26 | run: npm test 27 | env: 28 | CI: true 29 | 30 | build: 31 | runs-on: ubuntu-20.04 32 | 33 | steps: 34 | - uses: actions/checkout@v1 35 | - name: node.js 14 36 | uses: actions/setup-node@v1 37 | with: 38 | node-version: 14 39 | - name: installing 40 | run: npm ci 41 | - name: testing 42 | run: npm test -- --coverage 43 | env: 44 | CI: true 45 | - name: coveralls 46 | uses: coverallsapp/github-action@master 47 | with: 48 | github-token: ${{ secrets.github_token }} 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | *.lock 5 | npm-debug.log* 6 | 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # nyc test coverage 19 | .nyc_output 20 | 21 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 22 | .grunt 23 | 24 | # node-waf configuration 25 | .lock-wscript 26 | 27 | # Compiled binary addons (http://nodejs.org/api/addons.html) 28 | build/Release 29 | 30 | # Dependency directories 31 | node_modules 32 | jspm_packages 33 | 34 | # Optional npm cache directory 35 | .npm 36 | 37 | # Optional REPL history 38 | .node_repl_history 39 | 40 | .vscode 41 | *.js 42 | *.scss 43 | !template/**/*.js 44 | !plugins/**/assets/*.js 45 | *.js.map 46 | *.d.ts 47 | gh-pages 48 | build/* 49 | /.idea 50 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | *.ts 3 | build -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Fede Ramirez 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.handlebars: -------------------------------------------------------------------------------- 1 | # {{project.description}} 2 | 3 | [![build](https://github.com/2fd/graphdoc/workflows/build/badge.svg?branch=master)](https://github.com/2fd/graphdoc/actions?query=workflow%3Abuild+branch%3Amaster+) 4 | [![coverage](https://coveralls.io/repos/github/2fd/graphdoc/badge.svg?branch=master)](https://coveralls.io/github/2fd/graphdoc?branch=master) 5 | ![npm](https://img.shields.io/npm/v/@2fd/graphdoc.svg) 6 | ![tag](https://img.shields.io/github/tag/2fd/graphdoc.svg) 7 | 8 | - [demos](#demos) 9 | - [install](#install) 10 | - [use](#use) 11 | - [plugin](#plugin) 12 | - [template](#template) 13 | - [contributors](#contributors) 14 | 15 | ## Demos 16 | 17 | - Facebook Test [Star Wars](https://2fd.github.io/graphdoc/star-wars) 18 | - [Github V4 API](https://2fd.github.io/graphdoc/github) 19 | - [Shopify API](https://2fd.github.io/graphdoc/shopify/) 20 | - [Pokemon GraphQL](https://2fd.github.io/graphdoc/pokemon) 21 | 22 | ## Install 23 | 24 | ```bash 25 | npm install -g @2fd/graphdoc 26 | ``` 27 | 28 | ## Use 29 | 30 | ### Generate documentation from live endpoint 31 | 32 | ```bash 33 | > graphdoc -e http://localhost:8080/graphql -o ./doc/schema 34 | ``` 35 | 36 | ### Generate documentation from IDL file 37 | 38 | ```bash 39 | > graphdoc -s ./schema.graphql -o ./doc/schema 40 | ``` 41 | 42 | ### Generate documentation from for the ["modularized schema"](http://dev.apollodata.com/tools/graphql-tools/generate-schema.html#modularizing) of graphql-tools 43 | 44 | ```bash 45 | > graphdoc -s ./schema.js -o ./doc/schema 46 | ``` 47 | 48 | > [`./schema.graphql`](https://github.com/2fd/graphdoc/blob/master/test/starwars.graphql) must be able to be interpreted 49 | > with [graphql-js/utilities#buildSchema](http://graphql.org/graphql-js/utilities/#buildschema) 50 | 51 | ### Generate documentation from json file 52 | 53 | ```bash 54 | > graphdoc -s ./schema.json -o ./doc/schema 55 | ``` 56 | 57 | > `./schema.json` contains the result of [GraphQL introspection 58 | > query](https://github.com/2fd/graphdoc/blob/gh-pages/introspection.graphql) 59 | 60 | ### Puts the options in your `package.json` 61 | 62 | ```javascript 63 | // package.json 64 | 65 | { 66 | "name": "project", 67 | "graphdoc": { 68 | "endpoint": "http://localhost:8080/graphql", 69 | "output": "./doc/schema", 70 | } 71 | } 72 | ``` 73 | 74 | And execute 75 | 76 | ```bash 77 | > graphdoc 78 | ``` 79 | 80 | ### Help 81 | 82 | ```bash 83 | 84 | > graphdoc -h 85 | {{{bash "node ./bin/graphdoc.js --no-color --help"}}} 86 | ``` 87 | 88 | ## Plugin 89 | 90 | In graphdoc a plugin is simply an object that controls the content that is displayed 91 | on every page of your document. 92 | 93 | This object should only implement the 94 | [`PluginInterface`](https://github.com/2fd/graphdoc/blob/master/lib/interface.d.ts#L12-L117). 95 | 96 | ### Make a Plugin 97 | 98 | To create your own plugin you should only create it as a `plain object` 99 | or a `constructor` and export it as `default` 100 | 101 | If you export your plugin as a constructor, when going to be initialized, 102 | will receive three parameters 103 | 104 | - `schema`: The full the result of [GraphQL introspection query](https://github.com/2fd/graphdoc/blob/gh-pages/introspection.graphql) 105 | - `projectPackage`: The content of `package.json` of current project (or the content of file defined with `--config` 106 | flag). 107 | - `graphdocPackage`: The content of `package.json` of graphdoc. 108 | 109 | > For performance reasons all plugins receive the reference to the same object 110 | > and therefore should not modify them directly as it could affect the behavior 111 | > of other plugins (unless of course that is your intention) 112 | 113 | #### Examples 114 | 115 | ```typescript 116 | // es2015 export constructor 117 | 118 | export default class MyPlugin { 119 | 120 | constructor(schema, projectPackage, graphdocPackage) {} 121 | 122 | getAssets() { 123 | /* ... */ 124 | } 125 | } 126 | ``` 127 | 128 | ```typescript 129 | // es2015 export plain object 130 | 131 | export default cost myPlugin = { 132 | getAssets() { 133 | /* ... */ 134 | }, 135 | } 136 | ``` 137 | 138 | ```javascript 139 | // export constructor 140 | 141 | function MyPlugin(schema, projectPackage, graphdocPackage) { 142 | /* ... */ 143 | } 144 | 145 | MyPlugin.prototype.getAssets = function() { 146 | /* ... */ 147 | }; 148 | 149 | exports.default = MyPlugin; 150 | ``` 151 | 152 | ```javascript 153 | // export plain object 154 | 155 | exports.default = { 156 | getAssets: function() { 157 | /* ... */ 158 | } 159 | }; 160 | ``` 161 | 162 | ### Use plugin 163 | 164 | You can use the plugins in 2 ways. 165 | 166 | #### Use plugins with command line 167 | 168 | ```bash 169 | > graphdoc -p graphdoc/plugins/default \ 170 | -p some-dependencies/plugin \ 171 | -p ./lib/plugin/my-own-plugin \ 172 | -s ./schema.json -o ./doc/schema 173 | ``` 174 | 175 | #### Use plugins with `package.json` 176 | 177 | ```javascript 178 | // package.json 179 | 180 | { 181 | "name": "project", 182 | "graphdoc": { 183 | "endpoint": "http://localhost:8080/graphql", 184 | "output": "./doc/schema", 185 | "plugins": [ 186 | "graphdoc/plugins/default", 187 | "some-dependencie/plugin", 188 | "./lib/plugin/my-own-plugin" 189 | ] 190 | } 191 | } 192 | ``` 193 | 194 | ### Build-in plugin 195 | 196 | > TODO 197 | 198 | ## Template 199 | 200 | > TODO 201 | 202 | ## Contributors 203 | 204 | {{#each contributors}}- [ {{{login}}}]({{{html_url}}}) 205 | {{/each}} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Static page generator for documenting GraphQL Schema 2 | 3 | [![build](https://github.com/2fd/graphdoc/workflows/build/badge.svg?branch=master)](https://github.com/2fd/graphdoc/actions?query=workflow%3Abuild+branch%3Amaster+) 4 | [![coverage](https://coveralls.io/repos/github/2fd/graphdoc/badge.svg?branch=master)](https://coveralls.io/github/2fd/graphdoc?branch=master) 5 | ![npm](https://img.shields.io/npm/v/@2fd/graphdoc.svg) 6 | ![tag](https://img.shields.io/github/tag/2fd/graphdoc.svg) 7 | 8 | - [demos](#demos) 9 | - [install](#install) 10 | - [use](#use) 11 | - [plugin](#plugin) 12 | - [template](#template) 13 | - [contributors](#contributors) 14 | 15 | ## Demos 16 | 17 | - Facebook Test [Star Wars](https://2fd.github.io/graphdoc/star-wars) 18 | - [Github V4 API](https://2fd.github.io/graphdoc/github) 19 | - [Shopify API](https://2fd.github.io/graphdoc/shopify/) 20 | - [Pokemon GraphQL](https://2fd.github.io/graphdoc/pokemon) 21 | 22 | ## Install 23 | 24 | ```bash 25 | npm install -g @2fd/graphdoc 26 | ``` 27 | 28 | ## Use 29 | 30 | ### Generate documentation from live endpoint 31 | 32 | ```bash 33 | > graphdoc -e http://localhost:8080/graphql -o ./doc/schema 34 | ``` 35 | 36 | ### Generate documentation from IDL file 37 | 38 | ```bash 39 | > graphdoc -s ./schema.graphql -o ./doc/schema 40 | ``` 41 | 42 | ### Generate documentation from for the ["modularized schema"](http://dev.apollodata.com/tools/graphql-tools/generate-schema.html#modularizing) of graphql-tools 43 | 44 | ```bash 45 | > graphdoc -s ./schema.js -o ./doc/schema 46 | ``` 47 | 48 | > [`./schema.graphql`](https://github.com/2fd/graphdoc/blob/master/test/starwars.graphql) must be able to be interpreted 49 | > with [graphql-js/utilities#buildSchema](http://graphql.org/graphql-js/utilities/#buildschema) 50 | 51 | ### Generate documentation from json file 52 | 53 | ```bash 54 | > graphdoc -s ./schema.json -o ./doc/schema 55 | ``` 56 | 57 | > `./schema.json` contains the result of [GraphQL introspection 58 | > query](https://github.com/2fd/graphdoc/blob/gh-pages/introspection.graphql) 59 | 60 | ### Puts the options in your `package.json` 61 | 62 | ```javascript 63 | // package.json 64 | 65 | { 66 | "name": "project", 67 | "graphdoc": { 68 | "endpoint": "http://localhost:8080/graphql", 69 | "output": "./doc/schema", 70 | } 71 | } 72 | ``` 73 | 74 | And execute 75 | 76 | ```bash 77 | > graphdoc 78 | ``` 79 | 80 | ### Help 81 | 82 | ```bash 83 | 84 | > graphdoc -h 85 | 86 | Static page generator for documenting GraphQL Schema v2.4.0 87 | 88 | Usage: node bin/graphdoc.js [OPTIONS] 89 | 90 | 91 | [OPTIONS]: 92 | -c, --config Configuration file [./package.json]. 93 | -e, --endpoint Graphql http endpoint ["https://domain.com/graphql"]. 94 | -x, --header HTTP header for request (use with --endpoint). ["Authorization: Token cb8795e7"]. 95 | -q, --query HTTP querystring for request (use with --endpoint) ["token=cb8795e7"]. 96 | -s, --schema, --schema-file Graphql Schema file ["./schema.json"]. 97 | -p, --plugin Use plugins [default=graphdoc/plugins/default]. 98 | -t, --template Use template [default=graphdoc/template/slds]. 99 | -o, --output Output directory. 100 | -d, --data Inject custom data. 101 | -b, --base-url Base url for templates. 102 | -f, --force Delete outputDirectory if exists. 103 | -v, --verbose Output more information. 104 | -V, --version Show graphdoc version. 105 | -h, --help Print this help 106 | 107 | 108 | ``` 109 | 110 | ## Plugin 111 | 112 | In graphdoc a plugin is simply an object that controls the content that is displayed 113 | on every page of your document. 114 | 115 | This object should only implement the 116 | [`PluginInterface`](https://github.com/2fd/graphdoc/blob/master/lib/interface.d.ts#L12-L117). 117 | 118 | ### Make a Plugin 119 | 120 | To create your own plugin you should only create it as a `plain object` 121 | or a `constructor` and export it as `default` 122 | 123 | If you export your plugin as a constructor, when going to be initialized, 124 | will receive three parameters 125 | 126 | - `schema`: The full the result of [GraphQL introspection query](https://github.com/2fd/graphdoc/blob/gh-pages/introspection.graphql) 127 | - `projectPackage`: The content of `package.json` of current project (or the content of file defined with `--config` 128 | flag). 129 | - `graphdocPackage`: The content of `package.json` of graphdoc. 130 | 131 | > For performance reasons all plugins receive the reference to the same object 132 | > and therefore should not modify them directly as it could affect the behavior 133 | > of other plugins (unless of course that is your intention) 134 | 135 | #### Examples 136 | 137 | ```typescript 138 | // es2015 export constructor 139 | 140 | export default class MyPlugin { 141 | 142 | constructor(schema, projectPackage, graphdocPackage) {} 143 | 144 | getAssets() { 145 | /* ... */ 146 | } 147 | } 148 | ``` 149 | 150 | ```typescript 151 | // es2015 export plain object 152 | 153 | export default cost myPlugin = { 154 | getAssets() { 155 | /* ... */ 156 | }, 157 | } 158 | ``` 159 | 160 | ```javascript 161 | // export constructor 162 | 163 | function MyPlugin(schema, projectPackage, graphdocPackage) { 164 | /* ... */ 165 | } 166 | 167 | MyPlugin.prototype.getAssets = function() { 168 | /* ... */ 169 | }; 170 | 171 | exports.default = MyPlugin; 172 | ``` 173 | 174 | ```javascript 175 | // export plain object 176 | 177 | exports.default = { 178 | getAssets: function() { 179 | /* ... */ 180 | } 181 | }; 182 | ``` 183 | 184 | ### Use plugin 185 | 186 | You can use the plugins in 2 ways. 187 | 188 | #### Use plugins with command line 189 | 190 | ```bash 191 | > graphdoc -p graphdoc/plugins/default \ 192 | -p some-dependencies/plugin \ 193 | -p ./lib/plugin/my-own-plugin \ 194 | -s ./schema.json -o ./doc/schema 195 | ``` 196 | 197 | #### Use plugins with `package.json` 198 | 199 | ```javascript 200 | // package.json 201 | 202 | { 203 | "name": "project", 204 | "graphdoc": { 205 | "endpoint": "http://localhost:8080/graphql", 206 | "output": "./doc/schema", 207 | "plugins": [ 208 | "graphdoc/plugins/default", 209 | "some-dependencie/plugin", 210 | "./lib/plugin/my-own-plugin" 211 | ] 212 | } 213 | } 214 | ``` 215 | 216 | ### Build-in plugin 217 | 218 | > TODO 219 | 220 | ## Template 221 | 222 | > TODO 223 | 224 | ## Contributors 225 | 226 | - [ dependabot-preview[bot]](https://github.com/apps/dependabot-preview) 227 | - [ bitliner](https://github.com/bitliner) 228 | - [ kbariotis](https://github.com/kbariotis) 229 | - [ 0xflotus](https://github.com/0xflotus) 230 | - [ Joatin](https://github.com/Joatin) 231 | - [ shiroyuki](https://github.com/shiroyuki) 232 | - [ kristiehowboutdat](https://github.com/kristiehowboutdat) 233 | - [ tony](https://github.com/tony) 234 | - [ dnalborczyk](https://github.com/dnalborczyk) 235 | -------------------------------------------------------------------------------- /README.ts: -------------------------------------------------------------------------------- 1 | import Bluebird from "bluebird"; 2 | // tslint:disable-next-line:no-implicit-dependencies 3 | import Handlebars from "handlebars"; 4 | import request from "request"; 5 | 6 | import { readFile, writeFile } from "./lib/utility/fs"; 7 | 8 | import { execSync } from "child_process"; 9 | 10 | Handlebars.registerHelper("bash", (command: string) => { 11 | return execSync(command).toString("ascii"); 12 | }); 13 | 14 | async function fromGithub(endpoint: string) { 15 | return Bluebird.promisify(request)({ 16 | method: "GET", 17 | url: "https://api.github.com/" + endpoint, 18 | json: true, 19 | headers: { "User-Agent": "README generator" } 20 | }).then((response: request.RequestResponse & { body: any }) => response.body); 21 | } 22 | 23 | Promise.all([ 24 | readFile("./README.handlebars", "utf8"), 25 | fromGithub("repos/2fd/graphdoc"), 26 | fromGithub("repos/2fd/graphdoc/contributors").then( 27 | (contributors: Array<{ login: string }>) => 28 | contributors.filter(c => c.login !== "2fd") 29 | ) 30 | ]).then(([template, project, contributors]) => { 31 | return writeFile( 32 | "README.md", 33 | Handlebars.compile(template)({ project, contributors }) 34 | ); 35 | }); 36 | -------------------------------------------------------------------------------- /bin/graphdoc.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | "use strict"; 4 | const { 5 | ArgvInput, 6 | ColorConsoleOutput, 7 | ConsoleOutput 8 | } = require("@2fd/command"); 9 | const { GraphQLDocumentGenerator } = require("../lib/command"); 10 | const argv = process.argv.filter(arg => arg !== "--no-color"); 11 | new GraphQLDocumentGenerator().handle( 12 | new ArgvInput(argv), 13 | argv.length === process.argv.length 14 | ? new ColorConsoleOutput() 15 | : new ConsoleOutput() 16 | ); 17 | -------------------------------------------------------------------------------- /example.github.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "github", 3 | "version": "x.x.x", 4 | "description": "GitHub GraphQL API", 5 | "homepage": "https://developer.github.com/early-access/graphql/", 6 | "dependencies": { 7 | "@2fd/command": "^1.1.2", 8 | "@types/jest": "^18.1.0", 9 | "@types/request": "^0.0.40", 10 | "fs-extra": "^0.30.0", 11 | "glob": "^7.1.0", 12 | "marked": "^0.3.6", 13 | "mustache": "^2.2.1", 14 | "request": "^2.79.0" 15 | }, 16 | "graphdoc": { 17 | "ga": "UA-54154153-2", 18 | "graphiql": "https://developer.github.com/early-access/graphql/explorer/", 19 | "logo": "", 20 | "schemaFile": "./test/github.json", 21 | "output": "./gh-pages/github", 22 | "baseUrl": "./" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /example.pokemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphql-pokemon", 3 | "description": "Get information of a Pokémon with GraphQL!", 4 | "version": "1.0.0", 5 | "author": { 6 | "name": "Lucas Bento da Silva", 7 | "email": "lucas.bsilva@outlook.com", 8 | "url": "https://github.com/lucasbento" 9 | }, 10 | "bugs": "https://github.com/lucasbento/graphql-pokemon/issues", 11 | "homepage": "https://github.com/lucasbento/graphql-pokemon#readme", 12 | "license": "MIT", 13 | "repository": "https://github.com/lucasbento/graphql-pokemon", 14 | "graphdoc": { 15 | "ga": "UA-54154153-2", 16 | "graphiql": "https://graphql-pokemon.now.sh/", 17 | "logo": "", 18 | "endpoint": "https://graphql-pokemon.now.sh/", 19 | "output": "./gh-pages/pokemon", 20 | "baseUrl": "./" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /example.scaphold.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphql-pokemon", 3 | "description": "Get information of a Pokémon with GraphQL!", 4 | "version": "1.0.0", 5 | "author": { 6 | "name": "Lucas Bento da Silva", 7 | "email": "lucas.bsilva@outlook.com", 8 | "url": "https://github.com/lucasbento" 9 | }, 10 | "bugs": "https://github.com/lucasbento/graphql-pokemon/issues", 11 | "homepage": "https://github.com/lucasbento/graphql-pokemon#readme", 12 | "license": "MIT", 13 | "repository": "https://github.com/lucasbento/graphql-pokemon", 14 | "graphdoc": { 15 | "ga": "UA-54154153-2", 16 | "graphiql": "https://us-west-2.api.scaphold.io/graphql/graphql-world", 17 | "logo": "", 18 | "endpoint": "https://us-west-2.api.scaphold.io/graphql/graphql-world", 19 | "output": "./gh-pages/scaphold", 20 | "baseUrl": "./" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /example.shopify.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphql-pokemon", 3 | "description": "Get information of a Pokémon with GraphQL!", 4 | "graphdoc": { 5 | "ga": "UA-54154153-2", 6 | "graphiql": "https://help.shopify.com/api/storefront-api/graphql-explorer/graphiql", 7 | "logo": "", 8 | "schemaFile": "./test/shopify.json", 9 | "output": "./gh-pages/shopify", 10 | "baseUrl": "./" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /example.starWars-js.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@2fd/graphdoc", 3 | "version": "1.2.0", 4 | "description": "Static page generator for documenting GraphQL Schema", 5 | "homepage": "https://github.com/2fd/graphdoc#readme", 6 | "graphdoc": { 7 | "ga": "UA-54154153-2", 8 | "schemaFile": "./test/starWars.js", 9 | "output": "./gh-pages/star-wars", 10 | "baseUrl": "./" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /example.starWars.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@2fd/graphdoc", 3 | "version": "1.2.0", 4 | "description": "Static page generator for documenting GraphQL Schema", 5 | "homepage": "https://github.com/2fd/graphdoc#readme", 6 | "graphdoc": { 7 | "ga": "UA-54154153-2", 8 | "schemaFile": "./test/starwars.graphql", 9 | "output": "./gh-pages/star-wars", 10 | "baseUrl": "./" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/command.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BooleanFlag, 3 | Command, 4 | InputInterface, 5 | ListValueFlag, 6 | NoParams, 7 | OutputInterface, 8 | ValueFlag 9 | } from "@2fd/command"; 10 | import Bluebird from "bluebird"; 11 | import fs from "fs"; 12 | import glob from "glob"; 13 | import { render } from "mustache"; 14 | import path from "path"; 15 | import { PluginInterface, Schema, TypeRef } from "./interface"; 16 | import { 17 | httpSchemaLoader, 18 | idlSchemaLoader, 19 | jsonSchemaLoader, 20 | jsSchemaLoader 21 | } from "./schema-loader"; 22 | import { createData, getFilenameOf, Output, Plugin } from "./utility"; 23 | import { 24 | createBuildDirectory, 25 | readFile, 26 | removeBuildDirectory, 27 | resolve, 28 | writeFile 29 | } from "./utility/fs"; 30 | 31 | // tslint:disable-next-line:no-var-requires 32 | const graphdocPackageJSON = require(path.resolve(__dirname, "../package.json")); 33 | 34 | export interface IFlags { 35 | configFile: string; 36 | endpoint: string; 37 | headers: string[]; 38 | queries: string[]; 39 | schemaFile: string; 40 | plugins: string[]; 41 | template: string; 42 | data: any; 43 | output: string; 44 | force: boolean; 45 | verbose: boolean; 46 | version: boolean; 47 | } 48 | 49 | export interface IPartials { 50 | [name: string]: string | undefined; 51 | index?: string; 52 | } 53 | 54 | export interface IProjectPackage { 55 | graphdoc: IFlags; 56 | } 57 | 58 | export type Input = InputInterface; 59 | 60 | export class GraphQLDocumentGenerator extends Command { 61 | public description = 62 | graphdocPackageJSON.description + " v" + graphdocPackageJSON.version; 63 | 64 | public params = new NoParams(); 65 | 66 | public flags = [ 67 | new ValueFlag( 68 | "configFile", 69 | ["-c", "--config"], 70 | "Configuration file [./package.json].", 71 | String, 72 | "./package.json" 73 | ), 74 | new ValueFlag( 75 | "endpoint", 76 | ["-e", "--endpoint"], 77 | 'Graphql http endpoint ["https://domain.com/graphql"].' 78 | ), 79 | new ListValueFlag( 80 | "headers", 81 | ["-x", "--header"], 82 | 'HTTP header for request (use with --endpoint). ["Authorization: Token cb8795e7"].' 83 | ), 84 | new ListValueFlag( 85 | "queries", 86 | ["-q", "--query"], 87 | 'HTTP querystring for request (use with --endpoint) ["token=cb8795e7"].' 88 | ), 89 | new ValueFlag( 90 | "schemaFile", 91 | ["-s", "--schema", "--schema-file"], 92 | 'Graphql Schema file ["./schema.json"].' 93 | ), 94 | new ListValueFlag( 95 | "plugins", 96 | ["-p", "--plugin"], 97 | "Use plugins [default=graphdoc/plugins/default]." 98 | ), 99 | new ValueFlag( 100 | "template", 101 | ["-t", "--template"], 102 | "Use template [default=graphdoc/template/slds]." 103 | ), 104 | new ValueFlag("output", ["-o", "--output"], "Output directory."), 105 | new ValueFlag( 106 | "data", 107 | ["-d", "--data"], 108 | "Inject custom data.", 109 | JSON.parse, 110 | {} 111 | ), 112 | new ValueFlag("baseUrl", ["-b", "--base-url"], "Base url for templates."), 113 | new BooleanFlag( 114 | "force", 115 | ["-f", "--force"], 116 | "Delete outputDirectory if exists." 117 | ), 118 | new BooleanFlag("verbose", ["-v", "--verbose"], "Output more information."), 119 | new BooleanFlag("version", ["-V", "--version"], "Show graphdoc version.") 120 | ]; 121 | 122 | public async action(input: Input, out: OutputInterface) { 123 | const output = new Output(out, input.flags); 124 | 125 | try { 126 | if (input.flags.version) { 127 | return output.out.log("graphdoc v%s", graphdocPackageJSON.version); 128 | } 129 | 130 | // Load project info 131 | const projectPackageJSON: IProjectPackage = await this.getProjectPackage( 132 | input 133 | ); 134 | 135 | // Load Schema 136 | const schema: Schema = await this.getSchema(projectPackageJSON); 137 | 138 | // Load plugins 139 | const plugins: PluginInterface[] = this.getPluginInstances( 140 | projectPackageJSON.graphdoc.plugins, 141 | schema, 142 | projectPackageJSON, 143 | graphdocPackageJSON 144 | ); 145 | 146 | projectPackageJSON.graphdoc.plugins.forEach(plugin => 147 | output.info("use plugin", plugin) 148 | ); 149 | 150 | // Collect assets 151 | const assets: string[] = await Plugin.collectAssets(plugins); 152 | assets.forEach(asset => 153 | output.info("use asset", path.relative(process.cwd(), asset)) 154 | ); 155 | 156 | // Ensure Ourput directory 157 | output.info( 158 | "output directory", 159 | path.relative(process.cwd(), projectPackageJSON.graphdoc.output) 160 | ); 161 | await this.ensureOutputDirectory( 162 | projectPackageJSON.graphdoc.output, 163 | projectPackageJSON.graphdoc.force 164 | ); 165 | 166 | // Create Output directory 167 | await createBuildDirectory( 168 | projectPackageJSON.graphdoc.output, 169 | projectPackageJSON.graphdoc.template, 170 | assets 171 | ); 172 | 173 | // Collect partials 174 | const partials: IPartials = await this.getTemplatePartials( 175 | projectPackageJSON.graphdoc.template 176 | ); 177 | // Render index.html 178 | output.info("render", "index"); 179 | await this.renderFile(projectPackageJSON, partials, plugins); 180 | 181 | // Render types 182 | const renderTypes = ([] as any[]) 183 | .concat(schema.types || []) 184 | .concat(schema.directives || []) 185 | .map((type: TypeRef) => { 186 | output.info("render", type.name || ""); 187 | return this.renderFile(projectPackageJSON, partials, plugins, type); 188 | }); 189 | 190 | const files = await Promise.all(renderTypes); 191 | output.ok( 192 | "complete", 193 | String(files.length + 1 /* index.html */) + " files generated." 194 | ); 195 | } catch (err) { 196 | output.error(err); 197 | } 198 | } 199 | 200 | public async ensureOutputDirectory(dir: string, force: boolean) { 201 | try { 202 | const stats = fs.statSync(dir); 203 | 204 | if (!stats.isDirectory()) { 205 | return Promise.reject( 206 | new Error("Unexpected output: " + dir + " is not a directory.") 207 | ); 208 | } 209 | 210 | if (!force) { 211 | return Promise.reject( 212 | new Error(dir + " already exists (delete it or use the --force flag)") 213 | ); 214 | } 215 | 216 | return removeBuildDirectory(dir); 217 | } catch (err) { 218 | return err.code === "ENOENT" ? Promise.resolve() : Promise.reject(err); 219 | } 220 | } 221 | 222 | public getProjectPackage(input: Input) { 223 | let packageJSON: any & { graphdoc: any }; 224 | 225 | try { 226 | packageJSON = require(path.resolve(input.flags.configFile)); 227 | } catch (err) { 228 | packageJSON = {}; 229 | } 230 | 231 | packageJSON.graphdoc = { ...(packageJSON.graphdoc || {}), ...input.flags }; 232 | 233 | if (packageJSON.graphdoc.data) { 234 | const data = packageJSON.graphdoc.data; 235 | packageJSON.graphdoc = { ...data, ...packageJSON.graphdoc }; 236 | } 237 | 238 | if (packageJSON.graphdoc.plugins.length === 0) { 239 | packageJSON.graphdoc.plugins = ["graphdoc/plugins/default"]; 240 | } 241 | 242 | packageJSON.graphdoc.baseUrl = packageJSON.graphdoc.baseUrl || "./"; 243 | packageJSON.graphdoc.template = resolve( 244 | packageJSON.graphdoc.template || "graphdoc/template/slds" 245 | ); 246 | packageJSON.graphdoc.output = path.resolve(packageJSON.graphdoc.output); 247 | packageJSON.graphdoc.version = graphdocPackageJSON.version; 248 | 249 | if (!packageJSON.graphdoc.output) { 250 | return Promise.reject( 251 | new Error("Flag output (-o, --output) is required") 252 | ); 253 | } 254 | 255 | return Promise.resolve(packageJSON); 256 | } 257 | 258 | public getPluginInstances( 259 | paths: string[], 260 | schema: Schema, 261 | projectPackageJSON: object, 262 | pluginGraphdocPackageJSON: object 263 | ): PluginInterface[] { 264 | return paths.map(p => { 265 | const absolutePaths = resolve(p); 266 | const plugin = require(absolutePaths).default; 267 | 268 | return typeof plugin === "function" 269 | ? // plugins as constructor 270 | new plugin(schema, projectPackageJSON, pluginGraphdocPackageJSON) 271 | : // plugins plain object 272 | plugin; 273 | }); 274 | } 275 | 276 | public async getTemplatePartials(templateDir: string): Promise { 277 | const partials: IPartials = {}; 278 | const files: string[] = await Bluebird.promisify( 279 | ( 280 | pattern: string, 281 | options: glob.IOptions, 282 | cb: (err: Error, matches: string[]) => void 283 | ) => glob(pattern, options, cb) 284 | )("**/*.mustache", { cwd: templateDir }); 285 | 286 | await Promise.all( 287 | files.map(file => { 288 | const name = path.basename(file, ".mustache"); 289 | return readFile(path.resolve(templateDir, file), "utf8").then( 290 | content => (partials[name] = content) 291 | ); 292 | }) 293 | ); 294 | 295 | if (!partials.index) { 296 | throw new Error( 297 | `The index partial is missing (file ${path.resolve( 298 | templateDir, 299 | "index.mustache" 300 | )} not found).` 301 | ); 302 | } 303 | 304 | return partials; 305 | } 306 | 307 | public async getSchema(projectPackage: IProjectPackage): Promise { 308 | if (projectPackage.graphdoc.schemaFile) { 309 | const schemaFileExt = path.extname(projectPackage.graphdoc.schemaFile); 310 | switch (schemaFileExt) { 311 | case ".json": 312 | return jsonSchemaLoader(projectPackage.graphdoc); 313 | case ".gql": 314 | case ".gqls": 315 | case ".graphqls": 316 | case ".graphql": 317 | return idlSchemaLoader(projectPackage.graphdoc); 318 | case ".js": 319 | return jsSchemaLoader(projectPackage.graphdoc); 320 | default: 321 | return Promise.reject( 322 | new Error("Unexpected schema extension name: " + schemaFileExt) 323 | ); 324 | } 325 | } else if (projectPackage.graphdoc.endpoint) { 326 | return httpSchemaLoader(projectPackage.graphdoc); 327 | } else { 328 | return Promise.reject( 329 | new Error( 330 | "Endpoint (--endpoint, -e) or Schema File (--schema, -s) are require." 331 | ) 332 | ); 333 | } 334 | } 335 | 336 | public async renderFile( 337 | projectPackageJSON: IProjectPackage, 338 | partials: IPartials, 339 | plugins: PluginInterface[], 340 | type?: TypeRef 341 | ) { 342 | const templateData = await createData( 343 | projectPackageJSON, 344 | graphdocPackageJSON, 345 | plugins, 346 | type 347 | ); 348 | const file = type ? getFilenameOf(type) : "index.html"; 349 | const filePath = path.resolve(projectPackageJSON.graphdoc.output, file); 350 | return writeFile( 351 | filePath, 352 | render(partials.index as string, templateData, partials) 353 | ); 354 | } 355 | } 356 | -------------------------------------------------------------------------------- /lib/interface.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * PluginConstructor 3 | */ 4 | export interface PluginConstructor { 5 | new ( 6 | document: Schema, 7 | graphdocPackage: any, 8 | projectPackage: any 9 | ): PluginInterface; 10 | } 11 | 12 | /** 13 | * PluginInterface 14 | */ 15 | export interface PluginInterface { 16 | /** 17 | * Return section elements that is going to be 18 | * inserted into the side navigation bar. 19 | * 20 | * @example plain javascript: 21 | * [ 22 | * { 23 | * title: 'Schema', 24 | * items: [ 25 | * { 26 | * text: 'Query', 27 | * href: './query.doc.html', 28 | * isActive: false 29 | * }, 30 | * // ... 31 | * } 32 | * // ... 33 | * ] 34 | * 35 | * @example with graphdoc utilities: 36 | * import { NavigationSection, NavigationItem } from 'graphdoc/lib/utility'; 37 | * 38 | * [ 39 | * new NavigationSection('Schema', [ 40 | * new NavigationItem('Query', ./query.doc.html', false) 41 | * ]), 42 | * // ... 43 | * ] 44 | * 45 | * @param {string} [buildForType] - 46 | * the name of the element for which the navigation section is being generated, 47 | * if it is `undefined it means that the index of documentation is being generated 48 | */ 49 | getNavigations?: ( 50 | buildForType?: string 51 | ) => NavigationSectionInterface[] | PromiseLike; 52 | 53 | /** 54 | * Return section elements that is going to be 55 | * inserted into the main section. 56 | * 57 | * @example plain javascript: 58 | * [ 59 | * { 60 | * title: 'GraphQL Schema definition', 61 | * description: 'HTML' 62 | * }, 63 | * // ... 64 | * ] 65 | * 66 | * @example with graphdoc utilities: 67 | * import { DocumentSection } from 'graphdoc/lib/utility'; 68 | * 69 | * [ 70 | * new DocumentSection('GraphQL Schema definition', 'HTML'), 71 | * // ... 72 | * ] 73 | * 74 | * @param {string} [buildForType] - 75 | * the name of the element for which the navigation section is being generated, 76 | * if it is `undefined it means that the index of documentation is being generated 77 | * 78 | */ 79 | getDocuments?: ( 80 | buildForType?: string 81 | ) => DocumentSectionInterface[] | PromiseLike; 82 | 83 | /** 84 | * Return a list of html tags that is going to be 85 | * inserted into the head tag of each page. 86 | * 87 | * @example 88 | * [ 89 | * '', 90 | * '', 91 | * ] 92 | */ 93 | getHeaders?: (buildForType?: string) => string[] | PromiseLike; 94 | 95 | /** 96 | * Return a list of absolute path to files that is going to be 97 | * copied to the assets directory. 98 | * 99 | * Unlike the previous methods that are executed each time that a page generated, 100 | * this method is called a single time before starting to generate the documentation 101 | * 102 | * @example 103 | * [ 104 | * '/local/path/to/my-custom-style.css', 105 | * '/local/path/to/my-custom-image.png', 106 | * ] 107 | * 108 | * there's will be copied to 109 | * /local/path/to/my-custom-style.css -> [OUTPUT_DIRECTORY]/assets/my-custom-style.css 110 | * /local/path/to/my-custom-image.png -> [OUTPUT_DIRECTORY]/assets/my-custom-image.png 111 | * 112 | * If you want to insert styles or scripts to the documentation, 113 | * you must combine this method with getHeaders 114 | * 115 | * @example 116 | * getAssets(): ['/local/path/to/my-custom-style.css'] 117 | * getHeaders(): [''] 118 | */ 119 | getAssets?: () => string[] | PromiseLike; 120 | } 121 | 122 | export interface PluginImplementedInterface { 123 | document: Schema; 124 | url: refToUrl; 125 | queryType: SchemaType | null; 126 | mutationType: SchemaType | null; 127 | subscriptionType: SchemaType | null; 128 | } 129 | 130 | export interface NavigationSectionInterface { 131 | title: string; 132 | items: NavigationItemInterface[]; 133 | } 134 | 135 | export interface NavigationItemInterface { 136 | href: string; 137 | text: string; 138 | isActive: boolean; 139 | } 140 | 141 | export interface DocumentSectionInterface { 142 | title: string; 143 | description: string; 144 | } 145 | 146 | /** 147 | * Convert TypeRef 148 | */ 149 | type refToUrl = (typeName: TypeRef) => string; 150 | 151 | /** 152 | * Introspection types 153 | */ 154 | type GraphQLIntrospection = { 155 | data: { 156 | __schema: Schema; 157 | }; 158 | }; 159 | type ApolloIntrospection = { 160 | __schema: Schema; 161 | }; 162 | type Introspection = GraphQLIntrospection | ApolloIntrospection; 163 | 164 | type Schema = { 165 | queryType: Description | null; 166 | mutationType: Description | null; 167 | subscriptionType: Description | null; 168 | types: SchemaType[]; 169 | directives: Directive[]; 170 | }; 171 | 172 | type Description = { 173 | name: string; 174 | description: string | null; 175 | kind?: string; 176 | }; 177 | 178 | type Deprecation = { 179 | isDeprecated: boolean; 180 | deprecationReason: string | null; 181 | }; 182 | 183 | type SchemaType = Description & { 184 | fields: Field[] | null; 185 | inputFields: InputValue[] | null; 186 | interfaces: TypeRef[] | null; 187 | enumValues: EnumValue[] | null; 188 | possibleTypes: TypeRef[] | null; 189 | kind: string; 190 | }; 191 | 192 | type Directive = Description & { 193 | locations: string[]; 194 | args: InputValue[]; 195 | }; 196 | 197 | type EnumValue = Description & Deprecation; 198 | 199 | type InputValue = Description & { 200 | type: DeepTypeRef | TypeRef; 201 | defaultValue: string | number | null; 202 | }; 203 | 204 | type Field = Description & 205 | Deprecation & { 206 | args: InputValue[]; 207 | type: DeepTypeRef | TypeRef; 208 | }; 209 | 210 | type TypeRef = { 211 | name: string; 212 | description: string | null; 213 | kind: string; 214 | ofType: null; 215 | }; 216 | 217 | type DeepTypeRef = { 218 | name: string | null; 219 | description: null; 220 | kind: "LIST" | "NON_NULL" | string; 221 | ofType: DeepTypeRef | TypeRef; 222 | }; 223 | 224 | export interface SchemaLoader { 225 | (options: any): Promise; 226 | } 227 | -------------------------------------------------------------------------------- /lib/schema-loader/http.ts: -------------------------------------------------------------------------------- 1 | import Bluebird from "bluebird"; 2 | import request from "request"; 3 | import { 4 | ApolloIntrospection, 5 | GraphQLIntrospection, 6 | Introspection, 7 | Schema, 8 | SchemaLoader 9 | } from "../interface"; 10 | import { query as introspectionQuery } from "../utility"; 11 | 12 | export interface IHttpSchemaLoaderOptions { 13 | endpoint: string; 14 | headers: string[]; 15 | queries: string[]; 16 | } 17 | 18 | async function r(options: request.OptionsWithUrl) { 19 | return new Bluebird((resolve, reject) => { 20 | request(options, (error, res, body: Introspection | string) => { 21 | if (error) { 22 | return reject(error); 23 | } 24 | 25 | if ((res.statusCode as number) >= 400) { 26 | return reject( 27 | new Error( 28 | "Unexpected HTTP Status Code " + 29 | res.statusCode + 30 | " (" + 31 | res.statusMessage + 32 | ") from: " + 33 | options.url 34 | ) 35 | ); 36 | } 37 | 38 | if (typeof body === "string") { 39 | return reject( 40 | new Error( 41 | 'Unexpected response from "' + 42 | options.url + 43 | '": ' + 44 | body.slice(0, 10) + 45 | "..." 46 | ) 47 | ); 48 | } 49 | 50 | return resolve( 51 | (body as ApolloIntrospection).__schema || 52 | (body as GraphQLIntrospection).data.__schema 53 | ); 54 | }); 55 | }); 56 | } 57 | 58 | export const httpSchemaLoader: SchemaLoader = async ( 59 | options: IHttpSchemaLoaderOptions 60 | ) => { 61 | const requestOptions: request.OptionsWithUrl = { 62 | url: options.endpoint, 63 | method: "POST", 64 | body: { query: introspectionQuery }, 65 | json: true 66 | }; 67 | 68 | requestOptions.headers = options.headers.reduce( 69 | (result: any, header: string) => { 70 | const [name, value] = header.split(": ", 2); 71 | result[name] = value; 72 | return result; 73 | }, 74 | {} 75 | ); 76 | 77 | requestOptions.qs = options.queries.reduce((result: any, query: string) => { 78 | const [name, value] = query.split("=", 2); 79 | result[name] = value; 80 | return result; 81 | }, {}); 82 | 83 | return r(requestOptions); 84 | }; 85 | -------------------------------------------------------------------------------- /lib/schema-loader/idl.ts: -------------------------------------------------------------------------------- 1 | import { buildSchema, execute, parse } from "graphql"; 2 | import { resolve } from "path"; 3 | import { 4 | ApolloIntrospection, 5 | GraphQLIntrospection, 6 | Introspection, 7 | SchemaLoader 8 | } from "../interface"; 9 | import { query as introspectionQuery } from "../utility"; 10 | import { readFile } from "../utility/fs"; 11 | 12 | export interface IIdlSchemaLoaderOptions { 13 | schemaFile: string; 14 | } 15 | 16 | export const idlSchemaLoader: SchemaLoader = async ( 17 | options: IIdlSchemaLoaderOptions 18 | ) => { 19 | const schemaPath = resolve(options.schemaFile); 20 | const idl = await readFile(schemaPath, "utf8"); 21 | const introspection = (await execute( 22 | buildSchema(idl), 23 | parse(introspectionQuery) 24 | )) as Introspection; 25 | 26 | return ( 27 | (introspection as ApolloIntrospection).__schema || 28 | (introspection as GraphQLIntrospection).data.__schema 29 | ); 30 | }; 31 | -------------------------------------------------------------------------------- /lib/schema-loader/index.ts: -------------------------------------------------------------------------------- 1 | export { IHttpSchemaLoaderOptions, httpSchemaLoader } from "./http"; 2 | export { IIdlSchemaLoaderOptions, idlSchemaLoader } from "./idl"; 3 | export { 4 | IJsSchemaLoaderOptions as TJsSchemaLoaderOptions, 5 | jsSchemaLoader 6 | } from "./js"; 7 | export { 8 | IJsonSchemaLoaderOptions as TJsonSchemaLoaderOptions, 9 | jsonSchemaLoader 10 | } from "./json"; 11 | -------------------------------------------------------------------------------- /lib/schema-loader/js.ts: -------------------------------------------------------------------------------- 1 | import { buildSchema, execute, parse } from "graphql"; 2 | import { resolve } from "path"; 3 | import { 4 | ApolloIntrospection, 5 | GraphQLIntrospection, 6 | Introspection, 7 | SchemaLoader 8 | } from "../interface"; 9 | import { query as introspectionQuery } from "../utility"; 10 | 11 | export interface IJsSchemaLoaderOptions { 12 | schemaFile: string; 13 | } 14 | 15 | export const jsSchemaLoader: SchemaLoader = async ( 16 | options: IJsSchemaLoaderOptions 17 | ) => { 18 | const schemaPath = resolve(options.schemaFile); 19 | let schemaModule = require(schemaPath); 20 | let schema: string; 21 | 22 | // check if exist default in module 23 | if (typeof schemaModule === "object") { 24 | schemaModule = schemaModule.default; 25 | } 26 | 27 | // check for array of definition 28 | if (Array.isArray(schemaModule)) { 29 | schema = schemaModule.join(""); 30 | 31 | // check for array array wrapped in a function 32 | } else if (typeof schemaModule === "function") { 33 | schema = schemaModule().join(""); 34 | } else { 35 | throw new Error( 36 | `Unexpected schema definition on "${schemaModule}", must be an array or function` 37 | ); 38 | } 39 | 40 | const introspection = (await execute( 41 | buildSchema(schema), 42 | parse(introspectionQuery) 43 | )) as Introspection; 44 | 45 | return ( 46 | (introspection as ApolloIntrospection).__schema || 47 | (introspection as GraphQLIntrospection).data.__schema 48 | ); 49 | }; 50 | -------------------------------------------------------------------------------- /lib/schema-loader/json.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from "path"; 2 | import { 3 | ApolloIntrospection, 4 | GraphQLIntrospection, 5 | Introspection, 6 | Schema, 7 | SchemaLoader 8 | } from "../interface"; 9 | 10 | export interface IJsonSchemaLoaderOptions { 11 | schemaFile: string; 12 | } 13 | 14 | export const jsonSchemaLoader: SchemaLoader = ( 15 | options: IJsonSchemaLoaderOptions 16 | ) => { 17 | try { 18 | const schemaPath = resolve(options.schemaFile); 19 | const introspection: Introspection = require(schemaPath); 20 | const schema: Schema = 21 | (introspection as ApolloIntrospection).__schema || 22 | (introspection as GraphQLIntrospection).data.__schema; 23 | return Promise.resolve(schema); 24 | } catch (err) { 25 | return Promise.reject(err); 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /lib/types/word-wrap.d.ts: -------------------------------------------------------------------------------- 1 | declare module "word-wrap" { 2 | /** 3 | * Wrap words to a specified length. 4 | */ 5 | export = wrap; 6 | 7 | function wrap(str: string, options?: wrap.IOptions): string; 8 | 9 | namespace wrap { 10 | export interface IOptions { 11 | /** 12 | * The width of the text before wrapping to a new line. 13 | * @default ´50´ 14 | */ 15 | width?: number; 16 | 17 | /** 18 | * The string to use at the beginning of each line. 19 | * @default ´ ´ (two spaces) 20 | */ 21 | indent?: string; 22 | 23 | /** 24 | * The string to use at the end of each line. 25 | * @default ´\n´ 26 | */ 27 | newline?: string; 28 | 29 | /** 30 | * An escape function to run on each line after splitting them. 31 | * @default (str: string) => string; 32 | */ 33 | escape?: (str: string) => string; 34 | 35 | /** 36 | * Trim trailing whitespace from the returned string. 37 | * This option is included since .trim() would also strip 38 | * the leading indentation from the first line. 39 | * @default true 40 | */ 41 | trim?: boolean; 42 | 43 | /** 44 | * Break a word between any two letters when the word is longer 45 | * than the specified width. 46 | * @default false 47 | */ 48 | cut?: boolean; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/utility/fs.ts: -------------------------------------------------------------------------------- 1 | import Bluebird from "bluebird"; 2 | import fs from "fs"; 3 | import fse from "fs-extra"; 4 | import path from "path"; 5 | 6 | /** 7 | * resolve 8 | * 9 | * transform a path relative to absolute, if relative 10 | * path start with `graphdoc/` return absolute path to 11 | * plugins directory 12 | */ 13 | const MODULE_BASE_PATH = "graphdoc/"; 14 | 15 | export function resolve(relative: string): string { 16 | if (relative.slice(0, MODULE_BASE_PATH.length) === MODULE_BASE_PATH) { 17 | return path.resolve( 18 | __dirname, 19 | "../../", 20 | relative.slice(MODULE_BASE_PATH.length) 21 | ); 22 | } 23 | 24 | return path.resolve(relative); 25 | } 26 | 27 | /** 28 | * Execute fs.read as Promise 29 | */ 30 | export const readFile = Bluebird.promisify(fs.readFile); 31 | export const writeFile = Bluebird.promisify( 32 | (file: string, data: any, cb: (err: Error, result?: undefined) => void) => 33 | fs.writeFile(file, data, cb) 34 | ); 35 | export const copyAll = Bluebird.promisify( 36 | (from: string, to: string, cb: (err: Error, result?: undefined) => void) => 37 | fse.copy(from, to, cb) 38 | ); 39 | export const readDir = Bluebird.promisify(fs.readdir); 40 | export const mkDir = Bluebird.promisify(fs.mkdir as any); 41 | export const removeBuildDirectory = Bluebird.promisify( 42 | fse.remove as any 43 | ); 44 | 45 | /** 46 | * Create build directory from a template directory 47 | */ 48 | export async function createBuildDirectory( 49 | buildDirectory: string, 50 | templateDirectory: string, 51 | assets: string[] 52 | ) { 53 | // read directory 54 | const files = await readDir(templateDirectory); 55 | await Bluebird.all( 56 | files 57 | 58 | // ignore *.mustache templates 59 | .filter(file => path.extname(file) !== ".mustache") 60 | 61 | // copy recursive 62 | .map(file => 63 | copyAll( 64 | path.resolve(templateDirectory, file), 65 | path.resolve(buildDirectory, file) 66 | ) 67 | ) 68 | ); 69 | 70 | // create assets directory 71 | await mkDir(path.resolve(buildDirectory, "assets")); 72 | 73 | await Bluebird.all( 74 | assets.map(asset => 75 | copyAll( 76 | asset, 77 | path.resolve(buildDirectory, "assets", path.basename(asset)) 78 | ) 79 | ) 80 | ); 81 | } 82 | -------------------------------------------------------------------------------- /lib/utility/html.test.ts: -------------------------------------------------------------------------------- 1 | import { Description, Field, InputValue, SchemaType } from '../interface' 2 | import { split, HTML } from './html' 3 | import { data } from '../../test/empty.schema.json' 4 | 5 | test('utility/html.split', () => { 6 | const LOREM_IPSU = `Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.` 7 | expect(split('', 0)).toEqual(['']) 8 | expect(split(LOREM_IPSU, 1)).toEqual(LOREM_IPSU.split(' ')) 9 | expect(split(LOREM_IPSU, 10)).toEqual([ 10 | "Lorem Ipsum", 11 | "is simply dummy", 12 | "text of the", 13 | "printing and", 14 | "typesetting", 15 | "industry. Lorem", 16 | "Ipsum has been", 17 | "the industry's", 18 | "standard dummy", 19 | "text ever since", 20 | "the 1500s,", 21 | "when an unknown", 22 | "printer took", 23 | "a galley of", 24 | "type and scrambled", 25 | "it to make", 26 | "a type specimen", 27 | "book.", 28 | ]) 29 | expect(split(LOREM_IPSU, 100)).toEqual([ 30 | "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's", 31 | "standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled", 32 | "it to make a type specimen book.", 33 | ]) 34 | }) 35 | 36 | 37 | describe('lib/utility/html#HTML', () => { 38 | test('.code', () => { 39 | const html = new HTML 40 | expect(html.code('CODE')).toBe('CODE
') 41 | }) 42 | 43 | test('.highlight', () => { 44 | const html = new HTML 45 | expect(html.highlight('CODE')).toBe('CODE') 46 | }) 47 | 48 | test('.sup', () => { 49 | const html = new HTML 50 | expect(html.sup('CODE')).toBe(' CODE') 51 | }) 52 | 53 | test('.line', () => { 54 | const html = new HTML 55 | expect(html.index).toBe(1) 56 | expect(html.line('CODE')).toBe('1CODE') 57 | expect(html.index).toBe(2) 58 | }) 59 | 60 | test('.tab', () => { 61 | const html = new HTML 62 | expect(html.tab('CODE')).toBe('CODE') 63 | }) 64 | 65 | test('.keyword', () => { 66 | const html = new HTML 67 | expect(html.keyword('CODE')).toBe('CODE') 68 | }) 69 | 70 | test('.comment', () => { 71 | const html = new HTML 72 | expect(html.comment('CODE')).toBe('# CODE') 73 | }) 74 | 75 | test('.identifier', () => { 76 | const html = new HTML 77 | const type = data.__schema.types.find(t => t.name === 'Query') 78 | expect(html.identifier(type as Description)).toBe('Query') 79 | }) 80 | 81 | test('.parameter', () => { 82 | const html = new HTML 83 | const input: InputValue = data.__schema.types.find(t => t.name === 'AddCommentInput') as any 84 | expect(html.parameter(input)).toBe('AddCommentInput') 85 | }) 86 | 87 | test('.property', () => { 88 | const html = new HTML 89 | expect(html.property('PROPERTY')).toBe('PROPERTY') 90 | }) 91 | 92 | test('.useIdentifier', () => { 93 | const html = new HTML 94 | const schema: SchemaType = data.__schema.types.find(t => t.name === '__Schema') as any 95 | const field: Field = (schema.fields || []).find(f => f.name === 'types') as any 96 | 97 | expect(html.useIdentifier(field.type, 'HREF')).toBe('[__Type!]!') 98 | }) 99 | 100 | test('.useIdentifierLength', () => { 101 | const html = new HTML 102 | const schema: SchemaType = data.__schema.types.find(t => t.name === '__Schema') as any 103 | const field: Field = (schema.fields || []).find(f => f.name === 'types') as any 104 | 105 | expect(html.useIdentifierLength(field.type)).toBe(10) 106 | }) 107 | 108 | test('.value', () => { 109 | const html = new HTML 110 | expect(html.value('"STRING"')).toBe('"STRING"') 111 | expect(html.value('NUMBER')).toBe('NUMBER') 112 | }) 113 | }) -------------------------------------------------------------------------------- /lib/utility/html.ts: -------------------------------------------------------------------------------- 1 | import { DeepTypeRef, Description, InputValue, TypeRef } from "../interface"; 2 | import { LIST, NON_NULL } from "./introspection"; 3 | 4 | export class HTML { 5 | index = 1; 6 | 7 | code(code: string): string { 8 | return `${code}
`; 9 | } 10 | 11 | highlight(text: string): string { 12 | return `${text}`; 13 | } 14 | 15 | sup(text: string): string { 16 | return ` ${text}`; 17 | } 18 | 19 | line(code?: string): string { 20 | const row = this.index++; 21 | return `${row}${code || 22 | ""}`; 23 | } 24 | 25 | tab(code: string): string { 26 | return `${code}`; 27 | } 28 | 29 | keyword(keyword: string): string { 30 | return `${keyword}`; 31 | } 32 | 33 | comment(comment: string): string { 34 | return `# ${comment}`; 35 | } 36 | 37 | identifier(type: Description): string { 38 | return `${type.name}`; 39 | } 40 | 41 | parameter(arg: InputValue): string { 42 | return `${arg.name}`; 43 | } 44 | 45 | property(name: string): string { 46 | return `${name}`; 47 | } 48 | 49 | useIdentifier( 50 | type: DeepTypeRef | TypeRef | Description, 51 | toUrl: string 52 | ): string { 53 | switch (type.kind) { 54 | case LIST: 55 | return ( 56 | "[" + this.useIdentifier((type as DeepTypeRef).ofType, toUrl) + "]" 57 | ); 58 | 59 | case NON_NULL: 60 | return this.useIdentifier((type as DeepTypeRef).ofType, toUrl) + "!"; 61 | 62 | default: 63 | return `${type.name}`; 64 | } 65 | } 66 | 67 | useIdentifierLength( 68 | type: DeepTypeRef | TypeRef | Description, 69 | base: number = 0 70 | ): number { 71 | switch (type.kind) { 72 | case LIST: 73 | return this.useIdentifierLength((type as DeepTypeRef).ofType, base + 2); 74 | 75 | case NON_NULL: 76 | return this.useIdentifierLength((type as DeepTypeRef).ofType, base + 1); 77 | 78 | default: 79 | return base + (type.name || "").length; 80 | } 81 | } 82 | 83 | value(val: string): string { 84 | return val[0] === '"' 85 | ? `${val}` 86 | : `${val}`; 87 | } 88 | } 89 | 90 | export function split(text: string, len: number): string[] { 91 | return text.split(/\s+/).reduce( 92 | (result: string[], word: string) => { 93 | const last = result.length - 1; 94 | const lineLen = result[last].length; 95 | 96 | if (lineLen === 0) { 97 | result[last] = word; 98 | } else if (lineLen < len) { 99 | result[last] = result[last] + " " + word; 100 | } else { 101 | result.push(word); 102 | } 103 | 104 | return result; 105 | }, 106 | [""] 107 | ); 108 | } 109 | -------------------------------------------------------------------------------- /lib/utility/index.ts: -------------------------------------------------------------------------------- 1 | export { HTML, split } from "./html"; 2 | 3 | export { 4 | NavigationItem, 5 | NavigationSection, 6 | DocumentSection, 7 | Plugin, 8 | sortTypes 9 | } from "./plugin"; 10 | 11 | export { 12 | query, 13 | getTypeOf, 14 | getFilenameOf, 15 | ENUM, 16 | INPUT_OBJECT, 17 | INTERFACE, 18 | LIST, 19 | NON_NULL, 20 | OBJECT, 21 | SCALAR, 22 | UNION 23 | } from "./introspection"; 24 | 25 | export { Output } from "./output"; 26 | 27 | export { createData } from "./template"; 28 | -------------------------------------------------------------------------------- /lib/utility/introspection.test.ts: -------------------------------------------------------------------------------- 1 | import { getTypeOf, getFilenameOf } from './introspection' 2 | import { data } from '../../test/empty.schema.json' 3 | import { DeepTypeRef, SchemaType, Field } from '../interface' 4 | 5 | test(`lib/utility/introspection#getTypeOf`, () => { 6 | const schema: SchemaType = data.__schema.types.find(t => t.name === '__Schema') as any 7 | const field: Field = (schema.fields || []).find(f => f.name === 'types') as any 8 | 9 | expect(getTypeOf(field.type)).toBe(field.type.ofType?.ofType?.ofType) 10 | }) 11 | 12 | test(`lib/utility/introspection#getTypeOf`, () => { 13 | const schema: SchemaType = data.__schema.types.find(t => t.name === '__Schema') as any 14 | const field: DeepTypeRef = (schema.fields || []).find(f => f.name === 'types') as any 15 | 16 | expect(getFilenameOf(field)).toBe('types.doc.html') 17 | }) -------------------------------------------------------------------------------- /lib/utility/introspection.ts: -------------------------------------------------------------------------------- 1 | import { DeepTypeRef, Description, TypeRef } from "../interface"; 2 | 3 | export const LIST = "LIST"; 4 | export const NON_NULL = "NON_NULL"; 5 | export const SCALAR = "SCALAR"; 6 | export const OBJECT = "OBJECT"; 7 | export const INTERFACE = "INTERFACE"; 8 | export const UNION = "UNION"; 9 | export const ENUM = "ENUM"; 10 | export const INPUT_OBJECT = "INPUT_OBJECT"; 11 | 12 | export function getTypeOf(t: DeepTypeRef | TypeRef): TypeRef { 13 | if (t.kind === LIST || t.kind === NON_NULL) { 14 | return getTypeOf((t as DeepTypeRef).ofType); 15 | } 16 | 17 | return t as TypeRef; 18 | } 19 | 20 | export function getFilenameOf( 21 | type: DeepTypeRef | TypeRef | Description 22 | ): string { 23 | const name = 24 | type.kind === LIST || type.kind === NON_NULL 25 | ? getTypeOf(type as DeepTypeRef).name.toLowerCase() 26 | : (type as Description).name.toLowerCase(); 27 | 28 | if (name[0] === "_" && name[1] === "_") { 29 | return name.slice(2) + ".spec.html"; 30 | } 31 | 32 | return name + ".doc.html"; 33 | } 34 | 35 | const fullTypeFragment = ` 36 | fragment FullType on __Type { 37 | fields(includeDeprecated: true) { 38 | name 39 | description 40 | args { 41 | ...InputValue 42 | } 43 | type { 44 | ...TypeRef 45 | } 46 | isDeprecated 47 | deprecationReason 48 | } 49 | inputFields { 50 | ...InputValue 51 | } 52 | interfaces { 53 | ...TypeRef 54 | } 55 | enumValues(includeDeprecated: true) { 56 | name 57 | description 58 | isDeprecated 59 | deprecationReason 60 | } 61 | possibleTypes { 62 | ...TypeRef 63 | } 64 | }`; 65 | 66 | const inputValueFragment = ` 67 | fragment InputValue on __InputValue { 68 | name 69 | description 70 | type { ...TypeRef } 71 | defaultValue 72 | }`; 73 | 74 | const typeRefFragment = ` 75 | fragment TypeRef on __Type { 76 | kind 77 | name 78 | description 79 | ofType { 80 | kind 81 | name 82 | description 83 | ofType { 84 | kind 85 | name 86 | description 87 | ofType { 88 | kind 89 | name 90 | description 91 | ofType { 92 | kind 93 | name 94 | description 95 | ofType { 96 | kind 97 | name 98 | description 99 | ofType { 100 | kind 101 | name 102 | description 103 | ofType { 104 | kind 105 | name 106 | description 107 | } 108 | } 109 | } 110 | } 111 | } 112 | } 113 | } 114 | }`; 115 | 116 | export const query = `query IntrospectionQuery { 117 | __schema { 118 | queryType { name description kind} 119 | mutationType { name description kind } 120 | subscriptionType { name description kind } 121 | types { 122 | name 123 | kind 124 | description 125 | ...FullType 126 | } 127 | directives { 128 | name 129 | description 130 | locations 131 | args { 132 | ...InputValue 133 | } 134 | } 135 | } 136 | } 137 | 138 | ${fullTypeFragment} 139 | ${inputValueFragment} 140 | ${typeRefFragment} 141 | `; 142 | 143 | export const queryRoot = `query IntrospectionQuery { 144 | __schema { 145 | queryType { name description kind} 146 | mutationType { name description kind } 147 | subscriptionType { name description kind } 148 | } 149 | } 150 | `; 151 | 152 | export const queryTypes = `query IntrospectionQuery { 153 | __schema { 154 | types { 155 | name 156 | kind 157 | description 158 | ...FullType 159 | } 160 | } 161 | } 162 | 163 | ${fullTypeFragment} 164 | ${inputValueFragment} 165 | ${typeRefFragment} 166 | `; 167 | 168 | export const queryDirectives = `query IntrospectionQuery { 169 | __schema { 170 | directives { 171 | name 172 | description 173 | locations 174 | args { 175 | ...InputValue 176 | } 177 | } 178 | } 179 | } 180 | 181 | ${inputValueFragment} 182 | ${typeRefFragment} 183 | `; 184 | -------------------------------------------------------------------------------- /lib/utility/output.test.ts: -------------------------------------------------------------------------------- 1 | import { Output } from './output' 2 | 3 | test(`lib/utility/output#Output`, () => { 4 | const m = { 5 | log: jest.fn(), 6 | error: jest.fn() 7 | } 8 | 9 | const exit = jest.fn() 10 | Object.assign(process, { exit }) 11 | 12 | const output = new Output(m, { verbose: false }) 13 | output.ok('ref', 'value') 14 | output.info('ref', 'value') 15 | output.error('string message' as any) 16 | output.error({ message: 'error message', stack: 'error stack' } as any) 17 | 18 | expect(m.log.mock.calls).toEqual([ 19 | [ 20 | "%c ✓ %s: %c%s", 21 | "color:green", 22 | "ref", 23 | "color:grey", 24 | "value", 25 | ] 26 | ]) 27 | expect(m.error.mock.calls).toEqual([ 28 | [ 29 | "%c ✗ %s", 30 | "color:red", 31 | "string message", 32 | ], 33 | [ 34 | "", 35 | ], 36 | [ 37 | "%c ✗ %s", 38 | "color:red", 39 | "error message", 40 | ], 41 | [ 42 | "", 43 | ], 44 | ]) 45 | expect(exit.mock.calls).toEqual([ 46 | [1], 47 | [1], 48 | ]) 49 | }) 50 | 51 | test(`lib/utility/output#Output (verbose)`, () => { 52 | const m = { 53 | log: jest.fn(), 54 | error: jest.fn() 55 | } 56 | 57 | const exit = jest.fn() 58 | Object.assign(process, { exit }) 59 | 60 | const output = new Output(m, { verbose: true }) 61 | output.ok('ref', 'value') 62 | output.info('ref', 'value') 63 | output.error('string message' as any) 64 | output.error({ message: 'error message', stack: 'error stack' } as any) 65 | 66 | expect(m.log.mock.calls).toEqual([ 67 | [ 68 | "%c ✓ %s: %c%s", 69 | "color:green", 70 | "ref", 71 | "color:grey", 72 | "value", 73 | ], 74 | [ 75 | "%c ❭ %s: %c%s", 76 | "color:yellow", 77 | "ref", 78 | "color:grey", 79 | "value", 80 | ], 81 | ]) 82 | expect(m.error.mock.calls).toEqual([ 83 | [ 84 | "%c ✗ %s", 85 | "color:red", 86 | "string message", 87 | ], 88 | [ 89 | "%c%s", 90 | "color:grey", 91 | " NO STACK", 92 | ], 93 | [ 94 | "", 95 | ], 96 | [ 97 | "%c ✗ %s", 98 | "color:red", 99 | "error message", 100 | ], 101 | [ 102 | "%c%s", 103 | "color:grey", 104 | "error stack", 105 | ], 106 | [ 107 | "", 108 | ], 109 | ]) 110 | 111 | expect(exit.mock.calls).toEqual([ 112 | [1], 113 | [1], 114 | ]) 115 | }) 116 | -------------------------------------------------------------------------------- /lib/utility/output.ts: -------------------------------------------------------------------------------- 1 | import { OutputInterface } from "@2fd/command"; 2 | 3 | export interface IOutputOptions { 4 | verbose: boolean; 5 | } 6 | 7 | export class Output { 8 | constructor(public out: OutputInterface, public options: IOutputOptions) {} 9 | 10 | ok(ref: string, value: string) { 11 | this.out.log("%c ✓ %s: %c%s", "color:green", ref, "color:grey", value); 12 | } 13 | 14 | info(ref: string, value: string) { 15 | if (this.options.verbose) { 16 | this.out.log("%c ❭ %s: %c%s", "color:yellow", ref, "color:grey", value); 17 | } 18 | } 19 | 20 | error(err: NodeJS.ErrnoException) { 21 | this.out.error("%c ✗ %s", "color:red", err.message || err); 22 | 23 | if (this.options.verbose) { 24 | this.out.error("%c%s", "color:grey", err.stack || " NO STACK"); 25 | } 26 | 27 | this.out.error(""); 28 | process.exit(1); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/utility/plugin.ts: -------------------------------------------------------------------------------- 1 | import url from "url"; 2 | import { 3 | DeepTypeRef, 4 | Description, 5 | Directive, 6 | DocumentSectionInterface, 7 | NavigationItemInterface, 8 | NavigationSectionInterface, 9 | PluginInterface, 10 | Schema, 11 | SchemaType, 12 | TypeRef 13 | } from "../interface"; 14 | import { getFilenameOf } from "./introspection"; 15 | 16 | /** 17 | * Plugin Base implementation 18 | */ 19 | export abstract class Plugin { 20 | static collect(collection: T[][]): T[] { 21 | let result: T[] = []; 22 | 23 | collection.forEach(item => { 24 | if (Array.isArray(item)) { 25 | result = result.concat(item); 26 | } 27 | }); 28 | 29 | return result; 30 | } 31 | 32 | static async collectNavigations( 33 | plugins: PluginInterface[], 34 | buildForType?: string 35 | ): Promise { 36 | const navigationCollection = await Promise.all< 37 | NavigationSectionInterface[] 38 | >( 39 | plugins.map(plugin => { 40 | return plugin.getNavigations 41 | ? plugin.getNavigations(buildForType) 42 | : (null as any); 43 | }) 44 | ); 45 | 46 | return Plugin.collect(navigationCollection); 47 | } 48 | 49 | static async collectDocuments( 50 | plugins: PluginInterface[], 51 | buildForType?: string 52 | ): Promise { 53 | const navigationCollection = await Promise.all( 54 | plugins.map(plugin => { 55 | return plugin.getDocuments 56 | ? plugin.getDocuments(buildForType) 57 | : (null as any); 58 | }) 59 | ); 60 | 61 | return Plugin.collect(navigationCollection); 62 | } 63 | 64 | static async collectHeaders( 65 | plugins: PluginInterface[], 66 | buildForType?: string 67 | ): Promise { 68 | const headerCollection = await Promise.all( 69 | plugins.map(plugin => { 70 | return plugin.getHeaders 71 | ? plugin.getHeaders(buildForType) 72 | : (null as any); 73 | }) 74 | ); 75 | 76 | return Plugin.collect(headerCollection); 77 | } 78 | 79 | static async collectAssets(plugins: PluginInterface[]): Promise { 80 | const assetCollection = await Promise.all( 81 | plugins.map(plugin => { 82 | return plugin.getAssets ? plugin.getAssets() : (null as any); 83 | }) 84 | ); 85 | 86 | return Plugin.collect(assetCollection); 87 | } 88 | 89 | queryType: SchemaType | null = null; 90 | 91 | mutationType: SchemaType | null = null; 92 | 93 | subscriptionType: SchemaType | null = null; 94 | 95 | typeMap: { [name: string]: SchemaType } = {}; 96 | 97 | directiveMap: { [name: string]: Directive } = {}; 98 | 99 | // getNavigations?: (buildForType?: string) => NavigationSectionInterface[] | PromiseLike; 100 | // getDocuments?: (buildForType?: string) => DocumentSectionInterface[] | PromiseLike; 101 | // getHeaders?: (buildForType?: string) => string[] | PromiseLike; 102 | // getAssets?: () => string[] | PromiseLike; 103 | 104 | constructor( 105 | public document: Schema, 106 | public projectPackage: any, 107 | public graphdocPackage: any 108 | ) { 109 | this.document.types = this.document.types 110 | ? this.document.types.sort(sortTypes) 111 | : []; 112 | 113 | this.document.directives = this.document.directives 114 | ? this.document.directives.sort((a, b) => 115 | (a.name || "").localeCompare(b.name || "") 116 | ) 117 | : []; 118 | 119 | this.document.types.forEach(type => { 120 | this.typeMap[type.name || ""] = type; 121 | }); 122 | 123 | this.document.directives.forEach(directive => { 124 | this.directiveMap[directive.name || ""] = directive; 125 | }); 126 | 127 | if (document.queryType) { 128 | this.queryType = this.typeMap[document.queryType.name || ""]; 129 | } 130 | 131 | if (document.mutationType) { 132 | this.mutationType = this.typeMap[document.mutationType.name || ""]; 133 | } 134 | 135 | if (document.subscriptionType) { 136 | this.subscriptionType = this.typeMap[ 137 | document.subscriptionType.name || "" 138 | ]; 139 | } 140 | } 141 | 142 | url(type: DeepTypeRef | TypeRef | Description): string { 143 | return url.resolve( 144 | this.projectPackage.graphdoc.baseUrl, 145 | getFilenameOf(type) 146 | ); 147 | } 148 | } 149 | 150 | /** 151 | * NavigationSectionInterface short implementation 152 | */ 153 | // tslint:disable-next-line:max-classes-per-file 154 | export class NavigationSection implements NavigationSectionInterface { 155 | constructor(public title: string, public items: NavigationItem[] = []) {} 156 | } 157 | 158 | /** 159 | * NavigationItemInterface short implementation 160 | */ 161 | // tslint:disable-next-line:max-classes-per-file 162 | export class NavigationItem implements NavigationItemInterface { 163 | constructor( 164 | public text: string, 165 | public href: string, 166 | public isActive: boolean 167 | ) {} 168 | } 169 | 170 | /** 171 | * DocumentSectionInterface short implementation 172 | */ 173 | // tslint:disable-next-line:max-classes-per-file 174 | export class DocumentSection implements DocumentSectionInterface { 175 | constructor(public title: string, public description: string) {} 176 | } 177 | 178 | function priorityType(type: SchemaType): number { 179 | const name = type.name || ""; 180 | if (name[0] === "_" && name[1] === "_") { 181 | return 2; 182 | } else if (name[0] === "_") { 183 | return 1; 184 | } else { 185 | return 0; 186 | } 187 | } 188 | 189 | export function sortTypes(a: SchemaType, b: SchemaType): number { 190 | const priorityA = priorityType(a); 191 | const priorityB = priorityType(b); 192 | 193 | if (priorityA === priorityB) { 194 | return (a.name || "").localeCompare(b.name || ""); 195 | } else { 196 | return priorityA - priorityB; 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /lib/utility/template.test.ts: -------------------------------------------------------------------------------- 1 | import { slugTemplate, createData } from './template' 2 | 3 | test('lib/utility/template#slugTemplate', () => { 4 | const slug = slugTemplate() 5 | expect(typeof slug).toBe('function') 6 | expect(slug('{{RAW TEXT}}', (value: string) => value)).toBe('raw-text') 7 | }) 8 | 9 | describe('lib/utility/template#createData', () => { 10 | // test(`default data`, async () => { 11 | // const data = await createData({}, {}, []) 12 | // expect(data).toHaveProperty('description', '') 13 | // expect(data).toHaveProperty('documents', []) 14 | // expect(data).toHaveProperty('graphdocPackage', {}) 15 | // expect(data).toHaveProperty('headers', '') 16 | // expect(data).toHaveProperty('navigations', []) 17 | // expect(data).toHaveProperty('projectPackage', {}) 18 | // expect(data).toHaveProperty('slug') 19 | // expect(data).toHaveProperty('title', "Graphql schema documentation") 20 | // expect(data).toHaveProperty('type', undefined) 21 | // }) 22 | 23 | test(`prop "title"`, async () => { 24 | const defaultTitle = await createData({}, {}, []) 25 | 26 | const configTitle = await createData({ 27 | graphdoc: { 28 | title: "Package title" 29 | } 30 | }, {}, []) 31 | 32 | const typeTitle = await createData({ 33 | graphdoc: { 34 | title: "Package title" 35 | } 36 | }, {}, [], { 37 | name: "Type title", 38 | description: "Type description", 39 | kind: "SCALAR", 40 | ofType: null 41 | }) 42 | 43 | expect(defaultTitle).toHaveProperty('title', "Graphql schema documentation") 44 | expect(configTitle).toHaveProperty('title', "Package title") 45 | expect(typeTitle).toHaveProperty('title', "Type title") 46 | }) 47 | 48 | test(`prop "description"`, async () => { 49 | const defaultDescription = await createData({}, {}, []) 50 | 51 | const configDescription = await createData({ 52 | description: "Package description", 53 | graphdoc: { 54 | title: "Package title" 55 | } 56 | }, {}, []) 57 | 58 | const typeDescription = await createData({ 59 | description: "Package description", 60 | graphdoc: { 61 | title: "Package title" 62 | } 63 | }, {}, [], { 64 | name: "Type title", 65 | description: "Type description", 66 | kind: "SCALAR", 67 | ofType: null 68 | }) 69 | 70 | expect(defaultDescription).toHaveProperty('description', "") 71 | expect(configDescription).toHaveProperty('description', "

Package description

\n") 72 | expect(typeDescription).toHaveProperty('description', "

Type description

\n") 73 | }) 74 | }) -------------------------------------------------------------------------------- /lib/utility/template.ts: -------------------------------------------------------------------------------- 1 | import marked from "marked"; 2 | import slug from "slug"; 3 | import { 4 | DocumentSectionInterface, 5 | NavigationSectionInterface, 6 | PluginInterface, 7 | TypeRef 8 | } from "../interface"; 9 | import { Plugin } from "./plugin"; 10 | 11 | export function slugTemplate() { 12 | return (text, render) => slug(render(text)).toLowerCase(); 13 | } 14 | 15 | export interface ITemplateData { 16 | title: string; 17 | type?: TypeRef; 18 | description: string; 19 | headers: string; 20 | navigations: NavigationSectionInterface[]; 21 | documents: DocumentSectionInterface[]; 22 | projectPackage: any; 23 | graphdocPackage: any; 24 | slug: typeof slugTemplate; 25 | } 26 | 27 | type Headers = string[]; 28 | type Navs = NavigationSectionInterface[]; 29 | type Docs = DocumentSectionInterface[]; 30 | 31 | export async function createData( 32 | projectPackage: any, 33 | graphdocPackage: any, 34 | plugins: PluginInterface[], 35 | type?: TypeRef 36 | ): Promise { 37 | const name = (type && type.name) || ""; 38 | const [headers, navigations, documents]: [ 39 | Headers, 40 | Navs, 41 | Docs 42 | ] = await Promise.all([ 43 | Plugin.collectHeaders(plugins, name), 44 | Plugin.collectNavigations(plugins, name), 45 | Plugin.collectDocuments(plugins, name) 46 | ]); 47 | 48 | const title = 49 | name || (projectPackage && projectPackage.graphdoc && projectPackage.graphdoc.title) || "Graphql schema documentation"; 50 | 51 | const description = type 52 | ? marked(type.description || "") 53 | : marked(projectPackage.description || ""); 54 | 55 | return { 56 | title, 57 | type, 58 | description, 59 | headers: headers.join(""), 60 | navigations, 61 | documents, 62 | projectPackage, 63 | graphdocPackage, 64 | slug: slugTemplate 65 | }; 66 | } 67 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@2fd/graphdoc", 3 | "version": "2.4.0", 4 | "description": "Static page generator for documenting GraphQL Schema", 5 | "main": "bin/graphdoc", 6 | "bin": { 7 | "graphdoc": "./bin/graphdoc.js" 8 | }, 9 | "scripts": { 10 | "compile": "tsc -p .", 11 | "declaration": "tsc -d -p tsconfig.json", 12 | "readme": "ts-node ./README.ts", 13 | "doc.github": "node bin/graphdoc.js -c example.github.json -f -v", 14 | "doc.pokemon": "node bin/graphdoc.js -c example.pokemon.json -f -v", 15 | "doc.shopify": "node bin/graphdoc.js -c example.shopify.json -f -v", 16 | "doc.scaphold": "node bin/graphdoc.js -c example.scaphold.json -f -v", 17 | "doc.starwars": "node bin/graphdoc.js -c example.starWars.json -f -v", 18 | "doc.starwars-js": "node bin/graphdoc.js -c example.starWars-js.json -f -v", 19 | "test": "jest" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/2fd/graphdoc.git" 24 | }, 25 | "keywords": [ 26 | "graphql", 27 | "documentation" 28 | ], 29 | "author": "Fede Ramirez ", 30 | "license": "MIT", 31 | "bugs": { 32 | "url": "https://github.com/2fd/graphdoc/issues" 33 | }, 34 | "jest": { 35 | "preset": "ts-jest", 36 | "testRegex": "\\.(test|spec)\\.ts$" 37 | }, 38 | "homepage": "https://github.com/2fd/graphdoc#readme", 39 | "devDependencies": { 40 | "@salesforce-ux/design-system": "2.2.1", 41 | "@types/bluebird": "^3.0.37", 42 | "@types/chai": "^4.2.14", 43 | "@types/fs-extra": "0.0.32", 44 | "@types/glob": "^7.1.3", 45 | "@types/graphql": "^0.8.6", 46 | "@types/jest": "^18.1.1", 47 | "@types/marked": "1.2.1", 48 | "@types/mocha": "^8.0.4", 49 | "@types/mustache": "^0.8.28", 50 | "@types/node": "^14.14.10", 51 | "@types/request": "^2.48.5", 52 | "@types/striptags": "^3.1.1", 53 | "chai": "^3.5.0", 54 | "express": "^4.17.1", 55 | "express-graphql": "^0.12.0", 56 | "handlebars": "^4.7.6", 57 | "jest": "^24.9.0", 58 | "prettier": "^1.18.2", 59 | "ts-jest": "^24.3.0", 60 | "ts-node": "^9.1.1", 61 | "tslint": "^6.1.3", 62 | "tslint-config-prettier": "^1.18.0", 63 | "typescript": "^3.9.7" 64 | }, 65 | "dependencies": { 66 | "@2fd/command": "^1.1.2", 67 | "acorn": "^8.0.4", 68 | "bluebird": "^3.5.5", 69 | "deepmerge": "^4.2.2", 70 | "fs-extra": "^9.0.1", 71 | "glob": "^7.1.0", 72 | "graphql": "^15.4.0", 73 | "lodash": "^4.17.20", 74 | "marked": "^1.2.5", 75 | "mustache": "^4.1.0", 76 | "request": "^2.88.0", 77 | "slug": "^4.0.2", 78 | "striptags": "^3.1.1", 79 | "word-wrap": "^1.2.1" 80 | }, 81 | "graphdoc": { 82 | "ga": "UA-54154153-2", 83 | "graphiql": "https://developer.github.com/early-access/graphql/explorer/", 84 | "logo": "", 85 | "schemaFile": "./test/github.json", 86 | "output": "./gh-pages/github", 87 | "baseUrl": "./" 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /plugins/default.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DocumentSectionInterface, 3 | NavigationSectionInterface, 4 | PluginInterface, 5 | Schema 6 | } from "../lib/interface"; 7 | import { Plugin } from "../lib/utility"; 8 | import RequireByPlugin from "./document.require-by"; 9 | import DocumentSchema from "./document.schema"; 10 | import NavigationDirective from "./navigation.directive"; 11 | import NavigationEnum from "./navigation.enum"; 12 | import NavigationInput from "./navigation.input"; 13 | import NavigationInterfaces from "./navigation.interface"; 14 | import NavigationObject from "./navigation.object"; 15 | import NavigationScalar from "./navigation.scalar"; 16 | import NavigationSchema from "./navigation.schema"; 17 | import NavigationUnion from "./navigation.union"; 18 | 19 | export default class NavigationDirectives extends Plugin 20 | implements PluginInterface { 21 | plugins: PluginInterface[]; 22 | 23 | constructor(document: Schema, graphdocPackage: any, projectPackage: any) { 24 | super(document, graphdocPackage, projectPackage); 25 | this.plugins = [ 26 | new NavigationSchema(document, graphdocPackage, projectPackage), 27 | new NavigationScalar(document, graphdocPackage, projectPackage), 28 | new NavigationEnum(document, graphdocPackage, projectPackage), 29 | new NavigationInterfaces(document, graphdocPackage, projectPackage), 30 | new NavigationUnion(document, graphdocPackage, projectPackage), 31 | new NavigationObject(document, graphdocPackage, projectPackage), 32 | new NavigationInput(document, graphdocPackage, projectPackage), 33 | new NavigationDirective(document, graphdocPackage, projectPackage), 34 | new DocumentSchema(document, graphdocPackage, projectPackage), 35 | new RequireByPlugin(document, graphdocPackage, projectPackage) 36 | ]; 37 | } 38 | 39 | getNavigations(buildForType?: string): Promise { 40 | return Plugin.collectNavigations(this.plugins, buildForType); 41 | } 42 | 43 | getDocuments(buildForType?: string): Promise { 44 | return Plugin.collectDocuments(this.plugins, buildForType); 45 | } 46 | 47 | getHeaders(buildForType?: string): Promise { 48 | return Plugin.collectHeaders(this.plugins, buildForType); 49 | } 50 | 51 | getAssets(): Promise { 52 | return Plugin.collectAssets(this.plugins); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /plugins/document.require-by/index.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from "path"; 2 | import striptags from "striptags"; 3 | import { 4 | DocumentSectionInterface, 5 | PluginInterface, 6 | Schema, 7 | SchemaType 8 | } from "../../lib/interface"; 9 | import { 10 | ENUM, 11 | getTypeOf, 12 | INPUT_OBJECT, 13 | INTERFACE, 14 | OBJECT, 15 | Plugin, 16 | SCALAR, 17 | UNION 18 | } from "../../lib/utility"; 19 | 20 | export default class RequireByPlugin extends Plugin implements PluginInterface { 21 | requireBy: Map; 22 | 23 | constructor( 24 | public document: Schema, 25 | public projectPackage: any, 26 | public graphdocPackage: any 27 | ) { 28 | super(document, projectPackage, graphdocPackage); 29 | 30 | this.requireBy = new Map(); 31 | 32 | if (Array.isArray(document.types)) { 33 | document.types.forEach((type: SchemaType) => { 34 | switch (type.kind) { 35 | // Scalars and enums have no dependencies 36 | case SCALAR: 37 | case ENUM: 38 | return; 39 | 40 | case OBJECT: 41 | case INTERFACE: 42 | case UNION: 43 | case INPUT_OBJECT: 44 | this.getDependencies(type).forEach((curr: string) => { 45 | const deps = this.requireBy.get(curr) || []; 46 | deps.push(type); 47 | this.requireBy.set(curr, deps); 48 | }); 49 | break; 50 | } 51 | }); 52 | } 53 | } 54 | 55 | getAssets() { 56 | return [resolve(__dirname, "require-by.css")]; 57 | } 58 | 59 | getDependencies(type: SchemaType): string[] { 60 | const deps: string[] = []; 61 | 62 | if (Array.isArray(type.interfaces) && type.interfaces.length > 0) { 63 | type.interfaces.forEach(i => deps.push(i.name)); 64 | } 65 | 66 | if (Array.isArray(type.fields) && type.fields.length > 0) { 67 | type.fields.forEach(field => { 68 | deps.push(getTypeOf(field.type).name); 69 | 70 | if (Array.isArray(field.args) && field.args.length > 0) { 71 | field.args.forEach(arg => { 72 | deps.push(getTypeOf(arg.type).name); 73 | }); 74 | } 75 | }); 76 | } 77 | 78 | if (Array.isArray(type.inputFields) && type.inputFields.length > 0) { 79 | type.inputFields.forEach(field => { 80 | deps.push(getTypeOf(field.type).name); 81 | }); 82 | } 83 | 84 | if ( 85 | type.kind !== INTERFACE && 86 | Array.isArray(type.possibleTypes) && 87 | type.possibleTypes.length > 0 88 | ) { 89 | type.possibleTypes.forEach(t => { 90 | deps.push(getTypeOf(t).name); 91 | }); 92 | } 93 | 94 | return deps; 95 | } 96 | 97 | getDescription(type: SchemaType): string { 98 | return ( 99 | "
  • " + 100 | '' + 107 | type.name + 108 | "" + 109 | type.description + 110 | "" + 111 | "" + 112 | "
  • " 113 | ); 114 | } 115 | 116 | getDocuments(buildForType?: string): DocumentSectionInterface[] { 117 | if (!buildForType) { 118 | return []; 119 | } 120 | 121 | const requireBy = this.requireBy.get(buildForType); 122 | 123 | if (!Array.isArray(requireBy) || requireBy.length === 0) { 124 | return [ 125 | { 126 | title: "Required by", 127 | description: 128 | '
    ' + 129 | "This element is not required by anyone" + 130 | "
    " 131 | } 132 | ]; 133 | } 134 | 135 | const used = new Set(); 136 | 137 | return [ 138 | { 139 | title: "Required by", 140 | description: 141 | '
      ' + 142 | requireBy 143 | .filter(t => { 144 | return used.has(t.name) ? false : used.add(t.name); 145 | }) 146 | .map(t => this.getDescription(t)) 147 | .join("") + 148 | "
    " 149 | } 150 | ]; 151 | } 152 | 153 | getHeaders(): string[] { 154 | return [ 155 | '' 156 | ]; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /plugins/document.require-by/require-by.css: -------------------------------------------------------------------------------- 1 | div.require-by.anyone, 2 | ul.require-by a { 3 | max-width: 100%; 4 | overflow: hidden; 5 | text-overflow: ellipsis; 6 | white-space: nowrap; 7 | display: block; 8 | } 9 | 10 | div.require-by.anyone { 11 | background-color: #f0f8fc; 12 | border: 1px solid #d8dde6; 13 | color: grey; 14 | padding: 2rem; 15 | text-align: center; 16 | margin: 1rem 0; 17 | border-radius: 0.25rem; 18 | } 19 | 20 | ul.require-by { 21 | margin: 0; 22 | padding: 0; 23 | } 24 | 25 | ul.require-by a { 26 | border-left: .25rem solid transparent; 27 | border-top: 1px solid transparent; 28 | border-bottom: 1px solid transparent; 29 | padding: .5rem 1.5rem; 30 | } 31 | 32 | ul.require-by a:hover { 33 | text-decoration: none; 34 | background-color: #f0f8fc; 35 | border-color: #d8dde6; 36 | border-left-color: #005fb2; 37 | } 38 | 39 | ul.require-by a em { 40 | margin-left: 1rem; 41 | font-size: .75rem; 42 | color: grey; 43 | } -------------------------------------------------------------------------------- /plugins/document.schema/assets/code.css: -------------------------------------------------------------------------------- 1 | .code { 2 | background-color: #fff; 3 | color: #4D4D4C; 4 | border: 1px solid #4D4D4C; 5 | font-size: 14px; 6 | font-family: 'Ubuntu Mono'; 7 | list-style-type: decimal; 8 | border-radius: 0.25rem; 9 | } 10 | 11 | .code .gutter { 12 | background: #f6f6f6; 13 | color: #4D4D4C; 14 | } 15 | 16 | .code .print-margin { 17 | width: 1px; 18 | background: #f6f6f6 19 | } 20 | 21 | .code .row:hover { 22 | background: #EFEFEF; 23 | } 24 | 25 | .code .td-index { 26 | color: rgba(27, 31, 35, .3); 27 | padding: 1px 8px 1px 16px; 28 | text-align: right; 29 | width: 1%; 30 | } 31 | 32 | .code .td-index:hover { 33 | cursor: pointer; 34 | color: rgba(27, 31, 35, .6); 35 | } 36 | 37 | .code .td-code { 38 | padding: 1px 8px; 39 | } 40 | 41 | .code .td-code.highlighted { 42 | background-color: #fffbdd; 43 | } 44 | 45 | .code .tab { 46 | padding-left: 2em; 47 | } 48 | 49 | .code .cursor { 50 | color: #AEAFAD 51 | } 52 | 53 | .code .marker-layer .selection { 54 | background: #D6D6D6 55 | } 56 | 57 | .code.multiselect .selection.start { 58 | box-shadow: 0 0 3px 0px #FFFFFF; 59 | } 60 | 61 | .code .marker-layer .step { 62 | background: rgb(255, 255, 0) 63 | } 64 | 65 | .code .marker-layer .bracket { 66 | margin: -1px 0 0 -1px; 67 | border: 1px solid #D1D1D1 68 | } 69 | 70 | .code .marker-layer .active-line { 71 | background: #EFEFEF 72 | } 73 | 74 | .code .gutter-active-line { 75 | background-color: #dcdcdc 76 | } 77 | 78 | .code .marker-layer .selected-word { 79 | border: 1px solid #D6D6D6 80 | } 81 | 82 | .code .invisible { 83 | color: #D1D1D1 84 | } 85 | 86 | .code .keyword, 87 | .code .meta, 88 | .code .storage, 89 | .code .storage.type, 90 | .code .support.type { 91 | color: #8959A8 92 | } 93 | 94 | .code .keyword.operator { 95 | color: #3E999F 96 | } 97 | 98 | .code .constant.character, 99 | .code .constant.language, 100 | .code .constant.numeric, 101 | .code .keyword.other.unit, 102 | .code .support.constant { 103 | color: #F5871F 104 | } 105 | 106 | .code .constant.other { 107 | color: #666969 108 | } 109 | 110 | .code .invalid { 111 | color: #FFFFFF; 112 | background-color: #C82829 113 | } 114 | 115 | .code .invalid.deprecated { 116 | color: #FFFFFF; 117 | background-color: #8959A8 118 | } 119 | 120 | .code .fold { 121 | background-color: #4271AE; 122 | border-color: #4D4D4C 123 | } 124 | 125 | .code .entity.name.function, 126 | .code .support.function, 127 | .code .variable.parameter, 128 | .code .variable { 129 | color: #4271AE 130 | } 131 | 132 | .code .support.class, 133 | .code .support.type { 134 | color: #C99E00 135 | } 136 | 137 | .code .heading, 138 | .code .markup.heading, 139 | .code .string { 140 | color: #718C00 141 | } 142 | 143 | .code .entity.name.tag, 144 | .code .entity.other.attribute-name, 145 | .code .meta.tag, 146 | .code .string.regexp, 147 | .code .variable { 148 | color: #C82829 149 | } 150 | 151 | .code .comment { 152 | color: #8E908C 153 | } 154 | 155 | .code .indent-guide { 156 | background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bdu3f/BwAlfgctduB85QAAAABJRU5ErkJggg==) right repeat-y 157 | } -------------------------------------------------------------------------------- /plugins/document.schema/assets/line-link.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | ready(function () { 3 | var tables = window.document.getElementsByClassName('code'); 4 | 5 | for (var i = 0; i < tables.length; i++) { 6 | var table = tables[i]; 7 | 8 | table.addEventListener('click', onClick); 9 | } 10 | 11 | window.addEventListener("hashchange", onHashChange, false); 12 | 13 | onHashChange(); 14 | }); 15 | 16 | function onHashChange() { 17 | var id = window.location.href.split('#')[1]; 18 | 19 | if (!id) { 20 | return; 21 | } 22 | 23 | var lcid = id.indexOf('C') === -1 ? id.replace('L', 'LC') : id; 24 | var lineCode = window.document.getElementById(lcid); 25 | 26 | if (!lineCode) { 27 | return; 28 | } 29 | 30 | var highlighted = lineCode.closest('.code').getElementsByClassName('highlighted'); 31 | 32 | for (var i = 0; i < highlighted.length; i++) { 33 | highlighted[i].classList.remove('highlighted'); 34 | } 35 | 36 | lineCode.classList.add('highlighted'); 37 | } 38 | 39 | function onClick(e) { 40 | var target = e.target; 41 | 42 | if (!target.classList.contains('td-index')) { 43 | return; 44 | } 45 | 46 | var href = window.location.href.split('#')[0]; 47 | 48 | window.location.href = href + '#' + target.id; 49 | } 50 | 51 | function ready(fn) { 52 | if (document.attachEvent ? document.readyState === "complete" : document.readyState !== "loading"){ 53 | fn(); 54 | } else { 55 | document.addEventListener('DOMContentLoaded', fn); 56 | } 57 | } 58 | })(); 59 | 60 | // https://developer.mozilla.org/ru/docs/Web/API/Element/closest 61 | (function(e){ 62 | e.closest = e.closest || function(css){ 63 | var node = this; 64 | 65 | while (node) { 66 | if (node.matches(css)) return node; 67 | else node = node.parentElement; 68 | } 69 | return null; 70 | } 71 | })(Element.prototype); -------------------------------------------------------------------------------- /plugins/document.schema/index.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from "path"; 2 | import wrap from "word-wrap"; 3 | import { 4 | Directive, 5 | DocumentSectionInterface, 6 | EnumValue, 7 | Field, 8 | InputValue, 9 | PluginInterface, 10 | Schema, 11 | SchemaType 12 | } from "../../lib/interface"; 13 | import { 14 | DocumentSection, 15 | ENUM, 16 | HTML, 17 | INPUT_OBJECT, 18 | INTERFACE, 19 | OBJECT, 20 | Plugin, 21 | SCALAR, 22 | UNION 23 | } from "../../lib/utility"; 24 | 25 | const MAX_CODE_LEN = 80; 26 | // const MAX_COMMENT_LEN = 80; 27 | 28 | export default class SchemaPlugin extends Plugin implements PluginInterface { 29 | private html: HTML; 30 | 31 | getHeaders(): string[] { 32 | return [ 33 | '', 34 | '', 35 | '' 36 | ]; 37 | } 38 | 39 | getAssets() { 40 | return [ 41 | resolve(__dirname, "assets/code.css"), 42 | resolve(__dirname, "assets/line-link.js") 43 | ]; 44 | } 45 | 46 | getDocuments(buildForType?: string): DocumentSectionInterface[] { 47 | this.html = new HTML(); 48 | const code = this.code(buildForType); 49 | 50 | if (code) { 51 | return [ 52 | new DocumentSection("GraphQL Schema definition", this.html.code(code)) 53 | ]; 54 | } 55 | 56 | return []; 57 | } 58 | 59 | code(buildForType?: string): string { 60 | if (!buildForType) { 61 | return this.schema(this.document); 62 | } 63 | 64 | const directive = this.document.directives.find( 65 | eachDirective => eachDirective.name === (buildForType as string) 66 | ); 67 | 68 | if (directive) { 69 | return this.directive(directive); 70 | } 71 | 72 | const type = this.document.types.find( 73 | eachType => eachType.name === (buildForType as string) 74 | ); 75 | 76 | if (type) { 77 | switch (type.kind) { 78 | case SCALAR: 79 | return this.scalar(type); 80 | 81 | case OBJECT: 82 | return this.object(type); 83 | 84 | case INTERFACE: 85 | return this.interfaces(type); 86 | 87 | case UNION: 88 | return this.union(type); 89 | 90 | case ENUM: 91 | return this.enum(type); 92 | 93 | case INPUT_OBJECT: 94 | return this.inputObject(type); 95 | } 96 | } 97 | 98 | throw new TypeError("Unexpected type: " + buildForType); 99 | } 100 | 101 | argument(arg: InputValue): string { 102 | return ( 103 | this.html.property(arg.name) + 104 | ": " + 105 | this.html.useIdentifier(arg.type, this.url(arg.type)) // + ' ' + this.deprecated(arg); 106 | ); 107 | } 108 | 109 | argumentLength(arg: InputValue): number { 110 | return arg.name.length + 1 + this.html.useIdentifierLength(arg.type); 111 | } 112 | 113 | arguments(fieldOrDirectives: Field | Directive): string { 114 | if (fieldOrDirectives.args.length === 0) { 115 | return ""; 116 | } 117 | 118 | return ( 119 | "(" + 120 | fieldOrDirectives.args.map(arg => this.argument(arg)).join(", ") + 121 | ")" 122 | ); 123 | } 124 | 125 | argumentsLength(fieldOrDirectives: Field | Directive): number { 126 | if (fieldOrDirectives.args.length === 0) { 127 | return 0; 128 | } 129 | 130 | return fieldOrDirectives.args.reduce( 131 | (sum, arg) => sum + this.argumentLength(arg), 132 | 2 133 | ); 134 | } 135 | 136 | argumentsMultiline(fieldOrDirectives: Field | Directive): string[] { 137 | if (fieldOrDirectives.args.length === 0) { 138 | return []; 139 | } 140 | 141 | const maxIndex = fieldOrDirectives.args.length - 1; 142 | return fieldOrDirectives.args.map((arg, index) => { 143 | return index < maxIndex ? this.argument(arg) + "," : this.argument(arg); 144 | }); 145 | } 146 | 147 | argumentDescription(arg: InputValue): string[] { 148 | const desc = 149 | arg.description === null 150 | ? "[" + this.html.highlight("Not documented") + "]" 151 | : arg.description; 152 | 153 | return this.description(this.html.highlight(arg.name) + ": " + desc); 154 | } 155 | 156 | argumentsDescription(fieldOrDirectives: Field | Directive): string[] { 157 | if (fieldOrDirectives.args.length === 0) { 158 | return []; 159 | } 160 | 161 | const reduceArguments = (descriptions: string[], arg: InputValue) => 162 | descriptions.concat(this.argumentDescription(arg)); 163 | 164 | return fieldOrDirectives.args.reduce(reduceArguments, [ 165 | this.html.comment("Arguments") 166 | ]); 167 | } 168 | 169 | deprecated(fieldOrEnumVal: Field | EnumValue): string { 170 | if (!fieldOrEnumVal.isDeprecated) { 171 | return ""; 172 | } 173 | 174 | if (!fieldOrEnumVal.deprecationReason) { 175 | return this.html.keyword("@deprecated"); 176 | } 177 | 178 | return ( 179 | this.html.keyword("@deprecated") + 180 | "( reason: " + 181 | this.html.value('"' + fieldOrEnumVal.deprecationReason + '" ') + 182 | " )" 183 | ); 184 | } 185 | 186 | deprecatedLength(fieldOrEnumVal: Field | EnumValue): number { 187 | if (!fieldOrEnumVal.isDeprecated) { 188 | return 0; 189 | } 190 | 191 | if (!fieldOrEnumVal.deprecationReason) { 192 | return "@deprecated".length; 193 | } 194 | 195 | return ( 196 | '@deprecated( reason: "'.length + 197 | fieldOrEnumVal.deprecationReason.length + 198 | '" )'.length 199 | ); 200 | } 201 | 202 | description(description: string | null): string[] { 203 | if (description) { 204 | return wrap(description, { 205 | width: MAX_CODE_LEN 206 | }) 207 | .split("\n") 208 | .map(l => this.html.comment(l)); 209 | } 210 | 211 | return []; 212 | } 213 | 214 | directive(directive: Directive): string { 215 | return this.html.line( 216 | this.html.keyword("directive") + 217 | " " + 218 | this.html.keyword("@" + directive.name) + 219 | this.arguments(directive) + 220 | " on " + 221 | directive.locations 222 | .map(location => this.html.keyword(location)) 223 | .join(" | ") 224 | ); 225 | } 226 | 227 | enum(type: SchemaType): string { 228 | const reduceEnumValues = (lines: string[], enumValue: EnumValue) => 229 | lines.concat([""], this.description(enumValue.description), [ 230 | this.html.property(enumValue.name) + this.deprecated(enumValue) 231 | ]); 232 | 233 | return ( 234 | this.html.line( 235 | this.html.keyword("enum") + " " + this.html.identifier(type) + " {" 236 | ) + 237 | (type.enumValues || []) 238 | .reduce(reduceEnumValues, []) 239 | .map(line => this.html.line(this.html.tab(line))) 240 | .join("") + 241 | this.html.line("}") 242 | ); 243 | } 244 | 245 | field(field: Field): string { 246 | const fieldDescription = this.description(field.description); 247 | const argumentsDescription = this.argumentsDescription(field); 248 | 249 | if (fieldDescription.length > 0 && argumentsDescription.length) { 250 | fieldDescription.push(this.html.comment("")); 251 | } 252 | 253 | const fieldDefinition = 254 | field.args.length > 0 && this.fieldLength(field) > MAX_CODE_LEN 255 | ? // Multiline definition: 256 | // fieldName( 257 | // argumentName: ArgumentType, \n ... 258 | // ): ReturnType [@deprecated...] 259 | [ 260 | this.html.property(field.name) + "(", 261 | ...this.argumentsMultiline(field).map(l => this.html.tab(l)), 262 | "): " + 263 | this.html.useIdentifier(field.type, this.url(field.type)) + 264 | " " + 265 | this.deprecated(field) 266 | ] 267 | : // Single line 268 | // fieldName(argumentName: ArgumentType): ReturnType [@deprecated...] 269 | [ 270 | this.html.property(field.name) + 271 | this.arguments(field) + 272 | ": " + 273 | this.html.useIdentifier(field.type, this.url(field.type)) + 274 | " " + 275 | this.deprecated(field) 276 | ]; 277 | 278 | return ([] as string[]) 279 | .concat(fieldDescription) 280 | .concat(argumentsDescription) 281 | .concat(fieldDefinition) 282 | .map(line => this.html.line(this.html.tab(line))) 283 | .join(""); 284 | } 285 | 286 | fieldLength(field: Field): number { 287 | return ( 288 | field.name.length + 289 | this.argumentsLength(field) + 290 | ": ".length + 291 | this.html.useIdentifierLength(field) + 292 | " ".length + 293 | this.deprecatedLength(field) 294 | ); 295 | } 296 | 297 | fields(type: SchemaType): string { 298 | let fields = ""; 299 | fields += this.html.line(); 300 | fields += (type.fields || []) 301 | .map(field => this.field(field)) 302 | .join(this.html.line()); 303 | 304 | if (type.fields && type.fields.length > 0) { 305 | fields += this.html.line(); 306 | } 307 | 308 | return fields; 309 | } 310 | 311 | inputObject(type: SchemaType): string { 312 | return ( 313 | this.html.line( 314 | this.html.keyword("input") + " " + this.html.identifier(type) + " {" 315 | ) + 316 | this.inputValues(type.inputFields || []) + 317 | this.html.line("}") 318 | ); 319 | } 320 | 321 | inputValues(inputValues: InputValue[]): string { 322 | return inputValues 323 | .map(inputValue => 324 | this.html.line(this.html.tab(this.inputValue(inputValue))) 325 | ) 326 | .join(""); 327 | } 328 | 329 | inputValue(arg: InputValue): string { 330 | const argDescription = this.description(arg.description); 331 | 332 | return ([] as string[]) 333 | .concat(argDescription) 334 | .concat([ 335 | this.html.property(arg.name) + 336 | ": " + 337 | this.html.useIdentifier(arg.type, this.url(arg.type)) // + ' ' + this.deprecated(arg) 338 | ]) 339 | .map(line => this.html.line(this.html.tab(line))) 340 | .join(""); 341 | } 342 | 343 | interfaces(type: SchemaType): string { 344 | return ( 345 | this.html.line( 346 | this.html.keyword("interface") + " " + this.html.identifier(type) + " {" 347 | ) + 348 | this.fields(type) + 349 | this.html.line("}") 350 | ); 351 | } 352 | 353 | object(type: SchemaType): string { 354 | const interfaces = (type.interfaces || []) 355 | .map(i => this.html.useIdentifier(i, this.url(i))) 356 | .join(", "); 357 | 358 | const implement = 359 | interfaces.length === 0 360 | ? "" 361 | : " " + this.html.keyword("implements") + " " + interfaces; 362 | 363 | return ( 364 | this.html.line( 365 | this.html.keyword("type") + 366 | " " + 367 | this.html.identifier(type) + 368 | implement + 369 | " {" 370 | ) + 371 | this.fields(type) + 372 | this.html.line("}") 373 | ); 374 | } 375 | 376 | scalar(type: SchemaType): string { 377 | return this.html.line( 378 | this.html.keyword("scalar") + " " + this.html.identifier(type) 379 | ); 380 | } 381 | 382 | schema(schema: Schema): string { 383 | let definition = this.html.line(this.html.keyword("schema") + " {"); 384 | 385 | if (schema.queryType) { 386 | definition += 387 | this.html.line() + 388 | this.description(schema.queryType.description) 389 | .map(line => this.html.line(this.html.tab(line))) 390 | .join("") + 391 | this.html.line( 392 | this.html.tab( 393 | this.html.property("query") + 394 | ": " + 395 | this.html.useIdentifier( 396 | schema.queryType, 397 | this.url(schema.queryType) 398 | ) 399 | ) 400 | ); 401 | } 402 | 403 | if (schema.mutationType) { 404 | definition += 405 | this.html.line() + 406 | this.description(schema.mutationType.description) 407 | .map(line => this.html.line(this.html.tab(line))) 408 | .join("") + 409 | this.html.line( 410 | this.html.tab( 411 | this.html.property("mutation") + 412 | ": " + 413 | this.html.useIdentifier( 414 | schema.mutationType, 415 | this.url(schema.mutationType) 416 | ) 417 | ) 418 | ); 419 | } 420 | 421 | if (schema.subscriptionType) { 422 | definition += 423 | this.html.line() + 424 | this.description(schema.subscriptionType.description) 425 | .map(line => this.html.line(this.html.tab(line))) 426 | .join("") + 427 | this.html.line( 428 | this.html.tab( 429 | this.html.property("subscription") + 430 | ": " + 431 | this.html.useIdentifier( 432 | schema.subscriptionType, 433 | this.url(schema.subscriptionType) 434 | ) 435 | ) 436 | ); 437 | } 438 | 439 | definition += this.html.line("}"); 440 | 441 | return definition; 442 | /* .concat( 443 | schema.directives 444 | .map((directive) => { 445 | return this.html.line(this.html.comment('DIRECTIVE')) + 446 | this.description(directive.description) 447 | .map(line => this.html.line(line)) 448 | .join('') + 449 | this.code(directive.name); 450 | }), 451 | schema.types 452 | .sort((a: SchemaType, b: SchemaType) => { 453 | return order[a.kind].localeCompare(order[b.kind]); 454 | }) 455 | .map((type) => { 456 | return this.html.line(this.html.comment(type.kind)) + 457 | this.description(type.description) 458 | .map(line => this.html.line(line)) 459 | .join('') + 460 | this.code(type.name); 461 | }))*/ 462 | 463 | /* return [this.schemaDefinition(schema)] 464 | .concat( 465 | directives.map(directive => this.directive(directive)), 466 | types.map((type) => this.type(type) as string) 467 | ) 468 | .join('\n\n') + '\n';*/ 469 | } 470 | 471 | union(type: SchemaType): string { 472 | return this.html.line( 473 | this.html.keyword("union") + 474 | " " + 475 | this.html.identifier(type) + 476 | " = " + 477 | (type.possibleTypes || []) 478 | .map(eachType => 479 | this.html.useIdentifier(eachType, this.url(eachType)) 480 | ) 481 | .join(" | ") 482 | ); 483 | } 484 | } 485 | -------------------------------------------------------------------------------- /plugins/navigation.directive.test.ts: -------------------------------------------------------------------------------- 1 | import NavigationDirectives from "./navigation.directive"; 2 | import projectPackage from "../test/empty.package.json"; 3 | import schema from "../test/empty.schema.json"; 4 | 5 | describe("pĺugins/navigation.directive#NavigationDirectives", () => { 6 | 7 | test("plugin return empty", () => { 8 | const plugin = new NavigationDirectives( 9 | { 10 | ...schema.data.__schema, 11 | directives: [] 12 | }, 13 | projectPackage, 14 | {} 15 | ); 16 | 17 | expect(plugin.getNavigations("Query")).toEqual([]) 18 | }) 19 | 20 | test("plugin return navigation", () => { 21 | 22 | const plugin = new NavigationDirectives( 23 | schema.data.__schema, 24 | projectPackage, 25 | {} 26 | ); 27 | 28 | expect(plugin.getNavigations("Query")).toEqual([ 29 | { 30 | title: "Directives", 31 | items: [ 32 | { text: "deprecated", href: "/deprecated.doc.html", isActive: false }, 33 | { text: "include", href: "/include.doc.html", isActive: false }, 34 | { text: "skip", href: "/skip.doc.html", isActive: false } 35 | ] 36 | } 37 | ]); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /plugins/navigation.directive.ts: -------------------------------------------------------------------------------- 1 | import { NavigationItemInterface, PluginInterface } from "../lib/interface"; 2 | import { NavigationItem, NavigationSection, Plugin } from "../lib/utility"; 3 | 4 | export default class NavigationDirectives extends Plugin 5 | implements PluginInterface { 6 | getTypes(buildForType: string): NavigationItemInterface[] { 7 | return this.document.directives.map( 8 | directive => 9 | new NavigationItem( 10 | directive.name, 11 | this.url(directive), 12 | directive.name === buildForType 13 | ) 14 | ); 15 | } 16 | 17 | getNavigations(buildForType: string) { 18 | const types: NavigationItemInterface[] = this.getTypes(buildForType); 19 | 20 | if (types.length === 0) { 21 | return []; 22 | } 23 | 24 | return [new NavigationSection("Directives", types)]; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /plugins/navigation.enum.test.ts: -------------------------------------------------------------------------------- 1 | import projectPackage from "../test/empty.package.json"; 2 | import NavigationEnums from "./navigation.enum"; 3 | import schema from "../test/empty.schema.json"; 4 | 5 | describe("pĺugins/navigation.directive#NavigationDirectives", () => { 6 | test("plugin return empty", () => { 7 | const plugin = new NavigationEnums( 8 | { 9 | ...schema.data.__schema, 10 | types: [], 11 | }, 12 | projectPackage, 13 | {} 14 | ); 15 | 16 | expect(plugin.getNavigations("Query")).toEqual([]) 17 | }) 18 | 19 | test("plugin return navigation", () => { 20 | const plugin = new NavigationEnums(schema.data.__schema, projectPackage, {}); 21 | expect(plugin.getNavigations("Query")).toEqual([ 22 | { 23 | title: "Enums", 24 | items: [ 25 | { 26 | text: "__DirectiveLocation", 27 | href: "/directivelocation.spec.html", 28 | isActive: false 29 | }, 30 | { text: "__TypeKind", href: "/typekind.spec.html", isActive: false } 31 | ] 32 | } 33 | ]); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /plugins/navigation.enum.ts: -------------------------------------------------------------------------------- 1 | import { NavigationItemInterface, PluginInterface } from "../lib/interface"; 2 | import { 3 | ENUM, 4 | NavigationItem, 5 | NavigationSection, 6 | Plugin 7 | } from "../lib/utility"; 8 | 9 | export default class NavigationEnums extends Plugin implements PluginInterface { 10 | getTypes(buildForType: string): NavigationItemInterface[] { 11 | return this.document.types 12 | .filter(type => type.kind === ENUM) 13 | .map( 14 | type => 15 | new NavigationItem( 16 | type.name, 17 | this.url(type), 18 | type.name === buildForType 19 | ) 20 | ); 21 | } 22 | 23 | getNavigations(buildForType: string) { 24 | const types: NavigationItemInterface[] = this.getTypes(buildForType); 25 | 26 | if (types.length === 0) { 27 | return []; 28 | } 29 | 30 | return [new NavigationSection("Enums", types)]; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /plugins/navigation.input.test.ts: -------------------------------------------------------------------------------- 1 | import projectPackage from "../test/empty.package.json"; 2 | import NavigationInputs from "./navigation.input"; 3 | import schema from "../test/empty.schema.json"; 4 | 5 | describe("pĺugins/navigation.directive#NavigationDirectives", () => { 6 | test("plugin return empty", () => { 7 | const plugin = new NavigationInputs( 8 | { 9 | ...schema.data.__schema, 10 | types: [], 11 | }, 12 | projectPackage, 13 | {} 14 | ); 15 | 16 | expect(plugin.getNavigations("Query")).toEqual([]) 17 | }) 18 | 19 | test("plugin return navigation", () => { 20 | const plugin = new NavigationInputs(schema.data.__schema, projectPackage, {}); 21 | expect(plugin.getNavigations("Query")).toEqual([ 22 | { 23 | title: "Input Objects", 24 | items: [ 25 | { 26 | text: "AddCommentInput", 27 | href: "/addcommentinput.doc.html", 28 | isActive: false 29 | } 30 | ] 31 | } 32 | ]); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /plugins/navigation.input.ts: -------------------------------------------------------------------------------- 1 | import { NavigationItemInterface, PluginInterface } from "../lib/interface"; 2 | import { 3 | INPUT_OBJECT, 4 | NavigationItem, 5 | NavigationSection, 6 | Plugin 7 | } from "../lib/utility"; 8 | 9 | export default class NavigationInputs extends Plugin 10 | implements PluginInterface { 11 | getTypes(buildForType: string): NavigationItemInterface[] { 12 | return this.document.types 13 | .filter(type => type.kind === INPUT_OBJECT) 14 | .map( 15 | type => 16 | new NavigationItem( 17 | type.name, 18 | this.url(type), 19 | type.name === buildForType 20 | ) 21 | ); 22 | } 23 | 24 | getNavigations(buildForType: string) { 25 | const types: NavigationItemInterface[] = this.getTypes(buildForType); 26 | 27 | if (types.length === 0) { 28 | return []; 29 | } 30 | 31 | return [new NavigationSection("Input Objects", types)]; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /plugins/navigation.interface.test.ts: -------------------------------------------------------------------------------- 1 | import projectPackage from "../test/empty.package.json"; 2 | import NavigationInterfaces from "./navigation.interface"; 3 | import schema from "../test/github.json"; 4 | 5 | describe("pĺugins/navigation.interface#NavigationInterfaces", () => { 6 | test("plugin return empty", () => { 7 | const plugin = new NavigationInterfaces( 8 | { 9 | ...schema.data.__schema, 10 | types: [], 11 | }, 12 | projectPackage, 13 | {} 14 | ); 15 | 16 | expect(plugin.getNavigations("Query")).toEqual([]) 17 | }); 18 | 19 | test("plugin return navigation", () => { 20 | const plugin = new NavigationInterfaces( 21 | schema.data.__schema, 22 | projectPackage, 23 | {} 24 | ); 25 | 26 | expect(plugin.getNavigations("Query")).toEqual([ 27 | { 28 | title: "Interfaces", 29 | items: [ 30 | { 31 | "href": "/author.doc.html", 32 | "isActive": false, 33 | "text": "Author", 34 | }, 35 | { 36 | "href": "/comment.doc.html", 37 | "isActive": false, 38 | "text": "Comment", 39 | }, 40 | { 41 | "href": "/gitobject.doc.html", 42 | "isActive": false, 43 | "text": "GitObject", 44 | }, 45 | { 46 | "href": "/gitsignature.doc.html", 47 | "isActive": false, 48 | "text": "GitSignature", 49 | }, 50 | { 51 | "href": "/issueevent.doc.html", 52 | "isActive": false, 53 | "text": "IssueEvent", 54 | }, 55 | { 56 | "href": "/issueish.doc.html", 57 | "isActive": false, 58 | "text": "Issueish", 59 | }, 60 | { 61 | "href": "/node.doc.html", 62 | "isActive": false, 63 | "text": "Node", 64 | }, 65 | { 66 | "href": "/projectowner.doc.html", 67 | "isActive": false, 68 | "text": "ProjectOwner", 69 | }, 70 | { 71 | "href": "/reactable.doc.html", 72 | "isActive": false, 73 | "text": "Reactable", 74 | }, 75 | { 76 | "href": "/repositoryinfo.doc.html", 77 | "isActive": false, 78 | "text": "RepositoryInfo", 79 | }, 80 | { 81 | "href": "/repositorynode.doc.html", 82 | "isActive": false, 83 | "text": "RepositoryNode", 84 | }, 85 | { 86 | "href": "/repositoryowner.doc.html", 87 | "isActive": false, 88 | "text": "RepositoryOwner", 89 | }, 90 | { 91 | "href": "/subscribable.doc.html", 92 | "isActive": false, 93 | "text": "Subscribable", 94 | }, 95 | { 96 | "href": "/timeline.doc.html", 97 | "isActive": false, 98 | "text": "Timeline", 99 | }, 100 | { 101 | "href": "/uniformresourcelocatable.doc.html", 102 | "isActive": false, 103 | "text": "UniformResourceLocatable", 104 | }, 105 | ] 106 | } 107 | ]); 108 | }); 109 | }); 110 | -------------------------------------------------------------------------------- /plugins/navigation.interface.ts: -------------------------------------------------------------------------------- 1 | import { NavigationItemInterface, PluginInterface } from "../lib/interface"; 2 | import { 3 | INTERFACE, 4 | NavigationItem, 5 | NavigationSection, 6 | Plugin 7 | } from "../lib/utility"; 8 | 9 | export default class NavigationInterfaces extends Plugin 10 | implements PluginInterface { 11 | getTypes(buildForType: string): NavigationItemInterface[] { 12 | return this.document.types 13 | .filter(type => type.kind === INTERFACE) 14 | .map( 15 | type => 16 | new NavigationItem( 17 | type.name, 18 | this.url(type), 19 | type.name === buildForType 20 | ) 21 | ); 22 | } 23 | 24 | getNavigations(buildForType: string) { 25 | const types: NavigationItemInterface[] = this.getTypes(buildForType); 26 | 27 | if (types.length === 0) { 28 | return []; 29 | } 30 | 31 | return [new NavigationSection("Interfaces", types)]; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /plugins/navigation.object.test.ts: -------------------------------------------------------------------------------- 1 | import projectPackage from "../test/empty.package.json"; 2 | import NavigationObject from "./navigation.object"; 3 | import schema from "../test/github.json"; 4 | 5 | describe("pĺugins/navigation.directive#NavigationDirectives", () => { 6 | test("plugin return empty", () => { 7 | const plugin = new NavigationObject( 8 | { 9 | ...schema.data.__schema, 10 | types: [], 11 | }, 12 | projectPackage, 13 | {} 14 | ); 15 | 16 | expect(plugin.getNavigations("Query")).toEqual([]) 17 | }) 18 | 19 | test("plugin return navigation", () => { 20 | const plugin = new NavigationObject(schema.data.__schema, projectPackage, {}); 21 | expect(plugin.getNavigations("Query")).toEqual([ 22 | { 23 | title: "Objects", 24 | items: [ 25 | { 26 | "href": "/addcommentpayload.doc.html", 27 | "isActive": false, 28 | "text": "AddCommentPayload", 29 | }, 30 | { 31 | "href": "/addprojectcardpayload.doc.html", 32 | "isActive": false, 33 | "text": "AddProjectCardPayload", 34 | }, 35 | { 36 | "href": "/addprojectcolumnpayload.doc.html", 37 | "isActive": false, 38 | "text": "AddProjectColumnPayload", 39 | }, 40 | { 41 | "href": "/addpullrequestreviewcommentpayload.doc.html", 42 | "isActive": false, 43 | "text": "AddPullRequestReviewCommentPayload", 44 | }, 45 | { 46 | "href": "/addpullrequestreviewpayload.doc.html", 47 | "isActive": false, 48 | "text": "AddPullRequestReviewPayload", 49 | }, 50 | { 51 | "href": "/addreactionpayload.doc.html", 52 | "isActive": false, 53 | "text": "AddReactionPayload", 54 | }, 55 | { 56 | "href": "/assignedevent.doc.html", 57 | "isActive": false, 58 | "text": "AssignedEvent", 59 | }, 60 | { 61 | "href": "/baserefforcepushedevent.doc.html", 62 | "isActive": false, 63 | "text": "BaseRefForcePushedEvent", 64 | }, 65 | { 66 | "href": "/blame.doc.html", 67 | "isActive": false, 68 | "text": "Blame", 69 | }, 70 | { 71 | "href": "/blamerange.doc.html", 72 | "isActive": false, 73 | "text": "BlameRange", 74 | }, 75 | { 76 | "href": "/blob.doc.html", 77 | "isActive": false, 78 | "text": "Blob", 79 | }, 80 | { 81 | "href": "/bot.doc.html", 82 | "isActive": false, 83 | "text": "Bot", 84 | }, 85 | { 86 | "href": "/closedevent.doc.html", 87 | "isActive": false, 88 | "text": "ClosedEvent", 89 | }, 90 | { 91 | "href": "/commit.doc.html", 92 | "isActive": false, 93 | "text": "Commit", 94 | }, 95 | { 96 | "href": "/commitcomment.doc.html", 97 | "isActive": false, 98 | "text": "CommitComment", 99 | }, 100 | { 101 | "href": "/commitcommentconnection.doc.html", 102 | "isActive": false, 103 | "text": "CommitCommentConnection", 104 | }, 105 | { 106 | "href": "/commitcommentedge.doc.html", 107 | "isActive": false, 108 | "text": "CommitCommentEdge", 109 | }, 110 | { 111 | "href": "/commitconnection.doc.html", 112 | "isActive": false, 113 | "text": "CommitConnection", 114 | }, 115 | { 116 | "href": "/commitedge.doc.html", 117 | "isActive": false, 118 | "text": "CommitEdge", 119 | }, 120 | { 121 | "href": "/commithistoryconnection.doc.html", 122 | "isActive": false, 123 | "text": "CommitHistoryConnection", 124 | }, 125 | { 126 | "href": "/createprojectpayload.doc.html", 127 | "isActive": false, 128 | "text": "CreateProjectPayload", 129 | }, 130 | { 131 | "href": "/deleteprojectcardpayload.doc.html", 132 | "isActive": false, 133 | "text": "DeleteProjectCardPayload", 134 | }, 135 | { 136 | "href": "/deleteprojectcolumnpayload.doc.html", 137 | "isActive": false, 138 | "text": "DeleteProjectColumnPayload", 139 | }, 140 | { 141 | "href": "/deleteprojectpayload.doc.html", 142 | "isActive": false, 143 | "text": "DeleteProjectPayload", 144 | }, 145 | { 146 | "href": "/deletepullrequestreviewpayload.doc.html", 147 | "isActive": false, 148 | "text": "DeletePullRequestReviewPayload", 149 | }, 150 | { 151 | "href": "/demilestonedevent.doc.html", 152 | "isActive": false, 153 | "text": "DemilestonedEvent", 154 | }, 155 | { 156 | "href": "/deployedevent.doc.html", 157 | "isActive": false, 158 | "text": "DeployedEvent", 159 | }, 160 | { 161 | "href": "/deployment.doc.html", 162 | "isActive": false, 163 | "text": "Deployment", 164 | }, 165 | { 166 | "href": "/deploymentstatus.doc.html", 167 | "isActive": false, 168 | "text": "DeploymentStatus", 169 | }, 170 | { 171 | "href": "/deploymentstatusconnection.doc.html", 172 | "isActive": false, 173 | "text": "DeploymentStatusConnection", 174 | }, 175 | { 176 | "href": "/deploymentstatusedge.doc.html", 177 | "isActive": false, 178 | "text": "DeploymentStatusEdge", 179 | }, 180 | { 181 | "href": "/dismisspullrequestreviewpayload.doc.html", 182 | "isActive": false, 183 | "text": "DismissPullRequestReviewPayload", 184 | }, 185 | { 186 | "href": "/followerconnection.doc.html", 187 | "isActive": false, 188 | "text": "FollowerConnection", 189 | }, 190 | { 191 | "href": "/followingconnection.doc.html", 192 | "isActive": false, 193 | "text": "FollowingConnection", 194 | }, 195 | { 196 | "href": "/gist.doc.html", 197 | "isActive": false, 198 | "text": "Gist", 199 | }, 200 | { 201 | "href": "/gistcomment.doc.html", 202 | "isActive": false, 203 | "text": "GistComment", 204 | }, 205 | { 206 | "href": "/gistconnection.doc.html", 207 | "isActive": false, 208 | "text": "GistConnection", 209 | }, 210 | { 211 | "href": "/gistedge.doc.html", 212 | "isActive": false, 213 | "text": "GistEdge", 214 | }, 215 | { 216 | "href": "/gitactor.doc.html", 217 | "isActive": false, 218 | "text": "GitActor", 219 | }, 220 | { 221 | "href": "/gpgsignature.doc.html", 222 | "isActive": false, 223 | "text": "GpgSignature", 224 | }, 225 | { 226 | "href": "/headrefdeletedevent.doc.html", 227 | "isActive": false, 228 | "text": "HeadRefDeletedEvent", 229 | }, 230 | { 231 | "href": "/headrefforcepushedevent.doc.html", 232 | "isActive": false, 233 | "text": "HeadRefForcePushedEvent", 234 | }, 235 | { 236 | "href": "/headrefrestoredevent.doc.html", 237 | "isActive": false, 238 | "text": "HeadRefRestoredEvent", 239 | }, 240 | { 241 | "href": "/issue.doc.html", 242 | "isActive": false, 243 | "text": "Issue", 244 | }, 245 | { 246 | "href": "/issuecomment.doc.html", 247 | "isActive": false, 248 | "text": "IssueComment", 249 | }, 250 | { 251 | "href": "/issuecommentconnection.doc.html", 252 | "isActive": false, 253 | "text": "IssueCommentConnection", 254 | }, 255 | { 256 | "href": "/issuecommentedge.doc.html", 257 | "isActive": false, 258 | "text": "IssueCommentEdge", 259 | }, 260 | { 261 | "href": "/issueconnection.doc.html", 262 | "isActive": false, 263 | "text": "IssueConnection", 264 | }, 265 | { 266 | "href": "/issueedge.doc.html", 267 | "isActive": false, 268 | "text": "IssueEdge", 269 | }, 270 | { 271 | "href": "/issuetimelineconnection.doc.html", 272 | "isActive": false, 273 | "text": "IssueTimelineConnection", 274 | }, 275 | { 276 | "href": "/issuetimelineitemedge.doc.html", 277 | "isActive": false, 278 | "text": "IssueTimelineItemEdge", 279 | }, 280 | { 281 | "href": "/label.doc.html", 282 | "isActive": false, 283 | "text": "Label", 284 | }, 285 | { 286 | "href": "/labelconnection.doc.html", 287 | "isActive": false, 288 | "text": "LabelConnection", 289 | }, 290 | { 291 | "href": "/labeledevent.doc.html", 292 | "isActive": false, 293 | "text": "LabeledEvent", 294 | }, 295 | { 296 | "href": "/labeledge.doc.html", 297 | "isActive": false, 298 | "text": "LabelEdge", 299 | }, 300 | { 301 | "href": "/language.doc.html", 302 | "isActive": false, 303 | "text": "Language", 304 | }, 305 | { 306 | "href": "/languageconnection.doc.html", 307 | "isActive": false, 308 | "text": "LanguageConnection", 309 | }, 310 | { 311 | "href": "/languageedge.doc.html", 312 | "isActive": false, 313 | "text": "LanguageEdge", 314 | }, 315 | { 316 | "href": "/lockedevent.doc.html", 317 | "isActive": false, 318 | "text": "LockedEvent", 319 | }, 320 | { 321 | "href": "/mentionedevent.doc.html", 322 | "isActive": false, 323 | "text": "MentionedEvent", 324 | }, 325 | { 326 | "href": "/mergedevent.doc.html", 327 | "isActive": false, 328 | "text": "MergedEvent", 329 | }, 330 | { 331 | "href": "/milestone.doc.html", 332 | "isActive": false, 333 | "text": "Milestone", 334 | }, 335 | { 336 | "href": "/milestoneconnection.doc.html", 337 | "isActive": false, 338 | "text": "MilestoneConnection", 339 | }, 340 | { 341 | "href": "/milestonedevent.doc.html", 342 | "isActive": false, 343 | "text": "MilestonedEvent", 344 | }, 345 | { 346 | "href": "/milestoneedge.doc.html", 347 | "isActive": false, 348 | "text": "MilestoneEdge", 349 | }, 350 | { 351 | "href": "/moveprojectcardpayload.doc.html", 352 | "isActive": false, 353 | "text": "MoveProjectCardPayload", 354 | }, 355 | { 356 | "href": "/moveprojectcolumnpayload.doc.html", 357 | "isActive": false, 358 | "text": "MoveProjectColumnPayload", 359 | }, 360 | { 361 | "href": "/organization.doc.html", 362 | "isActive": false, 363 | "text": "Organization", 364 | }, 365 | { 366 | "href": "/organizationconnection.doc.html", 367 | "isActive": false, 368 | "text": "OrganizationConnection", 369 | }, 370 | { 371 | "href": "/organizationedge.doc.html", 372 | "isActive": false, 373 | "text": "OrganizationEdge", 374 | }, 375 | { 376 | "href": "/organizationinvitation.doc.html", 377 | "isActive": false, 378 | "text": "OrganizationInvitation", 379 | }, 380 | { 381 | "href": "/organizationinvitationconnection.doc.html", 382 | "isActive": false, 383 | "text": "OrganizationInvitationConnection", 384 | }, 385 | { 386 | "href": "/organizationinvitationedge.doc.html", 387 | "isActive": false, 388 | "text": "OrganizationInvitationEdge", 389 | }, 390 | { 391 | "href": "/pageinfo.doc.html", 392 | "isActive": false, 393 | "text": "PageInfo", 394 | }, 395 | { 396 | "href": "/project.doc.html", 397 | "isActive": false, 398 | "text": "Project", 399 | }, 400 | { 401 | "href": "/projectcard.doc.html", 402 | "isActive": false, 403 | "text": "ProjectCard", 404 | }, 405 | { 406 | "href": "/projectcardconnection.doc.html", 407 | "isActive": false, 408 | "text": "ProjectCardConnection", 409 | }, 410 | { 411 | "href": "/projectcardedge.doc.html", 412 | "isActive": false, 413 | "text": "ProjectCardEdge", 414 | }, 415 | { 416 | "href": "/projectcolumn.doc.html", 417 | "isActive": false, 418 | "text": "ProjectColumn", 419 | }, 420 | { 421 | "href": "/projectcolumnconnection.doc.html", 422 | "isActive": false, 423 | "text": "ProjectColumnConnection", 424 | }, 425 | { 426 | "href": "/projectcolumnedge.doc.html", 427 | "isActive": false, 428 | "text": "ProjectColumnEdge", 429 | }, 430 | { 431 | "href": "/projectconnection.doc.html", 432 | "isActive": false, 433 | "text": "ProjectConnection", 434 | }, 435 | { 436 | "href": "/projectedge.doc.html", 437 | "isActive": false, 438 | "text": "ProjectEdge", 439 | }, 440 | { 441 | "href": "/protectedbranch.doc.html", 442 | "isActive": false, 443 | "text": "ProtectedBranch", 444 | }, 445 | { 446 | "href": "/protectedbranchconnection.doc.html", 447 | "isActive": false, 448 | "text": "ProtectedBranchConnection", 449 | }, 450 | { 451 | "href": "/protectedbranchedge.doc.html", 452 | "isActive": false, 453 | "text": "ProtectedBranchEdge", 454 | }, 455 | { 456 | "href": "/pullrequest.doc.html", 457 | "isActive": false, 458 | "text": "PullRequest", 459 | }, 460 | { 461 | "href": "/pullrequestconnection.doc.html", 462 | "isActive": false, 463 | "text": "PullRequestConnection", 464 | }, 465 | { 466 | "href": "/pullrequestedge.doc.html", 467 | "isActive": false, 468 | "text": "PullRequestEdge", 469 | }, 470 | { 471 | "href": "/pullrequestreview.doc.html", 472 | "isActive": false, 473 | "text": "PullRequestReview", 474 | }, 475 | { 476 | "href": "/pullrequestreviewcomment.doc.html", 477 | "isActive": false, 478 | "text": "PullRequestReviewComment", 479 | }, 480 | { 481 | "href": "/pullrequestreviewcommentconnection.doc.html", 482 | "isActive": false, 483 | "text": "PullRequestReviewCommentConnection", 484 | }, 485 | { 486 | "href": "/pullrequestreviewcommentedge.doc.html", 487 | "isActive": false, 488 | "text": "PullRequestReviewCommentEdge", 489 | }, 490 | { 491 | "href": "/pullrequestreviewconnection.doc.html", 492 | "isActive": false, 493 | "text": "PullRequestReviewConnection", 494 | }, 495 | { 496 | "href": "/pullrequestreviewedge.doc.html", 497 | "isActive": false, 498 | "text": "PullRequestReviewEdge", 499 | }, 500 | { 501 | "href": "/pullrequestreviewthread.doc.html", 502 | "isActive": false, 503 | "text": "PullRequestReviewThread", 504 | }, 505 | { 506 | "href": "/reactinguserconnection.doc.html", 507 | "isActive": false, 508 | "text": "ReactingUserConnection", 509 | }, 510 | { 511 | "href": "/reactinguseredge.doc.html", 512 | "isActive": false, 513 | "text": "ReactingUserEdge", 514 | }, 515 | { 516 | "href": "/reaction.doc.html", 517 | "isActive": false, 518 | "text": "Reaction", 519 | }, 520 | { 521 | "href": "/reactionconnection.doc.html", 522 | "isActive": false, 523 | "text": "ReactionConnection", 524 | }, 525 | { 526 | "href": "/reactionedge.doc.html", 527 | "isActive": false, 528 | "text": "ReactionEdge", 529 | }, 530 | { 531 | "href": "/reactiongroup.doc.html", 532 | "isActive": false, 533 | "text": "ReactionGroup", 534 | }, 535 | { 536 | "href": "/ref.doc.html", 537 | "isActive": false, 538 | "text": "Ref", 539 | }, 540 | { 541 | "href": "/refconnection.doc.html", 542 | "isActive": false, 543 | "text": "RefConnection", 544 | }, 545 | { 546 | "href": "/refedge.doc.html", 547 | "isActive": false, 548 | "text": "RefEdge", 549 | }, 550 | { 551 | "href": "/referencedevent.doc.html", 552 | "isActive": false, 553 | "text": "ReferencedEvent", 554 | }, 555 | { 556 | "href": "/release.doc.html", 557 | "isActive": false, 558 | "text": "Release", 559 | }, 560 | { 561 | "href": "/releaseasset.doc.html", 562 | "isActive": false, 563 | "text": "ReleaseAsset", 564 | }, 565 | { 566 | "href": "/releaseassetconnection.doc.html", 567 | "isActive": false, 568 | "text": "ReleaseAssetConnection", 569 | }, 570 | { 571 | "href": "/releaseassetedge.doc.html", 572 | "isActive": false, 573 | "text": "ReleaseAssetEdge", 574 | }, 575 | { 576 | "href": "/releaseconnection.doc.html", 577 | "isActive": false, 578 | "text": "ReleaseConnection", 579 | }, 580 | { 581 | "href": "/releaseedge.doc.html", 582 | "isActive": false, 583 | "text": "ReleaseEdge", 584 | }, 585 | { 586 | "href": "/removeoutsidecollaboratorpayload.doc.html", 587 | "isActive": false, 588 | "text": "RemoveOutsideCollaboratorPayload", 589 | }, 590 | { 591 | "href": "/removereactionpayload.doc.html", 592 | "isActive": false, 593 | "text": "RemoveReactionPayload", 594 | }, 595 | { 596 | "href": "/renamedevent.doc.html", 597 | "isActive": false, 598 | "text": "RenamedEvent", 599 | }, 600 | { 601 | "href": "/reopenedevent.doc.html", 602 | "isActive": false, 603 | "text": "ReopenedEvent", 604 | }, 605 | { 606 | "href": "/repository.doc.html", 607 | "isActive": false, 608 | "text": "Repository", 609 | }, 610 | { 611 | "href": "/repositoryconnection.doc.html", 612 | "isActive": false, 613 | "text": "RepositoryConnection", 614 | }, 615 | { 616 | "href": "/repositoryedge.doc.html", 617 | "isActive": false, 618 | "text": "RepositoryEdge", 619 | }, 620 | { 621 | "href": "/repositoryinvitation.doc.html", 622 | "isActive": false, 623 | "text": "RepositoryInvitation", 624 | }, 625 | { 626 | "href": "/repositoryinvitationrepository.doc.html", 627 | "isActive": false, 628 | "text": "RepositoryInvitationRepository", 629 | }, 630 | { 631 | "href": "/requestreviewspayload.doc.html", 632 | "isActive": false, 633 | "text": "RequestReviewsPayload", 634 | }, 635 | { 636 | "href": "/reviewdismissalallowance.doc.html", 637 | "isActive": false, 638 | "text": "ReviewDismissalAllowance", 639 | }, 640 | { 641 | "href": "/reviewdismissalallowanceconnection.doc.html", 642 | "isActive": false, 643 | "text": "ReviewDismissalAllowanceConnection", 644 | }, 645 | { 646 | "href": "/reviewdismissalallowanceedge.doc.html", 647 | "isActive": false, 648 | "text": "ReviewDismissalAllowanceEdge", 649 | }, 650 | { 651 | "href": "/reviewdismissedevent.doc.html", 652 | "isActive": false, 653 | "text": "ReviewDismissedEvent", 654 | }, 655 | { 656 | "href": "/reviewrequest.doc.html", 657 | "isActive": false, 658 | "text": "ReviewRequest", 659 | }, 660 | { 661 | "href": "/reviewrequestconnection.doc.html", 662 | "isActive": false, 663 | "text": "ReviewRequestConnection", 664 | }, 665 | { 666 | "href": "/reviewrequestedevent.doc.html", 667 | "isActive": false, 668 | "text": "ReviewRequestedEvent", 669 | }, 670 | { 671 | "href": "/reviewrequestedge.doc.html", 672 | "isActive": false, 673 | "text": "ReviewRequestEdge", 674 | }, 675 | { 676 | "href": "/reviewrequestremovedevent.doc.html", 677 | "isActive": false, 678 | "text": "ReviewRequestRemovedEvent", 679 | }, 680 | { 681 | "href": "/searchresultitemconnection.doc.html", 682 | "isActive": false, 683 | "text": "SearchResultItemConnection", 684 | }, 685 | { 686 | "href": "/searchresultitemedge.doc.html", 687 | "isActive": false, 688 | "text": "SearchResultItemEdge", 689 | }, 690 | { 691 | "href": "/smimesignature.doc.html", 692 | "isActive": false, 693 | "text": "SmimeSignature", 694 | }, 695 | { 696 | "href": "/stargazerconnection.doc.html", 697 | "isActive": false, 698 | "text": "StargazerConnection", 699 | }, 700 | { 701 | "href": "/stargazeredge.doc.html", 702 | "isActive": false, 703 | "text": "StargazerEdge", 704 | }, 705 | { 706 | "href": "/starredrepositoryconnection.doc.html", 707 | "isActive": false, 708 | "text": "StarredRepositoryConnection", 709 | }, 710 | { 711 | "href": "/starredrepositoryedge.doc.html", 712 | "isActive": false, 713 | "text": "StarredRepositoryEdge", 714 | }, 715 | { 716 | "href": "/status.doc.html", 717 | "isActive": false, 718 | "text": "Status", 719 | }, 720 | { 721 | "href": "/statuscontext.doc.html", 722 | "isActive": false, 723 | "text": "StatusContext", 724 | }, 725 | { 726 | "href": "/submitpullrequestreviewpayload.doc.html", 727 | "isActive": false, 728 | "text": "SubmitPullRequestReviewPayload", 729 | }, 730 | { 731 | "href": "/subscribedevent.doc.html", 732 | "isActive": false, 733 | "text": "SubscribedEvent", 734 | }, 735 | { 736 | "href": "/tag.doc.html", 737 | "isActive": false, 738 | "text": "Tag", 739 | }, 740 | { 741 | "href": "/team.doc.html", 742 | "isActive": false, 743 | "text": "Team", 744 | }, 745 | { 746 | "href": "/teamconnection.doc.html", 747 | "isActive": false, 748 | "text": "TeamConnection", 749 | }, 750 | { 751 | "href": "/teamedge.doc.html", 752 | "isActive": false, 753 | "text": "TeamEdge", 754 | }, 755 | { 756 | "href": "/tree.doc.html", 757 | "isActive": false, 758 | "text": "Tree", 759 | }, 760 | { 761 | "href": "/treeentry.doc.html", 762 | "isActive": false, 763 | "text": "TreeEntry", 764 | }, 765 | { 766 | "href": "/unassignedevent.doc.html", 767 | "isActive": false, 768 | "text": "UnassignedEvent", 769 | }, 770 | { 771 | "href": "/unknownsignature.doc.html", 772 | "isActive": false, 773 | "text": "UnknownSignature", 774 | }, 775 | { 776 | "href": "/unlabeledevent.doc.html", 777 | "isActive": false, 778 | "text": "UnlabeledEvent", 779 | }, 780 | { 781 | "href": "/unlockedevent.doc.html", 782 | "isActive": false, 783 | "text": "UnlockedEvent", 784 | }, 785 | { 786 | "href": "/unsubscribedevent.doc.html", 787 | "isActive": false, 788 | "text": "UnsubscribedEvent", 789 | }, 790 | { 791 | "href": "/updateprojectcardpayload.doc.html", 792 | "isActive": false, 793 | "text": "UpdateProjectCardPayload", 794 | }, 795 | { 796 | "href": "/updateprojectcolumnpayload.doc.html", 797 | "isActive": false, 798 | "text": "UpdateProjectColumnPayload", 799 | }, 800 | { 801 | "href": "/updateprojectpayload.doc.html", 802 | "isActive": false, 803 | "text": "UpdateProjectPayload", 804 | }, 805 | { 806 | "href": "/updatepullrequestreviewcommentpayload.doc.html", 807 | "isActive": false, 808 | "text": "UpdatePullRequestReviewCommentPayload", 809 | }, 810 | { 811 | "href": "/updatepullrequestreviewpayload.doc.html", 812 | "isActive": false, 813 | "text": "UpdatePullRequestReviewPayload", 814 | }, 815 | { 816 | "href": "/updatesubscriptionpayload.doc.html", 817 | "isActive": false, 818 | "text": "UpdateSubscriptionPayload", 819 | }, 820 | { 821 | "href": "/user.doc.html", 822 | "isActive": false, 823 | "text": "User", 824 | }, 825 | { 826 | "href": "/userconnection.doc.html", 827 | "isActive": false, 828 | "text": "UserConnection", 829 | }, 830 | { 831 | "href": "/useredge.doc.html", 832 | "isActive": false, 833 | "text": "UserEdge", 834 | }, 835 | { 836 | "href": "/directive.spec.html", 837 | "isActive": false, 838 | "text": "__Directive", 839 | }, 840 | { 841 | "href": "/enumvalue.spec.html", 842 | "isActive": false, 843 | "text": "__EnumValue", 844 | }, 845 | { 846 | "href": "/field.spec.html", 847 | "isActive": false, 848 | "text": "__Field", 849 | }, 850 | { 851 | "href": "/inputvalue.spec.html", 852 | "isActive": false, 853 | "text": "__InputValue", 854 | }, 855 | { 856 | "href": "/schema.spec.html", 857 | "isActive": false, 858 | "text": "__Schema", 859 | }, 860 | { 861 | "href": "/type.spec.html", 862 | "isActive": false, 863 | "text": "__Type", 864 | }, 865 | ] 866 | } 867 | ]); 868 | }); 869 | }); 870 | -------------------------------------------------------------------------------- /plugins/navigation.object.ts: -------------------------------------------------------------------------------- 1 | import { NavigationItemInterface, PluginInterface } from "../lib/interface"; 2 | import { 3 | NavigationItem, 4 | NavigationSection, 5 | OBJECT, 6 | Plugin 7 | } from "../lib/utility"; 8 | 9 | export default class NavigationObjects extends Plugin 10 | implements PluginInterface { 11 | getTypes(buildForType: string): NavigationItemInterface[] { 12 | const obj = this.document.types.filter(type => { 13 | return ( 14 | type.kind === OBJECT && 15 | (!this.queryType || this.queryType.name !== type.name) && 16 | (!this.mutationType || this.mutationType.name !== type.name) && 17 | (!this.subscriptionType || this.subscriptionType.name !== type.name) 18 | ); 19 | }); 20 | 21 | return obj.map( 22 | type => 23 | new NavigationItem( 24 | type.name, 25 | this.url(type), 26 | type.name === buildForType 27 | ) 28 | ); 29 | } 30 | 31 | getNavigations(buildForType: string) { 32 | const types: NavigationItemInterface[] = this.getTypes(buildForType); 33 | 34 | if (types.length === 0) { 35 | return []; 36 | } 37 | 38 | return [new NavigationSection("Objects", types)]; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /plugins/navigation.scalar.test.ts: -------------------------------------------------------------------------------- 1 | import projectPackage from "../test/empty.package.json"; 2 | import NavigationScalar from "./navigation.object"; 3 | import schema from "../test/empty.schema.json"; 4 | 5 | describe("pĺugins/navigation.directive#NavigationDirectives", () => { 6 | test("plugin return empty", () => { 7 | const plugin = new NavigationScalar( 8 | { 9 | ...schema.data.__schema, 10 | types: [], 11 | }, 12 | projectPackage, 13 | {} 14 | ); 15 | 16 | expect(plugin.getNavigations("Query")).toEqual([]) 17 | }) 18 | 19 | test("plugin return navigation", () => { 20 | const plugin = new NavigationScalar(schema.data.__schema, projectPackage, {}); 21 | expect(plugin.getNavigations("Query")).toEqual([ 22 | { 23 | title: "Objects", 24 | items: [ 25 | { 26 | "href": "/directive.spec.html", 27 | "isActive": false, 28 | "text": "__Directive", 29 | }, 30 | { 31 | "href": "/enumvalue.spec.html", 32 | "isActive": false, 33 | "text": "__EnumValue", 34 | }, 35 | { 36 | "href": "/field.spec.html", 37 | "isActive": false, 38 | "text": "__Field", 39 | }, 40 | { 41 | "href": "/inputvalue.spec.html", 42 | "isActive": false, 43 | "text": "__InputValue", 44 | }, 45 | { 46 | "href": "/schema.spec.html", 47 | "isActive": false, 48 | "text": "__Schema", 49 | }, 50 | { 51 | "href": "/type.spec.html", 52 | "isActive": false, 53 | "text": "__Type", 54 | }, 55 | ] 56 | } 57 | ]); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /plugins/navigation.scalar.ts: -------------------------------------------------------------------------------- 1 | import { NavigationItemInterface, PluginInterface } from "../lib/interface"; 2 | import { 3 | NavigationItem, 4 | NavigationSection, 5 | Plugin, 6 | SCALAR 7 | } from "../lib/utility"; 8 | 9 | export default class NavigationScalars extends Plugin 10 | implements PluginInterface { 11 | getTypes(buildForType: string): NavigationItemInterface[] { 12 | return this.document.types 13 | .filter(type => type.kind === SCALAR) 14 | .map( 15 | type => 16 | new NavigationItem( 17 | type.name, 18 | this.url(type), 19 | type.name === buildForType 20 | ) 21 | ); 22 | } 23 | 24 | getNavigations(buildForType: string) { 25 | const types: NavigationItemInterface[] = this.getTypes(buildForType); 26 | 27 | if (types.length === 0) { 28 | return []; 29 | } 30 | 31 | return [new NavigationSection("Scalars", types)]; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /plugins/navigation.schema.test.ts: -------------------------------------------------------------------------------- 1 | import projectPackage from "../test/empty.package.json"; 2 | import NavigationSchema from "./navigation.schema"; 3 | import schema from "../test/github.json"; 4 | 5 | describe("pĺugins/navigation.directive#NavigationDirectives", () => { 6 | test("plugin return empty", () => { 7 | const plugin = new NavigationSchema( 8 | { 9 | ...schema.data.__schema, 10 | mutationType: null, 11 | queryType: null, 12 | subscriptionType: null 13 | }, 14 | projectPackage, 15 | {} 16 | ); 17 | 18 | expect(plugin.getNavigations("Query")).toEqual([]) 19 | }) 20 | 21 | test("plugin return navigation", () => { 22 | const plugin = new NavigationSchema( 23 | { 24 | ...schema.data.__schema, 25 | subscriptionType: { 26 | "name": "Subscription", 27 | "description": "The subscription root of GitHub's GraphQL interface.", 28 | "kind": "OBJECT" 29 | }, 30 | }, 31 | projectPackage, 32 | {} 33 | ); 34 | expect(plugin.getNavigations("Query")).toEqual([ 35 | { 36 | title: "Schema", 37 | items: [ 38 | { 39 | "href": "/query.doc.html", 40 | "isActive": true, 41 | "text": "Query" 42 | }, 43 | { 44 | "href": "/mutation.doc.html", 45 | "isActive": false, 46 | "text": "Mutation", 47 | }, 48 | { 49 | "href": "/subscription.doc.html", 50 | "isActive": false, 51 | "text": "Subscription" 52 | }, 53 | ] 54 | } 55 | ]); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /plugins/navigation.schema.ts: -------------------------------------------------------------------------------- 1 | import { NavigationSectionInterface, PluginInterface } from "../lib/interface"; 2 | import { NavigationItem, NavigationSection, Plugin } from "../lib/utility"; 3 | 4 | export default class NavigationSchema extends Plugin 5 | implements PluginInterface { 6 | getNavigations(buildFrom?: string): NavigationSectionInterface[] { 7 | 8 | if ( 9 | !this.document.queryType && 10 | !this.document.mutationType && 11 | !this.document.subscriptionType 12 | ) { 13 | return [] 14 | } 15 | 16 | const section = new NavigationSection("Schema", []); 17 | 18 | // Query 19 | if (this.document.queryType) { 20 | section.items.push( 21 | new NavigationItem( 22 | this.document.queryType.name, 23 | this.url(this.document.queryType), 24 | buildFrom === this.document.queryType.name 25 | ) 26 | ); 27 | } 28 | 29 | // Mutation 30 | if (this.document.mutationType) { 31 | section.items.push( 32 | new NavigationItem( 33 | this.document.mutationType.name, 34 | this.url(this.document.mutationType), 35 | buildFrom === this.document.mutationType.name 36 | ) 37 | ); 38 | } 39 | 40 | // Subscription 41 | if (this.document.subscriptionType) { 42 | section.items.push( 43 | new NavigationItem( 44 | this.document.subscriptionType.name, 45 | this.url(this.document.subscriptionType), 46 | buildFrom === this.document.subscriptionType.name 47 | ) 48 | ); 49 | } 50 | 51 | return [section]; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /plugins/navigation.union.test.ts: -------------------------------------------------------------------------------- 1 | import projectPackage from "../test/empty.package.json"; 2 | import NavigationUnion from "./navigation.union"; 3 | import schema from "../test/github.json"; 4 | 5 | describe("pĺugins/navigation.directive#NavigationDirectives", () => { 6 | test("plugin return empty", () => { 7 | const plugin = new NavigationUnion( 8 | { 9 | ...schema.data.__schema, 10 | types: [], 11 | }, 12 | projectPackage, 13 | {} 14 | ); 15 | 16 | expect(plugin.getNavigations("Query")).toEqual([]) 17 | }) 18 | 19 | test("plugin return navigation", () => { 20 | const plugin = new NavigationUnion(schema.data.__schema, projectPackage, {}); 21 | expect(plugin.getNavigations("Query")).toEqual([ 22 | { 23 | title: "Unions", 24 | items: [ 25 | { 26 | "href": "/issuetimelineitem.doc.html", 27 | "isActive": false, 28 | "text": "IssueTimelineItem", 29 | }, 30 | { 31 | "href": "/projectcarditem.doc.html", 32 | "isActive": false, 33 | "text": "ProjectCardItem", 34 | }, 35 | { 36 | "href": "/reviewdismissalallowanceactor.doc.html", 37 | "isActive": false, 38 | "text": "ReviewDismissalAllowanceActor", 39 | }, 40 | { 41 | "href": "/searchresultitem.doc.html", 42 | "isActive": false, 43 | "text": "SearchResultItem", 44 | }, 45 | ] 46 | } 47 | ]); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /plugins/navigation.union.ts: -------------------------------------------------------------------------------- 1 | import { NavigationItemInterface, PluginInterface } from "../lib/interface"; 2 | import { 3 | NavigationItem, 4 | NavigationSection, 5 | Plugin, 6 | UNION 7 | } from "../lib/utility"; 8 | 9 | export default class NavigationScalars extends Plugin 10 | implements PluginInterface { 11 | getTypes(buildForType: string): NavigationItemInterface[] { 12 | return this.document.types 13 | .filter(type => type.kind === UNION) 14 | .map( 15 | type => 16 | new NavigationItem( 17 | type.name, 18 | this.url(type), 19 | type.name === buildForType 20 | ) 21 | ); 22 | } 23 | 24 | getNavigations(buildForType: string) { 25 | const types: NavigationItemInterface[] = this.getTypes(buildForType); 26 | 27 | if (types.length === 0) { 28 | return []; 29 | } 30 | 31 | return [new NavigationSection("Unions", types)]; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /template/slds/fonts/webfonts/SalesforceSans-Bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2fd/graphdoc/8be9dbff4c17fba2870c4a3eec773f4590cf0679/template/slds/fonts/webfonts/SalesforceSans-Bold.eot -------------------------------------------------------------------------------- /template/slds/fonts/webfonts/SalesforceSans-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2fd/graphdoc/8be9dbff4c17fba2870c4a3eec773f4590cf0679/template/slds/fonts/webfonts/SalesforceSans-Bold.woff -------------------------------------------------------------------------------- /template/slds/fonts/webfonts/SalesforceSans-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2fd/graphdoc/8be9dbff4c17fba2870c4a3eec773f4590cf0679/template/slds/fonts/webfonts/SalesforceSans-Bold.woff2 -------------------------------------------------------------------------------- /template/slds/fonts/webfonts/SalesforceSans-BoldItalic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2fd/graphdoc/8be9dbff4c17fba2870c4a3eec773f4590cf0679/template/slds/fonts/webfonts/SalesforceSans-BoldItalic.eot -------------------------------------------------------------------------------- /template/slds/fonts/webfonts/SalesforceSans-BoldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2fd/graphdoc/8be9dbff4c17fba2870c4a3eec773f4590cf0679/template/slds/fonts/webfonts/SalesforceSans-BoldItalic.woff -------------------------------------------------------------------------------- /template/slds/fonts/webfonts/SalesforceSans-BoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2fd/graphdoc/8be9dbff4c17fba2870c4a3eec773f4590cf0679/template/slds/fonts/webfonts/SalesforceSans-BoldItalic.woff2 -------------------------------------------------------------------------------- /template/slds/fonts/webfonts/SalesforceSans-Italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2fd/graphdoc/8be9dbff4c17fba2870c4a3eec773f4590cf0679/template/slds/fonts/webfonts/SalesforceSans-Italic.eot -------------------------------------------------------------------------------- /template/slds/fonts/webfonts/SalesforceSans-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2fd/graphdoc/8be9dbff4c17fba2870c4a3eec773f4590cf0679/template/slds/fonts/webfonts/SalesforceSans-Italic.woff -------------------------------------------------------------------------------- /template/slds/fonts/webfonts/SalesforceSans-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2fd/graphdoc/8be9dbff4c17fba2870c4a3eec773f4590cf0679/template/slds/fonts/webfonts/SalesforceSans-Italic.woff2 -------------------------------------------------------------------------------- /template/slds/fonts/webfonts/SalesforceSans-Light.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2fd/graphdoc/8be9dbff4c17fba2870c4a3eec773f4590cf0679/template/slds/fonts/webfonts/SalesforceSans-Light.eot -------------------------------------------------------------------------------- /template/slds/fonts/webfonts/SalesforceSans-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2fd/graphdoc/8be9dbff4c17fba2870c4a3eec773f4590cf0679/template/slds/fonts/webfonts/SalesforceSans-Light.woff -------------------------------------------------------------------------------- /template/slds/fonts/webfonts/SalesforceSans-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2fd/graphdoc/8be9dbff4c17fba2870c4a3eec773f4590cf0679/template/slds/fonts/webfonts/SalesforceSans-Light.woff2 -------------------------------------------------------------------------------- /template/slds/fonts/webfonts/SalesforceSans-LightItalic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2fd/graphdoc/8be9dbff4c17fba2870c4a3eec773f4590cf0679/template/slds/fonts/webfonts/SalesforceSans-LightItalic.eot -------------------------------------------------------------------------------- /template/slds/fonts/webfonts/SalesforceSans-LightItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2fd/graphdoc/8be9dbff4c17fba2870c4a3eec773f4590cf0679/template/slds/fonts/webfonts/SalesforceSans-LightItalic.woff -------------------------------------------------------------------------------- /template/slds/fonts/webfonts/SalesforceSans-LightItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2fd/graphdoc/8be9dbff4c17fba2870c4a3eec773f4590cf0679/template/slds/fonts/webfonts/SalesforceSans-LightItalic.woff2 -------------------------------------------------------------------------------- /template/slds/fonts/webfonts/SalesforceSans-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2fd/graphdoc/8be9dbff4c17fba2870c4a3eec773f4590cf0679/template/slds/fonts/webfonts/SalesforceSans-Regular.eot -------------------------------------------------------------------------------- /template/slds/fonts/webfonts/SalesforceSans-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2fd/graphdoc/8be9dbff4c17fba2870c4a3eec773f4590cf0679/template/slds/fonts/webfonts/SalesforceSans-Regular.woff -------------------------------------------------------------------------------- /template/slds/fonts/webfonts/SalesforceSans-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2fd/graphdoc/8be9dbff4c17fba2870c4a3eec773f4590cf0679/template/slds/fonts/webfonts/SalesforceSans-Regular.woff2 -------------------------------------------------------------------------------- /template/slds/fonts/webfonts/SalesforceSans-Thin.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2fd/graphdoc/8be9dbff4c17fba2870c4a3eec773f4590cf0679/template/slds/fonts/webfonts/SalesforceSans-Thin.eot -------------------------------------------------------------------------------- /template/slds/fonts/webfonts/SalesforceSans-Thin.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2fd/graphdoc/8be9dbff4c17fba2870c4a3eec773f4590cf0679/template/slds/fonts/webfonts/SalesforceSans-Thin.woff -------------------------------------------------------------------------------- /template/slds/fonts/webfonts/SalesforceSans-Thin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2fd/graphdoc/8be9dbff4c17fba2870c4a3eec773f4590cf0679/template/slds/fonts/webfonts/SalesforceSans-Thin.woff2 -------------------------------------------------------------------------------- /template/slds/fonts/webfonts/SalesforceSans-ThinItalic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2fd/graphdoc/8be9dbff4c17fba2870c4a3eec773f4590cf0679/template/slds/fonts/webfonts/SalesforceSans-ThinItalic.eot -------------------------------------------------------------------------------- /template/slds/fonts/webfonts/SalesforceSans-ThinItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2fd/graphdoc/8be9dbff4c17fba2870c4a3eec773f4590cf0679/template/slds/fonts/webfonts/SalesforceSans-ThinItalic.woff -------------------------------------------------------------------------------- /template/slds/fonts/webfonts/SalesforceSans-ThinItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2fd/graphdoc/8be9dbff4c17fba2870c4a3eec773f4590cf0679/template/slds/fonts/webfonts/SalesforceSans-ThinItalic.woff2 -------------------------------------------------------------------------------- /template/slds/index.mustache: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | {{title}} 11 | {{{headers}}} 12 | 13 | 14 | 33 |
    {{> main}}
    34 | 35 | 36 | 37 | {{#projectPackage.graphdoc.ga}} 38 | 50 | {{/projectPackage.graphdoc.ga}} 51 | -------------------------------------------------------------------------------- /template/slds/main.mustache: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 | 8 |
    9 | {{#projectPackage.graphdoc.graphiql}} 10 |
    11 | 12 | GraphiQL 13 | open_in_new 14 | 15 | {{/projectPackage.graphdoc.graphiql}} 16 |
    17 |
    18 | {{! 19 | Only navs varaible is defined: 20 | @example: 21 | title: String, 22 | description: String, 23 | sections: [ 24 | { 25 | title: String, 26 | description: String 27 | } 28 | 29 | -- MORE SECTIONS -- 30 | ] 31 | }} 32 |
    33 | {{#type}}

    {{type.kind}}

    {{/type}} 34 |

    {{title}}

    35 |
    {{{description}}}
    36 |
    37 |
    38 | {{#documents}} 39 |
    40 |
    41 |

    42 | 43 | link 44 | 45 | {{title}} 46 |

    47 | {{{description}}} 48 |
    49 |
    50 | {{/documents}} 51 | {{#graphdocPackage}} 52 | 59 | {{/graphdocPackage}} -------------------------------------------------------------------------------- /template/slds/nav.mustache: -------------------------------------------------------------------------------- 1 | {{! Only navs varaible is defined: @example: navs: [ { title: String, items: [ { href: String, text: String } -- MORE ITEMS 2 | --- ] } -- MORE NAVS -- ] }}{{#navigations}} 3 |
    4 |

    {{title}}

    5 | 14 |
    15 | {{/navigations}} -------------------------------------------------------------------------------- /template/slds/scripts/filter-types.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var HIDE_CLASS = 'slds-hide'; 3 | var ITEM_CLASS = 'slds-item'; 4 | 5 | /** 6 | * @class Item 7 | * @param {HTMLLIElement} li 8 | */ 9 | function Item(li) { 10 | this.li = li; 11 | this.type = li.title; 12 | this.typeLowerCase = li.title.toLowerCase(); 13 | } 14 | 15 | /** 16 | * @return boolean 17 | */ 18 | Item.prototype.contains = function (searchText) { 19 | return this.typeLowerCase.indexOf(searchText) >= 0; 20 | } 21 | 22 | /** 23 | * @return boolean 24 | */ 25 | Item.prototype.isHide = function () { 26 | this.li.classList.contains(HIDE_CLASS); 27 | } 28 | 29 | /** 30 | * @return void 31 | */ 32 | Item.prototype.hide = function () { 33 | if (!this.isHide()) 34 | this.li.classList.add(HIDE_CLASS); 35 | } 36 | 37 | /** 38 | * @return void 39 | */ 40 | Item.prototype.show = function () { 41 | this.li.classList.remove(HIDE_CLASS); 42 | } 43 | 44 | /** 45 | * @class ItemList 46 | * @param {Item[]} items 47 | */ 48 | function ItemList(items) { 49 | this.items = items; 50 | } 51 | 52 | /** 53 | * @function ItemsList.fromSelector 54 | * @param {string} selector 55 | * @return ItemList 56 | */ 57 | ItemList.fromSelector = function (selector) { 58 | 59 | var lis = document.querySelectorAll(selector); 60 | var items = Array.prototype.map.call(lis, function (li) { 61 | return new Item(li); 62 | }) 63 | 64 | return new ItemList(items); 65 | } 66 | 67 | /** 68 | * @return void 69 | */ 70 | ItemList.prototype.showIfmatch = function (match) { 71 | 72 | match = match.toLowerCase(match); 73 | 74 | this 75 | .items 76 | .forEach(function (item) { 77 | item.contains(match) ? 78 | item.show(): 79 | item.hide(); 80 | }) 81 | } 82 | 83 | /** 84 | * @var {ItemList} items 85 | * @var {HTMLInputElement} input 86 | */ 87 | var items = ItemList.fromSelector('nav .slds-navigation-list--vertical li'); 88 | var input = document.getElementById('type-search'); 89 | var lastMatch = ''; 90 | 91 | function onChange() { 92 | if (input.value === lastMatch) 93 | return; 94 | 95 | lastMatch = input.value; 96 | items.showIfmatch(lastMatch); 97 | } 98 | 99 | input.addEventListener('change', onChange); 100 | input.addEventListener('keyup', onChange); 101 | input.addEventListener('mouseup', onChange); 102 | })() -------------------------------------------------------------------------------- /template/slds/scripts/focus-active.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var navScroll = document.getElementById('navication-scroll'); 3 | var header = document.querySelector('nav header'); 4 | var active = document.querySelector('.slds-is-active a'); 5 | 6 | if(active) 7 | navScroll.scrollTop = active.offsetTop - header.offsetHeight - Math.ceil(active.offsetHeight / 2) 8 | })() -------------------------------------------------------------------------------- /template/slds/scripts/toggle-navigation.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 3 | var ACTIVE_CLASS = 'is-active'; 4 | var navigation = document.querySelector('nav'); 5 | var toggles = document.querySelectorAll('.js-toggle-navigation'); 6 | 7 | function toggleNavigation() { 8 | navigation.classList.contains(ACTIVE_CLASS) ? 9 | navigation.classList.remove(ACTIVE_CLASS) : 10 | navigation.classList.add(ACTIVE_CLASS); 11 | } 12 | 13 | Array.prototype.forEach.call( 14 | toggles, 15 | /** 16 | * @param {HTMLElement} toggle 17 | */ 18 | function (toggle) { 19 | toggle.addEventListener('click', toggleNavigation); 20 | } 21 | ) 22 | 23 | })() -------------------------------------------------------------------------------- /test/dump-schema.ts: -------------------------------------------------------------------------------- 1 | import { writeFileSync } from "fs"; 2 | import { printSchema } from "graphql"; 3 | import { StarWarsSchema } from "./schema"; 4 | writeFileSync(__dirname + "/starwars.graphql", printSchema(StarWarsSchema)); 5 | -------------------------------------------------------------------------------- /test/empty.package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "project-example", 3 | "version": "x.x.x", 4 | "description": "Example project", 5 | "author": "Fede Ramirez ", 6 | "graphdoc": { 7 | "baseUrl": "/", 8 | "force": true, 9 | "verbose": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/empty.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable-next-line:no-implicit-dependencies 2 | import express from "express"; 3 | // tslint:disable-next-line:no-implicit-dependencies 4 | import { graphqlHTTP } from "express-graphql"; 5 | import { 6 | GraphQLNonNull, 7 | GraphQLObjectType, 8 | GraphQLSchema, 9 | GraphQLString 10 | } from "graphql"; 11 | import pack from "../package.json"; 12 | 13 | const app = express(); 14 | 15 | export const EmptySchema = new GraphQLSchema({ 16 | query: new GraphQLObjectType({ 17 | name: "Query", 18 | description: "Root query", 19 | fields: { 20 | version: { 21 | type: new GraphQLNonNull(GraphQLString), 22 | resolve: () => pack.version 23 | } 24 | } 25 | }) 26 | }); 27 | 28 | app.use( 29 | "/graphql", 30 | graphqlHTTP({ 31 | schema: EmptySchema, 32 | graphiql: true 33 | }) 34 | ); 35 | app.listen(4000); 36 | -------------------------------------------------------------------------------- /test/schema.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | import { 10 | GraphQLEnumType, 11 | GraphQLID, 12 | GraphQLInterfaceType, 13 | GraphQLList, 14 | GraphQLNonNull, 15 | GraphQLObjectType, 16 | GraphQLSchema, 17 | GraphQLString 18 | } from "graphql"; 19 | 20 | /** 21 | * This is designed to be an end-to-end test, demonstrating 22 | * the full GraphQL stack. 23 | * 24 | * We will create a GraphQL schema that describes the major 25 | * characters in the original Star Wars trilogy. 26 | * 27 | * NOTE: This may contain spoilers for the original Star 28 | * Wars trilogy. 29 | */ 30 | 31 | /** 32 | * Using our shorthand to describe type systems, the type system for our 33 | * Star Wars example is: 34 | * 35 | * enum Episode { NEWHOPE, EMPIRE, JEDI } 36 | * 37 | * interface Character { 38 | * id: ID 39 | * name: String 40 | * friends: [Character] 41 | * appearsIn: [Episode] 42 | * } 43 | * 44 | * type Human : Character { 45 | * id: ID 46 | * name: String 47 | * friends: [Character] 48 | * appearsIn: [Episode] 49 | * homePlanet: String 50 | * } 51 | * 52 | * type Droid : Character { 53 | * id: ID 54 | * name: String 55 | * friends: [Character] 56 | * appearsIn: [Episode] 57 | * primaryFunction: String 58 | * } 59 | * 60 | * type Query { 61 | * hero(episode: Episode): Character 62 | * human(id: ID): Human 63 | * droid(id: ID): Droid 64 | * } 65 | * 66 | * We begin by setting up our schema. 67 | */ 68 | 69 | /** 70 | * The original trilogy consists of three movies. 71 | * 72 | * This implements the following type system shorthand: 73 | * enum Episode { NEWHOPE, EMPIRE, JEDI } 74 | */ 75 | const episodeEnum = new GraphQLEnumType({ 76 | description: "One of the films in the Star Wars Trilogy", 77 | name: "Episode", 78 | values: { 79 | EMPIRE: { 80 | description: "Released in 1980.", 81 | value: 5 82 | }, 83 | JEDI: { 84 | description: "Released in 1983.", 85 | value: 6 86 | }, 87 | NEWHOPE: { 88 | description: "Released in 1977.", 89 | value: 4 90 | } 91 | } 92 | }); 93 | 94 | /** 95 | * Characters in the Star Wars trilogy are either humans or droids. 96 | * 97 | * This implements the following type system shorthand: 98 | * interface Character { 99 | * id: ID 100 | * name: String 101 | * friends: [Character] 102 | * appearsIn: [Episode] 103 | * secretBackstory: String 104 | * } 105 | */ 106 | const characterInterface = new GraphQLInterfaceType({ 107 | name: "Character", 108 | description: "A character in the Star Wars Trilogy", 109 | fields: () => ({ 110 | id: { 111 | type: new GraphQLNonNull(GraphQLID), 112 | description: "The id of the character." 113 | }, 114 | name: { 115 | type: GraphQLString, 116 | description: "The name of the character." 117 | }, 118 | friends: { 119 | type: new GraphQLList(characterInterface), 120 | description: 121 | "The friends of the character, or an empty list if they " + "have none." 122 | }, 123 | appearsIn: { 124 | type: new GraphQLList(episodeEnum), 125 | description: "Which movies they appear in." 126 | }, 127 | secretBackstory: { 128 | type: GraphQLString, 129 | description: "All secrets about their past." 130 | } 131 | }), 132 | resolveType: _ => humanType 133 | }); 134 | 135 | /** 136 | * We define our human type, which implements the character interface. 137 | * 138 | * This implements the following type system shorthand: 139 | * type Human : Character { 140 | * id: ID 141 | * name: String 142 | * friends: [Character] 143 | * appearsIn: [Episode] 144 | * secretBackstory: String 145 | * } 146 | */ 147 | const humanType = new GraphQLObjectType({ 148 | name: "Human", 149 | description: "A humanoid creature in the Star Wars universe.", 150 | fields: () => ({ 151 | id: { 152 | type: new GraphQLNonNull(GraphQLID), 153 | description: "The id of the human." 154 | }, 155 | name: { 156 | type: GraphQLString, 157 | description: "The name of the human." 158 | }, 159 | friends: { 160 | type: new GraphQLList(characterInterface), 161 | description: 162 | "The friends of the human, or an empty list if they " + "have none.", 163 | resolve: human => human 164 | }, 165 | appearsIn: { 166 | type: new GraphQLList(episodeEnum), 167 | description: "Which movies they appear in." 168 | }, 169 | homePlanet: { 170 | type: GraphQLString, 171 | description: "The home planet of the human, or null if unknown." 172 | }, 173 | secretBackstory: { 174 | type: GraphQLString, 175 | description: "Where are they from and how they came to be who they are.", 176 | resolve: () => { 177 | throw new Error("secretBackstory is secret."); 178 | } 179 | } 180 | }), 181 | interfaces: [characterInterface] 182 | }); 183 | 184 | /** 185 | * The other type of character in Star Wars is a droid. 186 | * 187 | * This implements the following type system shorthand: 188 | * type Droid : Character { 189 | * id: ID 190 | * name: String 191 | * friends: [Character] 192 | * appearsIn: [Episode] 193 | * secretBackstory: String 194 | * primaryFunction: String 195 | * } 196 | */ 197 | const droidType = new GraphQLObjectType({ 198 | name: "Droid", 199 | description: "A mechanical creature in the Star Wars universe.", 200 | fields: () => ({ 201 | id: { 202 | type: new GraphQLNonNull(GraphQLID), 203 | description: "The id of the droid." 204 | }, 205 | name: { 206 | type: GraphQLString, 207 | description: "The name of the droid." 208 | }, 209 | friends: { 210 | type: new GraphQLList(characterInterface), 211 | description: 212 | "The friends of the droid, or an empty list if they " + "have none.", 213 | resolve: droid => droid 214 | }, 215 | appearsIn: { 216 | type: new GraphQLList(episodeEnum), 217 | description: "Which movies they appear in." 218 | }, 219 | secretBackstory: { 220 | type: GraphQLString, 221 | description: "Construction date and the name of the designer.", 222 | resolve: () => { 223 | throw new Error("secretBackstory is secret."); 224 | } 225 | }, 226 | primaryFunction: { 227 | type: GraphQLString, 228 | description: "The primary function of the droid." 229 | } 230 | }), 231 | interfaces: [characterInterface] 232 | }); 233 | 234 | /** 235 | * This is the type that will be the root of our query, and the 236 | * entry point into our schema. It gives us the ability to fetch 237 | * objects by their IDs, as well as to fetch the undisputed hero 238 | * of the Star Wars trilogy, R2-D2, directly. 239 | * 240 | * This implements the following type system shorthand: 241 | * type Query { 242 | * hero(episode: Episode): Character 243 | * human(id: ID): Human 244 | * droid(id: ID): Droid 245 | * } 246 | * 247 | */ 248 | const queryType = new GraphQLObjectType({ 249 | name: "Query", 250 | description: "Root query", 251 | fields: () => ({ 252 | hero: { 253 | type: characterInterface, 254 | description: "Return the hero by episode.", 255 | args: { 256 | episode: { 257 | description: 258 | "If omitted, returns the hero of the whole saga. If " + 259 | "provided, returns the hero of that particular episode.", 260 | type: episodeEnum 261 | } 262 | }, 263 | resolve: () => null 264 | }, 265 | human: { 266 | type: humanType, 267 | description: "Return the Human by ID.", 268 | args: { 269 | id: { 270 | description: "id of the human", 271 | type: new GraphQLNonNull(GraphQLID) 272 | } 273 | }, 274 | resolve: () => null 275 | }, 276 | droid: { 277 | type: droidType, 278 | description: "Return the Droid by ID.", 279 | args: { 280 | id: { 281 | description: "id of the droid", 282 | type: new GraphQLNonNull(GraphQLID) 283 | } 284 | }, 285 | resolve: () => null 286 | } 287 | }) 288 | }); 289 | 290 | /** 291 | * type Mutation { 292 | * favorite(episode: Episode!): Episode 293 | * } 294 | */ 295 | const mutationType = new GraphQLObjectType({ 296 | name: "Mutation", 297 | description: "Root Mutation", 298 | fields: () => ({ 299 | favorite: { 300 | type: episodeEnum, 301 | description: "Save the favorite episode.", 302 | args: { 303 | episode: { 304 | type: new GraphQLNonNull(episodeEnum), 305 | description: "Favorite episode." 306 | } 307 | }, 308 | resolve: (_, { episode }) => episode 309 | } 310 | }) 311 | }); 312 | 313 | /** 314 | * Finally, we construct our schema (whose starting query type is the query 315 | * type we defined above) and export it. 316 | */ 317 | export const StarWarsSchema = new GraphQLSchema({ 318 | query: queryType, 319 | mutation: mutationType 320 | }); 321 | -------------------------------------------------------------------------------- /test/starWars.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Test case for the ["modularized schema"](http://dev.apollodata.com/tools/graphql-tools/generate-schema.html#modularizing) of graphql-tools. 3 | */ 4 | const Character = ` 5 | # A character in the Star Wars Trilogy 6 | interface Character { 7 | # The id of the character. 8 | id: ID! 9 | 10 | # The name of the character. 11 | name: String 12 | 13 | # The friends of the character, or an empty list if they have none. 14 | friends: [Character] 15 | 16 | # Which movies they appear in. 17 | appearsIn: [Episode] 18 | 19 | # All secrets about their past. 20 | secretBackstory: String 21 | } 22 | `; 23 | 24 | const Droid = ` 25 | # A mechanical creature in the Star Wars universe. 26 | type Droid implements Character { 27 | # The id of the droid. 28 | id: ID! 29 | 30 | # The name of the droid. 31 | name: String 32 | 33 | # The friends of the droid, or an empty list if they have none. 34 | friends: [Character] 35 | 36 | # Which movies they appear in. 37 | appearsIn: [Episode] 38 | 39 | # Construction date and the name of the designer. 40 | secretBackstory: String 41 | 42 | # The primary function of the droid. 43 | primaryFunction: String 44 | } 45 | `; 46 | 47 | const Episode = ` 48 | # One of the films in the Star Wars Trilogy 49 | enum Episode { 50 | # Released in 1977. 51 | NEWHOPE 52 | 53 | # Released in 1980. 54 | EMPIRE 55 | 56 | # Released in 1983. 57 | JEDI 58 | } 59 | `; 60 | 61 | const Human = ` 62 | # A humanoid creature in the Star Wars universe. 63 | type Human implements Character { 64 | # The id of the human. 65 | id: ID! 66 | 67 | # The name of the human. 68 | name: String 69 | 70 | # The friends of the human, or an empty list if they have none. 71 | friends: [Character] 72 | 73 | # Which movies they appear in. 74 | appearsIn: [Episode] 75 | 76 | # The home planet of the human, or null if unknown. 77 | homePlanet: String 78 | 79 | # Where are they from and how they came to be who they are. 80 | secretBackstory: String 81 | } 82 | `; 83 | 84 | const Mutation = ` 85 | # Root Mutation 86 | type Mutation { 87 | # Save the favorite episode. 88 | favorite( 89 | # Favorite episode. 90 | episode: Episode! 91 | ): Episode 92 | } 93 | `; 94 | 95 | const Query = ` 96 | # Root query 97 | type Query { 98 | # Return the hero by episode. 99 | hero( 100 | # If omitted, returns the hero of the whole saga. If provided, returns the hero of that particular episode. 101 | episode: Episode 102 | ): Character 103 | 104 | # Return the Human by ID. 105 | human( 106 | # id of the human 107 | id: ID! 108 | ): Human 109 | 110 | # Return the Droid by ID. 111 | droid( 112 | # id of the droid 113 | id: ID! 114 | ): Droid 115 | } 116 | `; 117 | 118 | const Schema = ` 119 | schema { 120 | query: Query 121 | mutation: Mutation 122 | } 123 | `; 124 | 125 | export default () => [ 126 | Character, 127 | Droid, 128 | Episode, 129 | Human, 130 | Mutation, 131 | Query, 132 | Schema 133 | ]; 134 | -------------------------------------------------------------------------------- /test/starwars.graphql: -------------------------------------------------------------------------------- 1 | # A character in the Star Wars Trilogy 2 | interface Character { 3 | # The id of the character. 4 | id: ID! 5 | 6 | # The name of the character. 7 | name: String 8 | 9 | # The friends of the character, or an empty list if they have none. 10 | friends: [Character] 11 | 12 | # Which movies they appear in. 13 | appearsIn: [Episode] 14 | 15 | # All secrets about their past. 16 | secretBackstory: String 17 | } 18 | 19 | # A mechanical creature in the Star Wars universe. 20 | type Droid implements Character { 21 | # The id of the droid. 22 | id: ID! 23 | 24 | # The name of the droid. 25 | name: String 26 | 27 | # The friends of the droid, or an empty list if they have none. 28 | friends: [Character] 29 | 30 | # Which movies they appear in. 31 | appearsIn: [Episode] 32 | 33 | # Construction date and the name of the designer. 34 | secretBackstory: String 35 | 36 | # The primary function of the droid. 37 | primaryFunction: String 38 | } 39 | 40 | # One of the films in the Star Wars Trilogy 41 | enum Episode { 42 | # Released in 1977. 43 | NEWHOPE 44 | 45 | # Released in 1980. 46 | EMPIRE 47 | 48 | # Released in 1983. 49 | JEDI 50 | } 51 | 52 | # A humanoid creature in the Star Wars universe. 53 | type Human implements Character { 54 | # The id of the human. 55 | id: ID! 56 | 57 | # The name of the human. 58 | name: String 59 | 60 | # The friends of the human, or an empty list if they have none. 61 | friends: [Character] 62 | 63 | # Which movies they appear in. 64 | appearsIn: [Episode] 65 | 66 | # The home planet of the human, or null if unknown. 67 | homePlanet: String 68 | 69 | # Where are they from and how they came to be who they are. 70 | secretBackstory: String 71 | } 72 | 73 | # Root Mutation 74 | type Mutation { 75 | # Save the favorite episode. 76 | favorite( 77 | # Favorite episode. 78 | episode: Episode! 79 | ): Episode 80 | } 81 | 82 | # Root query 83 | type Query { 84 | # Return the hero by episode. 85 | hero( 86 | # If omitted, returns the hero of the whole saga. If provided, returns the hero of that particular episode. 87 | episode: Episode 88 | ): Character 89 | 90 | # Return the Human by ID. 91 | human( 92 | # id of the human 93 | id: ID! 94 | ): Human 95 | 96 | # Return the Droid by ID. 97 | droid( 98 | # id of the droid 99 | id: ID! 100 | ): Droid 101 | } 102 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": true, 3 | "compilerOptions": { 4 | "target": "es5", 5 | "moduleResolution": "node", 6 | "noUnusedLocals": true, 7 | "noUnusedParameters": true, 8 | "resolveJsonModule": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "module": "commonjs", 12 | "lib": ["es5", "es2015", "es2015.core", "scripthost"], 13 | "types": ["node", "jest"], 14 | "strictNullChecks": true, 15 | "sourceMap": false 16 | }, 17 | "exclude": ["node_modules/*", "gh-pages", "templates"] 18 | } 19 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint:latest", "tslint-config-prettier"], 3 | "rules": { 4 | "member-access": false, 5 | "object-literal-sort-keys": false 6 | } 7 | } 8 | --------------------------------------------------------------------------------