| 29 | | { ok: false; reason: 'invalid'; error: string } |
| 30 | |
| 31 | export async function readWorkspaceMetadata(wsDir: string): Promise<ReadWorkspaceMetadataResult> { |
| 32 | let raw: string | null |
| 33 | try { |
| 34 | raw = await readWorkspaceFile(wsDir, WORKSPACE_METADATA_REL) |
| 35 | } catch (err) { |
| 36 | return { ok: false, reason: 'invalid', error: err instanceof Error ? err.message : String(err) } |
| 37 | } |
| 38 | if (raw === null) return { ok: false, reason: 'absent' } |
| 39 | if (Buffer.byteLength(raw, 'utf8') > MAX_BYTES) { |
| 40 | return { ok: false, reason: 'invalid', error: `workspace metadata file too large (max ${MAX_BYTES} bytes)` } |
| 41 | } |
| 42 | |
| 43 | let parsed: unknown |
| 44 | try { |
| 45 | parsed = JSON.parse(raw) |
| 46 | } catch (err) { |
| 47 | return { ok: false, reason: 'invalid', error: `invalid JSON: ${err instanceof Error ? err.message : String(err)}` } |
| 48 | } |
| 49 | |
| 50 | const res = workspaceMetadataSchema.safeParse(parsed) |
| 51 | if (!res.success) { |
| 52 | return { ok: false, reason: 'invalid', error: res.error.issues.map((i) => `${i.path.join('.')}: ${i.message}`).join('; ') } |
| 53 | } |
| 54 | return { ok: true, metadata: res.data } |
| 55 | } |
| 56 | |
| 57 | export async function writeWorkspaceMetadata(wsDir: string, metadata: WorkspaceMetadata): Promise<WorkspaceMetadata> { |
| 58 | const parsed = workspaceMetadataSchema.parse(metadata) |