Skip to content

Commit 8093fbb

Browse files
committed
Add version-driven GitHub releases
1 parent 84c4951 commit 8093fbb

File tree

3 files changed

+88
-5
lines changed

3 files changed

+88
-5
lines changed

.github/workflows/release.yml

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
name: Create Release
2+
3+
env:
4+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
5+
6+
on:
7+
push:
8+
branches:
9+
- main
10+
paths:
11+
- VERSION
12+
workflow_dispatch:
13+
14+
permissions:
15+
contents: write
16+
17+
jobs:
18+
release:
19+
runs-on: ubuntu-latest
20+
steps:
21+
- name: Check out repository
22+
uses: actions/checkout@v4
23+
with:
24+
fetch-depth: 0
25+
26+
- name: Read release version
27+
id: version
28+
run: |
29+
version="$(tr -d '[:space:]' < VERSION)"
30+
version="${version#v}"
31+
32+
if ! printf '%s' "$version" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$'; then
33+
echo "VERSION must contain a semantic version like 1.2.3 or 1.2.3-beta.1"
34+
exit 1
35+
fi
36+
37+
echo "version=$version" >> "$GITHUB_OUTPUT"
38+
echo "tag=v$version" >> "$GITHUB_OUTPUT"
39+
40+
- name: Check whether the release already exists
41+
id: existing
42+
env:
43+
GH_TOKEN: ${{ github.token }}
44+
run: |
45+
if gh release view "${{ steps.version.outputs.tag }}" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
46+
echo "exists=true" >> "$GITHUB_OUTPUT"
47+
else
48+
echo "exists=false" >> "$GITHUB_OUTPUT"
49+
fi
50+
51+
- name: Create GitHub release
52+
if: steps.existing.outputs.exists != 'true'
53+
env:
54+
GH_TOKEN: ${{ github.token }}
55+
run: |
56+
gh release create "${{ steps.version.outputs.tag }}" \
57+
--repo "$GITHUB_REPOSITORY" \
58+
--target "$GITHUB_SHA" \
59+
--title "TPS ${{ steps.version.outputs.tag }}" \
60+
--generate-notes

VERSION

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
1.0.0

scripts/build-site.mjs

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import markdownItAnchor from "markdown-it-anchor";
99
const __dirname = path.dirname(fileURLToPath(import.meta.url));
1010
const rootDir = path.resolve(__dirname, "..");
1111
const readmePath = path.join(rootDir, "README.md");
12+
const versionPath = path.join(rootDir, "VERSION");
1213
const stylesPath = path.join(rootDir, "website", "site.css");
1314
const publicDir = path.join(rootDir, "public");
1415
const distDir = path.join(rootDir, "dist");
@@ -36,6 +37,7 @@ const emotionStyles = {
3637
};
3738

3839
const readme = await readFile(readmePath, "utf8");
40+
const version = normalizeVersion(await readFile(versionPath, "utf8"));
3941
const styles = await readFile(stylesPath, "utf8");
4042

4143
const md = new MarkdownIt({
@@ -69,19 +71,22 @@ const quickAnswers = buildQuickAnswers(summary);
6971
const keywords = buildKeywords();
7072
const buildDate = new Date();
7173
const dateModifiedIso = buildDate.toISOString();
74+
const releaseTag = `v${version}`;
7275
const structuredData = buildStructuredData({
7376
dateModifiedIso,
7477
keywords,
7578
licenseUrl,
7679
quickAnswers,
7780
readmeUrl,
7881
repoUrl,
82+
releaseTag,
7983
sections,
8084
socialImageUrl,
8185
siteName,
8286
siteUrl,
8387
summary,
84-
title
88+
title,
89+
version
8590
});
8691
const builtAt = new Intl.DateTimeFormat("en", {
8792
dateStyle: "long",
@@ -101,6 +106,7 @@ const page = `<!DOCTYPE html>
101106
<title>${escapeHtml(title)}</title>
102107
<meta name="description" content="${escapeHtml(summary)}" />
103108
<meta name="keywords" content="${escapeHtml(keywords.join(", "))}" />
109+
<meta name="version" content="${escapeHtml(version)}" />
104110
<meta name="author" content="Managed Code" />
105111
<meta name="publisher" content="Managed Code" />
106112
<meta name="robots" content="index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1" />
@@ -186,6 +192,7 @@ const page = `<!DOCTYPE html>
186192
<article class="content-card" id="specification">
187193
<div class="content-meta" id="top">
188194
<span class="meta-chip">Source of truth <code>README.md</code></span>
195+
<span class="meta-chip">Version ${escapeHtml(releaseTag)}</span>
189196
<span class="meta-chip">${stats.sectionCount} sections / ${stats.subsectionCount} subsections</span>
190197
<span class="meta-chip">Built ${escapeHtml(builtAt)} UTC</span>
191198
</div>
@@ -209,7 +216,7 @@ await writeFile(path.join(distDir, "sitemap.xml"), buildSitemapXml(siteUrl, date
209216
await writeFile(path.join(distDir, "robots.txt"), buildRobotsTxt(siteUrl), "utf8");
210217
await writeFile(
211218
path.join(distDir, "llms.txt"),
212-
buildLlmsTxt({ licenseUrl, quickAnswers, readmeUrl, repoUrl, siteUrl, stats, summary, title }),
219+
buildLlmsTxt({ licenseUrl, quickAnswers, readmeUrl, releaseTag, repoUrl, siteUrl, stats, summary, title, version }),
213220
"utf8"
214221
);
215222

@@ -386,12 +393,14 @@ function buildStructuredData({
386393
quickAnswers,
387394
readmeUrl,
388395
repoUrl,
396+
releaseTag,
389397
sections,
390398
socialImageUrl,
391399
siteName,
392400
siteUrl,
393401
summary,
394-
title
402+
title,
403+
version
395404
}) {
396405
const primarySections = sections
397406
.filter((section) => section.depth === 2)
@@ -406,6 +415,7 @@ function buildStructuredData({
406415
url: siteUrl,
407416
name: siteName,
408417
description: summary,
418+
version,
409419
inLanguage: "en",
410420
publisher: {
411421
"@type": "Organization",
@@ -419,6 +429,7 @@ function buildStructuredData({
419429
headline: title,
420430
description: summary,
421431
url: siteUrl,
432+
version,
422433
mainEntityOfPage: siteUrl,
423434
isPartOf: {
424435
"@id": `${siteUrl}#website`
@@ -445,7 +456,7 @@ function buildStructuredData({
445456
dateModified: dateModifiedIso,
446457
inLanguage: "en",
447458
image: socialImageUrl,
448-
sameAs: [repoUrl, readmeUrl],
459+
sameAs: [repoUrl, readmeUrl, `${repoUrl}/releases/tag/${releaseTag}`],
449460
speakable: {
450461
"@type": "SpeakableSpecification",
451462
cssSelector: [".hero-summary", ".answer-answer"]
@@ -501,14 +512,16 @@ Sitemap: ${siteUrl}sitemap.xml
501512
`;
502513
}
503514

504-
function buildLlmsTxt({ licenseUrl, quickAnswers, readmeUrl, repoUrl, siteUrl, stats, summary, title }) {
515+
function buildLlmsTxt({ licenseUrl, quickAnswers, readmeUrl, releaseTag, repoUrl, siteUrl, stats, summary, title, version }) {
505516
return `# ${title}
506517
507518
> ${summary}
508519
509520
Canonical: ${siteUrl}
510521
Repository: ${repoUrl}
511522
Source of truth: ${readmeUrl}
523+
Version: ${version}
524+
Release tag: ${releaseTag}
512525
License: ${licenseUrl}
513526
514527
## Key Facts
@@ -552,3 +565,12 @@ function escapeHtml(value) {
552565
function escapeRegExp(value) {
553566
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
554567
}
568+
569+
function normalizeVersion(value) {
570+
const normalized = value.trim().replace(/^v/i, "");
571+
if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(normalized)) {
572+
throw new Error(`Invalid VERSION value: ${value.trim()}`);
573+
}
574+
575+
return normalized;
576+
}

0 commit comments

Comments
 (0)