diff --git a/README.md b/README.md index 38e0c46..2ac02e9 100644 --- a/README.md +++ b/README.md @@ -43,9 +43,11 @@ This is the complete type: ```js module.exports = { watcher: { - tasks?: (string | { command: string, params?: { [key: string] => any } })[]; // Every task of the hardhat runtime is supported (including other plugins!) - files?: string[]; // Files, directories or glob patterns to watch for changes. (defaults to `[config.paths.sources]`, which itself defaults to the `contracts` dir) - verbose?: boolean; // Turn on for extra logging + [key: string]: { // key is the name for the watcherTask + tasks?: (string | { command: string, params?: { [key: string] => any } })[]; // Every task of the hardhat runtime is supported (including other plugins!) + files?: string[]; // Files, directories or glob patterns to watch for changes. (defaults to `[config.paths.sources]`, which itself defaults to the `contracts` dir) + verbose?: boolean; // Turn on for extra logging + } } }; ``` @@ -57,23 +59,29 @@ The most basic use case, which is simply compiling your files on change, is acco ```js module.exports = { watcher: { - tasks: ["compile"], + compilation: { + tasks: ["compile"], + } }, } ``` -and subsequently running `npx hardhat watch` +and subsequently running `npx hardhat watch compilation` A bit more involved and showcasing the use of parameters for tasks: ```js module.exports = { watcher: { - tasks: ["clean", { command: "compile", params: { quiet: true } }, { command: "test", params: { noCompile: true, testFiles: ["testfile.ts"] } } ], + ci: { + tasks: ["clean", { command: "compile", params: { quiet: true } }, { command: "test", params: { noCompile: true, testFiles: ["testfile.ts"] } } ], + } }, } ``` +Run with `npx hardhat watch ci` to clean, compile and test on every file change. + ### Positional arguments Positional arguments are provided in the same way as "normal" arguments (check out the `testFiles` argument in the example above, it's a positional argument). diff --git a/src/index.ts b/src/index.ts index d38d16e..1f695f7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ import { extendConfig, task } from "hardhat/config"; -import { HardhatConfig, HardhatUserConfig } from "hardhat/types"; +import { HardhatConfig, HardhatUserConfig, WatcherConfig } from "hardhat/types"; import chokidar from "chokidar"; import "./type-extensions"; @@ -7,82 +7,101 @@ import "./type-extensions"; extendConfig( (config: HardhatConfig, userConfig: Readonly) => { let w = userConfig.watcher ?? {}; - const tasks = w.tasks ?? []; - config.watcher = { - tasks: tasks.map((t) => { - if (typeof t === "string") { - return { - command: t, - params: {}, - }; - } else { - return { - command: t.command, - params: t.params ?? {}, - }; - } - }), - files: w.files ?? [config.paths.sources], - verbose: w.verbose ?? false, - }; - } -); + const normalizedWatcher: WatcherConfig = {}; -task("watch", "Start the file watcher").setAction( - async ({}, { run, tasks, config: { watcher, paths } }) => { - const logVerbose = (...messages: any) => { - if (watcher.verbose) console.log(...messages); - }; + 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], + verbose: task.verbose ?? false, + }; + }); - logVerbose("Starting file watcher", watcher.files); + config.watcher = normalizedWatcher; + } +); - // Validate tasks - watcher.tasks.forEach((task) => { - if (!(task.command in tasks)) { +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 error: task "${task.command}" is not supported by hardhat runtime.` + `Watcher task "${watcherTask}" was not found in hardhat config.` ); - console.log(`Found tasks: ${JSON.stringify(Object.keys(tasks))}`); process.exit(1); } - }); - chokidar - .watch(watcher.files, { - ignoreInitial: true, - usePolling: true, - interval: 250, - }) - .on("change", async () => { - for (let i = 0; i < watcher.tasks.length; i++) { - const task = watcher.tasks[i]; - logVerbose( - `Running task "${task.command}" with params ${JSON.stringify( - task.params - )}` + const taskConfig = watcher[watcherTask]; + + const logVerbose = (...messages: any) => { + if (taskConfig.verbose) console.log(...messages); + }; + + logVerbose("Starting file watcher", taskConfig.files); + + // Validate tasks + taskConfig.tasks.forEach((task) => { + if (!(task.command in tasks)) { + console.log( + `Watcher error: task "${task.command}" is not supported by hardhat runtime.` ); - try { - await run(task.command, task.params); - // 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); - } + console.log(`Found tasks: ${JSON.stringify(Object.keys(tasks))}`); + process.exit(1); } - }) - .on("error", (error: Error) => { - console.log(`Watcher error: ${error}`); - process.exit(1); }); - console.log("File watcher started."); + chokidar + .watch(taskConfig.files, { + ignoreInitial: true, + usePolling: true, + interval: 250, + }) + .on("change", async () => { + for (let i = 0; i < taskConfig.tasks.length; i++) { + const task = taskConfig.tasks[i]; + logVerbose( + `Running task "${task.command}" with params ${JSON.stringify( + task.params + )}` + ); + try { + await run(task.command, task.params); + // 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); + } + } + }) + .on("error", (error: Error) => { + console.log(`Watcher error: ${error}`); + process.exit(1); + }); - await new Promise((resolve) => setTimeout(resolve, 2000000000)); - } -); + console.log("File watcher started."); + + await new Promise((resolve) => setTimeout(resolve, 2000000000)); + } + ); diff --git a/src/type-extensions.ts b/src/type-extensions.ts index 86248f3..64624fb 100644 --- a/src/type-extensions.ts +++ b/src/type-extensions.ts @@ -2,30 +2,36 @@ import "hardhat/types/config"; import "hardhat/types/runtime"; declare module "hardhat/types/config" { - export type WatcherTask = string | ExpandedWatcherTask; + export type HardhatTask = string | ExpandedHardhatTask; - export type ExpandedWatcherTask = { + export type ExpandedHardhatTask = { command: string; params?: { [key: string]: any; }; }; + export type WatcherTask = { + tasks?: HardhatTask[]; + files?: string[]; + verbose?: boolean; + }; + // User facing config export interface HardhatUserConfig { - watcher?: { - tasks?: WatcherTask[]; - files?: string[]; - verbose?: boolean; - }; + watcher?: { [key: string]: WatcherTask }; } - // Fully resolved config - export interface HardhatConfig { - watcher: { - tasks: Required[]; + export type WatcherConfig = { + [key: string]: { + tasks: Required[]; files: string[]; verbose: boolean; }; + }; + + // Fully resolved config + export interface HardhatConfig { + watcher: WatcherConfig; } } diff --git a/test/fixture-projects/hardhat-project/hardhat.config.ts b/test/fixture-projects/hardhat-project/hardhat.config.ts index 7d3aca2..b3a58c3 100644 --- a/test/fixture-projects/hardhat-project/hardhat.config.ts +++ b/test/fixture-projects/hardhat-project/hardhat.config.ts @@ -6,9 +6,11 @@ const config: HardhatUserConfig = { solidity: "0.7.3", defaultNetwork: "hardhat", watcher: { - tasks: ["clean", { command: "compile", params: { quiet: true } }], - files: ["./contracts"], - verbose: true, + compilation: { + tasks: ["clean", { command: "compile", params: { quiet: true } }], + files: ["./contracts"], + verbose: true, + }, }, }; diff --git a/test/project.test.ts b/test/project.test.ts index c0d1011..58a23fe 100644 --- a/test/project.test.ts +++ b/test/project.test.ts @@ -19,7 +19,7 @@ describe("Watcher tests", function () { it("Watch contracts and clean + compile on change", async function () { try { simulateFileChange("Contract.sol"); - this.hre.run("watch"); + this.hre.run("watch", { watcherTask: "compilation" }); await sleep(1000); console.log("=========== Simulating file change =============");