refactor: split renderer ipc and unraid domains

This commit is contained in:
NuklearRabbit
2026-07-29 18:11:07 +02:00
parent 5d3731a853
commit 7b05c953b6
36 changed files with 9316 additions and 8330 deletions
+14 -744
View File
@@ -1,5 +1,4 @@
"use strict";
const path = require("node:path");
const fs = require("node:fs/promises");
const { fileURLToPath } = require("node:url");
@@ -9,12 +8,14 @@ const {
cloneDirectoryName,
resolveCloneTarget,
} = require("../shared/clone-target.cjs");
const { registerRepositoryIpc } = require("./ipc/repository-handlers.cjs");
const { registerDeploymentIpc } = require("./ipc/deployment-handlers.cjs");
const { registerOperationsIpc } = require("./ipc/operations-handlers.cjs");
const {
createEncryptedBackup,
readEncryptedBackup,
} = require("./configuration-backup.cjs");
const { evaluateDeploymentPolicy } = require("../shared/deployment-policy.cjs");
let diagnosticsService = null;
const TRUSTED_RENDERER_PATH = path.resolve(
__dirname,
@@ -76,7 +77,6 @@ function register(channel, handler) {
}
});
}
function registerIpc({
store,
git,
@@ -502,383 +502,11 @@ function registerIpc({
}),
);
register("repositories:refresh", async () => {
const result = await repositories.refresh();
monitor?.setPaths(repositories.getWatchPaths());
return result;
});
register("repositories:discover", async ({ roots }) => {
const paths = await repositories.discoverAll(
roots || store.data.workspaceRoots,
);
return repositories.getLocalDescriptors(paths);
});
register("repository:favorite", async ({ fullName, favorite }) =>
store.setFavorite(fullName, favorite),
);
register("repository:link", async ({ fullName, localPath }) => {
await git.ensureRepository(localPath);
const remoteUrl = await git.getRemoteUrl(localPath).catch(() => "");
if (
!remoteUrl ||
!matchRemoteToRepository(remoteUrl, [{ full_name: fullName }])
) {
throw new Error(
`The selected folder's origin does not match ${fullName}.`,
);
}
await store.saveMapping(fullName, localPath);
await diagnostics.info("repository.linked", { fullName, localPath });
const result = await repositories.refresh();
monitor?.setPaths(repositories.getWatchPaths());
return result;
});
register("repository:unlink", async ({ fullName }) => {
await store.removeMapping(fullName);
await diagnostics.info("repository.unlinked", { fullName });
const result = await repositories.refresh();
monitor?.setPaths(repositories.getWatchPaths());
return result;
});
register("repository:status", async ({ localPath }) =>
git.status(await assertKnownRepositoryPath(localPath)),
);
register("repository:diff", async ({ localPath, filePath, staged }) =>
git.diff(await assertKnownRepositoryPath(localPath), filePath, staged),
);
register("repository:diff-hunks", async ({ localPath, filePath }) =>
git.diffHunks(await assertKnownRepositoryPath(localPath), filePath),
);
register(
"repository:stage-hunks",
async ({ localPath, filePath, hunkIndexes }) => {
const safePath = await assertKnownRepositoryPath(localPath);
return withRepositoryMutation(safePath, () =>
git.stageHunks(safePath, filePath, hunkIndexes),
);
},
);
register("repository:conflicts", async ({ localPath }) =>
git.conflictState(await assertKnownRepositoryPath(localPath)),
);
register(
"repository:resolve-conflict",
async ({ localPath, filePath, resolution }) => {
const safePath = await assertKnownRepositoryPath(localPath);
const result = await withRepositoryMutation(safePath, () =>
git.resolveConflict(safePath, filePath, resolution),
);
await audit.append("git.conflict.resolved", {
localPath: safePath,
filePath,
resolution,
});
return result;
},
);
register("repository:continue-operation", async ({ localPath }) => {
const safePath = await assertKnownRepositoryPath(localPath);
const result = await withRepositoryMutation(safePath, () =>
git.continueInterruptedOperation(safePath),
);
await audit.append("git.operation.continued", { localPath: safePath });
return result;
});
register("repository:abort-operation", async ({ localPath }) => {
const safePath = await assertKnownRepositoryPath(localPath);
const result = await withRepositoryMutation(safePath, () =>
git.abortInterruptedOperation(safePath),
);
await audit.append("git.operation.aborted", {
localPath: safePath,
operation: result.aborted,
});
return result;
});
register("repository:stage", async ({ localPath, files }) => {
const safePath = await assertKnownRepositoryPath(localPath);
return withRepositoryMutation(safePath, () => git.stage(safePath, files));
});
register("repository:unstage", async ({ localPath, files }) => {
const safePath = await assertKnownRepositoryPath(localPath);
return withRepositoryMutation(safePath, () => git.unstage(safePath, files));
});
register("repository:commit", async ({ localPath, message, files }) => {
const safePath = await assertKnownRepositoryPath(localPath);
return withRepositoryMutation(safePath, () =>
git.commit(safePath, message, files),
);
});
register("repository:commit-staged", async ({ localPath, message }) => {
const safePath = await assertKnownRepositoryPath(localPath);
return withRepositoryMutation(safePath, () =>
git.commitStaged(safePath, message),
);
});
register("repository:commit-staged-push", async ({ localPath, message }) => {
const safePath = await assertKnownRepositoryPath(localPath);
return withRepositoryMutation(safePath, () =>
git.commitStagedAndPush(safePath, message),
);
});
register("repository:commit-push", async ({ localPath, message, files }) => {
const safePath = await assertKnownRepositoryPath(localPath);
return withRepositoryMutation(safePath, () =>
git.commitAndPush(safePath, message, files),
);
});
register("repository:push", async ({ localPath }) => {
const safePath = await assertKnownRepositoryPath(localPath);
return withRepositoryMutation(safePath, () => git.push(safePath));
});
register("repository:fetch", async ({ localPath }) => {
const safePath = await assertKnownRepositoryPath(localPath);
return withRepositoryMutation(safePath, () => git.fetch(safePath));
});
register("repository:pull", async ({ localPath }) => {
const safePath = await assertKnownRepositoryPath(localPath);
return withRepositoryMutation(safePath, () =>
git.pullFastForward(safePath),
);
});
register("repository:history", async ({ localPath, limit }) =>
git.history(await assertKnownRepositoryPath(localPath), limit),
);
register("repository:branch-protection", async ({ fullName, branch }) => {
const repository = await resolveRepository({ fullName });
return gitea.getBranchProtection(
repository.owner.login,
repository.name,
branch ||
repository.localStatus?.branch?.head ||
repository.defaultBranch,
);
});
register("repository:pull-requests", async ({ fullName, state = "open" }) => {
const repository = await resolveRepository({ fullName });
return gitea.listPullRequests({
owner: repository.owner.login,
repo: repository.name,
state,
});
});
register(
"repository:create-pull-request",
async ({ fullName, title, body, base }) => {
const repository = await resolveRepository({ fullName });
if (!repository.localPath || !repository.localStatus?.clean)
throw new Error(
"A clean linked repository is required before creating a pull request.",
);
const head = repository.localStatus.branch?.head;
if (!head || !repository.localStatus.branch?.upstream)
throw new Error(
"Publish the current branch before creating a pull request.",
);
if (repository.localStatus.branch.ahead > 0)
throw new Error(
"Push all local commits before creating a pull request.",
);
const pullRequest = await gitea.createPullRequest({
owner: repository.owner.login,
repo: repository.name,
head,
base: base || repository.defaultBranch,
title,
body,
});
await audit.append("pull-request.created", {
repository: repository.fullName,
number: pullRequest.number,
head,
base: base || repository.defaultBranch,
url: pullRequest.html_url,
});
return pullRequest;
},
);
register("repository:branches", async ({ localPath }) =>
git.branches(await assertKnownRepositoryPath(localPath)),
);
register("repository:checkout-branch", async ({ localPath, branch }) => {
const safePath = await assertKnownRepositoryPath(localPath);
return withRepositoryMutation(safePath, () =>
git.checkoutBranch(safePath, branch),
);
});
register("repository:create-branch", async ({ localPath, branch }) => {
const safePath = await assertKnownRepositoryPath(localPath);
return withRepositoryMutation(safePath, () =>
git.createBranch(safePath, branch),
);
});
register("repository:stash", async ({ localPath, message }) => {
const safePath = await assertKnownRepositoryPath(localPath);
return withRepositoryMutation(safePath, () => git.stash(safePath, message));
});
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 () => {
const current = await repositories.refresh();
const changes = [];
for (const repository of current) {
if (!repository.localPath || !repository.sshUrl) continue;
const actual = await git
.getRemoteUrl(repository.localPath)
.catch(() => "");
if (actual === repository.sshUrl) continue;
await withRepositoryMutation(repository.localPath, () =>
git.setRemoteUrl(repository.localPath, repository.sshUrl),
);
changes.push({
fullName: repository.fullName,
previous: actual,
next: repository.sshUrl,
});
}
const refreshed = await repositories.refresh();
monitor?.setPaths(repositories.getWatchPaths());
await diagnostics.info("repositories.origins.normalized", {
count: changes.length,
changes,
});
return { changes, repositories: refreshed };
});
register("repository:clone", async ({ fullName, mode = "default" }) => {
if (!["default", "custom"].includes(mode))
throw new Error("Unsupported clone location mode.");
let projectRoot = store.data.workspaceRoots[0] || null;
if (mode === "custom" || !projectRoot) {
const result = await dialog.showOpenDialog({
title: `Choose a project root for ${String(fullName || "repository")}`,
defaultPath: projectRoot || undefined,
buttonLabel: "Use this project root",
properties: ["openDirectory", "createDirectory"],
});
if (result.canceled || !result.filePaths[0]) return { cancelled: true };
projectRoot = result.filePaths[0];
}
return cloneRepositoryInto(fullName, projectRoot);
});
register("repository:open-path", async ({ localPath }) => {
const safePath = await assertKnownRepositoryPath(localPath);
const error = await shell.openPath(safePath);
if (error) throw new Error(error);
return true;
});
register(
"repository:open-editor",
async ({ localPath, filePath = "", line = 1 }) =>
externalTools.launch(
"editor",
await assertKnownRepositoryPath(localPath),
filePath,
line,
),
);
register("repository:open-terminal", async ({ localPath }) =>
externalTools.launch(
"terminal",
await assertKnownRepositoryPath(localPath),
),
);
register("external:open", async ({ url }) => {
const parsed = new URL(url);
if (!["http:", "https:"].includes(parsed.protocol))
throw new Error("Only HTTP and HTTPS links can be opened.");
await shell.openExternal(parsed.toString());
return true;
});
register("git-validator:scan", async ({ fullName }) => {
const repository = await resolveRepository({ fullName });
const report = await gitValidator.scan(repository);
await diagnostics.info("git-validator.scan.completed", {
repository: repository.fullName,
score: report.score,
summary: report.summary,
});
return report;
});
register("git-validator:repair", async ({ fullName, check }) => {
const repository = await resolveRepository({ fullName });
const allowed = new Set([
"align-origin",
"configure-local-safety",
"add-gitignore",
"add-gitattributes",
"add-editorconfig",
"protect-default-branch",
]);
if (!allowed.has(check?.fixAction))
throw new Error("Unsupported Git Validator repair request.");
const result = await gitValidator.repair(repository, check);
await audit.append("git-validator.repair", {
repository: repository.fullName,
checkId: check.id,
action: check.fixAction,
});
await diagnostics.info("git-validator.repair.completed", {
repository: repository.fullName,
checkId: check.id,
action: check.fixAction,
});
return result;
registerRepositoryIpc({
register, repositories, store, git, gitea, monitor, diagnostics, audit,
externalTools, gitValidator, withRepositoryMutation, assertKnownRepositoryPath,
resolveRepository, cloneRepositoryInto, cloneDirectoryName,
matchRemoteToRepository, shell, dialog,
});
register("troubleshooter:scan", async ({ fullName = null }) => {
@@ -1102,371 +730,13 @@ function registerIpc({
return results;
});
register("deployment:save-profile", async ({ fullName, profile }) => {
const saved = await store.saveDeploymentProfile(fullName, profile);
await diagnostics.info("deployment.profile.saved", {
repository: fullName,
profile: saved,
});
return { profile: saved, state: store.getPublicState() };
registerDeploymentIpc({
register, store, resolveRepository, unraid, deployments, evaluateDeploymentPolicy,
audit, deployKeys, repositories, inventoryReviews, diagnostics, git, gitea, ssh,
});
register("deployment:delete-profile", async ({ fullName, profileId }) => {
const profiles = await store.deleteDeploymentProfile(fullName, profileId);
await diagnostics.info("deployment.profile.deleted", {
repository: fullName,
profileId,
});
return { profiles, state: store.getPublicState() };
});
register("deployment:preflight", async ({ repository, profileId }) => {
const current = await resolveRepository(repository);
const profile = store.getDeploymentProfile(current.fullName, profileId);
if (profile?.provider === "ssh-unraid")
return unraid.preflight({ repository: current, profileId });
return preflight.runDeployment({ repository: current, profileId });
});
register("deployment:repair-write-access", async ({ repository, profileId }) => {
const current = await resolveRepository(repository);
const profile = store.getDeploymentProfile(current.fullName, profileId);
if (profile?.provider !== "ssh-unraid")
throw new Error("Write-access repair is available only for SSH / Unraid deployment profiles.");
const result = await unraid.repairWriteAccess({ repository: current, profileId });
await audit.append("deployment.write-access.repaired", {
repository: current.fullName,
profileId,
changed: result.changed,
remotePath: result.after?.remotePath || result.before?.remotePath || null,
});
return result;
});
register(
"deployment:dispatch",
async ({
repository,
profileId,
sha,
note = "",
override = false,
overrideReason = "",
}) => {
const current = await resolveRepository(repository);
const profile = store.getDeploymentProfile(current.fullName, profileId);
const policy = evaluateDeploymentPolicy(profile, {
note,
override,
reason: overrideReason,
});
await audit.append("deployment.requested", {
repository: current.fullName,
profileId,
sha,
note: policy.note,
overridden: policy.overridden,
overrideReason: policy.reason,
});
const operation =
profile?.provider === "ssh-unraid"
? await unraid.deploy({ repository: current, profileId, sha })
: await deployments.deploy({ repository: current, profileId, sha });
if (operation?.id)
await store.addOperation({
...operation,
releaseNote: policy.note,
policyOverride: policy.overridden
? { reason: policy.reason, violations: policy.violations }
: null,
});
return operation;
},
);
register(
"deployment:rollback",
async ({ repository, profileId, targetSha }) => {
const current = await resolveRepository(repository);
const profile = store.getDeploymentProfile(current.fullName, profileId);
if (profile?.provider === "ssh-unraid")
return unraid.rollback({ repository: current, profileId, targetSha });
return deployments.rollback({
repository: current,
profileId,
targetSha,
});
},
);
register("deployment:health", ({ url }) => deployments.checkHealth(url));
register("deployment:link-server-workload", async ({ repository, serverId, workloadId, deploymentMode = "server-git", remoteFolder = "" }) => {
const current = await resolveRepository(repository);
const result = await unraid.linkServerWorkload({
repository: current,
serverId,
workloadId,
deploymentMode,
remoteFolder,
});
return { ...result, state: store.getPublicState() };
});
register("deployment:configure-server-git-access", async ({ repository, profileId }) => {
const current = await resolveRepository(repository);
const result = await unraid.configureServerGitAccess({ repository: current, profileId });
await audit.append("deployment.server-git-access-configured", {
repository: current.fullName,
profileId,
keyFingerprint: result.keyFingerprint,
hostFingerprint: result.hostFingerprint,
});
return { ...result, state: store.getPublicState() };
});
register("deployment:verify-server-git-profile", async ({ repository, profileId }) => {
const current = await resolveRepository(repository);
const result = await unraid.verifyServerGitProfile({ repository: current, profileId });
await audit.append("deployment.server-git-access-verified", {
repository: current.fullName,
profileId,
readiness: result.readiness,
ready: result.ready,
checkedAt: result.checkedAt,
});
return result;
});
register("deployment:deploy-key-inventory", async ({ repository, profileId }) => {
const current = await resolveRepository(repository);
return deployKeys.inventory({ repository: current, profileId });
});
register("deployment:plan-deploy-key-rotation", async ({ repository, profileId }) => {
const current = await resolveRepository(repository);
return deployKeys.planRotation({ repository: current, profileId });
});
register("deployment:apply-deploy-key-rotation", async ({ repository, profileId, planId }) => {
const current = await resolveRepository(repository);
const result = await deployKeys.rotate({ repository: current, profileId, expectedPlanId: planId });
return { ...result, state: store.getPublicState() };
});
register("deployment:plan-deploy-key-revocation", async ({ repository, profileId }) => {
const current = await resolveRepository(repository);
return deployKeys.planRevocation({ repository: current, profileId });
});
register("deployment:apply-deploy-key-revocation", async ({ repository, profileId, planId }) => {
const current = await resolveRepository(repository);
const result = await deployKeys.revoke({ repository: current, profileId, expectedPlanId: planId });
return { ...result, state: store.getPublicState() };
});
register("deployment:restore-deploy-key", async ({ repository, profileId }) => {
const current = await resolveRepository(repository);
const result = await deployKeys.restore({ repository: current, profileId });
return { ...result, state: store.getPublicState() };
});
register("deployment:discover-server-workloads", async () => {
const repositoryList = await repositories.refresh();
const remoteRepositories = repositoryList.filter(
(repository) => repository.owner?.login !== "local",
);
const results = [];
for (const server of store.data.servers || []) {
try {
results.push(
await unraid.discoverServerWorkloads(server.id, remoteRepositories),
);
} catch (error) {
results.push({
serverId: server.id,
serverName: server.name,
detected: 0,
adopted: 0,
verified: 0,
linked: 0,
unmatched: 0,
needsReview: 0,
capabilities: {},
warnings: [],
workloads: [],
error: error.message,
});
}
}
return results;
});
register("deployment:plan-server-reconciliation", async ({ serverId }) => {
const repositoryList = await repositories.refresh();
const remoteRepositories = repositoryList.filter((repository) => repository.owner?.login !== "local");
const result = await unraid.planServerInventoryReconciliation(serverId, remoteRepositories, { autoLink: true });
await audit.append("deployment.server-reconciliation-planned", {
serverId,
planId: result.plan.id,
summary: result.plan.summary,
});
return result;
});
register("deployment:apply-server-reconciliation", async ({ serverId, planId }) => {
const repositoryList = await repositories.refresh();
const remoteRepositories = repositoryList.filter((repository) => repository.owner?.login !== "local");
const result = await unraid.reconcileServerInventory(serverId, remoteRepositories, { autoLink: true, expectedPlanId: planId });
await audit.append("deployment.server-reconciliation-applied", {
serverId,
planId,
adopted: result.adopted,
refreshed: result.refreshed,
retired: result.retired,
recoverySnapshot: result.recoverySnapshot?.filePath || null,
});
return { ...result, state: store.getPublicState() };
});
register("deployment:plan-inventory-review", async ({ serverId, workloadId, action, reason = "", repositoryFullName = null }) => {
const repositoryList = await repositories.refresh();
const inventory = await unraid.scanServerInventory(serverId, repositoryList.filter((item) => item.owner?.login !== "local"));
const workload = inventory.workloads.find((item) => item.workloadId === workloadId);
if (!workload) throw Object.assign(new Error("The workload changed or disappeared. Rescan before reviewing it."), { code: "INVENTORY_REVIEW_WORKLOAD_STALE" });
return inventoryReviews.preview({ serverId, workload, action, reason, repositoryFullName });
});
register("deployment:apply-inventory-review", async ({ serverId, workloadId, action, reason = "", repositoryFullName = null, planId }) => {
const repositoryList = await repositories.refresh();
const inventory = await unraid.scanServerInventory(serverId, repositoryList.filter((item) => item.owner?.login !== "local"));
const workload = inventory.workloads.find((item) => item.workloadId === workloadId);
if (!workload) throw Object.assign(new Error("The workload changed or disappeared. Rescan before applying the review."), { code: "INVENTORY_REVIEW_WORKLOAD_STALE" });
const plan = inventoryReviews.preview({ serverId, workload, action, reason, repositoryFullName });
const result = await inventoryReviews.apply({ plan, expectedPlanId: planId });
return { ...result, inventory: await unraid.scanServerInventory(serverId, repositoryList.filter((item) => item.owner?.login !== "local")), state: store.getPublicState() };
});
register("deployment:profile-state", async ({ fullName, profileId }) => {
const profile = store.getDeploymentProfile(fullName, profileId);
if (profile?.provider === "ssh-unraid") {
let giteaSha = null;
try {
const [owner, repo] = String(fullName || "").split("/");
const branch = await gitea.getBranch(owner, repo, profile.branch);
giteaSha = branch?.commit?.id || branch?.commit?.sha || null;
} catch {}
return unraid.refreshProfileState(fullName, profileId, giteaSha);
}
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 [owner, repo] = String(fullName || "").split("/");
const branch = await gitea.getBranch(owner, repo, profile.branch);
const giteaSha =
branch?.commit?.id ||
branch?.commit?.sha ||
branch?.commit?.commit?.id ||
null;
const state = await unraid.refreshProfileState(
fullName,
profileId,
giteaSha,
);
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: await unraid.reconcileRecordedOperations(profileId, state),
};
});
register("operations:refresh", async ({ operationId }) => {
if (operationId) {
const operation = store.getOperation(operationId);
if (operation?.provider === "ssh-unraid")
return unraid.refreshOperation(operationId);
return deployments.refreshOperation(operationId);
}
const [actions, sshOperations] = await Promise.all([
deployments.refreshActiveOperations(),
unraid.refreshActiveOperations(),
]);
return [...actions, ...sshOperations];
});
register("operations:get", ({ operationId }) =>
store.getOperation(operationId),
);
register("diagnostics:status", () => diagnostics.getStatus());
register("diagnostics:clear", () => diagnostics.clear());
register("diagnostics:open-folder", async () => {
const error = await shell.openPath(diagnostics.logDirectory);
if (error) throw new Error(error);
return true;
});
register("diagnostics:export", async ({ privacyMode = "standard" }) => {
if (!["standard", "strict"].includes(privacyMode))
throw new Error("Unsupported diagnostic privacy mode.");
const result = await dialog.showSaveDialog({
title: "Export ForgeFlow diagnostic bundle",
defaultPath: path.join(
app.getPath("downloads"),
`ForgeFlow-Diagnostics-${new Date().toISOString().replace(/[:.]/g, "-")}.zip`,
),
filters: [{ name: "ZIP archive", extensions: ["zip"] }],
});
if (result.canceled || !result.filePath) return null;
const repositoryState = await repositories.refresh().catch((error) => {
diagnostics.warning("diagnostics.repository-snapshot.failed", error);
return [];
});
const systemPreflight = await preflight
.runSystem()
.catch((error) => ({ error: error.message }));
const destinationPath =
path.extname(result.filePath).toLowerCase() === ".zip"
? result.filePath
: `${result.filePath}.zip`;
return diagnostics.exportSupportBundle({
destinationPath,
publicState: store.getPublicState(),
repositories: repositoryState,
operations: store.data.operations,
preflight: systemPreflight,
privacyMode,
extra: {
appVersion: app.getVersion(),
setupComplete: store.data.setupComplete,
},
});
});
register("diagnostics:show-bundle", async ({ filePath }) => {
if (!diagnostics.isKnownBundlePath(filePath))
throw new Error(
"Only the most recently generated support bundle can be revealed.",
);
shell.showItemInFolder(filePath);
return true;
});
register(
"renderer:report",
async ({ level = "info", event = "renderer.event", details = {} }) => {
const method = ["debug", "info", "warning", "error"].includes(level)
? level
: "info";
await diagnostics[method](
`renderer.${String(event || "event").slice(0, 120)}`,
details,
);
return true;
},
);
register("app:reset", async () => {
await diagnostics.info("app.reset.requested", {});
store.data = store.migrate({});
store.sessionToken = null;
await store.save();
monitor?.setPaths([]);
monitor?.restart();
return store.getPublicState();
registerOperationsIpc({
register, store, unraid, deployments, diagnostics, shell, dialog, path, app,
repositories, preflight, monitor,
});
}