Files
vercel__next.js/apps/bundle-analyzer/lib/snapshot.ts
Will Binns-Smith 57a8476086 bundle-analyzer: record historical snapshots on each analyze run (#93520)
After each `next experimental-analyze` run, snapshot the output data
into a rolling `history/` directory so later builds can use it as a
comparison baseline.

Each snapshot gets a `metadata.json` with git branch/sha/dirty status,
Next.js version, and route count. A `history/history.json` index
(newest-first, capped at 20 entries) lets the UI list available
baselines without re-reading every snapshot directory. Old snapshots
beyond the cap are pruned automatically.

The client-side `SnapshotMetadata` type mirror lives in
`apps/bundle-analyzer/lib/snapshot.ts` so the compare UI can reference
it without a build-time dependency on the Next.js package.

<!-- NEXT_JS_LLM_PR -->

---------

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Will Binns-Smith <755844+wbinnssmith@users.noreply.github.com>
2026-09-09 14:11:28 -07:00

66 lines
2.1 KiB
TypeScript

/**
* Mirrors {@link SnapshotMetadata} written by the build (see
* `packages/next/src/build/analyze/snapshot.ts`). Field semantics must stay in
* sync.
*/
export interface SnapshotMetadata {
id: string
createdAt: string
nextVersion?: string
gitBranch?: string
gitSha?: string
gitShortSha?: string
gitDirty?: boolean
/** First line of the HEAD commit message when available. */
gitMessage?: string
appDirOnly?: boolean
noMangling?: boolean
/** User-supplied baseline name, overriding branch/sha in display. See `--baseline-name`. */
baselineName?: string
routeCount: number
}
/** Mirrors {@link HistoryIndex}. */
export interface HistoryIndex {
snapshots: SnapshotMetadata[]
}
/**
* Returns a short, human-friendly label for a snapshot. Used by the picker
* and the diff header bar. Format prefers branch + short sha, falling back to
* timestamp when neither is available.
*/
export function formatSnapshotLabel(metadata: SnapshotMetadata): string {
if (metadata.baselineName) return metadata.baselineName
const sha = metadata.gitShortSha ? metadata.gitShortSha : null
const branch = metadata.gitBranch ?? null
if (branch && sha)
return `${branch}@${sha}${metadata.gitDirty ? ' (dirty)' : ''}`
if (sha) return `${sha}${metadata.gitDirty ? ' (dirty)' : ''}`
if (branch) return branch
return formatRelativeTime(metadata.createdAt)
}
/**
* Returns a relative time string for an ISO timestamp ("3m ago", "2h ago",
* "yesterday", "Jan 4"). Designed for compact list rows.
*/
export function formatRelativeTime(iso: string): string {
const then = new Date(iso).getTime()
if (Number.isNaN(then)) return iso
const diffMs = Date.now() - then
const seconds = Math.round(diffMs / 1000)
if (seconds < 60) return 'just now'
const minutes = Math.round(seconds / 60)
if (minutes < 60) return `${minutes}m ago`
const hours = Math.round(minutes / 60)
if (hours < 24) return `${hours}h ago`
const days = Math.round(hours / 24)
if (days === 1) return 'yesterday'
if (days < 14) return `${days}d ago`
return new Date(iso).toLocaleDateString(undefined, {
month: 'short',
day: 'numeric',
})
}