Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: support pnpm.overrides and resolutions, remove --dev and --prod cli option #92

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 0 additions & 12 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,18 +57,6 @@ function commonOptions(args: Argv<object>): Argv<CommonOptions> {
type: 'string',
describe: 'exclude dependencies to be checked, will override --include options',
})
.option('dev', {
alias: 'D',
type: 'boolean',
describe: 'update only for devDependencies',
conflicts: ['prod'],
})
.option('prod', {
alias: 'P',
type: 'boolean',
describe: 'update only for dependencies',
conflicts: ['dev'],
})
}

// eslint-disable-next-line no-unused-expressions
Expand Down
3 changes: 1 addition & 2 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ export const DEFAULT_COMMON_OPTIONS: CommonOptions = {
ignorePaths: '',
include: '',
exclude: '',
dev: false,
prod: false,
depFields: {},
}

export const DEFAULT_USAGE_OPTIONS: UsageOptions = {
Expand Down
13 changes: 12 additions & 1 deletion src/io/dependencies.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
import type { DepType, RawDep, ResolvedDepChange } from '../types'

export function getByPath(obj: any, path: string) {
return path.split('.').reduce((o, i) => o?.[i], obj)
}

export function setByPath(obj: any, path: string, value: any) {
const keys = path.split('.')
const lastKey = keys.pop() as string
const target = keys.reduce((o, i) => o[i] = o[i] || {}, obj)
target[lastKey] = value
}

export function parseDependencies(pkg: any, type: DepType, shouldUpdate: (name: string) => boolean): RawDep[] {
return Object.entries(pkg[type] || {}).map(([name, version]) => parseDependency(name, version as string, type, shouldUpdate))
return Object.entries(getByPath(pkg, type) || {}).map(([name, version]) => parseDependency(name, version as string, type, shouldUpdate))
}

export function parseDependency(name: string, version: string, type: DepType, shouldUpdate: (name: string) => boolean): RawDep {
Expand Down
84 changes: 45 additions & 39 deletions src/io/packages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import fg from 'fast-glob'
import detectIndent from 'detect-indent'
import type { CommonOptions, PackageMeta, RawDep } from '../types'
import { createDependenciesFilter } from '../utils/dependenciesFilter'
import { dumpDependencies, parseDependencies, parseDependency } from './dependencies'
import { dumpDependencies, getByPath, parseDependencies, parseDependency, setByPath } from './dependencies'

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

const depsFields = [
'dependencies',
'devDependencies',
'optionalDependencies',
'packageManager',
'pnpm.overrides',
'resolutions',
'overrides',
] as const

export async function writePackage(pkg: PackageMeta, options: CommonOptions) {
const { raw, filepath, resolved } = pkg

let changed = false

const depKeys = [
['dependencies', !options.dev],
['devDependencies', !options.prod],
['optionalDependencies', !options.prod && !options.dev],
] as const

depKeys.forEach(([key, shouldWrite]) => {
if (raw[key] && shouldWrite) {
raw[key] = dumpDependencies(resolved, key)
changed = true
depsFields.forEach((key) => {
if (options.depFields?.[key] === false)
return
if (key === 'packageManager') {
const value = Object.entries(dumpDependencies(resolved, 'packageManager'))[0]
if (value) {
raw.packageManager = `${value[0]}@${value[1].replace('^', '')}`
changed = true
}
}
})

if (raw.packageManager) {
const value = Object.entries(dumpDependencies(resolved, 'packageManager'))[0]
if (value) {
raw.packageManager = `${value[0]}@${value[1].replace('^', '')}`
changed = true
else {
if (getByPath(raw, key)) {
setByPath(raw, key, dumpDependencies(resolved, key))
changed = true
}
}
}
})

if (changed)
await writeJSON(filepath, raw)
}

export async function loadPackage(relative: string, options: CommonOptions, shouldUpdate: (name: string) => boolean): Promise<PackageMeta> {
export async function loadPackage(
relative: string,
options: CommonOptions,
shouldUpdate: (name: string) => boolean,
): Promise<PackageMeta> {
const filepath = path.resolve(options.cwd ?? '', relative)
const raw = await readJSON(filepath)
let deps: RawDep[] = []

if (options.prod) {
deps = parseDependencies(raw, 'dependencies', shouldUpdate)
}
else if (options.dev) {
deps = parseDependencies(raw, 'devDependencies', shouldUpdate)
}
else {
deps = [
...parseDependencies(raw, 'dependencies', shouldUpdate),
...parseDependencies(raw, 'devDependencies', shouldUpdate),
...parseDependencies(raw, 'optionalDependencies', shouldUpdate),
]
}

if (raw.packageManager) {
const [name, version] = raw.packageManager.split('@')
deps.push(parseDependency(name, `^${version}`, 'packageManager', shouldUpdate))
const deps: RawDep[] = []

for (const key of depsFields) {
if (options.depFields?.[key] !== false) {
if (key === 'packageManager') {
if (raw.packageManager) {
const [name, version] = raw.packageManager.split('@')
deps.push(parseDependency(name, `^${version}`, 'packageManager', shouldUpdate))
}
}
else {
deps.push(...parseDependencies(raw, key, shouldUpdate))
}
}
}

return {
Expand Down
31 changes: 23 additions & 8 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@ import type { SortOption } from './utils/sort'

export type RangeMode = 'default' | 'major' | 'minor' | 'patch' | 'latest' | 'newest'
export type PackageMode = Omit<RangeMode, 'default'> | 'ignore'
export type DepType = 'dependencies' | 'devDependencies' | 'peerDependencies' | 'optionalDependencies' | 'packageManager'
export type DepType = 'dependencies' | 'devDependencies' | 'peerDependencies' | 'optionalDependencies' | 'packageManager' | 'pnpm.overrides' | 'resolutions' | 'overrides'

export const DependenciesTypeShortMap = {
dependencies: '',
devDependencies: 'dev',
peerDependencies: 'peer',
optionalDependencies: 'optional',
packageManager: 'package-manager',
'dependencies': '',
'devDependencies': 'dev',
'peerDependencies': 'peer',
'optionalDependencies': 'optional',
'packageManager': 'package-manager',
'pnpm.overrides': 'pnpm-overrides',
'resolutions': 'resolutions',
'overrides': 'overrides',
}

export interface RawDep {
Expand Down Expand Up @@ -49,12 +53,21 @@ export interface CommonOptions {
ignorePaths?: string | string[]
include?: string | string[]
exclude?: string | string[]
prod?: boolean
dev?: boolean
loglevel?: LogLevel
failOnOutdated?: boolean
silent?: boolean
/**
* Fields in package.json to be checked
* By default all fields will be checked
*/
depFields?: DepFieldOptions
/**
* Bypass cache
*/
force?: boolean
/**
* Override bumping mode for specific dependencies
*/
packageMode?: { [name: string]: PackageMode }
}

Expand All @@ -63,6 +76,8 @@ export interface UsageOptions extends CommonOptions {
recursive?: true
}

export type DepFieldOptions = Partial<Record<DepType, boolean>>

export interface CheckOptions extends CommonOptions {
mode?: RangeMode
write?: boolean
Expand Down
59 changes: 59 additions & 0 deletions test/dumpDependencies.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { describe, expect, it } from 'vitest'
import { dumpDependencies } from '../src/io/dependencies'
import type { DepType, ResolvedDepChange } from '../src/types'

describe('dumpDependencies', () => {
function getPackageBySource(source: DepType) {
return {
name: '@types/semver',
currentVersion: '^7.3.10',
source,
update: true,
targetVersion: '^7.3.12',
diff: 'patch',
} as ResolvedDepChange
}

it('dump `dependencies` type', () => {
const dump = dumpDependencies([getPackageBySource('dependencies')], 'dependencies')
expect(dump).toMatchInlineSnapshot(`
{
"@types/semver": "^7.3.12",
}
`)
})
it('dump `devDependencies` type', () => {
const dump = dumpDependencies([getPackageBySource('devDependencies')], 'devDependencies')
expect(dump).toMatchInlineSnapshot(`
{
"@types/semver": "^7.3.12",
}
`)
})
it('dump `pnpm.overrides` type', () => {
const dump = dumpDependencies([getPackageBySource('pnpm.overrides')], 'pnpm.overrides')
expect(dump).toMatchInlineSnapshot(`
{
"@types/semver": "^7.3.12",
}
`)
})

it('dump `resolutions` type', () => {
const dump = dumpDependencies([getPackageBySource('resolutions')], 'resolutions')
expect(dump).toMatchInlineSnapshot(`
{
"@types/semver": "^7.3.12",
}
`)
})

it('dump `overrides` type', () => {
const dump = dumpDependencies([getPackageBySource('overrides')], 'overrides')
expect(dump).toMatchInlineSnapshot(`
{
"@types/semver": "^7.3.12",
}
`)
})
})
90 changes: 90 additions & 0 deletions test/parseDependencies.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { describe, expect, it } from 'vitest'
import { parseDependencies } from '../src/io/dependencies'

describe('parseDependencies', () => {
it('parse package `dependencies`', () => {
const myPackage = {
name: '@taze/package1',
private: true,
dependencies: {
'@taze/not-exists': '^4.13.19',
'@typescript/lib-dom': 'npm:@types/web@^0.0.80',
},
}
const result = parseDependencies(myPackage, 'dependencies', () => true)
expect(result).toMatchInlineSnapshot(`
[
{
"currentVersion": "^4.13.19",
"name": "@taze/not-exists",
"source": "dependencies",
"update": true,
},
{
"currentVersion": "npm:@types/web@^0.0.80",
"name": "@typescript/lib-dom",
"source": "dependencies",
"update": true,
},
]
`)
})

it('parse package `devDependencies`', () => {
const myPackage = {
name: '@taze/package1',
private: true,
devDependencies: {
'@taze/not-exists': '^4.13.19',
'@typescript/lib-dom': 'npm:@types/web@^0.0.80',
},
}
const result = parseDependencies(myPackage, 'devDependencies', () => true)
expect(result).toMatchInlineSnapshot(`
[
{
"currentVersion": "^4.13.19",
"name": "@taze/not-exists",
"source": "devDependencies",
"update": true,
},
{
"currentVersion": "npm:@types/web@^0.0.80",
"name": "@typescript/lib-dom",
"source": "devDependencies",
"update": true,
},
]
`)
})

it('parse package `pnpm.overrides`', () => {
const myPackage = {
name: '@taze/package1',
private: true,
pnpm: {
overrides: {
'@taze/not-exists': '^4.13.19',
'@typescript/lib-dom': 'npm:@types/web@^0.0.80',
},
},
}
const result = parseDependencies(myPackage, 'pnpm.overrides', () => true)
expect(result).toMatchInlineSnapshot(`
[
{
"currentVersion": "^4.13.19",
"name": "@taze/not-exists",
"source": "pnpm.overrides",
"update": true,
},
{
"currentVersion": "npm:@types/web@^0.0.80",
"name": "@typescript/lib-dom",
"source": "pnpm.overrides",
"update": true,
},
]
`)
})
})