Repository: mozilla/web-ext (JavaScript)

Issue:
Firefox fails to launch when using `webExt.cmd.run` in NodeJS
### Is this a feature request or a bug?

Bug.

### What is the current behavior?

When running `webExt.cmd.run` in a NodeJS script, Firefox fails to open, and `web-ext` fails with the following error:

```console
file:///path-to-extension/node_modules/web-ext/lib/firefox/remote.js:92
      throw new WebExtError(`installTemporaryAddon: Error: ${message}`);
            ^
WebExtError: installTemporaryAddon: Error: Error: Could not install add-on at '.': [Exception... "Component returned failure code: 0x80520001 (NS_ERROR_FILE_UNRECOGNIZED_PATH) [nsIFile.initWithPath]"  nsresult: "0x80520001 (NS_ERROR_FILE_UNRECOGNIZED_PATH)"  location: "JS frame :: resource://devtools/server/actors/addon/addons.js :: installTemporaryAddon :: line 31"  data: no]
    at RemoteFirefox.installTemporaryAddon (file:///path-to-extension/node_modules/web-ext/lib/firefox/remote.js:92:13)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async FirefoxDesktopExtensionRunner.startFirefoxInstance (file:///path-to-extension/node_modules/web-ext/lib/extension-runners/firefox-desktop.js:216:27)
    at async FirefoxDesktopExtensionRunner.run (file:///path-to-extension/node_modules/web-ext/lib/extension-runners/firefox-desktop.js:49:5)
    at async Promise.all (index 0)
    at async MultiExtensionRunner.run (file:///path-to-extension/node_modules/web-ext/lib/extension-runners/index.js:66:5)
    at async run (file:///path-to-extension/node_modules/web-ext/lib/cmd/run.js:177:3)

Node.js v21.4.0
```
Everything works as expected when passing `target: chromium` as an option, and Chromium launches with the extension loaded. The issue only occurs in Firefox. There are also no issues when `web-ext` is run via the command line, and Firefox launches as expected:
```console
npx web-ext run --source-dir=.
```
The issue only occurs when using `web-ext` in a NodeJS script.

### Steps to reproduce
1. This bug appears to be independent of the extension used. One should therefore be able to reproduce this bug with any minimal extension. To create a MRE, I used one of the [example extensionslons](https://github.com/mdn/webextensions-examples):
```console
git clone git clone https://github.com/mdn/webextensions-examples.git && cd webextensions-examples/beastify
```
2. Create the file `package.json` with the following contents:
```json
{
  "type": "module",
  "devDependencies": {
    "web-ext": "^7.9.0"
  }
}
```
3. Run `npm install`.
4. Create a file `run.js` with the following contents:
```javascript
import webExt from 'web-ext';

webExt.cmd.run({sourceDir: '.'});
```
5. Run `node run.js` and observe the above error.
6. Execute `npx web-ext run --source-dir=.` and observe that Firefox launches without issue.
7. Modify the file `run.js` to have the following contents:
```javascript
import webExt from 'web-ext';

webExt.cmd.run({sourceDir: '.', target: 'chromium'});
```
8. Run `node run.js` again and observe that Chromium launches without issue.

### What is the expected or desired behavior?

Firefox should launch as it does when running `npx web-ext` from the command line.

### Version information (for bug reports)

- **Firefox version**: Mozilla Firefox 120.0.1
- **Your OS and version**: Arch Linux
- Output of commands `node --version && npm --version && npx web-ext --version`:
```console
v21.4.0
10.2.5
7.9.0
```


Discussion:
@fregante, this is **not a duplicate of #2975**. I can run the command causing issues in #2975 (`web-ext run --verbose --firefox "deved" --firefox-profile=mc02 --profile-create-if-missing --source-dir=$PWD --devtools --no-config-discovery`) without any errors.

It is also unlikely that this is an issue with Firefox 121, as I do not have an installation of Firefox 121 on my computer (I tested with Firefox 120 as per the bug report above, and just tested with Firefox Developer Edition 122 as well, and the issue occurs in both versions).

As you can read in the report above, the issue only occurs when using `web-ext`'s API, but not when running `web-ext` from the command line. This leads me to belive that this is an issue with `web-ext` and not with Firefox, since it works fine when using the command line.
Firefox doesn't accept relative paths. When `web-ext run` is used from the CLI, it passes an absolute path to the extension: https://github.com/mozilla/web-ext/blob/3063a770638f411a2e77b3ab7dc9eea3e536906a/src/program.js#L433

We would be willing to accept a patch that turns the input (`sourceDir`) of `run` into an absolute path if needed: https://github.com/mozilla/web-ext/blob/3063a770638f411a2e77b3ab7dc9eea3e536906a/src/cmd/run.js#L64
Hello, I've opened a PR #3024 to add a patch for this, would really appreciate any feedback and suggestions. Thanks!



Patch:
diff --git a/src/cmd/run.js b/src/cmd/run.js
index 8ff45dc30b..76f7af18dd 100644
--- a/src/cmd/run.js
+++ b/src/cmd/run.js
@@ -1,3 +1,5 @@
+import path from 'path';
+
 import { fs } from 'mz';
 
 import defaultBuildExtension from './build.js';
@@ -61,6 +63,7 @@ export default async function run(
     getValidatedManifest = defaultGetValidatedManifest,
   } = {},
 ) {
+  sourceDir = path.resolve(sourceDir);
   log.info(`Running web extension from ${sourceDir}`);
   if (preInstall) {
     log.info(
diff --git a/src/program.js b/src/program.js
index 35d794a912..a090e29158 100644
--- a/src/program.js
+++ b/src/program.js
@@ -430,7 +430,7 @@ Example: $0 --help run.
       default: process.cwd(),
       requiresArg: true,
       type: 'string',
-      coerce: (arg) => (arg != null ? path.resolve(arg) : undefined),
+      coerce: (arg) => arg ?? undefined,
     },
     'artifacts-dir': {
       alias: 'a',


Test patch:
diff --git a/tests/unit/test-cmd/test.run.js b/tests/unit/test-cmd/test.run.js
index 4fa0e37bbe..b1338e6204 100644
--- a/tests/unit/test-cmd/test.run.js
+++ b/tests/unit/test-cmd/test.run.js
@@ -128,6 +128,21 @@ describe('run', () => {
     });
   });
 
+  it('turns sourceDir into an absolute path', async () => {
+    const getFakeManifest = sinon.spy();
+    const cmd = await prepareRun();
+
+    await cmd.run(
+      { sourceDir: '.' },
+      { getValidatedManifest: getFakeManifest },
+    );
+
+    sinon.assert.calledOnce(desktopRunnerStub);
+    const runnerParams = desktopRunnerStub.firstCall.args[0];
+    const [{ sourceDir: expectedSourceDir }] = runnerParams.extensions;
+    assert.equal(expectedSourceDir, process.cwd());
+  });
+
   it('passes the expected parameters to the extension runner', async () => {
     const cmd = await prepareRun();
     const runOptions = {
diff --git a/tests/unit/test.program.js b/tests/unit/test.program.js
index e70508f559..ab41ccbb5b 100644
--- a/tests/unit/test.program.js
+++ b/tests/unit/test.program.js
@@ -481,19 +481,6 @@ describe('program.main', () => {
     sinon.assert.calledWith(fakeVersionGetter, projectRoot);
   });
 
-  it('turns sourceDir into an absolute path', () => {
-    const fakeCommands = fake(commands, {
-      build: () => Promise.resolve(),
-    });
-    return execProgram(['build', '--source-dir', '..'], {
-      commands: fakeCommands,
-    }).then(() => {
-      sinon.assert.calledWithMatch(fakeCommands.build, {
-        sourceDir: path.resolve(path.join(process.cwd(), '..')),
-      });
-    });
-  });
-
   it('normalizes the artifactsDir path', () => {
     const fakeCommands = fake(commands, {
       build: () => Promise.resolve(),