Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 31 additions & 2 deletions .github/workflows/changelog.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,41 @@ jobs:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Require an Unreleased changelog entry
persist-credentials: false
- name: Require a changelog entry or a versioned release
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
set -euo pipefail
VERSION=$(node -p "require('./package.json').version")
BASE_VERSION=$(git show "${BASE_SHA}:package.json" | node -pe "JSON.parse(require('fs').readFileSync(0, 'utf8')).version")
if [ "$VERSION" != "$BASE_VERSION" ]; then
node -e '
const [next, base] = process.argv.slice(1).map(v => v.split(".").map(Number));
const difference = next.map((part, i) => part - base[i]).find(part => part !== 0);
if (!(difference > 0)) process.exit(1);
' "$VERSION" "$BASE_VERSION"
FIRST_RELEASE=$(grep -m1 -E '^## \[[0-9]+\.[0-9]+\.[0-9]+\] - [0-9]{4}-[0-9]{2}-[0-9]{2}$' CHANGELOG.md)
if [[ "$FIRST_RELEASE" != "## [$VERSION] - "* ]]; then
echo "The latest dated release must match package.json." >&2
exit 1
fi
if git show "${BASE_SHA}:CHANGELOG.md" | grep -Fq "## [$VERSION]"; then
echo "The release version already exists in the base changelog." >&2
exit 1
fi
if ! awk -v heading="$FIRST_RELEASE" '
$0 == heading { in_release = 1; next }
in_release && /^## / { exit }
in_release && /^- [[:space:]]*[^[:space:]]/ { found = 1 }
END { exit !found }
' CHANGELOG.md; then
echo "The release section must contain a nonempty changelog bullet." >&2
exit 1
fi
exit 0
fi

extract_unreleased() {
awk '
/^## \[Unreleased\]$/ { in_unreleased = 1; next }
Expand Down
36 changes: 18 additions & 18 deletions .github/workflows/release-from-changelog.yml
Original file line number Diff line number Diff line change
@@ -1,36 +1,35 @@
name: Release from Changelog

on:
push:
branches: [ main ]
paths:
- 'CHANGELOG.md'
workflow_dispatch:
check_run:
types: [completed]

permissions:
contents: write

jobs:
changelog_release:
if: >-
github.event.check_run.conclusion == 'success' &&
github.event.check_run.app.slug == 'cloudflare-workers-and-pages' &&
github.event.check_run.name == 'Workers Builds: runesswap'
Comment thread
robin-liquidium marked this conversation as resolved.
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
ref: ${{ github.event.check_run.head_sha }}
persist-credentials: false

- name: Verify deployed commit belongs to main
run: git merge-base --is-ancestor HEAD origin/main

- name: Extract latest version and notes
id: extract
run: |
set -euo pipefail
# Ensure the top section is a version, not Unreleased
TOP_LINE=$(grep -m1 -E '^## \[' CHANGELOG.md || true)
if echo "$TOP_LINE" | grep -qE '^## \[Unreleased\]'; then
echo "Top CHANGELOG section is [Unreleased]; skipping release." >&2
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi
VERSION_LINE=$(grep -m1 -E '^## \[[0-9]+\.[0-9]+\.[0-9]+\]' CHANGELOG.md || true)
VERSION_LINE=$(grep -m1 -E '^## \[[0-9]+\.[0-9]+\.[0-9]+\] - [0-9]{4}-[0-9]{2}-[0-9]{2}$' CHANGELOG.md || true)
if [ -z "${VERSION_LINE}" ]; then
echo "No version section found; skipping."
echo "skip=true" >> "$GITHUB_OUTPUT"
Expand All @@ -45,13 +44,13 @@ jobs:
exit 0
fi

awk -v v="$VERSION" '
$0 ~ "^## \\[" v "\\]" {found=1; next}
found && $0 ~ "^## \\[" { exit }
awk -v heading="$VERSION_LINE" '
$0 == heading {found=1; next}
found && /^## / { exit }
found { print }
' CHANGELOG.md > RELEASE_NOTES.md

if [ ! -s RELEASE_NOTES.md ]; then
if ! grep -qE '^- [[:space:]]*[^[:space:]]' RELEASE_NOTES.md; then
echo "No notes captured for $VERSION; skipping."
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
Expand All @@ -76,6 +75,7 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ steps.extract.outputs.version }}
DEPLOYED_SHA: ${{ github.event.check_run.head_sha }}
run: |
set -euo pipefail
TAG="v${VERSION}"
Expand All @@ -84,5 +84,5 @@ jobs:
if gh release view "$TAG" >/dev/null 2>&1; then
gh release edit "$TAG" --title "$TITLE" --latest -F RELEASE_NOTES.md
else
gh release create "$TAG" --title "$TITLE" --latest -F RELEASE_NOTES.md --target "${GITHUB_SHA}"
gh release create "$TAG" --title "$TITLE" --latest -F RELEASE_NOTES.md --target "${DEPLOYED_SHA}"
fi
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@

## [Unreleased]

## [0.3.0] - 2026-09-07

### Fixed
- Aligned release checks with dated releases and kept unreleased notes out of the public changelog.
- Hardened PR CI by restricting token permissions and disabling persisted checkout credentials before running repository scripts.
- Prevented config-only commits from failing when lint-staged passed unsupported Markdown and YAML files to Biome.

### Added
- Automatic deployments from `main` through native Cloudflare Workers Builds, with GitHub releases published after Cloudflare reports a successful deployment.
- Automatic PR reviews in the opencode workflow (runs on PR open/update, reviews with Kimi k3).
- Add opencode GitHub workflow for automated PR reviews and `/oc` commands (Kimi k3).
- Centralized QueryClient defaults and query key factories.
Expand Down Expand Up @@ -273,7 +277,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- Initial release: swap interface, price chart, core API routes and foundational UI.
[Unreleased]: https://github.com/ropl-btc/RunesSwap.app/compare/v0.2.8...HEAD
[Unreleased]: https://github.com/robin-liquidium/RunesSwap.app/compare/v0.3.0...HEAD
[0.3.0]: https://github.com/robin-liquidium/RunesSwap.app/compare/v0.2.8...v0.3.0
[0.2.8]: https://github.com/ropl-btc/RunesSwap.app/compare/v0.2.7...v0.2.8
[0.2.7]: https://github.com/ropl-btc/RunesSwap.app/compare/v0.2.6...v0.2.7
[0.2.4]: https://github.com/ropl-btc/RunesSwap.app/compare/v0.2.3...v0.2.4
Expand Down
8 changes: 7 additions & 1 deletion docs/cloudflare-migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Hostinger is the domain registrar. Nameservers were changed to Cloudflare on 202

The zone initially mirrored Vercel's apex/wildcard aliases and CAA records to preserve service during DNS activation. No MX or TXT records were present at migration inventory time.

Both `runesswap.app` and `www.runesswap.app` are attached directly to the `runesswap` Worker and recorded in `wrangler.jsonc`. The apex Vercel CNAME was replaced; the wildcard record and CAA records remain. Future deployments use `bun run deploy` and retain both custom domains. Verify TLS, redirects, pages, static assets, and APIs after deployments.
Both `runesswap.app` and `www.runesswap.app` are attached directly to the `runesswap` Worker and recorded in `wrangler.jsonc`. The apex Vercel CNAME was replaced; the wildcard record and CAA records remain. Native Cloudflare Workers Builds connects the repository's `main` branch to production deployments. Its build command is `bun run ai-check`, its deploy command is `bunx --no-install wrangler deploy`, and its build variables are `BUN_VERSION=1.4.2` and `NODE_VERSION=22`. The Main Build GitHub workflow validates the application separately. GitHub releases are published only after Cloudflare's successful GitHub check, using the deployed commit. Manual deployments use `bun run deploy`; both paths retain the custom domains. Verify TLS, redirects, pages, static assets, and APIs after deployments.

## Rollback

Expand All @@ -36,3 +36,9 @@ A registrar-level rollback can restore Vercel's original nameservers while the V
## Verification boundaries

Automated checks can exercise validation, quotes, market data, routing, wallet menus, and mocked signing flows. Actual wallet signatures, swaps, borrowing, and repayment require Robin's final verification. Do not delete Vercel until that verification is complete.

## Releases

Feature PRs add notes under `Unreleased`. A release PR moves shipped notes into a dated version section and updates `package.json` to match. Keep an empty `Unreleased` section for subsequent work. The public changelog shows only dated releases. The changelog gate accepts either a new unreleased bullet or a new dated version section containing a nonempty bullet and matching an increased package version.

Cloudflare manages deployment authentication through its native Git integration; no Cloudflare GitHub secret or account variable is required. Runtime service secrets stay on the Worker. Do not add them to GitHub or the build environment.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "runesswap.app",
"version": "0.2.8",
"version": "0.3.0",
"private": true,
"scripts": {
"test": "NODE_OPTIONS=\"--max-old-space-size=4096\" jest --runInBand",
Expand Down
13 changes: 5 additions & 8 deletions src/routes/changelog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@ import fileContents from '../../CHANGELOG.md?raw';
function getCleanChangelog(raw: string): string {
const lines = raw.split(/\r?\n/);

// Find first version heading (e.g., "## [0.2.1] - 2025-08-23" or any H2)
let startIdx = lines.findIndex((l) => l.trim().startsWith('## '));
if (startIdx === -1) startIdx = 0;

// Slice from first H2 to end
const startIdx = lines.findIndex((line) =>
/^## \[\d+\.\d+\.\d+\] - \d{4}-\d{2}-\d{2}$/.test(line),
);
if (startIdx === -1) return 'No releases are currently available.';
const sliced = lines.slice(startIdx);

// Filter out reference-style link definitions at the bottom like: [0.2.1]: https://...
Expand All @@ -21,9 +20,7 @@ function getCleanChangelog(raw: string): string {
}

function ChangelogPage() {
const content = fileContents
? getCleanChangelog(fileContents)
: 'No changelog is currently available.';
const content = getCleanChangelog(fileContents);

return (
<div className={styles.container}>
Expand Down
Loading