chore: prepare 0.0.3 release notes (#37)

This commit is contained in:
Ben Senescu 2026-03-16 21:53:36 -04:00 committed by GitHub
parent 23fb595755
commit abe0bf3c13
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 120 additions and 3 deletions

View File

@ -26,6 +26,7 @@ Rules:
After reviewing the generated notes: After reviewing the generated notes:
- confirm the `package.json` version matches the intended release tag when one is provided - confirm the `package.json` version matches the intended release tag when one is provided
- sanity-check that the notes only cover changes since the previous release
- tighten wording only when it improves clarity - tighten wording only when it improves clarity
- preserve the existing section structure unless there is a strong reason to merge sections - preserve the existing section structure unless there is a strong reason to merge sections
- suggest saving the finalized notes to `release-notes/v<version>.md` when a version is known - suggest saving the finalized notes to `release-notes/v<version>.md` when a version is known

View File

@ -11,6 +11,7 @@
], ],
"project": ["**/*.{js,mjs,ts,tsx}", "!src/routeTree.gen.ts", "!web/**"], "project": ["**/*.{js,mjs,ts,tsx}", "!src/routeTree.gen.ts", "!web/**"],
"ignore": ["drizzle-prod.config.ts"], "ignore": ["drizzle-prod.config.ts"],
"ignoreBinaries": ["tsx"],
// Disable Drizzle plugin - it tries to load drizzle.config.ts which imports cloudflare:workers // Disable Drizzle plugin - it tries to load drizzle.config.ts which imports cloudflare:workers
"drizzle": false, "drizzle": false,
"ignoreDependencies": [ "ignoreDependencies": [

View File

@ -2,7 +2,7 @@
"name": "open-seo", "name": "open-seo",
"private": true, "private": true,
"sideEffects": false, "sideEffects": false,
"version": "0.0.2", "version": "0.0.3",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "AUTH_MODE=local_noauth vite dev", "dev": "AUTH_MODE=local_noauth vite dev",

14
release-notes/v0.0.3.md Normal file
View File

@ -0,0 +1,14 @@
## Added
- Added the Backlinks page.
## Fixed
- Renamed from OpenRank back to OpenSEO.
- Fixed website styling issues.
## Docs
- Clarified how to update self-hosted Docker instances.
Full Changelog: https://github.com/bensenescu/open-seo/compare/v0.0.2...HEAD

View File

@ -3,10 +3,10 @@
// @ts-check // @ts-check
import { execFileSync } from "node:child_process"; import { execFileSync } from "node:child_process";
import { readFileSync, readdirSync, writeFileSync } from "node:fs";
import os from "node:os"; import os from "node:os";
import path from "node:path"; import path from "node:path";
import { parseArgs } from "node:util"; import { parseArgs } from "node:util";
import { writeFileSync } from "node:fs";
const argv = process.argv.slice(2); const argv = process.argv.slice(2);
const normalizedArgv = argv[0] === "--" ? argv.slice(1) : argv; const normalizedArgv = argv[0] === "--" ? argv.slice(1) : argv;
@ -54,6 +54,107 @@ function getLatestSemverTag() {
); );
} }
/** @typedef {{ major: number, minor: number, patch: number }} Semver */
/** @typedef {{ tag: string, version: Semver, source: "tag" | "release-notes" }} VersionCandidate */
/** @param {string} value */
function parseSemver(value) {
const match = value.match(/^v?(\d+)\.(\d+)\.(\d+)(?:[-+][0-9A-Za-z.-]+)?$/);
if (!match) return null;
return {
major: Number(match[1]),
minor: Number(match[2]),
patch: Number(match[3]),
};
}
/** @param {Semver} left @param {Semver} right */
function compareSemver(left, right) {
if (left.major !== right.major) return left.major - right.major;
if (left.minor !== right.minor) return left.minor - right.minor;
return left.patch - right.patch;
}
/** @param {unknown} value @returns {value is Record<string, unknown>} */
function isRecord(value) {
return Boolean(value) && typeof value === "object";
}
function getPackageVersion() {
/** @type {unknown} */
const packageJson = JSON.parse(readFileSync("package.json", "utf8"));
if (!isRecord(packageJson)) return null;
const version = packageJson.version;
return typeof version === "string" ? version : null;
}
function getReleaseNoteVersions() {
try {
return readdirSync("release-notes").flatMap((name) => {
const version = name.match(
/^v(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)\.md$/,
)?.[1];
return version ? [version] : [];
});
} catch {
return [];
}
}
/** @param {string[]} versionValues @param {VersionCandidate["source"]} source */
function collectVersionCandidates(versionValues, source) {
return versionValues.flatMap((value) => {
const version = parseSemver(value);
if (!version) return [];
return [{ tag: source === "tag" ? value : `v${value}`, version, source }];
});
}
function getDefaultFromTag() {
/** @type {string | null} */
const currentVersion = getPackageVersion();
const parsedCurrentVersion = currentVersion
? parseSemver(currentVersion)
: null;
if (!parsedCurrentVersion) return getLatestSemverTag();
const tagCandidates = collectVersionCandidates(
git(["tag", "--sort=-version:refname"])
.split("\n")
.map((tag) => tag.trim())
.filter(Boolean),
"tag",
);
const releaseNoteCandidates = collectVersionCandidates(
getReleaseNoteVersions(),
"release-notes",
);
const candidates = [...tagCandidates, ...releaseNoteCandidates]
.filter((entry) => compareSemver(entry.version, parsedCurrentVersion) < 0)
.reduce((sorted, entry) => {
const insertAt = sorted.findIndex(
(candidate) => compareSemver(entry.version, candidate.version) > 0,
);
if (insertAt === -1) {
sorted.push(entry);
} else {
sorted.splice(insertAt, 0, entry);
}
return sorted;
}, /** @type {VersionCandidate[]} */ ([]));
const bestVersion = candidates[0]?.tag;
if (!bestVersion) return getLatestSemverTag();
const matchingTag = tagCandidates.find((entry) => entry.tag === bestVersion);
if (matchingTag) return matchingTag.tag;
return getLatestSemverTag();
}
function getOriginRepo() { function getOriginRepo() {
const remote = git(["remote", "get-url", "origin"]); const remote = git(["remote", "get-url", "origin"]);
const match = remote.match(/github\.com[:/]([^/]+\/[^/.]+)(?:\.git)?$/); const match = remote.match(/github\.com[:/]([^/]+\/[^/.]+)(?:\.git)?$/);
@ -164,7 +265,7 @@ function buildNotes({ from, to, repo }) {
return lines.join("\n").trim(); return lines.join("\n").trim();
} }
const from = values.from ?? getLatestSemverTag(); const from = values.from ?? getDefaultFromTag();
const to = values.to; const to = values.to;
const repo = values.repo ?? getOriginRepo(); const repo = values.repo ?? getOriginRepo();
const notes = buildNotes({ from, to, repo }); const notes = buildNotes({ from, to, repo });