tl;dr
npx yarn-audit-fix
Issue
Auditing and updating dependencies is an important part of the code lifecycle. There are different approaches to implement this task: services (renovate, snyk, dependabot and so on) and instruments (npm, yarn).
If you develop proprietary things in an isolated environment, external services are most likely unavailable. If you use monorepos, yarn is your only choice.
But there is a problem.
yarn audit fix
...does not quite what is expected. It just shows vulnerabilities, but doesn't fix them. And here is a long discussion of why this happens: https://github.com/yarnpkg/yarn/issues/7075
Most workarounds are based on different variations of semi-manual dependency patching.
{
"resolutions": {
"package-merge-lodash-4/*/lodash": "4.17.12"
},
}
...
"scripts": {
"preinstall": "npx npm-force-resolutions"
}
Fortunately, Gianfranco P. proposed another solution:
1. npm i --package-lock-only
2. npm audit fix
3. rm yarn.lock
4. yarn import
5. rm package-lock.json
It's simple, clear and it works. But this script ignores the case of monorepos, because npm
doesn't support workspaces yet.
There must be another way to generate lock file. It exists! Synp converts yarn.lock
to package-lock.json
and vice versa. So
synp --source-file yarn.lock
gives package-lock.json
, that contains all the dependencies (nope, but it will be fixed asap. Fixed) of the monorepository. Next npm audit fix
works normally.
However, yarn import
raises an error.
error An unexpected error occurred: "https://registry.yarnpkg.com/workspace-aggregator-bbb13589-b149-4858-b202-4f4e90c33e3f: Not found".
info If you think this is a bug, please open a bug report with the information provided in "/<root>/projects/foobar/yarn-error.log".
info Visit https://yarnpkg.com/en/docs/cli/import for documentation about this command.
One more side-effect of workspace
field in package.json
. Not a problem. It disappears if you temporarily remove/rename the field before synp
invocation.
Performing these steps manually each time is slightly tedious. Let's automate.
import fs from 'fs-extra'
import synp from 'synp'
import {join} from 'path'
import findCacheDir from 'find-cache-dir'
import chalk from 'chalk'
import {invoke} from './invoke'
type TContext = { cwd: string, temp: string }
type TCallback = (cxt: TContext) => void
type TStage = [string, ...TCallback[]]
/**
* Prepare temp assets.
* @param {TContext} cxt
* @return {void}
*/
const createTempAssets: TCallback = ({temp}) => {
fs.copyFileSync('yarn.lock', join(temp, 'yarn.lock'))
fs.copyFileSync('package.json', join(temp, 'package.json'))
fs.createSymlinkSync('node_modules', join(temp, 'node_modules'), 'dir')
}
/**
* Remove workspace field from package.json due to npm issue.
* https://github.com/antongolub/yarn-audit-fix/issues/2
* @param {TContext} cxt
* @return {void}
*/
const fixWorkspaces: TCallback = ({temp}) => {
const pkgJsonData = JSON.parse(fs.readFileSync(join(temp, 'package.json'), 'utf-8').trim())
delete pkgJsonData.workspaces
fs.writeFileSync(join(temp, 'package.json'), JSON.stringify(pkgJsonData, null, 2))
}
/**
* Convert yarn.lock to package.json for further audit.
* @param {TContext} cxt
* @return {void}
*/
const yarnLockToPkgLock: TCallback = ({temp}) => {
const pgkLockJsonData = synp.yarnToNpm(temp)
fs.writeFileSync(join(temp, 'package-lock.json'), pgkLockJsonData)
fs.removeSync(join(temp, 'yarn.lock'))
}
/**
* Apply npm audit fix.
* @param {TContext} cxt
* @return {void}
*/
const npmAuditFix: TCallback = ({temp}) =>
invoke('npm', ['audit', 'fix', '--package-lock-only'], temp)
/**
* Generate yarn.lock by package-lock.json data.
* @param {TContext} cxt
* @return {void}
*/
const yarnImport: TCallback = ({temp}) => {
invoke('yarn', ['import'], temp)
fs.copyFileSync(join(temp, 'yarn.lock'), 'yarn.lock')
}
/**
* Apply yarn install to fetch packages after yarn.lock update.
* @param {TContext} cxt
* @return {void}
*/
const yarnInstall: TCallback = ({cwd}) =>
invoke('yarn', [], cwd)
/**
* Clean up temporaries.
* @param {TContext} cxt
* @return {void}
*/
const clear: TCallback = ({temp}) =>
fs.emptyDirSync(temp)
export const stages: TStage[] = [
[
'Preparing temp assets...',
clear,
createTempAssets,
fixWorkspaces,
],
[
'Generating package-lock.json from yarn.lock...',
yarnLockToPkgLock,
],
[
'Applying npm audit fix...',
npmAuditFix,
],
[
'Updating yarn.lock from package-lock.json...',
yarnImport,
yarnInstall,
clear,
],
[
'Done',
],
]
/**
* Public static void main.
*/
export const run = async() => {
const ctx = {
cwd: process.cwd(),
temp: findCacheDir({name: 'yarn-audit-fix', create: true}) + '',
}
for (const [description, ...steps] of stages) {
console.log(chalk.bold(description))
for (const step of steps) step(ctx)
}
}
And finally it looks to be working.
antongolub / yarn-audit-fix
The missing `yarn audit fix`
Any feedback is welcome.
Top comments (2)
Very nice tool, my suggestion would be to create a custom script in package.json:
And of course
yarn-audit-fix
as devDependency.So it's even easier to run audit fix:
This works perfectly, thank you.