Release ForgeFlow 0.6.0
This commit is contained in:
+59
-5
@@ -64,7 +64,32 @@ function registerIpc({ store, git, gitea, repositories, deployments, unraid, ssh
|
||||
const withRepositoryMutation = async (localPath, action) => {
|
||||
const key = path.resolve(localPath);
|
||||
const previous = repositoryMutations.get(key) || Promise.resolve();
|
||||
const current = previous.catch(() => {}).then(() => withRepositoryPause(key, action));
|
||||
const execute = async () => {
|
||||
try { return await withRepositoryPause(key, action); }
|
||||
catch (error) {
|
||||
if (!git.isGitLockError(error)) throw error;
|
||||
let repair = null;
|
||||
let lockDiagnosis = null;
|
||||
try {
|
||||
repair = await git.repairStaleGitLocks(key, { minimumAgeMs: 2_000 });
|
||||
} catch (repairError) {
|
||||
lockDiagnosis = repairError;
|
||||
if (repairError?.code === 'GIT_LOCKS_RECENT') {
|
||||
await new Promise((resolve) => setTimeout(resolve, 2_500));
|
||||
try {
|
||||
repair = await git.repairStaleGitLocks(key, { minimumAgeMs: 2_000 });
|
||||
lockDiagnosis = null;
|
||||
} catch (retryError) {
|
||||
lockDiagnosis = retryError;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!repair?.repaired) throw lockDiagnosis || error;
|
||||
await diagnostics.info('git.lock.auto-repaired', { localPath: key, locks: repair.removed.map((item) => item.name) });
|
||||
return withRepositoryPause(key, action);
|
||||
}
|
||||
};
|
||||
const current = previous.catch(() => {}).then(execute);
|
||||
repositoryMutations.set(key, current);
|
||||
try { return await current; }
|
||||
finally { if (repositoryMutations.get(key) === current) repositoryMutations.delete(key); }
|
||||
@@ -139,7 +164,8 @@ function registerIpc({ store, git, gitea, repositories, deployments, unraid, ssh
|
||||
platform: process.platform,
|
||||
state: store.getPublicState(),
|
||||
git: await git.isAvailable(),
|
||||
diagnostics: await diagnostics.getStatus()
|
||||
diagnostics: await diagnostics.getStatus(),
|
||||
updateResult: await updates.consumeLatestResult()
|
||||
}));
|
||||
|
||||
register('dialog:select-directory', async ({ title = 'Select folder', defaultPath }) => {
|
||||
@@ -156,6 +182,16 @@ function registerIpc({ store, git, gitea, repositories, deployments, unraid, ssh
|
||||
return result.canceled ? null : result.filePaths[0];
|
||||
});
|
||||
|
||||
register('dialog:select-image-file', async ({ title = 'Select PNG image', defaultPath }) => {
|
||||
const result = await dialog.showOpenDialog({
|
||||
title,
|
||||
defaultPath,
|
||||
properties: ['openFile'],
|
||||
filters: [{ name: 'PNG image', extensions: ['png'] }]
|
||||
});
|
||||
return result.canceled ? null : result.filePaths[0];
|
||||
});
|
||||
|
||||
register('setup:preflight', ({ baseUrl, token, roots }) => preflight.runSystem({ baseUrl, token, roots }));
|
||||
register('setup:validate-gitea', ({ baseUrl, token }) => gitea.validateConnection(baseUrl, token));
|
||||
register('setup:complete', async ({ baseUrl, token, workspaceRoots }) => {
|
||||
@@ -201,7 +237,8 @@ function registerIpc({ store, git, gitea, repositories, deployments, unraid, ssh
|
||||
register('updates:download', () => updates.download());
|
||||
register('updates:apply', async () => {
|
||||
const result = await updates.apply();
|
||||
setTimeout(() => app.quit(), 650).unref?.();
|
||||
if (!result?.confirmed) throw new Error('The update helper did not confirm ownership of the update. ForgeFlow will remain open.');
|
||||
setTimeout(() => app.quit(), 350).unref?.();
|
||||
return result;
|
||||
});
|
||||
|
||||
@@ -286,7 +323,11 @@ function registerIpc({ store, git, gitea, repositories, deployments, unraid, ssh
|
||||
register('repository:stash-list', async ({ localPath }) => git.stashList(await assertKnownRepositoryPath(localPath)));
|
||||
register('repository:stash-pop', async ({ localPath, ref }) => { const safePath = await assertKnownRepositoryPath(localPath); return withRepositoryMutation(safePath, () => git.popStash(safePath, ref)); });
|
||||
register('repository:index-lock', async ({ localPath }) => git.getIndexLockInfo(await assertKnownRepositoryPath(localPath)));
|
||||
register('repository:git-recovery-status', async ({ localPath }) => git.reconcile(await assertKnownRepositoryPath(localPath)));
|
||||
register('repository:repair-index-lock', async ({ localPath }) => { const safePath = await assertKnownRepositoryPath(localPath); return withRepositoryMutation(safePath, () => git.removeStaleIndexLock(safePath)); });
|
||||
register('repository:repair-git-locks', async ({ localPath, force = false }) => { const safePath = await assertKnownRepositoryPath(localPath); return withRepositoryMutation(safePath, () => git.repairStaleGitLocks(safePath, { minimumAgeMs: force ? 0 : 10_000, allowWithoutProcessProbe: force === true })); });
|
||||
register('repository:reconcile', async ({ localPath }) => git.reconcile(await assertKnownRepositoryPath(localPath)));
|
||||
register('repository:repair-sync', async ({ localPath, strategy }) => { const safePath = await assertKnownRepositoryPath(localPath); return withRepositoryMutation(safePath, () => git.repairSync(safePath, strategy)); });
|
||||
register('repository:set-origin', async ({ localPath, remoteUrl }) => { const safePath = await assertKnownRepositoryPath(localPath); return withRepositoryMutation(safePath, () => git.setRemoteUrl(safePath, remoteUrl)); });
|
||||
|
||||
register('repositories:normalize-origins', async () => {
|
||||
@@ -371,13 +412,26 @@ function registerIpc({ store, git, gitea, repositories, deployments, unraid, ssh
|
||||
if (profile?.provider === 'ssh-unraid') return unraid.refreshProfileState(fullName, profileId);
|
||||
return deployments.refreshProfileState(fullName, profileId);
|
||||
});
|
||||
register('deployment:apply-dockerman-metadata', async ({ repository, profileId }) => {
|
||||
const current = await resolveRepository(repository);
|
||||
return unraid.applyDockerManMetadata({ repository: current, profileId });
|
||||
});
|
||||
register('deployment:reconcile', async ({ fullName, profileId }) => {
|
||||
const profile = store.getDeploymentProfile(fullName, profileId);
|
||||
if (profile?.provider !== 'ssh-unraid') return deployments.refreshProfileState(fullName, profileId);
|
||||
const state = await unraid.refreshProfileState(fullName, profileId);
|
||||
const operations = store.data.operations.filter((item) => item.profileId === profileId && item.provider === 'ssh-unraid' && !['success', 'failed', 'cancelled', 'rolled-back'].includes(item.status));
|
||||
for (const operation of operations) await unraid.refreshOperation(operation.id);
|
||||
return { state, operations: store.data.operations.filter((item) => item.profileId === profileId).slice(0, 10) };
|
||||
});
|
||||
register('operations:refresh', async ({ operationId }) => {
|
||||
if (operationId) {
|
||||
const operation = store.getOperation(operationId);
|
||||
if (operation?.provider === 'ssh-unraid') return operation;
|
||||
if (operation?.provider === 'ssh-unraid') return unraid.refreshOperation(operationId);
|
||||
return deployments.refreshOperation(operationId);
|
||||
}
|
||||
return deployments.refreshActiveOperations();
|
||||
const [actions, sshOperations] = await Promise.all([deployments.refreshActiveOperations(), unraid.refreshActiveOperations()]);
|
||||
return [...actions, ...sshOperations];
|
||||
});
|
||||
register('operations:get', ({ operationId }) => store.getOperation(operationId));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user