chore: Remove flow types annotations and dependencies (#2765)

* chore: Remove all flow type annotations

* chore: Fixed not critical mistake in a fake extension runner used in tests

* chore: Removed all flow related dependencies

* chore: Removed all flow related parts from circleci job config

* chore: Removed all mentions to flow from CONTRIBUTING.md

* chore: Removed remaining flow supress comments (and some more now unnecessary comments)

* chore: One last prettier reformatting pass
pull/2770/head
Luca Greco 2023-06-01 11:31:49 +02:00 committed by GitHub
parent ba3439fd4d
commit a59873b964
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
94 changed files with 433 additions and 2224 deletions

View File

@ -6,8 +6,7 @@
// Leave import/export statements unchanged in the babel transpiling output.
"modules": false
}
],
"@babel/flow"
]
],
"plugins": [
["transform-inline-environment-variables", {

View File

@ -171,15 +171,6 @@ jobs:
suffix: << parameters.nodejs >>
- run_npm_build:
node_env: test
- unless:
condition:
equal: [*nodejs_current, << parameters.nodejs >>]
steps:
# `flow check` does a static analysis, no need to run more than once
# per worflow run.
- run:
name: Turn off flow checks on non current nodejs version
command: export CI_SKIP_FLOWCHECK=y
## Skip code coverage and the additional legacy bundling tests on jobs
## running on the next nodejs versions.
- unless:
@ -237,10 +228,6 @@ jobs:
- run:
name: run linting checks and unit tests
command: npm run test
environment:
# `flow check` does a static analysis, no need to run more than once
# per worflow run.
CI_SKIP_FLOWCHECK: y
- run_functional_tests:
retry_once: "y"

View File

@ -2,5 +2,4 @@ lib/
!scripts/lib
coverage/
artifacts/
flow-typed/
commitlint.config.js

View File

@ -20,21 +20,14 @@
},
"plugins": [
"async-await",
"ft-flow",
"import"
],
"settings": {
"ft-flow": {
"onlyFilesWithFlowAnnotation": true
}
},
"env": {
"node": true,
"es6": true
},
"extends": [
"eslint:recommended",
"plugin:ft-flow/recommended"
],
"globals": {
"exports": false,
@ -113,10 +106,6 @@
"async-await/space-after-async": 2,
"async-await/space-after-await": 2,
"ft-flow/define-flow-type": 1,
"ft-flow/use-flow-type": 1,
"ft-flow/space-after-type-colon": [1, "always"],
// This makes sure imported modules exist.
"import/no-unresolved": 2,
// This makes sure imported names exist.

View File

@ -1,19 +0,0 @@
[include]
[libs]
flow-typed/
[options]
module.system=node
log.file=./artifacts/flow.log
[ignore]
<PROJECT_ROOT>/dist/.*
# A lot of babel modules seem to use Flow and for some reason they're mostly
# broken.
.*/node_modules/babel.*
# This ignores .json files in a bunch of places. It can go away when the
# bug is fixed: https://github.com/facebook/flow/issues/1420
.*/node_modules.*/\(binary\-extensions\|builtin\-modules\|iconv\-lite\|lib\|test\|url\-regex\|spdx\-exceptions\|spdx\-license\-ids\)/.*\.json$

View File

@ -8,7 +8,6 @@ LICENSE
# exclude these directories
/artifacts/
/coverage/
/flow-typed/
/lib/
/node_modules/
/tests/fixtures/

View File

@ -18,10 +18,6 @@ development more awesome by contributing to the `web-ext` tool. Here are links t
- [Debug a test](#debug-a-test)
- [Build web-ext](#build-web-ext)
- [Check for lint](#check-for-lint)
- [Check for Flow errors](#check-for-flow-errors)
- [Missing annotation](#missing-annotation)
- [How to read Flow errors related to type inconsistencies](#how-to-read-flow-errors-related-to-type-inconsistencies)
- [Flow type conventions](#flow-type-conventions)
- [Code Coverage](#code-coverage)
- [Working on the CLI](#working-on-the-cli)
- [Adding a command option](#adding-a-command-option)
@ -51,7 +47,7 @@ To get started on a patch, first install `web-ext` from [source](README.md#insta
## Develop all the things
Your one stop command to continuously build, run tests, check for
JavaScript syntax problems, and check for [Flow] errors is this:
JavaScript syntax problems is:
npm start
@ -111,99 +107,6 @@ the test suite without lint checks:
SKIP_LINT=1 npm test
## Check for Flow errors
This project relies on [Flow] to ensure functions and
classes are used correctly. Run all Flow checks with `npm run flow-check`.
The first steps to learn how to fix flow errors are:
- learn how to read the flow annotations
- learn how to write new type definitions or change the existing definitions
- learn to read the flow errors and know some of the more common errors
To learn more about the syntax used to add the flow annotations and how to
write/change the type definitions, you should take a look at the official
[Flow docs](https://flowtype.org/docs/getting-started.html)
The following sections contain additional information related to common
flow errors and how to read and fix them.
### Missing annotation
This is a pretty common flow error and it is usually the simplest to fix.
It means that the new code added in the sources doesn't define the types
of the functions and methods parameters, e.g. on the following snippet:
```js
export default async function getValidatedManifest(sourceDir) {
...
}
```
flow is going to raise the error:
```
src/util/manifest.js:32
32: sourceDir
^^^^^^^^^ parameter `sourceDir`. Missing annotation
```
which is fixed by annotating the function correctly, e.g.:
```js
export default async function getValidatedManifest(
sourceDir: string
): Promise<ExtensionManifest> {
...
}
```
### How to read Flow errors related to type inconsistencies
Some of the flow errors are going to contain references to the two sides
of the flowtype errors:
```
tests/unit/test-cmd/test.build.js:193
193: manifestData: basicManifest,
^^^^^^^^^^^^^ property `applications`. Property not found in
24: export type ExtensionManifest = {|
^ object type. See: src/util/manifest.js:24
```
- The first part points to the offending code (where the type violation has been found)
- The second part points to the violated type annotation (where the type has been defined)
When flow raises this kind of error (e.g. it is pretty common during a refactoring),
we have to evaluate which one of the two sides is wrong.
As an example, by reading the above error it is not immediately clear which part should be fixed.
To be sure about which is the proper fix, we have to look at the code near to both the lines
and evaluate the actual reason, e.g.:
- it is possible that we wrote some of the property names wrong (in the code or in the type definitions)
- or the defined type is supposed to contain a new property and it is not yet in the related type definitions
### Flow type conventions
In the `web-ext` sources we are currently using the following conventions (and they should be preserved
when we change or add flow type definitions):
- the type names should be CamelCased (e.g. `ExtensionManifest`)
- the types used to annotate functions or methods defined in a module should be exported only when
they are supposed to be used by other modules (`export type ExtensionManifest = ...`)
- any type imported from the other modules should be in the module preamble (near to the regular ES6 imports)
- object types should be exact object types (e.g. `{| ... |}`), because flow will be able to raise
errors when we try to set or get a property not explicitly defined in the flow type (which is
particularly helpful during refactorings)
- all the flow type definitions should be as close as possible to the function they annotate
- we prefer not to use external files (e.g. `.flow.js` files or declaration files configured in the
`.flowconfig` file) for the `web-ext` flow types.
## Code Coverage
You can generate code coverage reports every time you run the test suite
@ -408,5 +311,3 @@ The schedule is flexible, if a release is due to happen close to a Firefox relea
If the issue you're working on involves changing any of the headings in this document [CONTRIBUTING.md](https://github.com/mozilla/web-ext/blob/master/CONTRIBUTING.md),
before making a commit and submitting a pull request, please remember to update the table of contents.
To update the TOC, run the command `npm run gen-contributing-toc` from your root directory and you will auto generate a new TOC.
[flow]: http://flowtype.org/

View File

@ -1,6 +0,0 @@
This directory contains flowtyped declarations for the npm modules that do need
valid flow signatures to be able to flow check the modules part of this project.
The declarations in this directory are not complete definitions of the type defined
in the related modules source code, this declaration files define just as much as
needed for the purpose of flow checking this project source code.

View File

@ -1,23 +0,0 @@
// flow-typed signatures for 'chrome-launcher' module.
declare module "chrome-launcher" {
declare type LaunchOptions = {
enableExtensions: boolean,
chromePath: ?string,
chromeFlags: Array<string>,
startingUrl: ?string,
userDataDir: ?string,
ignoreDefaultFlags: boolean,
};
declare class Launcher {
static defaultFlags: () => Array<string>,
process: child_process$ChildProcess,
kill(): Promise<void>,
}
declare module.exports: {
Launcher: Class<Launcher>,
launch(options?: LaunchOptions): Promise<Launcher>,
}
}

View File

@ -1,39 +0,0 @@
// flow-typed signatures for 'firefox-profile' module.
declare module "firefox-profile" {
declare type ProfileOptions = {
destinationDirectory: string,
}
declare type ProfileCallback =
(err: ?Error, profile: FirefoxProfile) => void;
declare class Finder {
constructor(baseDir: ?string): Finder,
readProfiles(cb: Function): void,
getPath(name: string, cb: (err: ?Error, profilePath: string) => void): void,
profiles: Array<{ [key:string]: string }>,
static locateUserDirectory(): string,
}
declare class FirefoxProfile {
constructor(opts: ?ProfileOptions): FirefoxProfile,
extensionsDir: string,
profileDir: string,
userPrefs: string,
defaultPreferences: { [key: string]: any },
path(): string,
setPreference(pref: string, value: any): void,
updatePreferences(): void,
static copy({profileDirectory: string}, cb: ProfileCallback): void,
static copyFromUserProfile({name: string}, cb: ProfileCallback): void,
static Finder: Class<Finder>,
}
declare module.exports: Class<FirefoxProfile>;
}

View File

@ -1,22 +0,0 @@
// flow-typed signatures for 'watchpack' module.
declare module "watchpack" {
declare type WatchpackOptions = {
ignored: Array<string>,
};
declare type WatchOptions = {
files: Array<string>,
directories: Array<string>,
missing: Array<string>,
startTime?: number
};
declare class Watchpack extends events$EventEmitter {
constructor(options?: WatchpackOptions): Watchpack,
close(): void,
watch(options: WatchOptions): void,
}
declare module.exports: Class<Watchpack>;
}

31
flow-typed/ws.decl.js vendored
View File

@ -1,31 +0,0 @@
// flow-typed signatures for 'ws' module.
declare module "ws" {
declare type ServerOptions = {
host: string,
port: number,
}
declare export class WebSocket extends events$EventEmitter {
constructor(url: string): WebSocket,
removeEventListener(eventName: string, cb: Function): void,
readyState: "CONNECTING" | "OPEN" | "CLOSING" | "CLOSED",
send(msg: string): void,
close(code?: number, reason?: string | Buffer): void,
terminate(): void,
static OPEN: "OPEN",
static CLOSED: "CLOSED",
static CONNECTING: "CONNECTING",
static CLOSING: "CLOSING",
}
declare export class WebSocketServer extends net$Server {
constructor(opts?: ServerOptions, listenCb: Function): WebSocketServer,
address(): net$Socket$address,
clients: Set<WebSocket>,
close(closedCb: Function): WebSocketServer,
}
declare export default typeof WebSocket;
}

138
package-lock.json generated
View File

@ -51,7 +51,6 @@
"@babel/core": "7.22.1",
"@babel/eslint-parser": "7.21.8",
"@babel/preset-env": "7.22.4",
"@babel/preset-flow": "7.21.4",
"@babel/register": "7.21.0",
"@commitlint/cli": "17.6.5",
"@commitlint/config-conventional": "17.6.3",
@ -65,9 +64,7 @@
"deepcopy": "2.1.0",
"eslint": "8.41.0",
"eslint-plugin-async-await": "0.0.0",
"eslint-plugin-ft-flow": "2.0.3",
"eslint-plugin-import": "2.27.5",
"flow-bin": "0.182.0",
"git-rev-sync": "3.0.2",
"html-entities": "2.3.3",
"mocha": "10.2.0",
@ -730,21 +727,6 @@
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-syntax-flow": {
"version": "7.21.4",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.21.4.tgz",
"integrity": "sha512-l9xd3N+XG4fZRxEP3vXdK6RW7vN1Uf5dxzRC/09wV86wqZ/YYQooBIGNsiRdfNR3/q2/5pPzV4B54J/9ctX5jw==",
"dev": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.20.2"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-syntax-import-assertions": {
"version": "7.20.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz",
@ -1163,22 +1145,6 @@
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-flow-strip-types": {
"version": "7.21.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.21.0.tgz",
"integrity": "sha512-FlFA2Mj87a6sDkW4gfGrQQqwY/dLlBAyJa2dJEZ+FHXUVHBflO2wyKvg+OOEzXfrKYIa4HWl0mgmbCzt0cMb7w==",
"dev": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.20.2",
"@babel/plugin-syntax-flow": "^7.18.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-for-of": {
"version": "7.21.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.21.5.tgz",
@ -1799,23 +1765,6 @@
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/preset-flow": {
"version": "7.21.4",
"resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.21.4.tgz",
"integrity": "sha512-F24cSq4DIBmhq4OzK3dE63NHagb27OPE3eWR+HLekt4Z3Y5MzIIUGF3LlLgV0gN8vzbDViSY7HnrReNVCJXTeA==",
"dev": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.20.2",
"@babel/helper-validator-option": "^7.21.0",
"@babel/plugin-transform-flow-strip-types": "^7.21.0"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/preset-modules": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz",
@ -5722,23 +5671,6 @@
"integrity": "sha512-CNizhDO2f1dLaoA6wah3Yj8bSmsDC7wRTt4bsFBOUEYvzcd6XNhxBmz3EgqxD6a+V4Zjl8qTiDMZo3ug+qjojg==",
"dev": true
},
"node_modules/eslint-plugin-ft-flow": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/eslint-plugin-ft-flow/-/eslint-plugin-ft-flow-2.0.3.tgz",
"integrity": "sha512-Vbsd/b+LYA99jUbsL6viEUWShFaYQt2YQs3QN3f+aeszOhh2sgdcU0mjzDyD4yyBvMc8qy2uwvBBWfMzEX06tg==",
"dev": true,
"dependencies": {
"lodash": "^4.17.21",
"string-natural-compare": "^3.0.1"
},
"engines": {
"node": ">=12.22.0"
},
"peerDependencies": {
"@babel/eslint-parser": "^7.12.0",
"eslint": "^8.1.0"
}
},
"node_modules/eslint-plugin-import": {
"version": "2.27.5",
"resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz",
@ -6345,18 +6277,6 @@
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.6.tgz",
"integrity": "sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ=="
},
"node_modules/flow-bin": {
"version": "0.182.0",
"resolved": "https://registry.npmjs.org/flow-bin/-/flow-bin-0.182.0.tgz",
"integrity": "sha512-Ux90c2sMfoV/VVjOEFT2OHFJFnyfoIbTK/5AKAMnU4Skfru1G+FyS5YLu3XxQl0R6mpA9+rrFlPfYZq/5B+J3w==",
"dev": true,
"bin": {
"flow": "cli.js"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/for-each": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
@ -11587,12 +11507,6 @@
"safe-buffer": "~5.1.0"
}
},
"node_modules/string-natural-compare": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/string-natural-compare/-/string-natural-compare-3.0.1.tgz",
"integrity": "sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==",
"dev": true
},
"node_modules/string-width": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
@ -13392,15 +13306,6 @@
"@babel/helper-plugin-utils": "^7.8.3"
}
},
"@babel/plugin-syntax-flow": {
"version": "7.21.4",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.21.4.tgz",
"integrity": "sha512-l9xd3N+XG4fZRxEP3vXdK6RW7vN1Uf5dxzRC/09wV86wqZ/YYQooBIGNsiRdfNR3/q2/5pPzV4B54J/9ctX5jw==",
"dev": true,
"requires": {
"@babel/helper-plugin-utils": "^7.20.2"
}
},
"@babel/plugin-syntax-import-assertions": {
"version": "7.20.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz",
@ -13675,16 +13580,6 @@
"@babel/plugin-syntax-export-namespace-from": "^7.8.3"
}
},
"@babel/plugin-transform-flow-strip-types": {
"version": "7.21.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.21.0.tgz",
"integrity": "sha512-FlFA2Mj87a6sDkW4gfGrQQqwY/dLlBAyJa2dJEZ+FHXUVHBflO2wyKvg+OOEzXfrKYIa4HWl0mgmbCzt0cMb7w==",
"dev": true,
"requires": {
"@babel/helper-plugin-utils": "^7.20.2",
"@babel/plugin-syntax-flow": "^7.18.6"
}
},
"@babel/plugin-transform-for-of": {
"version": "7.21.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.21.5.tgz",
@ -14101,17 +13996,6 @@
"semver": "^6.3.0"
}
},
"@babel/preset-flow": {
"version": "7.21.4",
"resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.21.4.tgz",
"integrity": "sha512-F24cSq4DIBmhq4OzK3dE63NHagb27OPE3eWR+HLekt4Z3Y5MzIIUGF3LlLgV0gN8vzbDViSY7HnrReNVCJXTeA==",
"dev": true,
"requires": {
"@babel/helper-plugin-utils": "^7.20.2",
"@babel/helper-validator-option": "^7.21.0",
"@babel/plugin-transform-flow-strip-types": "^7.21.0"
}
},
"@babel/preset-modules": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz",
@ -17090,16 +16974,6 @@
"integrity": "sha512-CNizhDO2f1dLaoA6wah3Yj8bSmsDC7wRTt4bsFBOUEYvzcd6XNhxBmz3EgqxD6a+V4Zjl8qTiDMZo3ug+qjojg==",
"dev": true
},
"eslint-plugin-ft-flow": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/eslint-plugin-ft-flow/-/eslint-plugin-ft-flow-2.0.3.tgz",
"integrity": "sha512-Vbsd/b+LYA99jUbsL6viEUWShFaYQt2YQs3QN3f+aeszOhh2sgdcU0mjzDyD4yyBvMc8qy2uwvBBWfMzEX06tg==",
"dev": true,
"requires": {
"lodash": "^4.17.21",
"string-natural-compare": "^3.0.1"
}
},
"eslint-plugin-import": {
"version": "2.27.5",
"resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz",
@ -17406,12 +17280,6 @@
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.6.tgz",
"integrity": "sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ=="
},
"flow-bin": {
"version": "0.182.0",
"resolved": "https://registry.npmjs.org/flow-bin/-/flow-bin-0.182.0.tgz",
"integrity": "sha512-Ux90c2sMfoV/VVjOEFT2OHFJFnyfoIbTK/5AKAMnU4Skfru1G+FyS5YLu3XxQl0R6mpA9+rrFlPfYZq/5B+J3w==",
"dev": true
},
"for-each": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
@ -21262,12 +21130,6 @@
"safe-buffer": "~5.1.0"
}
},
"string-natural-compare": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/string-natural-compare/-/string-natural-compare-3.0.1.tgz",
"integrity": "sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==",
"dev": true
},
"string-width": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",

View File

@ -97,7 +97,6 @@
"@babel/core": "7.22.1",
"@babel/eslint-parser": "7.21.8",
"@babel/preset-env": "7.22.4",
"@babel/preset-flow": "7.21.4",
"@babel/register": "7.21.0",
"@commitlint/cli": "17.6.5",
"@commitlint/config-conventional": "17.6.3",
@ -111,9 +110,7 @@
"deepcopy": "2.1.0",
"eslint": "8.41.0",
"eslint-plugin-async-await": "0.0.0",
"eslint-plugin-ft-flow": "2.0.3",
"eslint-plugin-import": "2.27.5",
"flow-bin": "0.182.0",
"git-rev-sync": "3.0.2",
"html-entities": "2.3.3",
"mocha": "10.2.0",

View File

@ -7,18 +7,12 @@ import notifier from 'node-notifier';
import config from './lib/config.js';
import eslint from './lib/eslint.js';
import { flowStatus } from './lib/flow.js';
import { mochaUnit, mochaFunctional } from './lib/mocha.js';
import babel from './lib/babel.js';
const COVERAGE =
process.argv.includes('--coverage') || process.env.COVERAGE === 'y';
console.log('Starting flow server...');
if (!flowStatus()) {
process.exit(1);
}
const wp = new Watchpack();
wp.watch(config.watch.files, config.watch.dirs);
@ -35,12 +29,6 @@ async function runTasks(changes) {
console.log(changesDetected);
notify(changesDetected);
console.log('\nRunning flow checks');
if (!flowStatus()) {
notify('flow check errors');
return;
}
console.log('\nRunning eslint checks');
if (!eslint()) {
notify('eslint errors');

View File

@ -1,7 +1,7 @@
export default {
clean: ['lib/*'],
watch: {
files: ['package.json', 'webpack.config.js', '.flowconfig'],
files: ['package.json', 'webpack.config.js'],
dirs: ['src', 'tests', 'scripts'],
},
eslint: {

View File

@ -1,27 +0,0 @@
import { spawnSync } from 'child_process';
export const flowCheck = () => {
if (process.env.CI_SKIP_FLOWCHECK) {
console.log('flow check task skipped on CI_SKIP_FLOWCHECK env set');
} else {
const res = spawnSync('flow', ['check'], { stdio: 'inherit', shell: true });
if (res.error || res.status !== 0) {
if (res.error) {
console.error(res.error);
}
return false;
}
}
return true;
};
export const flowStatus = () => {
const res = spawnSync('flow', ['status'], { stdio: 'inherit', shell: true });
if (res.error) {
console.error(res.error);
return false;
}
return res.status === 0;
};

View File

@ -1,7 +1,6 @@
#!/usr/bin/env node
import eslint from './lib/eslint.js';
import { flowCheck } from './lib/flow.js';
import { mochaUnit } from './lib/mocha.js';
import { isBuilt } from './lib/babel.js';
@ -18,11 +17,6 @@ if (!eslint()) {
process.exit(1);
}
console.log('Running flow check...');
if (!flowCheck()) {
process.exit(1);
}
console.log('Running mocha unit tests...', COVERAGE ? '(COVERAGE)' : '');
const ok = mochaUnit({}, COVERAGE);
process.exit(ok ? 0 : 1);

View File

@ -1,4 +1,3 @@
/* @flow */
import path from 'path';
import { createWriteStream } from 'fs';
@ -13,63 +12,24 @@ import getValidatedManifest, { getManifestId } from '../util/manifest.js';
import { prepareArtifactsDir } from '../util/artifacts.js';
import { createLogger } from '../util/logger.js';
import { UsageError, isErrorWithCode } from '../errors.js';
import {
createFileFilter as defaultFileFilterCreator,
FileFilter,
} from '../util/file-filter.js';
// Import flow types.
import type { OnSourceChangeFn } from '../watcher.js';
import type { ExtensionManifest } from '../util/manifest.js';
import type { FileFilterCreatorFn } from '../util/file-filter.js';
import { createFileFilter as defaultFileFilterCreator } from '../util/file-filter.js';
const log = createLogger(import.meta.url);
const DEFAULT_FILENAME_TEMPLATE = '{name}-{version}.zip';
export function safeFileName(name: string): string {
export function safeFileName(name) {
return name.toLowerCase().replace(/[^a-z0-9.-]+/g, '_');
}
// defaultPackageCreator types and implementation.
export type ExtensionBuildResult = {|
extensionPath: string,
|};
export type PackageCreatorParams = {|
manifestData?: ExtensionManifest,
sourceDir: string,
fileFilter: FileFilter,
artifactsDir: string,
overwriteDest: boolean,
showReadyMessage: boolean,
filename?: string,
|};
export type LocalizedNameParams = {|
messageFile: string,
manifestData: ExtensionManifest,
|};
export type PackageCreatorOptions = {
fromEvent: typeof defaultFromEvent,
};
// This defines the _locales/messages.json type. See:
// https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Internationalization#Providing_localized_strings_in__locales
type LocalizedMessageData = {|
[messageName: string]: {|
description: string,
message: string,
|},
|};
export async function getDefaultLocalizedName({
messageFile,
manifestData,
}: LocalizedNameParams): Promise<string> {
let messageData: LocalizedMessageData;
let messageContents: string | Buffer;
let extensionName: string = manifestData.name;
export async function getDefaultLocalizedName({ messageFile, manifestData }) {
let messageData;
let messageContents;
let extensionName = manifestData.name;
try {
messageContents = await fs.readFile(messageFile, { encoding: 'utf-8' });
@ -107,7 +67,7 @@ export async function getDefaultLocalizedName({
}
// https://stackoverflow.com/a/22129960
export function getStringPropertyValue(prop: string, obj: Object): string {
export function getStringPropertyValue(prop, obj) {
const properties = prop.split('.');
const value = properties.reduce((prev, curr) => prev && prev[curr], obj);
if (!['string', 'number'].includes(typeof value)) {
@ -122,10 +82,7 @@ export function getStringPropertyValue(prop: string, obj: Object): string {
return stringValue;
}
function getPackageNameFromTemplate(
filenameTemplate: string,
manifestData: ExtensionManifest
): string {
function getPackageNameFromTemplate(filenameTemplate, manifestData) {
const packageName = filenameTemplate.replace(
/{([A-Za-z0-9._]+?)}/g,
(match, manifestProperty) => {
@ -154,10 +111,6 @@ function getPackageNameFromTemplate(
return packageName;
}
export type PackageCreatorFn = (
params: PackageCreatorParams
) => Promise<ExtensionBuildResult>;
export async function defaultPackageCreator(
{
manifestData,
@ -167,9 +120,9 @@ export async function defaultPackageCreator(
overwriteDest,
showReadyMessage,
filename = DEFAULT_FILENAME_TEMPLATE,
}: PackageCreatorParams,
{ fromEvent = defaultFromEvent }: PackageCreatorOptions = {}
): Promise<ExtensionBuildResult> {
},
{ fromEvent = defaultFromEvent } = {}
) {
let id;
if (manifestData) {
id = getManifestId(manifestData);
@ -242,25 +195,6 @@ export async function defaultPackageCreator(
// Build command types and implementation.
export type BuildCmdParams = {|
sourceDir: string,
artifactsDir: string,
asNeeded?: boolean,
overwriteDest?: boolean,
ignoreFiles?: Array<string>,
filename?: string,
|};
export type BuildCmdOptions = {
manifestData?: ExtensionManifest,
fileFilter?: FileFilter,
onSourceChange?: OnSourceChangeFn,
packageCreator?: PackageCreatorFn,
showReadyMessage?: boolean,
createFileFilter?: FileFilterCreatorFn,
shouldExitProgram?: boolean,
};
export default async function build(
{
sourceDir,
@ -269,7 +203,7 @@ export default async function build(
overwriteDest = false,
ignoreFiles = [],
filename = DEFAULT_FILENAME_TEMPLATE,
}: BuildCmdParams,
},
{
manifestData,
createFileFilter = defaultFileFilterCreator,
@ -281,8 +215,8 @@ export default async function build(
onSourceChange = defaultSourceWatcher,
packageCreator = defaultPackageCreator,
showReadyMessage = true,
}: BuildCmdOptions = {}
): Promise<ExtensionBuildResult> {
} = {}
) {
const rebuildAsNeeded = asNeeded; // alias for `build --as-needed`
log.info(`Building web extension from ${sourceDir}`);

View File

@ -1,27 +1,14 @@
/* @flow */
import open from 'open';
import { createLogger } from '../util/logger.js';
const log = createLogger(import.meta.url);
export type DocsParams = {
noInput?: boolean,
shouldExitProgram?: boolean,
};
export type DocsOptions = {
openUrl?: typeof open,
};
// eslint-disable-next-line max-len
export const url =
'https://extensionworkshop.com/documentation/develop/getting-started-with-web-ext/';
export default async function docs(
params: DocsParams,
{ openUrl = open }: DocsOptions = {}
): Promise<void> {
export default async function docs(params, { openUrl = open } = {}) {
try {
await openUrl(url);
} catch (error) {

View File

@ -1,54 +1,29 @@
/* @flow */
import type {
BuildCmdParams,
BuildCmdOptions,
ExtensionBuildResult,
} from './build.js';
import type { LintCmdParams, LintCmdOptions } from './lint.js';
import type { CmdRunParams, CmdRunOptions } from './run.js';
import type { MultiExtensionRunner } from '../extension-runners/index.js';
import type { SignParams, SignOptions, SignResult } from './sign.js';
import type { DocsParams, DocsOptions } from './docs.js';
// This module exports entry points for all supported commands. For performance
// reasons (faster start-up), the implementations are not statically imported
// at the top of the file, but lazily loaded in the (exported) functions.
// The latter would slow down start-up by several seconds, as seen in #1302 .
async function build(
params: BuildCmdParams,
options: BuildCmdOptions
): Promise<ExtensionBuildResult> {
async function build(params, options) {
const { default: runCommand } = await import('./build.js');
return runCommand(params, options);
}
async function lint(
params: LintCmdParams,
options: LintCmdOptions
): Promise<void> {
async function lint(params, options) {
const { default: runCommand } = await import('./lint.js');
return runCommand(params, options);
}
async function run(
params: CmdRunParams,
options: CmdRunOptions
): Promise<MultiExtensionRunner> {
async function run(params, options) {
const { default: runCommand } = await import('./run.js');
return runCommand(params, options);
}
async function sign(
params: SignParams,
options: SignOptions
): Promise<SignResult> {
async function sign(params, options) {
const { default: runCommand } = await import('./sign.js');
return runCommand(params, options);
}
async function docs(params: DocsParams, options: DocsOptions): Promise<void> {
async function docs(params, options) {
const { default: runCommand } = await import('./docs.js');
return runCommand(params, options);
}

View File

@ -1,65 +1,12 @@
/* @flow */
import { createInstance as defaultLinterCreator } from 'addons-linter';
import { createLogger } from '../util/logger.js';
import { createFileFilter as defaultFileFilterCreator } from '../util/file-filter.js';
// import flow types
import type { FileFilterCreatorFn } from '../util/file-filter.js';
const log = createLogger(import.meta.url);
// Define the needed 'addons-linter' module flow types.
export type LinterOutputType = 'text' | 'json';
export type LinterCreatorParams = {|
config: {|
logLevel: 'debug' | 'fatal',
stack: boolean,
pretty?: boolean,
warningsAsErrors?: boolean,
metadata?: boolean,
minManifestVersion?: number,
maxManifestVersion?: number,
output?: LinterOutputType,
privileged?: boolean,
boring?: boolean,
selfHosted?: boolean,
shouldScanFile: (fileName: string) => boolean,
_: Array<string>,
|},
runAsBinary: boolean,
|};
export type Linter = {|
run: () => Promise<void>,
|};
export type LinterCreatorFn = (params: LinterCreatorParams) => Linter;
// Lint command types and implementation.
export type LintCmdParams = {|
artifactsDir?: string,
boring?: boolean,
firefoxPreview: Array<string>,
ignoreFiles?: Array<string>,
metadata?: boolean,
output?: LinterOutputType,
pretty?: boolean,
privileged?: boolean,
selfHosted?: boolean,
sourceDir: string,
verbose?: boolean,
warningsAsErrors?: boolean,
|};
export type LintCmdOptions = {
createLinter?: LinterCreatorFn,
createFileFilter?: FileFilterCreatorFn,
shouldExitProgram?: boolean,
};
export default function lint(
{
artifactsDir,
@ -74,13 +21,13 @@ export default function lint(
selfHosted,
verbose,
warningsAsErrors,
}: LintCmdParams,
},
{
createLinter = defaultLinterCreator,
createFileFilter = defaultFileFilterCreator,
shouldExitProgram = true,
}: LintCmdOptions = {}
): Promise<void> {
} = {}
) {
const fileFilter = createFileFilter({ sourceDir, ignoreFiles, artifactsDir });
const config = {

View File

@ -1,4 +1,3 @@
/* @flow */
import { fs } from 'mz';
import defaultBuildExtension from './build.js';
@ -13,60 +12,11 @@ import {
defaultReloadStrategy,
MultiExtensionRunner as DefaultMultiExtensionRunner,
} from '../extension-runners/index.js';
// Import objects that are only used as Flow types.
import type { FirefoxPreferences } from '../firefox/preferences.js';
const log = createLogger(import.meta.url);
// Run command types and implementation.
export type CmdRunParams = {|
artifactsDir: string,
browserConsole: boolean,
devtools: boolean,
pref?: FirefoxPreferences,
firefox: string,
firefoxProfile?: string,
profileCreateIfMissing?: boolean,
ignoreFiles?: Array<string>,
keepProfileChanges: boolean,
noInput?: boolean,
noReload: boolean,
preInstall: boolean,
sourceDir: string,
watchFile?: Array<string>,
watchIgnored?: Array<string>,
startUrl?: Array<string>,
target?: Array<string>,
args?: Array<string>,
firefoxPreview: Array<string>,
// Android CLI options.
adbBin?: string,
adbHost?: string,
adbPort?: string,
adbDevice?: string,
adbDiscoveryTimeout?: number,
adbRemoveOldArtifacts?: boolean,
firefoxApk?: string,
firefoxApkComponent?: string,
// Chromium Desktop CLI options.
chromiumBinary?: string,
chromiumProfile?: string,
|};
export type CmdRunOptions = {
buildExtension: typeof defaultBuildExtension,
desktopNotifications: typeof defaultDesktopNotifications,
firefoxApp: typeof defaultFirefoxApp,
firefoxClient: typeof defaultFirefoxClient,
reloadStrategy: typeof defaultReloadStrategy,
shouldExitProgram?: boolean,
MultiExtensionRunner?: typeof DefaultMultiExtensionRunner,
getValidatedManifest?: typeof defaultGetValidatedManifest,
};
export default async function run(
{
artifactsDir,
@ -100,7 +50,7 @@ export default async function run(
// Chromium CLI options.
chromiumBinary,
chromiumProfile,
}: CmdRunParams,
},
{
buildExtension = defaultBuildExtension,
desktopNotifications = defaultDesktopNotifications,
@ -109,8 +59,8 @@ export default async function run(
reloadStrategy = defaultReloadStrategy,
MultiExtensionRunner = DefaultMultiExtensionRunner,
getValidatedManifest = defaultGetValidatedManifest,
}: CmdRunOptions = {}
): Promise<DefaultMultiExtensionRunner> {
} = {}
) {
log.info(`Running web extension from ${sourceDir}`);
if (preInstall) {
log.info(
@ -130,7 +80,7 @@ export default async function run(
// Create an alias for --pref since it has been transformed into an
// object containing one or more preferences.
const customPrefs: FirefoxPreferences = { ...pref };
const customPrefs = { ...pref };
if (firefoxPreview.includes('mv3')) {
log.info('Configuring Firefox preferences for Manifest V3');
customPrefs['extensions.manifestV3.enabled'] = true;
@ -213,7 +163,7 @@ export default async function run(
firefoxApp,
firefoxClient,
desktopNotifications: defaultDesktopNotifications,
buildSourceDir: (extensionSourceDir: string, tmpArtifactsDir: string) => {
buildSourceDir: (extensionSourceDir, tmpArtifactsDir) => {
return buildExtension(
{
sourceDir: extensionSourceDir,

View File

@ -1,4 +1,3 @@
/* @flow */
import path from 'path';
import { signAddon as defaultAddonSigner } from 'sign-addon';
@ -8,18 +7,14 @@ import { isErrorWithCode, UsageError, WebExtError } from '../errors.js';
import { prepareArtifactsDir } from '../util/artifacts.js';
import { createLogger } from '../util/logger.js';
import getValidatedManifest, { getManifestId } from '../util/manifest.js';
import type { ExtensionManifest } from '../util/manifest.js';
import {
defaultAsyncFsReadFile,
signAddon as defaultSubmitAddonSigner,
saveIdToFile,
} from '../util/submit-addon.js';
import type { SignResult } from '../util/submit-addon.js';
import { withTempDir } from '../util/temp-dir.js';
import { isTTY } from '../util/stdin.js';
export type { SignResult };
const log = createLogger(import.meta.url);
export const extensionIdFile = '.web-extension-id';
@ -27,34 +22,6 @@ export const uploadUuidFile = '.amo-upload-uuid';
// Sign command types and implementation.
export type SignParams = {|
amoBaseUrl: string,
apiKey: string,
apiProxy?: string,
apiSecret: string,
apiUrlPrefix: string,
useSubmissionApi?: boolean,
artifactsDir: string,
id?: string,
ignoreFiles?: Array<string>,
sourceDir: string,
timeout: number,
disableProgressBar?: boolean,
verbose?: boolean,
channel?: string,
amoMetadata?: string,
webextVersion: string,
|};
export type SignOptions = {
build?: typeof defaultBuilder,
signAddon?: typeof defaultAddonSigner,
submitAddon?: typeof defaultSubmitAddonSigner,
preValidatedManifest?: ExtensionManifest,
shouldExitProgram?: boolean,
asyncFsReadFile?: typeof defaultAsyncFsReadFile,
};
export default function sign(
{
amoBaseUrl,
@ -73,15 +40,15 @@ export default function sign(
channel,
amoMetadata,
webextVersion,
}: SignParams,
},
{
build = defaultBuilder,
preValidatedManifest,
signAddon = defaultAddonSigner,
submitAddon = defaultSubmitAddonSigner,
asyncFsReadFile = defaultAsyncFsReadFile,
}: SignOptions = {}
): Promise<SignResult> {
} = {}
) {
return withTempDir(async function (tmpDir) {
await prepareArtifactsDir(artifactsDir);
@ -184,7 +151,6 @@ export default function sign(
result = await submitAddon({
...signSubmitArgs,
amoBaseUrl,
// $FlowIgnore: we verify 'channel' is set above
channel,
savedIdPath,
savedUploadUuidPath,
@ -225,9 +191,9 @@ export default function sign(
}
export async function getIdFromFile(
filePath: string,
asyncFsReadFile: typeof defaultAsyncFsReadFile = defaultAsyncFsReadFile
): Promise<string | void> {
filePath,
asyncFsReadFile = defaultAsyncFsReadFile
) {
let content;
try {

View File

@ -1,4 +1,3 @@
/* @flow */
import os from 'os';
import path from 'path';
@ -12,24 +11,13 @@ import { UsageError, WebExtError } from './errors.js';
const log = createLogger(import.meta.url);
type ApplyConfigToArgvParams = {|
// This is the argv object which will get updated by each
// config applied.
argv: Object,
// This is the argv that only has CLI values applied to it.
argvFromCLI: Object,
configObject: Object,
options: Object,
configFileName: string,
|};
export function applyConfigToArgv({
argv,
argvFromCLI,
configObject,
options,
configFileName,
}: ApplyConfigToArgvParams): Object {
}) {
let newArgv = { ...argv };
for (const option of Object.keys(configObject)) {
@ -127,7 +115,7 @@ export function applyConfigToArgv({
return newArgv;
}
export function loadJSConfigFile(filePath: string): Object {
export function loadJSConfigFile(filePath) {
const resolvedFilePath = path.resolve(filePath);
log.debug(
`Loading JS config file: "${filePath}" ` +
@ -156,13 +144,7 @@ export function loadJSConfigFile(filePath: string): Object {
return configObject;
}
type DiscoverConfigFilesParams = {
getHomeDir: () => string,
};
export async function discoverConfigFiles({
getHomeDir = os.homedir,
}: DiscoverConfigFilesParams = {}): Promise<Array<string>> {
export async function discoverConfigFiles({ getHomeDir = os.homedir } = {}) {
const magicConfigName = 'web-ext-config.js';
// Config files will be loaded in this order.

View File

@ -1,11 +1,10 @@
/* @flow */
import ExtendableError from 'es6-error';
/*
* Base error for all custom web-ext errors.
*/
export class WebExtError extends ExtendableError {
constructor(message: string) {
constructor(message) {
super(message);
}
}
@ -14,7 +13,7 @@ export class WebExtError extends ExtendableError {
* The class for errors that can be fixed by the developer.
*/
export class UsageError extends WebExtError {
constructor(message: string) {
constructor(message) {
super(message);
}
}
@ -23,7 +22,7 @@ export class UsageError extends WebExtError {
* The manifest for the extension is invalid (or missing).
*/
export class InvalidManifest extends UsageError {
constructor(message: string) {
constructor(message) {
super(message);
}
}
@ -32,7 +31,7 @@ export class InvalidManifest extends UsageError {
* The remote Firefox does not support temporary add-on installation.
*/
export class RemoteTempInstallNotSupported extends WebExtError {
constructor(message: string) {
constructor(message) {
super(message);
}
}
@ -42,7 +41,7 @@ export class RemoteTempInstallNotSupported extends WebExtError {
* (initialized from a map of errors by extensionSourceDir string).
*/
export class MultiExtensionsReloadError extends WebExtError {
constructor(errorsMap: Map<string, Error>) {
constructor(errorsMap) {
let errors = '';
for (const [sourceDir, error] of errorsMap) {
const msg = String(error);
@ -68,10 +67,7 @@ export class MultiExtensionsReloadError extends WebExtError {
* All other errors will be re-thrown.
*
*/
export function onlyInstancesOf(
predicate: Function,
errorHandler: Function
): Function {
export function onlyInstancesOf(predicate, errorHandler) {
return (error) => {
if (error instanceof predicate) {
return errorHandler(error);
@ -101,10 +97,7 @@ export function onlyInstancesOf(
* All other errors will be re-thrown.
*
*/
export function onlyErrorsWithCode(
codeWanted: (string | number) | Array<string | number>,
errorHandler: Function
): Function {
export function onlyErrorsWithCode(codeWanted, errorHandler) {
return (error) => {
let throwError = true;
@ -127,10 +120,7 @@ export function onlyErrorsWithCode(
};
}
export function isErrorWithCode(
codeWanted: string | Array<string>,
error: Object
): boolean {
export function isErrorWithCode(codeWanted, error) {
if (Array.isArray(codeWanted) && codeWanted.indexOf(error.code) !== -1) {
return true;
} else if (error.code === codeWanted) {

View File

@ -1,38 +0,0 @@
/* @flow */
import { showDesktopNotification as defaultDesktopNotifications } from '../util/desktop-notifier';
import type { ExtensionManifest } from '../util/manifest';
export type Extension = {|
sourceDir: string,
manifestData: ExtensionManifest,
|};
export type ExtensionRunnerParams = {|
// Common cli params.
extensions: Array<Extension>,
profilePath?: string,
keepProfileChanges: boolean,
startUrl: ?string | ?Array<string>,
args?: Array<string>,
// Common injected dependencies.
desktopNotifications: typeof defaultDesktopNotifications,
|};
export type ExtensionRunnerReloadResult = {|
runnerName: string,
reloadError?: Error,
sourceDir?: string,
|};
export interface IExtensionRunner {
getName(): string;
run(): Promise<void>;
reloadAllExtensions(): Promise<Array<ExtensionRunnerReloadResult>>;
reloadExtensionBySourceDir(
extensionSourceDir: string
): Promise<Array<ExtensionRunnerReloadResult>>;
registerCleanup(fn: Function): void;
exit(): Promise<void>;
}

View File

@ -1,5 +1,3 @@
/* @flow */
/**
* This module provide an ExtensionRunner subclass that manage an extension executed
* in a Chromium-based browser instance.
@ -17,48 +15,31 @@ import WebSocket, { WebSocketServer } from 'ws';
import { createLogger } from '../util/logger.js';
import { TempDir } from '../util/temp-dir.js';
import type {
ExtensionRunnerParams,
ExtensionRunnerReloadResult,
} from './base';
import isDirectory from '../util/is-directory.js';
import fileExists from '../util/file-exists.js';
type ChromiumSpecificRunnerParams = {|
chromiumBinary?: string,
chromiumProfile?: string,
chromiumLaunch?: typeof defaultChromiumLaunch,
|};
export type ChromiumExtensionRunnerParams = {|
...ExtensionRunnerParams,
// Chromium desktop CLI params.
...ChromiumSpecificRunnerParams,
|};
const log = createLogger(import.meta.url);
const EXCLUDED_CHROME_FLAGS = ['--disable-extensions', '--mute-audio'];
export const DEFAULT_CHROME_FLAGS: Array<string> =
ChromeLauncher.defaultFlags().filter(
(flag) => !EXCLUDED_CHROME_FLAGS.includes(flag)
);
export const DEFAULT_CHROME_FLAGS = ChromeLauncher.defaultFlags().filter(
(flag) => !EXCLUDED_CHROME_FLAGS.includes(flag)
);
/**
* Implements an IExtensionRunner which manages a Chromium instance.
*/
export class ChromiumExtensionRunner {
cleanupCallbacks: Set<Function>;
params: ChromiumExtensionRunnerParams;
chromiumInstance: ?ChromeLauncher;
chromiumLaunch: typeof defaultChromiumLaunch;
reloadManagerExtension: string;
wss: ?WebSocketServer;
exiting: boolean;
_promiseSetupDone: ?Promise<void>;
cleanupCallbacks;
params;
chromiumInstance;
chromiumLaunch;
reloadManagerExtension;
wss;
exiting;
_promiseSetupDone;
constructor(params: ChromiumExtensionRunnerParams) {
constructor(params) {
const { chromiumLaunch = defaultChromiumLaunch } = params;
this.params = params;
this.chromiumLaunch = chromiumLaunch;
@ -70,17 +51,17 @@ export class ChromiumExtensionRunner {
/**
* Returns the runner name.
*/
getName(): string {
getName() {
return 'Chromium';
}
async run(): Promise<void> {
async run() {
// Run should never be called more than once.
this._promiseSetupDone = this.setupInstance();
await this._promiseSetupDone;
}
static async isUserDataDir(dirPath: string): Promise<boolean> {
static async isUserDataDir(dirPath) {
const localStatePath = path.join(dirPath, 'Local State');
const defaultPath = path.join(dirPath, 'Default');
// Local State and Default are typical for the user-data-dir
@ -89,16 +70,13 @@ export class ChromiumExtensionRunner {
);
}
static async isProfileDir(dirPath: string): Promise<boolean> {
static async isProfileDir(dirPath) {
const securePreferencesPath = path.join(dirPath, 'Secure Preferences');
//Secure Preferences is typical for a profile dir inside a user data dir
return await fileExists(securePreferencesPath);
}
static async getProfilePaths(chromiumProfile: ?string): Promise<{
userDataDir: ?string,
profileDirName: ?string,
}> {
static async getProfilePaths(chromiumProfile) {
if (!chromiumProfile) {
return {
userDataDir: null,
@ -128,7 +106,7 @@ export class ChromiumExtensionRunner {
/**
* Setup the Chromium Profile and run a Chromium instance.
*/
async setupInstance(): Promise<void> {
async setupInstance() {
// Start a websocket server on a free localhost TCP port.
this.wss = await new Promise((resolve) => {
const server = new WebSocketServer(
@ -244,7 +222,7 @@ export class ChromiumExtensionRunner {
});
}
async wssBroadcast(data: Object): Promise<void> {
async wssBroadcast(data) {
return new Promise((resolve) => {
const clients = this.wss ? new Set(this.wss.clients) : new Set();
@ -283,7 +261,7 @@ export class ChromiumExtensionRunner {
});
}
async createReloadManagerExtension(): Promise<string> {
async createReloadManagerExtension() {
const tmpDir = new TempDir();
await tmpDir.create();
this.registerCleanup(() => tmpDir.remove());
@ -310,7 +288,6 @@ export class ChromiumExtensionRunner {
})
);
// $FlowIgnore: this method is only called right after creating the server and so wss should be defined.
const wssInfo = this.wss.address();
const bgPage = `(function bgPage() {
@ -356,7 +333,7 @@ export class ChromiumExtensionRunner {
* Reloads all the extensions, collect any reload error and resolves to
* an array composed by a single ExtensionRunnerReloadResult object.
*/
async reloadAllExtensions(): Promise<Array<ExtensionRunnerReloadResult>> {
async reloadAllExtensions() {
const runnerName = this.getName();
await this.wssBroadcast({
@ -376,8 +353,8 @@ export class ChromiumExtensionRunner {
* an array composed by a single ExtensionRunnerReloadResult object.
*/
async reloadExtensionBySourceDir(
extensionSourceDir: string // eslint-disable-line no-unused-vars
): Promise<Array<ExtensionRunnerReloadResult>> {
extensionSourceDir // eslint-disable-line no-unused-vars
) {
// TODO(rpl): detect the extension ids assigned to the
// target extensions and map it to the extensions source dir
// (https://github.com/mozilla/web-ext/issues/1687).
@ -389,14 +366,14 @@ export class ChromiumExtensionRunner {
* (e.g. the Chromium instance exits or the user has requested web-ext
* to exit).
*/
registerCleanup(fn: Function): void {
registerCleanup(fn) {
this.cleanupCallbacks.add(fn);
}
/**
* Exits the runner, by closing the managed Chromium instance.
*/
async exit(): Promise<void> {
async exit() {
this.exiting = true;
// Wait for the setup to complete if the extension runner is already

View File

@ -1,5 +1,3 @@
/* @flow */
/**
* This module provide an ExtensionRunner subclass that manage an extension executed
* in a Firefox for Android instance.
@ -10,26 +8,14 @@ import readline from 'readline';
import { withTempDir } from '../util/temp-dir.js';
import DefaultADBUtils from '../util/adb.js';
import { showDesktopNotification as defaultDesktopNotifications } from '../util/desktop-notifier.js';
import {
MultiExtensionsReloadError,
UsageError,
WebExtError,
} from '../errors.js';
import * as defaultFirefoxApp from '../firefox/index.js';
import {
connectWithMaxRetries as defaultFirefoxConnector,
findFreeTcpPort,
} from '../firefox/remote.js';
import { findFreeTcpPort } from '../firefox/remote.js';
import { createLogger } from '../util/logger.js';
import { isTTY, setRawMode } from '../util/stdin.js';
import type {
ExtensionRunnerParams,
ExtensionRunnerReloadResult,
} from './base';
import type { FirefoxPreferences } from '../firefox/preferences';
import type { FirefoxRDPResponseAddon, RemoteFirefox } from '../firefox/remote';
import type { ExtensionBuildResult } from '../cmd/build';
const log = createLogger(import.meta.url);
@ -52,58 +38,29 @@ const getIgnoredParamsWarningsMessage = (optionName) => {
return `The Firefox for Android target does not support ${optionName}`;
};
export type FirefoxAndroidExtensionRunnerParams = {|
...ExtensionRunnerParams,
// Firefox specific.
customPrefs?: FirefoxPreferences,
// Not supported (currently ignored with logged warning).
preInstall?: boolean,
browserConsole?: boolean,
// Firefox android injected dependencies.
adbBin?: string,
adbHost?: string,
adbPort?: string,
adbDevice?: string,
adbDiscoveryTimeout?: number,
adbRemoveOldArtifacts?: boolean,
firefoxApk?: string,
firefoxApkComponent?: string,
// Injected Dependencies.
firefoxApp: typeof defaultFirefoxApp,
firefoxClient: typeof defaultFirefoxConnector,
ADBUtils?: typeof DefaultADBUtils,
buildSourceDir: (string, string) => Promise<ExtensionBuildResult>,
desktopNotifications: typeof defaultDesktopNotifications,
stdin?: stream$Readable,
|};
/**
* Implements an IExtensionRunner which manages a Firefox for Android instance.
*/
export class FirefoxAndroidExtensionRunner {
// Wait 3s before the next unix socket discovery loop.
static unixSocketDiscoveryRetryInterval: number = 3 * 1000;
static unixSocketDiscoveryRetryInterval = 3 * 1000;
// Wait for at most 3 minutes before giving up.
static unixSocketDiscoveryMaxTime: number = 3 * 60 * 1000;
static unixSocketDiscoveryMaxTime = 3 * 60 * 1000;
params: FirefoxAndroidExtensionRunnerParams;
adbUtils: DefaultADBUtils;
exiting: boolean;
selectedAdbDevice: string;
selectedFirefoxApk: string;
selectedArtifactsDir: string;
selectedRDPSocketFile: string;
selectedTCPPort: number;
cleanupCallbacks: Set<Function>;
adbExtensionsPathBySourceDir: Map<string, string>;
reloadableExtensions: Map<string, string>;
remoteFirefox: RemoteFirefox;
params;
adbUtils;
exiting;
selectedAdbDevice;
selectedFirefoxApk;
selectedArtifactsDir;
selectedRDPSocketFile;
selectedTCPPort;
cleanupCallbacks;
adbExtensionsPathBySourceDir;
reloadableExtensions;
remoteFirefox;
constructor(params: FirefoxAndroidExtensionRunnerParams) {
constructor(params) {
this.params = params;
this.cleanupCallbacks = new Set();
this.adbExtensionsPathBySourceDir = new Map();
@ -114,7 +71,7 @@ export class FirefoxAndroidExtensionRunner {
this.printIgnoredParamsWarnings();
}
async run(): Promise<void> {
async run() {
const {
adbBin,
adbHost = DEFAULT_ADB_HOST,
@ -167,7 +124,7 @@ export class FirefoxAndroidExtensionRunner {
/**
* Returns the runner name.
*/
getName(): string {
getName() {
return 'Firefox Android';
}
@ -175,7 +132,7 @@ export class FirefoxAndroidExtensionRunner {
* Reloads all the extensions, collect any reload error and resolves to
* an array composed by a single ExtensionRunnerReloadResult object.
*/
async reloadAllExtensions(): Promise<Array<ExtensionRunnerReloadResult>> {
async reloadAllExtensions() {
const runnerName = this.getName();
const reloadErrors = new Map();
@ -202,9 +159,7 @@ export class FirefoxAndroidExtensionRunner {
* Reloads a single extension, collect any reload error and resolves to
* an array composed by a single ExtensionRunnerReloadResult object.
*/
async reloadExtensionBySourceDir(
extensionSourceDir: string
): Promise<Array<ExtensionRunnerReloadResult>> {
async reloadExtensionBySourceDir(extensionSourceDir) {
const runnerName = this.getName();
const addonId = this.reloadableExtensions.get(extensionSourceDir);
@ -242,14 +197,14 @@ export class FirefoxAndroidExtensionRunner {
* (e.g. the Firefox instance exits or the user has requested web-ext
* to exit).
*/
registerCleanup(fn: Function): void {
registerCleanup(fn) {
this.cleanupCallbacks.add(fn);
}
/**
* Exits the runner, by closing the managed Firefox instance.
*/
async exit(): Promise<void> {
async exit() {
const { adbUtils, selectedAdbDevice, selectedArtifactsDir } = this;
this.exiting = true;
@ -275,7 +230,7 @@ export class FirefoxAndroidExtensionRunner {
// Private helper methods.
getDeviceProfileDir(): string {
getDeviceProfileDir() {
return `${this.selectedArtifactsDir}/profile`;
}
@ -496,7 +451,7 @@ export class FirefoxAndroidExtensionRunner {
);
}
async buildAndPushExtension(sourceDir: string) {
async buildAndPushExtension(sourceDir) {
const {
adbUtils,
selectedAdbDevice,
@ -637,7 +592,7 @@ export class FirefoxAndroidExtensionRunner {
const addonId = await remoteFirefox
.installTemporaryAddon(adbExtensionPath)
.then((installResult: FirefoxRDPResponseAddon) => {
.then((installResult) => {
return installResult.addon.id;
});

View File

@ -1,47 +1,14 @@
/* @flow */
/**
* This module provide an ExtensionRunner subclass that manage an extension executed
* in a Firefox for Desktop instance.
*/
// Import flow types from npm dependencies.
import type FirefoxProfile from 'firefox-profile';
import {
MultiExtensionsReloadError,
RemoteTempInstallNotSupported,
WebExtError,
} from '../errors.js';
import * as defaultFirefoxApp from '../firefox/index.js';
import { connectWithMaxRetries as defaultFirefoxConnector } from '../firefox/remote.js';
import { createLogger } from '../util/logger.js';
// Import flow types from project files.
import type { FirefoxRDPResponseAddon, RemoteFirefox } from '../firefox/remote';
import type {
ExtensionRunnerParams,
ExtensionRunnerReloadResult,
} from './base';
import type { FirefoxPreferences } from '../firefox/preferences';
import type { FirefoxInfo } from '../firefox/index'; // eslint-disable-line import/named
type FirefoxDesktopSpecificRunnerParams = {|
customPrefs?: FirefoxPreferences,
browserConsole: boolean,
devtools: boolean,
firefoxBinary: string,
preInstall: boolean,
// Firefox desktop injected dependencies.
firefoxApp: typeof defaultFirefoxApp,
firefoxClient: typeof defaultFirefoxConnector,
|};
export type FirefoxDesktopExtensionRunnerParams = {|
...ExtensionRunnerParams,
// Firefox desktop CLI params.
...FirefoxDesktopSpecificRunnerParams,
|};
const log = createLogger(import.meta.url);
@ -49,15 +16,15 @@ const log = createLogger(import.meta.url);
* Implements an IExtensionRunner which manages a Firefox Desktop instance.
*/
export class FirefoxDesktopExtensionRunner {
cleanupCallbacks: Set<Function>;
params: FirefoxDesktopExtensionRunnerParams;
profile: FirefoxProfile;
cleanupCallbacks;
params;
profile;
// Map extensions sourceDir to their related addon ids.
reloadableExtensions: Map<string, string>;
remoteFirefox: RemoteFirefox;
runningInfo: FirefoxInfo;
reloadableExtensions;
remoteFirefox;
runningInfo;
constructor(params: FirefoxDesktopExtensionRunnerParams) {
constructor(params) {
this.params = params;
this.reloadableExtensions = new Map();
@ -69,14 +36,14 @@ export class FirefoxDesktopExtensionRunner {
/**
* Returns the runner name.
*/
getName(): string {
getName() {
return 'Firefox Desktop';
}
/**
* Setup the Firefox Profile and run a Firefox Desktop instance.
*/
async run(): Promise<void> {
async run() {
// Get a firefox profile with the custom Prefs set (a new or a cloned one).
// Pre-install extensions as proxy if needed (and disable auto-reload if you do)
await this.setupProfileDir();
@ -92,7 +59,7 @@ export class FirefoxDesktopExtensionRunner {
* Reloads all the extensions, collect any reload error and resolves to
* an array composed by a single ExtensionRunnerReloadResult object.
*/
async reloadAllExtensions(): Promise<Array<ExtensionRunnerReloadResult>> {
async reloadAllExtensions() {
const runnerName = this.getName();
const reloadErrors = new Map();
for (const { sourceDir } of this.params.extensions) {
@ -118,9 +85,7 @@ export class FirefoxDesktopExtensionRunner {
* Reloads a single extension, collect any reload error and resolves to
* an array composed by a single ExtensionRunnerReloadResult object.
*/
async reloadExtensionBySourceDir(
extensionSourceDir: string
): Promise<Array<ExtensionRunnerReloadResult>> {
async reloadExtensionBySourceDir(extensionSourceDir) {
const runnerName = this.getName();
const addonId = this.reloadableExtensions.get(extensionSourceDir);
@ -157,14 +122,14 @@ export class FirefoxDesktopExtensionRunner {
* (e.g. the Firefox instance exits or the user has requested web-ext
* to exit).
*/
registerCleanup(fn: Function): void {
registerCleanup(fn) {
this.cleanupCallbacks.add(fn);
}
/**
* Exits the runner, by closing the managed Firefox instance.
*/
async exit(): Promise<void> {
async exit() {
if (!this.runningInfo || !this.runningInfo.firefox) {
throw new WebExtError('No firefox instance is currently running');
}
@ -270,7 +235,7 @@ export class FirefoxDesktopExtensionRunner {
try {
const addonId = await remoteFirefox
.installTemporaryAddon(extension.sourceDir, devtools)
.then((installResult: FirefoxRDPResponseAddon) => {
.then((installResult) => {
return installResult.addon.id;
});

View File

@ -1,49 +1,14 @@
/* @flow */
import readline from 'readline';
import type Watchpack from 'watchpack';
import type {
IExtensionRunner, // eslint-disable-line import/named
ExtensionRunnerReloadResult,
} from './base';
import { WebExtError } from '../errors.js';
import { showDesktopNotification as defaultDesktopNotifications } from '../util/desktop-notifier.js';
import type { FirefoxAndroidExtensionRunnerParams } from './firefox-android.js';
import type { FirefoxDesktopExtensionRunnerParams } from './firefox-desktop.js';
import type { ChromiumExtensionRunnerParams } from './chromium.js';
import { createLogger } from '../util/logger.js';
import type { FileFilterCreatorFn } from '../util/file-filter.js';
import { createFileFilter as defaultFileFilterCreator } from '../util/file-filter.js';
import { isTTY, setRawMode } from '../util/stdin.js';
import defaultSourceWatcher from '../watcher.js';
import type { OnSourceChangeFn } from '../watcher';
const log = createLogger(import.meta.url);
export type ExtensionRunnerConfig =
| {|
target: 'firefox-desktop',
params: FirefoxDesktopExtensionRunnerParams,
|}
| {|
target: 'firefox-android',
params: FirefoxAndroidExtensionRunnerParams,
|}
| {|
target: 'chromium',
params: ChromiumExtensionRunnerParams,
|};
export type MultiExtensionRunnerParams = {|
runners: Array<IExtensionRunner>,
desktopNotifications: typeof defaultDesktopNotifications,
|};
export async function createExtensionRunner(
config: ExtensionRunnerConfig
): Promise<IExtensionRunner> {
export async function createExtensionRunner(config) {
switch (config.target) {
case 'firefox-desktop': {
const { FirefoxDesktopExtensionRunner } = await import(
@ -72,10 +37,10 @@ export async function createExtensionRunner(
* a Firefox Desktop instance alongside to a Firefox for Android instance).
*/
export class MultiExtensionRunner {
extensionRunners: Array<IExtensionRunner>;
desktopNotifications: typeof defaultDesktopNotifications;
extensionRunners;
desktopNotifications;
constructor(params: MultiExtensionRunnerParams) {
constructor(params) {
this.extensionRunners = params.runners;
this.desktopNotifications = params.desktopNotifications;
}
@ -85,7 +50,7 @@ export class MultiExtensionRunner {
/**
* Returns the runner name.
*/
getName(): string {
getName() {
return 'Multi Extension Runner';
}
@ -93,7 +58,7 @@ export class MultiExtensionRunner {
* Call the `run` method on all the managed extension runners,
* and awaits that all the runners has been successfully started.
*/
async run(): Promise<void> {
async run() {
const promises = [];
for (const runner of this.extensionRunners) {
promises.push(runner.run());
@ -110,7 +75,7 @@ export class MultiExtensionRunner {
* Any detected reload error is also logged on the terminal and shows as a
* desktop notification.
*/
async reloadAllExtensions(): Promise<Array<ExtensionRunnerReloadResult>> {
async reloadAllExtensions() {
log.debug('Reloading all reloadable add-ons');
const promises = [];
@ -144,12 +109,10 @@ export class MultiExtensionRunner {
* Any detected reload error is also logged on the terminal and shows as a
* desktop notification.
*/
async reloadExtensionBySourceDir(
sourceDir: string
): Promise<Array<ExtensionRunnerReloadResult>> {
async reloadExtensionBySourceDir(sourceDir) {
log.debug(`Reloading add-on at ${sourceDir}`);
const promises: Array<Promise<ExtensionRunnerReloadResult>> = [];
const promises = [];
for (const runner of this.extensionRunners) {
const reloadPromise = runner.reloadExtensionBySourceDir(sourceDir).then(
() => {
@ -176,7 +139,7 @@ export class MultiExtensionRunner {
/**
* Register a callback to be called when all the managed runners has been exited.
*/
registerCleanup(cleanupCallback: Function): void {
registerCleanup(cleanupCallback) {
const promises = [];
// Create a promise for every extension runner managed by this instance,
@ -199,7 +162,7 @@ export class MultiExtensionRunner {
/**
* Exits all the managed runner has been exited.
*/
async exit(): Promise<void> {
async exit() {
const promises = [];
for (const runner of this.extensionRunners) {
promises.push(runner.exit());
@ -210,7 +173,7 @@ export class MultiExtensionRunner {
// Private helper methods.
handleReloadResults(results: Array<ExtensionRunnerReloadResult>): void {
handleReloadResults(results) {
for (const { runnerName, reloadError, sourceDir } of results) {
if (reloadError instanceof Error) {
let message = 'Error occurred while reloading';
@ -234,19 +197,6 @@ export class MultiExtensionRunner {
// defaultWatcherCreator types and implementation.
export type WatcherCreatorParams = {|
reloadExtension: (string) => void,
sourceDir: string,
watchFile?: Array<string>,
watchIgnored?: Array<string>,
artifactsDir: string,
onSourceChange?: OnSourceChangeFn,
ignoreFiles?: Array<string>,
createFileFilter?: FileFilterCreatorFn,
|};
export type WatcherCreatorFn = (params: WatcherCreatorParams) => Watchpack;
export function defaultWatcherCreator({
reloadExtension,
sourceDir,
@ -256,7 +206,7 @@ export function defaultWatcherCreator({
ignoreFiles,
onSourceChange = defaultSourceWatcher,
createFileFilter = defaultFileFilterCreator,
}: WatcherCreatorParams): Watchpack {
}) {
const fileFilter = createFileFilter({ sourceDir, artifactsDir, ignoreFiles });
return onSourceChange({
sourceDir,
@ -270,22 +220,6 @@ export function defaultWatcherCreator({
// defaultReloadStrategy types and implementation.
export type ReloadStrategyParams = {|
extensionRunner: IExtensionRunner,
sourceDir: string,
watchFile?: Array<string>,
watchIgnored?: Array<string>,
artifactsDir: string,
ignoreFiles?: Array<string>,
noInput?: boolean,
|};
export type ReloadStrategyOptions = {
createWatcher?: WatcherCreatorFn,
stdin?: stream$Readable,
kill?: (pid: number, signal?: string | number) => void,
};
export function defaultReloadStrategy(
{
artifactsDir,
@ -295,20 +229,19 @@ export function defaultReloadStrategy(
sourceDir,
watchFile,
watchIgnored,
}: ReloadStrategyParams,
},
{
createWatcher = defaultWatcherCreator,
stdin = process.stdin,
// $FlowIgnore: ignore method-unbinding.
kill = process.kill,
}: ReloadStrategyOptions = {}
): void {
} = {}
) {
const allowInput = !noInput;
if (!allowInput) {
log.debug('Input has been disabled because of noInput==true');
}
const watcher: Watchpack = createWatcher({
const watcher = createWatcher({
reloadExtension: (watchedSourceDir) => {
extensionRunner.reloadExtensionBySourceDir(watchedSourceDir);
},

View File

@ -1,4 +1,3 @@
/* @flow */
import nodeFs from 'fs';
import path from 'path';
import { promisify } from 'util';
@ -14,18 +13,10 @@ import { getPrefs as defaultPrefGetter } from './preferences.js';
import { getManifestId } from '../util/manifest.js';
import { findFreeTcpPort as defaultRemotePortFinder } from './remote.js';
import { createLogger } from '../util/logger.js';
// Import flow types
import type {
PreferencesAppName,
PreferencesGetterFn,
FirefoxPreferences,
} from './preferences';
import type { ExtensionManifest } from '../util/manifest.js';
import type { Extension } from '../extension-runners/base.js';
const log = createLogger(import.meta.url);
const defaultAsyncFsStat: typeof fs.stat = fs.stat.bind(fs);
const defaultAsyncFsStat = fs.stat.bind(fs);
const defaultUserProfileCopier = FirefoxProfile.copyFromUserProfile;
@ -34,67 +25,11 @@ export const defaultFirefoxEnv = {
NS_TRACE_MALLOC_DISABLE_STACKS: '1',
};
// defaultRemotePortFinder types and implementation.
export type RemotePortFinderFn = () => Promise<number>;
// Declare the needed 'fx-runner' module flow types.
export type FirefoxRunnerParams = {|
binary: ?string,
profile?: string,
'new-instance'?: boolean,
'no-remote'?: boolean,
foreground?: boolean,
listen: number,
'binary-args'?: Array<string> | string,
'binary-args-first'?: boolean,
env?: {
// This match the flowtype signature for process.env (and prevent flow
// from complaining about differences between their type signature)
[key: string]: string | void,
},
verbose?: boolean,
|};
export interface FirefoxProcess extends events$EventEmitter {
stderr: events$EventEmitter;
stdout: events$EventEmitter;
kill: Function;
}
export type FirefoxRunnerResults = {|
process: FirefoxProcess,
binary: string,
args: Array<string>,
|};
export type FirefoxRunnerFn = (
params: FirefoxRunnerParams
) => Promise<FirefoxRunnerResults>;
export type FirefoxInfo = {|
firefox: FirefoxProcess,
debuggerPort: number,
|};
// Run command types and implementaion.
export type FirefoxRunOptions = {
fxRunner?: FirefoxRunnerFn,
findRemotePort?: RemotePortFinderFn,
firefoxBinary?: string,
binaryArgs?: Array<string>,
args?: Array<any>,
extensions: Array<Extension>,
devtools: boolean,
};
/*
* Runs Firefox with the given profile object and resolves a promise on exit.
*/
export async function run(
profile: FirefoxProfile,
profile,
{
fxRunner = defaultFxRunner,
findRemotePort = defaultRemotePortFinder,
@ -102,8 +37,8 @@ export async function run(
binaryArgs,
extensions,
devtools,
}: FirefoxRunOptions = {}
): Promise<FirefoxInfo> {
} = {}
) {
log.debug(`Running Firefox with profile at ${profile.path()}`);
const remotePort = await findRemotePort();
@ -189,12 +124,6 @@ export async function run(
const DEFAULT_PROFILES_NAMES = ['default', 'dev-edition-default'];
export type IsDefaultProfileFn = (
profilePathOrName: string,
ProfileFinder?: typeof FirefoxProfile.Finder,
fsStat?: typeof fs.stat
) => Promise<boolean>;
/*
* Tests if a profile is a default Firefox profile (both as a profile name or
* profile path).
@ -202,10 +131,10 @@ export type IsDefaultProfileFn = (
* Returns a promise that resolves to true if the profile is one of default Firefox profile.
*/
export async function isDefaultProfile(
profilePathOrName: string,
ProfileFinder?: typeof FirefoxProfile.Finder = FirefoxProfile.Finder,
fsStat?: typeof fs.stat = fs.stat
): Promise<boolean> {
profilePathOrName,
ProfileFinder = FirefoxProfile.Finder,
fsStat = fs.stat
) {
if (DEFAULT_PROFILES_NAMES.includes(profilePathOrName)) {
return true;
}
@ -270,17 +199,6 @@ export async function isDefaultProfile(
// configureProfile types and implementation.
export type ConfigureProfileOptions = {
app?: PreferencesAppName,
getPrefs?: PreferencesGetterFn,
customPrefs?: FirefoxPreferences,
};
export type ConfigureProfileFn = (
profile: FirefoxProfile,
options?: ConfigureProfileOptions
) => Promise<FirefoxProfile>;
/*
* Configures a profile with common preferences that are required to
* activate extension development.
@ -288,13 +206,9 @@ export type ConfigureProfileFn = (
* Returns a promise that resolves with the original profile object.
*/
export function configureProfile(
profile: FirefoxProfile,
{
app = 'firefox',
getPrefs = defaultPrefGetter,
customPrefs = {},
}: ConfigureProfileOptions = {}
): Promise<FirefoxProfile> {
profile,
{ app = 'firefox', getPrefs = defaultPrefGetter, customPrefs = {} } = {}
) {
// Set default preferences. Some of these are required for the add-on to
// operate, such as disabling signatures.
const prefs = getPrefs(app);
@ -312,21 +226,14 @@ export function configureProfile(
return Promise.resolve(profile);
}
export type getProfileFn = (profileName: string) => Promise<string | void>;
export type CreateProfileFinderParams = {
userDirectoryPath?: string,
FxProfile?: typeof FirefoxProfile,
};
export function defaultCreateProfileFinder({
userDirectoryPath,
FxProfile = FirefoxProfile,
}: CreateProfileFinderParams = {}): getProfileFn {
} = {}) {
const finder = new FxProfile.Finder(userDirectoryPath);
const readProfiles = promisify((...args) => finder.readProfiles(...args));
const getPath = promisify((...args) => finder.getPath(...args));
return async (profileName: string): Promise<string | void> => {
return async (profileName) => {
try {
await readProfiles();
const hasProfileName =
@ -346,26 +253,18 @@ export function defaultCreateProfileFinder({
// useProfile types and implementation.
export type UseProfileParams = {
app?: PreferencesAppName,
configureThisProfile?: ConfigureProfileFn,
isFirefoxDefaultProfile?: IsDefaultProfileFn,
customPrefs?: FirefoxPreferences,
createProfileFinder?: typeof defaultCreateProfileFinder,
};
// Use the target path as a Firefox profile without cloning it
export async function useProfile(
profilePath: string,
profilePath,
{
app,
configureThisProfile = configureProfile,
isFirefoxDefaultProfile = isDefaultProfile,
customPrefs = {},
createProfileFinder = defaultCreateProfileFinder,
}: UseProfileParams = {}
): Promise<FirefoxProfile> {
} = {}
) {
const isForbiddenProfile = await isFirefoxDefaultProfile(profilePath);
if (isForbiddenProfile) {
throw new UsageError(
@ -400,12 +299,6 @@ export async function useProfile(
// createProfile types and implementation.
export type CreateProfileParams = {
app?: PreferencesAppName,
configureThisProfile?: ConfigureProfileFn,
customPrefs?: FirefoxPreferences,
};
/*
* Creates a new temporary profile and resolves with the profile object.
*
@ -415,20 +308,13 @@ export async function createProfile({
app,
configureThisProfile = configureProfile,
customPrefs = {},
}: CreateProfileParams = {}): Promise<FirefoxProfile> {
} = {}) {
const profile = new FirefoxProfile();
return await configureThisProfile(profile, { app, customPrefs });
}
// copyProfile types and implementation.
export type CopyProfileOptions = {
app?: PreferencesAppName,
configureThisProfile?: ConfigureProfileFn,
copyFromUserProfile?: Function,
customPrefs?: FirefoxPreferences,
};
/*
* Copies an existing Firefox profile and creates a new temporary profile.
* The new profile will be configured with some preferences required to
@ -442,14 +328,14 @@ export type CopyProfileOptions = {
* one that exists in the current user's Firefox directory.
*/
export async function copyProfile(
profileDirectory: string,
profileDirectory,
{
app,
configureThisProfile = configureProfile,
copyFromUserProfile = defaultUserProfileCopier,
customPrefs = {},
}: CopyProfileOptions = {}
): Promise<FirefoxProfile> {
} = {}
) {
const copy = promisify(FirefoxProfile.copy);
const copyByName = promisify(copyFromUserProfile);
@ -476,14 +362,6 @@ export async function copyProfile(
// installExtension types and implementation.
export type InstallExtensionParams = {|
asProxy?: boolean,
manifestData: ExtensionManifest,
profile: FirefoxProfile,
extensionPath: string,
asyncFsStat?: typeof defaultAsyncFsStat,
|};
/*
* Installs an extension into the given Firefox profile object.
* Resolves when complete.
@ -501,7 +379,7 @@ export async function installExtension({
profile,
extensionPath,
asyncFsStat = defaultAsyncFsStat,
}: InstallExtensionParams): Promise<any> {
}) {
// This more or less follows
// https://github.com/saadtazi/firefox-profile-js/blob/master/lib/firefox_profile.js#L531
// (which is broken for web extensions).

View File

@ -1,5 +1,3 @@
/* @flow */
// This list should have more specific identifiers listed first because of the
// logic in `src/util/adb.js`, e.g. `some.id.debug` is more specific than `some.id`.
export default [

View File

@ -1,4 +1,3 @@
/* @flow */
import { WebExtError, UsageError } from '../errors.js';
import { createLogger } from '../util/logger.js';
@ -10,17 +9,9 @@ export const nonOverridablePreferences = [
'xpinstall.signatures.required',
];
// Flow Types
export type FirefoxPreferences = {
[key: string]: boolean | string | number,
};
export type PreferencesAppName = 'firefox' | 'fennec';
// Preferences Maps
const prefsCommon: FirefoxPreferences = {
const prefsCommon = {
// Allow debug output via dump to be printed to the system console
'browser.dom.window.dump.enabled': true,
@ -69,14 +60,14 @@ const prefsCommon: FirefoxPreferences = {
};
// Prefs specific to Firefox for Android.
const prefsFennec: FirefoxPreferences = {
const prefsFennec = {
'browser.console.showInPanel': true,
'browser.firstrun.show.uidiscovery': false,
'devtools.remote.usb.enabled': true,
};
// Prefs specific to Firefox for desktop.
const prefsFirefox: FirefoxPreferences = {
const prefsFirefox = {
'browser.startup.homepage': 'about:blank',
'startup.homepage_welcome_url': 'about:blank',
'startup.homepage_welcome_url.additional': '',
@ -114,13 +105,7 @@ const prefs = {
// Module exports
export type PreferencesGetterFn = (
appName: PreferencesAppName
) => FirefoxPreferences;
export function getPrefs(
app: PreferencesAppName = 'firefox'
): FirefoxPreferences {
export function getPrefs(app = 'firefox') {
const appPrefs = prefs[app];
if (!appPrefs) {
throw new WebExtError(`Unsupported application: ${app}`);
@ -131,9 +116,7 @@ export function getPrefs(
};
}
export function coerceCLICustomPreference(
cliPrefs: Array<string>
): FirefoxPreferences {
export function coerceCLICustomPreference(cliPrefs) {
const customPrefs = {};
for (const pref of cliPrefs) {

View File

@ -1,30 +1,7 @@
/* @flow */
import net from 'net';
import EventEmitter from 'events';
import domain from 'domain';
export type RDPRequest = {
to: string,
type: string,
};
export type RDPResult = {
from: string,
type: string,
};
export type Deferred = {|
resolve: Function,
reject: Function,
|};
type ParseResult = {|
data: Buffer,
rdpMessage?: Object,
error?: Error,
fatal?: boolean,
|};
export const DEFAULT_PORT = 6000;
export const DEFAULT_HOST = '127.0.0.1';
@ -41,7 +18,7 @@ const UNSOLICITED_EVENTS = new Set([
]);
// Parse RDP packets: BYTE_LENGTH + ':' + DATA.
export function parseRDPMessage(data: Buffer): ParseResult {
export function parseRDPMessage(data) {
const str = data.toString();
const sepIdx = str.indexOf(':');
if (sepIdx < 1) {
@ -70,20 +47,20 @@ export function parseRDPMessage(data: Buffer): ParseResult {
}
}
export function connectToFirefox(port: number): Promise<FirefoxRDPClient> {
export function connectToFirefox(port) {
const client = new FirefoxRDPClient();
return client.connect(port).then(() => client);
}
export default class FirefoxRDPClient extends EventEmitter {
_incoming: Buffer;
_pending: Array<{| request: RDPRequest, deferred: Deferred |}>;
_active: Map<string, Deferred>;
_rdpConnection: net.Socket;
_onData: Function;
_onError: Function;
_onEnd: Function;
_onTimeout: Function;
_incoming;
_pending;
_active;
_rdpConnection;
_onData;
_onError;
_onEnd;
_onTimeout;
constructor() {
super();
@ -97,7 +74,7 @@ export default class FirefoxRDPClient extends EventEmitter {
this._onTimeout = (...args) => this.onTimeout(...args);
}
connect(port: number): Promise<void> {
connect(port) {
return new Promise((resolve, reject) => {
// Create a domain to wrap the errors that may be triggered
// by creating the client connection (e.g. ECONNREFUSED)
@ -124,7 +101,7 @@ export default class FirefoxRDPClient extends EventEmitter {
});
}
disconnect(): void {
disconnect() {
if (!this._rdpConnection) {
return;
}
@ -139,7 +116,7 @@ export default class FirefoxRDPClient extends EventEmitter {
this._rejectAllRequests(new Error('RDP connection closed'));
}
_rejectAllRequests(error: Error) {
_rejectAllRequests(error) {
for (const activeDeferred of this._active.values()) {
activeDeferred.reject(error);
}
@ -151,8 +128,8 @@ export default class FirefoxRDPClient extends EventEmitter {
this._pending = [];
}
async request(requestProps: string | RDPRequest): Promise<RDPResult> {
let request: RDPRequest;
async request(requestProps) {
let request;
if (typeof requestProps === 'string') {
request = { to: 'root', type: requestProps };
@ -173,7 +150,7 @@ export default class FirefoxRDPClient extends EventEmitter {
});
}
_flushPendingRequests(): void {
_flushPendingRequests() {
this._pending = this._pending.filter(({ request, deferred }) => {
if (this._active.has(request.to)) {
// Keep in the pending requests until there are no requests
@ -200,7 +177,7 @@ export default class FirefoxRDPClient extends EventEmitter {
});
}
_expectReply(targetActor: string, deferred: Deferred): void {
_expectReply(targetActor, deferred) {
if (this._active.has(targetActor)) {
throw new Error(`${targetActor} does already have an active request`);
}
@ -208,7 +185,7 @@ export default class FirefoxRDPClient extends EventEmitter {
this._active.set(targetActor, deferred);
}
_handleMessage(rdpData: Object): void {
_handleMessage(rdpData) {
if (rdpData.from == null) {
if (rdpData.error) {
this.emit('rdp-error', rdpData);
@ -249,7 +226,7 @@ export default class FirefoxRDPClient extends EventEmitter {
);
}
_readMessage(): boolean {
_readMessage() {
const { data, rdpMessage, error, fatal } = parseRDPMessage(this._incoming);
this._incoming = data;
@ -279,7 +256,7 @@ export default class FirefoxRDPClient extends EventEmitter {
return true;
}
onData(data: Buffer) {
onData(data) {
this._incoming = Buffer.concat([this._incoming, data]);
while (this._readMessage()) {
// Keep parsing and handling messages until readMessage
@ -287,7 +264,7 @@ export default class FirefoxRDPClient extends EventEmitter {
}
}
onError(error: Error) {
onError(error) {
this.emit('error', error);
}

View File

@ -1,9 +1,6 @@
/* @flow */
import net from 'net';
import FirefoxRDPClient, {
connectToFirefox as defaultFirefoxConnector,
} from './rdp-client.js';
import { connectToFirefox as defaultFirefoxConnector } from './rdp-client.js';
import { createLogger } from '../util/logger.js';
import {
isErrorWithCode,
@ -14,35 +11,10 @@ import {
const log = createLogger(import.meta.url);
export type FirefoxConnectorFn = (port: number) => Promise<FirefoxRDPClient>;
export type FirefoxRDPAddonActor = {|
id: string,
actor: string,
|};
export type FirefoxRDPResponseError = {|
error: string,
message: string,
|};
export type FirefoxRDPResponseAddon = {|
addon: FirefoxRDPAddonActor,
|};
export type FirefoxRDPResponseRequestTypes = {|
requestTypes: Array<string>,
|};
// NOTE: this type aliases Object to catch any other possible response.
export type FirefoxRDPResponseAny = Object;
export type FirefoxRDPResponseMaybe =
| FirefoxRDPResponseRequestTypes
| FirefoxRDPResponseAny;
// Convert a request rejection to a message string.
function requestErrorToMessage(err: Error | FirefoxRDPResponseError) {
function requestErrorToMessage(err) {
if (err instanceof Error) {
return String(err);
}
@ -50,10 +22,10 @@ function requestErrorToMessage(err: Error | FirefoxRDPResponseError) {
}
export class RemoteFirefox {
client: Object;
checkedForAddonReloading: boolean;
client;
checkedForAddonReloading;
constructor(client: FirefoxRDPClient) {
constructor(client) {
this.client = client;
this.checkedForAddonReloading = false;
@ -78,10 +50,7 @@ export class RemoteFirefox {
this.client.disconnect();
}
async addonRequest(
addon: FirefoxRDPAddonActor,
request: string
): Promise<FirefoxRDPResponseMaybe> {
async addonRequest(addon, request) {
try {
const response = await this.client.request({
to: addon.actor,
@ -95,7 +64,7 @@ export class RemoteFirefox {
}
}
async getAddonsActor(): Promise<string> {
async getAddonsActor() {
try {
// getRoot should work since Firefox 55 (bug 1352157).
const response = await this.client.request('getRoot');
@ -137,10 +106,7 @@ export class RemoteFirefox {
}
}
async installTemporaryAddon(
addonPath: string,
openDevTools?: boolean
): Promise<FirefoxRDPResponseAddon> {
async installTemporaryAddon(addonPath, openDevTools) {
const addonsActor = await this.getAddonsActor();
try {
@ -159,7 +125,7 @@ export class RemoteFirefox {
}
}
async getInstalledAddon(addonId: string): Promise<FirefoxRDPAddonActor> {
async getInstalledAddon(addonId) {
try {
const response = await this.client.request('listAddons');
for (const addon of response.addons) {
@ -181,9 +147,7 @@ export class RemoteFirefox {
}
}
async checkForAddonReloading(
addon: FirefoxRDPAddonActor
): Promise<FirefoxRDPAddonActor> {
async checkForAddonReloading(addon) {
if (this.checkedForAddonReloading) {
// We only need to check once if reload() is supported.
return addon;
@ -204,7 +168,7 @@ export class RemoteFirefox {
}
}
async reloadAddon(addonId: string): Promise<void> {
async reloadAddon(addonId) {
const addon = await this.getInstalledAddon(addonId);
await this.checkForAddonReloading(addon);
await this.addonRequest(addon, 'reload');
@ -217,14 +181,10 @@ export class RemoteFirefox {
// Connect types and implementation
export type ConnectOptions = {
connectToFirefox: FirefoxConnectorFn,
};
export async function connect(
port: number,
{ connectToFirefox = defaultFirefoxConnector }: ConnectOptions = {}
): Promise<RemoteFirefox> {
port,
{ connectToFirefox = defaultFirefoxConnector } = {}
) {
log.debug(`Connecting to Firefox on port ${port}`);
const client = await connectToFirefox(port);
log.debug(`Connected to the remote Firefox debugger on port ${port}`);
@ -233,21 +193,11 @@ export async function connect(
// ConnectWithMaxRetries types and implementation
export type ConnectWithMaxRetriesParams = {|
maxRetries?: number,
retryInterval?: number,
port: number,
|};
export type ConnectWithMaxRetriesDeps = {
connectToFirefox: typeof connect,
};
export async function connectWithMaxRetries(
// A max of 250 will try connecting for 30 seconds.
{ maxRetries = 250, retryInterval = 120, port }: ConnectWithMaxRetriesParams,
{ connectToFirefox = connect }: ConnectWithMaxRetriesDeps = {}
): Promise<RemoteFirefox> {
{ maxRetries = 250, retryInterval = 120, port },
{ connectToFirefox = connect } = {}
) {
async function establishConnection() {
var lastError;
@ -280,10 +230,9 @@ export async function connectWithMaxRetries(
return establishConnection();
}
export function findFreeTcpPort(): Promise<number> {
export function findFreeTcpPort() {
return new Promise((resolve) => {
const srv = net.createServer();
// $FlowFixMe: signature for listen() is missing - see https://github.com/facebook/flow/pull/8290
srv.listen(0, '127.0.0.1', () => {
const freeTcpPort = srv.address().port;
srv.close(() => resolve(freeTcpPort));

View File

@ -1,4 +1,3 @@
/* @flow */
import { main } from './program.js';
import cmd from './cmd/index.js';

View File

@ -1,4 +1,3 @@
/* @flow */
import os from 'os';
import path from 'path';
import { readFileSync } from 'fs';
@ -28,45 +27,22 @@ const envPrefix = 'WEB_EXT';
// by babel-plugin-transform-inline-environment-variables).
const defaultGlobalEnv = process.env.WEBEXT_BUILD_ENV || 'development';
type ProgramOptions = {
absolutePackageDir?: string,
};
export type VersionGetterFn = (absolutePackageDir: string) => Promise<string>;
// TODO: add pipes to Flow type after https://github.com/facebook/flow/issues/2405 is fixed
type ExecuteOptions = {
checkForUpdates?: Function,
systemProcess?: typeof process,
logStream?: typeof defaultLogStream,
getVersion?: VersionGetterFn,
applyConfigToArgv?: typeof defaultApplyConfigToArgv,
discoverConfigFiles?: typeof defaultConfigDiscovery,
loadJSConfigFile?: typeof defaultLoadJSConfigFile,
shouldExitProgram?: boolean,
globalEnv?: string | void,
};
export const AMO_BASE_URL = 'https://addons.mozilla.org/api/v5/';
/*
* The command line program.
*/
export class Program {
absolutePackageDir: string;
yargs: any;
commands: { [key: string]: Function };
shouldExitProgram: boolean;
verboseEnabled: boolean;
options: Object;
programArgv: Array<string>;
demandedOptions: Object;
absolutePackageDir;
yargs;
commands;
shouldExitProgram;
verboseEnabled;
options;
programArgv;
demandedOptions;
constructor(
argv: ?Array<string>,
{ absolutePackageDir = process.cwd() }: ProgramOptions = {}
) {
constructor(argv, { absolutePackageDir = process.cwd() } = {}) {
// This allows us to override the process argv which is useful for
// testing.
// NOTE: process.argv.slice(2) removes the path to node and web-ext
@ -98,12 +74,7 @@ export class Program {
this.options = {};
}
command(
name: string,
description: string,
executor: Function,
commandOptions: Object = {}
): Program {
command(name, description, executor, commandOptions = {}) {
this.options[camelCase(name)] = commandOptions;
this.yargs.command(name, description, (yargsForCmd) => {
@ -133,7 +104,7 @@ export class Program {
return this;
}
setGlobalOptions(options: Object): Program {
setGlobalOptions(options) {
// This is a convenience for setting global options.
// An option is only global (i.e. available to all sub commands)
// with the `global` flag so this makes sure every option has it.
@ -150,7 +121,7 @@ export class Program {
return this;
}
enableVerboseMode(logStream: typeof defaultLogStream, version: string): void {
enableVerboseMode(logStream, version) {
if (this.verboseEnabled) {
return;
}
@ -162,7 +133,7 @@ export class Program {
// Retrieve the yargs argv object and apply any further fix needed
// on the output of the yargs options parsing.
getArguments(): Object {
getArguments() {
// To support looking up required parameters via config files, we need to
// temporarily disable the requiredArguments validation. Otherwise yargs
// would exit early. Validation is enforced by the checkRequiredArguments()
@ -238,7 +209,7 @@ export class Program {
// read parameters from config files first. Before the program continues, it
// must call checkRequiredArguments() to ensure that required parameters are
// defined (in the CLI or in a config file).
checkRequiredArguments(adjustedArgv: Object): void {
checkRequiredArguments(adjustedArgv) {
const validationInstance = this.yargs
.getInternalMethods()
.getValidationInstance();
@ -247,7 +218,7 @@ export class Program {
// Remove WEB_EXT_* environment vars that are not a global cli options
// or an option supported by the current command (See #793).
cleanupProcessEnvConfigs(systemProcess: typeof process) {
cleanupProcessEnvConfigs(systemProcess) {
const cmd = yargsParser(this.programArgv)._[0];
const env = systemProcess.env || {};
const toOptionKey = (k) =>
@ -279,7 +250,7 @@ export class Program {
loadJSConfigFile = defaultLoadJSConfigFile,
shouldExitProgram = true,
globalEnv = defaultGlobalEnv,
}: ExecuteOptions = {}): Promise<void> {
} = {}) {
this.shouldExitProgram = shouldExitProgram;
this.yargs.exitProcess(this.shouldExitProgram);
@ -377,17 +348,14 @@ export class Program {
}
//A defintion of type of argument for defaultVersionGetter
type VersionGetterOptions = {
globalEnv?: string,
};
export async function defaultVersionGetter(
absolutePackageDir: string,
{ globalEnv = defaultGlobalEnv }: VersionGetterOptions = {}
): Promise<string> {
absolutePackageDir,
{ globalEnv = defaultGlobalEnv } = {}
) {
if (globalEnv === 'production') {
log.debug('Getting the version from package.json');
const packageData: any = readFileSync(
const packageData = readFileSync(
path.join(absolutePackageDir, 'package.json')
);
return JSON.parse(packageData).version;
@ -402,17 +370,8 @@ export async function defaultVersionGetter(
}
}
// TODO: add pipes to Flow type after https://github.com/facebook/flow/issues/2405 is fixed
type MainParams = {
getVersion?: VersionGetterFn,
commands?: Object,
argv: Array<any>,
runOptions?: Object,
};
export function throwUsageErrorIfArray(errorMessage: string): any {
return (value: any): any => {
export function throwUsageErrorIfArray(errorMessage) {
return (value) => {
if (Array.isArray(value)) {
throw new UsageError(errorMessage);
}
@ -421,14 +380,14 @@ export function throwUsageErrorIfArray(errorMessage: string): any {
}
export async function main(
absolutePackageDir: string,
absolutePackageDir,
{
getVersion = defaultVersionGetter,
commands = defaultCommands,
argv,
runOptions = {},
}: MainParams = {}
): Promise<any> {
} = {}
) {
const program = new Program(argv, { absolutePackageDir });
const version = await getVersion(absolutePackageDir);

View File

@ -1,4 +1,3 @@
/* @flow */
import ADBKit from '@devicefarmer/adbkit';
import { isErrorWithCode, UsageError, WebExtError } from '../errors.js';
@ -14,22 +13,8 @@ const defaultADB = ADBKit.default;
const log = createLogger(import.meta.url);
export type ADBUtilsParams = {|
adb?: typeof defaultADB,
// ADB configs.
adbBin?: string,
adbHost?: string,
adbPort?: string,
adbDevice?: string,
|};
export type DiscoveryParams = {
maxDiscoveryTime: number,
retryInterval: number,
};
// Helper function used to raise an UsageError when the adb binary has not been found.
async function wrapADBCall(asyncFn: (...any) => Promise<any>): Promise<any> {
async function wrapADBCall(asyncFn) {
try {
return await asyncFn();
} catch (error) {
@ -49,17 +34,17 @@ async function wrapADBCall(asyncFn: (...any) => Promise<any>): Promise<any> {
}
export default class ADBUtils {
params: ADBUtilsParams;
adb: typeof defaultADB;
adbClient: any; // TODO: better flow typing here.
params;
adb;
adbClient;
// Map<deviceId -> artifactsDir>
artifactsDirMap: Map<string, string>;
artifactsDirMap;
// Toggled when the user wants to abort the RDP Unix Socket discovery loop
// while it is still executing.
userAbortDiscovery: boolean;
userAbortDiscovery;
constructor(params: ADBUtilsParams) {
constructor(params) {
this.params = params;
const { adb, adbBin, adbHost, adbPort } = params;
@ -77,10 +62,7 @@ export default class ADBUtils {
this.userAbortDiscovery = false;
}
runShellCommand(
deviceId: string,
cmd: string | Array<string>
): Promise<string> {
runShellCommand(deviceId, cmd) {
const { adb, adbClient } = this;
log.debug(`Run adb shell command on ${deviceId}: ${JSON.stringify(cmd)}`);
@ -93,7 +75,7 @@ export default class ADBUtils {
}).then((res) => res.toString());
}
async discoverDevices(): Promise<Array<string>> {
async discoverDevices() {
const { adbClient } = this;
let devices = [];
@ -104,10 +86,7 @@ export default class ADBUtils {
return devices.map((dev) => dev.id);
}
async discoverInstalledFirefoxAPKs(
deviceId: string,
firefoxApk?: string
): Promise<Array<string>> {
async discoverInstalledFirefoxAPKs(deviceId, firefoxApk) {
log.debug(`Listing installed Firefox APKs on ${deviceId}`);
const pmList = await this.runShellCommand(deviceId, [
@ -135,7 +114,7 @@ export default class ADBUtils {
});
}
async getAndroidVersionNumber(deviceId: string): Promise<number> {
async getAndroidVersionNumber(deviceId) {
const androidVersion = (
await this.runShellCommand(deviceId, ['getprop', 'ro.build.version.sdk'])
).trim();
@ -154,11 +133,7 @@ export default class ADBUtils {
}
// Raise an UsageError when the given APK does not have the required runtime permissions.
async ensureRequiredAPKRuntimePermissions(
deviceId: string,
apk: string,
permissions: Array<string>
): Promise<void> {
async ensureRequiredAPKRuntimePermissions(deviceId, apk, permissions) {
const permissionsMap = {};
// Initialize every permission to false in the permissions map.
@ -195,11 +170,11 @@ export default class ADBUtils {
}
}
async amForceStopAPK(deviceId: string, apk: string): Promise<void> {
async amForceStopAPK(deviceId, apk) {
await this.runShellCommand(deviceId, ['am', 'force-stop', apk]);
}
async getOrCreateArtifactsDir(deviceId: string): Promise<string> {
async getOrCreateArtifactsDir(deviceId) {
let artifactsDir = this.artifactsDirMap.get(deviceId);
if (artifactsDir) {
@ -226,10 +201,7 @@ export default class ADBUtils {
return artifactsDir;
}
async detectOrRemoveOldArtifacts(
deviceId: string,
removeArtifactDirs?: boolean = false
): Promise<boolean> {
async detectOrRemoveOldArtifacts(deviceId, removeArtifactDirs = false) {
const { adbClient } = this;
log.debug('Checking adb device for existing web-ext artifacts dirs');
@ -269,7 +241,7 @@ export default class ADBUtils {
});
}
async clearArtifactsDir(deviceId: string): Promise<void> {
async clearArtifactsDir(deviceId) {
const artifactsDir = this.artifactsDirMap.get(deviceId);
if (!artifactsDir) {
@ -286,11 +258,7 @@ export default class ADBUtils {
await this.runShellCommand(deviceId, ['rm', '-rf', artifactsDir]);
}
async pushFile(
deviceId: string,
localPath: string,
devicePath: string
): Promise<void> {
async pushFile(deviceId, localPath, devicePath) {
const { adbClient } = this;
log.debug(`Pushing ${localPath} to ${devicePath} on ${deviceId}`);
@ -307,12 +275,7 @@ export default class ADBUtils {
});
}
async startFirefoxAPK(
deviceId: string,
apk: string,
apkComponent: ?string,
deviceProfileDir: string
): Promise<void> {
async startFirefoxAPK(deviceId, apk, apkComponent, deviceProfileDir) {
const { adbClient } = this;
log.debug(`Starting ${apk} on ${deviceId}`);
@ -378,15 +341,15 @@ export default class ADBUtils {
});
}
setUserAbortDiscovery(value: boolean) {
setUserAbortDiscovery(value) {
this.userAbortDiscovery = value;
}
async discoverRDPUnixSocket(
deviceId: string,
apk: string,
{ maxDiscoveryTime, retryInterval }: DiscoveryParams = {}
): Promise<string> {
deviceId,
apk,
{ maxDiscoveryTime, retryInterval } = {}
) {
let rdpUnixSockets = [];
const discoveryStartedAt = Date.now();
@ -439,7 +402,7 @@ export default class ADBUtils {
return rdpUnixSockets[0];
}
async setupForward(deviceId: string, remote: string, local: string) {
async setupForward(deviceId, remote, local) {
const { adbClient } = this;
// TODO(rpl): we should use adb.listForwards and reuse the existing one if any (especially
@ -452,15 +415,12 @@ export default class ADBUtils {
}
}
export async function listADBDevices(adbBin?: string): Promise<Array<string>> {
export async function listADBDevices(adbBin) {
const adbUtils = new ADBUtils({ adbBin });
return adbUtils.discoverDevices();
}
export async function listADBFirefoxAPKs(
deviceId: string,
adbBin?: string
): Promise<Array<string>> {
export async function listADBFirefoxAPKs(deviceId, adbBin) {
const adbUtils = new ADBUtils({ adbBin });
return adbUtils.discoverInstalledFirefoxAPKs(deviceId);
}

View File

@ -1,4 +1,3 @@
/* @flow */
import { fs } from 'mz';
import defaultAsyncMkdirp from 'mkdirp';
@ -7,20 +6,15 @@ import { createLogger } from './logger.js';
const log = createLogger(import.meta.url);
const defaultAsyncFsAccess: typeof fs.access = fs.access.bind(fs);
type PrepareArtifactsDirOptions = {
asyncMkdirp?: typeof defaultAsyncMkdirp,
asyncFsAccess?: typeof defaultAsyncFsAccess,
};
const defaultAsyncFsAccess = fs.access.bind(fs);
export async function prepareArtifactsDir(
artifactsDir: string,
artifactsDir,
{
asyncMkdirp = defaultAsyncMkdirp,
asyncFsAccess = defaultAsyncFsAccess,
}: PrepareArtifactsDirOptions = {}
): Promise<string> {
} = {}
) {
try {
const stats = await fs.stat(artifactsDir);
if (!stats.isDirectory()) {

View File

@ -1,29 +1,13 @@
/* @flow */
import defaultNotifier from 'node-notifier';
import { createLogger } from './logger.js';
import type { Logger } from './logger';
const defaultLog = createLogger(import.meta.url);
export type DesktopNotificationsParams = {|
title: string,
message: string,
icon?: string,
|};
export type DesktopNotificationsOptions = {
notifier?: typeof defaultNotifier,
log?: Logger,
};
export function showDesktopNotification(
{ title, message, icon }: DesktopNotificationsParams,
{
notifier = defaultNotifier,
log = defaultLog,
}: DesktopNotificationsOptions = {}
): Promise<void> {
{ title, message, icon },
{ notifier = defaultNotifier, log = defaultLog } = {}
) {
return new Promise((resolve, reject) => {
notifier.notify({ title, message, icon }, (err, res) => {
if (err) {

View File

@ -1,12 +1,7 @@
/* @flow */
import { fs } from 'mz';
import { isErrorWithCode } from '../errors.js';
type FileExistsOptions = {
fileIsReadable: (filePath: string) => Promise<boolean>,
};
/*
* Resolves true if the path is a readable file.
*
@ -19,11 +14,9 @@ type FileExistsOptions = {
*
* */
export default async function fileExists(
path: string,
{
fileIsReadable = (f) => fs.access(f, fs.constants.R_OK),
}: FileExistsOptions = {}
): Promise<boolean> {
path,
{ fileIsReadable = (f) => fs.access(f, fs.constants.R_OK) } = {}
) {
try {
await fileIsReadable(path);
const stat = await fs.stat(path);

View File

@ -1,4 +1,3 @@
/* @flow */
import path from 'path';
import multimatch from 'multimatch';
@ -8,7 +7,7 @@ import { createLogger } from './logger.js';
const log = createLogger(import.meta.url);
// check if target is a sub directory of src
export const isSubPath = (src: string, target: string): boolean => {
export const isSubPath = (src, target) => {
const relate = path.relative(src, target);
// same dir
if (!relate) {
@ -22,19 +21,12 @@ export const isSubPath = (src: string, target: string): boolean => {
// FileFilter types and implementation.
export type FileFilterOptions = {
baseIgnoredPatterns?: Array<string>,
ignoreFiles?: Array<string>,
sourceDir: string,
artifactsDir?: string,
};
/*
* Allows or ignores files.
*/
export class FileFilter {
filesToIgnore: Array<string>;
sourceDir: string;
filesToIgnore;
sourceDir;
constructor({
baseIgnoredPatterns = [
@ -48,7 +40,7 @@ export class FileFilter {
ignoreFiles = [],
sourceDir,
artifactsDir,
}: FileFilterOptions = {}) {
} = {}) {
sourceDir = path.resolve(sourceDir);
this.filesToIgnore = [];
@ -71,7 +63,7 @@ export class FileFilter {
/**
* Resolve relative path to absolute path with sourceDir.
*/
resolveWithSourceDir(file: string): string {
resolveWithSourceDir(file) {
const resolvedPath = path.resolve(this.sourceDir, file);
log.debug(
`Resolved path ${file} with sourceDir ${this.sourceDir} ` +
@ -83,7 +75,7 @@ export class FileFilter {
/**
* Insert more files into filesToIgnore array.
*/
addToIgnoreList(files: Array<string>) {
addToIgnoreList(files) {
for (const file of files) {
if (file.charAt(0) === '!') {
const resolvedFile = this.resolveWithSourceDir(file.substr(1));
@ -104,7 +96,7 @@ export class FileFilter {
* Example: this is called by zipdir as wantFile(filePath) for each
* file in the folder that is being archived.
*/
wantFile(filePath: string): boolean {
wantFile(filePath) {
const resolvedPath = this.resolveWithSourceDir(filePath);
const matches = multimatch(resolvedPath, this.filesToIgnore);
if (matches.length > 0) {
@ -117,7 +109,4 @@ export class FileFilter {
// a helper function to make mocking easier
export const createFileFilter = (params: FileFilterOptions): FileFilter =>
new FileFilter(params);
export type FileFilterCreatorFn = typeof createFileFilter;
export const createFileFilter = (params) => new FileFilter(params);

View File

@ -1,4 +1,3 @@
/* @flow */
import { fs } from 'mz';
import { onlyErrorsWithCode } from '../errors.js';
@ -14,7 +13,7 @@ import { onlyErrorsWithCode } from '../errors.js';
* });
*
* */
export default function isDirectory(path: string): Promise<boolean> {
export default function isDirectory(path) {
return fs
.stat(path)
.then((stats) => stats.isDirectory())

View File

@ -1,4 +1,3 @@
/* @flow */
import { fileURLToPath } from 'url';
import bunyan, {
@ -6,52 +5,18 @@ import bunyan, {
createLogger as defaultLogCreator,
} from 'bunyan';
// Bunyan-related Flow types
export type TRACE = 10;
export type DEBUG = 20;
export type INFO = 30;
export type WARN = 40;
export type ERROR = 50;
export type FATAL = 60;
export type BunyanLogLevel = TRACE | DEBUG | INFO | WARN | ERROR | FATAL;
export type BunyanLogEntry = {|
name: string,
msg: string,
level: BunyanLogLevel,
|};
export type Logger = {
debug: (msg: string, ...args: any) => void,
error: (msg: string, ...args: any) => void,
info: (msg: string, ...args: any) => void,
warn: (msg: string, ...args: any) => void,
};
// ConsoleStream types and implementation.
export type ConsoleStreamParams = {
verbose?: boolean,
};
export type ConsoleOptions = {
localProcess?: typeof process,
};
export class ConsoleStream {
verbose: boolean;
isCapturing: boolean;
capturedMessages: Array<string>;
verbose;
isCapturing;
capturedMessages;
constructor({ verbose = false }: ConsoleStreamParams = {}) {
constructor({ verbose = false } = {}) {
this.verbose = verbose;
this.isCapturing = false;
this.capturedMessages = [];
}
format({ name, msg, level }: BunyanLogEntry): string {
format({ name, msg, level }) {
const prefix = this.verbose ? `[${name}][${nameFromLevel[level]}] ` : '';
return `${prefix}${msg}\n`;
}
@ -60,11 +25,8 @@ export class ConsoleStream {
this.verbose = true;
}
write(
packet: BunyanLogEntry,
{ localProcess = process }: ConsoleOptions = {}
): void {
const thisLevel: BunyanLogLevel = this.verbose ? bunyan.TRACE : bunyan.INFO;
write(packet, { localProcess = process } = {}) {
const thisLevel = this.verbose ? bunyan.TRACE : bunyan.INFO;
if (packet.level >= thisLevel) {
const msg = this.format(packet);
if (this.isCapturing) {
@ -84,7 +46,7 @@ export class ConsoleStream {
this.capturedMessages = [];
}
flushCapturedLogs({ localProcess = process }: ConsoleOptions = {}) {
flushCapturedLogs({ localProcess = process } = {}) {
for (const msg of this.capturedMessages) {
localProcess.stdout.write(msg);
}
@ -92,31 +54,14 @@ export class ConsoleStream {
}
}
export const consoleStream: ConsoleStream = new ConsoleStream();
export const consoleStream = new ConsoleStream();
// createLogger types and implementation.
export type BunyanStreamConfig = {
type: string,
stream: ConsoleStream,
};
export type CreateBunyanLogParams = {
name: string,
level: BunyanLogLevel,
streams: Array<BunyanStreamConfig>,
};
export type CreateBunyanLogFn = (params: CreateBunyanLogParams) => Logger;
export type CreateLoggerOptions = {
createBunyanLog: CreateBunyanLogFn,
};
export function createLogger(
moduleURL?: string,
{ createBunyanLog = defaultLogCreator }: CreateLoggerOptions = {}
): Logger {
moduleURL,
{ createBunyanLog = defaultLogCreator } = {}
) {
return createBunyanLog({
// Strip the leading src/ from file names (which is in all file names) to
// make the name less redundant.

View File

@ -1,4 +1,3 @@
/* @flow */
import path from 'path';
import { fs } from 'mz';
@ -13,27 +12,7 @@ const log = createLogger(import.meta.url);
// getValidatedManifest helper types and implementation
export type ExtensionManifestApplications = {|
gecko?: {|
id?: string,
strict_min_version?: string,
strict_max_version?: string,
update_url?: string,
|},
|};
export type ExtensionManifest = {|
name: string,
version: string,
default_locale?: string,
applications?: ExtensionManifestApplications,
browser_specific_settings?: ExtensionManifestApplications,
permissions?: Array<string>,
|};
export default async function getValidatedManifest(
sourceDir: string
): Promise<ExtensionManifest> {
export default async function getValidatedManifest(sourceDir) {
const manifestFile = path.join(sourceDir, 'manifest.json');
log.debug(`Validating manifest at ${manifestFile}`);
@ -86,7 +65,7 @@ export default async function getValidatedManifest(
return manifestData;
}
export function getManifestId(manifestData: ExtensionManifest): string | void {
export function getManifestId(manifestData) {
const manifestApps = [
manifestData.browser_specific_settings,
manifestData.applications,

View File

@ -1,13 +1,5 @@
/* @flow */
import { promisify } from 'util';
// promisify.custom is missing from the node types know to flow,
// and it triggers flow-check errors if used directly.
// By using the value exported here, flow-check passes successfully
// using a single FLOW_IGNORE suppress comment.
// $FlowIgnore: promisify.custom is missing in flow type signatures.
export const promisifyCustom = promisify.custom;
/*
@ -21,8 +13,8 @@ export const promisifyCustom = promisify.custom;
* aCallbackBasedFn[promisify.custom] = multiArgsPromisedFn(tmp.dir);
* ...
*/
export function multiArgsPromisedFn(fn: Function): Function {
return (...callerArgs: Array<any>): Promise<any> => {
export function multiArgsPromisedFn(fn) {
return (...callerArgs) => {
return new Promise((resolve, reject) => {
fn(...callerArgs, (err, ...rest) => {
if (err) {

View File

@ -1,13 +1,7 @@
/* @flow */
import type { Readable } from 'stream';
export function isTTY(stream: Readable): boolean {
// $FlowFixMe: flow complains that stream may not provide isTTY as a property.
export function isTTY(stream) {
return stream.isTTY;
}
export function setRawMode(stream: Readable, rawMode: boolean) {
// $FlowFixMe: flow complains that stdin may not provide setRawMode.
export function setRawMode(stream, rawMode) {
stream.setRawMode(rawMode);
}

View File

@ -1,11 +1,10 @@
/* @flow */
import { createHash } from 'crypto';
import { createWriteStream, promises as fsPromises } from 'fs';
import { pipeline } from 'stream';
import { promisify } from 'util';
// eslint-disable-next-line no-shadow
import fetch, { FormData, fileFromSync, Response } from 'node-fetch';
import fetch, { FormData, fileFromSync } from 'node-fetch';
import { SignJWT } from 'jose';
import JSZip from 'jszip';
@ -16,35 +15,22 @@ const log = createLogger(import.meta.url);
export const defaultAsyncFsReadFile = fsPromises.readFile;
export type SignResult = {|
id: string,
downloadedFiles: Array<string>,
|};
export interface ApiAuth {
getAuthHeader(): Promise<string>;
}
export class JwtApiAuth {
#apiKey: string;
#apiSecret: string;
#apiJwtExpiresIn: number;
#apiKey;
#apiSecret;
#apiJwtExpiresIn;
constructor({
apiKey,
apiSecret,
apiJwtExpiresIn = 60 * 5, // 5 minutes
}: {
apiKey: string,
apiSecret: string,
apiJwtExpiresIn?: number,
}) {
this.#apiKey = apiKey;
this.#apiSecret = apiSecret;
this.#apiJwtExpiresIn = apiJwtExpiresIn;
}
async signJWT(): Promise<string> {
async signJWT() {
return (
new SignJWT({ iss: this.#apiKey })
.setProtectedHeader({ alg: 'HS256' })
@ -57,32 +43,21 @@ export class JwtApiAuth {
);
}
async getAuthHeader(): Promise<string> {
async getAuthHeader() {
const authToken = await this.signJWT();
return `JWT ${authToken}`;
}
}
type ClientConstructorParams = {|
apiAuth: ApiAuth,
baseUrl: URL,
validationCheckInterval?: number,
validationCheckTimeout?: number,
approvalCheckInterval?: number,
approvalCheckTimeout?: number,
downloadDir?: string,
userAgentString: string,
|};
export default class Client {
apiAuth: ApiAuth;
apiUrl: URL;
validationCheckInterval: number;
validationCheckTimeout: number;
approvalCheckInterval: number;
approvalCheckTimeout: number;
downloadDir: string;
userAgentString: string;
apiAuth;
apiUrl;
validationCheckInterval;
validationCheckTimeout;
approvalCheckInterval;
approvalCheckTimeout;
downloadDir;
userAgentString;
constructor({
apiAuth,
@ -93,7 +68,7 @@ export default class Client {
approvalCheckTimeout = 900000, // 15 minutes.
downloadDir = process.cwd(),
userAgentString,
}: ClientConstructorParams) {
}) {
this.apiAuth = apiAuth;
if (!baseUrl.pathname.endsWith('/')) {
baseUrl = new URL(baseUrl.href);
@ -108,26 +83,15 @@ export default class Client {
this.userAgentString = userAgentString;
}
fileFromSync(path: string): File {
fileFromSync(path) {
return fileFromSync(path);
}
nodeFetch(
url: URL,
{
method,
headers,
body,
}: {
method: string,
headers: { [key: string]: string },
body?: typeof FormData | string,
}
): Promise<typeof Response> {
nodeFetch(url, { method, headers, body }) {
return fetch(url, { method, headers, body });
}
async doUploadSubmit(xpiPath: string, channel: string): Promise<string> {
async doUploadSubmit(xpiPath, channel) {
const url = new URL('upload/', this.apiUrl);
const formData = new FormData();
formData.set('channel', channel);
@ -136,13 +100,7 @@ export default class Client {
return this.waitForValidation(uuid);
}
waitRetry(
successFunc: (detailResponseData: any) => string | null,
checkUrl: URL,
checkInterval: number,
abortInterval: number,
context: string
): Promise<string> {
waitRetry(successFunc, checkUrl, checkInterval, abortInterval, context) {
let checkTimeout;
return new Promise((resolve, reject) => {
@ -178,10 +136,10 @@ export default class Client {
});
}
waitForValidation(uuid: string): Promise<string> {
waitForValidation(uuid) {
log.info('Waiting for Validation...');
return this.waitRetry(
(detailResponseData): string | null => {
(detailResponseData) => {
if (!detailResponseData.processed) {
return null;
}
@ -204,7 +162,7 @@ export default class Client {
);
}
async doNewAddonSubmit(uuid: string, metaDataJson: Object): Promise<any> {
async doNewAddonSubmit(uuid, metaDataJson) {
const url = new URL('addon/', this.apiUrl);
const jsonData = {
...metaDataJson,
@ -213,11 +171,7 @@ export default class Client {
return this.fetchJson(url, 'POST', JSON.stringify(jsonData));
}
doNewAddonOrVersionSubmit(
addonId: string,
uuid: string,
metaDataJson: Object
): Promise<typeof Response> {
doNewAddonOrVersionSubmit(addonId, uuid, metaDataJson) {
const url = new URL(`addon/${addonId}/`, this.apiUrl);
const jsonData = {
...metaDataJson,
@ -226,10 +180,10 @@ export default class Client {
return this.fetchJson(url, 'PUT', JSON.stringify(jsonData));
}
waitForApproval(addonId: string, versionId: number): Promise<string> {
waitForApproval(addonId, versionId) {
log.info('Waiting for Approval...');
return this.waitRetry(
(detailResponseData): string | null => {
(detailResponseData) => {
const { file } = detailResponseData;
if (file && file.status === 'public') {
return file.url;
@ -244,12 +198,7 @@ export default class Client {
);
}
async fetchJson(
url: URL,
method: string = 'GET',
body?: typeof FormData | string,
errorMsg: string = 'Bad Request'
): Promise<any> {
async fetchJson(url, method = 'GET', body, errorMsg = 'Bad Request') {
const response = await this.fetch(url, method, body);
if (response.status < 200 || response.status >= 500) {
throw new Error(
@ -267,11 +216,7 @@ export default class Client {
return data;
}
async fetch(
url: URL,
method: string = 'GET',
body?: typeof FormData | string
): Promise<typeof Response> {
async fetch(url, method = 'GET', body) {
log.info(`Fetching URL: ${url.href}`);
let headers = {
Authorization: await this.apiAuth.getAuthHeader(),
@ -287,7 +232,7 @@ export default class Client {
return this.nodeFetch(url, { method, body, headers });
}
async downloadSignedFile(fileUrl: URL, addonId: string): Promise<SignResult> {
async downloadSignedFile(fileUrl, addonId) {
const filename = fileUrl.pathname.split('/').pop(); // get the name from fileUrl
const dest = `${this.downloadDir}/${filename}`;
try {
@ -306,10 +251,7 @@ export default class Client {
};
}
async saveToFile(
contents: typeof Response.body,
destPath: string
): Promise<any> {
async saveToFile(contents, destPath) {
return promisify(pipeline)(contents, createWriteStream(destPath));
}
@ -324,10 +266,7 @@ export default class Client {
but returning a different hash when the contents are the same in some cases is acceptable
- a false mismatch does not result in lost data.
*/
async hashXpiCrcs(
filePath: string,
asyncFsReadFile: typeof defaultAsyncFsReadFile = defaultAsyncFsReadFile
): Promise<string> {
async hashXpiCrcs(filePath, asyncFsReadFile = defaultAsyncFsReadFile) {
const zip = await JSZip.loadAsync(
asyncFsReadFile(filePath, { createFolders: true })
);
@ -347,15 +286,12 @@ export default class Client {
}
async getPreviousUuidOrUploadXpi(
xpiPath: string,
channel: string,
savedUploadUuidPath: string,
saveUploadUuidToFileFunc: (
string,
uploadUuidDataType
) => Promise<void> = saveUploadUuidToFile,
getUploadUuidFromFileFunc: (string) => Promise<uploadUuidDataType> = getUploadUuidFromFile
): Promise<string> {
xpiPath,
channel,
savedUploadUuidPath,
saveUploadUuidToFileFunc = saveUploadUuidToFile,
getUploadUuidFromFileFunc = getUploadUuidFromFile
) {
const [
{
uploadUuid: previousUuid,
@ -383,11 +319,11 @@ export default class Client {
}
async postNewAddon(
uploadUuid: string,
savedIdPath: string,
metaDataJson: Object,
saveIdToFileFunc: (string, string) => Promise<void> = saveIdToFile
): Promise<SignResult> {
uploadUuid,
savedIdPath,
metaDataJson,
saveIdToFileFunc = saveIdToFile
) {
const {
guid: addonId,
version: { id: newVersionId },
@ -403,11 +339,7 @@ export default class Client {
return this.downloadSignedFile(fileUrl, addonId);
}
async putVersion(
uploadUuid: string,
addonId: string,
metaDataJson: Object
): Promise<SignResult> {
async putVersion(uploadUuid, addonId, metaDataJson) {
const {
version: { id: newVersionId },
} = await this.doNewAddonOrVersionSubmit(addonId, uploadUuid, metaDataJson);
@ -418,23 +350,6 @@ export default class Client {
}
}
type signAddonParams = {|
apiKey: string,
apiSecret: string,
amoBaseUrl: string,
timeout: number,
id?: string,
xpiPath: string,
downloadDir: string,
channel: string,
savedIdPath: string,
savedUploadUuidPath: string,
metaDataJson?: Object,
userAgentString: string,
SubmitClient?: typeof Client,
ApiAuthClass?: typeof JwtApiAuth,
|};
export async function signAddon({
apiKey,
apiSecret,
@ -450,7 +365,7 @@ export async function signAddon({
userAgentString,
SubmitClient = Client,
ApiAuthClass = JwtApiAuth,
}: signAddonParams): Promise<SignResult> {
}) {
try {
const stats = await fsPromises.stat(xpiPath);
@ -491,10 +406,7 @@ export async function signAddon({
return client.putVersion(uploadUuid, id, metaDataJson);
}
export async function saveIdToFile(
filePath: string,
id: string
): Promise<void> {
export async function saveIdToFile(filePath, id) {
await fsPromises.writeFile(
filePath,
[
@ -507,16 +419,10 @@ export async function saveIdToFile(
log.debug(`Saved auto-generated ID ${id} to ${filePath}`);
}
type uploadUuidDataType = {
uploadUuid: string,
channel: string,
xpiCrcHash: string,
};
export async function saveUploadUuidToFile(
filePath: string,
{ uploadUuid, channel, xpiCrcHash }: uploadUuidDataType
): Promise<void> {
filePath,
{ uploadUuid, channel, xpiCrcHash }
) {
await fsPromises.writeFile(
filePath,
JSON.stringify({ uploadUuid, channel, xpiCrcHash })
@ -527,9 +433,9 @@ export async function saveUploadUuidToFile(
}
export async function getUploadUuidFromFile(
filePath: string,
asyncFsReadFile: typeof defaultAsyncFsReadFile = defaultAsyncFsReadFile
): Promise<uploadUuidDataType> {
filePath,
asyncFsReadFile = defaultAsyncFsReadFile
) {
try {
const content = await asyncFsReadFile(filePath, 'utf-8');
const { uploadUuid, channel, xpiCrcHash } = JSON.parse(content);

View File

@ -1,4 +1,3 @@
/* @flow */
import { promisify } from 'util';
import tmp from 'tmp';
@ -8,8 +7,6 @@ import { multiArgsPromisedFn, promisifyCustom } from './promisify.js';
const log = createLogger(import.meta.url);
export type MakePromiseCallback = (tmpDir: TempDir) => any;
tmp.dir[promisifyCustom] = multiArgsPromisedFn(tmp.dir);
const createTempDir = promisify(tmp.dir);
@ -29,7 +26,7 @@ const createTempDir = promisify(tmp.dir);
* );
*
*/
export function withTempDir(makePromise: MakePromiseCallback): Promise<any> {
export function withTempDir(makePromise) {
const tmpDir = new TempDir();
return tmpDir
.create()
@ -56,8 +53,8 @@ export function withTempDir(makePromise: MakePromiseCallback): Promise<any> {
*
*/
export class TempDir {
_path: string | void;
_removeTempDir: Function | void;
_path;
_removeTempDir;
constructor() {
this._path = undefined;
@ -68,7 +65,7 @@ export class TempDir {
* Returns a promise that is fulfilled when the temp directory has
* been created.
*/
create(): Promise<TempDir> {
create() {
return createTempDir({
prefix: 'tmp-web-ext-',
// This allows us to remove a non-empty tmp dir.
@ -90,7 +87,7 @@ export class TempDir {
/*
* Get the absolute path of the temp directory.
*/
path(): string {
path() {
if (!this._path) {
throw new Error('You cannot access path() before calling create()');
}
@ -104,7 +101,7 @@ export class TempDir {
* This is intended for use in a promise like
* Promise().catch(tmp.errorHandler())
*/
errorHandler(): Function {
errorHandler() {
return async (error) => {
await this.remove();
throw error;
@ -117,7 +114,7 @@ export class TempDir {
* This is intended for use in a promise like
* Promise().then(tmp.successHandler())
*/
successHandler(): Function {
successHandler() {
return async (promiseResult) => {
await this.remove();
return promiseResult;
@ -127,7 +124,7 @@ export class TempDir {
/*
* Remove the temp directory.
*/
remove(): Promise<void> | void {
remove() {
if (!this._removeTempDir) {
return;
}

View File

@ -1,15 +1,9 @@
/* @flow */
import defaultUpdateNotifier from 'update-notifier';
type CheckForUpdatesParams = {|
version: string,
updateNotifier?: typeof defaultUpdateNotifier,
|};
export function checkForUpdates({
version,
updateNotifier = defaultUpdateNotifier,
}: CheckForUpdatesParams) {
}) {
const pkg = { name: 'web-ext', version };
updateNotifier({

View File

@ -1,4 +1,3 @@
/* @flow */
import { fs } from 'mz';
import Watchpack from 'watchpack';
import debounce from 'debounce';
@ -10,22 +9,6 @@ const log = createLogger(import.meta.url);
// onSourceChange types and implementation
export type ShouldWatchFn = (filePath: string) => boolean;
export type OnChangeFn = () => any;
export type OnSourceChangeParams = {|
sourceDir: string,
watchFile?: Array<string>,
watchIgnored?: Array<string>,
artifactsDir: string,
onChange: OnChangeFn,
shouldWatchFile: ShouldWatchFn,
debounceTime?: number,
|};
export type OnSourceChangeFn = (params: OnSourceChangeParams) => Watchpack;
export default function onSourceChange({
sourceDir,
watchFile,
@ -34,7 +17,7 @@ export default function onSourceChange({
onChange,
shouldWatchFile,
debounceTime = 500,
}: OnSourceChangeParams): Watchpack {
}) {
// When running on Windows, transform the ignored paths and globs
// as Watchpack does translate the changed files path internally
// (See https://github.com/webpack/watchpack/blob/v2.1.1/lib/DirectoryWatcher.js#L99-L103).
@ -90,19 +73,12 @@ export default function onSourceChange({
// proxyFileChanges types and implementation.
export type ProxyFileChangesParams = {|
artifactsDir: string,
onChange: OnChangeFn,
filePath: string,
shouldWatchFile: ShouldWatchFn,
|};
export function proxyFileChanges({
artifactsDir,
onChange,
filePath,
shouldWatchFile,
}: ProxyFileChangesParams): void {
}) {
if (filePath.indexOf(artifactsDir) === 0 || !shouldWatchFile(filePath)) {
log.debug(`Ignoring change to: ${filePath}`);
} else {

View File

@ -4,8 +4,7 @@
*
* npx mocha -n "loader=./tests/babel-loader.js" -r tests/setup.js tests/unit/test.config.js
*
* It is responsible for the transpiling on the fly of the imported tests modules using babel (otherwise the flowtype
* syntaxes will trigger syntax errors while loaded by node plain ESM module loader).
* It is responsible for the transpiling on the fly of the imported tests modules using babel.
*
* The simplified transformSource function that follows has been derived from the existing node ESM loader:
*

View File

@ -1,6 +1,5 @@
/* @flow */
import path from 'path';
import { ChildProcess, spawn } from 'child_process';
import { spawn } from 'child_process';
import { promisify } from 'util';
import { fileURLToPath } from 'url';
@ -11,55 +10,32 @@ import * as tmpDirUtils from '../../src/util/temp-dir.js';
export const withTempDir = tmpDirUtils.withTempDir;
export const functionalTestsDir: string = path.resolve(
export const functionalTestsDir = path.resolve(
path.dirname(fileURLToPath(import.meta.url || ''))
);
export const projectDir: string = path.join(functionalTestsDir, '..', '..');
export const webExt: string = process.env.TEST_WEB_EXT_BIN
export const projectDir = path.join(functionalTestsDir, '..', '..');
export const webExt = process.env.TEST_WEB_EXT_BIN
? path.resolve(process.env.TEST_WEB_EXT_BIN)
: path.join(projectDir, 'bin', 'web-ext');
export const fixturesDir: string = path.join(
functionalTestsDir,
'..',
'fixtures'
);
export const minimalAddonPath: string = path.join(
fixturesDir,
'minimal-web-ext'
);
export const fixturesUseAsLibrary: string = path.join(
fixturesDir,
'webext-as-library'
);
export const fakeFirefoxPath: string = path.join(
export const fixturesDir = path.join(functionalTestsDir, '..', 'fixtures');
export const minimalAddonPath = path.join(fixturesDir, 'minimal-web-ext');
export const fixturesUseAsLibrary = path.join(fixturesDir, 'webext-as-library');
export const fakeFirefoxPath = path.join(
functionalTestsDir,
process.platform === 'win32'
? 'fake-firefox-binary.bat'
: 'fake-firefox-binary.js'
);
export const fakeServerPath: string = path.join(
export const fakeServerPath = path.join(
functionalTestsDir,
'fake-amo-server.js'
);
// withTempAddonDir helper
export type TempAddonParams = {|
addonPath: string,
runFromCwd?: boolean,
|};
export type TempAddonCallback = (
tmpAddonDir: string,
tmpDir: string
) => Promise<any>;
const copyDirAsPromised = promisify(copyDir);
export function withTempAddonDir(
{ addonPath }: TempAddonParams,
makePromise: TempAddonCallback
): Promise<any> {
export function withTempAddonDir({ addonPath }, makePromise) {
return withTempDir((tmpDir) => {
const tempAddonDir = path.join(tmpDir.path(), 'tmp-addon-dir');
return copyDirAsPromised(addonPath, tempAddonDir).then(() => {
@ -77,7 +53,7 @@ export function withTempAddonDir(
// reportCommandErrors helper
export function reportCommandErrors(obj: Object, msg: ?string) {
export function reportCommandErrors(obj, msg) {
const errorMessage = msg || 'Unexpected web-ext functional test result';
const formattedErrorData = prettyjson.render(obj);
const error = new Error(`${errorMessage}: \n${formattedErrorData}`);
@ -93,22 +69,7 @@ export function reportCommandErrors(obj: Object, msg: ?string) {
// execWebExt helper
export type WebExtResult = {|
exitCode: number,
stderr: string,
stdout: string,
|};
export type RunningWebExt = {|
argv: Array<string>,
waitForExit: Promise<WebExtResult>,
spawnedProcess: ChildProcess,
|};
export function execWebExt(
argv: Array<string>,
spawnOptions: child_process$spawnOpts
): RunningWebExt {
export function execWebExt(argv, spawnOptions) {
if (spawnOptions.env) {
spawnOptions.env = {
// Propagate the current environment when redefining it from the `spawnOptions`

View File

@ -1,4 +1,3 @@
/* @flow */
import { describe, it } from 'mocha';
import { assert } from 'chai';

View File

@ -1,4 +1,3 @@
/* @flow */
import { describe, it } from 'mocha';
import {

View File

@ -1,4 +1,3 @@
/* @flow */
import { describe, it } from 'mocha';
import { assert } from 'chai';

View File

@ -1,4 +1,3 @@
/* @flow */
import path from 'path';
import { describe, it } from 'mocha';

View File

@ -1,4 +1,3 @@
/* @flow */
import { spawn } from 'child_process';
import path from 'path';

View File

@ -1,4 +1,3 @@
/* @flow */
import { execFileSync } from 'child_process';
import path from 'path';
import { fileURLToPath } from 'url';

View File

@ -1,4 +1,3 @@
/* @flow */
import { describe, it } from 'mocha';
import { assert } from 'chai';

View File

@ -1,5 +1,3 @@
/* @flow */
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';

View File

@ -1,7 +1,5 @@
/* @flow */
import path from 'path';
import EventEmitter from 'events';
import tty from 'tty';
import stream from 'stream';
import { promisify } from 'util';
import { fileURLToPath, pathToFileURL } from 'url';
@ -22,8 +20,8 @@ const log = createLogger(import.meta.url);
* A way to read zip files using promises for all the things.
*/
export class ZipFile {
_zip: any;
_close: Promise<void> | null;
_zip;
_close;
constructor() {
this._zip = null;
@ -34,7 +32,7 @@ export class ZipFile {
* Open a zip file and return a promise that resolves to a yauzl
* zipfile object.
*/
open(...args: Array<any>): Promise<void> {
open(...args) {
return promisify(yauzl.open)(...args).then((zip) => {
this._zip = zip;
this._close = new Promise((resolve) => {
@ -46,7 +44,7 @@ export class ZipFile {
/**
* Close the zip file and wait fd to release.
*/
close(): Promise<void> | null {
close() {
this._zip.close();
return this._close;
}
@ -57,7 +55,7 @@ export class ZipFile {
*
* The onRead callback receives a single argument, a yauzl Entry object.
*/
readEach(onRead: Function): Promise<void> {
readEach(onRead) {
return new Promise((resolve, reject) => {
if (!this._zip) {
throw new Error(
@ -82,7 +80,7 @@ export class ZipFile {
/*
* Resolve a promise with an array of all file names in the zip archive.
*/
extractFilenames(): Promise<Array<String>> {
extractFilenames() {
return new Promise((resolve, reject) => {
var fileNames = [];
this.readEach((entry) => {
@ -101,7 +99,7 @@ export class ZipFile {
/*
* Returns a path to a test fixture file. Invoke it the same as path.join().
*/
export function fixturePath(...pathParts: Array<string>): string {
export function fixturePath(...pathParts) {
return path.join(
moduleURLToDirname(import.meta.url),
'..',
@ -120,7 +118,7 @@ export function fixturePath(...pathParts: Array<string>): string {
* // Safely make assertions about the error...
* });
*/
export function makeSureItFails(): Function {
export function makeSureItFails() {
return () => {
throw new Error('This test unexpectedly succeeded without an error');
};
@ -155,12 +153,7 @@ export function makeSureItFails(): Function {
*
*/
// $FlowIgnore: fake can return any kind of object and fake a defined set of methods for testing.
export function fake<T>(
original: Object,
methods: Object = {},
skipProperties: Array<string> = []
): T {
export function fake(original, methods = {}, skipProperties = []) {
const stub = {};
// Provide stubs for all original members (fallback to Object if original
// doesn't have a defined prototype):
@ -193,17 +186,16 @@ export function fake<T>(
stub[key] = sinon.spy(stub[key]);
});
// $FlowIgnore: fake can return any kind of object for testing.
return stub;
}
export class StubChildProcess extends EventEmitter {
stderr: EventEmitter = new EventEmitter();
stdout: EventEmitter = new EventEmitter();
kill: any = sinon.spy(() => {});
stderr = new EventEmitter();
stdout = new EventEmitter();
kill = sinon.spy(() => {});
}
export function createFakeProcess(): any {
export function createFakeProcess() {
return fake(process, {}, ['EventEmitter', 'stdin']);
}
@ -211,7 +203,7 @@ export function createFakeProcess(): any {
* Returns a fake FirefoxRDPClient as would be returned by
* rdp-module connectToFirefox().
*/
export function fakeFirefoxClient(): any {
export function fakeFirefoxClient() {
return {
disconnect: sinon.spy(() => {}),
on: () => {},
@ -225,16 +217,16 @@ export function fakeFirefoxClient(): any {
* By default, the error code will be ECONNREFUSED.
*/
export class TCPConnectError extends ExtendableError {
code: string;
constructor(msg: string = 'simulated connection error') {
code;
constructor(msg = 'simulated connection error') {
super(msg);
this.code = 'ECONNREFUSED';
}
}
export class ErrorWithCode extends Error {
code: string;
constructor(code: ?string, message: ?string) {
code;
constructor(code, message) {
super(`${code || ''}: ${message || 'pretend this is a system error'}`);
this.code = code || 'SOME_CODE';
}
@ -256,39 +248,36 @@ export const basicManifest = {
/*
* A basic manifest fixture without an applications property.
*/
export const manifestWithoutApps: any = deepcopy(basicManifest);
export const manifestWithoutApps = deepcopy(basicManifest);
delete manifestWithoutApps.applications;
/*
* A class that implements an empty IExtensionRunner interface.
*/
export class FakeExtensionRunner {
params: any;
params;
constructor(params: any) {
constructor(params) {
this.params = params;
}
getName(): string {
getName() {
return 'Fake Extension Runner';
}
async run() {}
async exit() {}
async reloadAllExtensions(): Promise<any> {
async reloadAllExtensions() {
return [];
}
async reloadExtensionBySourceDir(sourceDir: string): Promise<any> {
async reloadExtensionBySourceDir(sourceDir) {
const runnerName = this.getName();
return [{ runnerName, sourceDir }];
}
registerCleanup(fn: Function) {} // eslint-disable-line no-unused-vars
registerCleanup(fn) {} // eslint-disable-line no-unused-vars
}
export function getFakeFirefox(
implementations: Object = {},
port: number = 6005
): any {
export function getFakeFirefox(implementations = {}, port = 6005) {
const profile = {}; // empty object just to avoid errors.
const firefox = () => Promise.resolve();
const allImplementations = {
@ -302,7 +291,7 @@ export function getFakeFirefox(
return fake(defaultFirefoxApp, allImplementations);
}
export function getFakeRemoteFirefox(implementations: Object = {}): any {
export function getFakeRemoteFirefox(implementations = {}) {
return fake(RemoteFirefox.prototype, implementations);
}
@ -316,12 +305,11 @@ class FakeStdin extends stream.Readable {
_read() {}
}
export function createFakeStdin(): tty.ReadStream {
// $FlowIgnore: flow complains that the return value is incompatible with tty.ReadStream
export function createFakeStdin() {
return new FakeStdin();
}
export function moduleURLToDirname(moduleURL: ?string): string {
export function moduleURLToDirname(moduleURL) {
if (!moduleURL) {
throw new Error('Unexpected undefined module url');
}
@ -333,16 +321,10 @@ export function mockModule({
importerModuleURL,
namedExports,
defaultExport,
}: {
moduleURL: string,
importerModuleURL: ?string,
namedExports?: ?Object,
defaultExport?: any,
}): void {
}) {
// Compute the full URL to the module to mock, otherwise
// quibble will compute the wrong module URL when running
// on windows (which would be looking as "C:\\C:\\Users\\...").
// $FlowIgnore: ignore issue with import.meta.url being typed as void or string.
const baseDir = path.dirname(fileURLToPath(importerModuleURL));
const fullModuleURL = pathToFileURL(
path.resolve(path.join(baseDir, moduleURL))
@ -352,7 +334,7 @@ export function mockModule({
global.__webextMocks?.add(fullModuleURL);
}
export function resetMockModules(): void {
export function resetMockModules() {
td.reset();
global.__webextMocks?.clear();
}

View File

@ -1,4 +1,3 @@
/* @flow */
import path from 'path';
import { fs } from 'mz';
@ -438,7 +437,6 @@ describe('build', () => {
// Make sure it uses the file filter.
assert.typeOf(args.shouldWatchFile, 'function');
args.shouldWatchFile('/some/path');
// $FlowIgnore: ignore method-unbinding, sinon just checks the spy properties.
sinon.assert.called(fileFilter.wantFile);
// Remove the built extension.

View File

@ -1,4 +1,3 @@
/* @flow */
import { it, describe } from 'mocha';
import * as sinon from 'sinon';
import { assert } from 'chai';

View File

@ -1,17 +1,11 @@
/* @flow */
import { it, describe } from 'mocha';
import { assert } from 'chai';
import * as sinon from 'sinon';
import defaultLintCommand from '../../../src/cmd/lint.js';
type setUpParams = {
createLinter?: Function,
createFileFilter?: Function,
};
describe('lint', () => {
function setUp({ createLinter, createFileFilter }: setUpParams = {}) {
function setUp({ createLinter, createFileFilter } = {}) {
const lintResult = '<lint.run() result placeholder>';
const runLinter = sinon.spy(() => Promise.resolve(lintResult));
if (!createLinter) {
@ -30,7 +24,6 @@ describe('lint', () => {
createFileFilter,
...options,
};
// $FlowIgnore: allow use of inexact object literal for testing purpose.
return defaultLintCommand(mergedArgs, mergedOpts);
},
};

View File

@ -1,4 +1,3 @@
/* @flow */
import path from 'path';
import { fs } from 'mz';
@ -57,15 +56,14 @@ async function prepareRun(fakeInstallResult) {
options,
errors,
run: (customArgv = {}, customOpt = {}) =>
// $FlowIgnore: allow use of inexact object literal for testing purpose.
cmdRun.default({ ...argv, ...customArgv }, { ...options, ...customOpt }),
};
}
describe('run', () => {
let androidRunnerStub: any;
let desktopRunnerStub: any;
let chromiumRunnerStub: any;
let androidRunnerStub;
let desktopRunnerStub;
let chromiumRunnerStub;
beforeEach(async () => {
const firefoxAndroidModule = {
@ -154,9 +152,7 @@ describe('run', () => {
firefoxBinary: runOptions.firefox,
customPrefs: runOptions.pref,
};
// $FlowIgnore: Allow deleting properties for testing purpose.
delete expectedRunnerParams.firefox;
// $FlowIgnore: Allow deleting properties for testing purpose.
delete expectedRunnerParams.pref;
assert.deepEqual(

View File

@ -1,4 +1,3 @@
/* @flow */
import path from 'path';
import { promisify } from 'util';
@ -18,8 +17,6 @@ import completeSignCommand, {
getIdFromFile,
} from '../../../src/cmd/sign.js';
import { basicManifest, manifestWithoutApps, fixturePath } from '../helpers.js';
// Import flow type
import type { ExtensionManifestApplications } from '../../../src/util/manifest';
describe('sign', () => {
function getStubs() {
@ -62,11 +59,7 @@ describe('sign', () => {
/*
* Run the sign command with stubs for all dependencies.
*/
function sign(
tmpDir: Object,
stubs: Object,
{ extraArgs = {}, extraOptions = {} }: Object = {}
): Promise<*> {
function sign(tmpDir, stubs, { extraArgs = {}, extraOptions = {} } = {}) {
return completeSignCommand(
{
verbose: false,
@ -408,8 +401,9 @@ describe('sign', () => {
const stubs = getStubs();
const artifactsDir = path.join(tmpDir.path(), 'some-artifacts-dir');
const apiProxy = 'http://yourproxy:6000';
const applications: ExtensionManifestApplications = stubs
.preValidatedManifest.applications || { gecko: {} };
const applications = stubs.preValidatedManifest.applications || {
gecko: {},
};
const userAgentString = `web-ext/${stubs.signingConfig.webextVersion}`;
const apiRequestConfig = { headers: { 'User-Agent': userAgentString } };
return sign(tmpDir, stubs, {
@ -435,8 +429,9 @@ describe('sign', () => {
withTempDir((tmpDir) => {
const stubs = getStubs();
const artifactsDir = path.join(tmpDir.path(), 'some-artifacts-dir');
const applications: ExtensionManifestApplications = stubs
.preValidatedManifest.applications || { gecko: {} };
const applications = stubs.preValidatedManifest.applications || {
gecko: {},
};
const userAgentString = `web-ext/${stubs.signingConfig.webextVersion}`;
const channel = 'unlisted';
return sign(tmpDir, stubs, {

View File

@ -1,5 +1,3 @@
/* @flow */
import path from 'path';
import EventEmitter from 'events';
@ -16,7 +14,6 @@ import {
ChromiumExtensionRunner,
DEFAULT_CHROME_FLAGS,
} from '../../../src/extension-runners/chromium.js';
import type { ChromiumExtensionRunnerParams } from '../../../src/extension-runners/chromium';
import {
consoleStream, // instance is imported to inspect logged messages
} from '../../../src/util/logger.js';
@ -29,7 +26,7 @@ function prepareExtensionRunnerParams({ params } = {}) {
process: new StubChildProcess(),
kill: sinon.spy(async () => {}),
};
const runnerParams: ChromiumExtensionRunnerParams = {
const runnerParams = {
extensions: [
{
sourceDir: '/fake/sourceDir',
@ -120,7 +117,6 @@ describe('util/extension-runners/chromium', async () => {
const runnerInstance = new ChromiumExtensionRunner(params);
await runnerInstance.run();
// $FlowIgnore: allow to call addess even wss property can be undefined.
const wssInfo = runnerInstance.wss.address();
const wsURL = `ws://${wssInfo.address}:${wssInfo.port}`;
const wsClient = new WebSocket(wsURL);
@ -139,7 +135,6 @@ describe('util/extension-runners/chromium', async () => {
const fakeSocket = new EventEmitter();
sinon.spy(fakeSocket, 'on');
runnerInstance.wss?.emit('connection', fakeSocket);
// $FlowIgnore: ignore method-unbinding, sinon just checks the spy properties.
sinon.assert.calledOnce(fakeSocket.on);
fakeSocket.emit('error', new Error('Fake wss socket ERROR'));
@ -247,7 +242,6 @@ describe('util/extension-runners/chromium', async () => {
await exitDone;
// $FlowIgnore: ignore method-unbinding, sinon just checks the spy properties.
sinon.assert.calledOnce(runnerInstance.exit);
});
@ -622,8 +616,8 @@ describe('util/extension-runners/chromium', async () => {
);
describe('reloadAllExtensions', () => {
let runnerInstance: ChromiumExtensionRunner;
let wsClient: WebSocket;
let runnerInstance;
let wsClient;
beforeEach(async () => {
const { params } = prepareExtensionRunnerParams();
@ -635,7 +629,6 @@ describe('util/extension-runners/chromium', async () => {
if (!runnerInstance.wss) {
throw new Error('WebSocker server is not running');
}
// $FlowIgnore: if runnerInstance.wss would be unexpectedly undefined the test case will fail.
const wssInfo = runnerInstance.wss.address();
const wsURL = `ws://${wssInfo.address}:${wssInfo.port}`;
wsClient = new WebSocket(wsURL);
@ -645,7 +638,6 @@ describe('util/extension-runners/chromium', async () => {
afterEach(async () => {
if (wsClient && wsClient.readyState === WebSocket.OPEN) {
wsClient.close();
// $FlowIgnore: allow to nullify wsClient even if wsClient signature doesn't allow it.
wsClient = null;
}
await runnerInstance.exit();
@ -692,7 +684,6 @@ describe('util/extension-runners/chromium', async () => {
});
wsClient.close();
});
// $FlowIgnore: allow to nullify wsClient even if wsClient signature doesn't allow it.
wsClient = null;
});

View File

@ -1,4 +1,3 @@
/* @flow */
import stream from 'stream';
import { describe, it } from 'mocha';
@ -12,17 +11,8 @@ import {
MultiExtensionRunner,
} from '../../../src/extension-runners/index.js';
import { createFakeStdin, FakeExtensionRunner } from '../helpers.js';
import type {
IExtensionRunner, // eslint-disable-line import/named
} from '../../../src/extension-runners/base';
function createFakeExtensionRunner({
params = {},
overriddenMethods = {},
}: {
params?: Object,
overriddenMethods?: Object,
}): IExtensionRunner {
function createFakeExtensionRunner({ params = {}, overriddenMethods = {} }) {
const runner = new FakeExtensionRunner(params);
for (const [fnName, fn] of Object.entries(overriddenMethods)) {
@ -62,7 +52,6 @@ function exitKeypressLoop(stdin) {
describe('util/extension-runners', () => {
describe('createExtensionRunner', () => {
it('requires a valid target', async () => {
// $FlowIgnore: Want to pass invalid argument and check the error.
const promise = createExtensionRunner({});
await assert.isRejected(promise, /Unknown target: "undefined"/);
});
@ -82,9 +71,7 @@ describe('util/extension-runners', () => {
await runnerInstance.run();
// $FlowIgnore: ignore method-unbinding, sinon just checks the spy properties.
sinon.assert.calledOnce(fakeExtensionRunner.run);
// $FlowIgnore: ignore method-unbinding, sinon just checks the spy properties.
sinon.assert.calledOnce(anotherFakeExtensionRunner.run);
});
@ -99,12 +86,8 @@ describe('util/extension-runners', () => {
await runnerInstance.reloadAllExtensions();
// $FlowIgnore: ignore method-unbinding, sinon just checks the spy properties.
sinon.assert.calledOnce(fakeExtensionRunner.reloadAllExtensions);
sinon.assert.calledOnce(
// $FlowIgnore: ignore method-unbinding, sinon just checks the spy properties.
anotherFakeExtensionRunner.reloadAllExtensions
);
sinon.assert.calledOnce(anotherFakeExtensionRunner.reloadAllExtensions);
});
it('calls the "reloadExtensionBySourceDir" on all the created runners', async () => {
@ -118,11 +101,8 @@ describe('util/extension-runners', () => {
await runnerInstance.reloadExtensionBySourceDir('/fake/source/dir');
const spyReloadExtension =
// $FlowIgnore: ignore method-unbinding, sinon just checks the spy properties.
fakeExtensionRunner.reloadExtensionBySourceDir;
const spyReloadExtension = fakeExtensionRunner.reloadExtensionBySourceDir;
const spyAnotherReloadExtension =
// $FlowIgnore: ignore method-unbinding, sinon just checks the spy properties.
anotherFakeExtensionRunner.reloadExtensionBySourceDir;
sinon.assert.calledOnce(spyReloadExtension);
@ -149,9 +129,7 @@ describe('util/extension-runners', () => {
await runnerInstance.exit();
// $FlowIgnore: ignore method-unbinding, sinon just checks the spy properties.
sinon.assert.calledOnce(fakeExtensionRunner.exit);
// $FlowIgnore: ignore method-unbinding, sinon just checks the spy properties.
sinon.assert.calledOnce(anotherFakeExtensionRunner.exit);
});
@ -166,8 +144,8 @@ describe('util/extension-runners', () => {
},
});
const anotherFakeExtensionRunner = createFakeExtensionRunner({
getName: () => 'anotherFakeExtensionRunner',
overriddenMethods: {
getName: () => 'anotherFakeExtensionRunner',
reloadAllExtensions: () => {
return Promise.reject(new Error('reload error 2'));
},
@ -180,12 +158,8 @@ describe('util/extension-runners', () => {
await runnerInstance.reloadAllExtensions();
// $FlowIgnore: ignore method-unbinding, sinon just checks the spy properties.
sinon.assert.calledOnce(fakeExtensionRunner.reloadAllExtensions);
sinon.assert.calledOnce(
// $FlowIgnore: ignore method-unbinding, sinon just checks the spy properties.
anotherFakeExtensionRunner.reloadAllExtensions
);
sinon.assert.calledOnce(anotherFakeExtensionRunner.reloadAllExtensions);
sinon.assert.callCount(params.desktopNotifications, 2);
sinon.assert.calledWith(
params.desktopNotifications,
@ -223,12 +197,8 @@ describe('util/extension-runners', () => {
assert.equal(res.length, 2);
assert.equal(errors.length, 1);
sinon.assert.calledOnce(fakeExtensionRunner.reloadExtensionBySourceDir);
sinon.assert.calledOnce(
// $FlowIgnore: ignore method-unbinding, sinon just checks the spy properties.
fakeExtensionRunner.reloadExtensionBySourceDir
);
sinon.assert.calledOnce(
// $FlowIgnore: ignore method-unbinding, sinon just checks the spy properties.
anotherFakeExtensionRunner.reloadExtensionBySourceDir
);
sinon.assert.calledOnce(params.desktopNotifications);
@ -259,13 +229,10 @@ describe('util/extension-runners', () => {
runnerInstance.registerCleanup(resolve);
});
// $FlowIgnore: ignore method-unbinding, sinon just checks the spy properties.
sinon.assert.calledOnce(fakeExtensionRunner.registerCleanup);
// $FlowIgnore: ignore method-unbinding, sinon just checks the spy properties.
sinon.assert.calledOnce(anotherFakeExtensionRunner.registerCleanup);
// Call the cleanup callback on the first runner.
// $FlowIgnore: ignore method-unbinding, we just access the spy properties here.
fakeExtensionRunner.registerCleanup.firstCall.args[0]();
const checkIncompleteCleanup = await Promise.race([
@ -283,7 +250,6 @@ describe('util/extension-runners', () => {
);
// Call the cleanup callback on the second and last runner.
// $FlowIgnore: ignore method-unbinding, we just access the spy properties here.
anotherFakeExtensionRunner.registerCleanup.firstCall.args[0]();
await waitRegisterCleanup;
@ -305,7 +271,6 @@ describe('util/extension-runners', () => {
return {
config,
createWatcher: (customConfig = {}) => {
// $FlowIgnore: allow use of inexact object literal for testing purpose.
return defaultWatcherCreator({ ...config, ...customConfig });
},
};
@ -405,7 +370,6 @@ describe('util/extension-runners', () => {
reloadStrategy: async (argOverride = {}, optOverride = {}) => {
const mergedArgs = { ...args, ...argOverride };
const mergedOpts = { ...options, ...optOverride };
// $FlowIgnore: allow use of inexact object literal for testing purpose.
return defaultReloadStrategy(mergedArgs, mergedOpts);
},
};
@ -449,7 +413,6 @@ describe('util/extension-runners', () => {
const { reloadExtension } = createWatcher.firstCall.args[0];
reloadExtension(sourceDir);
// $FlowIgnore: ignore method-unbinding, used here for testing purpose.
const { reloadExtensionBySourceDir } = extensionRunner;
sinon.assert.calledOnce(reloadExtensionBySourceDir);
@ -470,7 +433,6 @@ describe('util/extension-runners', () => {
reloadStrategy();
// $FlowIgnore: ignore method-unbinding, used here for testing purpose.
const { registerCleanup } = extensionRunner;
sinon.assert.called(registerCleanup);
@ -481,7 +443,6 @@ describe('util/extension-runners', () => {
registeredCb();
sinon.assert.called(watcher.close);
// $FlowIgnore: ignore method-unbinding, sinon just checks the spy properties.
sinon.assert.called(stdin.pause);
});
@ -499,9 +460,7 @@ describe('util/extension-runners', () => {
// Wait for one tick.
await Promise.resolve();
// $FlowIgnore: ignore method-unbinding, sinon just checks the spy properties.
sinon.assert.called(fakeStdin.setRawMode);
// $FlowIgnore: ignore method-unbinding, sinon just checks the spy properties.
sinon.assert.called(extensionRunner.reloadAllExtensions);
} finally {
exitKeypressLoop(fakeStdin);
@ -519,16 +478,13 @@ describe('util/extension-runners', () => {
try {
await reloadStrategy({ noInput: true }, { stdin: fakeStdin });
// This is meant to test that all input is ignored.
// $FlowIgnore: ignore method-unbinding, sinon just checks the spy properties.
sinon.assert.notCalled(fakeStdin.setRawMode);
} finally {
exitKeypressLoop(fakeStdin);
}
// $FlowIgnore: ignore method-unbinding, used here for testing purpose.
const cleanupCb = extensionRunner.registerCleanup.firstCall.args[0];
cleanupCb();
// $FlowIgnore: ignore method-unbinding, sinon just checks the spy properties.
sinon.assert.notCalled(fakeStdin.pause);
});
@ -547,13 +503,11 @@ describe('util/extension-runners', () => {
// Stub the `fakeStdin.once` method to be able to wait
// once a promise resolved when the reloadStrategy method
// did call `stdin.once('keypress', ...)`.
// $FlowIgnore: ignore method-unbinding, used here for testing purpose.
const fakeStdinOnce = fakeStdin.once;
sinon.stub(fakeStdin, 'once');
function promiseWaitKeypress() {
return new Promise((resolve) => {
// $FlowIgnore: ignore method-unbinding, sinon just checks the spy properties.
fakeStdin.once.callsFake((...args) => {
if (args[0] === 'keypress') {
resolve();
@ -572,10 +526,8 @@ describe('util/extension-runners', () => {
fakeStdin.emit('keypress', 'r', { name: 'r', ctrl: false });
await onceWaitKeypress;
// $FlowIgnore: ignore method-unbinding, sinon just checks the spy properties.
const { reloadAllExtensions } = extensionRunner;
// $FlowIgnore: ignore method-unbinding, sinon just checks the spy properties.
sinon.assert.called(fakeStdin.setRawMode);
sinon.assert.calledOnce(reloadAllExtensions);
@ -609,7 +561,6 @@ describe('util/extension-runners', () => {
// Wait for one tick.
await Promise.resolve();
// $FlowIgnore: ignore method-unbinding, sinon just checks the spy properties.
sinon.assert.called(extensionRunner.exit);
} finally {
exitKeypressLoop(fakeStdin);

View File

@ -1,5 +1,3 @@
/* @flow */
import EventEmitter from 'events';
import { assert } from 'chai';
@ -9,7 +7,6 @@ import * as sinon from 'sinon';
import { consoleStream } from '../../../src/util/logger.js';
import { FirefoxAndroidExtensionRunner } from '../../../src/extension-runners/firefox-android.js';
import type { FirefoxAndroidExtensionRunnerParams } from '../../../src/extension-runners/firefox-android';
import { UsageError, WebExtError } from '../../../src/errors.js';
import {
basicManifest,
@ -38,14 +35,6 @@ const fakeRDPUnixSocketFile =
const fakeRDPUnixAbstractSocketFile =
'@org.mozilla.firefox/firefox-debugger-socket';
type PrepareParams = {
params?: Object,
debuggerPort?: number,
fakeFirefoxApp?: Object,
fakeRemoteFirefox?: Object,
fakeADBUtils?: Object,
};
// Reduce the waiting time during tests.
FirefoxAndroidExtensionRunner.unixSocketDiscoveryRetryInterval = 0;
@ -55,7 +44,7 @@ function prepareExtensionRunnerParams({
fakeRemoteFirefox,
fakeADBUtils,
params,
}: PrepareParams = {}) {
} = {}) {
const fakeRemoteFirefoxClient = new EventEmitter();
const remoteFirefox = getFakeRemoteFirefox({
installTemporaryAddon: sinon.spy(() => Promise.resolve(tempInstallResult)),
@ -63,8 +52,7 @@ function prepareExtensionRunnerParams({
});
remoteFirefox.client = fakeRemoteFirefoxClient;
// $FlowIgnore: allow overriden params for testing purpose.
const runnerParams: FirefoxAndroidExtensionRunnerParams = {
const runnerParams = {
extensions: [
{
sourceDir: '/fake/sourceDir',
@ -89,7 +77,6 @@ function prepareExtensionRunnerParams({
return Promise.resolve(remoteFirefox);
}),
desktopNotifications: sinon.spy(() => {}),
// $FlowIgnore: Allow to mock stdin with an EventEmitter for testing purpose.
stdin: new EventEmitter(),
...(params || {}),
};
@ -595,7 +582,6 @@ describe('util/extension-runners/firefox-android', () => {
port: runnerInstance.selectedTCPPort,
});
// $FlowIgnore: ignore method-unbinding, sinon does just check the spy properties.
const { installTemporaryAddon } = runnerInstance.remoteFirefox;
sinon.assert.calledWithMatch(
@ -640,7 +626,6 @@ describe('util/extension-runners/firefox-android', () => {
await runnerInstance.reloadAllExtensions();
// $FlowIgnore: ignore method-unbinding, sinon does just check the spy properties.
sinon.assert.calledOnce(runnerInstance.remoteFirefox.reloadAddon);
});
@ -654,7 +639,6 @@ describe('util/extension-runners/firefox-android', () => {
params.extensions[0].sourceDir
);
// $FlowIgnore: ignore method-unbinding, sinon does just check the spy properties.
sinon.assert.calledOnce(runnerInstance.remoteFirefox.reloadAddon);
});
@ -676,7 +660,6 @@ describe('util/extension-runners/firefox-android', () => {
'"/non-existent/source-dir"'
);
// $FlowIgnore: ignore method-unbinding, sinon does just check the spy properties.
sinon.assert.notCalled(runnerInstance.remoteFirefox.reloadAddon);
});
@ -703,7 +686,6 @@ describe('util/extension-runners/firefox-android', () => {
)
);
// $FlowIgnore: ignore method-unbinding, sinon does just check the spy properties.
sinon.assert.called(runnerInstance.remoteFirefox.reloadAddon);
});
@ -855,7 +837,7 @@ describe('util/extension-runners/firefox-android', () => {
});
it('raises an error when unable to find an android version number', async () => {
async function expectInvalidVersionError(version: any) {
async function expectInvalidVersionError(version) {
const { params, fakeADBUtils } = prepareSelectedDeviceAndAPKParams();
fakeADBUtils.getAndroidVersionNumber = sinon.spy(() => {
@ -975,7 +957,6 @@ describe('util/extension-runners/firefox-android', () => {
for (const testCase of optionsWarningTestCases) {
const runnerOptions = { ...params, ...testCase.params };
// $FlowIgnore: allow use of inexact object literal for testing purpose.
new FirefoxAndroidExtensionRunner(runnerOptions); // eslint-disable-line no-new
assert.match(

View File

@ -1,12 +1,9 @@
/* @flow */
import { assert } from 'chai';
import { describe, it } from 'mocha';
import deepcopy from 'deepcopy';
import * as sinon from 'sinon';
import { FirefoxDesktopExtensionRunner } from '../../../src/extension-runners/firefox-desktop.js';
import type { FirefoxDesktopExtensionRunnerParams } from '../../../src/extension-runners/firefox-desktop';
import {
basicManifest,
getFakeFirefox,
@ -29,28 +26,19 @@ const tempInstallResultMissingAddonId = {
addon: { id: null },
};
type PrepareParams = {
params?: Object,
deps?: Object,
fakeFirefoxApp?: Object,
fakeRemoteFirefox?: Object,
debuggerPort?: number,
};
function prepareExtensionRunnerParams({
params,
fakeFirefoxApp,
fakeRemoteFirefox,
debuggerPort,
}: PrepareParams = {}) {
} = {}) {
const remoteFirefox = getFakeRemoteFirefox({
installTemporaryAddon: sinon.spy(() => Promise.resolve(tempInstallResult)),
...fakeRemoteFirefox,
});
const firefoxProcess = new StubChildProcess();
// $FlowIgnore: allow overriden params for testing purpose.
const runnerParams: FirefoxDesktopExtensionRunnerParams = {
const runnerParams = {
extensions: [
{
sourceDir: '/fake/sourceDir',
@ -290,7 +278,6 @@ describe('util/extension-runners/firefox-desktop', () => {
extensionPath: sourceDir,
})
);
// $FlowIgnore: ignored 'property not found' on sinon spy.
const install = params.firefoxApp.installExtension.firstCall.args[0];
assert.equal(install.asProxy, true);

View File

@ -1,4 +1,3 @@
/* @flow */
import path from 'path';
import deepcopy from 'deepcopy';
@ -53,10 +52,7 @@ function createFakeProfileFinder(profilesDirPath) {
return FakeProfileFinder;
}
async function createFakeProfilesIni(
dirPath: string,
profilesDefs: Array<Object>
): Promise<void> {
async function createFakeProfilesIni(dirPath, profilesDefs) {
let content = '';
for (const [idx, profile] of profilesDefs.entries()) {
@ -97,16 +93,7 @@ describe('firefox', () => {
// TODO: This object should accept dynamic properties since those are passed to firefox.run()
type RunFirefoxOptions = {
profile?: typeof FirefoxProfile,
};
function runFirefox({
// $FlowIgnore: for the purpose of this test, fakeProfile includes only a subset of the expected properties.
profile = fakeProfile,
...args
}: RunFirefoxOptions = {}) {
// $FlowIgnore: allow use of inexact object literal for testing purpose.
function runFirefox({ profile = fakeProfile, ...args } = {}) {
return firefox.run(profile, {
fxRunner: createFakeFxRunner(),
findRemotePort: () => Promise.resolve(6000),
@ -117,7 +104,6 @@ describe('firefox', () => {
it('executes the Firefox runner with a given profile', () => {
const runner = createFakeFxRunner();
const profile = fakeProfile;
// $FlowIgnore: allow use of fakeProfile as a fake FirefoxProfile instance.
return runFirefox({ fxRunner: runner, profile }).then(() => {
sinon.assert.called(runner);
assert.equal(runner.firstCall.args[0].profile, profile.path());
@ -217,7 +203,6 @@ describe('firefox', () => {
const extensions = [{ sourceDir: '/path/to/extension' }];
return runFirefox({
// $FlowIgnore: allow use of fakeProfile as a fake FirefoxProfile instance.
profile,
fxRunner: runner,
firefoxBinary,
@ -655,11 +640,6 @@ describe('firefox', () => {
describe('defaultCreateProfileFinder', () => {
// Define the params type for the prepareProfileFinderTest helper.
type PrepareProfileFinderTestParams = {
readProfiles?: Function,
getPath?: Function,
profiles?: Array<{ Name: string }>,
};
const defaultFakeProfiles = [{ Name: 'someName' }];
@ -675,7 +655,7 @@ describe('firefox', () => {
readProfiles = defaultReadProfilesMock,
getPath = defaultGetPathMock,
profiles = defaultFakeProfiles,
}: PrepareProfileFinderTestParams = {}) {
} = {}) {
const fakeReadProfiles = sinon.spy(readProfiles);
const fakeGetPath = sinon.spy(getPath);
const fakeProfiles = profiles;
@ -702,7 +682,6 @@ describe('firefox', () => {
it('creates a finder', () => {
const { FxProfile } = prepareProfileFinderTest();
FxProfile.Finder = sinon.spy(FxProfile.Finder);
// $FlowIgnore: allow use of FxProfile as a fake FirefoxProfile class.
firefox.defaultCreateProfileFinder({ FxProfile });
sinon.assert.calledWith(FxProfile.Finder, sinon.match(undefined));
});
@ -712,7 +691,6 @@ describe('firefox', () => {
FxProfile.Finder = sinon.spy(FxProfile.Finder);
const userDirectoryPath = '/non/existent/path';
// $FlowIgnore: allow use of FxProfile as a fake FirefoxProfile class.
firefox.defaultCreateProfileFinder({ userDirectoryPath, FxProfile });
sinon.assert.called(FxProfile.Finder);
@ -730,7 +708,6 @@ describe('firefox', () => {
const profileFinder = firefox.defaultCreateProfileFinder({
userDirectoryPath,
// $FlowIgnore: allow use of FxProfile as a fake FirefoxProfile class.
FxProfile,
});
@ -755,7 +732,6 @@ describe('firefox', () => {
const getter = firefox.defaultCreateProfileFinder({
userDirectoryPath,
// $FlowIgnore: allow use of FxProfile as a fake FirefoxProfile class.
FxProfile,
});
@ -781,7 +757,6 @@ describe('firefox', () => {
const getter = firefox.defaultCreateProfileFinder({
userDirectoryPath,
// $FlowIgnore: allow use of FxProfile as a fake FirefoxProfile class.
FxProfile,
});
@ -882,7 +857,7 @@ describe('firefox', () => {
});
describe('installExtension', () => {
function setUp(testPromise: Function) {
function setUp(testPromise) {
return withTempDir((tmpDir) => {
const data = {
extensionPath: fixturePath('minimal_extension-1.0.zip'),

View File

@ -1,4 +1,3 @@
/* @flow */
import { describe, it } from 'mocha';
import { assert } from 'chai';
@ -32,7 +31,6 @@ describe('firefox/preferences', () => {
it('throws an error for unsupported apps', () => {
assert.throws(
// $FlowIgnore: ignore type errors on testing nonexistent 'thunderbird' prefs
() => getPrefs('thunderbird'),
WebExtError,
/Unsupported application: thunderbird/

View File

@ -1,4 +1,3 @@
/* @flow */
import net from 'net';
import { describe, it, beforeEach, afterEach } from 'mocha';
@ -13,7 +12,7 @@ import FirefoxRDPClient, {
function createFakeRDPServer() {
let lastSocket;
function sendRDPMessage(msg: Object) {
function sendRDPMessage(msg) {
let data = Buffer.from(JSON.stringify(msg));
data = Buffer.concat([Buffer.from(`${data.length}:`), data]);
lastSocket.write(data);
@ -104,7 +103,7 @@ describe('rdp-client', () => {
describe('FirefoxRDPClient', () => {
let fakeRDPServer;
let client: FirefoxRDPClient;
let client;
beforeEach(() => {
fakeRDPServer = createFakeRDPServer();
@ -190,7 +189,6 @@ describe('rdp-client', () => {
it('rejects on RDP request without a target actor', async () => {
await getConnectedRDPClient();
await assert.isRejected(
// $FlowIgnore: ignore flowtype error for testing purpose.
client.request({ type: 'getRoot' }),
/Unexpected RDP request without target actor/
);
@ -313,15 +311,10 @@ describe('rdp-client', () => {
// Create an object with a circular dependency to trigger a stringify.
// exception.
const req = { to: 'root' };
// $FlowIgnore: ignore flowtype error for testing purpose.
req.circular = req;
await getConnectedRDPClient();
await assert.isRejected(
// $FlowIgnore: ignore flowtype error for testing purpose.
client.request(req),
Error
);
await assert.isRejected(client.request(req), Error);
assertClientHasNoRequests();
});
@ -352,15 +345,10 @@ describe('rdp-client', () => {
off: sinon.spy(),
};
// $FlowIgnore: allow overwrite property for testing purpose.
c._rdpConnection = fakeConn;
// $FlowIgnore
c._onData = function fakeOnData() {};
// $FlowIgnore
c._onError = function fakeOnError() {};
// $FlowIgnore
c._onEnd = function fakeOnEnd() {};
// $FlowIgnore
c._onTimeout = function fakeOnTimeout() {};
c.disconnect();

View File

@ -1,4 +1,3 @@
/* @flow */
import net from 'net';
import { describe, it } from 'mocha';
@ -30,7 +29,6 @@ describe('firefox.remote', () => {
connectToFirefox: sinon.spy(() => Promise.resolve(fakeFirefoxClient())),
...options,
};
// $FlowIgnore: allow use of inexact object literal for testing purpose.
const connect = defaultConnector(port, options);
return { options, connect };
}
@ -49,7 +47,6 @@ describe('firefox.remote', () => {
it('lets you configure the port', async () => {
const { connect, options } = prepareConnection(7000);
await connect;
// $FlowIgnore: flow doesn't know about sinon spy properties.
assert.equal(options.connectToFirefox.args[0], 7000);
});
});
@ -194,7 +191,6 @@ describe('firefox.remote', () => {
const conn = makeInstance();
const addonRequest = sinon.stub().resolves(stubResponse);
// $FlowIgnore: allow overwrite not writable property for testing purpose.
conn.addonRequest = addonRequest;
const returnedAddon = await conn.checkForAddonReloading(addon);
@ -212,7 +208,6 @@ describe('firefox.remote', () => {
const stubResponse = { requestTypes: ['install'] };
const conn = makeInstance();
// $FlowIgnore: allow overwrite not writable property for testing purpose.
conn.addonRequest = () => Promise.resolve(stubResponse);
await conn
@ -232,7 +227,6 @@ describe('firefox.remote', () => {
.stub()
.resolves({ requestTypes: ['reload'] });
// $FlowIgnore: allow overwrite not writable property for testing purpose.
conn.addonRequest = addonRequest;
const checkedAddon = await conn.checkForAddonReloading(addon);
const finalAddon = await conn.checkForAddonReloading(checkedAddon);
@ -404,12 +398,9 @@ describe('firefox.remote', () => {
const getInstalledAddon = sinon.stub().resolves(addon);
const addonRequest = sinon.stub().resolves({});
// $FlowIgnore: allow overwrite not writable property for testing purpose.
conn.getInstalledAddon = getInstalledAddon;
// $FlowIgnore: allow overwrite not writable property for testing purpose.
conn.checkForAddonReloading = (addonToCheck) =>
Promise.resolve(addonToCheck);
// $FlowIgnore: allow overwrite not writable property for testing purpose.
conn.addonRequest = addonRequest;
await conn.reloadAddon('some-id');
@ -431,9 +422,7 @@ describe('firefox.remote', () => {
Promise.resolve(addonToCheck)
);
// $FlowIgnore: allow overwrite not writable property for testing purpose.
conn.getInstalledAddon = getInstalledAddon;
// $FlowIgnore: allow overwrite not writable property for testing purpose.
conn.checkForAddonReloading = checkForAddonReloading;
await conn.reloadAddon(addon.id);
@ -447,7 +436,6 @@ describe('firefox.remote', () => {
describe('connectWithMaxRetries', () => {
function firefoxClient(opt = {}, deps) {
return connectWithMaxRetries(
// $FlowIgnore: allow use of inexact object literal for testing purpose.
{
maxRetries: 0,
retryInterval: 1,
@ -506,10 +494,9 @@ describe('firefox.remote', () => {
});
describe('findFreeTcpPort', () => {
async function promiseServerOnPort(port): Promise<net.Server> {
async function promiseServerOnPort(port) {
return new Promise((resolve) => {
const srv = net.createServer();
// $FlowFixMe: signature for listen() is missing - see https://github.com/facebook/flow/pull/8290
srv.listen(port, '127.0.0.1', () => {
resolve(srv);
});

View File

@ -1,5 +1,3 @@
/* @flow */
import EventEmitter from 'events';
import chai from 'chai';
@ -54,15 +52,7 @@ android.permission.WRITE_EXTERNAL_STORAGE, granted=true
const { assert } = chai;
function getFakeADBKit({
adbClient = {},
adbkitUtil = {},
adbDevice = {},
}: {
adbClient?: Object,
adbkitUtil?: Object,
adbDevice?: Object,
}) {
function getFakeADBKit({ adbClient = {}, adbkitUtil = {}, adbDevice = {} }) {
const fakeTransfer = new EventEmitter();
const adbUtilReadAllStub = sinon.stub();
@ -101,7 +91,6 @@ function getFakeADBKit({
function createSpawnADBErrorSpy() {
return sinon.spy(() => {
const fakeADBError = new Error('spawn adb');
// $FlowFixMe: reuse ErrorWithCode from other tests
fakeADBError.code = 'ENOENT';
return Promise.reject(fakeADBError);
});
@ -112,11 +101,6 @@ async function testSpawnADBUsageError({
adbClient,
adbkitUtil,
adbDevice,
}: {
testFn: Function,
adbClient?: Object,
adbkitUtil?: Object,
adbDevice?: Object,
}) {
const adb = getFakeADBKit({ adbClient, adbkitUtil, adbDevice });
const adbUtils = new ADBUtils({ adb });
@ -665,7 +649,7 @@ describe('utils/adb', () => {
});
describe('detectOrRemoveOldArtifacts', () => {
function createFakeReaddirFile(artifactName: string, isDirectory: boolean) {
function createFakeReaddirFile(artifactName, isDirectory) {
return {
name: artifactName,
isDirectory: () => {
@ -992,8 +976,8 @@ describe('utils/adb', () => {
});
async function testReferenceBrowserApkComponent(
firefoxApkComponent?: string,
expectedApkComponent: string
firefoxApkComponent,
expectedApkComponent
) {
const adb = getFakeADBKit({
adbDevice: {

View File

@ -1,4 +1,3 @@
/* @flow */
import path from 'path';
import { it, describe } from 'mocha';

View File

@ -1,4 +1,3 @@
/* @flow */
import { it, describe } from 'mocha';
import * as sinon from 'sinon';

View File

@ -1,4 +1,3 @@
/* @flow */
import path from 'path';
import { assert } from 'chai';

View File

@ -1,4 +1,3 @@
/* @flow */
import { describe, it } from 'mocha';
import { assert } from 'chai';
@ -6,7 +5,6 @@ import { FileFilter, isSubPath } from '../../../src/util/file-filter.js';
describe('util/file-filter', () => {
const newFileFilter = (params = {}) => {
// $FlowIgnore: allow use of inexact object literal for testing purpose.
return new FileFilter({
sourceDir: '.',
...params,

View File

@ -1,4 +1,3 @@
/* @flow */
import path from 'path';
import { fs } from 'mz';

View File

@ -1,4 +1,3 @@
/* @flow */
import { Writable as WritableStream } from 'stream';
import { pathToFileURL } from 'url';
@ -32,7 +31,6 @@ describe('logger', () => {
};
}
// NOTE: create a fake process that makes flow happy.
function fakeProcess() {
class FakeWritableStream extends WritableStream {
write = () => true;
@ -76,7 +74,6 @@ describe('logger', () => {
it('does not log debug packets unless verbose', () => {
const log = new ConsoleStream({ verbose: false });
const localProcess = fakeProcess();
// $FlowIgnore: fake process for testing reasons.
log.write(packet({ level: bunyan.DEBUG }), { localProcess });
sinon.assert.notCalled(localProcess.stdout.write);
});
@ -84,7 +81,6 @@ describe('logger', () => {
it('does not log trace packets unless verbose', () => {
const log = new ConsoleStream({ verbose: false });
const localProcess = fakeProcess();
// $FlowIgnore: fake process for testing reasons.
log.write(packet({ level: bunyan.TRACE }), { localProcess });
sinon.assert.notCalled(localProcess.stdout.write);
});
@ -92,7 +88,6 @@ describe('logger', () => {
it('logs debug packets when verbose', () => {
const log = new ConsoleStream({ verbose: true });
const localProcess = fakeProcess();
// $FlowIgnore: fake process for testing reasons.
log.write(packet({ level: bunyan.DEBUG }), { localProcess });
sinon.assert.called(localProcess.stdout.write);
});
@ -100,7 +95,6 @@ describe('logger', () => {
it('logs trace packets when verbose', () => {
const log = new ConsoleStream({ verbose: true });
const localProcess = fakeProcess();
// $FlowIgnore: fake process for testing reasons.
log.write(packet({ level: bunyan.TRACE }), { localProcess });
sinon.assert.called(localProcess.stdout.write);
});
@ -108,10 +102,8 @@ describe('logger', () => {
it('logs info packets when verbose or not', () => {
const log = new ConsoleStream({ verbose: false });
const localProcess = fakeProcess();
// $FlowIgnore: fake process for testing reasons.
log.write(packet({ level: bunyan.INFO }), { localProcess });
log.makeVerbose();
// $FlowIgnore: fake process for testing reasons.
log.write(packet({ level: bunyan.INFO }), { localProcess });
sinon.assert.callCount(localProcess.stdout.write, 2);
});
@ -121,10 +113,8 @@ describe('logger', () => {
const localProcess = fakeProcess();
log.startCapturing();
// $FlowIgnore: fake process for testing reasons.
log.write(packet({ msg: 'message' }), { localProcess });
sinon.assert.notCalled(localProcess.stdout.write);
// $FlowIgnore: fake process for testing reasons.
log.flushCapturedLogs({ localProcess });
sinon.assert.calledWith(localProcess.stdout.write, 'message\n');
});
@ -134,14 +124,11 @@ describe('logger', () => {
let localProcess = fakeProcess();
log.startCapturing();
// $FlowIgnore: fake process for testing reasons.
log.write(packet(), { localProcess });
// $FlowIgnore: fake process for testing reasons.
log.flushCapturedLogs({ localProcess });
// Make sure there is nothing more to flush.
localProcess = fakeProcess();
// $FlowIgnore: fake process for testing reasons.
log.flushCapturedLogs({ localProcess });
sinon.assert.notCalled(localProcess.stdout.write);
});
@ -151,12 +138,10 @@ describe('logger', () => {
let localProcess = fakeProcess();
log.startCapturing();
// $FlowIgnore: fake process for testing reasons.
log.write(packet(), { localProcess });
sinon.assert.notCalled(localProcess.stdout.write);
log.stopCapturing();
// $FlowIgnore: fake process for testing reasons.
log.write(packet(), { localProcess });
sinon.assert.callCount(localProcess.stdout.write, 1);
@ -165,7 +150,6 @@ describe('logger', () => {
log.startCapturing();
log.write(packet());
localProcess = fakeProcess();
// $FlowIgnore: fake process for testing reasons.
log.flushCapturedLogs({ localProcess });
sinon.assert.callCount(localProcess.stdout.write, 1);
});

View File

@ -1,4 +1,3 @@
/* @flow */
import path from 'path';
import { describe, it } from 'mocha';
@ -223,7 +222,7 @@ describe('util/manifest', () => {
describe('getManifestId', () => {
const id = 'basic-manifest@web-ext-test-suite';
['applications', 'browser_specific_settings'].forEach((key: string) => {
['applications', 'browser_specific_settings'].forEach((key) => {
describe(`with ${key}`, () => {
it('returns gecko.id if present', () => {
assert.equal(

View File

@ -1,4 +1,3 @@
/* @flow */
import { promisify } from 'util';
import { describe, it } from 'mocha';

View File

@ -1,4 +1,3 @@
/* @flow */
import { createHash } from 'crypto';
import { promises as fsPromises, readFileSync } from 'fs';
import path from 'path';
@ -28,15 +27,7 @@ class JSONResponse extends Response {
}
}
const mockNodeFetch = (
nodeFetchStub: any,
url: URL | string,
method: string,
responses: Array<{
body: any,
status: number,
}>
): void => {
const mockNodeFetch = (nodeFetchStub, url, method, responses) => {
const stubMatcher = nodeFetchStub.withArgs(
url instanceof URL ? url : new URL(url),
sinon.match.has('method', method)

View File

@ -1,4 +1,3 @@
/* @flow */
import { describe, it } from 'mocha';
import { fs } from 'mz';
import { assert } from 'chai';

View File

@ -1,4 +1,3 @@
/* @flow */
import { it, describe } from 'mocha';
import * as sinon from 'sinon';

View File

@ -1,4 +1,3 @@
/* @flow */
import path from 'path';
import { assert } from 'chai';
@ -15,15 +14,6 @@ import {
import { withTempDir } from '../../src/util/temp-dir.js';
import { UsageError, WebExtError } from '../../src/errors.js';
type MakeArgvParams = {|
userCmd?: Array<string>,
command?: string,
commandDesc?: string,
commandExecutor?: Function,
commandOpt?: Object,
globalOpt?: Object,
|};
function makeArgv({
userCmd = ['fakecommand'],
command = 'fakecommand',
@ -31,7 +21,7 @@ function makeArgv({
commandExecutor = sinon.stub(),
commandOpt,
globalOpt,
}: MakeArgvParams) {
}) {
const program = new Program(userCmd);
if (globalOpt) {
@ -996,7 +986,6 @@ describe('config', () => {
describe('discoverConfigFiles', () => {
function _discoverConfigFiles(params = {}) {
// $FlowIgnore: allow use of inexact object literal for testing purpose.
return discoverConfigFiles({
// By default, do not look in the real home directory.
getHomeDir: () => '/not-a-directory',

View File

@ -1,4 +1,3 @@
/* @flow */
import { describe, it } from 'mocha';
import { assert } from 'chai';
@ -35,7 +34,7 @@ describe('errors', () => {
describe('onlyErrorsWithCode', () => {
class ErrorWithErrno extends Error {
errno: number;
errno;
constructor() {
super('pretend this is a system error');
this.errno = 53;

View File

@ -1,4 +1,3 @@
/* @flow */
import path from 'path';
import { describe, it } from 'mocha';
@ -406,7 +405,6 @@ describe('program.Program', () => {
},
});
// $FlowIgnore: override systemProcess for testing purpose.
program.cleanupProcessEnvConfigs({ env: fakeEnv });
assert.deepEqual(fakeEnv, {
WEB_EXT_RUN_OPTION: 'from-env',
@ -419,7 +417,7 @@ describe('program.Program', () => {
describe('program.main', () => {
function execProgram(
argv,
{ projectRoot = '', runOptions, ...mainOptions }: Object = {}
{ projectRoot = '', runOptions, ...mainOptions } = {}
) {
return main(projectRoot, {
argv,
@ -435,11 +433,7 @@ describe('program.main', () => {
});
}
type MakeConfigLoaderParams = {|
configObjects: { [fileName: string]: Object },
|};
function makeConfigLoader({ configObjects }: MakeConfigLoaderParams) {
function makeConfigLoader({ configObjects }) {
return (fileName) => {
const conf = configObjects[fileName];
if (!conf) {

View File

@ -1,4 +1,3 @@
/* @flow */
import { beforeEach, afterEach } from 'mocha';
import { consoleStream } from '../../src/util/logger.js';

View File

@ -1,5 +1,4 @@
/* eslint-disable no-console */
/* @flow */
import path from 'path';
import { it, describe } from 'mocha';
@ -15,13 +14,8 @@ import {
import { withTempDir } from '../../src/util/temp-dir.js';
import { makeSureItFails } from './helpers.js';
type AssertWatchedParams = {
watchFile?: Array<string>,
touchedFile: string,
};
describe('watcher', () => {
const watchChange = ({ watchFile, touchedFile }: AssertWatchedParams = {}) =>
const watchChange = ({ watchFile, touchedFile } = {}) =>
withTempDir(async (tmpDir) => {
const artifactsDir = path.join(tmpDir.path(), 'web-ext-artifacts');
const someFile = path.join(tmpDir.path(), touchedFile);
@ -47,7 +41,6 @@ describe('watcher', () => {
shouldWatchFile: () => true,
});
// $FlowIgnore: retrieve internal Watchpack properties for testing purpose.
const { fileWatchers, directoryWatchers } = watcher;
let watchedFilePath;
let watchedDirPath;

View File

@ -1,4 +1,3 @@
/* @flow */
import { afterEach, describe, it } from 'mocha';
import { assert } from 'chai';
import * as sinon from 'sinon';
@ -13,14 +12,13 @@ describe('webExt', () => {
});
describe('exposes commands', () => {
let stub: any;
let stub;
afterEach(() => {
resetMockModules();
stub = undefined;
});
for (const cmd of ['run', 'lint', 'build', 'sign', 'docs']) {
it(`lazily loads cmd/${cmd}`, async () => {
// $FlowIgnore: non string literal imports are not supported by flow.
const cmdModule = await import(`../../src/cmd/${cmd}.js`);
stub = sinon.stub({ default: cmdModule.default }, 'default');
@ -37,7 +35,7 @@ describe('webExt', () => {
stub?.returns(expectedResult);
const { default: webExtModule } = await import('../../src/main.js');
const runCommand: Function = webExtModule.cmd[cmd];
const runCommand = webExtModule.cmd[cmd];
const result = await runCommand(params, options);
// Check whether parameters and return values are forwarded as-is.