Skip to content

Commit 8236049

Browse files
committed
Initial version
1 parent 33ce3a6 commit 8236049

File tree

6 files changed

+1936
-0
lines changed

6 files changed

+1936
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,4 @@ typings/
5757
# dotenv environment variables file
5858
.env
5959

60+
dist

package.json

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"name": "graphql-coverage",
3+
"version": "0.0.1",
4+
"description": "",
5+
"main": "index.js",
6+
"bin": "dist/index.js",
7+
"scripts": {
8+
"test": "echo \"Error: no test specified\" && exit 1",
9+
"start": "nodemon -e ts --exec 'ts-node src/index.ts'",
10+
"build": "tsc && cp -R src/static dist"
11+
},
12+
"repository": {
13+
"type": "git",
14+
"url": "git+https://github.com/APIs-guru/graphql-coverage.git"
15+
},
16+
"author": "",
17+
"license": "MIT",
18+
"bugs": {
19+
"url": "https://github.com/APIs-guru/graphql-coverage/issues"
20+
},
21+
"homepage": "https://github.com/APIs-guru/graphql-coverage#readme",
22+
"dependencies": {
23+
"chalk": "^2.1.0",
24+
"express": "^4.16.1",
25+
"express-graphql": "github:apis-guru/express-graphql#applyFn_dist",
26+
"graphql": "^0.11.7",
27+
"node-fetch": "^1.7.3"
28+
},
29+
"devDependencies": {
30+
"@types/chalk": "^0.4.31",
31+
"@types/express": "^4.0.37",
32+
"@types/graphql": "^0.11.5",
33+
"@types/node-fetch": "^1.6.7",
34+
"nodemon": "^1.12.1"
35+
}
36+
}

src/index.ts

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import * as path from 'path';
2+
import * as express from 'express';
3+
import * as graphqlHTTP from 'express-graphql';
4+
import {red, green, blue} from 'chalk';
5+
import fetch from 'node-fetch';
6+
import { Kind, print, introspectionQuery, buildClientSchema, visit, TypeInfo, visitWithTypeInfo } from 'graphql';
7+
8+
const coverage = {};
9+
const originUrl = process.argv[2];
10+
if (!originUrl) throw Error('Specify URL as the 1st argument!');
11+
12+
(async () => {
13+
const introspection = await graphqlFetch({query: introspectionQuery});
14+
if (introspection.errors) throw Error(JSON.stringify(introspection.errors, null, 2));
15+
const schema = buildClientSchema(introspection.data), app = express();
16+
const formatErrorFn = (err) => err;
17+
app.use('/graphql', graphqlHTTP({ schema, execute, graphiql: true, formatErrorFn }));
18+
app.get('/coverage-map', (_, res) => res.status(200).json(coverage));
19+
app.use('/coverage', express.static(path.join(__dirname, 'static')));
20+
const port = 9003;
21+
app.listen(port);
22+
console.log(`\n${green('✔')} Your GraphQL Fake API is ready to use 🚀 \n
23+
${blue('❯')} Coverage Graph:\t http://localhost:${port}/coverage
24+
${blue('❯')} GraphQL API:\t http://localhost:${port}/graphql`);
25+
})()
26+
.catch(error => console.error(red(error.stack)));
27+
28+
async function graphqlFetch(body) {
29+
const res = await fetch(originUrl, {
30+
method: 'POST',
31+
body: JSON.stringify(body),
32+
headers: { "content-type": 'application/json' },
33+
});
34+
if (!res.ok)
35+
throw Error(`${res.status} ${res.statusText}\n${await res.text()}`);
36+
return res.json();
37+
}
38+
39+
async function execute({schema, document, variableValues: variables, operationName}) {
40+
const typeInfo = new TypeInfo(schema);
41+
visit(document, visitWithTypeInfo(typeInfo, {
42+
[Kind.FIELD]: () => {
43+
const typeName = typeInfo.getParentType().name;
44+
const fieldName = typeInfo.getFieldDef().name;
45+
if (typeName.startsWith('__') || fieldName.startsWith('__')) return;
46+
coverage[`${typeName}::${fieldName}`] = true;
47+
},
48+
}));
49+
return await graphqlFetch({ query: print(document), variables, operationName });
50+
}

src/static/index.html

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset=utf-8 />
5+
<meta name="viewport" content="user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, minimal-ui">
6+
<title>GraphQL Voyager</title>
7+
<style>
8+
body {
9+
padding: 0;
10+
margin: 0;
11+
width: 100%;
12+
height: 100vh;
13+
overflow: hidden;
14+
}
15+
#voyager {
16+
height: 100vh;
17+
}
18+
.edge.green > path:not(.hover-path): {
19+
stroke: green;
20+
}
21+
.field.green > polygon {
22+
fill: rgba(0,255,0,.18)
23+
}
24+
</style>
25+
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/graphql-voyager/dist/voyager.css" />
26+
<script src="//cdn.jsdelivr.net/fetch/2.0.1/fetch.min.js"/></script>
27+
<script src="//cdn.jsdelivr.net/react/15.4.2/react.min.js"></script>
28+
<script src="//cdn.jsdelivr.net/react/15.4.2/react-dom.min.js"></script>
29+
<script src="//cdn.jsdelivr.net/npm/graphql-voyager/dist/voyager.min.js"></script>
30+
</head>
31+
<body>
32+
<main id="voyager">
33+
<h1 style="text-align: center; color: #5d7e86;"> Loading... </h1>
34+
</main>
35+
<script>
36+
window.addEventListener('load', function() {
37+
GraphQLVoyager.init(document.getElementById('voyager'), {
38+
introspection: function (introspectionQuery) {
39+
return fetch('/graphql?query=' + introspectionQuery)
40+
.then(function (res) { return res.ok ? res.json() : res.text(); });
41+
}
42+
});
43+
setInterval(function(){
44+
fetch('/coverage-map').then(function (res) { return res.json() })
45+
.then(function (coverage) {
46+
var greenElements = document.getElementsByClassName('green');
47+
while (greenElements.length)
48+
greenElements[0].classList.remove('green');
49+
for (var field in coverage) {
50+
markGreen('[id="FIELD::' + field + '"]');
51+
markGreen('[data-from="FIELD::' + field + '"]');
52+
}
53+
});
54+
}, 1000);
55+
});
56+
function markGreen(selector) {
57+
var el = document.querySelector(selector);
58+
el && el.classList.add('green');
59+
}
60+
</script>
61+
</body>
62+
</html>

tsconfig.json

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"compilerOptions": {
3+
"inlineSources": true,
4+
"experimentalDecorators": true,
5+
"emitDecoratorMetadata": true,
6+
"module": "commonjs",
7+
"target": "es5",
8+
"noImplicitAny": false,
9+
"noUnusedLocals": true,
10+
"noUnusedParameters": true,
11+
"sourceMap": true,
12+
"outDir": "dist",
13+
"pretty": true,
14+
"moduleResolution": "node",
15+
"lib": [
16+
"es2017",
17+
"esnext.asynciterable"
18+
]
19+
},
20+
"compileOnSave": false,
21+
"exclude": [
22+
"node_modules",
23+
".tmp",
24+
"dist"
25+
]
26+
}

0 commit comments

Comments
 (0)