[Init] Initial commit - NetMesh terminal manager
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled

This commit is contained in:
2026-09-13 18:24:01 +08:00
commit 3c72efcb7f
3255 changed files with 907009 additions and 0 deletions

View File

@@ -0,0 +1,204 @@
import { useCallback, useRef } from "react";
import type { GroupConfig, Host, ManagedSource } from "../../domain/models";
import { removeSnippetTargetGroupPaths } from "../../domain/hostGroupPathMutations";
import { buildVaultGroupDeletion } from "../../domain/vaultGroupDeletion";
import type {
VaultGroupMutationResult,
VaultGroupMutationState,
} from "../../domain/vaultGroupMutation";
import {
type VaultLockHandle,
withVaultImportLock,
} from "./vaultManagedImportLock";
const RETRY_VAULT_GROUP_DELETION = Symbol("retry-vault-group-deletion");
const managedSourceSnapshotsMatch = (
left: ManagedSource[],
right: ManagedSource[],
): boolean => {
const serialize = (sources: ManagedSource[]) => JSON.stringify(
[...sources].sort((a, b) => a.id.localeCompare(b.id)),
);
return serialize(left) === serialize(right);
};
export function useVaultGroupDeletion({
customGroups,
hosts,
groupConfigs,
managedSources,
onReadPersistedHosts,
onReadPersistedManagedSources,
onCommitVaultGroupMutation,
onClearAndRemoveManagedSource,
onClearAndRemoveManagedSources,
onDeletedPaths,
}: {
customGroups: string[];
hosts: Host[];
groupConfigs: GroupConfig[];
managedSources: ManagedSource[];
onReadPersistedHosts: () => Promise<Host[]>;
onReadPersistedManagedSources: () => ManagedSource[];
onCommitVaultGroupMutation: (
mutate: (current: VaultGroupMutationState) => VaultGroupMutationResult,
lock?: VaultLockHandle | null,
) => Promise<VaultGroupMutationResult | { ok: false; superseded: true }>;
onClearAndRemoveManagedSource?: (source: ManagedSource) => Promise<() => Promise<void>>;
onClearAndRemoveManagedSources?: (sources: ManagedSource[]) => Promise<() => Promise<void>>;
onDeletedPaths?: (selectedRoots: string[]) => void;
}) {
const latestRef = useRef({ customGroups, hosts, groupConfigs, managedSources });
latestRef.current = { customGroups, hosts, groupConfigs, managedSources };
return useCallback(async (
paths: Iterable<string>,
deleteHosts: boolean = false,
additionallyDeletedHostIds: ReadonlySet<string> = new Set(),
) => {
const selectedPaths = [...paths];
let deletedRoots: string[] = [];
while (true) {
let restoreManagedFiles: (() => Promise<void>) | undefined;
try {
const outcome = await withVaultImportLock("vault", async (lock) => {
const [latestHosts, latestManagedSources] = await Promise.all([
onReadPersistedHosts(),
Promise.resolve(onReadPersistedManagedSources()),
]);
latestRef.current = {
...latestRef.current,
hosts: latestHosts,
managedSources: latestManagedSources,
};
let deletion = buildVaultGroupDeletion({
selectedPaths,
deleteHosts,
...latestRef.current,
});
if (deletion.selectedRoots.length === 0) {
return { status: "empty" as const };
}
if (deletion.sourcesToRemove.length > 0) {
if (onClearAndRemoveManagedSources) {
restoreManagedFiles = await onClearAndRemoveManagedSources(deletion.sourcesToRemove);
} else if (onClearAndRemoveManagedSource) {
const restores = await Promise.all(
deletion.sourcesToRemove.map((source) => onClearAndRemoveManagedSource(source)),
);
restoreManagedFiles = async () => {
await Promise.all(restores.map((restore) => restore()));
};
}
}
latestRef.current = {
...latestRef.current,
hosts: await onReadPersistedHosts(),
managedSources: onReadPersistedManagedSources(),
};
const refreshedDeletion = buildVaultGroupDeletion({
selectedPaths,
deleteHosts,
...latestRef.current,
});
if (!managedSourceSnapshotsMatch(
deletion.sourcesToRemove,
refreshedDeletion.sourcesToRemove,
)) {
throw RETRY_VAULT_GROUP_DELETION;
}
deletion = refreshedDeletion;
const expectedSources = deletion.sourcesToRemove;
const transaction = await onCommitVaultGroupMutation(
(current) => {
const currentDeletion = buildVaultGroupDeletion({
selectedPaths,
deleteHosts,
customGroups: current.groups,
hosts: current.hosts,
groupConfigs: current.configs,
managedSources: current.managedSources,
});
if (!managedSourceSnapshotsMatch(
expectedSources,
currentDeletion.sourcesToRemove,
)) {
throw RETRY_VAULT_GROUP_DELETION;
}
return {
ok: true,
state: {
groups: currentDeletion.customGroups,
hosts: currentDeletion.hosts.filter(
(host) => !additionallyDeletedHostIds.has(host.id),
),
configs: currentDeletion.groupConfigs,
managedSources: current.managedSources.filter(
(source) => !currentDeletion.sourcesToRemove.some(
(removed) => removed.id === source.id,
),
),
snippets: removeSnippetTargetGroupPaths(
current.snippets,
currentDeletion.selectedRoots,
),
},
};
},
lock,
);
if (!transaction.ok && "superseded" in transaction) {
// Release the lock so concurrent saves can finish before retry.
return { status: "retry" as const, restoreManagedFiles };
}
if ("error" in transaction) {
throw new Error(transaction.error);
}
latestRef.current = {
customGroups: transaction.state.groups,
hosts: transaction.state.hosts,
groupConfigs: transaction.state.configs,
managedSources: transaction.state.managedSources,
};
return {
status: "done" as const,
deletedRoots: deletion.selectedRoots,
};
});
if (outcome.status === "empty") {
onDeletedPaths?.([]);
return;
}
if (outcome.status === "retry") {
await outcome.restoreManagedFiles?.();
// Drain concurrent locked writes outside the critical section.
await onReadPersistedHosts();
continue;
}
deletedRoots = outcome.deletedRoots;
break;
} catch (error) {
await restoreManagedFiles?.();
if (error === RETRY_VAULT_GROUP_DELETION) {
await onReadPersistedHosts();
continue;
}
throw error;
}
}
onDeletedPaths?.(deletedRoots);
}, [
onClearAndRemoveManagedSource,
onClearAndRemoveManagedSources,
onCommitVaultGroupMutation,
onDeletedPaths,
onReadPersistedHosts,
onReadPersistedManagedSources,
]);
}