mirror of https://github.com/mozilla/web-ext.git
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 passpull/2770/head
parent
ba3439fd4d
commit
a59873b964
3
.babelrc
3
.babelrc
|
@ -6,8 +6,7 @@
|
|||
// Leave import/export statements unchanged in the babel transpiling output.
|
||||
"modules": false
|
||||
}
|
||||
],
|
||||
"@babel/flow"
|
||||
]
|
||||
],
|
||||
"plugins": [
|
||||
["transform-inline-environment-variables", {
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -2,5 +2,4 @@ lib/
|
|||
!scripts/lib
|
||||
coverage/
|
||||
artifacts/
|
||||
flow-typed/
|
||||
commitlint.config.js
|
||||
|
|
11
.eslintrc
11
.eslintrc
|
@ -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.
|
||||
|
|
19
.flowconfig
19
.flowconfig
|
@ -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$
|
|
@ -8,7 +8,6 @@ LICENSE
|
|||
# exclude these directories
|
||||
/artifacts/
|
||||
/coverage/
|
||||
/flow-typed/
|
||||
/lib/
|
||||
/node_modules/
|
||||
/tests/fixtures/
|
||||
|
|
101
CONTRIBUTING.md
101
CONTRIBUTING.md
|
@ -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/
|
||||
|
|
|
@ -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.
|
|
@ -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>,
|
||||
}
|
||||
}
|
|
@ -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>;
|
||||
}
|
|
@ -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>;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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;
|
||||
};
|
|
@ -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);
|
||||
|
|
|
@ -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}`);
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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>;
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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 [
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
/* @flow */
|
||||
import { main } from './program.js';
|
||||
import cmd from './cmd/index.js';
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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:
|
||||
*
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
/* @flow */
|
||||
import { describe, it } from 'mocha';
|
||||
import { assert } from 'chai';
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
/* @flow */
|
||||
import { describe, it } from 'mocha';
|
||||
|
||||
import {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
/* @flow */
|
||||
import { describe, it } from 'mocha';
|
||||
import { assert } from 'chai';
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
/* @flow */
|
||||
import path from 'path';
|
||||
|
||||
import { describe, it } from 'mocha';
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
/* @flow */
|
||||
import { spawn } from 'child_process';
|
||||
import path from 'path';
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
/* @flow */
|
||||
import { execFileSync } from 'child_process';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
/* @flow */
|
||||
import { describe, it } from 'mocha';
|
||||
import { assert } from 'chai';
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
/* @flow */
|
||||
|
||||
import chai from 'chai';
|
||||
import chaiAsPromised from 'chai-as-promised';
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
/* @flow */
|
||||
import { it, describe } from 'mocha';
|
||||
import * as sinon from 'sinon';
|
||||
import { assert } from 'chai';
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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, {
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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/
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
/* @flow */
|
||||
import path from 'path';
|
||||
|
||||
import { it, describe } from 'mocha';
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
/* @flow */
|
||||
import { it, describe } from 'mocha';
|
||||
import * as sinon from 'sinon';
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
/* @flow */
|
||||
import path from 'path';
|
||||
|
||||
import { assert } from 'chai';
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
/* @flow */
|
||||
import path from 'path';
|
||||
|
||||
import { fs } from 'mz';
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
/* @flow */
|
||||
import { promisify } from 'util';
|
||||
|
||||
import { describe, it } from 'mocha';
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
/* @flow */
|
||||
import { describe, it } from 'mocha';
|
||||
import { fs } from 'mz';
|
||||
import { assert } from 'chai';
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
/* @flow */
|
||||
import { it, describe } from 'mocha';
|
||||
import * as sinon from 'sinon';
|
||||
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
/* @flow */
|
||||
import { beforeEach, afterEach } from 'mocha';
|
||||
|
||||
import { consoleStream } from '../../src/util/logger.js';
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue