дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-09 11:15:40 +02:00
fix(theme): Define the chart series colour in the high-contrast dark theme
Цей коміт міститься в:
1 джерело
575749f2ac
коміт
0ab50e0dd4
51 files changed
+4398
-537
No files matched your search
+232
-9
@@ -755,6 +755,51 @@ export const commands = {
|
||||
* "Unknown SSH host key" (records it in `~/.ssh/known_hosts`).
|
||||
*/
|
||||
dbTrustSshHost: (id: string) => typedError<null, IdeError>(__TAURI_INVOKE("db_trust_ssh_host", { id })),
|
||||
/** Test Connection: connects with `config` as edited (not necessarily saved). */
|
||||
sshTest: (config: SshConfiguration, secret: string | null) => typedError<string, IdeError>(__TAURI_INVOKE("ssh_test", { config, secret })),
|
||||
/** Remembers the password / passphrase of configuration `id` (`None` forgets it). */
|
||||
sshSetSecret: (id: string, secret: string | null, save: boolean) => __TAURI_INVOKE<void>("ssh_set_secret", { id, secret, save }),
|
||||
/** Whether connecting with configuration `id` needs a password first. */
|
||||
sshNeedsSecret: (id: string) => typedError<boolean, IdeError>(__TAURI_INVOKE("ssh_needs_secret", { id })),
|
||||
/** Trusts the host key that failed the last connection of configuration `id`. */
|
||||
sshTrustHost: (id: string) => typedError<null, IdeError>(__TAURI_INVOKE("ssh_trust_host", { id })),
|
||||
/** Ids of the configurations with an open connection. */
|
||||
sshConnected: () => __TAURI_INVOKE<string[]>("ssh_connected"),
|
||||
sshDisconnect: (id: string) => __TAURI_INVOKE<void>("ssh_disconnect", { id }),
|
||||
/** Answers an `IdeEvent::Askpass` prompt (`None`: cancelled); every window drops it. */
|
||||
sshAskpassAnswer: (id: number, answer: string | null) => __TAURI_INVOKE<void>("ssh_askpass_answer", { id, answer }),
|
||||
/**
|
||||
* Tools › Start SSH Session: the system `ssh` command line for configuration `id`
|
||||
* (run in a terminal tab, so prompts and host-key questions are answered there).
|
||||
*/
|
||||
sshTerminalCommand: (id: string) => typedError<SshTerminalCommand, IdeError>(__TAURI_INVOKE("ssh_terminal_command", { id })),
|
||||
/** Tools › Deployment › Upload to…: files and folders to `server` (default server when `None`). */
|
||||
deployUpload: (paths: IpcPath[], server: string | null, request: RequestId) => typedError<TransferReport, IdeError>(__TAURI_INVOKE("deploy_upload", { paths, server, request })),
|
||||
/** Download from…: the remote counterparts of files and folders. */
|
||||
deployDownload: (paths: IpcPath[], server: string | null, request: RequestId) => typedError<TransferReport, IdeError>(__TAURI_INVOKE("deploy_download", { paths, server, request })),
|
||||
/** Deletes the remote counterparts of files and folders. */
|
||||
deployDeleteRemote: (paths: IpcPath[], server: string | null) => typedError<TransferReport, IdeError>(__TAURI_INVOKE("deploy_delete_remote", { paths, server })),
|
||||
/** Sync with Deployed: local and remote files under `path`, side by side. */
|
||||
deployCompare: (path: IpcPath, server: string | null, request: RequestId) => typedError<SyncEntry[], IdeError>(__TAURI_INVOKE("deploy_compare", { path, server, request })),
|
||||
/** Applies the Sync with Deployed choices. */
|
||||
deploySync: (steps: SyncStep[], server: string | null, request: RequestId) => typedError<TransferReport, IdeError>(__TAURI_INVOKE("deploy_sync", { steps, server, request })),
|
||||
/** Where `path` is deployed (remote path, web URL); `None` when not mapped. */
|
||||
deployInfo: (path: IpcPath, server: string | null) => typedError<{
|
||||
server: string,
|
||||
remotePath: string,
|
||||
webUrl: string | null,
|
||||
} | null, IdeError>(__TAURI_INVOKE("deploy_info", { path, server })),
|
||||
/** Remote Host tool window: the server root of `server`, resolved on the host. */
|
||||
deployServerRoot: (server: string) => typedError<string, IdeError>(__TAURI_INVOKE("deploy_server_root", { server })),
|
||||
/** Entries of a remote folder of SSH configuration `ssh` (`~`: the login directory). */
|
||||
remoteList: (ssh: string, path: string) => typedError<RemoteEntry[], IdeError>(__TAURI_INVOKE("remote_list", { ssh, path })),
|
||||
/** Opens a remote file for editing: a local copy, uploaded back on save. */
|
||||
remoteOpen: (ssh: string, path: string) => typedError<IpcPath, IdeError>(__TAURI_INVOKE("remote_open", { ssh, path })),
|
||||
/** The remote file a local copy stands for: (SSH configuration id, remote path). */
|
||||
remoteOf: (path: IpcPath) => __TAURI_INVOKE<[string, string] | null>("remote_of", { path }),
|
||||
remoteDelete: (ssh: string, path: string) => typedError<null, IdeError>(__TAURI_INVOKE("remote_delete", { ssh, path })),
|
||||
remoteMkdir: (ssh: string, path: string) => typedError<null, IdeError>(__TAURI_INVOKE("remote_mkdir", { ssh, path })),
|
||||
remoteRename: (ssh: string, from: string, to: string) => typedError<null, IdeError>(__TAURI_INVOKE("remote_rename", { ssh, from, to })),
|
||||
/** Test Connection: connects with unsaved settings and returns the server version. */
|
||||
dbTest: (source: DataSource, password: string | null, sshSecret: string | null) => typedError<string, IdeError>(__TAURI_INVOKE("db_test", { source, password, sshSecret })),
|
||||
dbConnected: () => typedError<string[], IdeError>(__TAURI_INVOKE("db_connected")),
|
||||
@@ -897,6 +942,15 @@ export type ApplyOutcome = {
|
||||
conflicts: string[],
|
||||
};
|
||||
|
||||
/** What an askpass prompt asks for. */
|
||||
export type AskpassKind =
|
||||
/** Password or passphrase: hidden input. */
|
||||
"secret" |
|
||||
/** User name: visible input. */
|
||||
"text" |
|
||||
/** Yes / no (an unknown host key); answered with `yes` or `no`. */
|
||||
"confirm";
|
||||
|
||||
export type AttachKind = "java" | "node" | "python" | "go" | "native";
|
||||
|
||||
export type AttachableProcess = {
|
||||
@@ -918,6 +972,13 @@ export type AutoImportSettings = {
|
||||
optimizeOnTheFly?: boolean,
|
||||
};
|
||||
|
||||
/** When local changes are uploaded to the default deployment server. */
|
||||
export type AutoUpload = "never" |
|
||||
/** Files saved with Save / Save All (⌘S). */
|
||||
"onExplicitSave" |
|
||||
/** Every save, including autosave and external changes. */
|
||||
"always";
|
||||
|
||||
/** A step run before a configuration launches; a failing step aborts the launch. */
|
||||
export type BeforeLaunchTask = {
|
||||
/**
|
||||
@@ -1682,24 +1743,19 @@ export type DbSortKey = {
|
||||
/** Connect through an SSH tunnel: the database host and port are reached from `host`. */
|
||||
export type DbSsh = {
|
||||
enabled?: boolean,
|
||||
/** A Settings › SSH Configurations id to connect with; empty = the fields below. */
|
||||
config?: string,
|
||||
/** Host name, or a `Host` alias of `~/.ssh/config` with [`DbSshAuth::OpenSsh`]. */
|
||||
host?: string,
|
||||
port?: number | null,
|
||||
user?: string,
|
||||
auth?: DbSshAuth,
|
||||
auth?: SshAuth,
|
||||
/** Private key for [`DbSshAuth::KeyPair`]; `~/` is the home directory. */
|
||||
keyFile?: string,
|
||||
/** Keep the SSH password / key passphrase in the saved passwords. */
|
||||
saveSecret?: boolean,
|
||||
};
|
||||
|
||||
/** How the SSH tunnel authenticates (IDEA's SSH configuration "Authentication type"). */
|
||||
export type DbSshAuth = "password" |
|
||||
/** A private key file, optionally protected by a passphrase. */
|
||||
"keyPair" |
|
||||
/** `~/.ssh/config` (HostName, User, Port, IdentityFile, ProxyJump) and the SSH agent. */
|
||||
"openSsh";
|
||||
|
||||
export type DbSslMode = "disable" | "prefer" | "require" |
|
||||
/** Require, and check the server certificate against the CA file. */
|
||||
"verifyCa" |
|
||||
@@ -1798,6 +1854,51 @@ export type DeletedBranch = {
|
||||
unmerged: boolean,
|
||||
};
|
||||
|
||||
/** Where a local file is deployed. */
|
||||
export type DeployedPath = {
|
||||
server: string,
|
||||
remotePath: string,
|
||||
webUrl: string | null,
|
||||
};
|
||||
|
||||
export type DeploymentMapping = {
|
||||
/** Project-relative folder; empty = the project root. */
|
||||
local?: string,
|
||||
/** Remote folder, relative to the server root (a leading `/` is the root too). */
|
||||
deployment?: string,
|
||||
/** Web path, relative to the server's URL. */
|
||||
web?: string,
|
||||
};
|
||||
|
||||
/** An SFTP server and how project folders map onto it. */
|
||||
export type DeploymentServer = {
|
||||
id?: string,
|
||||
name?: string,
|
||||
/** [`SshConfiguration::id`]. */
|
||||
sshConfig?: string,
|
||||
/** Absolute remote folder the mappings are relative to; empty = the login directory. */
|
||||
rootPath?: string,
|
||||
/** Web server URL of the root (Open in Browser on the deployed file). */
|
||||
webUrl?: string,
|
||||
mappings?: DeploymentMapping[],
|
||||
/** Remote paths (relative to the root) never touched. */
|
||||
excludedPaths?: string[],
|
||||
};
|
||||
|
||||
/** Build, Execution, Deployment › Deployment (and its Options page). */
|
||||
export type DeploymentSettings = {
|
||||
servers?: DeploymentServer[],
|
||||
/** Server used by Upload / Download / Sync and automatic upload; empty = none. */
|
||||
defaultServer?: string,
|
||||
autoUpload?: AutoUpload,
|
||||
/** Delete remote files when their local file is deleted (automatic upload). */
|
||||
deleteRemote?: boolean,
|
||||
/** Keep local modification times on uploaded files. */
|
||||
preserveTimestamps?: boolean,
|
||||
/** File names never uploaded or downloaded (`*` and `?` wildcards; `;`-separated in the UI). */
|
||||
exclude?: string[],
|
||||
};
|
||||
|
||||
export type Diagnostic = {
|
||||
range: Range,
|
||||
severity: Severity,
|
||||
@@ -2386,6 +2487,11 @@ export type FileSettings = {
|
||||
fileTypeOverrides?: { [key in string]: string },
|
||||
};
|
||||
|
||||
export type FileStamp = {
|
||||
size: number,
|
||||
mtime: number | null,
|
||||
};
|
||||
|
||||
export type FileStatus = {
|
||||
path: IpcPath,
|
||||
relPath: string,
|
||||
@@ -2731,7 +2837,11 @@ export type IdeEvent =
|
||||
* The project's source / test / resource / excluded roots changed (build files
|
||||
* changed or folders were marked); the UI re-reads them with `project_roots`.
|
||||
*/
|
||||
{ type: "projectRootsChanged"; root: IpcPath };
|
||||
{ type: "projectRootsChanged"; root: IpcPath } |
|
||||
/** A child process (`ssh`, `git`) asks for a credential; answered with `ssh_askpass_answer`. */
|
||||
{ type: "askpass"; id: number; prompt: string; kind: AskpassKind } |
|
||||
/** Prompt `id` was answered (in any window) or gave up. */
|
||||
{ type: "askpassDone"; id: number };
|
||||
|
||||
/** An identifier-like token whose text equals the searched name. */
|
||||
export type IdentifierOccurrence = {
|
||||
@@ -3975,6 +4085,20 @@ export type RemoteEdit = { op: "add"; name: string; url: string } | { op: "remov
|
||||
/** Renames `name` to `new_name` (when different) and sets its URL. */
|
||||
{ op: "edit"; name: string; new_name: string; url: string };
|
||||
|
||||
/** A remote directory entry. */
|
||||
export type RemoteEntry = {
|
||||
name: string,
|
||||
/** Absolute remote path. */
|
||||
path: string,
|
||||
isDir: boolean,
|
||||
isSymlink: boolean,
|
||||
size: number,
|
||||
/** Seconds since the Unix epoch. */
|
||||
mtime: number | null,
|
||||
/** `rwxr-xr-x`. */
|
||||
permissions: string,
|
||||
};
|
||||
|
||||
export type RemoteOp = { op: "fetch" } | { op: "pull"; rebase: boolean } | { op: "push"; force_with_lease: boolean; set_upstream: boolean;
|
||||
/** Push dialog › Push tags. */
|
||||
tags?: PushTags | null } |
|
||||
@@ -4121,6 +4245,8 @@ export type RunExtras_Deserialize = {
|
||||
* with the run's working directory and environment.
|
||||
*/
|
||||
stopCommand?: string | null,
|
||||
/** Settings › Run Targets id the process runs on (over SSH); `None` = this machine. */
|
||||
target?: string | null,
|
||||
};
|
||||
|
||||
export type RunExtras_Serialize = {
|
||||
@@ -4156,6 +4282,8 @@ export type RunExtras_Serialize = {
|
||||
* with the run's working directory and environment.
|
||||
*/
|
||||
stopCommand?: string | null,
|
||||
/** Settings › Run Targets id the process runs on (over SSH); `None` = this machine. */
|
||||
target?: string | null,
|
||||
};
|
||||
|
||||
/** A process started from a run configuration. */
|
||||
@@ -4181,6 +4309,22 @@ export type RunSettings = {
|
||||
terminationTimeoutMs?: number,
|
||||
};
|
||||
|
||||
/** Build, Execution, Deployment › Run Targets: run configurations on another machine. */
|
||||
export type RunTarget = {
|
||||
id?: string,
|
||||
name?: string,
|
||||
/** [`SshConfiguration::id`]. */
|
||||
sshConfig?: string,
|
||||
/** Remote folder the project is synchronised to and run from. */
|
||||
projectRoot?: string,
|
||||
/** Upload changed project files before each run. */
|
||||
syncBeforeRun?: boolean,
|
||||
/** Interpreters on the target; empty = the one on the remote `PATH`. */
|
||||
python?: string,
|
||||
node?: string,
|
||||
java?: string,
|
||||
};
|
||||
|
||||
export type RustSettings = {
|
||||
/** Directory holding `cargo` / `rustc`; empty = `PATH` / rustup default. */
|
||||
toolchain?: string,
|
||||
@@ -4380,6 +4524,12 @@ export type Settings_Deserialize = {
|
||||
inspections?: InspectionSettings,
|
||||
/** Editor › Natural Languages › Spelling. */
|
||||
spelling?: SpellingSettings,
|
||||
/** Tools › SSH Configurations. */
|
||||
ssh?: SshSettings,
|
||||
/** Build, Execution, Deployment › Deployment. */
|
||||
deployment?: DeploymentSettings,
|
||||
/** Build, Execution, Deployment › Run Targets. */
|
||||
runTargets?: RunTarget[],
|
||||
};
|
||||
|
||||
export type Settings_Serialize = {
|
||||
@@ -4422,6 +4572,12 @@ export type Settings_Serialize = {
|
||||
inspections: InspectionSettings,
|
||||
/** Editor › Natural Languages › Spelling. */
|
||||
spelling: SpellingSettings,
|
||||
/** Tools › SSH Configurations. */
|
||||
ssh: SshSettings,
|
||||
/** Build, Execution, Deployment › Deployment. */
|
||||
deployment: DeploymentSettings,
|
||||
/** Build, Execution, Deployment › Run Targets. */
|
||||
runTargets: RunTarget[],
|
||||
};
|
||||
|
||||
export type Severity = "error" | "warning" | "info" | "hint";
|
||||
@@ -4506,6 +4662,39 @@ export type SqlStatement = {
|
||||
text: string,
|
||||
};
|
||||
|
||||
/** How an SSH connection authenticates. */
|
||||
export type SshAuth = "password" |
|
||||
/** A private key file, optionally protected by a passphrase. */
|
||||
"keyPair" |
|
||||
/** `~/.ssh/config` (HostName, User, Port, IdentityFile, ProxyJump) and the SSH agent. */
|
||||
"openSsh";
|
||||
|
||||
/** A named SSH host, shared by terminals, deployment, run targets and data sources. */
|
||||
export type SshConfiguration = {
|
||||
id?: string,
|
||||
name?: string,
|
||||
/** Host name, or a `Host` alias of `~/.ssh/config` with [`SshAuth::OpenSsh`]. */
|
||||
host?: string,
|
||||
port?: number | null,
|
||||
user?: string,
|
||||
auth?: SshAuth,
|
||||
/** Private key for [`SshAuth::KeyPair`]; `~/` is the home directory. */
|
||||
keyFile?: string,
|
||||
/** Keep the password / key passphrase in the saved passwords. */
|
||||
saveSecret?: boolean,
|
||||
};
|
||||
|
||||
/** Tools › SSH Configurations. */
|
||||
export type SshSettings = {
|
||||
configs?: SshConfiguration[],
|
||||
};
|
||||
|
||||
export type SshTerminalCommand = {
|
||||
shell: string,
|
||||
args: string[],
|
||||
title: string,
|
||||
};
|
||||
|
||||
export type StackFrame = {
|
||||
id: number,
|
||||
name: string,
|
||||
@@ -4632,12 +4821,34 @@ export type SymbolMatch = {
|
||||
indices: number[],
|
||||
};
|
||||
|
||||
export type SyncAction = "upload" | "download" | "deleteRemote" | "deleteLocal";
|
||||
|
||||
/** One file of Sync with Deployed. */
|
||||
export type SyncEntry = {
|
||||
/** Project-relative, `/`-separated. */
|
||||
path: string,
|
||||
remotePath: string,
|
||||
local: FileStamp | null,
|
||||
remote: FileStamp | null,
|
||||
status: SyncStatus,
|
||||
};
|
||||
|
||||
export type SyncProgress = {
|
||||
message: string,
|
||||
/** 0..1, or `None` when indeterminate. */
|
||||
fraction: number | null,
|
||||
};
|
||||
|
||||
export type SyncStatus = "localOnly" | "remoteOnly" | "localNewer" | "remoteNewer" |
|
||||
/** Same time, different size. */
|
||||
"different" | "same";
|
||||
|
||||
export type SyncStep = {
|
||||
/** Project-relative, `/`-separated. */
|
||||
path: string,
|
||||
action: SyncAction,
|
||||
};
|
||||
|
||||
/** Counters for diagnostics (`app_metrics`). */
|
||||
export type SyntaxCacheStats = {
|
||||
/** Documents tracked (open and observed). */
|
||||
@@ -4875,6 +5086,18 @@ export type ToolStatus =
|
||||
/** The executable chosen in settings. */
|
||||
"custom" | "missing";
|
||||
|
||||
export type TransferError = {
|
||||
path: string,
|
||||
message: string,
|
||||
};
|
||||
|
||||
export type TransferReport = {
|
||||
files: number,
|
||||
/** Files left alone: excluded, or outside every mapping. */
|
||||
skipped: number,
|
||||
errors: TransferError[],
|
||||
};
|
||||
|
||||
export type TypeScriptSettings = {
|
||||
/**
|
||||
* `bundled` (the server's own TypeScript), `workspace` (`node_modules/typescript`),
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
type DbOptions,
|
||||
type DbProperty,
|
||||
type DbSsh,
|
||||
type DbSshAuth,
|
||||
type SshAuth,
|
||||
type DbSslMode,
|
||||
type DbTxMode,
|
||||
} from "@/platform/ipc";
|
||||
@@ -51,7 +51,7 @@ const TX: Array<SelectOption<DbTxMode>> = [
|
||||
{ value: "manual", label: "Manual" },
|
||||
];
|
||||
|
||||
const SSH_AUTH: Array<SelectOption<DbSshAuth>> = [
|
||||
const SSH_AUTH: Array<SelectOption<SshAuth>> = [
|
||||
{ value: "openSsh", label: "OpenSSH config and authentication agent" },
|
||||
{ value: "password", label: "Password" },
|
||||
{ value: "keyPair", label: "Key pair" },
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
/**
|
||||
* SSH configurations, deployment servers and run targets as features consume
|
||||
* them (mirrors `ide-core::remote_settings` defaults). Normalisation never throws.
|
||||
*/
|
||||
export type SshAuthValid = "password" | "keyPair" | "openSsh";
|
||||
export type AutoUploadValid = "never" | "onExplicitSave" | "always";
|
||||
|
||||
export interface SshConfigValid {
|
||||
id: string;
|
||||
name: string;
|
||||
host: string;
|
||||
port: number | null;
|
||||
user: string;
|
||||
auth: SshAuthValid;
|
||||
keyFile: string;
|
||||
saveSecret: boolean;
|
||||
}
|
||||
|
||||
export interface DeploymentMappingValid {
|
||||
local: string;
|
||||
deployment: string;
|
||||
web: string;
|
||||
}
|
||||
|
||||
export interface DeploymentServerValid {
|
||||
id: string;
|
||||
name: string;
|
||||
sshConfig: string;
|
||||
rootPath: string;
|
||||
webUrl: string;
|
||||
mappings: DeploymentMappingValid[];
|
||||
excludedPaths: string[];
|
||||
}
|
||||
|
||||
export interface DeploymentSettingsValid {
|
||||
servers: DeploymentServerValid[];
|
||||
defaultServer: string;
|
||||
autoUpload: AutoUploadValid;
|
||||
deleteRemote: boolean;
|
||||
preserveTimestamps: boolean;
|
||||
exclude: string[];
|
||||
}
|
||||
|
||||
export interface RunTargetValid {
|
||||
id: string;
|
||||
name: string;
|
||||
sshConfig: string;
|
||||
projectRoot: string;
|
||||
syncBeforeRun: boolean;
|
||||
python: string;
|
||||
node: string;
|
||||
java: string;
|
||||
}
|
||||
|
||||
export interface RemoteSettings {
|
||||
ssh: { configs: SshConfigValid[] };
|
||||
deployment: DeploymentSettingsValid;
|
||||
runTargets: RunTargetValid[];
|
||||
}
|
||||
|
||||
export const DEFAULT_SSH_CONFIG: SshConfigValid = {
|
||||
id: "",
|
||||
name: "",
|
||||
host: "",
|
||||
port: null,
|
||||
user: "",
|
||||
auth: "openSsh",
|
||||
keyFile: "",
|
||||
saveSecret: false,
|
||||
};
|
||||
|
||||
export const DEFAULT_DEPLOYMENT_SERVER: DeploymentServerValid = {
|
||||
id: "",
|
||||
name: "",
|
||||
sshConfig: "",
|
||||
rootPath: "",
|
||||
webUrl: "",
|
||||
mappings: [{ local: "", deployment: "", web: "" }],
|
||||
excludedPaths: [],
|
||||
};
|
||||
|
||||
export const DEFAULT_DEPLOYMENT: DeploymentSettingsValid = {
|
||||
servers: [],
|
||||
defaultServer: "",
|
||||
autoUpload: "never",
|
||||
deleteRemote: false,
|
||||
preserveTimestamps: true,
|
||||
exclude: [".git", ".svn", ".hg", ".idea", ".openidea", ".DS_Store", "*.pyc", "__pycache__"],
|
||||
};
|
||||
|
||||
export const DEFAULT_RUN_TARGET: RunTargetValid = {
|
||||
id: "",
|
||||
name: "",
|
||||
sshConfig: "",
|
||||
projectRoot: "",
|
||||
syncBeforeRun: true,
|
||||
python: "",
|
||||
node: "",
|
||||
java: "",
|
||||
};
|
||||
|
||||
export const DEFAULT_REMOTE: RemoteSettings = {
|
||||
ssh: { configs: [] },
|
||||
deployment: DEFAULT_DEPLOYMENT,
|
||||
runTargets: [],
|
||||
};
|
||||
|
||||
type Obj = Record<string, unknown>;
|
||||
const obj = (v: unknown): Obj => (v && typeof v === "object" && !Array.isArray(v) ? (v as Obj) : {});
|
||||
const text = (v: unknown, d: string) => (typeof v === "string" ? v : d);
|
||||
const bool = (v: unknown, d: boolean) => (typeof v === "boolean" ? v : d);
|
||||
const strings = (v: unknown, d: string[]) => (Array.isArray(v) ? v.filter((x): x is string => typeof x === "string") : d);
|
||||
const oneOf = <T extends string>(v: unknown, values: readonly T[], d: T): T =>
|
||||
values.includes(v as T) ? (v as T) : d;
|
||||
const port = (v: unknown): number | null =>
|
||||
typeof v === "number" && Number.isInteger(v) && v > 0 && v <= 65535 ? v : null;
|
||||
|
||||
/** Items with a unique, non-empty id (duplicates get `_` appended, missing ones `<prefix><n>`). */
|
||||
function withIds<T extends { id: string }>(raw: unknown, prefix: string, item: (o: Obj) => T | null): T[] {
|
||||
if (!Array.isArray(raw)) return [];
|
||||
const seen = new Set<string>();
|
||||
const out: T[] = [];
|
||||
raw.forEach((x, i) => {
|
||||
const v = item(obj(x));
|
||||
if (!v) return;
|
||||
let id = v.id.trim() || `${prefix}${i + 1}`;
|
||||
while (seen.has(id)) id += "_";
|
||||
seen.add(id);
|
||||
out.push({ ...v, id });
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
export function validateSshConfigs(raw: unknown): SshConfigValid[] {
|
||||
const d = DEFAULT_SSH_CONFIG;
|
||||
return withIds(raw, "ssh", (o) => ({
|
||||
id: text(o.id, ""),
|
||||
name: text(o.name, ""),
|
||||
host: text(o.host, ""),
|
||||
port: port(o.port),
|
||||
user: text(o.user, ""),
|
||||
auth: oneOf(o.auth, ["password", "keyPair", "openSsh"] as const, d.auth),
|
||||
keyFile: text(o.keyFile, ""),
|
||||
saveSecret: bool(o.saveSecret, d.saveSecret),
|
||||
}));
|
||||
}
|
||||
|
||||
function validateMappings(raw: unknown): DeploymentMappingValid[] {
|
||||
if (!Array.isArray(raw)) return [];
|
||||
return raw.map((m) => {
|
||||
const o = obj(m);
|
||||
return { local: text(o.local, ""), deployment: text(o.deployment, ""), web: text(o.web, "") };
|
||||
});
|
||||
}
|
||||
|
||||
export function validateDeployment(raw: unknown): DeploymentSettingsValid {
|
||||
const o = obj(raw);
|
||||
const d = DEFAULT_DEPLOYMENT;
|
||||
const servers = withIds(o.servers, "server", (s) => ({
|
||||
id: text(s.id, ""),
|
||||
name: text(s.name, ""),
|
||||
sshConfig: text(s.sshConfig, ""),
|
||||
rootPath: text(s.rootPath, ""),
|
||||
webUrl: text(s.webUrl, ""),
|
||||
mappings: validateMappings(s.mappings),
|
||||
excludedPaths: strings(s.excludedPaths, []),
|
||||
}));
|
||||
return {
|
||||
servers,
|
||||
defaultServer: text(o.defaultServer, d.defaultServer),
|
||||
autoUpload: oneOf(o.autoUpload, ["never", "onExplicitSave", "always"] as const, d.autoUpload),
|
||||
deleteRemote: bool(o.deleteRemote, d.deleteRemote),
|
||||
preserveTimestamps: bool(o.preserveTimestamps, d.preserveTimestamps),
|
||||
exclude: strings(o.exclude, d.exclude),
|
||||
};
|
||||
}
|
||||
|
||||
export function validateRunTargets(raw: unknown): RunTargetValid[] {
|
||||
const d = DEFAULT_RUN_TARGET;
|
||||
return withIds(raw, "target", (o) => ({
|
||||
id: text(o.id, ""),
|
||||
name: text(o.name, ""),
|
||||
sshConfig: text(o.sshConfig, ""),
|
||||
projectRoot: text(o.projectRoot, ""),
|
||||
syncBeforeRun: bool(o.syncBeforeRun, d.syncBeforeRun),
|
||||
python: text(o.python, ""),
|
||||
node: text(o.node, ""),
|
||||
java: text(o.java, ""),
|
||||
}));
|
||||
}
|
||||
|
||||
export function validateRemote(root: unknown): RemoteSettings {
|
||||
const o = obj(root);
|
||||
return {
|
||||
ssh: { configs: validateSshConfigs(obj(o.ssh).configs) },
|
||||
deployment: validateDeployment(o.deployment),
|
||||
runTargets: validateRunTargets(o.runTargets),
|
||||
};
|
||||
}
|
||||
|
||||
/** `user@host:port` with what is known (the name when the host is empty). */
|
||||
export function sshLabel(c: Pick<SshConfigValid, "name" | "host" | "port" | "user">): string {
|
||||
const host = c.host.trim();
|
||||
if (!host) return c.name;
|
||||
return `${c.user.trim() ? `${c.user.trim()}@` : ""}${host}${c.port ? `:${c.port}` : ""}`;
|
||||
}
|
||||
|
||||
let seq = 0;
|
||||
/** A fresh id for a new SSH configuration, server or target. */
|
||||
export const newRemoteId = (prefix: string) => `${prefix}-${Date.now().toString(36)}-${++seq}`;
|
||||
@@ -4,6 +4,7 @@
|
||||
* `ide-core::workflow_settings` defaults). Normalisation never throws.
|
||||
*/
|
||||
import { useProject } from "./project";
|
||||
import { DEFAULT_REMOTE, validateRemote, type RemoteSettings } from "./remoteSettings";
|
||||
|
||||
export type UpdateMethod = "merge" | "rebase";
|
||||
export type IncomingCheck = "auto" | "always" | "never";
|
||||
@@ -121,7 +122,7 @@ export interface ExternalToolValid {
|
||||
openConsole: boolean;
|
||||
}
|
||||
|
||||
export interface WorkflowSettings {
|
||||
export interface WorkflowSettings extends RemoteSettings {
|
||||
vcs: VcsSettingsValid;
|
||||
console: ConsoleSettingsValid;
|
||||
debugger: DebuggerSettingsValid;
|
||||
@@ -239,6 +240,7 @@ export const DEFAULT_WORKFLOW: WorkflowSettings = {
|
||||
},
|
||||
actionsOnSave: { optimizeImports: false, cleanup: false },
|
||||
externalTools: [],
|
||||
...DEFAULT_REMOTE,
|
||||
};
|
||||
|
||||
type Obj = Record<string, unknown>;
|
||||
@@ -423,6 +425,7 @@ export function validateWorkflow(raw: unknown): WorkflowSettings {
|
||||
terminal: validateTerminal(root.terminal),
|
||||
actionsOnSave: validateActionsOnSave(root.actionsOnSave),
|
||||
externalTools: validateExternalTools(root.externalTools),
|
||||
...validateRemote(root),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -645,6 +645,8 @@ html {
|
||||
--link: #a7c5ff;
|
||||
--link-hover: #d0dffe;
|
||||
|
||||
--chart-series: #f29598;
|
||||
|
||||
--danger: #ff9b9f;
|
||||
--warning: #f2c55c;
|
||||
--success: #8ec39d;
|
||||
|
||||
Reference in new issue
Заблокувати користувача