Файли
openidea/src/features/deployment/store.ts
T
Florent Vтаflorentvdry 2d4d6ee6bf fix(deployment): accept absolute local paths in mappings and name menu entries after the server (#885)
A mapping's local path may now be an absolute path inside the project (the form IntelliJ writes), and a deployment path of . means the server root; absolute paths outside the project are kept as they are. The project-tree Deployment submenu names the default server (Upload to <server>, Download from <server>, Sync with Deployed to <server>…) and gains Sync with Deployed to Default Server. Remote Host downloads use the backend-resolved mapping, which fixes downloads under symlinked or case-different paths.

Fixes #877

Co-authored-by: florentvdry <[email protected]>
2026-11-03 23:35:43 +08:00

98 рядки
2.8 KiB
TypeScript

/** Session state of the deployment feature: the Sync dialog, the SSH secret prompt and the Remote Host tree. */
import { create } from "zustand";
import type { TransferReport, ResolvedMapping, RemoteEntry, RequestId, SyncEntry } from "@/platform/ipc";
import type { FocusedPart, SyncChoice } from "./model";
export const REMOTE_HOST_WINDOW = "remoteHost";
export interface SyncDialogState {
server: string;
serverName: string;
/** Absolute local paths selected for comparison. */
paths: string[];
skipped: TransferReport["errors"];
request: RequestId | null;
entries: SyncEntry[] | null;
error: string | null;
choices: Record<string, SyncChoice>;
hideSame: boolean;
running: boolean;
}
export interface SecretPrompt {
ssh: string;
message: string;
resolve: (answer: { secret: string; save: boolean } | null) => void;
}
/** Remote Host tool window, per server: resolved root, loaded folders, expanded folders. */
export interface RemoteTreeState {
source: string | null;
root: string | null;
mappings: ResolvedMapping[];
error: string | null;
listings: Record<string, RemoteEntry[]>;
folderErrors: Record<string, string>;
expanded: ReadonlySet<string>;
}
interface DeploymentState {
sync: SyncDialogState | null;
secret: SecretPrompt | null;
/** Which workbench part had focus last (targets of actions run from Find Action). */
part: FocusedPart;
/** Server shown in the Remote Host tool window. */
remoteServer: string | null;
trees: Record<string, RemoteTreeState>;
}
export const useDeployment = create<DeploymentState>(() => ({
sync: null,
secret: null,
part: null,
remoteServer: null,
trees: {},
}));
export const EMPTY_TREE: RemoteTreeState = {
source: null,
root: null,
mappings: [],
error: null,
listings: {},
folderErrors: {},
expanded: new Set(),
};
export function patchTree(
server: string,
patch: Partial<RemoteTreeState> | ((t: RemoteTreeState) => Partial<RemoteTreeState>),
) {
useDeployment.setState((s) => {
const current = s.trees[server] ?? EMPTY_TREE;
const next = typeof patch === "function" ? patch(current) : patch;
return { trees: { ...s.trees, [server]: { ...current, ...next } } };
});
}
export function patchSync(patch: Partial<SyncDialogState>) {
useDeployment.setState((s) => (s.sync ? { sync: { ...s.sync, ...patch } } : {}));
}
/** Asks for the password / passphrase of SSH configuration `ssh`. */
export function promptSecret(ssh: string, message: string): Promise<{ secret: string; save: boolean } | null> {
return new Promise((resolve) => {
useDeployment.getState().secret?.resolve(null);
useDeployment.setState({
secret: {
ssh,
message,
resolve: (answer) => {
useDeployment.setState({ secret: null });
resolve(answer);
},
},
});
});
}