Skip to content

Commit

Permalink
Allow multiple watcher tasks to be defined.
Browse files Browse the repository at this point in the history
  • Loading branch information
xanderdeseyn committed Nov 5, 2020
1 parent b12cd85 commit 01f77f1
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 87 deletions.
20 changes: 14 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
};
```
Expand All @@ -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).
Expand Down
151 changes: 85 additions & 66 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,88 +1,107 @@
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";

extendConfig(
(config: HardhatConfig, userConfig: Readonly<HardhatUserConfig>) => {
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));
}
);
28 changes: 17 additions & 11 deletions src/type-extensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ExpandedWatcherTask>[];
export type WatcherConfig = {
[key: string]: {
tasks: Required<ExpandedHardhatTask>[];
files: string[];
verbose: boolean;
};
};

// Fully resolved config
export interface HardhatConfig {
watcher: WatcherConfig;
}
}
8 changes: 5 additions & 3 deletions test/fixture-projects/hardhat-project/hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
},
};

Expand Down
2 changes: 1 addition & 1 deletion test/project.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 =============");
Expand Down

0 comments on commit 01f77f1

Please sign in to comment.