44 * Persists and loads local sync state and configuration.
55 */
66
7- import { readFile , writeFile , mkdir } from 'node:fs/promises' ;
7+ import { readFile , writeFile , mkdir , rename , unlink } from 'node:fs/promises' ;
88import { randomUUID } from 'node:crypto' ;
99import { hostname } from 'node:os' ;
10+ import { dirname } from 'node:path' ;
1011import type { SyncConfig , LocalSyncState , PathConfig } from '../types/index.js' ;
1112import { DEFAULT_CONFIG } from '../types/index.js' ;
1213
@@ -74,11 +75,10 @@ export function getTokenSource(): 'config' | 'env' | 'none' {
7475}
7576
7677/**
77- * Save plugin configuration to disk.
78+ * Save plugin configuration to disk (atomic write) .
7879 */
7980export async function saveConfig ( pathConfig : PathConfig , config : SyncConfig ) : Promise < void > {
80- await ensureDir ( pathConfig . configDir ) ;
81- await writeFile ( pathConfig . pluginConfigPath , JSON . stringify ( config , null , 2 ) , 'utf-8' ) ;
81+ await atomicWriteFile ( pathConfig . pluginConfigPath , JSON . stringify ( config , null , 2 ) ) ;
8282}
8383
8484/**
@@ -94,11 +94,10 @@ export async function loadLocalState(pathConfig: PathConfig): Promise<LocalSyncS
9494}
9595
9696/**
97- * Save local sync state to disk.
97+ * Save local sync state to disk (atomic write) .
9898 */
9999export async function saveLocalState ( pathConfig : PathConfig , state : LocalSyncState ) : Promise < void > {
100- await ensureDir ( pathConfig . dataDir ) ;
101- await writeFile ( pathConfig . localStatePath , JSON . stringify ( state , null , 2 ) , 'utf-8' ) ;
100+ await atomicWriteFile ( pathConfig . localStatePath , JSON . stringify ( state , null , 2 ) ) ;
102101}
103102
104103/**
@@ -140,3 +139,25 @@ export function createInitialConfig(token: string, defaults: typeof DEFAULT_CONF
140139async function ensureDir ( dirPath : string ) : Promise < void > {
141140 await mkdir ( dirPath , { recursive : true } ) ;
142141}
142+
143+ /**
144+ * Atomically write a file using write-to-temp-then-rename pattern.
145+ * This prevents partial writes from corrupting files when multiple
146+ * processes write simultaneously.
147+ */
148+ async function atomicWriteFile ( filePath : string , content : string ) : Promise < void > {
149+ const tempPath = `${ filePath } .${ String ( process . pid ) } .tmp` ;
150+ await ensureDir ( dirname ( filePath ) ) ;
151+ try {
152+ await writeFile ( tempPath , content , 'utf-8' ) ;
153+ await rename ( tempPath , filePath ) ;
154+ } catch ( error ) {
155+ // Clean up temp file on error
156+ try {
157+ await unlink ( tempPath ) ;
158+ } catch {
159+ // Ignore cleanup errors
160+ }
161+ throw error ;
162+ }
163+ }
0 commit comments