import { quote } from 'shlex'; import upath from 'upath'; import { TEMPORARY_ERROR } from '../../../constants/error-messages.ts'; import { logger } from '../../../logger/index.ts'; import { coerceArray } from '../../../util/array.ts'; import { exec } from '../../../util/exec/index.ts'; import type { ExecOptions } from '../../../util/exec/types.ts'; import { ensureCacheDir, getSiblingFileName, readLocalFile, writeLocalFile, } from '../../../util/fs/index.ts'; import { getRepoStatus } from '../../../util/git/index.ts'; import { newlineRegex, regEx } from '../../../util/regex.ts'; import type { UpdateArtifact, UpdateArtifactsResult } from '../types.ts'; const pluginRegex = regEx(`^\\s*plugin\\s*(['"])(?[^'"]+)(['"])`); function getPluginCommands(content: string): string[] { const result = new Set(); const lines: string[] = content.split(newlineRegex); lines.forEach((line) => { const match = pluginRegex.exec(line); if (match?.groups) { const { plugin } = match.groups; result.add(`gem install ${quote(plugin)}`); } }); return [...result]; } export async function updateArtifacts({ packageFileName, updatedDeps, newPackageFileContent, }: UpdateArtifact): Promise { logger.debug(`cocoapods.getArtifacts(${packageFileName})`); if (updatedDeps.length < 1) { logger.debug('CocoaPods: empty update - returning null'); return null; } const lockFileName = getSiblingFileName(packageFileName, 'Podfile.lock'); try { await writeLocalFile(packageFileName, newPackageFileContent); } catch (err) { logger.warn({ err }, 'Podfile could not be written'); return [ { artifactError: { fileName: lockFileName, stderr: err.message, }, }, ]; } const existingLockFileContent = await readLocalFile(lockFileName, 'utf8'); if (!existingLockFileContent) { logger.debug(`Lockfile not found: ${lockFileName}`); return null; } const match = regEx(/^COCOAPODS: (?.*)$/m).exec( existingLockFileContent, ); const cocoapods = match?.groups?.cocoapodsVersion ?? null; const cmd = [...getPluginCommands(newPackageFileContent), 'pod install']; const execOptions: ExecOptions = { cwdFile: packageFileName, extraEnv: { CP_HOME_DIR: await ensureCacheDir('cocoapods'), }, docker: {}, toolConstraints: [ { toolName: 'ruby', }, { toolName: 'cocoapods', constraint: cocoapods, }, ], }; try { await exec(cmd, execOptions); } catch (err) { // istanbul ignore if if (err.message === TEMPORARY_ERROR) { throw err; } return [ { artifactError: { fileName: lockFileName, stderr: err.stderr ?? err.stdout ?? err.message, }, }, ]; } const status = await getRepoStatus(); if (!status.modified.includes(lockFileName)) { return null; } logger.debug(`Returning updated lockfile: ${lockFileName}`); const lockFileContent = await readLocalFile(lockFileName); const res: UpdateArtifactsResult[] = [ { file: { type: 'addition', path: lockFileName, contents: lockFileContent, }, }, ]; const podsDir = upath.join(upath.dirname(packageFileName), 'Pods'); const podsManifestFileName = upath.join(podsDir, 'Manifest.lock'); if (await readLocalFile(podsManifestFileName, 'utf8')) { for (const f of status.modified.concat(status.not_added)) { if (f.startsWith(podsDir)) { res.push({ file: { type: 'addition', path: f, contents: await readLocalFile(f), }, }); } } for (const f of coerceArray(status.deleted)) { res.push({ file: { type: 'deletion', path: f, }, }); } } return res; }