orca/src/shared/setup-script-package-manage...

116 lines
3.5 KiB
TypeScript

import type {
SetupScriptImportCandidate,
SetupScriptImportFileExists,
SetupScriptImportFileRead
} from './setup-script-imports'
import { isSetupScriptImportFieldWithinLimit } from './setup-script-import-limits'
const PACKAGE_JSON_PATH = 'package.json'
type PackageManagerName = 'pnpm' | 'bun' | 'yarn' | 'npm'
const PACKAGE_MANAGER_LOCKFILES = [
{ path: 'pnpm-lock.yaml', manager: 'pnpm', setup: 'pnpm install' },
{ path: 'bun.lock', manager: 'bun', setup: 'bun install' },
{ path: 'bun.lockb', manager: 'bun', setup: 'bun install' },
{ path: 'yarn.lock', manager: 'yarn', setup: 'yarn install' },
{ path: 'package-lock.json', manager: 'npm', setup: 'npm install' },
{ path: 'npm-shrinkwrap.json', manager: 'npm', setup: 'npm install' }
] as const
export async function inspectPackageManagerSetupCandidate(
readFile: SetupScriptImportFileRead,
fileExists?: SetupScriptImportFileExists
): Promise<SetupScriptImportCandidate | null> {
const packageJsonContent = await readFile(PACKAGE_JSON_PATH)
const packageJson = parsePackageJson(packageJsonContent)
if (!packageJson) {
return null
}
const packageManager = getPackageManagerName(packageJson.packageManager)
const packageManagerSetup = packageManager ? getPackageManagerSetup(packageManager) : null
if (packageManagerSetup) {
return {
provider: 'package-manager',
label: 'package manager',
files: [PACKAGE_JSON_PATH],
setup: packageManagerSetup,
unsupportedFields: []
}
}
const checkFileExists = fileExists ?? fallbackFileExists(readFile)
const lockfileReads = await Promise.all(
PACKAGE_MANAGER_LOCKFILES.map(async (entry) => ({
...entry,
exists: await checkFileExists(entry.path)
}))
)
const lockfiles = lockfileReads.filter((entry) => entry.exists)
const lockfileManagers = new Set(lockfiles.map((entry) => entry.manager))
const selectedLockfile = lockfileManagers.size === 1 ? lockfiles[0] : null
if (lockfileManagers.size > 1) {
return null
}
const setup = selectedLockfile?.setup ?? 'npm install'
return {
provider: 'package-manager',
label: 'package manager',
files: selectedLockfile ? [selectedLockfile.path] : [PACKAGE_JSON_PATH],
setup,
unsupportedFields: []
}
}
function fallbackFileExists(readFile: SetupScriptImportFileRead): SetupScriptImportFileExists {
return async (relativePath) => (await readFile(relativePath)) !== null
}
function parsePackageJson(content: string | null): Record<string, unknown> | null {
if (!content) {
return null
}
try {
const parsed = JSON.parse(content)
return parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)
? (parsed as Record<string, unknown>)
: null
} catch {
return null
}
}
function getPackageManagerName(value: unknown): PackageManagerName | null {
if (typeof value !== 'string' || !isSetupScriptImportFieldWithinLimit(value)) {
return null
}
const packageManager = value.trim().toLowerCase()
if (packageManager.startsWith('pnpm@')) {
return 'pnpm'
}
if (packageManager.startsWith('bun@')) {
return 'bun'
}
if (packageManager.startsWith('yarn@')) {
return 'yarn'
}
if (packageManager.startsWith('npm@')) {
return 'npm'
}
return null
}
function getPackageManagerSetup(packageManager: PackageManagerName): string {
switch (packageManager) {
case 'pnpm':
return 'pnpm install'
case 'bun':
return 'bun install'
case 'yarn':
return 'yarn install'
case 'npm':
return 'npm install'
}
}