Skip to content
This repository has been archived by the owner on Jan 24, 2022. It is now read-only.

Use require.resolve to lookup contracts in deps #1110

Merged
merged 3 commits into from
Jul 17, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"devDependencies": {
"husky": "^2.7.0",
"lerna": "~3.13.1",
"typescript": "^3.2.2"
"typescript": "^3.2.2",
"mock-stdlib-root": "file:./packages/cli/test/mocks/mock-stdlib"
},
"husky": {
"hooks": {
Expand Down
31 changes: 17 additions & 14 deletions packages/cli/src/models/dependency/Dependency.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
import ProjectFile, { LEGACY_PROJECT_FILE_NAME, PROJECT_FILE_PATH } from '../files/ProjectFile';
import NetworkFile from '../files/NetworkFile';
import { OPEN_ZEPPELIN_FOLDER } from '../files/constants';
import { dirname } from 'path';

export default class Dependency {
public name: string;
Expand Down Expand Up @@ -55,7 +56,8 @@ export default class Dependency {
const hasDependenciesForDeploy = dependencies.find(
(depNameAndVersion): any => {
const [name, version] = depNameAndVersion.split('@');
const networkFilePath = Dependency.getExistingNetworkFilePath(name, network);
const dependency = new Dependency(name);
const networkFilePath = dependency.getExistingNetworkFilePath(network);
const projectDependency = networkDependencies[name];
const satisfiesVersion = projectDependency && this.satisfiesVersion(projectDependency.version, version);
return !fs.exists(networkFilePath) && !satisfiesVersion;
Expand Down Expand Up @@ -128,11 +130,9 @@ export default class Dependency {

public get projectFile(): ProjectFile | never {
if (!this._projectFile) {
const filePath = ProjectFile.getExistingFilePath(`node_modules/${this.name}`);
const filePath = ProjectFile.getExistingFilePath(this.getDependencyFolder());
if (!filePath) {
throw new Error(
`Could not find a project.json file for '${this.name}'. Make sure it is provided by the npm package.`,
);
throw new Error(`Could not find an .openzeppelin/project.json or zos.json file for '${this.name}'`);
}
this._projectFile = new ProjectFile(filePath);
}
Expand All @@ -141,7 +141,7 @@ export default class Dependency {

public getNetworkFile(network: string): NetworkFile | never {
if (!this._networkFiles[network]) {
const filePath = Dependency.getExistingNetworkFilePath(this.name, network);
const filePath = this.getExistingNetworkFilePath(network);

if (!fs.exists(filePath)) {
throw Error(`Could not find a project file for network '${network}' for '${this.name}'`);
Expand All @@ -154,18 +154,21 @@ export default class Dependency {
}

public isDeployedOnNetwork(network: string): boolean {
const filePath = Dependency.getExistingNetworkFilePath(this.name, network);
const filePath = this.getExistingNetworkFilePath(network);
if (!fs.exists(filePath)) return false;
return !!this.getNetworkFile(network).packageAddress;
}

private static getExistingNetworkFilePath(name: string, network: string): string {
// TODO-v3: Remove legacy project file support
let legacyFilePath = `node_modules/${name}/zos.${network}.json`;
legacyFilePath = fs.exists(legacyFilePath) ? legacyFilePath : null;
let filePath = `node_modules/${this.name}/${OPEN_ZEPPELIN_FOLDER}/${network}.json`;
filePath = fs.exists(filePath) ? filePath : null;
return filePath || legacyFilePath;
private getDependencyFolder(): string {
try {
return dirname(require.resolve(`${this.name}/package.json`, { paths: [process.cwd()] }));
} catch (err) {
throw new Error(`Could not find dependency ${this.name}.`)
}
}

private getExistingNetworkFilePath(network: string): string {
return NetworkFile.getExistingFilePath(network, this.getDependencyFolder());
}

private validateSatisfiesVersion(version: string, requirement: string | semver.Range): void | never {
Expand Down
115 changes: 57 additions & 58 deletions packages/cli/test/models/Dependency.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,6 @@ import ProjectFile from '../../src/models/files/ProjectFile';
import NetworkFile from '../../src/models/files/NetworkFile';

contract('Dependency', function([_, from]) {
const assertErrorMessage = (fn, errorMessage) => {
try {
fn();
} catch (error) {
error.message.should.match(errorMessage);
}
};

describe('static methods', function() {
describe('#satisfiesVersion', function() {
it('verifies if requirement satisfies version', function() {
Expand All @@ -29,10 +21,9 @@ contract('Dependency', function([_, from]) {
describe('#fromNameAndVersion', function() {
describe('with invalid nameAndVersion', function() {
it('throws error', function() {
assertErrorMessage(
() => Dependency.fromNameWithVersion('bildts-kcom'),
/Could not find a project.json file/,
);
expect(
() => Dependency.fromNameWithVersion('bildts-kcom')
).to.throw(/Could not find dependency bildts-kcom/);
});
});

Expand Down Expand Up @@ -114,19 +105,25 @@ contract('Dependency', function([_, from]) {
describe('#constructor', function() {
context('with invalid version', function() {
it('throws an error', function() {
assertErrorMessage(
() => new Dependency('mock-stdlib', '1.2.0'),
/does not match version/,
);
expect(
() => new Dependency('mock-stdlib', '1.2.0')
).to.throw(/does not match version/);
});
});

context('with non-existent dependency name', function() {
it('throws an error', function() {
assertErrorMessage(
() => new Dependency('bildts-kcom', '1.1.0'),
/Could not find a project.json file/,
);
expect(
() => new Dependency('bildts-kcom', '1.1.0')
).to.throw(/Could not find dependency bildts-kcom/);
});
});

context('with non-ethereum package', function() {
it('throws an error', function() {
expect(
() => new Dependency('chai')
).to.throw(/Could not find an \.openzeppelin\/project\.json/);
});
});

Expand All @@ -147,52 +144,54 @@ contract('Dependency', function([_, from]) {
});
});

describe('instance methods', function() {
beforeEach(function() {
this.dependency = new Dependency('mock-stdlib', '1.1.0');
this.txParams = {};
this.addresses = {};
delete this.dependency._projectFile;
});

describe('#deploy', function() {
it('deploys a dependency', async function() {
const project = await this.dependency.deploy({ from });
const address = await project.getImplementation({
contractName: 'Greeter',
function testInstanceMethodsFor(libname) {
describe(`instance methods for ${libname}`, function() {
beforeEach(function() {
this.dependency = new Dependency(libname, '1.1.0');
this.txParams = {};
this.addresses = {};
delete this.dependency._projectFile;
});

describe('#deploy', function() {
it('deploys a dependency', async function() {
const project = await this.dependency.deploy({ from });
const address = await project.getImplementation({
contractName: 'Greeter',
});
address.should.be.nonzeroAddress;
});
address.should.be.nonzeroAddress;
});
});

describe('#projectFile', function() {
it('generates a package file', function() {
const projectFile = this.dependency.projectFile;
projectFile.should.not.be.null;
projectFile.filePath.should.eq('node_modules/mock-stdlib/zos.json');
projectFile.version.should.eq('1.1.0');
projectFile.contracts.should.include({ Greeter: 'GreeterImpl' });
describe('#projectFile', function() {
it('generates a package file', function() {
const projectFile = this.dependency.projectFile;
projectFile.should.not.be.null;
projectFile.filePath.should.match(/mock-stdlib\/zos\.json$/);
projectFile.version.should.eq('1.1.0');
projectFile.contracts.should.include({ Greeter: 'GreeterImpl' });
});
});
});

describe('#getNetworkFile', function() {
context('for a non-existent network', function() {
it('throws an error', function() {
assertErrorMessage(
() => this.dependency.getNetworkFile('bildts-kcom'),
/Could not find a project file for network/,
);
describe('#getNetworkFile', function() {
context('for a non-existent network', function() {
it('throws an error', function() {
expect(
() => this.dependency.getNetworkFile('bildts-kcom')
).to.throw(/Could not find a project file for network/);
});
});
});

context('for an existent network', function() {
it('generates network file', function() {
const networkFile = this.dependency.getNetworkFile('test');
networkFile.filePath.should.eq(
'node_modules/mock-stdlib/zos.test.json',
);
context('for an existent network', function() {
it('generates network file', function() {
const networkFile = this.dependency.getNetworkFile('test');
networkFile.filePath.should.match(/mock-stdlib\/zos\.test\.json$/);
});
});
});
});
});
}

testInstanceMethodsFor('mock-stdlib');
testInstanceMethodsFor('mock-stdlib-root');
});
2 changes: 1 addition & 1 deletion packages/cli/test/scripts/unlink.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ contract('unlink script', function() {
dependencies: [dependencyName],
projectFile: this.projectFile,
}).should.be.rejectedWith(
`Could not find a project.json file for '${dependencyName}'. Make sure it is provided by the npm package.`,
`Could not find dependency ${dependencyName}.`,
);
});
});
Expand Down
11 changes: 10 additions & 1 deletion packages/lib/src/artifacts/Contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export default class Contracts {
private static timeout: number = Contracts.DEFAULT_SYNC_TIMEOUT;
private static buildDir: string = Contracts.DEFAULT_BUILD_DIR;
private static contractsDir: string = Contracts.DEFAULT_CONTRACTS_DIR;
private static projectRoot: string = null;
private static artifactDefaults: any = {};
private static defaultFromAddress: string;

Expand All @@ -27,6 +28,10 @@ export default class Contracts {
return path.resolve(Contracts.contractsDir || Contracts.DEFAULT_CONTRACTS_DIR);
}

public static getProjectRoot(): string {
return path.resolve(this.projectRoot || process.cwd());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Nice one!

}

public static async getDefaultTxParams(): Promise<any> {
const defaults = { ...Contracts.getArtifactsDefaults() };
if (!defaults.from) defaults.from = await Contracts.getDefaultFromAddress();
Expand All @@ -46,7 +51,7 @@ export default class Contracts {
}

public static getNodeModulesPath(dependency: string, contractName: string): string {
return `${process.cwd()}/node_modules/${dependency}/build/contracts/${contractName}.json`;
return require.resolve(`${dependency}/build/contracts/${contractName}.json`, { paths: [this.getProjectRoot()] });
}

public static getFromLocal(contractName: string): Contract {
Expand Down Expand Up @@ -85,6 +90,10 @@ export default class Contracts {
Contracts.contractsDir = dir;
}

public static setProjectRoot(dir: string): void {
Contracts.projectRoot = dir;
}

public static setArtifactsDefaults(defaults: any): void {
Contracts.artifactDefaults = {
...Contracts.getArtifactsDefaults(),
Expand Down
6 changes: 6 additions & 0 deletions packages/lib/test/src/artifacts/Contracts.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ contract('Contracts', function() {
instance.address.should.not.be.null;
});

it('can lookup contracts from hoisted node modules', async function() {
const GreeterLib = Contracts.getFromNodeModules('mock-stdlib-root', 'GreeterLib');
const instance = await GreeterLib.new();
instance.address.should.not.be.null;
});

describe('configuration', function() {
it('has some default configuration', function() {
Contracts.getSyncTimeout().should.be.eq(240000);
Expand Down