Skip to content

Commit b9f5d86

Browse files
mpetrunicachingbrain
andauthoredJan 12, 2023
feat!: add support for depchecking typescript projects (#1042)
@achingbrain not sure if this is too disruptive. BREAKING CHANGE: - no more forwarding flags - no more positional arguments - cannot depcheck single file - won't throw an error for missing dependency if it's in devDependencies section of package.json Co-authored-by: Alex Potsides <alex@achingbrain.net>
1 parent cf77bbb commit b9f5d86

File tree

12 files changed

+95
-122
lines changed

12 files changed

+95
-122
lines changed
 

‎package.json

+1-3
Original file line numberDiff line numberDiff line change
@@ -249,9 +249,7 @@
249249
"chai-subset": "^1.6.0",
250250
"conventional-changelog-conventionalcommits": "^5.0.0",
251251
"cors": "^2.8.5",
252-
"dependency-check": "^5.0.0-2",
253-
"detective-cjs": "^4.0.0",
254-
"detective-es6": "^3.0.0",
252+
"depcheck": "^1.4.3",
255253
"diff": "^5.1.0",
256254
"electron-mocha-main": "^11.0.3",
257255
"env-paths": "^3.0.0",

‎src/cmds/dependency-check.js

+8-7
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,20 @@ export default {
2525

2626
return yargs
2727
.epilog(EPILOG)
28-
.example('aegir dependency-check -- --unused', 'To check unused packages in your repo.')
29-
.example('aegir dependency-check -- --unused --ignore typescript', 'To check unused packages in your repo, ignoring typescript.')
30-
.positional('input', {
31-
describe: 'Files to check',
32-
type: 'string',
33-
array: true
34-
})
28+
.example('aegir dependency-check --unused', 'To check unused packages in your repo.')
29+
.example('aegir dependency-check --unused --ignore typescript', 'To check unused packages in your repo, ignoring typescript.')
3530
.option('i', {
3631
alias: 'ignore',
3732
describe: 'Ignore these dependencies when considering which are used and which are not',
3833
array: true,
3934
default: userConfig.dependencyCheck.ignore
4035
})
36+
.option('u', {
37+
alias: 'unused',
38+
describe: 'Checks for unused dependencies',
39+
default: false,
40+
type: 'boolean'
41+
})
4142
},
4243
/**
4344
* @param {any} argv

‎src/config/user.js

+1-28
Original file line numberDiff line numberDiff line change
@@ -103,34 +103,7 @@ const defaults = {
103103
},
104104
// dependency check cmd options
105105
dependencyCheck: {
106-
input: [
107-
'package.json',
108-
'.aegir.js',
109-
'.aegir.cjs',
110-
'src/**/*.js',
111-
'src/**/*.cjs',
112-
'test/**/*.js',
113-
'test/**/*.cjs',
114-
'dist/**/*.js',
115-
'benchmarks/**/*.js',
116-
'benchmarks/**/*.cjs',
117-
'utils/**/*.js',
118-
'utils/**/*.cjs',
119-
'!./test/fixtures/**/*.js',
120-
'!./test/fixtures/**/*.cjs',
121-
'!./dist/test/fixtures/**/*.js',
122-
'!./dist/test/fixtures/**/*.cjs',
123-
'!**/*.min.js'
124-
],
125-
productionOnly: false,
126-
productionInput: [
127-
'package.json',
128-
'src/**/*.js',
129-
'src/**/*.cjs',
130-
'dist/src/**/*.js',
131-
'utils/**/*.js',
132-
'utils/**/*.cjs'
133-
],
106+
unused: false,
134107
ignore: [
135108
'@types/*',
136109
'aegir'

‎src/dependency-check.js

+17-66
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
1-
import path from 'path'
2-
import { execa } from 'execa'
3-
import merge from 'merge-options'
4-
import { pkg } from './utils.js'
5-
import { fileURLToPath } from 'url'
61
import Listr from 'listr'
7-
8-
const __dirname = path.dirname(fileURLToPath(import.meta.url))
2+
import depcheck from 'depcheck'
3+
import { cwd } from 'process'
94

105
/**
116
* @typedef {import("listr").ListrTaskWrapper} Task
@@ -22,67 +17,23 @@ const tasks = new Listr(
2217
* @param {Task} task
2318
*/
2419
task: async (ctx, task) => {
25-
const forwardOptions = ctx['--'] ? ctx['--'] : []
26-
const input = ctx.input.length > 0 ? ctx.input : ctx.fileConfig.dependencyCheck.input
27-
const ignore = ctx.ignore
28-
.concat(ctx.fileConfig.dependencyCheck.ignore)
29-
.reduce((acc, i) => acc.concat('-i', i), /** @type {string[]} */ ([]))
30-
31-
const args = [...input, '--missing', ...ignore]
32-
33-
if (pkg.type === 'module') {
34-
// use detective-es6 for js, regular detective for cjs
35-
args.push(
36-
'--extensions', 'cjs:detective-cjs',
37-
'--extensions', 'js:detective-es6'
38-
)
39-
}
40-
41-
await execa(
42-
'dependency-check',
43-
[...args, ...forwardOptions],
44-
merge(
45-
{
46-
localDir: path.join(__dirname, '..'),
47-
preferLocal: true
48-
}
49-
)
50-
)
51-
}
52-
},
53-
{
54-
title: 'dependency-check (production only)',
55-
/**
56-
* @param {GlobalOptions & DependencyCheckOptions} ctx
57-
* @param {Task} task
58-
*/
59-
task: async (ctx, task) => {
60-
const forwardOptions = ctx['--'] ? ctx['--'] : []
61-
const input = ctx.input.length > 0 ? ctx.input : ctx.fileConfig.dependencyCheck.productionInput
62-
const ignore = ctx.ignore
63-
.concat(ctx.fileConfig.dependencyCheck.ignore)
64-
.reduce((acc, i) => acc.concat('-i', i), /** @type {string[]} */ ([]))
65-
66-
const args = [...input, '--missing', '--no-dev', ...ignore]
67-
68-
if (pkg.type === 'module') {
69-
// use detective-es6 for js, regular detective for cjs
70-
args.push(
71-
'--extensions', 'cjs:detective-cjs',
72-
'--extensions', 'js:detective-es6'
20+
const result = await depcheck(cwd(), {
21+
parsers: {
22+
'**/*.js': depcheck.parser.es6,
23+
'**/*.ts': depcheck.parser.typescript,
24+
'**/*.cjs': depcheck.parser.es6,
25+
'**/*.mjs': depcheck.parser.es6
26+
},
27+
ignoreMatches: ['eslint*', '@types/*', '@semantic-release/*'].concat(ctx.fileConfig.dependencyCheck.ignore).concat(ctx.ignore)
28+
})
29+
if (Object.keys(result.missing).length > 0 || (ctx.unused && (result.dependencies.length > 0 || result.devDependencies.length > 0))) {
30+
throw new Error(
31+
'Some dependencies are missing or unused.\n' +
32+
'Missing: \n' + Object.entries(result.missing).map(([dep, path]) => dep + ': ' + path).join('\n') +
33+
'\nUnused production dependencies: \n' + result.dependencies.join('\n') + '\n' +
34+
'Unused dev dependencies: \n' + result.devDependencies.join('\n')
7335
)
7436
}
75-
76-
await execa(
77-
'dependency-check',
78-
[...args, ...forwardOptions],
79-
merge(
80-
{
81-
localDir: path.join(__dirname, '..'),
82-
preferLocal: true
83-
}
84-
)
85-
)
8637
}
8738
}
8839
]

‎src/types.ts

+3-10
Original file line numberDiff line numberDiff line change
@@ -316,21 +316,14 @@ interface ReleaseRcOptions {
316316

317317
interface DependencyCheckOptions {
318318
/**
319-
* Files to check
319+
* throws error on unused dependencies, default is false
320320
*/
321-
input: string[]
322-
/**
323-
* Check production dependencies and paths only
324-
*/
325-
productionOnly: boolean
321+
unused: boolean
326322
/**
327323
* Ignore these dependencies when considering which are used and which are not
328324
*/
329325
ignore: string[]
330-
/**
331-
* Files to check when in production only mode
332-
*/
333-
productionInput: string[]
326+
334327
}
335328

336329
interface ExecOptions {

‎test/dependency-check.js

+36-8
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,17 @@ describe('dependency check', () => {
3030
.and.include('pico')
3131
})
3232

33+
it('should fail for missing deps in an ts project', async () => {
34+
await expect(
35+
execa(bin, ['dependency-check'], {
36+
cwd: path.join(__dirname, 'fixtures/dependency-check/ts-fail')
37+
})
38+
).to.eventually.be.rejected()
39+
.with.property('message')
40+
.that.include('execa')
41+
.and.include('pico')
42+
})
43+
3344
it('should pass when there are no missing deps', async () => {
3445
await expect(
3546
execa(bin, ['dependency-check'], {
@@ -46,25 +57,39 @@ describe('dependency check', () => {
4657
).to.eventually.be.fulfilled()
4758
})
4859

49-
it('should forward options', async () => {
60+
it('should pass when there are no missing deps in an ts project', async () => {
61+
await expect(
62+
execa(bin, ['dependency-check'], {
63+
cwd: path.join(__dirname, 'fixtures/dependency-check/ts-pass')
64+
})
65+
).to.eventually.be.fulfilled()
66+
})
67+
68+
it('should check unused', async () => {
5069
await expect(
51-
execa(bin, ['dependency-check', '--', '--unused'], {
70+
execa(bin, ['dependency-check', '--unused'], {
5271
cwd: path.join(__dirname, 'fixtures/dependency-check/pass')
5372
})
54-
).to.eventually.be.rejectedWith(
55-
'Modules in package.json not used in code: pico'
73+
).to.eventually.be.rejected.with.property('message').that.include(
74+
'Unused production dependencies: \npico'
5675
)
5776
})
5877

59-
it('should fail for missing production deps', async () => {
78+
/**
79+
* depcheck removed this option as it caused too many false positives
80+
*/
81+
it.skip('should fail for missing production deps', async () => {
6082
await expect(
6183
execa(bin, ['dependency-check', '-p'], {
6284
cwd: path.join(__dirname, 'fixtures/dependency-check/fail-prod')
6385
})
6486
).to.eventually.be.rejectedWith('execa')
6587
})
6688

67-
it('should pass for passed files', async () => {
89+
/**
90+
* not supporting depchecking individual files
91+
*/
92+
it.skip('should pass for passed files', async () => {
6893
const file = 'derp/foo.js'
6994

7095
await expect(
@@ -78,7 +103,10 @@ describe('dependency check', () => {
78103
).to.eventually.be.fulfilled()
79104
})
80105

81-
it('should pass for passed production files', async () => {
106+
/**
107+
* not supporting depchecking individual files
108+
*/
109+
it.skip('should pass for passed production files', async () => {
82110
const file = 'derp/foo.js'
83111

84112
await expect(
@@ -103,7 +131,7 @@ describe('dependency check', () => {
103131

104132
it('should pass for modules used in .aegir.js', async () => {
105133
await expect(
106-
execa(bin, ['dependency-check', '--', '--unused'], {
134+
execa(bin, ['dependency-check', '--unused'], {
107135
cwd: path.join(
108136
__dirname,
109137
'fixtures/dependency-check/with-aegir-config'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"name": "ts-dep-check-fail",
3+
"version": "1.0.0",
4+
"main": "index.js",
5+
"type": "module"
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// @ts-nocheck
2+
/* eslint-disable @typescript-eslint/no-unused-vars */
3+
import { execa } from 'execa'
4+
import './other.js'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// @ts-nocheck
2+
/* eslint-disable @typescript-eslint/no-unused-vars */
3+
import * as pico from 'pico'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"name": "esm-dep-check-pass",
3+
"version": "1.0.0",
4+
"main": "index.js",
5+
"type": "module",
6+
"dependencies": {
7+
"execa": "1.0.0",
8+
"pico": "1.0.0"
9+
}
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// @ts-nocheck
2+
/* eslint-disable @typescript-eslint/no-unused-vars */
3+
import { execa } from 'execa'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// @ts-nocheck
2+
/* eslint-disable @typescript-eslint/no-unused-vars */
3+
import { execa } from 'execa'

0 commit comments

Comments
 (0)
Please sign in to comment.