diff --git a/.fallout/build.schema.json b/.fallout/build.schema.json index 75375d2..86ca2ac 100644 --- a/.fallout/build.schema.json +++ b/.fallout/build.schema.json @@ -29,8 +29,8 @@ "GitHubRelease", "Images", "Push", + "PushDevelop", "PushDockerHub", - "PushEdge", "PushGhcr", "ReleaseBundle", "Test", diff --git a/.github/workflows/publish-edge.yml b/.github/workflows/publish-develop.yml similarity index 91% rename from .github/workflows/publish-edge.yml rename to .github/workflows/publish-develop.yml index 5e0a3dc..c445b96 100644 --- a/.github/workflows/publish-edge.yml +++ b/.github/workflows/publish-develop.yml @@ -9,12 +9,12 @@ # # - To trigger manual generation invoke: # -# fallout --generate-configuration GitHubActions_publish-edge --host GitHubActions +# fallout --generate-configuration GitHubActions_publish-develop --host GitHubActions # # # ------------------------------------------------------------------------------ -name: publish-edge +name: publish-develop on: push: @@ -49,8 +49,8 @@ jobs: global-json-file: global.json - name: 'Restore: dotnet tools' run: dotnet tool restore - - name: 'Run: PushEdge' - run: dotnet fallout PushEdge + - name: 'Run: PushDevelop' + run: dotnet fallout PushDevelop env: RegistryUser: ${{ secrets.REGISTRY_USER }} RegistryPassword: ${{ secrets.REGISTRY_PASSWORD }} diff --git a/CLAUDE.md b/CLAUDE.md index 67f82a0..02c8c6c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -190,7 +190,7 @@ dotnet fallout Test # same thing via the tool, which is what CI invokes > Change the attribute, then regenerate — once per workflow: > ```bash > dotnet fallout --generate-configuration GitHubActions_build --host GitHubActions -> # …and publish-edge · publish-ghcr · publish-release · publish-dockerhub +> # …and publish-develop · publish-ghcr · publish-release · publish-dockerhub > ``` ### Branching — GitFlow (2026-08-16) @@ -200,7 +200,7 @@ tagged `v*`. Work goes `feat|fix|chore|docs/*` → PR into `develop`. A release `release/*` window) **fast-forwarded** into `main`, then tagged. `hotfix/*` is cut from `main` and **must** be ported back to `develop`. -- Every non-docs push to `develop` republishes the images as `:edge` (`PushEdge`). +- Every non-docs push to `develop` republishes the images as `:develop` (`PushDevelop`). - `GitHubRelease` **refuses** a tag that is not reachable from `main` or `support/*` — the trunk is never tagged for release. - **Never merge a release PR with GitHub's button.** It rewrites the commits, which severs the diff --git a/README.md b/README.md index 858bf78..91e5b64 100644 --- a/README.md +++ b/README.md @@ -452,7 +452,7 @@ pinned as a local dotnet tool, so run `dotnet tool restore` once on a fresh clon **The whole CI/CD pipeline is Fallout.** Every workflow under `.github/workflows/` is *generated* from the `[GitHubActions]` attributes in [`build/Build.CI.GitHubActions.cs`](build/Build.CI.GitHubActions.cs), and each one only provisions a runner and invokes a target — the gate runs `Test`, the trunk runs -`PushEdge`, a tag runs `PushGhcr` and `GitHubRelease`. Edit the attribute, not the YAML, or your +`PushDevelop`, a tag runs `PushGhcr` and `GitHubRelease`. Edit the attribute, not the YAML, or your change is overwritten: ```bash @@ -504,8 +504,8 @@ gh pr create --base develop --label enhancement # one category label — th ### Running the trunk -Every push to `develop` republishes the images as `:edge` (multi-arch, GHCR). Point an existing -deployment's `.env` at that tag to follow along — it moves under you, migrations included, and +Every push to `develop` republishes the images as `:develop` (multi-arch, GHCR and Docker Hub). +Point an existing deployment's `.env` at that tag to follow along — it moves under you, migrations included, and downgrading back to a release is not supported, so back up first. --- diff --git a/build/Build.CI.GitHubActions.cs b/build/Build.CI.GitHubActions.cs index 06511e4..69bae1a 100644 --- a/build/Build.CI.GitHubActions.cs +++ b/build/Build.CI.GitHubActions.cs @@ -43,9 +43,10 @@ }, InvokedTargets = new[] { nameof(Test) })] -// ── The edge channel (GitFlow's preview channel) ────────────────────────────── +// ── The develop channel (GitFlow's preview channel) ─────────────────────────── // -// Every push to the trunk republishes the six images under `:edge`, so a tester can run the next +// Every push to the trunk republishes the six images under `:develop` — named after the branch +// they are built from — so a tester can run the next // release before it is a release. Mirrors the extension repo's rolling `preview` VSIX; the shape // differs only because our artefact is a registry tag rather than a GitHub release asset. // @@ -53,16 +54,16 @@ // and rebuilding six multi-arch images because a markdown file changed is pure waste. // // Concurrency QUEUES rather than cancels (ConcurrencyCancelInProgress is left at its default -// false): cancelling a push mid-way can leave `:edge` pointing at a half-written manifest list, -// which is worse than an edge build running a few minutes behind. +// false): cancelling a push mid-way can leave `:develop` pointing at a half-written manifest +// list, which is worse than the channel running a few minutes behind the branch. [GitHubActions( - "publish-edge", + "publish-develop", GitHubActionsImage.UbuntuLatest, FetchDepth = 0, OnPushBranches = new[] { DevelopBranch }, OnPushExcludePaths = new[] { "**/*.md", "docs/**" }, ConcurrencyGroup = "${{ github.workflow }}", - InvokedTargets = new[] { nameof(PushEdge) }, + InvokedTargets = new[] { nameof(PushDevelop) }, EnvironmentName = "ghcr", ImportSecrets = new[] { nameof(RegistryUser), nameof(RegistryPassword) })] @@ -100,7 +101,7 @@ // // It also refuses to go out from the wrong branch: GitHubRelease asserts the tag is reachable from // main or a support line (see Build.Release.cs). Under GitFlow the trunk is never tagged for -// release — it ships through the edge channel instead. +// release — it ships through the develop channel instead. [GitHubActions( "publish-release", GitHubActionsImage.UbuntuLatest, diff --git a/build/Build.Publish.cs b/build/Build.Publish.cs index 678d20f..c6f514e 100644 --- a/build/Build.Publish.cs +++ b/build/Build.Publish.cs @@ -73,7 +73,7 @@ partial class Build AbsolutePath ComposeDirectory => RootDirectory / ".artifacts" / "compose"; - /// Set by ; wins over every other tag resolution. + /// Set by ; wins over every other tag resolution. string _tagOverride; /// @@ -94,7 +94,7 @@ string EffectiveTag { get { - // An edge build runs off a branch, where the resolutions below would answer "dev" — + // A trunk build runs off a branch, where the resolutions below would answer "dev" — // the point of the channel is that it has its own, stable, name. if (!string.IsNullOrWhiteSpace(_tagOverride)) return _tagOverride; @@ -208,16 +208,16 @@ string RemoteImage(string service) => PushImages(); }); - /// The rolling tag the trunk publishes under. + /// The rolling tag the trunk publishes under — named after the branch it comes from. /// /// Not "latest": that name is conventionally the newest stable image, and a compose file - /// left on the default tag would silently follow the trunk. Anyone running :edge has - /// typed the word. + /// left on the default tag would silently follow the trunk. Anyone running :develop has + /// typed the name of a branch, which says exactly what they are getting. /// - const string EdgeTag = "edge"; + const string DevelopTag = "develop"; - Target PushEdge => _ => _ - .Description($"Push the rolling ':{EdgeTag}' images to GHCR — CI target for pushes to the trunk") + Target PushDevelop => _ => _ + .Description($"Push the rolling ':{DevelopTag}' images to GHCR — CI target for pushes to the trunk") .DependsOn(Compile) .Requires(() => RegistryUser) .Requires(() => RegistryPassword) @@ -225,9 +225,9 @@ string RemoteImage(string service) => { // The images are the whole channel — there is no separate release artefact to attach, // as there would be for a versioned release. A tester points their existing .env at - // :edge (docs/releasing.md#the-edge-channel) and pulls. + // :develop (docs/releasing.md#the-develop-channel) and pulls. _targetRegistry = "ghcr.io"; - _tagOverride = EdgeTag; + _tagOverride = DevelopTag; PushImages(); }); diff --git a/build/Build.Release.cs b/build/Build.Release.cs index 20ab2fc..82ec89d 100644 --- a/build/Build.Release.cs +++ b/build/Build.Release.cs @@ -110,7 +110,7 @@ void AssertTagged() => /// /// /// - /// Under GitFlow the trunk is never tagged for release — develop ships through the edge + /// Under GitFlow the trunk is never tagged for release — develop ships through its own /// channel, and a release comes from main after a stabilisation window /// (docs/branching-and-release.md). Without this check that rule is documentation only, and /// breaking it is silent: a v* tag anywhere publishes real images and a real release @@ -149,7 +149,7 @@ void AssertReleasableRef() Assert.True(releasable, $"{ReleaseTag} is not reachable from origin/{MainBranch} or a support line — it is on " + $"[{string.Join(", ", branches)}]. Releases are cut from {MainBranch}; the trunk ships " + - "through the edge channel. See docs/branching-and-release.md."); + "through the develop channel. See docs/branching-and-release.md."); Log.Information("{Tag} is reachable from {Branches}", ReleaseTag, string.Join(", ", branches)); } diff --git a/docs/branching-and-release.md b/docs/branching-and-release.md index d080fd1..79c7dc0 100644 --- a/docs/branching-and-release.md +++ b/docs/branching-and-release.md @@ -13,7 +13,7 @@ runs. | Branch | Purpose | Lifetime | Tagged? | |---|---|---|---| -| `develop` | **Integration trunk. Default branch.** All finished work lands here first. Every push republishes the [`:edge` images](releasing.md#the-edge-channel). | Permanent | No | +| `develop` | **Integration trunk. Default branch.** All finished work lands here first. Every push republishes the [`:develop` images](releasing.md#the-develop-channel). | Permanent | No | | `main` | **Production.** Only receives merges from `release/*` and `hotfix/*`, and every one of those is tagged. Never committed to directly. | Permanent | **Yes** | | `release/*` | **Stabilisation window** for a release being prepared. Cut from `develop`; takes only fixes and release prep. Merges to `main` *and back to* `develop`, then deleted. | Short-lived | No (the merge into `main` is) | | `hotfix/*` | **Urgent production fix.** Cut from `main`. Merges to `main` *and* `develop`, then deleted. | Short-lived | No (the merge into `main` is) | @@ -89,7 +89,7 @@ flowchart TD A["A change needs to ship"] --> B{"Is a released version
broken right now?"} B -->|No| C["feat/* or fix/*
off develop"] C --> D["PR → develop"] - D --> E["Ships on the next release
(and on :edge immediately)"] + D --> E["Ships on the next release
(and on :develop immediately)"] B -->|Yes| F{"Does it affect the
version main is on?"} F -->|Yes| G["hotfix/* off main"] G --> H["PR → main, tag,
then merge back to develop"] diff --git a/docs/ci.md b/docs/ci.md index 0d54218..23e4d99 100644 --- a/docs/ci.md +++ b/docs/ci.md @@ -18,7 +18,7 @@ it. | File | Trigger | Invokes | Environment | |---|---|---|---| | `build.yml` | push to `develop`/`main`; PR into `develop`, `main`, `release/*`, `hotfix/*`, `support/*` | `Test` — the required check | — | -| `publish-edge.yml` | push to `develop` (non-docs) | `PushEdge` — rolling `:edge` images | `ghcr` | +| `publish-develop.yml` | push to `develop` (non-docs) | `PushDevelop` — rolling `:develop` images | `ghcr` | | `publish-ghcr.yml` | `v*` tag | `PushGhcr` — versioned multi-arch images | `ghcr` | | `publish-release.yml` | `v*` tag | `GitHubRelease` — compose bundle + notes | `github-release` | | `publish-dockerhub.yml` | dispatch only | `PushDockerHub` — mirror | `dockerhub` | @@ -29,8 +29,8 @@ flowchart LR B --> C(["ubuntu-latest ✓"]) D["Push to develop"] --> B - D --> E["publish-edge.yml
PushEdge"] - E --> ET["ghcr.io/…:edge"] + D --> E["publish-develop.yml
PushDevelop"] + E --> ET["ghcr.io/…:develop"] T["Tag v* on main"] --> G["publish-ghcr.yml
PushGhcr"] T --> R["publish-release.yml
GitHubRelease"] @@ -63,7 +63,7 @@ The generator names a job after its runner image, so **all five workflows produc In practice it only shows up in one place. A PR from a working branch has a head SHA that only the gate ran on, so its check list is clean. A **release PR from `develop`** has a head SHA that -`publish-edge` also ran on, so the PR lists the gate's check *and* the edge publish's, both under +`publish-develop` also ran on, so the PR lists the gate's check *and* the edge publish's, both under the same name — and GitHub requires every check with that name to pass. That is a defensible thing to be blocked by (don't cut a release from a commit whose images @@ -82,23 +82,24 @@ complements forever. The extension repo pays that price because its gate packages a VSIX. Ours is a five-minute `dotnet test`. We pay the five minutes. -`publish-edge.yml` *does* filter paths, and safely: it is not a required check, so a skipped run +`publish-develop.yml` *does* filter paths, and safely: it is not a required check, so a skipped run blocks nothing. -## publish-edge.yml — the trunk channel +## publish-develop.yml — the trunk channel Every non-docs push to `develop` rebuilds the six service images for `linux/amd64` and -`linux/arm64` and pushes them to GHCR under `:edge`, replacing what was there. See -[releasing.md](releasing.md#the-edge-channel) for how to run it. +`linux/arm64` and pushes them to GHCR under `:develop`, replacing what was there. See +[releasing.md](releasing.md#the-develop-channel) for how to run it. Two deliberate choices worth knowing when reading the attribute: - **Concurrency queues, never cancels.** `ConcurrencyCancelInProgress` stays at its default - `false`. A cancelled push can leave `:edge` pointing at a half-written manifest list, which is - worse than an edge image running a few minutes behind the trunk. -- **`edge`, not `latest`.** `latest` is conventionally the newest *stable* image, and it is what a - compose file falls back to when a tag is omitted — so naming the trunk channel `latest` would - silently upgrade people who never asked for it. Anyone on `:edge` typed the word. + `false`. A cancelled push can leave `:develop` pointing at a half-written manifest list, which is + worse than the channel running a few minutes behind the branch. +- **`develop`, not `latest`.** `latest` is conventionally the newest *stable* image, and it is what + a compose file falls back to when a tag is omitted — so naming the trunk channel `latest` would + silently upgrade people who never asked for it. Anyone on `:develop` typed the name of a branch, + which says exactly what they are getting. ## The tag pipeline @@ -165,7 +166,7 @@ After changing any `[GitHubActions]` attribute: ```bash ./build.sh --generate-configuration GitHubActions_build --host GitHubActions -./build.sh --generate-configuration GitHubActions_publish-edge --host GitHubActions +./build.sh --generate-configuration GitHubActions_publish-develop --host GitHubActions ./build.sh --generate-configuration GitHubActions_publish-ghcr --host GitHubActions ./build.sh --generate-configuration GitHubActions_publish-release --host GitHubActions ./build.sh --generate-configuration GitHubActions_publish-dockerhub --host GitHubActions diff --git a/docs/releasing.md b/docs/releasing.md index 7e9edea..c8f4bd7 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -8,7 +8,7 @@ Channels, versioning, and the runbook for every kind of release. The branch mode ```mermaid flowchart LR - DEV["develop"] -->|every push| EDGE["ghcr.io/…:edge
rolling"] + DEV["develop"] -->|every push| EDGE["…:develop
rolling"] MAIN["main / support/*"] -->|"v* tag"| GHCR["ghcr.io/…:0.3.0"] MAIN -->|"v* tag"| REL["GitHub release
compose + env template"] GHCR -.->|"manual dispatch"| DH["docker.io mirror"] @@ -21,7 +21,7 @@ flowchart LR | Channel | Trigger | Gating | |---|---|---| -| `:edge` images | every non-docs push to `develop` | none | +| `:develop` images | every non-docs push to `develop` | none | | versioned images + GitHub release | any `v*` tag reachable from `main`/`support/*` | the [release guard](ci.md#the-release-guard) | | Docker Hub mirror | manual dispatch | manual by definition | @@ -36,24 +36,25 @@ same string with the `v` stripped (`v0.3.0` → `:0.3.0`). Pre-1.0, so: There is deliberately **no Nerdbank.GitVersioning here**, unlike the Fallout repos. They need a computed monotonic version because every preview build publishes a numbered package to a registry -that will not accept the same number twice. Our preview channel is a single rolling `:edge` tag with +that will not accept the same number twice. Our preview channel is a single rolling `:develop` tag with no number in it, so the machinery would buy nothing and cost a `version.json` to keep honest. -## The edge channel +## The develop channel -Every non-docs push to `develop` republishes all six images under `:edge`. That is the whole +Every non-docs push to `develop` republishes all six images under `:develop`, named after the branch +they are built from. That is the whole channel: no release object, no version, just the trunk in runnable form. -To follow it, point an existing deployment's image tags at `edge`: +To follow it, point an existing deployment's image tags at `develop`: ```bash -sed -i '' 's/:[0-9]\+\.[0-9]\+\.[0-9]\+$/:edge/' .env # in a release bundle's .env +sed -i '' 's/:[0-9]\+\.[0-9]\+\.[0-9]\+$/:develop/' .env # in a release bundle's .env docker compose pull && docker compose up -d ``` -`:edge` moves under you — that is the point, and it is why the tag is not called `latest`. Expect a +`:develop` moves under you — that is the point, and it is why the tag is not called `latest`. Expect a schema migration to land there before it lands in a release; the Migrator runs to completion on -every start, so an edge deployment upgrades itself, and **downgrading back to a release is not +every start, so a develop deployment upgrades itself, and **downgrading back to a release is not supported**. Take a database backup before following the trunk with data you care about. ## Cutting a release