aboutsummaryrefslogtreecommitdiffstats
path: root/packages/abi-gen
diff options
context:
space:
mode:
Diffstat (limited to 'packages/abi-gen')
-rw-r--r--packages/abi-gen/CHANGELOG.json9
-rwxr-xr-xpackages/abi-gen/bin/abi-gen.js2
-rw-r--r--packages/abi-gen/coverage/.gitkeep0
-rw-r--r--packages/abi-gen/package.json15
-rw-r--r--packages/abi-gen/src/index.ts26
-rw-r--r--packages/abi-gen/src/utils.ts24
-rw-r--r--packages/abi-gen/test/utils_test.ts86
7 files changed, 143 insertions, 19 deletions
diff --git a/packages/abi-gen/CHANGELOG.json b/packages/abi-gen/CHANGELOG.json
index 38b72dbf3..ef8f5dd39 100644
--- a/packages/abi-gen/CHANGELOG.json
+++ b/packages/abi-gen/CHANGELOG.json
@@ -1,5 +1,14 @@
[
{
+ "version": "0.4.1",
+ "changes": [
+ {
+ "note": "skip generation of wrappers that are already up to date",
+ "pr": 788
+ }
+ ]
+ },
+ {
"version": "0.4.0",
"changes": [
{
diff --git a/packages/abi-gen/bin/abi-gen.js b/packages/abi-gen/bin/abi-gen.js
index c46eb9b66..8d6bdccf8 100755
--- a/packages/abi-gen/bin/abi-gen.js
+++ b/packages/abi-gen/bin/abi-gen.js
@@ -1,2 +1,2 @@
#!/usr/bin/env node
-require('../lib/index.js')
+require('../lib/src/index.js')
diff --git a/packages/abi-gen/coverage/.gitkeep b/packages/abi-gen/coverage/.gitkeep
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/packages/abi-gen/coverage/.gitkeep
diff --git a/packages/abi-gen/package.json b/packages/abi-gen/package.json
index 7d7a20e68..4fb335545 100644
--- a/packages/abi-gen/package.json
+++ b/packages/abi-gen/package.json
@@ -12,6 +12,11 @@
"lint": "tslint --project .",
"clean": "shx rm -rf lib scripts",
"build": "tsc && copyfiles -u 2 './lib/monorepo_scripts/**/*' ./scripts",
+ "test": "yarn run_mocha",
+ "run_mocha": "mocha --require source-map-support/register --require make-promises-safe lib/test/**/*_test.js --bail --exit",
+ "test:circleci": "yarn test:coverage",
+ "test:coverage": "nyc npm run test --all && yarn coverage:report:lcov",
+ "coverage:report:lcov": "nyc report --reporter=text-lcov > coverage/lcov.info",
"manual:postpublish": "yarn build; node ./scripts/postpublish.js"
},
"bin": {
@@ -31,11 +36,13 @@
"@0xproject/utils": "^0.7.2",
"ethereum-types": "^0.0.2",
"chalk": "^2.3.0",
+ "ethereum-types": "^0.0.2",
"glob": "^7.1.2",
"handlebars": "^4.0.11",
"lodash": "^4.17.4",
- "rimraf": "^2.6.2",
"mkdirp": "^0.5.1",
+ "sleep": "^5.1.1",
+ "tmp": "^0.0.33",
"to-snake-case": "^1.0.0",
"yargs": "^10.0.3"
},
@@ -43,13 +50,17 @@
"@0xproject/monorepo-scripts": "^0.2.2",
"@0xproject/tslint-config": "^0.4.21",
"@types/glob": "5.0.35",
- "@types/rimraf": "^2.0.2",
"@types/handlebars": "^4.0.36",
"@types/mkdirp": "^0.5.1",
"@types/node": "^8.0.53",
+ "@types/sleep": "^0.0.7",
+ "@types/tmp": "^0.0.33",
"@types/yargs": "^10.0.0",
+ "chai": "^4.1.2",
"copyfiles": "^1.2.0",
+ "dirty-chai": "^2.0.1",
"make-promises-safe": "^1.1.0",
+ "mocha": "^5.2.0",
"npm-run-all": "^4.1.2",
"shx": "^0.2.2",
"tslint": "5.8.0",
diff --git a/packages/abi-gen/src/index.ts b/packages/abi-gen/src/index.ts
index 47f2c404b..753bb2cce 100644
--- a/packages/abi-gen/src/index.ts
+++ b/packages/abi-gen/src/index.ts
@@ -3,16 +3,12 @@
import { abiUtils, logUtils } from '@0xproject/utils';
import chalk from 'chalk';
import { AbiDefinition, ConstructorAbi, EventAbi, MethodAbi } from 'ethereum-types';
-import * as fs from 'fs';
import { sync as globSync } from 'glob';
import * as Handlebars from 'handlebars';
import * as _ from 'lodash';
import * as mkdirp from 'mkdirp';
-import * as rimraf from 'rimraf';
import * as yargs from 'yargs';
-import toSnakeCase = require('to-snake-case');
-
import { ContextData, ContractsBackend, ParamKind } from './types';
import { utils } from './utils';
@@ -71,16 +67,6 @@ function registerPartials(partialsGlob: string): void {
}
}
-function writeOutputFile(name: string, renderedTsCode: string): void {
- let fileName = toSnakeCase(name);
- // HACK: Snake case doesn't make a lot of sense for abbreviated names but we can't reliably detect abbreviations
- // so we special-case the abbreviations we use.
- fileName = fileName.replace('z_r_x', 'zrx').replace('e_r_c', 'erc');
- const filePath = `${args.output}/${fileName}.ts`;
- fs.writeFileSync(filePath, renderedTsCode);
- logUtils.log(`Created: ${chalk.bold(filePath)}`);
-}
-
Handlebars.registerHelper('parameterType', utils.solTypeToTsType.bind(utils, ParamKind.Input, args.backend));
Handlebars.registerHelper('returnType', utils.solTypeToTsType.bind(utils, ParamKind.Output, args.backend));
if (args.partials) {
@@ -97,7 +83,6 @@ if (_.isEmpty(abiFileNames)) {
process.exit(1);
} else {
logUtils.log(`Found ${chalk.green(`${abiFileNames.length}`)} ${chalk.bold('ABI')} files`);
- rimraf.sync(args.output);
mkdirp.sync(args.output);
}
for (const abiFileName of abiFileNames) {
@@ -120,6 +105,14 @@ for (const abiFileName of abiFileNames) {
process.exit(1);
}
+ const outFileName = utils.makeOutputFileName(namedContent.name);
+ const outFilePath = `${args.output}/${outFileName}.ts`;
+
+ if (utils.isOutputFileUpToDate(abiFileName, outFilePath)) {
+ logUtils.log(`Aready up to date: ${chalk.bold(outFilePath)}`);
+ continue;
+ }
+
let ctor = ABI.find((abi: AbiDefinition) => abi.type === ABI_TYPE_CONSTRUCTOR) as ConstructorAbi;
if (_.isUndefined(ctor)) {
ctor = utils.getEmptyConstructor(); // The constructor exists, but it's implicit in JSON's ABI definition
@@ -154,5 +147,6 @@ for (const abiFileName of abiFileNames) {
events: eventAbis,
};
const renderedTsCode = template(contextData);
- writeOutputFile(namedContent.name, renderedTsCode);
+ utils.writeOutputFile(outFilePath, renderedTsCode);
+ logUtils.log(`Created: ${chalk.bold(outFilePath)}`);
}
diff --git a/packages/abi-gen/src/utils.ts b/packages/abi-gen/src/utils.ts
index 66390174c..56b996ce3 100644
--- a/packages/abi-gen/src/utils.ts
+++ b/packages/abi-gen/src/utils.ts
@@ -2,6 +2,7 @@ import { AbiType, ConstructorAbi, DataItem } from 'ethereum-types';
import * as fs from 'fs';
import * as _ from 'lodash';
import * as path from 'path';
+import toSnakeCase = require('to-snake-case');
import { ContractsBackend, ParamKind } from './types';
@@ -92,4 +93,27 @@ export const utils = {
inputs: [],
};
},
+ makeOutputFileName(name: string): string {
+ let fileName = toSnakeCase(name);
+ // HACK: Snake case doesn't make a lot of sense for abbreviated names but we can't reliably detect abbreviations
+ // so we special-case the abbreviations we use.
+ fileName = fileName.replace('z_r_x', 'zrx').replace('e_r_c', 'erc');
+ return fileName;
+ },
+ writeOutputFile(filePath: string, renderedTsCode: string): void {
+ fs.writeFileSync(filePath, renderedTsCode);
+ },
+ isOutputFileUpToDate(abiFile: string, outputFile: string): boolean {
+ const abiFileModTimeMs = fs.statSync(abiFile).mtimeMs;
+ try {
+ const outFileModTimeMs = fs.statSync(outputFile).mtimeMs;
+ return outFileModTimeMs > abiFileModTimeMs;
+ } catch (err) {
+ if (err.code === 'ENOENT') {
+ return false;
+ } else {
+ throw err;
+ }
+ }
+ },
};
diff --git a/packages/abi-gen/test/utils_test.ts b/packages/abi-gen/test/utils_test.ts
new file mode 100644
index 000000000..c6147df38
--- /dev/null
+++ b/packages/abi-gen/test/utils_test.ts
@@ -0,0 +1,86 @@
+import * as chai from 'chai';
+import * as dirtyChai from 'dirty-chai';
+import * as fs from 'fs';
+import 'mocha';
+import * as sleep from 'sleep';
+import * as tmp from 'tmp';
+
+import { utils } from '../src/utils';
+
+tmp.setGracefulCleanup(); // remove tmp files even if there are failures
+
+chai.use(dirtyChai);
+
+const expect = chai.expect;
+
+const SLEEP_MS = 10; // time to wait before re-timestamping a file
+
+describe('makeOutputFileName()', () => {
+ it('should handle Metacoin usage', () => {
+ expect(utils.makeOutputFileName('Metacoin')).to.equal('metacoin');
+ });
+ it('should handle special zrx_token case', () => {
+ expect(utils.makeOutputFileName('ZRXToken')).to.equal('zrx_token');
+ });
+ it('should handle special erc_token case', () => {
+ expect(utils.makeOutputFileName('ERC20Token')).to.equal('erc20_token');
+ });
+});
+
+describe('writeOutputFile()', () => {
+ let tempFilePath: string;
+ before(() => {
+ tempFilePath = tmp.fileSync(
+ { discardDescriptor: true }, // close file (so we can update it)
+ ).name;
+ });
+ it('should write content to output file', () => {
+ const content = 'hello world';
+
+ utils.writeOutputFile(tempFilePath, content);
+
+ expect(fs.readFileSync(tempFilePath).toString()).to.equal(content);
+ });
+});
+
+describe('isOutputFileUpToDate()', () => {
+ it('should throw ENOENT when there is no abi file', () => {
+ expect(utils.isOutputFileUpToDate.bind('nonexistant1', 'nonexistant2')).to.throw('ENOENT');
+ });
+
+ describe('when the abi input file exists', () => {
+ let abiFile: string;
+ before(() => {
+ abiFile = tmp.fileSync(
+ { discardDescriptor: true }, // close file (set timestamp)
+ ).name;
+ });
+
+ describe('without an existing output file', () => {
+ it('should return false', () => {
+ expect(utils.isOutputFileUpToDate(abiFile, 'nonexistant_file')).to.be.false();
+ });
+ });
+
+ describe('with an existing output file', () => {
+ let outputFile: string;
+ before(() => {
+ sleep.msleep(SLEEP_MS); // to ensure different timestamp
+ outputFile = tmp.fileSync(
+ { discardDescriptor: true }, // close file (set timestamp)
+ ).name;
+ });
+
+ it('should return true when output file and is newer than abi file', async () => {
+ expect(utils.isOutputFileUpToDate(abiFile, outputFile)).to.be.true();
+ });
+
+ it('should return false when output file exists but is older than abi file', () => {
+ sleep.msleep(SLEEP_MS); // to ensure different timestamp
+ fs.closeSync(fs.openSync(abiFile, 'w')); // touch abi file
+
+ expect(utils.isOutputFileUpToDate(abiFile, outputFile)).to.be.false();
+ });
+ });
+ });
+});