import { isString } from '@sindresorhus/is'; import { miscUtils, structUtils } from '@yarnpkg/core'; import { parseSyml } from '@yarnpkg/parsers'; import { logger } from '../../../../logger/index.ts'; import { getSiblingFileName, localPathExists, readLocalFile, } from '../../../../util/fs/index.ts'; import type { PackageFileContent } from '../../types.ts'; import type { YarnCatalogs } from '../schema.ts'; import type { NpmManagerData } from '../types.ts'; import { extractCatalogDeps } from './common/catalogs.ts'; import type { Catalog, LockFile } from './types.ts'; export async function getYarnLock(filePath: string): Promise { // TODO #22198 const yarnLockRaw = (await readLocalFile(filePath, 'utf8'))!; try { const parsed = parseSyml(yarnLockRaw); const lockedVersions: Record = {}; let lockfileVersion: number | undefined; for (const [key, val] of Object.entries(parsed)) { if (key === '__metadata') { // yarn 2 lockfileVersion = parseInt(val.cacheKey, 10); logger.once.debug( `yarn.lock ${filePath} has __metadata.cacheKey=${lockfileVersion}`, ); } else { for (const entry of key.split(', ')) { try { const { scope, name, range } = structUtils.parseDescriptor(entry); const packageName = scope ? `@${scope}/${name}` : name; const { selector } = structUtils.parseRange(range); logger.trace({ entry, version: val.version }); lockedVersions[`${packageName}@${selector}`] = parsed[key].version; } catch (err) { logger.debug( { entry, err }, 'Invalid descriptor or range found in yarn.lock', ); } } } } const isYarn1 = !('__metadata' in parsed); if (isYarn1) { logger.once.debug( `yarn.lock ${filePath} is has no __metadata so is yarn 1`, ); } else { logger.once.debug( `yarn.lock ${filePath} is has __metadata so is yarn 2+`, ); } return { isYarn1, lockfileVersion, lockedVersions, }; } catch (err) { logger.debug({ filePath, err }, 'Warning: Exception parsing yarn.lock'); return { isYarn1: true, lockedVersions: {} }; } } export function getZeroInstallPaths(yarnrcYml: string): string[] { // TODO: fix type #22198 let conf: any; try { conf = parseSyml(yarnrcYml); } catch (err) /* v8 ignore next -- TODO: add test #40625 */ { logger.warn({ err }, 'Error parsing .yarnrc.yml'); } const paths = [ conf?.cacheFolder ?? './.yarn/cache', '.pnp.cjs', '.pnp.js', '.pnp.loader.mjs', ]; if ( conf && miscUtils.tryParseOptionalBoolean(conf.pnpEnableInlining) === false ) { paths.push(conf.pnpDataPath ?? './.pnp.data.json'); } return paths; } export async function isZeroInstall(yarnrcYmlPath: string): Promise { const yarnrcYml = await readLocalFile(yarnrcYmlPath, 'utf8'); // v8 ignore else -- TODO: add test #40625 if (isString(yarnrcYml)) { const paths = getZeroInstallPaths(yarnrcYml); for (const p of paths) { if (await localPathExists(getSiblingFileName(yarnrcYmlPath, p))) { logger.debug(`Detected Yarn zero-install in ${p}`); return true; } } } return false; } export function getYarnVersionFromLock(lockfile: LockFile): string { const { lockfileVersion, isYarn1 } = lockfile; if (isYarn1) { return '^1.22.18'; } if (lockfileVersion && lockfileVersion >= 12) { // This will probably be v5 return '>=4.0.0'; } if (lockfileVersion && lockfileVersion >= 10) { return '^4.0.0'; } if (lockfileVersion && lockfileVersion >= 8) { // https://github.com/yarnpkg/berry/commit/9bcd27ae34aee77a567dd104947407532fa179b3 return '^3.0.0'; } if (lockfileVersion && lockfileVersion >= 6) { // https://github.com/yarnpkg/berry/commit/f753790380cbda5b55d028ea84b199445129f9ba return '^2.2.0'; } return '^2.0.0'; } export async function extractYarnCatalogs( catalogs: YarnCatalogs, packageFile: string, hasPackageManager: boolean, ): Promise> { logger.trace(`yarn.extractYarnCatalogs(${packageFile})`); const yarnCatalogs = yarnCatalogsToArray(catalogs); const deps = extractCatalogDeps(yarnCatalogs, 'yarn'); let yarnLock: string | undefined; const filePath = getSiblingFileName(packageFile, 'yarn.lock'); if (await localPathExists(filePath)) { yarnLock = filePath; } return { deps, managerData: { yarnLock, hasPackageManager, }, }; } function yarnCatalogsToArray({ catalog: defaultCatalogDeps, catalogs: namedCatalogs, }: YarnCatalogs): Catalog[] { const result: Catalog[] = []; if (defaultCatalogDeps !== undefined) { result.push({ name: 'default', dependencies: defaultCatalogDeps }); } if (!namedCatalogs) { return result; } for (const [name, dependencies] of Object.entries(namedCatalogs)) { result.push({ name, dependencies, }); } return result; }