Skip to content

Commit

Permalink
feat: CLI event hook flags (#4457)
Browse files Browse the repository at this point in the history
* added error handling for execSync

* now get hooks from config, abstracted into own file, with typings added

* CLI only

* removing an unintended import in types.d.ts

* add new flags to cli/help.md

* added some docs

* update docs

* added a cli test for watch-event-hooks

* scaffold test, need to figure out how to actually write it now

* need guidance

* tests passing now with change for child.stdout -> process.stderr

* fixed test abortOnStderr

Co-authored-by: Harris Miller <hmiller@alteryx.com>
  • Loading branch information
Harris-Miller and Harris Miller committed May 5, 2022
1 parent f44a3a3 commit 0c75914
Show file tree
Hide file tree
Showing 13 changed files with 149 additions and 0 deletions.
5 changes: 5 additions & 0 deletions cli/help.md
Expand Up @@ -73,6 +73,11 @@ Basic options:
--watch.skipWrite Do not write files to disk when watching
--watch.exclude <files> Exclude files from being watched
--watch.include <files> Limit watching to specified files
--watch.onStart <cmd> Shell command to run on `"START"` event
--watch.onBundleStart <cmd> Shell command to run on `"BUNDLE_START"` event
--watch.onBundleEnd <cmd> Shell command to run on `"BUNDLE_END"` event
--watch.onEnd <cmd> Shell command to run on `"END"` event
--watch.onError <cmd> Shell command to run on `"ERROR"` event
--validate Validate output

Examples:
Expand Down
8 changes: 8 additions & 0 deletions cli/run/watch-cli.ts
Expand Up @@ -15,6 +15,7 @@ import loadAndParseConfigFile from './loadConfigFile';
import loadConfigFromCommand from './loadConfigFromCommand';
import { getResetScreen } from './resetScreen';
import { printTimings } from './timings';
import { createWatchHooks } from './watchHooks';

export async function watch(command: Record<string, any>): Promise<void> {
process.env.ROLLUP_WATCH = 'true';
Expand All @@ -24,6 +25,7 @@ export async function watch(command: Record<string, any>): Promise<void> {
let configWatcher: FSWatcher;
let resetScreen: (heading: string) => void;
const configFile = command.config ? await getConfigPath(command.config) : null;
const runWatchHook = createWatchHooks(command);

onExit(close);
process.on('uncaughtException', close);
Expand Down Expand Up @@ -84,6 +86,7 @@ export async function watch(command: Record<string, any>): Promise<void> {
case 'ERROR':
warnings.flush();
handleError(event.error, true);
runWatchHook('onError');
break;

case 'START':
Expand All @@ -93,6 +96,8 @@ export async function watch(command: Record<string, any>): Promise<void> {
}
resetScreen(underline(`rollup v${rollup.VERSION}`));
}
runWatchHook('onStart');

break;

case 'BUNDLE_START':
Expand All @@ -107,6 +112,7 @@ export async function watch(command: Record<string, any>): Promise<void> {
cyan(`bundles ${bold(input)}${bold(event.output.map(relativeId).join(', '))}...`)
);
}
runWatchHook('onBundleStart');
break;

case 'BUNDLE_END':
Expand All @@ -119,12 +125,14 @@ export async function watch(command: Record<string, any>): Promise<void> {
)}`
)
);
runWatchHook('onBundleEnd');
if (event.result && event.result.getTimings) {
printTimings(event.result.getTimings());
}
break;

case 'END':
runWatchHook('onEnd');
if (!silent && isTTY) {
stderr(`\n[${dateTime()}] waiting for changes...`);
}
Expand Down
36 changes: 36 additions & 0 deletions cli/run/watchHooks.ts
@@ -0,0 +1,36 @@
import { execSync } from 'child_process';
import type { RollupWatchHooks } from '../../src/rollup/types';
import { bold, cyan } from '../../src/utils/colors';
import { stderr } from '../logging';

function extractWatchHooks(
command: Record<string, any>
): Partial<Record<RollupWatchHooks, string>> {
if (!Array.isArray(command.watch)) return {};

return command.watch
.filter(value => typeof value === 'object')
.reduce((acc, keyValueOption) => ({ ...acc, ...keyValueOption }), {});
}

export function createWatchHooks(command: Record<string, any>): (hook: RollupWatchHooks) => void {
const watchHooks = extractWatchHooks(command);

return function (hook: RollupWatchHooks): void {
if (watchHooks[hook]) {
const cmd = watchHooks[hook]!;

if (!command.silent) {
stderr(cyan(`watch.${hook} ${bold(`$ ${cmd}`)}`));
}

try {
// !! important - use stderr for all writes from execSync
const stdio = [process.stdin, process.stderr, process.stderr];
execSync(cmd, { stdio: command.silent ? 'ignore' : stdio });
} catch (e) {
stderr((e as Error).message);
}
}
};
}
13 changes: 13 additions & 0 deletions docs/01-command-line-reference.md
Expand Up @@ -379,6 +379,11 @@ Many options have command line equivalents. In those cases, any arguments passed
--watch.skipWrite Do not write files to disk when watching
--watch.exclude <files> Exclude files from being watched
--watch.include <files> Limit watching to specified files
--watch.onStart <cmd> Shell command to run on `"START"` event
--watch.onBundleStart <cmd> Shell command to run on `"BUNDLE_START"` event
--watch.onBundleEnd <cmd> Shell command to run on `"BUNDLE_END"` event
--watch.onEnd <cmd> Shell command to run on `"END"` event
--watch.onError <cmd> Shell command to run on `"ERROR"` event
--validate Validate output
```

Expand Down Expand Up @@ -496,6 +501,14 @@ Specify a virtual file extension when reading content from stdin. By default, Ro

Do not read files from `stdin`. Setting this flag will prevent piping content to Rollup and make sure Rollup interprets `-` and `-.[ext]` as a regular file names instead of interpreting these as the name of `stdin`. See also [Reading a file from stdin](guide/en/#reading-a-file-from-stdin).

#### `--watch.onStart <cmd>`, `--watch.onBundleStart <cmd>`, `--watch.onBundleEnd <cmd>`, `--watch.onEnd <cmd>`, `--watch.onError <cmd>`

When in watch mode, run a shell command `<cmd>` for a watch event code. See also [rollup.watch](guide/en/#rollupwatch).

```sh
rollup -c --watch --watch.onEnd="node ./afterBuildScript.js"
```

### Reading a file from stdin

When using the command line interface, Rollup can also read content from stdin:
Expand Down
2 changes: 2 additions & 0 deletions src/rollup/types.d.ts
Expand Up @@ -864,6 +864,8 @@ export interface ChokidarOptions {
usePolling?: boolean;
}

export type RollupWatchHooks = 'onError' | 'onStart' | 'onBundleStart' | 'onBundleEnd' | 'onEnd';

export interface WatcherOptions {
buildDelay?: number;
chokidar?: ChokidarOptions;
Expand Down
18 changes: 18 additions & 0 deletions test/cli/samples/watch/watch-event-hooks-error/_config.js
@@ -0,0 +1,18 @@
const { assertIncludes } = require('../../../../utils.js');

module.exports = {
description: 'onError event hook shell commands write to stderr',
command: 'node wrapper.js -cw --watch.onError "echo error"',
abortOnStderr(data) {
if (data.includes('waiting for changes')) {
return true;
}
},
stderr(stderr) {
assertIncludes(
stderr,
`watch.onError $ echo error
error`
);
}
};
4 changes: 4 additions & 0 deletions test/cli/samples/watch/watch-event-hooks-error/main.js
@@ -0,0 +1,4 @@
// missing `=` to trigger onError
var main 42;

export { main as default };
@@ -0,0 +1,7 @@
export default {
input: 'main.js',
output: {
dir: '_actual',
format: 'es'
}
};
5 changes: 5 additions & 0 deletions test/cli/samples/watch/watch-event-hooks-error/wrapper.js
@@ -0,0 +1,5 @@
#!/usr/bin/env node

process.stdout.isTTY = true;
process.stderr.isTTY = true;
require('../../../../../dist/bin/rollup');
36 changes: 36 additions & 0 deletions test/cli/samples/watch/watch-event-hooks/_config.js
@@ -0,0 +1,36 @@
const { assertIncludes } = require('../../../../utils.js');

module.exports = {
description: 'event hook shell commands write to stderr',
command:
'node wrapper.js -cw --watch.onStart "echo start" --watch.onBundleStart "echo bundleStart" --watch.onBundleEnd "echo bundleEnd" --watch.onEnd "echo end"',
abortOnStderr(data) {
process.stderr.write(data);
if (data.includes('waiting for changes')) {
return true;
}
},
stderr(stderr) {
// assert each hook individually
assertIncludes(
stderr,
`watch.onStart $ echo start
start`
);
assertIncludes(
stderr,
`watch.onBundleStart $ echo bundleStart
bundleStart`
);
assertIncludes(
stderr,
`watch.onBundleEnd $ echo bundleEnd
bundleEnd`
);
assertIncludes(
stderr,
`watch.onEnd $ echo end
end`
);
}
};
3 changes: 3 additions & 0 deletions test/cli/samples/watch/watch-event-hooks/main.js
@@ -0,0 +1,3 @@
var main = 42;

export { main as default };
7 changes: 7 additions & 0 deletions test/cli/samples/watch/watch-event-hooks/rollup.config.js
@@ -0,0 +1,7 @@
export default {
input: 'main.js',
output: {
dir: '_actual',
format: 'es'
}
};
5 changes: 5 additions & 0 deletions test/cli/samples/watch/watch-event-hooks/wrapper.js
@@ -0,0 +1,5 @@
#!/usr/bin/env node

process.stdout.isTTY = true;
process.stderr.isTTY = true;
require('../../../../../dist/bin/rollup');

0 comments on commit 0c75914

Please sign in to comment.