diff --git a/README.md b/README.md index 9fa887a..095a51f 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ For busy developers who appreciate the benefits of code linting but find it tedi - No configuration needed - Still fully flexible: you can specify your file patterns, disable rules that are not worth fixing , and have completely own, team- or company-wide ruleset (see [configuration](#configuration) section) - Supports new projects and existing codebases -- Automatically updates linting dependencies using your project's dependency manager, Yarn or NPM +- Automatically updates linting dependencies using your project's dependency manager. Currently supports `npm`, `yarn` and `pnpm`. - Supports monorepos - configures IDE once for the whole repo while keeping linting configurations per project ## Usage diff --git a/src/lib/context/dependencies.ts b/src/lib/context/dependencies.ts index 47aac04..41ea1ff 100644 --- a/src/lib/context/dependencies.ts +++ b/src/lib/context/dependencies.ts @@ -2,13 +2,15 @@ import type { LockFileObject } from '@yarnpkg/lockfile' import { parse as parseYarnLock } from '@yarnpkg/lockfile' import fs from 'fs' +import yaml from 'js-yaml' import path from 'path' import type { PackageJson } from 'type-fest' import type { PackageLock } from '../../types/packageLock' +import type { PnpmLock } from '../../types/pnpmLock' import { fileExists } from '../../util/file' -export type DependencyManager = 'npm' | 'yarn' +export type DependencyManager = 'npm' | 'yarn' | 'pnpm' export interface InstalledPackage { name: string @@ -97,16 +99,68 @@ interface GetInstalledPackagesParameters { projectDirectory: string } +// eslint-disable-next-line sonarjs/cognitive-complexity +const getPnpmPackages = (projectDirectory: string): InstalledPackage[] => { + const installedPackages: InstalledPackage[] = [] + const pnpmLockRaw = fs.readFileSync(path.join(projectDirectory, 'pnpm-lock.yaml'), 'utf8') + const pnpmLock = yaml.load(pnpmLockRaw) as PnpmLock + + for (const workspace in pnpmLock.importers) { + if (Object.prototype.hasOwnProperty.call(pnpmLock.importers, workspace)) { + for (const dependencyName in pnpmLock.importers[workspace].dependencies) { + if ( + Object.prototype.hasOwnProperty.call( + pnpmLock.importers[workspace].dependencies, + dependencyName + ) + ) { + const dependency = pnpmLock.importers[workspace].dependencies[dependencyName] + installedPackages.push({ + name: dependencyName, + isDev: false, + version: dependency.version, + }) + } + } + for (const dependencyName in pnpmLock.importers[workspace].devDependencies) { + if ( + Object.prototype.hasOwnProperty.call( + pnpmLock.importers[workspace].devDependencies, + dependencyName + ) + ) { + const dependency = pnpmLock.importers[workspace].devDependencies[dependencyName] + installedPackages.push({ + name: dependencyName, + isDev: true, + version: dependency.version, + }) + } + } + } + } + + return installedPackages +} + export const getInstalledPackages = ( parameters: GetInstalledPackagesParameters ): InstalledPackage[] => { const { packageJson, dependencyManager, projectDirectory } = parameters - let installedPackages: InstalledPackage[] = [] - installedPackages = - dependencyManager === 'npm' - ? getNpmPackages(projectDirectory) - : getYarnPackages(packageJson, projectDirectory) - return installedPackages + switch (dependencyManager) { + case 'npm': { + return getNpmPackages(projectDirectory) + } + case 'yarn': { + return getYarnPackages(packageJson, projectDirectory) + } + case 'pnpm': { + return getPnpmPackages(projectDirectory) + } + default: { + return [] + } + } } export const getDependencyManager = (projectDirectory: string): DependencyManager | undefined => { @@ -114,6 +168,8 @@ export const getDependencyManager = (projectDirectory: string): DependencyManage return 'npm' } else if (fileExists(path.join(projectDirectory, 'yarn.lock'))) { return 'yarn' + } else if (fileExists(path.join(projectDirectory, 'pnpm-lock.yaml'))) { + return 'pnpm' } return undefined } diff --git a/src/lib/eslint/installDependencies.ts b/src/lib/eslint/installDependencies.ts index baa8117..54468b7 100644 --- a/src/lib/eslint/installDependencies.ts +++ b/src/lib/eslint/installDependencies.ts @@ -75,6 +75,27 @@ const installYarnDependencies = (parameters: InstallDependenciesParameters): voi } } +const installPnpmDependencies = (parameters: InstallDependenciesParameters): void => { + const { dependenciesToUpdate, devDependenciesToUpdate, cwd, debug } = parameters + const dependencyList = dependenciesToUpdate + .map(([dependency, version]) => `${dependency}@${version}`) + .join(' ') + const developmentDependencyList = devDependenciesToUpdate + .map(([dependency, version]) => `${dependency}@${version}`) + .join(' ') + + if (dependencyList) { + execSync(`pnpm add ${dependencyList} -w`, { cwd, stdio: debug ? 'inherit' : 'ignore' }) + } + + if (developmentDependencyList) { + execSync(`pnpm add -D ${developmentDependencyList} -w`, { + cwd, + stdio: debug ? 'inherit' : 'ignore', + }) + } +} + interface Parameters { dependencyManager?: DependencyManager dependencies: ExactDependency[] @@ -102,27 +123,45 @@ export const installDependencies = (parameters: Parameters) => { log.debug(`dependencies to install or update: ${readableDependencyList}`) - if (!dependencyManager) { - execSync('npm init -y', { cwd, stdio: debug ? 'inherit' : 'ignore' }) - installNpmDependencies({ - dependenciesToUpdate, - devDependenciesToUpdate, - cwd, - debug, - }) - } else if (dependencyManager === 'npm') { - installNpmDependencies({ - dependenciesToUpdate, - devDependenciesToUpdate, - cwd, - debug, - }) - } else { - installYarnDependencies({ - dependenciesToUpdate, - devDependenciesToUpdate, - cwd, - debug, - }) + switch (dependencyManager) { + case 'npm': { + installNpmDependencies({ + dependenciesToUpdate, + devDependenciesToUpdate, + cwd, + debug, + }) + + break + } + case 'yarn': { + installYarnDependencies({ + dependenciesToUpdate, + devDependenciesToUpdate, + cwd, + debug, + }) + + break + } + case 'pnpm': { + installPnpmDependencies({ + dependenciesToUpdate, + devDependenciesToUpdate, + cwd, + debug, + }) + + break + } + default: { + execSync('npm init -y', { cwd, stdio: debug ? 'inherit' : 'ignore' }) + installNpmDependencies({ + dependenciesToUpdate, + devDependenciesToUpdate, + cwd, + debug, + }) + } } } diff --git a/src/types/pnpmLock.ts b/src/types/pnpmLock.ts new file mode 100644 index 0000000..3382efc --- /dev/null +++ b/src/types/pnpmLock.ts @@ -0,0 +1,39 @@ +import type { JsonObject } from 'type-fest' + +export interface PnpmLock { + lockfileVersion: string + settingsautoInstallPeers: { + autoInstallPeers: boolean + excludeLinksFromLockfile: boolean + } + importers: Record< + string, + { + dependencies: Record + devDependencies: Record + } + > + packages: Record< + string, + { + resolution: { + integrity: string + } + engines?: Record + transitivePeerDependencies?: string[] + dependencies?: Record + peerDependencies?: Record + peerDependenciesMeta?: Record + cpu?: string[] + os?: string[] + hasBin?: boolean + requiresBuild?: boolean + dev: boolean + } + > +} + +interface PackageDescriptor { + specifier: string + version: string +}