Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions scripts/lib/mocha.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,6 @@ const runMocha = (args, execMochaOptions = {}, coverageEnabled) => {
shell.echo(`\nSetting mocha timeout from env var: ${MOCHA_TIMEOUT}\n`);
}

// Pass testdouble node loader to support ESM module mocking and
// transpiling on the fly the tests modules.
binArgs.push('-n="loader=testdouble"');

const res = spawnSync(binPath, binArgs, {
...execMochaOptions,
env: {
Expand Down
56 changes: 20 additions & 36 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,29 +12,6 @@ import { UsageError, WebExtError } from './errors.js';

const log = createLogger(import.meta.url);

// NOTE: this error message is used in an interpolated string (while the other two help
// messages are being logged as is).
export const WARN_LEGACY_JS_EXT = [
'should be renamed to ".cjs" or ".mjs" file extension to ensure its format is not ambiguous.',
'Config files with the ".js" file extension are deprecated and will not be loaded anymore',
'in a future web-ext major version.',
].join(' ');

export const HELP_ERR_MODULE_FROM_ESM = [
'This config file belongs to a package.json file with "type" set to "module".',
'Change the file extension to ".cjs" or rewrite it as an ES module and use the ".mjs" file extension.',
].join(' ');

export const HELP_ERR_IMPORTEXPORT_CJS = [
'This config file is defined as an ES module, but it belongs to either a project directory',
'with a package.json file with "type" set to "commonjs" or one without any package.json file.',
'Change the file extension to ".mjs" to fix the config loading error.',
].join(' ');

const ERR_IMPORT_FROM_CJS = 'Cannot use import statement outside a module';
const ERR_EXPORT_FROM_CJS = "Unexpected token 'export'";
const ERR_MODULE_FROM_ESM = 'module is not defined in ES module scope';

export function applyConfigToArgv({
argv,
argvFromCLI,
Expand Down Expand Up @@ -145,12 +122,18 @@ export async function loadJSConfigFile(filePath) {
`Loading JS config file: "${filePath}" ` +
`(resolved to "${resolvedFilePath}")`,
);

if (filePath.endsWith('.js')) {
log.warn(`WARNING: config file ${filePath} ${WARN_LEGACY_JS_EXT}`);
throw new UsageError(
` Invalid config file "${resolvedFilePath}": the file extension should be` +
'".cjs" or ".mjs". More information at: https://mzl.la/web-ext-config-file',
);
}

let configObject;
try {
const nonce = `${Date.now()}-${Math.random()}`;

let configModule;
if (resolvedFilePath.endsWith('package.json')) {
configModule = parseJSON(
Expand All @@ -165,35 +148,36 @@ export async function loadJSConfigFile(filePath) {
// ES modules may expose both a default and named exports and so
// we merge the named exports on top of what may have been set in
// the default export.
if (filePath.endsWith('.cjs')) {
// Remove the additional 'module.exports' named export that Node.js >=
// 24 is returning from the dynamic import call (in addition to being
// also set on the default property as in Node.js < 24).
delete esmConfigMod['module.exports'];
}
configObject = { ...configDefault, ...esmConfigMod };
} else {
configObject = { ...configModule };
}
} catch (error) {
log.debug('Handling error:', error);
let errorMessage = error.message;
if (error.message.startsWith(ERR_MODULE_FROM_ESM)) {
errorMessage = HELP_ERR_MODULE_FROM_ESM;
} else if (
[ERR_IMPORT_FROM_CJS, ERR_EXPORT_FROM_CJS].includes(error.message)
) {
errorMessage = HELP_ERR_IMPORTEXPORT_CJS;
}
throw new UsageError(
`Cannot read config file: ${resolvedFilePath}\n` +
`Error: ${errorMessage}`,
const configFileError = new UsageError(
`Cannot read config file "${resolvedFilePath}":\n${error}`,
);
configFileError.cause = error;
throw configFileError;
}

if (filePath.endsWith('package.json')) {
log.debug('Looking for webExt key inside package.json file');
configObject = configObject.webExt || {};
}

if (Object.keys(configObject).length === 0) {
log.debug(
`Config file ${resolvedFilePath} did not define any options. ` +
'Did you set module.exports = {...}?',
);
}

return configObject;
}

Expand Down
3 changes: 3 additions & 0 deletions src/program.js
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,9 @@ export class Program {
if (error.code) {
log.error(`Error code: ${error.code}\n`);
}
if (error.cause && adjustedArgv.verbose) {
log.error(`Error cause: ${error.cause.stack}\n`);
}

log.debug(`Command executed: ${cmd}`);

Expand Down
12 changes: 6 additions & 6 deletions tests/functional/test.cli.sign.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ import {
reportCommandErrors,
} from './common.js';

// Put this as "web-ext-config.js" in the current directory, and replace
// Put this as "web-ext-config.mjs" in the current directory, and replace
// "FAKEAPIKEY" and "FAKEAPISECRET" with the actual values to enable
// "web-ext sign" without passing those values via the CLI parameters.
const GOOD_EXAMPLE_OF_WEB_EXT_CONFIG_JS = `
module.exports = {
export default {
sign: {
apiKey: "FAKEAPIKEY",
apiSecret: "FAKEAPISECRET",
Expand All @@ -27,7 +27,7 @@ module.exports = {

// Do NOT use this to specify the API key and secret. It won't work.
const BAD_EXAMPLE_OF_WEB_EXT_CONFIG_JS = `
module.exports = {
export default {
// Bad config: those should be under the "sign" key.
apiKey: "FAKEAPIKEY",
apiSecret: "FAKEAPISECRET",
Expand Down Expand Up @@ -86,7 +86,7 @@ describe('web-ext sign', () => {
it('should use config file if required parameters are not in the arguments', () =>
withTempAddonDir({ addonPath: minimalAddonPath }, (srcDir, tmpDir) => {
writeFileSync(
path.join(tmpDir, 'web-ext-config.js'),
path.join(tmpDir, 'web-ext-config.mjs'),
GOOD_EXAMPLE_OF_WEB_EXT_CONFIG_JS,
);

Expand Down Expand Up @@ -120,7 +120,7 @@ describe('web-ext sign', () => {

it('should show an error message if the api-key is not set in the config', () =>
withTempAddonDir({ addonPath: minimalAddonPath }, (srcDir, tmpDir) => {
const configFilePath = path.join(tmpDir, 'web-ext-config.js');
const configFilePath = path.join(tmpDir, 'web-ext-config.mjs');
writeFileSync(configFilePath, BAD_EXAMPLE_OF_WEB_EXT_CONFIG_JS);
const argv = [
'sign',
Expand All @@ -135,7 +135,7 @@ describe('web-ext sign', () => {
assert.notEqual(exitCode, 0);
assert.match(
stderr,
/web-ext-config.js specified an unknown option: "apiKey"/,
/web-ext-config.mjs specified an unknown option: "apiKey"/,
);
});
}));
Expand Down
Loading