@bfra.me/eslint-config
Version:
Shared ESLint configuration for bfra.me
112 lines (99 loc) • 3.24 kB
text/typescript
import {spawnSync} from 'node:child_process'
import {existsSync} from 'node:fs'
import {resolve} from 'node:path'
import process from 'node:process'
import {packageDirectorySync} from 'package-directory'
import {resolveCommand, type AgentName} from 'package-manager-detector'
const installedModules = new Set<string>()
export function tryInstall(module: string, targetFile: string = process.cwd()) {
if (installedModules.has(module)) {
return null
}
const cwd = packageDirectorySync({cwd: targetFile}) ?? ''
if (cwd) {
installedModules.add(module)
const {moduleName, version = ''} = parseModule(module)
const result = installPackageSync(moduleName + (version ? `@^${version}` : ''), {
cwd,
dev: true,
})
return result
}
return null
}
export function getPackageInstallCommand(module: string, targetFile: string = process.cwd()) {
const cwd = packageDirectorySync({cwd: targetFile}) ?? ''
if (!cwd) {
return null
}
const agent = detectPackageManagerSync(cwd)
if (!agent) {
return null
}
const command = resolveCommand(agent, 'install', ['-D', module])
return command ? `${command.command} ${command.args.join(' ')}` : null
}
function parseModule(name: string) {
const parts = name.split(/@/u)
if (parts.length > 1 && parts.slice(0, -1).join('@')) {
return {
moduleName: parts.slice(0, -1).join('@'),
version: parts.at(-1),
}
}
return {moduleName: name}
}
function installPackageSync(packages: string | string[], options: {cwd: string; dev: boolean}) {
const manager = (detectPackageManagerSync(options.cwd)?.split('@')[0] ?? '') || 'npm'
if (!Array.isArray(packages)) {
packages = [packages]
}
const args = []
if (
manager === 'pnpm' &&
existsSync(resolve(options.cwd ?? process.cwd(), 'pnpm-workspace.yaml'))
) {
args.unshift(
'-w',
/**
* Prevent pnpm from removing installed development deps when `NODE_ENV` is `production`
* @see https://pnpm.io/cli/install#--prod--p
*/
'--prod=false',
)
}
const result = spawnSync(
manager,
[manager === 'yarn' ? 'add' : 'install', options.dev ? '-D' : '', ...args, ...packages].filter(
Boolean,
),
{cwd: options.cwd, maxBuffer: Infinity, stdio: 'inherit', windowsHide: true},
)
if (result.error || result.status !== 0) {
const errorMessage =
(result.error?.message ?? '') || `Package installation failed with status ${result.status}`
throw new Error(errorMessage)
}
return `${result.output?.toString() ?? ''}`
}
function detectPackageManagerSync(cwd = process.cwd()): AgentName | null {
// We cannot reliably convert an async detect() to sync detectSync()
// without potentially blocking. The proper fix would be to make this
// function async, but that would break API compatibility.
// For now, we'll use a simple fallback approach:
try {
// Look for lockfiles directly
if (existsSync(resolve(cwd, 'package-lock.json'))) {
return 'npm'
}
if (existsSync(resolve(cwd, 'yarn.lock'))) {
return 'yarn'
}
if (existsSync(resolve(cwd, 'pnpm-lock.yaml'))) {
return 'pnpm'
}
return null
} catch {
return null
}
}