fix(store): back up unparseable connections/commands files before rebuild
This commit is contained in:
1 parent
7438d84d89
commit
655c660607
7 files changed
+320
-7
No files matched your search
+27
-3
@@ -20,7 +20,7 @@
|
||||
|
||||
import { app, shell } from 'electron'
|
||||
import { randomUUID } from 'crypto'
|
||||
import { mkdirSync, readFileSync, writeFileSync, existsSync, realpathSync, statSync } from 'fs'
|
||||
import { mkdirSync, readFileSync, writeFileSync, existsSync, realpathSync, statSync, copyFileSync } from 'fs'
|
||||
import { appendFile } from 'fs/promises'
|
||||
import { basename, dirname, join, resolve, sep } from 'path'
|
||||
import type { CommandItem, SessionLogMeta } from '../shared/commands'
|
||||
@@ -60,6 +60,29 @@ function stamp(d = new Date()): string {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A commands.json that exists but cannot be read or parsed must never be
|
||||
* silently destroyed: loadCommands falls back to an empty file, and the next
|
||||
* write (recording one command, or saving a library item) would rewrite the
|
||||
* file from that empty state — taking the whole command history *and* the
|
||||
* library with it. Keep a one-time copy the user can recover from before that
|
||||
* can happen.
|
||||
*/
|
||||
function backupUnparseableCommands(file: string, err: unknown): void {
|
||||
try {
|
||||
// ENOENT (first run) is the normal empty case: nothing to lose, stay quiet.
|
||||
if (!existsSync(file)) return
|
||||
const bak = `${file}.bak`
|
||||
if (existsSync(bak)) return // keep the first backup; later loads must not clobber it
|
||||
copyFileSync(file, bak)
|
||||
console.warn(
|
||||
`[commands] ${file} unparseable (${err instanceof Error ? err.message : String(err)}) — original kept at ${bak}`
|
||||
)
|
||||
} catch {
|
||||
// best effort: the backup must never break loading
|
||||
}
|
||||
}
|
||||
|
||||
export class CommandsStore {
|
||||
/** <userData>/commands.json */
|
||||
private readonly file: string
|
||||
@@ -107,8 +130,9 @@ export class CommandsStore {
|
||||
library: Array.isArray(data.library) ? data.library : []
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// missing / corrupted file -> start fresh
|
||||
} catch (err) {
|
||||
// missing -> start fresh; unreadable / corrupt -> keep the original first
|
||||
backupUnparseableCommands(this.file, err)
|
||||
}
|
||||
return emptyFile()
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
import { safeStorage } from 'electron'
|
||||
import { randomUUID } from 'crypto'
|
||||
import { readFileSync } from 'fs'
|
||||
import { copyFileSync, existsSync, readFileSync } from 'fs'
|
||||
import type { SshAuthMethod, SshConnection, SshConnectionInput } from '../shared/connections'
|
||||
import { writeJson } from './store'
|
||||
|
||||
@@ -144,6 +144,29 @@ function toPublic(stored: StoredConnection): SshConnection {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A connections.json that exists but cannot be read or parsed must never be
|
||||
* silently destroyed: load() falls back to an empty list, and the next write
|
||||
* (any bookmark edit, or just a `touch()` after connecting) would rewrite the
|
||||
* file from that empty list — taking every saved host and its `*_enc`
|
||||
* credentials with it. Keep a one-time copy the user can recover from before
|
||||
* that can happen.
|
||||
*/
|
||||
function backupUnparseableConnections(file: string, err: unknown): void {
|
||||
try {
|
||||
// ENOENT (first run) is the normal empty case: nothing to lose, stay quiet.
|
||||
if (!existsSync(file)) return
|
||||
const bak = `${file}.bak`
|
||||
if (existsSync(bak)) return // keep the first backup; later loads must not clobber it
|
||||
copyFileSync(file, bak)
|
||||
console.warn(
|
||||
`[connections] ${file} unparseable (${err instanceof Error ? err.message : String(err)}) — original kept at ${bak}`
|
||||
)
|
||||
} catch {
|
||||
// best effort: the backup must never break loading
|
||||
}
|
||||
}
|
||||
|
||||
export class ConnectionsStore {
|
||||
constructor(private readonly filePath: string) {}
|
||||
|
||||
@@ -159,8 +182,9 @@ export class ConnectionsStore {
|
||||
typeof (x as StoredConnection).host === 'string'
|
||||
)
|
||||
}
|
||||
} catch {
|
||||
// missing / corrupted file -> start fresh
|
||||
} catch (err) {
|
||||
// missing -> start fresh; unreadable / corrupt -> keep the original first
|
||||
backupUnparseableConnections(this.filePath, err)
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user