Skip to content

Commit

Permalink
feat!: add support for depchecking typescript projects (#1042)
Browse files Browse the repository at this point in the history
@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>
  • Loading branch information
mpetrunic and achingbrain authored Jan 12, 2023
1 parent cf77bbb commit b9f5d86
Show file tree
Hide file tree
Showing 12 changed files with 95 additions and 122 deletions.
4 changes: 1 addition & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -249,9 +249,7 @@
"chai-subset": "^1.6.0",
"conventional-changelog-conventionalcommits": "^5.0.0",
"cors": "^2.8.5",
"dependency-check": "^5.0.0-2",
"detective-cjs": "^4.0.0",
"detective-es6": "^3.0.0",
"depcheck": "^1.4.3",
"diff": "^5.1.0",
"electron-mocha-main": "^11.0.3",
"env-paths": "^3.0.0",
Expand Down
15 changes: 8 additions & 7 deletions src/cmds/dependency-check.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,20 @@ export default {

return yargs
.epilog(EPILOG)
.example('aegir dependency-check -- --unused', 'To check unused packages in your repo.')
.example('aegir dependency-check -- --unused --ignore typescript', 'To check unused packages in your repo, ignoring typescript.')
.positional('input', {
describe: 'Files to check',
type: 'string',
array: true
})
.example('aegir dependency-check --unused', 'To check unused packages in your repo.')
.example('aegir dependency-check --unused --ignore typescript', 'To check unused packages in your repo, ignoring typescript.')
.option('i', {
alias: 'ignore',
describe: 'Ignore these dependencies when considering which are used and which are not',
array: true,
default: userConfig.dependencyCheck.ignore
})
.option('u', {
alias: 'unused',
describe: 'Checks for unused dependencies',
default: false,
type: 'boolean'
})
},
/**
* @param {any} argv
Expand Down
29 changes: 1 addition & 28 deletions src/config/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,34 +103,7 @@ const defaults = {
},
// dependency check cmd options
dependencyCheck: {
input: [
'package.json',
'.aegir.js',
'.aegir.cjs',
'src/**/*.js',
'src/**/*.cjs',
'test/**/*.js',
'test/**/*.cjs',
'dist/**/*.js',
'benchmarks/**/*.js',
'benchmarks/**/*.cjs',
'utils/**/*.js',
'utils/**/*.cjs',
'!./test/fixtures/**/*.js',
'!./test/fixtures/**/*.cjs',
'!./dist/test/fixtures/**/*.js',
'!./dist/test/fixtures/**/*.cjs',
'!**/*.min.js'
],
productionOnly: false,
productionInput: [
'package.json',
'src/**/*.js',
'src/**/*.cjs',
'dist/src/**/*.js',
'utils/**/*.js',
'utils/**/*.cjs'
],
unused: false,
ignore: [
'@types/*',
'aegir'
Expand Down
83 changes: 17 additions & 66 deletions src/dependency-check.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import path from 'path'
import { execa } from 'execa'
import merge from 'merge-options'
import { pkg } from './utils.js'
import { fileURLToPath } from 'url'
import Listr from 'listr'

const __dirname = path.dirname(fileURLToPath(import.meta.url))
import depcheck from 'depcheck'
import { cwd } from 'process'

/**
* @typedef {import("listr").ListrTaskWrapper} Task
Expand All @@ -22,67 +17,23 @@ const tasks = new Listr(
* @param {Task} task
*/
task: async (ctx, task) => {
const forwardOptions = ctx['--'] ? ctx['--'] : []
const input = ctx.input.length > 0 ? ctx.input : ctx.fileConfig.dependencyCheck.input
const ignore = ctx.ignore
.concat(ctx.fileConfig.dependencyCheck.ignore)
.reduce((acc, i) => acc.concat('-i', i), /** @type {string[]} */ ([]))

const args = [...input, '--missing', ...ignore]

if (pkg.type === 'module') {
// use detective-es6 for js, regular detective for cjs
args.push(
'--extensions', 'cjs:detective-cjs',
'--extensions', 'js:detective-es6'
)
}

await execa(
'dependency-check',
[...args, ...forwardOptions],
merge(
{
localDir: path.join(__dirname, '..'),
preferLocal: true
}
)
)
}
},
{
title: 'dependency-check (production only)',
/**
* @param {GlobalOptions & DependencyCheckOptions} ctx
* @param {Task} task
*/
task: async (ctx, task) => {
const forwardOptions = ctx['--'] ? ctx['--'] : []
const input = ctx.input.length > 0 ? ctx.input : ctx.fileConfig.dependencyCheck.productionInput
const ignore = ctx.ignore
.concat(ctx.fileConfig.dependencyCheck.ignore)
.reduce((acc, i) => acc.concat('-i', i), /** @type {string[]} */ ([]))

const args = [...input, '--missing', '--no-dev', ...ignore]

if (pkg.type === 'module') {
// use detective-es6 for js, regular detective for cjs
args.push(
'--extensions', 'cjs:detective-cjs',
'--extensions', 'js:detective-es6'
const result = await depcheck(cwd(), {
parsers: {
'**/*.js': depcheck.parser.es6,
'**/*.ts': depcheck.parser.typescript,
'**/*.cjs': depcheck.parser.es6,
'**/*.mjs': depcheck.parser.es6
},
ignoreMatches: ['eslint*', '@types/*', '@semantic-release/*'].concat(ctx.fileConfig.dependencyCheck.ignore).concat(ctx.ignore)
})
if (Object.keys(result.missing).length > 0 || (ctx.unused && (result.dependencies.length > 0 || result.devDependencies.length > 0))) {
throw new Error(
'Some dependencies are missing or unused.\n' +
'Missing: \n' + Object.entries(result.missing).map(([dep, path]) => dep + ': ' + path).join('\n') +
'\nUnused production dependencies: \n' + result.dependencies.join('\n') + '\n' +
'Unused dev dependencies: \n' + result.devDependencies.join('\n')
)
}

await execa(
'dependency-check',
[...args, ...forwardOptions],
merge(
{
localDir: path.join(__dirname, '..'),
preferLocal: true
}
)
)
}
}
]
Expand Down
13 changes: 3 additions & 10 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -316,21 +316,14 @@ interface ReleaseRcOptions {

interface DependencyCheckOptions {
/**
* Files to check
* throws error on unused dependencies, default is false
*/
input: string[]
/**
* Check production dependencies and paths only
*/
productionOnly: boolean
unused: boolean
/**
* Ignore these dependencies when considering which are used and which are not
*/
ignore: string[]
/**
* Files to check when in production only mode
*/
productionInput: string[]

}

interface ExecOptions {
Expand Down
44 changes: 36 additions & 8 deletions test/dependency-check.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,17 @@ describe('dependency check', () => {
.and.include('pico')
})

it('should fail for missing deps in an ts project', async () => {
await expect(
execa(bin, ['dependency-check'], {
cwd: path.join(__dirname, 'fixtures/dependency-check/ts-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(
execa(bin, ['dependency-check'], {
Expand All @@ -46,25 +57,39 @@ describe('dependency check', () => {
).to.eventually.be.fulfilled()
})

it('should forward options', async () => {
it('should pass when there are no missing deps in an ts project', async () => {
await expect(
execa(bin, ['dependency-check'], {
cwd: path.join(__dirname, 'fixtures/dependency-check/ts-pass')
})
).to.eventually.be.fulfilled()
})

it('should check unused', async () => {
await expect(
execa(bin, ['dependency-check', '--', '--unused'], {
execa(bin, ['dependency-check', '--unused'], {
cwd: path.join(__dirname, 'fixtures/dependency-check/pass')
})
).to.eventually.be.rejectedWith(
'Modules in package.json not used in code: pico'
).to.eventually.be.rejected.with.property('message').that.include(
'Unused production dependencies: \npico'
)
})

it('should fail for missing production deps', async () => {
/**
* depcheck removed this option as it caused too many false positives
*/
it.skip('should fail for missing production deps', async () => {
await expect(
execa(bin, ['dependency-check', '-p'], {
cwd: path.join(__dirname, 'fixtures/dependency-check/fail-prod')
})
).to.eventually.be.rejectedWith('execa')
})

it('should pass for passed files', async () => {
/**
* not supporting depchecking individual files
*/
it.skip('should pass for passed files', async () => {
const file = 'derp/foo.js'

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

it('should pass for passed production files', async () => {
/**
* not supporting depchecking individual files
*/
it.skip('should pass for passed production files', async () => {
const file = 'derp/foo.js'

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

it('should pass for modules used in .aegir.js', async () => {
await expect(
execa(bin, ['dependency-check', '--', '--unused'], {
execa(bin, ['dependency-check', '--unused'], {
cwd: path.join(
__dirname,
'fixtures/dependency-check/with-aegir-config'
Expand Down
6 changes: 6 additions & 0 deletions test/fixtures/dependency-check/ts-fail/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "ts-dep-check-fail",
"version": "1.0.0",
"main": "index.js",
"type": "module"
}
4 changes: 4 additions & 0 deletions test/fixtures/dependency-check/ts-fail/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// @ts-nocheck
/* eslint-disable @typescript-eslint/no-unused-vars */
import { execa } from 'execa'
import './other.js'
3 changes: 3 additions & 0 deletions test/fixtures/dependency-check/ts-fail/src/other.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// @ts-nocheck
/* eslint-disable @typescript-eslint/no-unused-vars */
import * as pico from 'pico'
10 changes: 10 additions & 0 deletions test/fixtures/dependency-check/ts-pass/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "esm-dep-check-pass",
"version": "1.0.0",
"main": "index.js",
"type": "module",
"dependencies": {
"execa": "1.0.0",
"pico": "1.0.0"
}
}
3 changes: 3 additions & 0 deletions test/fixtures/dependency-check/ts-pass/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// @ts-nocheck
/* eslint-disable @typescript-eslint/no-unused-vars */
import { execa } from 'execa'
3 changes: 3 additions & 0 deletions test/fixtures/dependency-check/ts-pass/src/other.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// @ts-nocheck
/* eslint-disable @typescript-eslint/no-unused-vars */
import { execa } from 'execa'

0 comments on commit b9f5d86

Please sign in to comment.