Archilyzer · Source

archilyzer

Archilyzer
git clone https://archilyzer.pages.dev/source/archilyzer.git
Log | Files | Refs | README | LICENSE

commit 59ae09eea6ee07aeaccfafbb57bddcf6c87e36a1
parent ad302e7b45bc7915afa7f469da5b87731c2094f4
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Sat, 26 Sep 2026 19:01:36 -0400

editor: the Cut release form's action is a FormData adapter over cutReleaseForWorkspace; /sites reads the export changelog the form cuts

Fields, labels, refusals and the returned state are unchanged; the three
revalidations move to revalidateAfterReleaseCut, which the ops route shares.
/sites read `<exportDir>/CHANGELOG.md` while the action wrote
`exportChangelogFile` — the same file unless EXPORT_CHANGELOG_FILE is set,
which the e2e server now does (the ops-route commit), so the page reads the writer's.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

Diffstat:
Meditor/app/sites/lib/cutReleaseAction.ts | 103+++++++++++++------------------------------------------------------------------
Aeditor/app/sites/lib/revalidateAfterReleaseCut.ts | 12++++++++++++
Meditor/app/sites/page.tsx | 6++++--
3 files changed, 32 insertions(+), 89 deletions(-)

diff --git a/editor/app/sites/lib/cutReleaseAction.ts b/editor/app/sites/lib/cutReleaseAction.ts @@ -1,35 +1,18 @@ "use server"; -import fs from "node:fs"; -import path from "node:path"; -import { revalidatePath } from "next/cache"; -import { getPaths } from "yt-dlp-transcript-common/lib/paths"; -import { - cutRelease, - CutReleaseError, -} from "yt-dlp-transcript-common/lib/changelog"; -import { commitPath, listDirtyPaths } from "yt-dlp-transcript-common/lib/git"; -import { writeFileAtomic } from "yt-dlp-transcript-common/lib/jsonFile-server"; +import { cutReleaseForWorkspace } from "yt-dlp-transcript-common/controller/cutRelease"; +import { revalidateAfterReleaseCut } from "./revalidateAfterReleaseCut"; + +// The "Cut release" form's server action: a FormData adapter over the one +// writer, `cutReleaseForWorkspace` (common/controller/cutRelease.ts), which the +// CLI (`archilyzer release cut`) and POST /api/ops/cut-release call too. The +// dirty-tree guard, the cut, the atomic write, the commit and every refusal's +// sentence live there. export type CutReleaseState = | { ok: true; version: string; committed: boolean } | { ok: false; error: string }; -function changelogPathFor(workspace: "editor" | "export"): string { - const paths = getPaths(); - return workspace === "export" - ? paths.exportChangelogFile - : paths.editorChangelogFile; -} - -function todayISO(): string { - const now = new Date(); - const y = now.getFullYear().toString().padStart(4, "0"); - const m = (now.getMonth() + 1).toString().padStart(2, "0"); - const d = now.getDate().toString().padStart(2, "0"); - return `${y}-${m}-${d}`; -} - export async function cutReleaseAction( _prev: CutReleaseState | undefined, formData: FormData, @@ -50,66 +33,12 @@ export async function cutReleaseAction( if (!version) { return { ok: false, error: "Version is required." }; } - const paths = getPaths(); - const filePath = changelogPathFor(workspace); - const relPath = path.relative(paths.monorepoRoot, filePath); - // Before mutating anything, refuse to commit over an otherwise-dirty tree so - // the release commit contains only the changelog. A dirty changelog itself is - // fine (uncommitted [Unreleased] bullets get folded into the release commit). - if (shouldCommit) { - let dirty: string[]; - try { - dirty = await listDirtyPaths(paths.monorepoRoot); - } catch (err) { - return { - ok: false, - error: `Could not check git status: ${(err as Error).message}`, - }; - } - const others = dirty.filter((p) => p !== relPath); - if (others.length > 0) { - return { - ok: false, - error: `Other uncommitted changes present (${others.join( - ", ", - )}). Commit or stash them before cutting a release.`, - }; - } - } - let source: string; - try { - source = await fs.promises.readFile(filePath, "utf8"); - } catch (err) { - return { - ok: false, - error: `Could not read ${filePath}: ${(err as Error).message}`, - }; - } - let next: string; - try { - next = cutRelease(source, version, todayISO()); - } catch (err) { - if (err instanceof CutReleaseError) { - return { ok: false, error: err.message }; - } - throw err; - } - await writeFileAtomic(filePath, next); - if (shouldCommit) { - const result = await commitPath( - paths.monorepoRoot, - relPath, - `Release ${workspace} ${version}`, - ); - if (!result.ok) { - return { - ok: false, - error: `Cut release ${version}, but the commit failed: ${result.error}`, - }; - } - } - revalidatePath("/"); - revalidatePath("/changelog"); - revalidatePath("/sites"); - return { ok: true, version, committed: shouldCommit }; + const result = await cutReleaseForWorkspace({ + workspace, + version, + commit: shouldCommit, + }); + if (!result.ok) return { ok: false, error: result.error }; + revalidateAfterReleaseCut(); + return { ok: true, version: result.version, committed: result.committed }; } diff --git a/editor/app/sites/lib/revalidateAfterReleaseCut.ts b/editor/app/sites/lib/revalidateAfterReleaseCut.ts @@ -0,0 +1,12 @@ +import { revalidatePath } from "next/cache"; + +// The pages that render a changelog: the home page's "what's new", /changelog +// (the editor's notes and its Cut release form) and /sites (the export's notes +// and its form). Called after a cut by the form's server action and by +// POST /api/ops/cut-release — both run inside their request, so this is the +// plain `revalidatePath`, not the job-body `safeRevalidate`. +export function revalidateAfterReleaseCut(): void { + revalidatePath("/"); + revalidatePath("/changelog"); + revalidatePath("/sites"); +} diff --git a/editor/app/sites/page.tsx b/editor/app/sites/page.tsx @@ -1,5 +1,4 @@ import { readFileSync } from "node:fs"; -import path from "node:path"; import type { Metadata } from "next"; import Link from "next/link"; import { Changelog } from "yt-dlp-transcript-common/components/Changelog"; @@ -44,9 +43,12 @@ const BUILD_KINDS = new Set([ "archive-combined-live-chat", ]); +// The same file the Cut release form rewrites (`exportChangelogFile`, which +// EXPORT_CHANGELOG_FILE overrides for the e2e server), or the page would show +// one changelog while the form cut another. function loadExportChangelog(): string | null { try { - return readFileSync(path.join(getPaths().exportDir, "CHANGELOG.md"), "utf8"); + return readFileSync(getPaths().exportChangelogFile, "utf8"); } catch { return null; }