Created
August 14, 2023 01:01
-
-
Save jrson83/b19c9f5dd05051b7b72ab993ddeb9b76 to your computer and use it in GitHub Desktop.
detect package manager
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import fs from 'node:fs/promises' | |
import path from 'node:path' | |
import { exec } from 'node:child_process' | |
import { promisify } from 'node:util' | |
export const execAsync = promisify(exec) | |
export interface PackageJson { | |
name: string | |
version?: string | |
license?: string | |
scripts?: ScriptsMap | |
dependencies?: DependencyMap | |
packageManager?: string | |
} | |
export interface ScriptsMap { | |
[scriptName: string]: string | |
} | |
export interface DependencyMap { | |
[depName: string]: string | |
} | |
/** | |
* Available package managers | |
*/ | |
export const packageManager = ['npm', 'pnpm', 'yarn'] as const | |
export const packageManagerSpecs = [ | |
{ | |
name: 'npm', | |
lockfile: 'package-lock.json', | |
}, | |
{ | |
name: 'pnpm', | |
lockfile: 'pnpm-lock.yaml', | |
}, | |
{ | |
name: 'yarn', | |
lockfile: 'yarn.lock', | |
}, | |
] as const | |
export type PackageManagerName = typeof packageManager[number] | |
export type PackageManagerVersion = string | undefined | |
export type PackageManagerResult = { | |
name: PackageManagerName | |
version: PackageManagerVersion | |
filePath: string | |
} | |
/** | |
* Lockfiles which belong to the available package managers | |
*/ | |
export const lockFiles = ['package-lock.json', 'pnpm-lock.yaml', 'yarn.lock'] | |
/** | |
* Normalizes a given path. | |
* @param filePath - path to search | |
* @returns Returns if a path exists | |
*/ | |
const normalizePath = (dir: string, file?: string): string => { | |
return path.normalize(path.resolve(dir, file ? file : '')) | |
} | |
/** | |
* Checks if a given path exists. | |
* @param filePath - path to search | |
* @returns Returns if a path exists | |
*/ | |
export const pathExists = (filePath: string): Promise<boolean> => | |
fs | |
.access(filePath) | |
.then(() => true) | |
.catch(() => false) | |
export async function findFile( | |
filename: string, | |
startdir: string = process.cwd() | |
): Promise<string | undefined> { | |
if (!filename) | |
throw new Error('Error: No filename was specified for findFile()') | |
const filePath = normalizePath(startdir, filename) | |
return (await pathExists(filePath)) ? filePath : undefined | |
} | |
export async function detectFromPackageJson( | |
startdir: string = process.cwd() | |
): Promise<PackageManagerResult | undefined> { | |
const packageJsonPath = await findFile('package.json', startdir) | |
if (!packageJsonPath) { | |
return | |
} | |
const { packageManager }: PackageJson = JSON.parse( | |
await fs.readFile(packageJsonPath, { | |
encoding: 'utf8', | |
}) | |
) | |
if (!packageManager) { | |
return | |
} | |
const [name, version] = packageManager.split('@') as [ | |
PackageManagerName, | |
PackageManagerVersion | |
] | |
return { | |
name, | |
version, | |
filePath: packageJsonPath, | |
} | |
} | |
export async function detectFromLockfile(startdir: string = process.cwd()) { | |
return ( | |
await Promise.all( | |
packageManagerSpecs.map(async ({ name, lockfile }) => { | |
return { | |
name, | |
lockfile, | |
exists: await pathExists(normalizePath(startdir, lockfile)), | |
version: '', | |
} | |
}) | |
) | |
).filter(({ exists }) => { | |
return exists === true | |
}) | |
} | |
export async function detectPackageManager( | |
startdir: string = process.cwd(), | |
prefered: PackageManagerName = 'npm' | |
) { | |
const detectedPackageManager = await detectFromPackageJson(startdir) | |
if (detectedPackageManager) { | |
return detectedPackageManager | |
} | |
const detectedPackageManagerFromLockfile = await detectFromLockfile(startdir) | |
if ( | |
Array.isArray(detectedPackageManagerFromLockfile) && | |
!detectedPackageManagerFromLockfile.length | |
) { | |
throw new Error('Error: Could not detect package manager') | |
} | |
if ( | |
Array.isArray(detectedPackageManagerFromLockfile) && | |
detectedPackageManagerFromLockfile.length > 1 | |
) { | |
/* throw new Error('Error: Multiple package manager detected') */ | |
return detectedPackageManagerFromLockfile.filter( | |
({ name }) => name === prefered | |
) | |
} | |
const childProcess = await execAsync( | |
`${detectedPackageManagerFromLockfile[0].name} --version` | |
) | |
const version = childProcess.stdout.trim().replace(/^v/i, '') | |
detectedPackageManagerFromLockfile[0].version = version | |
return detectedPackageManagerFromLockfile[0] | |
} | |
console.log(await detectPackageManager()) | |
/* function getObjectProperty<T extends Object, K extends keyof T>( | |
o: T, | |
propertyName: K | |
): T[K] | undefined { | |
return Object.hasOwn(o, propertyName) ? o[propertyName] : undefined | |
} | |
const packageJsonPath = await findFile(startdir, 'package.json') | |
if (!packageJsonPath) { | |
return | |
} | |
const packageJsonData: PackageJson = JSON.parse( | |
await fs.readFile(packageJsonPath, { | |
encoding: 'utf8', | |
}) | |
) | |
console.log(getObjectProperty(packageJsonData, 'packageManager')) | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment