( updater: (cfg: PersistedConfig) => PersistedConfig | Promise<PersistedConfig> )
| 670 | } |
| 671 | |
| 672 | export async function updateConfig( |
| 673 | updater: (cfg: PersistedConfig) => PersistedConfig | Promise<PersistedConfig> |
| 674 | ): Promise<PersistedConfig> { |
| 675 | let nextConfig = { ...DEFAULT_CONFIG } |
| 676 | configWriteQueue = configWriteQueue |
| 677 | .catch(() => {}) |
| 678 | .then(async () => { |
| 679 | // If the existing config is on disk but unreadable, refuse to write — |
| 680 | // otherwise a transient read failure (or a half-written file from a |
| 681 | // crash) would clobber the user's vault path. The in-memory update |
| 682 | // is still returned so callers behave as expected this session; only |
| 683 | // persistence is skipped. |
| 684 | const result = await loadConfigSafely() |
| 685 | if (!result.readable) { |
| 686 | nextConfig = normalizePersistedConfig(await updater({ ...DEFAULT_CONFIG })) |
| 687 | throw new Error('Refusing to persist over unreadable config') |
| 688 | } |
| 689 | nextConfig = normalizePersistedConfig(await updater(result.config)) |
| 690 | await saveConfig(nextConfig) |
| 691 | }) |
| 692 | try { |
| 693 | await configWriteQueue |
| 694 | } catch (err) { |
| 695 | // Don't propagate: a single failed write shouldn't crash the caller. |
| 696 | // The queue's own `.catch(() => {})` on the next call clears the |
| 697 | // rejection so subsequent writes can still proceed. |
| 698 | if ( |
| 699 | !(err instanceof Error) || |
| 700 | err.message !== 'Refusing to persist over unreadable config' |
| 701 | ) { |
| 702 | console.error('updateConfig failed', err) |
| 703 | } |
| 704 | } |
| 705 | return nextConfig |
| 706 | } |
| 707 | |
| 708 | function vaultSettingsPath(root: string): string { |
| 709 | return path.join(root, INTERNAL_VAULT_DIR, VAULT_SETTINGS_FILE) |
no test coverage detected