Harden audit findings: correctness, type-safety, Windows conventions & polish
Audit-pass over CODE-AUDIT.md (~48 items closed this pass; all verified — typecheck + 234 tests + eslint + prettier green). Correctness / bugs: - B3: match the release checksum to the asset's filename line (no wrong-hash verify) - B4: newline-safe metadata probe (one --print with a unit-separator delimiter) - B5 / L88: guard the meta event against canceled items; progress no longer promotes a queued item outside pump() - B7: cookie-login promise always resolves (handles destroy-without-close) - L146: trim parser rejects >2 colon-group times; M36: Library selection counts only actionable rows - L11 / L50 / L156 / L57 / L159 / L15 / L3: live queue count, empty-cookie message, schedule picker min, dead-code/comment cleanup Type safety: - Enable noUncheckedIndexedAccess + noFallthroughCasesInSwitch (15 real edge cases fixed) Resilience / Windows / metadata: - R5: settings write failure handled (no unhandled IPC rejection; reconciles to truth) - W1 / W5 / W6: min window size, seeded folder picker, parented sign-in window; L147 dead macOS branches removed - CL1: shared stdout markers; package/builder metadata (license, homepage, repository, copyright, tsbuildinfo glob) Copy / docs / tests: - M37 / SR9 dev-jargon cleanup in hints; M8 / M25 / M26 / L66 / L80 / L81 reconciled - New unit tests for L35 (isValidMediaItem) and L36 (compareVersions) This commit also checkpoints the previously-uncommitted feat/tray-background-clipboard work it builds on: background running + auto-download, library clipboard detection, tray, binary management & library scale, credential encryption at rest, the shared jsonStore and ui/ primitives, and the eslint/prettier tooling. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+11
-34
@@ -1,38 +1,21 @@
|
||||
import { app } from 'electron'
|
||||
import { join } from 'path'
|
||||
import { readFileSync, writeFileSync, existsSync } from 'fs'
|
||||
import type { CommandTemplate } from '@shared/ipc'
|
||||
import { isTemplateLike } from './validation'
|
||||
import { createJsonStore } from './jsonStore'
|
||||
|
||||
// Plain JSON in userData, same shape as history.ts.
|
||||
// Plain JSON in userData, same shape as history.ts. Atomic writes / corruption
|
||||
// backup / caching come from the shared jsonStore (R1–R3).
|
||||
const MAX_TEMPLATES = 100
|
||||
|
||||
function templatesFile(): string {
|
||||
return join(app.getPath('userData'), 'templates.json')
|
||||
}
|
||||
|
||||
// A persisted template entry must at least be an object carrying an id (see
|
||||
// isTemplateLike in validation.ts); everything else is coerced by sanitize().
|
||||
// Drop anything that isn't, so a hand-edited templates.json can't inject
|
||||
// malformed entries. (audit S5)
|
||||
export function listTemplates(): CommandTemplate[] {
|
||||
try {
|
||||
if (!existsSync(templatesFile())) return []
|
||||
const data = JSON.parse(readFileSync(templatesFile(), 'utf8'))
|
||||
// Validate shape, then normalise each surviving entry through sanitize() so
|
||||
// name/args are always well-formed strings regardless of what was on disk.
|
||||
return Array.isArray(data) ? data.filter(isTemplateLike).map(sanitize) : []
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
const store = createJsonStore('templates.json', isTemplateLike, MAX_TEMPLATES)
|
||||
|
||||
function save(templates: CommandTemplate[]): void {
|
||||
try {
|
||||
writeFileSync(templatesFile(), JSON.stringify(templates.slice(0, MAX_TEMPLATES), null, 2))
|
||||
} catch {
|
||||
/* best-effort; a read-only data dir just means no persisted templates */
|
||||
}
|
||||
export function listTemplates(): CommandTemplate[] {
|
||||
// Normalise each surviving entry through sanitize() so name/args are always
|
||||
// well-formed strings regardless of what was on disk.
|
||||
return store.read().map(sanitize)
|
||||
}
|
||||
|
||||
function sanitize(t: CommandTemplate): CommandTemplate {
|
||||
@@ -49,20 +32,14 @@ function sanitize(t: CommandTemplate): CommandTemplate {
|
||||
/** Add a new template, or update an existing one (matched by id). */
|
||||
export function saveTemplate(template: CommandTemplate): CommandTemplate[] {
|
||||
const clean = sanitize(template)
|
||||
const templates = [clean, ...listTemplates().filter((t) => t.id !== clean.id)]
|
||||
save(templates)
|
||||
return templates
|
||||
return store.write([clean, ...listTemplates().filter((t) => t.id !== clean.id)])
|
||||
}
|
||||
|
||||
export function removeTemplate(id: string): CommandTemplate[] {
|
||||
const templates = listTemplates().filter((t) => t.id !== id)
|
||||
save(templates)
|
||||
return templates
|
||||
return store.write(listTemplates().filter((t) => t.id !== id))
|
||||
}
|
||||
|
||||
/** Replace the entire template list (backup restore) rather than merge-by-id. */
|
||||
export function replaceTemplates(templates: CommandTemplate[]): CommandTemplate[] {
|
||||
const clean = templates.map(sanitize)
|
||||
save(clean)
|
||||
return clean
|
||||
return store.write(templates.map(sanitize))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user