Skip to content
This repository has been archived by the owner on Sep 10, 2019. It is now read-only.

Commit

Permalink
feat: dev command ready for review
Browse files Browse the repository at this point in the history
- data sources are transpiled by default
- `--no-transpile` flag is respected
- a custom gateway can be supplied for dev (#10)
- temporary files are removed on process exit (#16)

close #11, close #10, close #16
  • Loading branch information
jlengstorf committed Dec 18, 2017
1 parent bb48037 commit baed286
Show file tree
Hide file tree
Showing 8 changed files with 301 additions and 119 deletions.
59 changes: 47 additions & 12 deletions bin/dev.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import { EOL } from 'os';
import path from 'path';
import yargs from 'yargs';
import cleanup from 'node-cleanup';
import { spawn } from 'cross-spawn';
import startDefaultGateway from '../lib/gateway';
import {
loadDataSources,
transpileDataSources,
cleanUpTempDir,
} from '../lib/data-sources';
import { log, success, warn } from '../lib/logger';

const getDirPath = dir => path.resolve(process.cwd(), dir);

Expand Down Expand Up @@ -45,23 +54,49 @@ export const builder = yargs =>
},
});

export const handler = ({
export const handler = async ({
dataSources = [],
mock = false,
gateway,
transpile,
}) => {
console.log({
dataSources,
gateway,
mock,
transpile,
});
warn('The GrAMPS CLI is intended for local development only.');

if (!gateway) {
startDefaultGateway({
dataSources,
enableMockData: mock,
});
let loadedDataSources = [];
if (dataSources.length) {
loadedDataSources = await transpileDataSources(transpile, dataSources).then(
loadDataSources,
);
}

// If a custom gateway was specified, set the env vars and start it.
if (gateway) {
// Define the `GRAMPS_DATA_SOURCES` env var.
process.env.GRAMPS_DATA_SOURCES = dataSources.length
? dataSources.join(',')
: '';

// Start the user-specified gateway.
spawn('node', [gateway], { stdio: 'inherit' });
return;
}

// If we get here, fire up the default gateway for development.
startDefaultGateway({
dataSources: loadedDataSources,
enableMockData: mock,
});
};

cleanup((exitCode, signal) => {
log(' -> cleaning up temporary files');
// Delete the temporary directory.
cleanUpTempDir().then(() => {
success('Successfully shut down. Thanks for using GrAMPS!');
process.kill(process.pid, signal);
});

// Uninstall the handler to prevent an infinite loop.
cleanup.uninstall();
return false;
});
15 changes: 1 addition & 14 deletions bin/gramps.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,8 @@
import { EOL } from 'os';
import yargs from 'yargs';
import cleanup from 'node-cleanup';

import { description, version } from '../package.json';
import dev from './dev';
// import cleanup from '../lib/cleanup';

yargs
.command(dev)
.demandCommand()
// .demandCommand()
.help().argv;

cleanup(
() => {
// console.log('exiting...');
},
{
ctrl_C: `${EOL}${EOL}Thanks for using GrAMPS!`,
},
);
20 changes: 0 additions & 20 deletions lib/cleanup.js

This file was deleted.

160 changes: 160 additions & 0 deletions lib/data-sources.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import fs from 'fs';
import path from 'path';
import cpy from 'cpy';
import del from 'del';
import mkdirp from 'mkdirp';
import babel from 'babel-core';
import globby from 'globby';
import { error, log, warn } from './logger';

const TEMP_DIR = path.resolve(__dirname, '..', '.tmp');

const handleError = (err, msg, callback) => {
if (!err) {
return;
}

error(msg || err);

if (callback) {
callback(err);
return;
}

throw err;
};

const getDirName = dir =>
dir
.split('/')
.filter(str => str)
.slice(-1)
.pop();

export const cleanUpTempDir = () => del(TEMP_DIR);

const makeTempDir = tmpDir =>
new Promise((resolve, reject) => {
mkdirp(tmpDir, err => {
handleError(err, `Unable to create ${tmpDir}`, reject);
log(` -> created a temporary directory at ${tmpDir}`);
resolve(tmpDir);
});
});

const makeParentTempDir = () =>
new Promise((resolve, reject) => {
cleanUpTempDir().then(() => {
mkdirp(TEMP_DIR, err => {
handleError(err, `Could not create ${TEMP_DIR}`);
resolve(TEMP_DIR);
});
});
});

const isValidDataSource = dataSourcePath => {
if (!fs.existsSync(dataSourcePath)) {
warn(`Could not load a data source from ${dataSourcePath}`);
return false;
}

return true;
};

const loadDataSourceFromPath = dataSourcePath => {
// eslint-disable-next-line global-require, import/no-dynamic-require
const src = require(dataSourcePath);
const dataSource = src.default || src;

// TODO check for required properties.

log(` -> successfully loaded ${dataSource.namespace}`);

return src.default || src;
};

export const loadDataSources = pathArr => {
return pathArr.filter(isValidDataSource).map(loadDataSourceFromPath);
};

const writeTranspiledFile = ({ filename, tmpFile, transpiled }) =>
new Promise((resolve, reject) => {
fs.writeFile(tmpFile, transpiled.code, err => {
handleError(err, `Unable to transpile ${tmpFile}`, reject);
resolve(filename);
});
});

// For convenience, we transpile data sources using GrAMPS settings.
const transpileJS = dataSource => tmpDir =>
new Promise((resolve, reject) => {
const filePromises = globby
.sync(path.join(dataSource, '{src,}/*.js'))
.map(file => {
const filename = path.basename(file);

return {
filename,
tmpFile: path.join(tmpDir, filename),
transpiled: babel.transformFileSync(file),
};
})
.map(writeTranspiledFile);

Promise.all(filePromises).then(() => resolve(tmpDir));
});

const copyGraphQLFiles = dataSource => tmpDir =>
new Promise((resolve, reject) => {
const filePromises = globby
.sync(path.join(dataSource, '{src,}/*.graphql'))
.map(file => cpy(file, tmpDir));

Promise.all(filePromises).then(() => resolve(tmpDir));
});

// We have to symlink node_modules or we’ll get errors when requiring packages.
const symlinkNodeModules = dataSource => tmpDir =>
new Promise((resolve, reject) => {
fs.symlink(
path.join(dataSource, 'node_modules'),
path.join(tmpDir, 'node_modules'),
err => {
handleError(err, 'Unable to symlink the Node modules folder', reject);
resolve(tmpDir);
},
);
});

const transpileDataSource = parentDir => dataSource =>
new Promise((resolve, reject) => {
const dirName = getDirName(dataSource);
const tmpDir = path.join(parentDir, dirName);

// Make a temporary directory for the data source.
makeTempDir(tmpDir)
.then(transpileJS(dataSource))
.then(copyGraphQLFiles(dataSource))
.then(symlinkNodeModules(dataSource))
.then(resolve)
.catch(err => handleError(err, null, reject));
});

export const transpileDataSources = (shouldTranspile, dataSources) =>
new Promise((resolve, reject) => {
if (!shouldTranspile) {
resolve(dataSources);
return;
}

makeParentTempDir()
.then(parentDir =>
dataSources
.filter(isValidDataSource)
.map(transpileDataSource(parentDir)),
)
.then(dataSourcePromises => {
Promise.all(dataSourcePromises).then(resolve);
})
.catch(reject);
});
25 changes: 22 additions & 3 deletions lib/gateway.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,34 @@ import gramps from '@gramps/gramps';
import { GraphQLSchema } from 'graphql';
import { graphqlExpress } from 'apollo-server-express';
import playground from 'graphql-playground-middleware-express';
import { success } from './logger';

const GRAPHQL_ENDPOINT = '/graphql';
const TESTING_ENDPOINT = '/playground';
const DEFAULT_PORT = 8080;

async function startServer(app) {
async function startServer(app, { enableMockData, dataSources }) {
const PORT = await getPort(DEFAULT_PORT);
app.listen(PORT, () => {
console.log(`=> http://localhost:${PORT}${TESTING_ENDPOINT}`);
const mode = enableMockData ? 'mock' : 'live';
success([
'='.repeat(65),
'',
` A GraphQL gateway has successfully started using ${mode} data.`,
``,
` The following GrAMPS data sources are running locally:`,
...dataSources.map(src => ` - ${src.namespace}`),
``,
` For UI development, point your GraphQL client to:`,
` http://localhost:${PORT}${GRAPHQL_ENDPOINT}`,
``,
` To test your data sources in the GraphQL Playground, visit:`,
` http://localhost:${PORT}${TESTING_ENDPOINT}`,
``,
' To stop this gateway, press `control` + `C`.',
``,
'='.repeat(65),
]);
});
}

Expand All @@ -29,5 +48,5 @@ export default config => {
app.use(GRAPHQL_ENDPOINT, graphqlExpress(GraphQLOptions));
app.use(TESTING_ENDPOINT, playground({ endpoint: GRAPHQL_ENDPOINT }));

startServer(app);
startServer(app, config);
};
13 changes: 13 additions & 0 deletions lib/logger.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { EOL } from 'os';
import chalk from 'chalk';

const padMsg = msg =>
['']
.concat(msg)
.concat('')
.join(EOL);

export const error = msg => console.error(chalk.red.bold(padMsg(msg)));
export const log = msg => console.log(chalk.dim(msg));
export const success = msg => console.log(chalk.green(padMsg(msg)));
export const warn = msg => console.warn(chalk.yellow.bold(padMsg(msg)));
14 changes: 9 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@gramps/cli",
"version": "0.0.0-development",
"version": "1.0.0-beta.1",
"description": "CLI for creating and developing with GrAMPS data sources.",
"files": [
"bin",
Expand All @@ -19,42 +19,46 @@
"gramps": "bin/index.js"
},
"scripts": {
"gramps": "bin/index.js",
"prepush": "npm test",
"lint": "eslint {bin,lib}/**/*.js",
"test": "npm run lint --silent",
"test:unit": "cross-env NODE_ENV=test LOG4JS_LEVEL='OFF' jest --coverage",
"semantic-release": "semantic-release pre && npm publish && semantic-release post"
},
"dependencies": {
"@gramps/gramps": "^1.0.0-beta-4",
"apollo-server-express": "^1.2.0",
"babel-core": "^6.26.0",
"body-parser": "^1.18.2",
"chalk": "^2.3.0",
"cpy": "^6.0.0",
"cross-env": "^5.1.1",
"cross-spawn": "^5.1.0",
"del": "^3.0.0",
"express": "^4.16.2",
"get-port": "^3.2.0",
"globby": "^7.1.1",
"graphql": "^0.11.7",
"graphql-playground-middleware-express": "^1.3.8",
"inquirer": "^4.0.1",
"mkdirp": "^0.5.1",
"node-cleanup": "^2.1.2",
"reify": "^0.13.3",
"yargs": "^10.0.3"
},
"peerDependencies": {
"@gramps/gramps": "^1.0.0-beta-5",
"graphql": "^0.11.7"
},
"devDependencies": {
"@gramps/gramps": "^1.0.0-beta-7",
"babel-cli": "^6.24.1",
"babel-core": "^6.26.0",
"babel-eslint": "^8.0.1",
"babel-jest": "^21.2.0",
"babel-plugin-inline-import": "^2.0.6",
"babel-preset-env": "^1.6.0",
"babel-preset-stage-2": "^6.24.1",
"condition-travis-enterprise": "^1.0.0",
"cpy-cli": "^1.0.1",
"del-cli": "^1.0.0",
"eslint": "^4.2.0",
"eslint-config-airbnb-base": "^12.0.2",
"eslint-config-prettier": "^2.3.0",
Expand Down
Loading

0 comments on commit baed286

Please sign in to comment.