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

feat: CLI event hook flags #4457

Merged
merged 14 commits into from May 5, 2022
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');