Skip to content

Commit

Permalink
feat: add lockfile patching flow
Browse files Browse the repository at this point in the history
  • Loading branch information
antongolub committed Jun 6, 2021
1 parent 0bd3f08 commit b01dd0d
Show file tree
Hide file tree
Showing 9 changed files with 247 additions and 34 deletions.
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,16 @@
"dependencies": {
"@types/find-cache-dir": "^3.2.0",
"@types/fs-extra": "^9.0.11",
"@types/lodash": "^4.14.170",
"@types/semver": "^7.3.6",
"@types/yarnpkg__lockfile": "^1.1.4",
"@yarnpkg/lockfile": "^1.1.0",
"chalk": "^4.1.1",
"commander": "^7.2.0",
"find-cache-dir": "^3.3.1",
"fs-extra": "^10.0.0",
"globby": "^11.0.3",
"lodash": "^4.17.21",
"npm": "7.16.0",
"pkg-dir": "^5.0.0",
"semver": "^7.3.5",
Expand Down
15 changes: 11 additions & 4 deletions src/main/ts/flow.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import {TFlow} from "./ifaces";
import {TFlow} from './ifaces'
import {
clear,
createSymlinks,
createTempAssets, exit,
createTempAssets,
exit,
npmAuditFix,
patchLockfile,
printRuntimeDigest,
yarnImport, yarnInstall,
yarnImport,
yarnInstall,
yarnLockToPkgLock
} from "./stages";

Expand All @@ -32,7 +35,11 @@ export const convert: TFlow = {
// Inject `yarn audit --json` data to lockfile.
export const patch: TFlow = {
main: [
['Runtime digest', printRuntimeDigest]
['Runtime digest', printRuntimeDigest],
['Preparing temp assets...', clear, createTempAssets, createSymlinks],
['Generating package-lock.json from yarn.lock...', yarnLockToPkgLock],
['Patching yarn.lock with audit data...', patchLockfile, yarnInstall, clear],
['Done'],
],
fallback: [
['Failure!', clear, exit]
Expand Down
29 changes: 29 additions & 0 deletions src/main/ts/ifaces.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
export type TFlags = Record<string, any>

export type TContext = {
ctx: TContext
cwd: string
temp: string
flags: TFlags
manifest: Record<string, any>
err?: any
report?: TAuditReport
}

export type TCallback = (cxt: TContext) => void | Promise<void>
Expand All @@ -18,3 +20,30 @@ export type TFlow = {
main: TStage[]
fallback: TStage[]
}

export type TAuditEntry = {
data: {
advisory: {
module_name: string
vulnerable_versions: string
patched_versions: string
}
}
}

export type TAuditReport = {
[versionInfo: string]: {
module_name: string
vulnerable_versions: string
patched_versions: string
}
}

export type TLockfileObject = {
[versionInfo: string]: {
version: string
resolved: string
integrity: string
dependencies: string[]
};
};
95 changes: 95 additions & 0 deletions src/main/ts/lockfile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import fs from 'fs'
import lf from '@yarnpkg/lockfile'
import { keyBy } from 'lodash'
import sv from 'semver'

import {TAuditEntry, TAuditReport, TContext, TLockfileObject} from './ifaces'
import {attempt, getNpm, getYarn, invoke} from './util'

export const read = (name: string): TLockfileObject => {
const data = lf.parse(fs.readFileSync(name, 'utf-8'))

if (data.type !== 'success') {
throw new Error('Merge conflict in yarn lockfile, aborting')
}

return data.object
}

export const write = (name: string, lockfile: TLockfileObject): void => {
fs.writeFileSync(name, lf.stringify(lockfile))
}

export const patch = (lockfile: TLockfileObject, report: TAuditReport, { flags }: TContext): TLockfileObject => {
if (Object.keys(report).length < 1) {
!flags.silent && console.log('Audit check found no issues')
return lockfile
}

const upgraded: string [] = []

for (let depSpec of Object.keys(lockfile)) {
let [pkgName, desiredRange] = depSpec.split('@')
let pkgAudit = report[pkgName]
if (!pkgAudit) continue
let pkgSpec = lockfile[depSpec]
if (sv.satisfies(pkgSpec.version, pkgAudit.vulnerable_versions)) {
let fix = sv.minVersion(pkgAudit.patched_versions)?.format()
if (fix == null) {
console.error(
'Can\'t find satisfactory version for',
pkgAudit.module_name,
pkgAudit.patched_versions
);
continue
}
if (!sv.satisfies(fix, desiredRange)) {
console.error(
'Cant find patched version that satisfies',
depSpec,
'in',
pkgAudit.patched_versions
);
continue
}
upgraded.push(`${pkgName}@${fix}`)
pkgSpec.version = fix
pkgSpec.dependencies = []
pkgSpec.integrity = ''
pkgSpec.resolved = ''
}
}

!flags.silent && console.log('Upgraded deps:', upgraded.join(', '));

return lockfile
}

export const audit = ({ flags, temp }: TContext): TAuditReport => {
const cmd = flags.reporter === 'npm'
? getNpm(flags['npm-path'])
: getYarn()
const report = invoke(cmd, ['audit', '--json'], temp, !!flags.silent, true)

return keyBy(
report
.toString()
.split('\n')
.map((item) => attempt(() => JSON.parse(item)) as TAuditEntry)
.map((item) => item?.data?.advisory)
.filter((item) => item != null)
.map((item) => ({
module_name: item.module_name,
vulnerable_versions: item.vulnerable_versions,
patched_versions: item.patched_versions,
})),
(item) => item.module_name
)
}

export default {
audit,
patch,
read,
write,
}
14 changes: 10 additions & 4 deletions src/main/ts/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import chalk from 'chalk'
import { join } from 'path'

import {TCallback, TContext, TFlags, TFlow, TStage} from './ifaces'
import { convert } from './flow'
import { convert, patch } from './flow'
import { getTemp, normalizeFlags, readJson } from './util'

/**
Expand All @@ -12,13 +12,15 @@ export const getContext = (flags: Record<string, any> = {}): TContext => {
const cwd = process.cwd()
const manifest = readJson(join(cwd, 'package.json'))
const temp = getTemp(cwd, flags.temp)

return {
const ctx = {
cwd,
temp,
flags,
manifest,
}
} as TContext
ctx.ctx = ctx

return ctx
}

/**
Expand All @@ -30,6 +32,10 @@ export const getFlow = ({ flow = 'convert' }: Record<string, any> = {}): TFlow =
return convert
}

if (flow === 'patch') {
return patch
}

throw new Error(`Unsupported flow: ${flow}`)
}

Expand Down
12 changes: 11 additions & 1 deletion src/main/ts/stages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { sync as pkgDir } from 'pkg-dir'
import semver from 'semver'
import synp from 'synp'

import { TCallback } from './ifaces'
import lf from './lockfile'
import {TCallback} from './ifaces'
import {
formatFlags,
getNpm,
Expand Down Expand Up @@ -192,3 +193,12 @@ export const exit: TCallback = ({ flags, err }) => {
!flags.silent && console.error(err)
process.exitCode = err?.status | 0 || 1
}

export const patchLockfile: TCallback = ({ flags , temp, ctx}) => {
const lockfilePath = join(temp, 'yarn.lock')
const lockfile = lf.read(lockfilePath)
const report = lf.audit(ctx)

lf.patch(lockfile, report, ctx)
lf.write(lockfilePath, lockfile)
}
8 changes: 8 additions & 0 deletions src/main/ts/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,11 @@ export const getTemp = (cwd: string, temp?: string): string => {

return ensureDir(tempDir)
}

export const attempt = <T>(f: () => T): T | null => {
try {
return f();
} catch {
return null;
}
}
Loading

0 comments on commit b01dd0d

Please sign in to comment.