Release ForgeFlow 0.6.1
This commit is contained in:
+39
-12
@@ -31,28 +31,52 @@ async function readJsonFile(filePath) {
|
||||
catch { return null; }
|
||||
}
|
||||
|
||||
async function readLogTail(filePath, maxLines = 12) {
|
||||
if (!filePath) return '';
|
||||
try {
|
||||
const text = await fs.readFile(filePath, 'utf8');
|
||||
return text.split(/\r?\n/).filter(Boolean).slice(-maxLines).join('\n');
|
||||
} catch { return ''; }
|
||||
}
|
||||
|
||||
async function updaterStartupError(message, code, { statusPath, logPath, expectedUpdateId } = {}) {
|
||||
const status = statusPath ? await readJsonFile(statusPath) : null;
|
||||
const logTail = await readLogTail(logPath);
|
||||
const details = [];
|
||||
if (status?.updateId && expectedUpdateId && status.updateId !== expectedUpdateId) details.push('The helper wrote a status for a different update request.');
|
||||
if (status?.message) details.push(status.message);
|
||||
if (logTail) details.push(`Update helper log:\n${logTail}`);
|
||||
const error = new Error([message, ...details].filter(Boolean).join('\n\n'));
|
||||
error.code = code;
|
||||
error.status = status;
|
||||
error.logPath = logPath || null;
|
||||
return error;
|
||||
}
|
||||
|
||||
async function waitForUpdaterStarted(statusPath, {
|
||||
timeoutMs = 12000,
|
||||
timeoutMs = 15000,
|
||||
pollMs = 100,
|
||||
childState = null
|
||||
childState = null,
|
||||
expectedUpdateId = null,
|
||||
logPath = null
|
||||
} = {}) {
|
||||
const deadline = Date.now() + timeoutMs;
|
||||
while (Date.now() < deadline) {
|
||||
const status = await readJsonFile(statusPath);
|
||||
if (status && ['started', 'waiting-for-exit', 'backing-up', 'extracting', 'applying', 'validating'].includes(status.state)) {
|
||||
const belongsToRequest = !expectedUpdateId || status?.updateId === expectedUpdateId;
|
||||
if (status && belongsToRequest && ['started', 'waiting-for-exit', 'backing-up', 'extracting', 'applying', 'validating'].includes(status.state)) {
|
||||
return status;
|
||||
}
|
||||
if (status && belongsToRequest && ['failed', 'rolled-back'].includes(status.state)) {
|
||||
throw await updaterStartupError('The update helper reported a failure before ForgeFlow could close.', 'UPDATE_HELPER_START_FAILED', { statusPath, logPath, expectedUpdateId });
|
||||
}
|
||||
if (childState?.error) throw childState.error;
|
||||
if (childState?.exited) {
|
||||
const error = new Error(`The update helper exited before it confirmed startup (exit code ${childState.code ?? 'unknown'}).`);
|
||||
error.code = 'UPDATE_HELPER_EXITED_EARLY';
|
||||
throw error;
|
||||
throw await updaterStartupError(`The update helper exited before it confirmed startup (exit code ${childState.code ?? 'unknown'}).`, 'UPDATE_HELPER_EXITED_EARLY', { statusPath, logPath, expectedUpdateId });
|
||||
}
|
||||
await delay(pollMs);
|
||||
}
|
||||
const error = new Error('The update helper did not confirm startup. ForgeFlow was left open and no source files were changed.');
|
||||
error.code = 'UPDATE_HELPER_START_TIMEOUT';
|
||||
throw error;
|
||||
throw await updaterStartupError('The update helper did not confirm startup. ForgeFlow was left open and no source files were changed.', 'UPDATE_HELPER_START_TIMEOUT', { statusPath, logPath, expectedUpdateId });
|
||||
}
|
||||
|
||||
class UpdateService {
|
||||
@@ -227,12 +251,14 @@ class UpdateService {
|
||||
if (!child.once) finish(resolve);
|
||||
});
|
||||
|
||||
child.unref?.();
|
||||
const started = await waitForUpdaterStarted(statusPath, {
|
||||
timeoutMs: this.handshakeTimeoutMs,
|
||||
pollMs: this.handshakePollMs,
|
||||
childState
|
||||
childState,
|
||||
expectedUpdateId: updateId,
|
||||
logPath
|
||||
});
|
||||
child.unref?.();
|
||||
|
||||
await this.diagnostics?.info('updates.apply-started', {
|
||||
updateId,
|
||||
@@ -281,5 +307,6 @@ module.exports = {
|
||||
safeRepositoryPart,
|
||||
resolveWindowsPowerShellPath,
|
||||
waitForUpdaterStarted,
|
||||
readJsonFile
|
||||
readJsonFile,
|
||||
readLogTail
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user