DEV Community

loading...
Cover image for Yarn audit fix: workaround

Yarn audit fix: workaround

Anton Golub
System Architect at QIWI
Updated on ・3 min read

tl;dr

npx yarn-audit-fix
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

...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"
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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)
  }
}
Enter fullscreen mode Exit fullscreen mode

And finally it looks to be working.

GitHub logo antongolub / yarn-audit-fix

Apply npm audit fix logic to yarn.lock



Any feedback is welcome.

Discussion (2)

Collapse
patrykmilewski profile image
Patryk Milewski

Very nice tool, my suggestion would be to create a custom script in package.json:

"audit:fix": "npx yarn-audit-fix",
Enter fullscreen mode Exit fullscreen mode

And of course yarn-audit-fix as devDependency.

So it's even easier to run audit fix:

yarn audit:fix
Enter fullscreen mode Exit fullscreen mode
Collapse
olivierjm profile image
Olivier JM Maniraho

This works perfectly, thank you.