mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-14 16:17:25 +00:00
Merge remote-tracking branch 'origin/dev' into codex-issue-66-channel-details-admin-design
This commit is contained in:
commit
07bdec3c08
5 files changed
+152
-25
No files matched your search
@@ -10,6 +10,16 @@ permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
release-versioning:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
- name: Release versioning tests
|
||||
run: node --test scripts/ci/semantic-beta-version.test.mjs
|
||||
|
||||
go:
|
||||
runs-on: ubuntu-latest
|
||||
# Postgres service container for integration tests. pgvector/pgvector:pg18
|
||||
|
||||
@@ -91,6 +91,8 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Release versioning tests
|
||||
run: node --test scripts/ci/semantic-beta-version.test.mjs
|
||||
- name: Fetch upstream release tags
|
||||
run: git fetch --force --tags https://github.com/nextlevelbuilder/goclaw.git "refs/tags/v*:refs/tags/v*"
|
||||
- name: Compute semantic beta version
|
||||
@@ -105,6 +107,8 @@ jobs:
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
if ! git rev-parse "$TAG" >/dev/null 2>&1; then
|
||||
git tag -a "$TAG" -m "Release $TAG"
|
||||
fi
|
||||
if ! git ls-remote --exit-code --tags origin "refs/tags/$TAG" >/dev/null 2>&1; then
|
||||
git push origin "$TAG"
|
||||
fi
|
||||
- name: Upload release notes
|
||||
|
||||
@@ -79,23 +79,41 @@ function writeNoRelease(reason) {
|
||||
console.log(reason);
|
||||
}
|
||||
|
||||
const tags = git(["tag", "--merged", "HEAD", "--list", "v[0-9]*"])
|
||||
.split(/\r?\n/)
|
||||
.filter(Boolean)
|
||||
.map(parseVersion)
|
||||
.filter(Boolean)
|
||||
.map((tag) => ({ ...tag, commit: tagCommit(tag.raw) }))
|
||||
.sort(compareVersion)
|
||||
.reverse();
|
||||
function releaseTags(args = []) {
|
||||
return git(["tag", ...args, "--list", "v[0-9]*"])
|
||||
.split(/\r?\n/)
|
||||
.filter(Boolean)
|
||||
.map(parseVersion)
|
||||
.filter(Boolean)
|
||||
.map((tag) => ({ ...tag, commit: tagCommit(tag.raw) }))
|
||||
.sort(compareVersion)
|
||||
.reverse();
|
||||
}
|
||||
|
||||
const latest = tags[0];
|
||||
const latestStable = tags.find((tag) => !tag.preid);
|
||||
const latestPrerelease = tags.find((tag) => tag.preid === prerelease);
|
||||
const versionTags = releaseTags();
|
||||
const mergedTags = releaseTags(["--merged", "HEAD"]);
|
||||
const latest = versionTags[0];
|
||||
const latestStable = versionTags.find((tag) => !tag.preid);
|
||||
function isUsablePrerelease(tag) {
|
||||
return tag.preid !== prerelease || !latestStable || compareBase(tag, latestStable) > 0;
|
||||
}
|
||||
|
||||
const usableVersionTags = versionTags.filter(isUsablePrerelease);
|
||||
const usableMergedTags = mergedTags.filter(isUsablePrerelease);
|
||||
const latestPrerelease = usableVersionTags.find((tag) => tag.preid === prerelease);
|
||||
const latestMerged = usableMergedTags[0];
|
||||
const latestMergedStable = usableMergedTags.find((tag) => !tag.preid);
|
||||
const latestMergedPrerelease = usableMergedTags.find((tag) => tag.preid === prerelease);
|
||||
const head = git(["rev-parse", "HEAD"]);
|
||||
const previousTag = tags.find((tag) => tag.raw !== latest?.raw);
|
||||
const repairTag = latest?.preid === prerelease && latest.commit === head ? latest : null;
|
||||
const range = latest ? [`${latest.raw}..HEAD`] : [];
|
||||
const logRange = repairTag && previousTag ? [`${previousTag.raw}..HEAD`] : range;
|
||||
const zero = { major: 0, minor: 0, patch: 0 };
|
||||
const initial = parseVersion(initialVersion) || { major: 0, minor: 1, patch: 0 };
|
||||
const previousMergedTag = usableMergedTags.find((tag) => tag.raw !== latestMerged?.raw);
|
||||
const repairTag = latestMergedPrerelease?.commit === head
|
||||
&& (!latestStable || compareBase(latestMergedPrerelease, latestStable) > 0)
|
||||
? latestMergedPrerelease
|
||||
: null;
|
||||
const range = latestMerged ? [`${latestMerged.raw}..HEAD`] : [];
|
||||
const logRange = repairTag && previousMergedTag ? [`${previousMergedTag.raw}..HEAD`] : range;
|
||||
const log = git(["log", "--format=%B%x1e", ...logRange]);
|
||||
const messages = log.split("\x1e").map((message) => message.trim()).filter(Boolean);
|
||||
|
||||
@@ -126,6 +144,8 @@ if (repairTag) {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const releaseBase = maxBase(latestStable || initial || zero, latestMergedStable || initial || zero);
|
||||
|
||||
let level = "";
|
||||
for (const message of messages) {
|
||||
const next = commitLevel(message);
|
||||
@@ -138,14 +158,11 @@ for (const message of messages) {
|
||||
}
|
||||
|
||||
if (!level) {
|
||||
writeNoRelease("No release-worthy conventional commits found since the last release tag.");
|
||||
writeNoRelease("No release-worthy conventional commits found since the last merged release tag.");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const zero = { major: 0, minor: 0, patch: 0 };
|
||||
const initial = parseVersion(initialVersion) || { major: 0, minor: 1, patch: 0 };
|
||||
const stableBase = latestStable || initial || zero;
|
||||
const bumpedBase = bump(stableBase, level);
|
||||
const bumpedBase = bump(releaseBase, level);
|
||||
const prereleaseBase = latestPrerelease || zero;
|
||||
let targetBase = maxBase(bumpedBase, prereleaseBase);
|
||||
if (!latest && compareBase(targetBase, initial) < 0) targetBase = initial;
|
||||
@@ -156,7 +173,7 @@ const nextNumber = compareBase(targetBase, prereleaseBase) === 0
|
||||
const version = `${versionText(targetBase)}-${prerelease}.${nextNumber}`;
|
||||
const tag = `v${version}`;
|
||||
|
||||
if (tags.some((existing) => existing.raw === tag)) {
|
||||
if (versionTags.some((existing) => existing.raw === tag)) {
|
||||
writeNoRelease(`Computed tag ${tag} already exists.`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { copyFileSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { dirname, join, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import test from "node:test";
|
||||
|
||||
const here = dirname(fileURLToPath(import.meta.url));
|
||||
const repoRoot = resolve(here, "../..");
|
||||
const scriptPath = join(repoRoot, "scripts/ci/semantic-beta-version.mjs");
|
||||
|
||||
function run(cwd, args, env = {}) {
|
||||
return execFileSync(args[0], args.slice(1), {
|
||||
cwd,
|
||||
encoding: "utf8",
|
||||
env: { ...process.env, ...env },
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
}).trim();
|
||||
}
|
||||
|
||||
function git(cwd, ...args) {
|
||||
return run(cwd, ["git", ...args]);
|
||||
}
|
||||
|
||||
function writeAndCommit(cwd, name, message) {
|
||||
writeFileSync(join(cwd, name), `${message}\n`);
|
||||
git(cwd, "add", name);
|
||||
git(cwd, "commit", "-m", message);
|
||||
}
|
||||
|
||||
function tag(cwd, name) {
|
||||
git(cwd, "tag", "-a", name, "-m", `Release ${name}`);
|
||||
}
|
||||
|
||||
function createRepo() {
|
||||
const cwd = mkdtempSync(join(tmpdir(), "goclaw-beta-version-"));
|
||||
mkdirSync(join(cwd, "scripts/ci"), { recursive: true });
|
||||
copyFileSync(scriptPath, join(cwd, "scripts/ci/semantic-beta-version.mjs"));
|
||||
git(cwd, "init", "-b", "main");
|
||||
git(cwd, "config", "user.name", "Version Test");
|
||||
git(cwd, "config", "user.email", "version-test@example.com");
|
||||
|
||||
writeAndCommit(cwd, "base.txt", "fix(base): initial stable");
|
||||
tag(cwd, "v3.11.3");
|
||||
|
||||
git(cwd, "checkout", "-b", "dev");
|
||||
writeAndCommit(cwd, "old-beta.txt", "fix(runtime): old beta fix");
|
||||
tag(cwd, "v3.12.0-beta.41");
|
||||
|
||||
git(cwd, "checkout", "main");
|
||||
writeAndCommit(cwd, "stable.txt", "feat(release): stable milestone");
|
||||
tag(cwd, "v3.12.0");
|
||||
|
||||
git(cwd, "checkout", "dev");
|
||||
writeAndCommit(cwd, "current.txt", "feat(api): new work after stable");
|
||||
return cwd;
|
||||
}
|
||||
|
||||
function runVersionScript(cwd) {
|
||||
return run(cwd, ["node", "scripts/ci/semantic-beta-version.mjs"], {
|
||||
GITHUB_OUTPUT: "",
|
||||
INITIAL_VERSION: "3.11.3",
|
||||
PRERELEASE_ID: "beta",
|
||||
});
|
||||
}
|
||||
|
||||
test("bumps beta above latest stable even when stable tag is not merged into dev", () => {
|
||||
const cwd = createRepo();
|
||||
try {
|
||||
const output = runVersionScript(cwd);
|
||||
assert.match(output, /version=3\.13\.0-beta\.1/);
|
||||
assert.match(output, /tag=v3\.13\.0-beta\.1/);
|
||||
} finally {
|
||||
rmSync(cwd, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("continues prerelease numbering when upstream already has the next beta base", () => {
|
||||
const cwd = createRepo();
|
||||
try {
|
||||
git(cwd, "checkout", "-b", "upstream-dev");
|
||||
writeAndCommit(cwd, "upstream.txt", "fix(vault): upstream beta work");
|
||||
tag(cwd, "v3.13.0-beta.2");
|
||||
git(cwd, "checkout", "dev");
|
||||
|
||||
const output = runVersionScript(cwd);
|
||||
assert.match(output, /version=3\.13\.0-beta\.3/);
|
||||
assert.match(output, /tag=v3\.13\.0-beta\.3/);
|
||||
} finally {
|
||||
rmSync(cwd, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("dev beta workflow pushes missing origin tags even if a fetched tag exists locally", () => {
|
||||
const workflow = readFileSync(join(repoRoot, ".github/workflows/dev-beta-release.yaml"), "utf8");
|
||||
assert.match(workflow, /git rev-parse "\$TAG"/);
|
||||
assert.match(workflow, /git ls-remote --exit-code --tags origin "refs\/tags\/\$TAG"/);
|
||||
assert.match(workflow, /git push origin "\$TAG"/);
|
||||
});
|
||||
@@ -16,8 +16,6 @@ import (
|
||||
// while a Chat call is in flight properly closes the HTTP connection.
|
||||
// Uses a stalling SSE server that only responds if data flows from it.
|
||||
func TestAnthropicChat_AbortContextCancel(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Start a stalling SSE server
|
||||
server := newStallingSSEServer(t)
|
||||
defer server.Close()
|
||||
@@ -68,8 +66,6 @@ func TestAnthropicChat_AbortContextCancel(t *testing.T) {
|
||||
|
||||
// TestOpenAIChat_AbortContextCancel mirrors the Anthropic test for OpenAI-compatible providers.
|
||||
func TestOpenAIChat_AbortContextCancel(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
server := newStallingSSEServer(t)
|
||||
defer server.Close()
|
||||
|
||||
|
||||
Reference in new issue
Block a user