From 48712b1f1f35d5aa6a39bf698a7cb30b9a523c5e Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Thu, 29 Jul 2021 16:48:20 +0100 Subject: [PATCH] feat: support dependency check in esm modules (#869) Auto-detect `type: "module"` in a project's `package.json` and switch `detective` implementation to `detective-es6` which understands `import`. --- package.json | 3 +- src/config/user.js | 11 +- src/dependency-check.js | 13 ++- test/dependency-check.js | 105 ++++++++---------- .../dependency-check/esm-fail/package.json | 11 ++ .../dependency-check/esm-fail/src/index.js | 3 + .../dependency-check/esm-fail/src/other.cjs | 3 + .../dependency-check/esm-pass/package.json | 15 +++ .../dependency-check/esm-pass/src/index.js | 3 + .../dependency-check/esm-pass/src/other.cjs | 3 + 10 files changed, 107 insertions(+), 63 deletions(-) create mode 100644 test/fixtures/dependency-check/esm-fail/package.json create mode 100644 test/fixtures/dependency-check/esm-fail/src/index.js create mode 100644 test/fixtures/dependency-check/esm-fail/src/other.cjs create mode 100644 test/fixtures/dependency-check/esm-pass/package.json create mode 100644 test/fixtures/dependency-check/esm-pass/src/index.js create mode 100644 test/fixtures/dependency-check/esm-pass/src/other.cjs diff --git a/package.json b/package.json index e2920bd5c..4c07120fb 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,6 @@ "release-major": "node cli.js release --type major" }, "dependencies": { - "@achingbrain/dependency-check": "^4.1.0", "@electron/get": "^1.12.3", "@polka/send-type": "^0.5.2", "@types/chai": "^4.2.16", @@ -80,6 +79,8 @@ "conventional-github-releaser": "^3.1.5", "copyfiles": "^2.4.1", "cors": "^2.8.5", + "dependency-check": "^5.0.0-1", + "detective-es6": "^2.2.0", "electron-mocha": "^10.0.0", "env-paths": "^2.2.1", "esbuild": "^0.12.15", diff --git a/src/config/user.js b/src/config/user.js index 45c4f3937..0128c68e7 100644 --- a/src/config/user.js +++ b/src/config/user.js @@ -96,16 +96,23 @@ const defaults = { input: [ 'package.json', '.aegir.js', + '.aegir.cjs', 'src/**/*.js', + 'src/**/*.cjs', 'test/**/*.js', + 'test/**/*.cjs', 'benchmarks/**/*.js', + 'benchmarks/**/*.cjs', 'utils/**/*.js', - '!./test/fixtures/**/*.js' + 'utils/**/*.cjs', + '!./test/fixtures/**/*.js', + '!./test/fixtures/**/*.cjs' ], productionOnly: false, productionInput: [ 'package.json', - 'src/**/*.js' + 'src/**/*.js', + 'src/**/*.cjs' ], ignore: [ '@types/*' diff --git a/src/dependency-check.js b/src/dependency-check.js index 37e7ffc3b..53a1bd813 100644 --- a/src/dependency-check.js +++ b/src/dependency-check.js @@ -3,6 +3,7 @@ const path = require('path') const execa = require('execa') const merge = require('merge-options') +const { pkg } = require('./utils') /** * @typedef {import("execa").Options} ExecaOptions @@ -34,9 +35,19 @@ const check = (argv, execaOptions) => { .concat(argv.fileConfig.dependencyCheck.ignore) .reduce((acc, i) => acc.concat('-i', i), /** @type {string[]} */ ([])) + const args = [...input, '--missing', ...noDev, ...ignore] + + if (pkg.type === 'module') { + // use detective-es6 for js, regular detective for cjs + args.push( + '--extensions', 'cjs:detective', + '--extensions', 'js:detective-es6' + ) + } + return execa( 'dependency-check', - [...input, '--missing', ...noDev, ...ignore, ...forwardOptions], + [...args, ...forwardOptions], merge( { localDir: path.join(__dirname, '..'), diff --git a/test/dependency-check.js b/test/dependency-check.js index 8fa1d6c94..0ebde3cc3 100644 --- a/test/dependency-check.js +++ b/test/dependency-check.js @@ -1,46 +1,50 @@ /* eslint-env mocha */ 'use strict' -const { check } = require('../src/dependency-check') const { expect } = require('../utils/chai') const path = require('path') -const merge = require('merge-options') -const { userConfig } = require('../src/config/user') - -// returns an object that looks like the yargs input -const yargsv = (overrides = {}) => { - const argv = { - _: ['dependency-check'], - input: userConfig.dependencyCheck.input, - productionOnly: false, - productionInput: userConfig.dependencyCheck.productionInput, - ignore: [], - fileConfig: userConfig - } - - return merge(argv, overrides) -} +const bin = require.resolve('../') +const execa = require('execa') describe('dependency check', () => { it('should fail for missing deps', async () => { await expect( - check(yargsv(), { + execa(bin, ['dependency-check'], { cwd: path.join(__dirname, 'fixtures/dependency-check/fail') }) ).to.eventually.be.rejectedWith('execa') }) + it('should fail for missing deps in an esm project', async () => { + await expect( + execa(bin, ['dependency-check'], { + cwd: path.join(__dirname, 'fixtures/dependency-check/esm-fail') + }) + ).to.eventually.be.rejected() + .with.property('message') + .that.include('execa') + .and.include('pico') + }) + it('should pass when there are no missing deps', async () => { await expect( - check(yargsv(), { + execa(bin, ['dependency-check'], { cwd: path.join(__dirname, 'fixtures/dependency-check/pass') }) ).to.eventually.be.fulfilled() }) + it('should pass when there are no missing deps in an esm project', async () => { + await expect( + execa(bin, ['dependency-check'], { + cwd: path.join(__dirname, 'fixtures/dependency-check/esm-pass') + }) + ).to.eventually.be.fulfilled() + }) + it('should forward options', async () => { await expect( - check(yargsv({ '--': ['--unused'] }), { + execa(bin, ['dependency-check', '--', '--unused'], { cwd: path.join(__dirname, 'fixtures/dependency-check/pass') }) ).to.eventually.be.rejectedWith( @@ -50,7 +54,7 @@ describe('dependency check', () => { it('should fail for missing production deps', async () => { await expect( - check(yargsv({ productionOnly: true }), { + execa(bin, ['dependency-check', '-p'], { cwd: path.join(__dirname, 'fixtures/dependency-check/fail-prod') }) ).to.eventually.be.rejectedWith('execa') @@ -60,16 +64,12 @@ describe('dependency check', () => { const file = 'derp/foo.js' await expect( - check( - yargsv({ - input: [file] - }), - { - cwd: path.join( - __dirname, - 'fixtures/dependency-check/pass-certain-files' - ) - } + execa(bin, ['dependency-check', file], { + cwd: path.join( + __dirname, + 'fixtures/dependency-check/pass-certain-files' + ) + } ) ).to.eventually.be.fulfilled() }) @@ -78,46 +78,33 @@ describe('dependency check', () => { const file = 'derp/foo.js' await expect( - check( - yargsv({ - productionOnly: true, - input: [file] - }), - { - cwd: path.join( - __dirname, - 'fixtures/dependency-check/pass-certain-files' - ) - } + execa(bin, ['dependency-check', file, '-p'], { + cwd: path.join( + __dirname, + 'fixtures/dependency-check/pass-certain-files' + ) + } ) ).to.eventually.be.fulfilled() }) it('should pass for ignored modules', async () => { await expect( - check( - yargsv({ - ignore: ['execa'] - }), - { - cwd: path.join(__dirname, 'fixtures/dependency-check/fail') - } + execa(bin, ['dependency-check', '-i', 'execa'], { + cwd: path.join(__dirname, 'fixtures/dependency-check/fail') + } ) ).to.eventually.be.fulfilled() }) it('should pass for modules used in .aegir.js', async () => { await expect( - check( - yargsv({ - '--': ['--unused'] - }), - { - cwd: path.join( - __dirname, - 'fixtures/dependency-check/with-aegir-config' - ) - } + execa(bin, ['dependency-check', '--', '--unused'], { + cwd: path.join( + __dirname, + 'fixtures/dependency-check/with-aegir-config' + ) + } ) ).to.eventually.be.fulfilled() }) diff --git a/test/fixtures/dependency-check/esm-fail/package.json b/test/fixtures/dependency-check/esm-fail/package.json new file mode 100644 index 000000000..77432d9bb --- /dev/null +++ b/test/fixtures/dependency-check/esm-fail/package.json @@ -0,0 +1,11 @@ +{ + "name": "esm-dep-check-fail", + "version": "1.0.0", + "main": "index.js", + "type": "module", + "eslintConfig": { + "parserOptions": { + "sourceType": "module" + } + } +} diff --git a/test/fixtures/dependency-check/esm-fail/src/index.js b/test/fixtures/dependency-check/esm-fail/src/index.js new file mode 100644 index 000000000..92c55ff01 --- /dev/null +++ b/test/fixtures/dependency-check/esm-fail/src/index.js @@ -0,0 +1,3 @@ +// @ts-nocheck +/* eslint-disable no-unused-vars */ +import execa from 'execa' diff --git a/test/fixtures/dependency-check/esm-fail/src/other.cjs b/test/fixtures/dependency-check/esm-fail/src/other.cjs new file mode 100644 index 000000000..10d4fe42c --- /dev/null +++ b/test/fixtures/dependency-check/esm-fail/src/other.cjs @@ -0,0 +1,3 @@ +// @ts-nocheck +/* eslint-disable no-unused-vars */ +const pico = require('pico') diff --git a/test/fixtures/dependency-check/esm-pass/package.json b/test/fixtures/dependency-check/esm-pass/package.json new file mode 100644 index 000000000..f5e0952ed --- /dev/null +++ b/test/fixtures/dependency-check/esm-pass/package.json @@ -0,0 +1,15 @@ +{ + "name": "esm-dep-check-pass", + "version": "1.0.0", + "main": "index.js", + "type": "module", + "eslintConfig": { + "parserOptions": { + "sourceType": "module" + } + }, + "dependencies": { + "execa": "1.0.0", + "pico": "1.0.0" + } +} diff --git a/test/fixtures/dependency-check/esm-pass/src/index.js b/test/fixtures/dependency-check/esm-pass/src/index.js new file mode 100644 index 000000000..92c55ff01 --- /dev/null +++ b/test/fixtures/dependency-check/esm-pass/src/index.js @@ -0,0 +1,3 @@ +// @ts-nocheck +/* eslint-disable no-unused-vars */ +import execa from 'execa' diff --git a/test/fixtures/dependency-check/esm-pass/src/other.cjs b/test/fixtures/dependency-check/esm-pass/src/other.cjs new file mode 100644 index 000000000..e38850deb --- /dev/null +++ b/test/fixtures/dependency-check/esm-pass/src/other.cjs @@ -0,0 +1,3 @@ +// @ts-nocheck +/* eslint-disable no-unused-vars */ +const execa = require('execa') \ No newline at end of file