Files
cs249r_book/shared/release/release-card.html
Vijay Janapa Reddi b8183404b8 chore(release): shared versioning infrastructure
Lays foundation for unified release versioning across MLSysBook
publishable artifacts. Pure additions — no existing builds, configs,
or sources are touched.

scripts/version/release.py
  Python CLI with helpers:
  - compute-id: semver bump from previous tag (patch/minor/major/none/explicit)
  - compute-hash: deterministic SHA-256 over input directories with per-file index
  - emit-release: writes releases/<project>-<id>/release.json (canonical artifact)
  - emit-manifest: writes the build-time manifest the deployable bundles
  Tier A (citable) emits per-file Merkle index; Tier B (lite) is flat.

scripts/version/schema.json
  JSON Schema for release.json. Validates project/tier/release_id/release_hash
  + Tier A's files[] index. Used by validators in CI.

shared/release/release-pill.html
  Footer snippet — fetches deployable manifest at runtime, renders
  "v0.1.0 · Apr 26, 2026" pill. Configured per-project via
  <meta name="release-manifest"> tag. Silent on any fetch failure.

shared/release/release-card.html
  About-page snippet — fuller release-identity card with
  click-to-copy hash. Same fetch + meta-tag conventions.

shared/release/README.md
  Operator-facing contract documentation.

.github/workflows/_release-prepare.yml
  Reusable workflow_call. Validates confirm == "PUBLISH", computes
  new_release_id from previous tag + bump (delegates to release.py
  for canonical math). Outputs new_release_id/new_tag/previous_*
  for caller's downstream build and finalize steps. Refuses to
  re-tag existing releases (citation integrity).

Caller workflows still own their build commands and tag/release
creation; this only standardizes the input shape and version math.
2026-04-28 18:06:07 -04:00

120 lines
3.9 KiB
HTML

<!--
MLSysBook release-card — fuller release-identity card for About pages.
Like release-pill.html but bigger: shows release_id, build date, and
full hash with a click-to-copy button. Use on About pages, citation
contexts, or anywhere users may want to copy the release identity for
reference. Same manifest-fetch and meta-tag conventions as the pill.
Per-project setup is identical to release-pill.html — set the
<meta name="release-manifest"> tag once in _quarto.yml; embed
this snippet on the About page only.
-->
<section class="release-card" data-release-card aria-label="Release identity"></section>
<style>
.release-card {
display: block;
padding: 1rem;
border: 1px solid var(--bs-border-color, #dee2e6);
border-radius: 0.5rem;
background: var(--bs-tertiary-bg, #f8f9fa);
font-size: 0.875rem;
margin: 1rem 0;
}
.release-card-empty { display: none; }
.release-card-row {
display: flex;
flex-wrap: wrap;
gap: 0.5rem 1.5rem;
align-items: baseline;
margin-bottom: 0.25rem;
}
.release-card-version {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, monospace;
font-weight: 600;
font-size: 1rem;
}
.release-card-meta {
color: var(--bs-secondary, #6c757d);
font-size: 0.8rem;
}
.release-card-hash {
display: inline-flex;
align-items: baseline;
gap: 0.5rem;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, monospace;
font-size: 0.7rem;
color: var(--bs-secondary, #6c757d);
margin-top: 0.5rem;
word-break: break-all;
}
.release-card-copy {
cursor: pointer;
border: 1px solid var(--bs-border-color, #dee2e6);
background: var(--bs-body-bg, #fff);
color: inherit;
padding: 0.125rem 0.5rem;
border-radius: 0.25rem;
font-size: 0.65rem;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.release-card-copy:hover {
border-color: var(--bs-link-color, #0d6efd);
color: var(--bs-link-color, #0d6efd);
}
</style>
<script>
(function () {
var el = document.querySelector('[data-release-card]');
if (!el) return;
var meta = document.querySelector('meta[name="release-manifest"]');
var manifestPath =
el.getAttribute('data-manifest') ||
(meta && meta.getAttribute('content')) ||
'./release-manifest.json';
fetch(manifestPath, { cache: 'no-store' })
.then(function (res) { return res.ok ? res.json() : null; })
.then(function (m) {
if (!m || !m.releaseId || !m.releaseHash) {
el.classList.add('release-card-empty');
return;
}
var date = m.buildDate
? new Date(m.buildDate).toLocaleDateString('en-US',
{ year: 'numeric', month: 'long', day: 'numeric' })
: '';
var project = m.project || 'Release';
var html =
'<div class="release-card-row">' +
'<span class="release-card-version">' + project + ' v' + m.releaseId + '</span>' +
(date ? '<span class="release-card-meta">built ' + date + '</span>' : '') +
(m.tier ? '<span class="release-card-meta">tier ' + m.tier + '</span>' : '') +
'</div>' +
'<div class="release-card-hash" id="release-card-hash">' +
'<span>hash <span data-hash>' + m.releaseHash + '</span></span>' +
'<button type="button" class="release-card-copy" data-copy>copy</button>' +
'</div>';
el.id = 'release';
el.innerHTML = html;
var btn = el.querySelector('[data-copy]');
if (btn && navigator.clipboard) {
btn.addEventListener('click', function () {
navigator.clipboard.writeText(m.releaseHash).then(function () {
btn.textContent = 'copied';
setTimeout(function () { btn.textContent = 'copy'; }, 1500);
});
});
}
})
.catch(function () {
el.classList.add('release-card-empty');
});
})();
</script>