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
165 changes: 135 additions & 30 deletions .github/RELEASE_WORKFLOW.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,43 @@
# Release Workflow Documentation

This module uses two separate GitHub Actions workflows for releases:
This module publishes **two parallel version lines to the same Foundry package
listing**: a legacy v13 line (`1.x`, from `main`) and a v14 line (`2.x`, from
`release/v14`). Foundry's registry natively supports multiple versions per
package, each with its own compatibility range — the Foundry client picks
whichever version matches the user's installed core version, so no separate
listing/package id is needed.

## 🚀 Production Release (main branch)
There are three GitHub Actions workflows involved:

| Branch | Workflow | Publishes to Foundry? | Version family |
|---|---|---|---|
| `main` | `auto-release.yml` | Yes | `1.x`, compat `13.x` |
| `release/v14` | `auto-release-v14.yml` | Yes | `2.x`, compat `14.x` |
| `staging` | `beta-release.yml` | No (GitHub pre-release only) | v14 betas |

Both stable workflows refuse to run if `module.json`'s version/compatibility
don't match their expected family — this guards against the exact incident
that previously broke v13 users (a v14-shaped build publishing under a v13
version string via `main`).

## 🚀 Production Release — v13 (main branch)

**Workflow:** `.github/workflows/auto-release.yml`

### When it runs:
- Automatically triggered when `module.json` or `package.json` is pushed to the `main` branch

### What it does:
1. ✅ Verifies version consistency between `module.json` and `package.json`
2. ✅ Checks that `CHANGELOG.md` has been updated for the new version
3. ✅ Creates a GitHub release with tag `vX.Y.Z`
4. ✅ Uploads `module.zip` and `module.json` as release assets
5. ✅ **Publishes to Foundry VTT package repository** (visible to all users)
1. ✅ Verifies `module.json` version is `1.x` and `compatibility.minimum` is `13.x` — refuses to publish otherwise
2. ✅ Verifies version consistency between `module.json` and `package.json`
3. ✅ Checks that `CHANGELOG.md` has been updated for the new version
4. ✅ Creates a GitHub release with tag `vX.Y.Z`
5. ✅ Updates the moving `v13-latest` tag/release so direct-manifest-URL installs auto-update
6. ✅ Uploads `module.zip` and `module.json` as release assets
7. ✅ **Publishes to Foundry VTT package repository** (visible to all users)

### How to create a production release:
1. Update version in both `module.json` and `package.json`
1. Update version in both `module.json` and `package.json` (keep it in the `1.x` family)
2. Add a section for the new version in `CHANGELOG.md`:
```markdown
## [1.2.0] - 2025-01-15
Expand All @@ -29,6 +49,47 @@ This module uses two separate GitHub Actions workflows for releases:

---

## 🚀 Production Release — v14 (release/v14 branch)

**Workflow:** `.github/workflows/auto-release-v14.yml`

### When it runs:
- Automatically triggered when `module.json` or `package.json` is pushed to the `release/v14` branch

### What it does:
Same steps as the v13 workflow above, but requires `module.json` version to be
`2.x` and `compatibility.minimum` to be `14.x`, and maintains its own moving
`v14-latest` tag/release instead of `v13-latest`. Publishes to the **same**
Foundry package listing as `main` (same `FOUNDRY_ADMIN_MODULE_ID`), as a
separate version entry with its own compatibility range.

Its GitHub releases are created with `make_latest: false`, so the repo-wide
`/releases/latest` URL always stays on the v13 track. This protects v13 users
who installed before `v13-latest` existed and still have
`/releases/latest/download/module.json` stored as their manifest URL — without
it, each v14 publish would silently repoint those installs at v14.

### How to create a v14 production release:
1. If `release/v14` doesn't exist yet, create it from a commit that already
contains `.github/workflows/auto-release-v14.yml` — i.e. from `main` after
the dual-publish CI merged, or cherry-pick that workflow file onto the new
branch.
> ⚠️ Do **not** branch `release/v14` off `staging` alone. GitHub evaluates
> workflows from the ref being pushed, so a `release/v14` without
> `auto-release-v14.yml` accepts version bumps and silently publishes
> nothing.
2. Merge/promote tested `staging` work into `release/v14`
3. Resolve the version in both `module.json` and `package.json` (keep it in the
`2.x` family, compat `14.x`) — `staging` merges often carry beta or `1.x`
values that the workflow will reject
4. Add a section for the new version in `CHANGELOG.md`
5. Push to `release/v14` — the workflow creates the release and publishes to Foundry

Before the *first* v14 stable release, complete the one-time
[Pre-v14 cutover](#-pre-v14-cutover-required-once) checklist.

---

## 🧪 Beta Release (staging branch)

**Workflow:** `.github/workflows/beta-release.yml`
Expand Down Expand Up @@ -77,28 +138,70 @@ In Foundry VTT:
## 📋 Branch Strategy

```
staging (beta releases)
↓ (merge when ready)
main (production releases)
staging (v14 beta releases)
↓ (merge tested work when ready)
release/v14 (v14 production releases) ──┐
├─→ same Foundry listing,
main (v13 production releases) ─────────┘ different version entries
```

`main` and `staging`/`release/v14` are independent lines — `main` no longer
receives the v14 rewrite. Fixes that apply to both must be ported by hand
(cherry-pick or reimplement), not by merging the branches into each other.

### Typical workflow:
1. **Development:** Make changes on feature branches, merge to `staging`
2. **Beta Testing:** Push version bump to `staging` → creates beta release
3. **Testing:** Testers install beta via manifest URL and provide feedback
4. **Release:** When ready, merge `staging` to `main` → creates production release
1. **v14 development:** Make changes on feature branches, merge to `staging`
2. **v14 Beta Testing:** Push version bump to `staging` → creates beta release
3. **v14 Release:** When ready, merge `staging` to `release/v14` → creates v14 production release
4. **v13 maintenance:** Fixes/parity changes for the legacy line go directly to `main` → creates v13 production release

---

## 🔀 Pre-v14 cutover (required once)

Installs made before `v13-latest` existed still store
`/releases/latest/download/module.json` as their manifest URL. That URL is
repo-wide, so whichever release GitHub last marked "latest" owns those users.
Run this checklist **once, before the first `release/v14` publish**:

1. **Reclaim `/releases/latest` for v13.** Push a normal v13 production release
on `main`. Its release is created with `make_latest: true`, which moves the
repo-wide latest pointer back onto the v13 track.
2. **Confirm it took.** Open the repo's Releases page (or
`/releases/latest`) and verify the release badged "Latest" is a `v1.x`
release.
3. **Only then publish v14.** Create/push the first `release/v14` stable
release. Its workflow uses `make_latest: false`, so it never touches the
repo-wide latest pointer.
4. **Optional user migration.** Legacy installs stay safe as long as `main`
keeps reclaiming latest and `release/v14` never sets `make_latest`. Users
who *want* the moving v13 track explicitly can reinstall with
`https://github.com/camrun91/archivist-sync/releases/download/v13-latest/module.json`.

---

## 🔧 Version Numbering

### Production (main):
### Production v13 (main):
- Tag: `v1.2.0`
- Version in module.json: `1.2.0`
- Manifest URL: `/releases/latest/download/module.json` (auto-updates)
- Download URL: `/releases/download/v1.2.0/module.zip` (specific version)
- Manifest URL: `/releases/download/v13-latest/module.json` (auto-updates)
- Download URL in the published manifest: `/releases/download/v1.2.0/module.zip` (immutable for that release)

### Production v14 (release/v14):
- Tag: `v2.1.0`
- Version in module.json: `2.1.0`
- Manifest URL: `/releases/download/v14-latest/module.json` (auto-updates)
- Download URL in the published manifest: `/releases/download/v2.1.0/module.zip` (immutable for that release)

**Manifest vs. download:** only the *manifest* URL moves. Each track's
`*-latest` release also hosts copies of `module.json` and `module.zip` so
Foundry's update check always reads the newest version for that track — but the
`download` field inside every published `module.json` points at that release's
own versioned zip. A given version therefore always installs the exact bits it
was built from, even after a newer release moves the `*-latest` tag.

### Beta (staging):
- **Versioned tag:** `v1.2.0-beta.5` (specific beta with full changelog)
Expand Down Expand Up @@ -132,28 +235,30 @@ main (production releases)
- Make changes but don't update the version numbers
- Or use `[skip ci]` in your commit message

### Merging staging to main
### Promoting staging to release/v14
- The `staging` branch's `module.json` will have beta URLs:
```json
"manifest": "https://github.com/camrun91/archivist-sync/releases/download/beta-latest/module.json",
"download": "https://github.com/camrun91/archivist-sync/releases/download/beta-latest/module.zip"
```
- Before merging to main, update these to production URLs:
```json
"manifest": "https://github.com/camrun91/archivist-sync/releases/latest/download/module.json",
"download": "https://github.com/camrun91/archivist-sync/releases/download/v1.2.0/module.zip"
```
- The main branch should use `releases/latest/download/` for manifest (auto-updates)
- Update the download URL to match the version you're releasing
- You do **not** need to fix these by hand: both stable workflows rewrite these
fields before packaging, precisely so leaked beta URLs can't ship — `manifest`
becomes their own track's moving tag (`v13-latest` or `v14-latest`), and
`download` becomes that release's versioned zip (`/releases/download/v2.1.0/module.zip`).
- Do **not** point any manifest at `releases/latest/download/`, and do **not**
hand-edit which release GitHub marks "latest". That pointer is repo-wide and is
managed by the workflows: `main` claims it (`make_latest: true`), `release/v14`
leaves it alone (`make_latest: false`). See
[Pre-v14 cutover](#-pre-v14-cutover-required-once).

---

## 🎯 Best Practices

1. **Always update CHANGELOG.md** before releasing (production)
2. **Test on staging** before merging to main
2. **Test on staging** before promoting to `release/v14`
3. **Keep versions in sync** between `module.json` and `package.json`
4. **Use semantic versioning**: `MAJOR.MINOR.PATCH`
5. **Beta testing**: Share the beta manifest URL with trusted testers
6. **Production release**: Only merge to main when ready for public release
6. **Production release**: Only push to `main` (v13) or `release/v14` (v14) when ready for public release

Loading