-
Notifications
You must be signed in to change notification settings - Fork 345
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: Added reload from the terminal by pressing a key #746
Changes from 34 commits
36e9947
8c93bbf
a50013a
5dc6098
1d75365
bf35ff6
de75bcf
bc924de
af979c9
cc1b862
0805d20
0d56e0f
953761e
4f3fa4b
b78ca40
fad5046
9a37106
13a4cc0
b665d85
2f1156e
bc83865
91843f0
bd799fb
4390477
a08177e
b4b7a11
f8b82da
d37e58c
025d40a
c2fca58
cf326b5
2e3e05c
a8e07e1
a02d4dd
a46281f
2a0994d
7dbb6ee
877bec1
68089e9
14f4a8c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,7 @@ | ||
/* @flow */ | ||
import readline from 'readline'; | ||
import tty from 'tty'; | ||
|
||
import type FirefoxProfile from 'firefox-profile'; | ||
import type Watchpack from 'watchpack'; | ||
|
||
|
@@ -41,9 +44,9 @@ export type WatcherCreatorParams = {| | |
sourceDir: string, | ||
artifactsDir: string, | ||
onSourceChange?: OnSourceChangeFn, | ||
desktopNotifications?: typeof defaultDesktopNotifications, | ||
ignoreFiles?: Array<string>, | ||
createFileFilter?: FileFilterCreatorFn, | ||
addonReload?: typeof defaultAddonReload, | ||
|}; | ||
|
||
export type WatcherCreatorFn = (params: WatcherCreatorParams) => Watchpack; | ||
|
@@ -52,8 +55,8 @@ export function defaultWatcherCreator( | |
{ | ||
addonId, client, sourceDir, artifactsDir, ignoreFiles, | ||
onSourceChange = defaultSourceWatcher, | ||
desktopNotifications = defaultDesktopNotifications, | ||
createFileFilter = defaultFileFilterCreator, | ||
addonReload = defaultAddonReload, | ||
}: WatcherCreatorParams | ||
): Watchpack { | ||
const fileFilter = createFileFilter( | ||
|
@@ -62,24 +65,36 @@ export function defaultWatcherCreator( | |
return onSourceChange({ | ||
sourceDir, | ||
artifactsDir, | ||
onChange: () => { | ||
log.debug(`Reloading add-on ID ${addonId}`); | ||
|
||
return client.reloadAddon(addonId) | ||
.catch((error) => { | ||
log.error('\n'); | ||
log.error(error.stack); | ||
desktopNotifications({ | ||
title: 'web-ext run: error occurred', | ||
message: error.message, | ||
}); | ||
throw error; | ||
}); | ||
}, | ||
onChange: () => addonReload({addonId, client}), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I'm not wrong, these Reverting these changes should help to greatly simplify this change. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, I was wrong, I was forgetting that the @saintsebastian As per discussion over email, you can ignore this comment and only handle the rest of the review comments. |
||
shouldWatchFile: (file) => fileFilter.wantFile(file), | ||
}); | ||
} | ||
|
||
export type ReloadParams = {| | ||
addonId: string, | ||
client: RemoteFirefox, | ||
desktopNotifications?: typeof defaultDesktopNotifications, | ||
|}; | ||
|
||
export function defaultAddonReload( | ||
{ | ||
addonId, client, | ||
desktopNotifications = defaultDesktopNotifications, | ||
}: ReloadParams | ||
): Promise<void> { | ||
log.debug(`Reloading add-on ID ${addonId}`); | ||
return client.reloadAddon(addonId) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there any reason why this is using promise code instead of async/await code? I think it would read better as an async function with a try/catch. |
||
.catch((error) => { | ||
log.error('\n'); | ||
log.error(error.stack); | ||
desktopNotifications({ | ||
title: 'web-ext run: error occurred', | ||
message: error.message, | ||
}); | ||
throw error; | ||
}); | ||
} | ||
|
||
|
||
// defaultReloadStrategy types and implementation. | ||
|
||
|
@@ -180,24 +195,30 @@ export type CmdRunParams = {| | |
customPrefs?: FirefoxPreferences, | ||
startUrl?: string | Array<string>, | ||
ignoreFiles?: Array<string>, | ||
stdin: stream$Readable, | ||
|}; | ||
|
||
export type CmdRunOptions = {| | ||
firefoxApp: typeof defaultFirefoxApp, | ||
firefoxClient: typeof defaultFirefoxClient, | ||
reloadStrategy: typeof defaultReloadStrategy, | ||
addonReload: typeof defaultAddonReload, | ||
AddonRunner: typeof ExtensionRunner | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: we typically use trailing commas to make refactoring easier but eslint doesn't enforce it in flow annotations. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about calling this |
||
|}; | ||
|
||
export default async function run( | ||
{ | ||
sourceDir, artifactsDir, firefox, firefoxProfile, | ||
keepProfileChanges = false, preInstall = false, noReload = false, | ||
browserConsole = false, customPrefs, startUrl, ignoreFiles, | ||
stdin = process.stdin, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In command handler functions, all of these parameters should correlate to command line options. Thus, |
||
}: CmdRunParams, | ||
{ | ||
firefoxApp = defaultFirefoxApp, | ||
firefoxClient = defaultFirefoxClient, | ||
reloadStrategy = defaultReloadStrategy, | ||
addonReload = defaultAddonReload, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it will be easier to move the new stdin loop logic to Here is a quick patch to illustrate what I mean: diff --git a/src/cmd/run.js b/src/cmd/run.js
index cdeab34..a524a25 100644
--- a/src/cmd/run.js
+++ b/src/cmd/run.js
@@ -46,17 +46,16 @@ export type WatcherCreatorParams = {|
onSourceChange?: OnSourceChangeFn,
ignoreFiles?: Array<string>,
createFileFilter?: FileFilterCreatorFn,
- addonReload?: typeof defaultAddonReload,
+ addonReload: typeof defaultAddonReload,
|};
export type WatcherCreatorFn = (params: WatcherCreatorParams) => Watchpack;
export function defaultWatcherCreator(
{
- addonId, client, sourceDir, artifactsDir, ignoreFiles,
+ addonId, addonReload, client, sourceDir, artifactsDir, ignoreFiles,
onSourceChange = defaultSourceWatcher,
createFileFilter = defaultFileFilterCreator,
- addonReload = defaultAddonReload,
}: WatcherCreatorParams
): Watchpack {
const fileFilter = createFileFilter(
@@ -109,6 +108,7 @@ export type ReloadStrategyParams = {|
|};
export type ReloadStrategyOptions = {|
+ addonReload?: typeof defaultAddonReload,
createWatcher?: WatcherCreatorFn,
createFileFilter?: FileFilterCreatorFn,
|};
@@ -119,18 +119,20 @@ export function defaultReloadStrategy(
sourceDir, artifactsDir, ignoreFiles,
}: ReloadStrategyParams,
{
+ addonReload = defaultAddonReload,
createWatcher = defaultWatcherCreator,
}: ReloadStrategyOptions = {}
): void {
- const watcher: Watchpack = (
- createWatcher({addonId, client, sourceDir, artifactsDir, ignoreFiles})
- );
+ const watcher: Watchpack = createWatcher({
+ addonId, addonReload, client, sourceDir, artifactsDir, ignoreFiles,
+ });
firefoxProcess.on('close', () => {
client.disconnect();
watcher.close();
});
+ // Do the while (!userExit) loop right here...
}
|
||
AddonRunner = ExtensionRunner, | ||
}: CmdRunOptions = {}): Promise<Object> { | ||
|
||
log.info(`Running web extension from ${sourceDir}`); | ||
|
@@ -216,7 +237,7 @@ export default async function run( | |
|
||
const manifestData = await getValidatedManifest(sourceDir); | ||
|
||
const runner = new ExtensionRunner({ | ||
const runner = new AddonRunner({ | ||
sourceDir, | ||
firefoxApp, | ||
firefox, | ||
|
@@ -272,6 +293,36 @@ export default async function run( | |
); | ||
} | ||
|
||
if (stdin.isTTY && stdin instanceof tty.ReadStream) { | ||
readline.emitKeypressEvents(stdin); | ||
stdin.setRawMode(true); | ||
|
||
// NOTE: this `Promise.resolve().then(...)` is basically used to spawn a "co-routine" that is executed | ||
// before the callback attached to the Promise returned by this function (and it allows the `run` function | ||
// to not be stuck in the while loop). | ||
Promise.resolve().then(async function() { | ||
log.info('Press R to reload (and Ctrl-C to quit)'); | ||
|
||
let userExit = false; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To expand on my comment above about moving this logic to
After this, firefoxProcess.on('close', () => {
client.disconnect();
watcher.close();
// stop polling stdin...
}); |
||
|
||
while (!userExit) { | ||
const keyPressed = await new Promise((resolve) => { | ||
stdin.once('keypress', (str, key) => resolve(key)); | ||
}); | ||
|
||
if (keyPressed.ctrl && keyPressed.name === 'c') { | ||
userExit = true; | ||
} else if (keyPressed.name === 'r' && addonId) { | ||
await addonReload({addonId, client}); | ||
} | ||
} | ||
|
||
log.info('\nExiting web-ext on user request'); | ||
runningFirefox.kill(); | ||
stdin.pause(); | ||
}); | ||
} | ||
|
||
log.info('The extension will reload if any source file changes'); | ||
reloadStrategy({ | ||
firefoxProcess: runningFirefox, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it looks like
addonId
andclient
are also unused in defaultWatcherCreator now (they are parameters of theaddonReload
)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rpl what's the best way to handle this in your opinion? Have a second set of types, so the function of both is clear like in
defaultWatcherCreator({...WatcherCreatorParams}, {...ReloadParams})
or pass them likedefaultWatcherCreator(addend, client, {...WatcherCreatorParams})
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@saintsebastian actually I think that we can leave it like it is right now
(initially I was thinking that we could pass an
addonReload
utility method that do not need to receive theaddonId
and theclient
from thedefaultWatcherCreator
, e.g. by capture them into a closure, which is absolutely possible, but after re-reading it again I changed my mind and I think that we can leave todefaultWatcherCreator
the responsability of providingaddonId
andclient
to theaddonReload
function)