| 184 | |
| 185 | /** Update an artifact: writes a NEW version file + updated manifest. */ |
| 186 | export async function updateArtifact(cwd: string, input: UpdateInput, write: WriteFn, now = new Date().toISOString()): Promise<{ manifest: ArtifactManifest; absFile: string; version: number }> { |
| 187 | // Serialize the read-modify-write so concurrent updates to the SAME artifact |
| 188 | // don't both compute the same next version number and lose one. |
| 189 | return withLock(manifestLockPath(cwd, input.id), async () => { |
| 190 | const existing = await readManifest(cwd, input.id); |
| 191 | if (!existing) throw new Error(`Artifact "${input.id}" not found.`); |
| 192 | const v = nextVersionNumber(existing); |
| 193 | const rel = path.join(`v${v}`, entryFileName(existing.type)); |
| 194 | const absFile = path.join(artifactDir(cwd, input.id), rel); |
| 195 | await write(absFile, input.content); |
| 196 | const manifest = addVersion(existing, rel, now, input.note); |
| 197 | await writeFileAtomic(manifestPath(cwd, input.id), JSON.stringify(manifest, null, 2)); |
| 198 | return { manifest, absFile, version: v }; |
| 199 | }); |
| 200 | } |
| 201 | |
| 202 | /** List all artifacts (newest-updated first). */ |
| 203 | export async function listArtifacts(cwd: string): Promise<ArtifactSummary[]> { |