Add database tools: snapshots and import/export (step 4)

Snapshot create/list/restore/delete and DB import/export via native
file dialogs, all routed through the streaming commandRunner from
step 3. `ddev snapshot restore` has no project-name flag (unlike every
other ddev command used so far) — it resolves the project from cwd,
so commandRunner/execDdev now accept an optional cwd and every
database command runs with cwd = project approot instead of passing
the name positionally.

Fixed a real bug found via live testing: the snapshot-naming UI used
window.prompt(), which Electron's renderer does not support (it
returns null with no dialog, silently). window.confirm() does work
(verified: real native dialog, respects accept/dismiss) so the delete
confirmation was left as-is. Snapshot naming now uses an inline text
input instead.

Verified end-to-end against the real scratch DDEV project via CDP-
driven clicks: named snapshot creation, deletion, and restore
(streamed output ending in "Database snapshot restoretest was
restored in 7s") all confirmed working through the actual UI.
import-db/export-db verified via direct CLI round-trip using the
exact --file= flag syntax the app invokes (skipped UI-driving these
since they open native OS file dialogs that CDP cannot dismiss).
This commit is contained in:
R3ap3R
2026-08-02 23:51:09 -05:00
parent 199c4d254e
commit 9f67b13a47
9 changed files with 396 additions and 7 deletions
+18
View File
@@ -3,6 +3,7 @@ import { electronAPI } from '@electron-toolkit/preload'
import type {
DdevProjectDetail,
DdevProjectSummary,
DdevSnapshot,
TerminalDataEvent,
TerminalExitEvent
} from '../shared/types'
@@ -33,6 +34,23 @@ const api = {
ipcRenderer.on('terminal:exit', listener)
return () => ipcRenderer.removeListener('terminal:exit', listener)
}
},
database: {
listSnapshots: (name: string, approot: string): Promise<DdevSnapshot[]> =>
ipcRenderer.invoke('database:listSnapshots', name, approot),
createSnapshot: (operationId: string, approot: string, snapshotName?: string): Promise<void> =>
ipcRenderer.invoke('database:createSnapshot', operationId, approot, snapshotName),
restoreSnapshot: (operationId: string, approot: string, snapshotName: string): Promise<void> =>
ipcRenderer.invoke('database:restoreSnapshot', operationId, approot, snapshotName),
deleteSnapshot: (operationId: string, approot: string, snapshotName: string): Promise<void> =>
ipcRenderer.invoke('database:deleteSnapshot', operationId, approot, snapshotName),
importFile: (operationId: string, approot: string, filePath: string): Promise<void> =>
ipcRenderer.invoke('database:importFile', operationId, approot, filePath),
exportFile: (operationId: string, approot: string, filePath: string): Promise<void> =>
ipcRenderer.invoke('database:exportFile', operationId, approot, filePath),
pickImportFile: (): Promise<string | null> => ipcRenderer.invoke('database:pickImportFile'),
pickExportPath: (defaultFileName: string): Promise<string | null> =>
ipcRenderer.invoke('database:pickExportPath', defaultFileName)
}
}