Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
5e3c574
chore(ci):add automated dated release workflow
Andes-indica Aug 18, 2026
282c579
updated docs
Andes-indica Aug 18, 2026
941706e
chore(ci): fix release workflow f-string and update docs
Andes-indica Aug 18, 2026
10892c7
ci: add workflow_dispatch to auto-release for manual testing
Andes-indica Aug 18, 2026
2b95956
ci: fix release step GITHUB_TOKEN and use
Andes-indica Aug 18, 2026
97095c0
ci: pin actions/checkout to full commit SHA
Andes-indica Aug 18, 2026
1f4024b
ci: auto-release - migrate notes to Bun TS, fix lint/format; adjust w…
Andes-indica Aug 19, 2026
877f28c
ci(workflows): pin oven-sh/setup-bun v2 to commit SHA in auto-release…
Andes-indica Aug 19, 2026
a1a0719
ci:resolved since-declaration causing the test error
Andes-indica Aug 20, 2026
337fb92
ci:resolved tag format error
Andes-indica Aug 20, 2026
b8f6fc3
ci:fix release retry boundary and orphan tag handling
Andes-indica Aug 21, 2026
fa29954
fix release retry and orphan tag recovery
Andes-indica Aug 21, 2026
7ecc729
Merge branch 'Noveum:main' into ci/auto-release-workflow
Andes-indica Aug 23, 2026
7585b19
ci:fix automated release workflow contract
Andes-indica Aug 24, 2026
2517e9f
Merge branch 'Noveum:main' into ci/auto-release-workflow
Andes-indica Aug 24, 2026
9cb3df4
fix release workflow orphan recovery
Andes-indica Aug 24, 2026
a3021d3
fix automated release workflow wiring
Andes-indica Aug 25, 2026
6d9ca76
docs: align automated release guidance
imshashank Aug 25, 2026
1da4666
fix(ci): recover prior dated release tags
imshashank Aug 25, 2026
c4c6d60
fix(ci): recover historical dated releases
imshashank Aug 25, 2026
1e0fa1e
fix(ci): avoid overlapping recovery notes
imshashank Aug 25, 2026
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
164 changes: 164 additions & 0 deletions .github/workflows/auto-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
name: Automated Releases

on:
schedule:
- cron: '0 0 * * 0'
workflow_dispatch:

concurrency:
group: automated-release
cancel-in-progress: false

permissions:
contents: write
pull-requests: read

jobs:
tag-and-release:
name: Create dated tag and release notes
runs-on: ubuntu-latest
steps:
- name: Checkout current main
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
with:
ref: main
fetch-depth: 0
Comment thread
coderabbitai[bot] marked this conversation as resolved.
persist-credentials: false

- name: Determine release target
id: target
run: |
set -euo pipefail
TARGET_SHA=$(git rev-parse HEAD)
echo "target_sha=$TARGET_SHA" >> "$GITHUB_OUTPUT"
echo "Release target: $TARGET_SHA"

- name: Configure git
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"

- name: Setup Bun for scripts
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6
with:
bun-version: '1.3.14'

- name: Install JS/TS deps
run: bun install --frozen-lockfile

- name: Build release notes (TypeScript)
id: build_notes
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Comment thread
greptile-apps[bot] marked this conversation as resolved.
RELEASE_TARGET_SHA: ${{ steps.target.outputs.target_sha }}
run: |
set -euo pipefail
bun ./scripts/release-notes.ts
test -s RELEASE_NOTES.md

- name: Select or recover dated tag
id: release_tag
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TARGET_SHA: ${{ steps.target.outputs.target_sha }}
run: |
set -euo pipefail

TAG="${{ steps.build_notes.outputs.tag }}"
TAG_ACTION="${{ steps.build_notes.outputs.tag_action }}"
Comment thread
Andes-indica marked this conversation as resolved.
Outdated
REPOSITORY_URL="https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git"

case "$TAG_ACTION" in
reuse)
EXISTING_SHA=$(git rev-list -n 1 "$TAG^{commit}")
if [ "$EXISTING_SHA" != "$TARGET_SHA" ]; then
echo "Selected tag $TAG does not point to target $TARGET_SHA"
exit 1
fi
echo "Reusing existing tag $TAG at $TARGET_SHA"
;;

repair)
RELEASE_RESPONSE=$(mktemp)
RELEASE_STATUS=$(
curl \
--silent \
--show-error \
--connect-timeout 10 \
--max-time 30 \
--output "$RELEASE_RESPONSE" \
--write-out '%{http_code}' \
--header "Authorization: Bearer $GH_TOKEN" \
--header "Accept: application/vnd.github+json" \
--header "X-GitHub-Api-Version: 2022-11-28" \
"https://api.github.com/repos/${GITHUB_REPOSITORY}/releases/tags/${TAG}"
)

case "$RELEASE_STATUS" in
200)
echo "Refusing to move $TAG because it has a GitHub release"
exit 1
;;
404)
echo "Confirmed that $TAG has no GitHub release"
;;
*)
echo "Unable to verify whether $TAG has a release (HTTP $RELEASE_STATUS)"
cat "$RELEASE_RESPONSE"
exit 1
;;
esac

OLD_TAG_OBJECT=$(git rev-parse "refs/tags/$TAG")
echo "Repairing unpublished tag $TAG at $TARGET_SHA"
git tag -fa "$TAG" "$TARGET_SHA" -m "Automated release $TAG"
git push \
--force-with-lease="refs/tags/$TAG:$OLD_TAG_OBJECT" \
"$REPOSITORY_URL" \
"refs/tags/$TAG"
Comment thread
greptile-apps[bot] marked this conversation as resolved.
Outdated
;;

create)
echo "Creating annotated tag $TAG at $TARGET_SHA"
git tag -a "$TAG" "$TARGET_SHA" -m "Automated release $TAG"
git push "$REPOSITORY_URL" "refs/tags/$TAG"
;;

*)
echo "Unknown tag action: $TAG_ACTION"
exit 1
;;
esac

echo "tag=$TAG" >> "$GITHUB_OUTPUT"

- name: Publish or recover GitHub release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ steps.release_tag.outputs.tag }}
run: |
set -euo pipefail

if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
DRAFT=$(gh release view "$TAG" --repo "$GITHUB_REPOSITORY" --json isDraft -q .isDraft)
PRERELEASE=$(gh release view "$TAG" --repo "$GITHUB_REPOSITORY" --json isPrerelease -q .isPrerelease)

if [ "$DRAFT" = "true" ] || [ "$PRERELEASE" = "true" ]; then
echo "Publishing existing release $TAG"
gh release edit "$TAG" \
--repo "$GITHUB_REPOSITORY" \
--draft=false \
--prerelease=false \
--notes-file RELEASE_NOTES.md
else
echo "Release $TAG already exists. Nothing to publish."
fi
exit 0
fi

gh release create "$TAG" \
--repo "$GITHUB_REPOSITORY" \
--title "$TAG" \
--notes-file RELEASE_NOTES.md

echo "Published release $TAG"
1 change: 1 addition & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions docs/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,10 @@ enforced.
## Releases

Orbit ships continuously from `main`. There are no long lived release branches
and no backporting, so self-hosted deployments should track `main` or a recent
tag.
and no backporting. To make deployments traceable we publish automated dated
tags and GitHub releases (weekly and on merges to `main`), so self-hosted
deployments can track `main` or a recent dated tag.

Anything requiring action from someone self-hosting is labelled
[`breaking change`](https://github.com/Noveum/orbit/labels/breaking%20change)
and called out in the release notes.
and called out prominently in the generated release notes.
8 changes: 5 additions & 3 deletions docs/self-hosting.md
Original file line number Diff line number Diff line change
Expand Up @@ -261,11 +261,13 @@ bun run build
Always complete the database release before the code that depends on it goes live.
The production Vercel build refuses to deploy when the configured database cannot
be verified or is missing a required schema object. Additional legacy tables and
indexes are reported and preserved. Orbit ships continuously from `main` and there
is no backporting, so track `main` or a recent tag.
indexes are reported and preserved. Orbit ships continuously from `main`. We
also publish automated dated tags and GitHub releases (weekly and can also be
created through a manual workflow dispatch) so you can track `main` or a recent
+dated tag for deployed versions.

Watch the [releases](https://github.com/Noveum/orbit/releases) for anything
labelled `breaking change`.
labelled `breaking change` and follow the upgrade notes in the associated release.
Comment thread
Andes-indica marked this conversation as resolved.

### Backups

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
},
"devDependencies": {
"@biomejs/biome": "2.5.7",
"@orbit/shared": "workspace:*",
"@types/bun": "1.3.14",
"lefthook": "2.1.10",
"typescript": "5.9.3"
Expand Down
38 changes: 38 additions & 0 deletions packages/shared/src/validators/github-release.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { z } from 'zod';

const labelSchema = z.object({
name: z.string(),
});

export const pullRequestSchema = z.object({
number: z.number(),
title: z.string(),
html_url: z.string().url(),
body: z.string().nullable(),
labels: z.array(labelSchema),
merged_at: z.string().nullable(),
base: z.object({
ref: z.string(),
}),
});

export const pullRequestListSchema = z.array(pullRequestSchema);

export const githubCommitSchema = z.object({
sha: z.string().min(1),
});

export const commitPageSchema = z.array(githubCommitSchema);

export const releaseSchema = z.object({
tag_name: z.string().min(1),
draft: z.boolean(),
prerelease: z.boolean(),
published_at: z.string().nullable(),
});

export const releaseListSchema = z.array(releaseSchema);

export type PullRequest = z.infer<typeof pullRequestSchema>;
export type GitHubCommit = z.infer<typeof githubCommitSchema>;
export type GitHubRelease = z.infer<typeof releaseSchema>;
1 change: 1 addition & 0 deletions packages/shared/src/validators/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export * from './comment.ts';
export * from './common.ts';
export * from './cycle.ts';
export * from './doc.ts';
export * from './github-release.ts';
export * from './integration.ts';
export * from './issue.ts';
export * from './label.ts';
Expand Down
Loading
Loading