Skip to content

Commit b9e8eb2

Browse files
patzickantfu
andauthoredNov 30, 2023
feat!: support pnpm.overrides and resolutions, remove --dev and --prod cli option (#92)
Co-authored-by: Anthony Fu <anthonyfu117@hotmail.com>
1 parent 9ad7f0a commit b9e8eb2

File tree

7 files changed

+230
-62
lines changed

7 files changed

+230
-62
lines changed
 

‎src/cli.ts

-12
Original file line numberDiff line numberDiff line change
@@ -57,18 +57,6 @@ function commonOptions(args: Argv<object>): Argv<CommonOptions> {
5757
type: 'string',
5858
describe: 'exclude dependencies to be checked, will override --include options',
5959
})
60-
.option('dev', {
61-
alias: 'D',
62-
type: 'boolean',
63-
describe: 'update only for devDependencies',
64-
conflicts: ['prod'],
65-
})
66-
.option('prod', {
67-
alias: 'P',
68-
type: 'boolean',
69-
describe: 'update only for dependencies',
70-
conflicts: ['dev'],
71-
})
7260
}
7361

7462
// eslint-disable-next-line no-unused-expressions

‎src/constants.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ export const DEFAULT_COMMON_OPTIONS: CommonOptions = {
1414
ignorePaths: '',
1515
include: '',
1616
exclude: '',
17-
dev: false,
18-
prod: false,
17+
depFields: {},
1918
}
2019

2120
export const DEFAULT_USAGE_OPTIONS: UsageOptions = {

‎src/io/dependencies.ts

+12-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,18 @@
11
import type { DepType, RawDep, ResolvedDepChange } from '../types'
22

3+
export function getByPath(obj: any, path: string) {
4+
return path.split('.').reduce((o, i) => o?.[i], obj)
5+
}
6+
7+
export function setByPath(obj: any, path: string, value: any) {
8+
const keys = path.split('.')
9+
const lastKey = keys.pop() as string
10+
const target = keys.reduce((o, i) => o[i] = o[i] || {}, obj)
11+
target[lastKey] = value
12+
}
13+
314
export function parseDependencies(pkg: any, type: DepType, shouldUpdate: (name: string) => boolean): RawDep[] {
4-
return Object.entries(pkg[type] || {}).map(([name, version]) => parseDependency(name, version as string, type, shouldUpdate))
15+
return Object.entries(getByPath(pkg, type) || {}).map(([name, version]) => parseDependency(name, version as string, type, shouldUpdate))
516
}
617

718
export function parseDependency(name: string, version: string, type: DepType, shouldUpdate: (name: string) => boolean): RawDep {

‎src/io/packages.ts

+45-39
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import fg from 'fast-glob'
44
import detectIndent from 'detect-indent'
55
import type { CommonOptions, PackageMeta, RawDep } from '../types'
66
import { createDependenciesFilter } from '../utils/dependenciesFilter'
7-
import { dumpDependencies, parseDependencies, parseDependency } from './dependencies'
7+
import { dumpDependencies, getByPath, parseDependencies, parseDependency, setByPath } from './dependencies'
88

99
export async function readJSON(filepath: string) {
1010
return JSON.parse(await fs.readFile(filepath, 'utf-8'))
@@ -17,58 +17,64 @@ export async function writeJSON(filepath: string, data: any) {
1717
return await fs.writeFile(filepath, `${JSON.stringify(data, null, fileIndent)}\n`, 'utf-8')
1818
}
1919

20+
const depsFields = [
21+
'dependencies',
22+
'devDependencies',
23+
'optionalDependencies',
24+
'packageManager',
25+
'pnpm.overrides',
26+
'resolutions',
27+
'overrides',
28+
] as const
29+
2030
export async function writePackage(pkg: PackageMeta, options: CommonOptions) {
2131
const { raw, filepath, resolved } = pkg
2232

2333
let changed = false
2434

25-
const depKeys = [
26-
['dependencies', !options.dev],
27-
['devDependencies', !options.prod],
28-
['optionalDependencies', !options.prod && !options.dev],
29-
] as const
30-
31-
depKeys.forEach(([key, shouldWrite]) => {
32-
if (raw[key] && shouldWrite) {
33-
raw[key] = dumpDependencies(resolved, key)
34-
changed = true
35+
depsFields.forEach((key) => {
36+
if (options.depFields?.[key] === false)
37+
return
38+
if (key === 'packageManager') {
39+
const value = Object.entries(dumpDependencies(resolved, 'packageManager'))[0]
40+
if (value) {
41+
raw.packageManager = `${value[0]}@${value[1].replace('^', '')}`
42+
changed = true
43+
}
3544
}
36-
})
37-
38-
if (raw.packageManager) {
39-
const value = Object.entries(dumpDependencies(resolved, 'packageManager'))[0]
40-
if (value) {
41-
raw.packageManager = `${value[0]}@${value[1].replace('^', '')}`
42-
changed = true
45+
else {
46+
if (getByPath(raw, key)) {
47+
setByPath(raw, key, dumpDependencies(resolved, key))
48+
changed = true
49+
}
4350
}
44-
}
51+
})
4552

4653
if (changed)
4754
await writeJSON(filepath, raw)
4855
}
4956

50-
export async function loadPackage(relative: string, options: CommonOptions, shouldUpdate: (name: string) => boolean): Promise<PackageMeta> {
57+
export async function loadPackage(
58+
relative: string,
59+
options: CommonOptions,
60+
shouldUpdate: (name: string) => boolean,
61+
): Promise<PackageMeta> {
5162
const filepath = path.resolve(options.cwd ?? '', relative)
5263
const raw = await readJSON(filepath)
53-
let deps: RawDep[] = []
54-
55-
if (options.prod) {
56-
deps = parseDependencies(raw, 'dependencies', shouldUpdate)
57-
}
58-
else if (options.dev) {
59-
deps = parseDependencies(raw, 'devDependencies', shouldUpdate)
60-
}
61-
else {
62-
deps = [
63-
...parseDependencies(raw, 'dependencies', shouldUpdate),
64-
...parseDependencies(raw, 'devDependencies', shouldUpdate),
65-
...parseDependencies(raw, 'optionalDependencies', shouldUpdate),
66-
]
67-
}
68-
69-
if (raw.packageManager) {
70-
const [name, version] = raw.packageManager.split('@')
71-
deps.push(parseDependency(name, `^${version}`, 'packageManager', shouldUpdate))
64+
const deps: RawDep[] = []
65+
66+
for (const key of depsFields) {
67+
if (options.depFields?.[key] !== false) {
68+
if (key === 'packageManager') {
69+
if (raw.packageManager) {
70+
const [name, version] = raw.packageManager.split('@')
71+
deps.push(parseDependency(name, `^${version}`, 'packageManager', shouldUpdate))
72+
}
73+
}
74+
else {
75+
deps.push(...parseDependencies(raw, key, shouldUpdate))
76+
}
77+
}
7278
}
7379

7480
return {

‎src/types.ts

+23-8
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,17 @@ import type { SortOption } from './utils/sort'
33

44
export type RangeMode = 'default' | 'major' | 'minor' | 'patch' | 'latest' | 'newest'
55
export type PackageMode = Omit<RangeMode, 'default'> | 'ignore'
6-
export type DepType = 'dependencies' | 'devDependencies' | 'peerDependencies' | 'optionalDependencies' | 'packageManager'
6+
export type DepType = 'dependencies' | 'devDependencies' | 'peerDependencies' | 'optionalDependencies' | 'packageManager' | 'pnpm.overrides' | 'resolutions' | 'overrides'
7+
78
export const DependenciesTypeShortMap = {
8-
dependencies: '',
9-
devDependencies: 'dev',
10-
peerDependencies: 'peer',
11-
optionalDependencies: 'optional',
12-
packageManager: 'package-manager',
9+
'dependencies': '',
10+
'devDependencies': 'dev',
11+
'peerDependencies': 'peer',
12+
'optionalDependencies': 'optional',
13+
'packageManager': 'package-manager',
14+
'pnpm.overrides': 'pnpm-overrides',
15+
'resolutions': 'resolutions',
16+
'overrides': 'overrides',
1317
}
1418

1519
export interface RawDep {
@@ -49,12 +53,21 @@ export interface CommonOptions {
4953
ignorePaths?: string | string[]
5054
include?: string | string[]
5155
exclude?: string | string[]
52-
prod?: boolean
53-
dev?: boolean
5456
loglevel?: LogLevel
5557
failOnOutdated?: boolean
5658
silent?: boolean
59+
/**
60+
* Fields in package.json to be checked
61+
* By default all fields will be checked
62+
*/
63+
depFields?: DepFieldOptions
64+
/**
65+
* Bypass cache
66+
*/
5767
force?: boolean
68+
/**
69+
* Override bumping mode for specific dependencies
70+
*/
5871
packageMode?: { [name: string]: PackageMode }
5972
}
6073

@@ -63,6 +76,8 @@ export interface UsageOptions extends CommonOptions {
6376
recursive?: true
6477
}
6578

79+
export type DepFieldOptions = Partial<Record<DepType, boolean>>
80+
6681
export interface CheckOptions extends CommonOptions {
6782
mode?: RangeMode
6883
write?: boolean

‎test/dumpDependencies.test.ts

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { describe, expect, it } from 'vitest'
2+
import { dumpDependencies } from '../src/io/dependencies'
3+
import type { DepType, ResolvedDepChange } from '../src/types'
4+
5+
describe('dumpDependencies', () => {
6+
function getPackageBySource(source: DepType) {
7+
return {
8+
name: '@types/semver',
9+
currentVersion: '^7.3.10',
10+
source,
11+
update: true,
12+
targetVersion: '^7.3.12',
13+
diff: 'patch',
14+
} as ResolvedDepChange
15+
}
16+
17+
it('dump `dependencies` type', () => {
18+
const dump = dumpDependencies([getPackageBySource('dependencies')], 'dependencies')
19+
expect(dump).toMatchInlineSnapshot(`
20+
{
21+
"@types/semver": "^7.3.12",
22+
}
23+
`)
24+
})
25+
it('dump `devDependencies` type', () => {
26+
const dump = dumpDependencies([getPackageBySource('devDependencies')], 'devDependencies')
27+
expect(dump).toMatchInlineSnapshot(`
28+
{
29+
"@types/semver": "^7.3.12",
30+
}
31+
`)
32+
})
33+
it('dump `pnpm.overrides` type', () => {
34+
const dump = dumpDependencies([getPackageBySource('pnpm.overrides')], 'pnpm.overrides')
35+
expect(dump).toMatchInlineSnapshot(`
36+
{
37+
"@types/semver": "^7.3.12",
38+
}
39+
`)
40+
})
41+
42+
it('dump `resolutions` type', () => {
43+
const dump = dumpDependencies([getPackageBySource('resolutions')], 'resolutions')
44+
expect(dump).toMatchInlineSnapshot(`
45+
{
46+
"@types/semver": "^7.3.12",
47+
}
48+
`)
49+
})
50+
51+
it('dump `overrides` type', () => {
52+
const dump = dumpDependencies([getPackageBySource('overrides')], 'overrides')
53+
expect(dump).toMatchInlineSnapshot(`
54+
{
55+
"@types/semver": "^7.3.12",
56+
}
57+
`)
58+
})
59+
})

‎test/parseDependencies.test.ts

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { describe, expect, it } from 'vitest'
2+
import { parseDependencies } from '../src/io/dependencies'
3+
4+
describe('parseDependencies', () => {
5+
it('parse package `dependencies`', () => {
6+
const myPackage = {
7+
name: '@taze/package1',
8+
private: true,
9+
dependencies: {
10+
'@taze/not-exists': '^4.13.19',
11+
'@typescript/lib-dom': 'npm:@types/web@^0.0.80',
12+
},
13+
}
14+
const result = parseDependencies(myPackage, 'dependencies', () => true)
15+
expect(result).toMatchInlineSnapshot(`
16+
[
17+
{
18+
"currentVersion": "^4.13.19",
19+
"name": "@taze/not-exists",
20+
"source": "dependencies",
21+
"update": true,
22+
},
23+
{
24+
"currentVersion": "npm:@types/web@^0.0.80",
25+
"name": "@typescript/lib-dom",
26+
"source": "dependencies",
27+
"update": true,
28+
},
29+
]
30+
`)
31+
})
32+
33+
it('parse package `devDependencies`', () => {
34+
const myPackage = {
35+
name: '@taze/package1',
36+
private: true,
37+
devDependencies: {
38+
'@taze/not-exists': '^4.13.19',
39+
'@typescript/lib-dom': 'npm:@types/web@^0.0.80',
40+
},
41+
}
42+
const result = parseDependencies(myPackage, 'devDependencies', () => true)
43+
expect(result).toMatchInlineSnapshot(`
44+
[
45+
{
46+
"currentVersion": "^4.13.19",
47+
"name": "@taze/not-exists",
48+
"source": "devDependencies",
49+
"update": true,
50+
},
51+
{
52+
"currentVersion": "npm:@types/web@^0.0.80",
53+
"name": "@typescript/lib-dom",
54+
"source": "devDependencies",
55+
"update": true,
56+
},
57+
]
58+
`)
59+
})
60+
61+
it('parse package `pnpm.overrides`', () => {
62+
const myPackage = {
63+
name: '@taze/package1',
64+
private: true,
65+
pnpm: {
66+
overrides: {
67+
'@taze/not-exists': '^4.13.19',
68+
'@typescript/lib-dom': 'npm:@types/web@^0.0.80',
69+
},
70+
},
71+
}
72+
const result = parseDependencies(myPackage, 'pnpm.overrides', () => true)
73+
expect(result).toMatchInlineSnapshot(`
74+
[
75+
{
76+
"currentVersion": "^4.13.19",
77+
"name": "@taze/not-exists",
78+
"source": "pnpm.overrides",
79+
"update": true,
80+
},
81+
{
82+
"currentVersion": "npm:@types/web@^0.0.80",
83+
"name": "@typescript/lib-dom",
84+
"source": "pnpm.overrides",
85+
"update": true,
86+
},
87+
]
88+
`)
89+
})
90+
})

0 commit comments

Comments
 (0)
Please sign in to comment.