Add rondo update script

This commit is contained in:
Jerko Steiner 2019-09-01 12:38:41 +07:00
parent a8af27bac3
commit fc613f426b
5 changed files with 171 additions and 3 deletions

View File

@ -14,4 +14,4 @@
"types": "lib/index.d.ts",
"devDependencies": {},
"module": "lib/index.js"
}
}

View File

@ -1,9 +1,9 @@
import {format} from 'util'
export function error(message: string, ...values: any[]) {
process.stderr.write(format(message + '\n', ...values))
process.stderr.write(format(message, ...values) + '\n')
}
export function info(message: string, ...values: any[]) {
process.stdout.write(format(message + '\n', ...values))
process.stdout.write(format(message, ...values) + '\n')
}

View File

@ -3,3 +3,4 @@ export * from './build'
export * from './exportDir'
export * from './intergen'
export * from './syncEsm'
export * from './update'

View File

@ -0,0 +1,80 @@
jest.mock('child_process')
jest.mock('fs')
jest.mock('../log')
import cp from 'child_process'
import * as fs from 'fs'
import {update, IOutdated} from './update'
describe('update', () => {
const stringify = (obj: object) => JSON.stringify(obj, null, ' ')
const readMock = fs.readFileSync as jest.Mock<typeof fs.readFileSync>
const writeMock = fs.writeFileSync as jest.Mock<typeof fs.writeFileSync>
const cpMock = cp.execFileSync as jest.Mock<typeof cp.execFileSync>
let outdated: Record<string, IOutdated> = {}
beforeEach(() => {
outdated = {}
cpMock.mockClear()
readMock.mockClear()
writeMock.mockClear()
cpMock.mockImplementation(() => {
const err = new Error('Exit code 1');
(err as any).stdout = stringify(outdated)
throw err
})
readMock.mockReturnValue(stringify({
name: 'test',
dependencies: {
a: '^1.2.3',
},
devDependencies: {
b: '^3.4.6',
},
}))
})
it('does not change when no changes', async () => {
cpMock.mockReturnValue('{}')
await update('update', '/my/dir')
expect(writeMock.mock.calls.length).toBe(0)
})
it('does not change when npm outdated output is empty', async () => {
cpMock.mockReturnValue('')
await update('update', '/my/dir')
expect(writeMock.mock.calls.length).toBe(0)
})
it('updates outdated dependencies in package.json', async () => {
outdated = {
a: {
wanted: '1.2.3',
latest: '1.4.0',
location: '',
},
b: {
wanted: '3.4.6',
latest: '3.4.7',
location: '',
},
}
await update('update', '/my/dir')
expect(writeMock.mock.calls).toEqual([[
'/my/dir/package.json', stringify({
name: 'test',
dependencies: {
a: '^1.4.0',
},
devDependencies: {
b: '^3.4.7',
},
}),
]])
})
})

View File

@ -0,0 +1,87 @@
import * as fs from 'fs'
import * as path from 'path'
import cp from 'child_process'
import {argparse, arg} from '@rondo.dev/argparse'
import {info} from '../log'
export interface IOutdated {
wanted: string
latest: string
location: string
}
export interface IPackage {
dependencies?: Record<string, string>
devDependencies?: Record<string, string>
}
function findOutdated(cwd: string): Record<string, IOutdated> {
try {
const result = cp.execFileSync('npm', ['outdated', '--json'], {
cwd,
encoding: 'utf8',
})
return result === '' ? {} : JSON.parse(result)
} catch (err) {
// npm outdated will exit with code 1 if there are outdated dependencies
return JSON.parse(err.stdout)
}
}
export async function update(...argv: string[]) {
const {parse} = argparse({
dirs: arg('string[]', {positional: true, default: ['.'], n: '+'}),
prefix: arg('string', {default: '^'}),
})
const {dirs, prefix} = parse(argv)
let updates = 0
for (const dir of dirs) {
info(dir)
const outdatedByName = findOutdated(dir)
const pkgFile = path.join(dir, 'package.json')
const pkg: IPackage = JSON.parse(fs.readFileSync(pkgFile, 'utf8'))
let pkgUpdate: IPackage = pkg
// tslint:disable-next-line
for (const name in outdatedByName) {
const outdated = outdatedByName[name]
pkgUpdate = updateDependency(
pkgUpdate, 'dependencies', name, prefix, outdated)
pkgUpdate = updateDependency(
pkgUpdate, 'devDependencies', name, prefix, outdated)
}
if (pkgUpdate !== pkg) {
updates += 1
info('Writing updates...')
fs.writeFileSync(pkgFile, JSON.stringify(pkgUpdate, null, ' '))
}
}
if (updates) {
info('Done! Do not forget to run npm install!')
}
}
function updateDependency(
pkg: IPackage,
key: 'dependencies' | 'devDependencies',
name: string,
prefix: string,
version: IOutdated,
): IPackage {
const deps = pkg[key]
if (!deps || !deps[name] || version.wanted === version.latest) {
return pkg
}
info(' %s.%s %s ==> %s', key, name, version.wanted, version.latest)
return {
...pkg,
[key]: {
...deps,
[name]: prefix + version.latest,
},
}
}