hardhat-watcher
Version:
Hardhat file watcher plugin
142 lines (122 loc) • 4.45 kB
text/typescript
import { extendConfig, task } from 'hardhat/config'
import { HardhatConfig, HardhatUserConfig, WatcherConfig } from 'hardhat/types'
import chokidar from 'chokidar'
const { execSync } = require('child_process')
import './type-extensions'
extendConfig((config: HardhatConfig, userConfig: Readonly<HardhatUserConfig>) => {
let w = userConfig.watcher ?? {}
const normalizedWatcher: WatcherConfig = {}
Object.entries(w).forEach(([name, task]) => {
normalizedWatcher[name] = {
tasks: (task?.tasks ?? []).map(t => {
if (typeof t === 'string') {
return {
command: t,
params: {},
}
} else {
return {
command: t.command,
params: t.params ?? {},
}
}
}),
files: task.files ?? [config.paths.sources],
ignoredFiles: task.ignoredFiles ?? [],
verbose: task.verbose ?? false,
start: task.start ?? '',
clearOnStart: task.clearOnStart ?? false,
runOnLaunch: task.runOnLaunch ?? false,
}
})
config.watcher = normalizedWatcher
})
task('watch', 'Start the file watcher')
.addPositionalParam('watcherTask', 'watcher task to run (as defined in hardhat config)')
.setAction(async ({ watcherTask }, { run, tasks, config: { watcher, paths } }) => {
if (!(watcherTask in watcher)) {
console.log(`Watcher task "${watcherTask}" was not found in hardhat config.`)
process.exit(1)
}
const taskConfig = watcher[watcherTask]
const logVerbose = (...messages: any) => {
if (taskConfig.verbose) console.log(...messages)
}
logVerbose('Starting file watcher', taskConfig.files)
const templateReplace = (value: any, pattern: string, replace: string) => {
if (Array.isArray(value)) {
return value.map(v => v.replace(pattern, replace))
} else if (typeof value === 'string') {
return value.replace(pattern, replace)
} else {
return value
}
}
const paramsTemplateReplace = (params: any, pattern: string, replace: string) => {
const newParams: any = {}
Object.keys(params).forEach(k => {
newParams[k] = templateReplace(params[k], pattern, replace)
})
return newParams
}
// Validate tasks
taskConfig.tasks.forEach(task => {
if (!(task.command in tasks)) {
console.log(`Watcher error: task "${task.command}" is not supported by hardhat runtime.`)
console.log(`Found tasks: ${JSON.stringify(Object.keys(tasks))}`)
process.exit(1)
}
})
const runTasks = async (path: string) => {
// Clear on on changed files received
if (taskConfig.clearOnStart) {
console.clear()
}
if (taskConfig.start) {
try {
execSync(taskConfig.start, { stdio: 'inherit' })
} catch (error) {
console.log("Failed to execute 'start' script:", taskConfig.start)
console.error(error)
}
}
for (let i = 0; i < taskConfig.tasks.length; i++) {
const task = taskConfig.tasks[i]
// Replace template pattern with the changed file
const newParams = paramsTemplateReplace(task.params, '{path}', path)
logVerbose(`Running task "${task.command}" with params ${JSON.stringify(newParams)}`)
try {
await run(task.command, newParams)
// This hack is required to allow running Mocha commands. Check out https://github.com/mochajs/mocha/issues/1938 for more details.
Object.keys(require.cache).forEach(function (key) {
if (key.startsWith(paths.tests)) {
delete require.cache[key]
}
})
} catch (err) {
console.log(`Task "${task.command}" failed.`)
console.log(err)
}
}
}
chokidar
.watch(taskConfig.files, {
ignored: taskConfig.ignoredFiles,
ignoreInitial: true,
usePolling: true,
interval: 250,
})
.on('ready', () => {
if (taskConfig.runOnLaunch) {
console.log('Run on launch is enabled, immediately running tasks.')
runTasks('none')
}
})
.on('change', runTasks)
.on('error', (error: Error) => {
console.log(`Watcher error: ${error}`)
process.exit(1)
})
console.log('File watcher started.')
await new Promise(resolve => setTimeout(resolve, 2000000000))
})