Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow specification of custom artifact glob in @typechain/hardhat #735

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
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
23 changes: 16 additions & 7 deletions packages/hardhat/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,18 @@ hardhat typechain # always regenerates typings to all files

## Configuration

This plugin extends the `hardhatConfig` optional `typechain` object. The object contains two fields, `outDir` and
`target`. `outDir` is the output directory of the artifacts that TypeChain creates (defaults to `typechain`). `target`
is one of the targets specified by the TypeChain [docs](https://github.com/ethereum-ts/TypeChain#cli) (defaults to
`ethers`).
This plugin extends the `hardhatConfig` optional `typechain` object. The object contains the following (optional)
fields:

- `target`: one of the targets specified by the TypeChain [docs](https://github.com/ethereum-ts/TypeChain#cli) (defaults
to `ethers`)
- `outDir`: the output directory of the artifacts that TypeChain creates (defaults to `typechain`).
- `artifacts`: glob pattern that defines for which build artifacts to generate types (defaults to all artifacts
generated in compile step)
- `alwaysGenerateOverloads`: some targets won't generate unnecessary types for overloaded functions by default, this
option forces to always generate them
- `externalArtifacts`: array of glob patterns with external artifacts to process (e.g external libs from `node_modules`)
- `dontOverrideCompile`: boolean disabling automatic inclusion of type generation in compile task.

This is an example of how to set it:

Expand All @@ -102,9 +110,10 @@ module.exports = {
typechain: {
outDir: 'src/types',
target: 'ethers-v5',
alwaysGenerateOverloads: false, // should overloads with full signatures like deposit(uint256) be generated always, even if there are no overloads?
externalArtifacts: ['externalArtifacts/*.json'], // optional array of glob patterns with external artifacts to process (for example external libs from node_modules)
dontOverrideCompile: false // defaults to false
artifacts: '**/TestContract.json',
alwaysGenerateOverloads: false,
externalArtifacts: ['externalArtifacts/*.json'],
dontOverrideCompile: false,
},
}
```
Expand Down
1 change: 1 addition & 0 deletions packages/hardhat/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export function getDefaultTypechainConfig(config: HardhatConfig): TypechainConfi
const defaultConfig: TypechainConfig = {
outDir: 'typechain-types',
target: 'ethers-v5',
artifacts: `${config.paths.artifacts}/!(build-info)/**/+([a-zA-Z0-9_]).json`,
alwaysGenerateOverloads: false,
discriminateTypes: false,
tsNocheck: false,
Expand Down
49 changes: 31 additions & 18 deletions packages/hardhat/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ import { glob, PublicConfig as RunTypeChainConfig, runTypeChain } from 'typechai
import { getDefaultTypechainConfig } from './config'
import { TASK_TYPECHAIN, TASK_TYPECHAIN_GENERATE_TYPES } from './constants'

function intersect<T>(a: Array<T>, b: Array<T>): Array<T> {
var setA = new Set(a)
var setB = new Set(b)
var intersection = new Set([...setA].filter((x) => setB.has(x)))
return Array.from(intersection)
}

const taskArgsStore: { noTypechain: boolean; fullRebuild: boolean } = { noTypechain: false, fullRebuild: false }

extendConfig((config) => {
Expand Down Expand Up @@ -38,15 +45,28 @@ subtask(TASK_TYPECHAIN_GENERATE_TYPES)
.addFlag('quiet', 'Makes the process less verbose')
.setAction(async ({ compileSolOutput, quiet }, { config, artifacts }) => {
const artifactFQNs: string[] = getFQNamesFromCompilationOutput(compileSolOutput)
const artifactPaths = uniq(artifactFQNs.map((fqn) => artifacts.formArtifactPathFromFullyQualifiedName(fqn)))
let artifactPaths = uniq(artifactFQNs.map((fqn) => artifacts.formArtifactPathFromFullyQualifiedName(fqn)))

if (taskArgsStore.noTypechain) {
return compileSolOutput
}

// RUN TYPECHAIN TASK
const typechainCfg = config.typechain
if (artifactPaths.length === 0 && !taskArgsStore.fullRebuild && !typechainCfg.externalArtifacts) {

const cwd = config.paths.root
const allFiles = glob(cwd, [typechainCfg.artifacts])
if (typechainCfg.externalArtifacts) {
allFiles.push(...glob(cwd, typechainCfg.externalArtifacts, false))
}

// incremental generation is only supported in 'ethers-v5'
// @todo: probably targets should specify somehow if then support incremental generation this won't work with custom targets
const needsFullRebuild = taskArgsStore.fullRebuild || typechainCfg.target !== 'ethers-v5'
artifactPaths = intersect(allFiles, artifactPaths)
const filesToProcess = needsFullRebuild ? allFiles : glob(cwd, artifactPaths)

if (filesToProcess.length === 0 && !typechainCfg.externalArtifacts) {
if (!quiet) {
// eslint-disable-next-line no-console
console.log('No need to generate any newer typings.')
Expand All @@ -55,21 +75,12 @@ subtask(TASK_TYPECHAIN_GENERATE_TYPES)
return compileSolOutput
}

// incremental generation is only supported in 'ethers-v5'
// @todo: probably targets should specify somehow if then support incremental generation this won't work with custom targets
const needsFullRebuild = taskArgsStore.fullRebuild || typechainCfg.target !== 'ethers-v5'
if (!quiet) {
// eslint-disable-next-line no-console
console.log(
`Generating typings for: ${artifactPaths.length} artifacts in dir: ${typechainCfg.outDir} for target: ${typechainCfg.target}`,
)
}
const cwd = config.paths.root

const allFiles = glob(cwd, [`${config.paths.artifacts}/!(build-info)/**/+([a-zA-Z0-9_]).json`])
if (typechainCfg.externalArtifacts) {
allFiles.push(...glob(cwd, typechainCfg.externalArtifacts, false))
}

const typechainOptions: Omit<RunTypeChainConfig, 'filesToProcess'> = {
cwd,
Expand All @@ -84,14 +95,16 @@ subtask(TASK_TYPECHAIN_GENERATE_TYPES)
},
}

const result = await runTypeChain({
...typechainOptions,
filesToProcess: needsFullRebuild ? allFiles : glob(cwd, artifactPaths), // only process changed files if not doing full rebuild
})
if (filesToProcess.length > 0) {
const result = await runTypeChain({
...typechainOptions,
filesToProcess, // only process changed files if not doing full rebuild
})

if (!quiet) {
// eslint-disable-next-line no-console
console.log(`Successfully generated ${result.filesGenerated} typings!`)
if (!quiet) {
// eslint-disable-next-line no-console
console.log(`Successfully generated ${result.filesGenerated} typings!`)
}
}

// if this is not full rebuilding, always re-generate types for external artifacts
Expand Down
1 change: 1 addition & 0 deletions packages/hardhat/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export interface TypechainConfig {
outDir: string
target: string
artifacts: string
alwaysGenerateOverloads: boolean
discriminateTypes: boolean
tsNocheck: boolean
Expand Down
80 changes: 80 additions & 0 deletions packages/hardhat/test/project.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,86 @@ describe('Typechain x Hardhat', function () {
expect(consoleLogMock).toHaveBeenCalledWith(['Successfully generated 14 typings for external artifacts!'])
})
})

describe('when setting custom artifact glob', () => {
let oldArtifactGlob: string
beforeEach(function () {
oldArtifactGlob = this.hre.config.typechain.artifacts
})
afterEach(function () {
this.hre.config.typechain.artifacts = oldArtifactGlob
})
;[true, false].forEach((forcedCompilation) => {
describe(`when type generation is ${forcedCompilation ? '' : 'not'} forced`, () => {
let subject: () => Promise<void>
beforeEach(async function () {
if (forcedCompilation) {
await this.hre.run('compile', { noTypechain: true })
}
subject = () => {
if (forcedCompilation) {
return this.hre.run('typechain')
} else {
return this.hre.run('compile')
}
}
})

describe('when glob matches some files', () => {
beforeEach(function () {
this.hre.config.typechain.artifacts = '**/EdgeCases.json'
})

it('includes build artifacts that match the glob', async function () {
const exists = existsSync(this.hre.config.typechain.outDir)
expect(exists).toEqual(false)

await subject()

const dir = await readdir(this.hre.config.typechain.outDir)
expect(dir.includes('EdgeCases.ts')).toEqual(true)
})

it('excludes build artifacts that do not match the glob', async function () {
const exists = existsSync(this.hre.config.typechain.outDir)
expect(exists).toEqual(false)

await subject()

const dir = await readdir(this.hre.config.typechain.outDir)
expect(dir.includes('TestContract.ts')).toEqual(false)
expect(dir.includes('TestContract1.ts')).toEqual(false)
})
})
describe('when glob matches no files', () => {
beforeEach(function () {
this.hre.config.typechain.artifacts = '**/THISDOESNTMATCHANYTHING.json'
})

describe('when no external artifacts are specified', () => {
it('does not generate any types', async function () {
const exists = existsSync(this.hre.config.typechain.outDir)
expect(exists).toEqual(false)

await subject()
expect(existsSync(this.hre.config.typechain.outDir)).toEqual(false)
})
})

describe('when external artifacts are specified', () => {
it('only generates types for external artifacts', async function () {
const exists = existsSync(this.hre.config.typechain.outDir)
expect(exists).toEqual(false)

this.hre.config.typechain.externalArtifacts = ['externalArtifacts/*.json']
await subject()
expect(existsSync(this.hre.config.typechain.outDir)).toEqual(true)
})
})
})
})
})
})
})

describe('dontOverrideCompile', function () {
Expand Down