import * as plugins from './plugins.js'; import { controllerPackageName } from '../ts_interfaces/index.js'; import { UpgradeCoordinator, upgradePackageTransitionSource, upgradePackageTransitionTarget, upgradePackageTransitionTransactionVersion, upgradeTransactionIsStalled, } from './classes.upgradecoordinator.js'; import { recoverStalledUpgrade, type IPnpmGlobalInstallation, } from './classes.upgradetransaction.js'; const currentCliName = String(controllerPackageName) === upgradePackageTransitionSource.packageName ? upgradePackageTransitionSource.cliName : upgradePackageTransitionTarget.cliName; export const followUpgradeTransaction = async ( coordinatorArg: UpgradeCoordinator, tokenArg: string, logFilePathArg: string, streamLogArg: boolean, installationArg: IPnpmGlobalInstallation, ): Promise>> => { let offset = 0; let activeLogFilePath = logFilePathArg; let lastRevision = -1; let workerMissingSince: number | undefined; while (true) { const transaction = await coordinatorArg.readTransaction(tokenArg); const transactionCliName = transaction.version === upgradePackageTransitionTransactionVersion ? transaction.sourceCliName : currentCliName; const retainedLogFilePath = transaction.worker?.logFilePath; if (retainedLogFilePath && retainedLogFilePath !== activeLogFilePath) { activeLogFilePath = retainedLogFilePath; offset = 0; if (streamLogArg) { process.stdout.write( `${transactionCliName} upgrade: recovery worker logs: ${activeLogFilePath}\n`, ); } } if (streamLogArg) { const handle = await plugins.fs.promises.open( activeLogFilePath, plugins.fs.constants.O_RDONLY | plugins.fs.constants.O_NOFOLLOW, ); try { const stats = await handle.stat(); if (!stats.isFile() || stats.size < offset) { throw new Error(`The ${transactionCliName} upgrade log changed unexpectedly.`); } if (stats.size > offset) { const bytes = Buffer.alloc(Math.min(stats.size - offset, 64 * 1024)); await handle.read(bytes, 0, bytes.byteLength, offset); offset += bytes.byteLength; if (!process.stdout.write(bytes)) { await new Promise((resolve) => process.stdout.once('drain', resolve)); } } } finally { await handle.close(); } } if (transaction.revision !== lastRevision) { lastRevision = transaction.revision; if (streamLogArg && transaction.message) { process.stdout.write(`${transactionCliName} upgrade: ${transaction.message}\n`); } } if (transaction.terminal) return transaction; if (upgradeTransactionIsStalled(transaction)) { const recovered = await recoverStalledUpgrade({ coordinator: coordinatorArg, token: tokenArg, transaction, installation: installationArg, command: 'upgrade', }); if (!recovered) await new Promise((resolve) => setTimeout(resolve, 5_000)); continue; } if (transaction.worker) { if (await coordinatorArg.transactionWorkerIsLive(transaction)) { workerMissingSince = undefined; } else { workerMissingSince ??= Date.now(); if (Date.now() - workerMissingSince > 5_000) { const recovered = await recoverStalledUpgrade({ coordinator: coordinatorArg, token: tokenArg, transaction, installation: installationArg, command: 'upgrade', }); if (!recovered) await new Promise((resolve) => setTimeout(resolve, 5_000)); continue; } } } else { workerMissingSince ??= Date.now(); if (Date.now() - workerMissingSince > 5_000) { return await coordinatorArg.finishTransaction( tokenArg, false, 'The upgrade worker did not establish durable ownership.', 'No upgrade worker identity was retained before the launch deadline.', ); } } await new Promise((resolve) => setTimeout(resolve, 100)); } };