refactor(config): centralize workspace config helpers in openwork-server (1/3)#1997
refactor(config): centralize workspace config helpers in openwork-server (1/3)#1997benjaminshafii wants to merge 1 commit into
Conversation
Make apps/server the single source of truth for the per-workspace openwork.json schema/defaults and the opencode.json default writer: - workspace-files.ts now exports WorkspaceOpenworkConfig plus defaultWorkspaceOpenworkConfig / readWorkspaceOpenworkConfig / writeWorkspaceOpenworkConfig (pure, no Electron return shape). - workspace-init.ts exports ensureOpencodeConfig and its ensureWorkspaceOpenworkConfig reuses the shared default/writer. - Electron main.mjs imports these lazily from the compiled server bundle (absolute-path dynamic import, mirroring runtime.mjs's dev-vs-packaged candidate resolution) and drops its drifting local copies of defaultWorkspaceOpenworkConfig, read/writeWorkspaceOpenworkConfig, ensureDefaultWorkspaceOpencodeConfig and workspaceOpencodeConfigPath. Behavior-preserving: same file paths, same schema, same config content. The check-electron-bridge validator only matches case labels, so the swapped handler bodies are unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
2 issues found across 3 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/desktop/electron/main.mjs">
<violation number="1" location="apps/desktop/electron/main.mjs:1443">
P2: If `import()` rejects (e.g., the bundle file exists but fails to load), the rejected promise is cached forever and never retried. Wrap the async body in try/catch and reset `serverConfigHelpersPromise = null` on any failure, not just the file-existence check.</violation>
</file>
<file name="apps/server/src/workspace-files.ts">
<violation number="1" location="apps/server/src/workspace-files.ts:77">
P2: Wrap `JSON.parse(raw)` in a try-catch and fall back to defaults on parse failure. The file is user-editable, and a malformed `openwork.json` will propagate an unhandled `SyntaxError` to callers that don't guard against it.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
| : [...packagedPaths, devPath]; | ||
| } | ||
|
|
||
| function loadServerConfigHelpers() { |
There was a problem hiding this comment.
P2: If import() rejects (e.g., the bundle file exists but fails to load), the rejected promise is cached forever and never retried. Wrap the async body in try/catch and reset serverConfigHelpersPromise = null on any failure, not just the file-existence check.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/electron/main.mjs, line 1443:
<comment>If `import()` rejects (e.g., the bundle file exists but fails to load), the rejected promise is cached forever and never retried. Wrap the async body in try/catch and reset `serverConfigHelpersPromise = null` on any failure, not just the file-existence check.</comment>
<file context>
@@ -1424,43 +1423,46 @@ function validateSkillName(raw) {
+ : [...packagedPaths, devPath];
+}
+
+function loadServerConfigHelpers() {
+ if (serverConfigHelpersPromise) return serverConfigHelpersPromise;
+ serverConfigHelpersPromise = (async () => {
</file context>
| const raw = await readFile(configPath, "utf8"); | ||
| return JSON.parse(raw) as WorkspaceOpenworkConfig; |
There was a problem hiding this comment.
P2: Wrap JSON.parse(raw) in a try-catch and fall back to defaults on parse failure. The file is user-editable, and a malformed openwork.json will propagate an unhandled SyntaxError to callers that don't guard against it.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/server/src/workspace-files.ts, line 77:
<comment>Wrap `JSON.parse(raw)` in a try-catch and fall back to defaults on parse failure. The file is user-editable, and a malformed `openwork.json` will propagate an unhandled `SyntaxError` to callers that don't guard against it.</comment>
<file context>
@@ -28,3 +29,61 @@ export function projectCommandsDir(workspaceRoot: string): string {
+ if (!existsSync(configPath)) {
+ return defaultWorkspaceOpenworkConfig(workspaceRoot);
+ }
+ const raw = await readFile(configPath, "utf8");
+ return JSON.parse(raw) as WorkspaceOpenworkConfig;
+}
</file context>
| const raw = await readFile(configPath, "utf8"); | |
| return JSON.parse(raw) as WorkspaceOpenworkConfig; | |
| try { | |
| const raw = await readFile(configPath, "utf8"); | |
| return JSON.parse(raw) as WorkspaceOpenworkConfig; | |
| } catch { | |
| return defaultWorkspaceOpenworkConfig(workspaceRoot); | |
| } |
|
please wait for #1999 |
What
First of a 3-PR cleanup of how OpenWork stores/injects config, plugins, and extensions. This one is a pure, behavior-preserving refactor — the foundation the next two build on.
Makes
apps/serverthe single source of truth for:openwork.jsonschema + defaultsopencode.jsonwriter…and has the Electron main process consume them instead of keeping its own drifting copies.
Why
The same path-resolution,
openwork.jsonschema, andopencode.jsondefault were defined twice — once inapps/server/src(the in-process server that actually boots on every launch) and once hand-rolled inapps/desktop/electron/main.mjs(the IPC handlers). They had already drifted. This collapses them to one definition.Changes
apps/server/src/workspace-files.ts— now exportsWorkspaceOpenworkConfig+defaultWorkspaceOpenworkConfig/readWorkspaceOpenworkConfig/writeWorkspaceOpenworkConfig(pure; the write helper returns the path string, no Electron-shaped return value).apps/server/src/workspace-init.ts— exportsensureOpencodeConfig; itsensureWorkspaceOpenworkConfignow reuses the shared default + writer (removes the duplicate inline schema).apps/desktop/electron/main.mjs— imports those helpers lazily from the compiled server bundle by absolute path, mirroring the existingruntime.mjs:1052dev-vs-packaged resolution. Drops the localdefaultWorkspaceOpenworkConfig,read/writeWorkspaceOpenworkConfig,ensureDefaultWorkspaceOpencodeConfig,workspaceOpencodeConfigPath, and the now-unusedBROWSER_PLUGINconst. The IPC handlers keep their exact return contracts (execResultwrapping for writes, raw config object for reads).Behavior change
None. Same file paths, same schema, same config content.
check-electron-bridge.mjsonly matchescaselabels (not handler bodies), so the swapped internals don't affect the bridge contract.Verification
pnpm --filter openwork-server typecheck✅pnpm --filter openwork-server build✅ (exports present indist)bun testworkspace-init / jsonc / portable-files / portable-opencode — 15 pass ✅pnpm --filter @openwork/desktop typecheck:electron✅node apps/desktop/scripts/check-electron-bridge.mjs— 50 methods covered ✅node --testruntime + remote-workspace — 13 pass ✅Follow-ups (stacked on this branch)
openwork.jsonout of.opencode/→.openwork/(with migration)OPENCODE_CONFIG_CONTENT)🤖 Generated with Claude Code