Hardening + collaboration features from full app review
Security and robustness (backend): - Strict CSP (was null); confine read_file/launch_file/ue_log to the client root - p4 trust without -f: surface fingerprint CHANGES instead of auto-accepting (MITM) - stdin=null by default + non-interactive P4EDITOR so terminal commands never hang - 64 MB read caps on read_depot/read_file/print_depot (no OOM on huge .uasset/.pak) - Recover from mutex poisoning instead of crashing every later p4 call - Decode p4 output as UTF-8 then cp1251 (Cyrillic paths no longer corrupt) - p4_changes never deletes changelists that hold shelved files - Validate changelist number in p4_undo_change Bug fixes (frontend): - Focus-refresh preserves the checkbox selection (never re-checks deselected files) - Staleness guards on refresh/describe/AI-summary/user-groups (no cross-selection races) - Real F5/Ctrl+R/G/S/B handlers; match e.code so Ctrl+backquote works on RU layout - Confirm dialog no longer fires on a stray global Enter - Panel/dock/history drags end on window blur (no stuck resize) - Commit draft clears on Submit only if unchanged since committing - Summary/IDE buttons gated to real source files (not .pak/.dll/binaries) New features: - Exclusive lock/unlock plus held-by/locked-by indicators (p4 opened -a) - Shelve/unshelve/delete-shelf on pending changelists - Resolve UI (auto-merge / accept yours / accept theirs, per file or all) + banner - Diff a history revision against the previous one; blame/annotate view - Depot file search (Tools -> Search depot) - Move opened files between pending changelists - New-submit toasts from teammates + offline banner with auto-reconnect Also: bake OpenRouter key at build time (git-ignored), drop AI settings UI, IDE picker (detect installed editors), AI replies in the UI language, persistent code summaries, metadata/CSP polish. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
38
src/p4.ts
38
src/p4.ts
@ -85,7 +85,8 @@ export const p4 = {
|
||||
findSln: (scope: string) => invoke<string>("find_sln", { scope }),
|
||||
buildSln: (path: string, config: string) => invoke<string>("build_sln", { path, config }),
|
||||
launchFile: (path: string) => invoke<void>("launch_file", { path }),
|
||||
openInVscode: (depot: string) => invoke<void>("open_in_vscode", { depot }),
|
||||
listEditors: () => invoke<Editor[]>("list_editors"),
|
||||
openInEditor: (depot: string, editor: string) => invoke<void>("open_in_editor", { depot, editor }),
|
||||
readDepot: async (depot: string): Promise<ArrayBuffer> => {
|
||||
const r: unknown = await invoke("read_depot", { depot });
|
||||
if (r instanceof ArrayBuffer) return r;
|
||||
@ -107,10 +108,35 @@ export const p4 = {
|
||||
groupMember: (group: string, user: string, add: boolean) => invoke<string>("p4_group_member", { group, user, add }),
|
||||
readAiKey: () => invoke<string>("read_ai_key"),
|
||||
saveAiKey: (key: string) => invoke<void>("save_ai_key", { key }),
|
||||
// run an arbitrary p4 command in the built-in terminal (combined stdout+stderr)
|
||||
console: (args: string[]) => invoke<string>("p4_console", { args }),
|
||||
// tail the newest Unreal log for a project (.uproject path → Saved/Logs/*.log)
|
||||
ueLog: (uproject: string, tailBytes = 200_000) => invoke<string>("ue_log", { uproject, tailBytes }),
|
||||
// --- collaboration / advanced Perforce ---
|
||||
openedAll: (scope = "") => invoke<OpenedFile[]>("p4_opened_all", { scope }),
|
||||
lock: (files: string[]) => invoke<string>("p4_lock", { files }),
|
||||
unlock: (files: string[]) => invoke<string>("p4_unlock", { files }),
|
||||
shelve: (change: string) => invoke<string>("p4_shelve", { change }),
|
||||
unshelve: (change: string) => invoke<string>("p4_unshelve", { change }),
|
||||
deleteShelf: (change: string) => invoke<string>("p4_delete_shelf", { change }),
|
||||
shelvedFiles: (change: string) => invoke<Describe>("p4_shelved_files", { change }),
|
||||
resolveList: () => invoke<OpenedFile[]>("p4_resolve_list"),
|
||||
resolveFile: (file: string, mode: "am" | "ay" | "at") => invoke<string>("p4_resolve_file", { file, mode }),
|
||||
diff2: (spec1: string, spec2: string) => invoke<string>("p4_diff2", { spec1, spec2 }),
|
||||
annotate: (spec: string) => invoke<string>("p4_annotate", { spec }),
|
||||
searchFiles: (query: string, scope = "") => invoke<{ depotFile?: string; rev?: string }[]>("p4_search_files", { query, scope }),
|
||||
reopenTo: (change: string, files: string[]) => invoke<string>("p4_reopen_to", { change, files }),
|
||||
latestChange: (scope = "") => invoke<Change | null>("p4_latest_change", { scope }),
|
||||
};
|
||||
|
||||
export interface User { User?: string; Email?: string; FullName?: string; Access?: string; Update?: string; [k: string]: unknown }
|
||||
|
||||
// a detected code editor / IDE
|
||||
export interface Editor { id: string; name: string; exe: string; args: string[] }
|
||||
// which editor to open code files in (persisted; "" → auto-pick)
|
||||
export function getEditor(): string { try { return localStorage.getItem("exd-editor") || ""; } catch { return ""; } }
|
||||
export function setEditorPref(id: string) { try { localStorage.setItem("exd-editor", id); } catch {} }
|
||||
|
||||
export interface Dir { dir?: string; [k: string]: unknown }
|
||||
// depot path -> short leaf name (last segment)
|
||||
export function leaf(path?: string): string {
|
||||
@ -186,3 +212,13 @@ export function kindOf(name: string): Kind {
|
||||
if (/\.(png|jpe?g|webp|gif|bmp|svg)$/.test(n)) return "image";
|
||||
return "code";
|
||||
}
|
||||
|
||||
// true only for real text/source files — gates the "Summary" and "Open in IDE"
|
||||
// buttons so they never appear on binaries (.pak/.dll/.exe/.bin/etc.) that
|
||||
// happen to fall through kindOf() as "code".
|
||||
const CODE_EXT = /\.(c|cc|cpp|cxx|h|hpp|hh|inl|cs|ts|tsx|js|jsx|mjs|cjs|py|rs|go|java|kt|kts|swift|m|mm|rb|php|lua|sh|bash|ps1|bat|cmd|sql|json|jsonc|yaml|yml|toml|ini|cfg|conf|xml|html|htm|css|scss|sass|less|md|markdown|txt|log|csv|gitignore|editorconfig|uproject|uplugin|build|target|usf|ush|hlsl|glsl|shader|cginc|props|targets|sln|vcxproj|csproj|gradle|cmake|make|mk)$/i;
|
||||
export function isCodeFile(name: string): boolean {
|
||||
const n = name.toLowerCase();
|
||||
if (/\.(uasset|umap|pak|exe|dll|so|dylib|bin|obj|lib|a|pdb|zip|7z|rar|gz|tar|png|jpe?g|gif|bmp|webp|svg|fbx|obj|gltf|glb|stl|ply|dae|wav|mp3|ogg|mp4|mov|ttf|otf)$/i.test(n)) return false;
|
||||
return CODE_EXT.test(n);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user