diff --git a/src/cli.ts b/src/cli.ts index ee55323..59bd8ed 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -57,18 +57,6 @@ function commonOptions(args: Argv): Argv { 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 diff --git a/src/constants.ts b/src/constants.ts index d719aef..ef3694f 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -14,8 +14,7 @@ export const DEFAULT_COMMON_OPTIONS: CommonOptions = { ignorePaths: '', include: '', exclude: '', - dev: false, - prod: false, + depFields: {}, } export const DEFAULT_USAGE_OPTIONS: UsageOptions = { diff --git a/src/io/dependencies.ts b/src/io/dependencies.ts index 245346f..b31d282 100644 --- a/src/io/dependencies.ts +++ b/src/io/dependencies.ts @@ -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 { diff --git a/src/io/packages.ts b/src/io/packages.ts index ca75ac3..6ab1d0e 100644 --- a/src/io/packages.ts +++ b/src/io/packages.ts @@ -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')) @@ -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 { +export async function loadPackage( + relative: string, + options: CommonOptions, + shouldUpdate: (name: string) => boolean, +): Promise { 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 { diff --git a/src/types.ts b/src/types.ts index ef7fc3b..18dcdd8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3,13 +3,17 @@ import type { SortOption } from './utils/sort' export type RangeMode = 'default' | 'major' | 'minor' | 'patch' | 'latest' | 'newest' export type PackageMode = Omit | '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 { @@ -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 } } @@ -63,6 +76,8 @@ export interface UsageOptions extends CommonOptions { recursive?: true } +export type DepFieldOptions = Partial> + export interface CheckOptions extends CommonOptions { mode?: RangeMode write?: boolean diff --git a/test/dumpDependencies.test.ts b/test/dumpDependencies.test.ts new file mode 100644 index 0000000..4093be5 --- /dev/null +++ b/test/dumpDependencies.test.ts @@ -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", + } + `) + }) +}) diff --git a/test/parseDependencies.test.ts b/test/parseDependencies.test.ts new file mode 100644 index 0000000..514db41 --- /dev/null +++ b/test/parseDependencies.test.ts @@ -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, + }, + ] + `) + }) +})