Skip to content

Commit

Permalink
fix(tests): Add tests (#12)
Browse files Browse the repository at this point in the history
* fix(tests): Refactor for tests

* fix(tests): Adding tests

* fix(test): fix dependencies

* fix(test): Add badges

* fix(test): Add badges
  • Loading branch information
Ben Ross authored Apr 20, 2018
1 parent a290acd commit 8bf73a8
Show file tree
Hide file tree
Showing 9 changed files with 980 additions and 79 deletions.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
command: yarn
- run:
name: Build
command: yarn tsc
command: yarn build
- run:
name: Test
command: echo done!
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# dotenvi
A library for generating dotenv files

[![CircleCI](https://circleci.com/gh/b3ross/dotenvi/tree/master.svg?style=svg)](https://circleci.com/gh/b3ross/dotenvi/tree/master)
[![npm version](https://badge.fury.io/js/dotenvi.svg)](https://badge.fury.io/js/dotenvi)
[![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier)
[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)

## Motivation
The library `dotenv` is a simple, convenient mechanism to add configuration to your node application. With simplicity, however, comes drawbacks. Dotenvi (pronounced "dotenvee") attempts to address those drawbacks.

Expand Down
17 changes: 17 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,37 @@
"url": "https://github.com/b3ross/dotenvi/issues"
},
"scripts": {
"build": "yarn tsc",
"test": "yarn jest",
"precommit": "lint-staged",
"release": "semantic-release"
},
"bin": {
"dotenvi": "dist/index.js"
},
"jest": {
"moduleFileExtensions": [
"ts",
"js"
],
"transform": {
"^.+\\.(ts|tsx)$": "./node_modules/ts-jest/preprocessor.js"
},
"testMatch": [
"**/*.test.(ts|js)"
],
"testEnvironment": "node"
},
"dependencies": {
"@types/argparse": "^1.0.33",
"@types/aws-sdk": "^2.7.0",
"@types/js-yaml": "^3.11.1",
"@types/node": "^9.6.4",
"argparse": "^1.0.10",
"aws-sdk": "^2.224.1",
"jest": "^22.4.3",
"js-yaml": "^3.11.0",
"ts-jest": "^22.4.4",
"typescript": "^2.8.1"
},
"devDependencies": {
Expand Down
45 changes: 7 additions & 38 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,42 +3,10 @@
import * as yaml from 'js-yaml';
import * as fs from 'fs';
import { ArgumentParser } from 'argparse';
import * as AWS from 'aws-sdk';
import { CloudFormation, Config } from 'aws-sdk';
import { DescribeStacksOutput } from 'aws-sdk/clients/cloudformation';

import { Parser } from './parse';
import { ResolverMap } from './types';

const resolvers: ResolverMap = {
cft: async (argument: string) => {
if (!AWS.config.region) {
AWS.config.update({ region: 'us-east-1' });
}
const cft = new CloudFormation();
const parsedArgument = argument.split('.', 2);
let stack: DescribeStacksOutput;
try {
stack = await cft.describeStacks({ StackName: parsedArgument[0] }).promise();
} catch (e) {
throw new Error(
`Could not get info for stack with name ${parsedArgument[0]} when parsing cft reference ${argument}: ${e}`
);
}
for (const output of stack.Stacks[0].Outputs) {
if (output.OutputKey === parsedArgument[1]) {
return output.OutputValue;
}
}
throw new Error(`Could not locate output ${parsedArgument[1]} of stack ${parsedArgument[0]}`);
},
env: async (argument: string) => {
return process.env[argument];
},
constant: async (argument: string) => {
return argument;
}
};
import { Rewriter } from './rewriter';
import { ResolverMap, Document } from './types';
import { resolvers } from './resolvers';

const parser = new ArgumentParser();
parser.addArgument(['-s', '--stage'], {
Expand All @@ -59,15 +27,16 @@ function writeFile(document: { [name: string]: string }) {
try {
// TODO Load external resolvers

let document = yaml.safeLoad(fs.readFileSync('./env.yml', 'utf8'));
let document = yaml.safeLoad(fs.readFileSync('./env.yml', 'utf8')) as Document;
if (args.stage) {
document = (document as any)[args.stage];
if (!document) {
throw new Error(`Could not locate stage ${args.stage} in file ${args.file}`);
}
}
const parser = new Parser(resolvers);
parser.parse(document).then(result => {
const rewriter = new Rewriter(resolvers);
rewriter.rewrite(document).then(result => {
console.info(`Writing .env file to ${process.cwd()}/.env`);
writeFile(result);
});
} catch (e) {
Expand Down
41 changes: 41 additions & 0 deletions src/resolvers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import * as AWS from 'aws-sdk';
import { CloudFormation, Config } from 'aws-sdk';
import { DescribeStacksOutput } from 'aws-sdk/clients/cloudformation';

import { ResolverMap } from './types';


export const resolvers: ResolverMap = {
cft: async (argument: string) => {
if (!AWS.config.region) {
AWS.config.update({ region: 'us-east-1' });
}
const cft = new CloudFormation();
const parsedArgument = argument.split('.', 2);
let stack: DescribeStacksOutput;
try {
stack = await cft.describeStacks({ StackName: parsedArgument[0] }).promise();
} catch (e) {
throw new Error(
`Could not get info for stack with name ${parsedArgument[0]} when parsing cft reference ${argument}: ${e}`
);
}
if (stack.Stacks.length == 0) {
throw new Error(
`Could not locate stack with name ${parsedArgument[0]} when parsing cft reference ${argument}`);
}

for (const output of stack.Stacks[0].Outputs) {
if (output.OutputKey === parsedArgument[1]) {
return output.OutputValue;
}
}
throw new Error(`Could not locate output ${parsedArgument[1]} of stack ${parsedArgument[0]}`);
},
env: async (argument: string) => {
return process.env[argument];
},
constant: async (argument: string) => {
return argument;
}
};
30 changes: 30 additions & 0 deletions src/rewriter.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const { Rewriter } = require('./rewriter');
const { resolvers } = require('./resolvers');


describe('Rewriter', () => {
it('Rewrites constants', () => {
const document = {
'explicit': '${constant:hello}',
'implicit': 'hello'
};

const rewriter = new Rewriter(resolvers);
return rewriter.rewrite(document).then((output) => {
expect(output['explicit']).toBe(output['implicit']);
});
});

it('Rewrites environment variables', () => {
process.env['TEST'] = 'hello';
const document = {
'test': '${env:TEST}'
};

const rewriter = new Rewriter(resolvers);
return rewriter.rewrite(document).then((output) => {
expect(output['test']).toBe('hello');
});
});

});
13 changes: 6 additions & 7 deletions src/parse.ts → src/rewriter.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
import { ResolverMap } from './types';
import { ResolverMap, Document } from './types';

export class Parser {
export class Rewriter {
constructor(private resolvers: ResolverMap) { }

async parse(document: any): Promise<any> {
async rewrite(document: Document): Promise<Document> {
const variables = Object.keys(document);
const result = {};
const result: Document = {};
for (const variable of variables) {
const value = document[variable];
(result as any)[variable] = await this.parseValue(value);
result[variable] = await this.rewriteValue(value);
}
return result;
}

private async parseValue(value: string): Promise<string> {
// TODO Handle parsing embedded references
private async rewriteValue(value: string): Promise<string> {
const regex = new RegExp('\\${([a-z]+):(.*)}');
const results = value.match(regex);
const resolverName = results && results[1];
Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export type ResolverMap = { [name: string]: (arg: string) => Promise<string> };
export type Document = { [name: string]: string }
Loading

0 comments on commit 8bf73a8

Please sign in to comment.