Skip to content

Commit 0a73310

Browse files
committed
🤖 refactor: remove atomicWrite proxy and make all writes async
Removed the atomicWrite.ts proxy file and import write-file-atomic directly. Converted all Config file writes to async, eliminating sync operations. Changes: - Deleted src/utils/atomicWrite.ts (unnecessary proxy) - Config.saveConfig() now async - Config.saveSecretsConfig() now async - Config.editConfig() now async - Config.updateProjectSecrets() now async - Updated all callers to await these methods - ExtensionMetadataService imports write-file-atomic directly - Updated tests to handle async config methods Benefits: - Simpler: no proxy layer, direct imports - Consistent: all file writes are now async - Non-blocking: no sync I/O operations blocking the event loop - Better performance: async I/O allows Node.js to handle other work
1 parent 044fdaf commit 0a73310

File tree

5 files changed

+26
-43
lines changed

5 files changed

+26
-43
lines changed

src/config.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,15 @@ describe("Config", () => {
3636
});
3737

3838
describe("getAllWorkspaceMetadata with migration", () => {
39-
it("should migrate legacy workspace without metadata file", () => {
39+
it("should migrate legacy workspace without metadata file", async () => {
4040
const projectPath = "/fake/project";
4141
const workspacePath = path.join(config.srcDir, "project", "feature-branch");
4242

4343
// Create workspace directory
4444
fs.mkdirSync(workspacePath, { recursive: true });
4545

4646
// Add workspace to config without metadata file
47-
config.editConfig((cfg) => {
47+
await config.editConfig((cfg) => {
4848
cfg.projects.set(projectPath, {
4949
workspaces: [{ path: workspacePath }],
5050
});
@@ -71,7 +71,7 @@ describe("Config", () => {
7171
expect(workspace.name).toBe("feature-branch");
7272
});
7373

74-
it("should use existing metadata file if present (legacy format)", () => {
74+
it("should use existing metadata file if present (legacy format)", async () => {
7575
const projectPath = "/fake/project";
7676
const workspaceName = "my-feature";
7777
const workspacePath = path.join(config.srcDir, "project", workspaceName);
@@ -95,7 +95,7 @@ describe("Config", () => {
9595
fs.writeFileSync(metadataPath, JSON.stringify(existingMetadata));
9696

9797
// Add workspace to config (without id/name, simulating legacy format)
98-
config.editConfig((cfg) => {
98+
await config.editConfig((cfg) => {
9999
cfg.projects.set(projectPath, {
100100
workspaces: [{ path: workspacePath }],
101101
});

src/config.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ import * as path from "path";
33
import * as os from "os";
44
import * as crypto from "crypto";
55
import * as jsonc from "jsonc-parser";
6+
import writeFileAtomic from "write-file-atomic";
67
import type { WorkspaceMetadata, FrontendWorkspaceMetadata } from "./types/workspace";
78
import type { Secret, SecretsConfig } from "./types/secrets";
89
import type { Workspace, ProjectConfig, ProjectsConfig } from "./types/project";
910
import type { RuntimeConfig } from "./types/runtime";
1011
import { DEFAULT_RUNTIME_CONFIG } from "./constants/workspace";
11-
import { writeFileAtomicallySync } from "./utils/atomicWrite";
1212

1313
// Re-export project types from dedicated types file (for preload usage)
1414
export type { Workspace, ProjectConfig, ProjectsConfig };
@@ -71,7 +71,7 @@ export class Config {
7171
};
7272
}
7373

74-
saveConfig(config: ProjectsConfig): void {
74+
async saveConfig(config: ProjectsConfig): Promise<void> {
7575
try {
7676
if (!fs.existsSync(this.rootDir)) {
7777
fs.mkdirSync(this.rootDir, { recursive: true });
@@ -81,7 +81,7 @@ export class Config {
8181
projects: Array.from(config.projects.entries()),
8282
};
8383

84-
writeFileAtomicallySync(this.configFile, JSON.stringify(data, null, 2));
84+
await writeFileAtomic(this.configFile, JSON.stringify(data, null, 2), "utf-8");
8585
} catch (error) {
8686
console.error("Error saving config:", error);
8787
}
@@ -91,10 +91,10 @@ export class Config {
9191
* Edit config atomically using a transformation function
9292
* @param fn Function that takes current config and returns modified config
9393
*/
94-
editConfig(fn: (config: ProjectsConfig) => ProjectsConfig): void {
94+
async editConfig(fn: (config: ProjectsConfig) => ProjectsConfig): Promise<void> {
9595
const config = this.loadConfigOrDefault();
9696
const newConfig = fn(config);
97-
this.saveConfig(newConfig);
97+
await this.saveConfig(newConfig);
9898
}
9999

100100
private getProjectName(projectPath: string): string {
@@ -354,9 +354,9 @@ export class Config {
354354
}
355355
}
356356

357-
// Save config if we migrated any workspaces
357+
// Save config if we migrated any workspaces (fire and forget - don't block)
358358
if (configModified) {
359-
this.saveConfig(config);
359+
void this.saveConfig(config);
360360
}
361361

362362
return workspaceMetadata;
@@ -479,13 +479,13 @@ ${jsonString}`;
479479
* Save secrets configuration to JSON file
480480
* @param config The secrets configuration to save
481481
*/
482-
saveSecretsConfig(config: SecretsConfig): void {
482+
async saveSecretsConfig(config: SecretsConfig): Promise<void> {
483483
try {
484484
if (!fs.existsSync(this.rootDir)) {
485485
fs.mkdirSync(this.rootDir, { recursive: true });
486486
}
487487

488-
writeFileAtomicallySync(this.secretsFile, JSON.stringify(config, null, 2));
488+
await writeFileAtomic(this.secretsFile, JSON.stringify(config, null, 2), "utf-8");
489489
} catch (error) {
490490
console.error("Error saving secrets config:", error);
491491
throw error;
@@ -507,10 +507,10 @@ ${jsonString}`;
507507
* @param projectPath The path to the project
508508
* @param secrets The secrets to save for the project
509509
*/
510-
updateProjectSecrets(projectPath: string, secrets: Secret[]): void {
510+
async updateProjectSecrets(projectPath: string, secrets: Secret[]): Promise<void> {
511511
const config = this.loadSecretsConfig();
512512
config[projectPath] = secrets;
513-
this.saveSecretsConfig(config);
513+
await this.saveSecretsConfig(config);
514514
}
515515
}
516516

src/services/ExtensionMetadataService.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { dirname } from "path";
22
import { mkdir, readFile } from "fs/promises";
33
import { existsSync } from "fs";
4+
import writeFileAtomic from "write-file-atomic";
45
import {
56
type ExtensionMetadata,
67
type ExtensionMetadataFile,
78
getExtensionMetadataPath,
89
} from "@/utils/extensionMetadata";
9-
import { writeFileAtomically } from "@/utils/atomicWrite";
1010

1111
/**
1212
* Stateless service for managing workspace metadata used by VS Code extension integration.
@@ -78,7 +78,7 @@ export class ExtensionMetadataService {
7878
private async save(data: ExtensionMetadataFile): Promise<void> {
7979
try {
8080
const content = JSON.stringify(data, null, 2);
81-
await writeFileAtomically(this.filePath, content);
81+
await writeFileAtomic(this.filePath, content, "utf-8");
8282
} catch (error) {
8383
console.error("[ExtensionMetadataService] Failed to save metadata:", error);
8484
}

src/services/ipcMain.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@ export class IpcMain {
355355
// Note: metadata.json no longer written - config is the only source of truth
356356

357357
// Update config to include the new workspace (with full metadata)
358-
this.config.editConfig((config) => {
358+
await this.config.editConfig((config) => {
359359
let projectConfig = config.projects.get(projectPath);
360360
if (!projectConfig) {
361361
// Create project config if it doesn't exist
@@ -483,7 +483,7 @@ export class IpcMain {
483483
const { oldPath, newPath } = renameResult;
484484

485485
// Update config with new name and path
486-
this.config.editConfig((config) => {
486+
await this.config.editConfig((config) => {
487487
const projectConfig = config.projects.get(projectPath);
488488
if (projectConfig) {
489489
const workspaceEntry = projectConfig.workspaces.find((w) => w.path === oldPath);
@@ -1111,7 +1111,7 @@ export class IpcMain {
11111111
}
11121112
}
11131113
if (configUpdated) {
1114-
this.config.saveConfig(projectsConfig);
1114+
await this.config.saveConfig(projectsConfig);
11151115
}
11161116

11171117
// Emit metadata event for workspace removal (with null metadata to indicate deletion)
@@ -1210,7 +1210,7 @@ export class IpcMain {
12101210

12111211
// Add to config with normalized path
12121212
config.projects.set(normalizedPath, projectConfig);
1213-
this.config.saveConfig(config);
1213+
await this.config.saveConfig(config);
12141214

12151215
// Return both the config and the normalized path so frontend can use it
12161216
return Ok({ projectConfig, normalizedPath });
@@ -1220,7 +1220,7 @@ export class IpcMain {
12201220
}
12211221
});
12221222

1223-
ipcMain.handle(IPC_CHANNELS.PROJECT_REMOVE, (_event, projectPath: string) => {
1223+
ipcMain.handle(IPC_CHANNELS.PROJECT_REMOVE, async (_event, projectPath: string) => {
12241224
try {
12251225
const config = this.config.loadConfigOrDefault();
12261226
const projectConfig = config.projects.get(projectPath);
@@ -1238,11 +1238,11 @@ export class IpcMain {
12381238

12391239
// Remove project from config
12401240
config.projects.delete(projectPath);
1241-
this.config.saveConfig(config);
1241+
await this.config.saveConfig(config);
12421242

12431243
// Also remove project secrets if any
12441244
try {
1245-
this.config.updateProjectSecrets(projectPath, []);
1245+
await this.config.updateProjectSecrets(projectPath, []);
12461246
} catch (error) {
12471247
log.error(`Failed to clean up secrets for project ${projectPath}:`, error);
12481248
// Continue - don't fail the whole operation if secrets cleanup fails
@@ -1299,9 +1299,9 @@ export class IpcMain {
12991299

13001300
ipcMain.handle(
13011301
IPC_CHANNELS.PROJECT_SECRETS_UPDATE,
1302-
(_event, projectPath: string, secrets: Array<{ key: string; value: string }>) => {
1302+
async (_event, projectPath: string, secrets: Array<{ key: string; value: string }>) => {
13031303
try {
1304-
this.config.updateProjectSecrets(projectPath, secrets);
1304+
await this.config.updateProjectSecrets(projectPath, secrets);
13051305
return Ok(undefined);
13061306
} catch (error) {
13071307
const message = error instanceof Error ? error.message : String(error);

src/utils/atomicWrite.ts

Lines changed: 0 additions & 17 deletions
This file was deleted.

0 commit comments

Comments
 (0)