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
2 changes: 1 addition & 1 deletion .github/.release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "1.1.0"
".": "1.2.0"
}
2 changes: 1 addition & 1 deletion .github/release-please-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,5 @@
"bootstrap-sha": "64f154b086cbad6cac232357dc21c954543ef217",
"separate-pull-requests": false,
"pull-request-title-pattern": "chore(release): ${version}",
"pull-request-header": "The changelog below is what will be published as the release notes. Edit the commit subjects, not this PR body — the body is regenerated on every push to release."
"pull-request-header": "The changelog below is what will be published as the release notes. Edit the commit subjects, not this PR body — the body is regenerated on every push to main. Merge this before the promotion pull request, so the tag lands on the commit `release` receives."
}
4 changes: 3 additions & 1 deletion .github/workflows/pr-title.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ permissions:
jobs:
conventional:
name: conventional commit
if: github.event.pull_request.draft == false
if: >-
github.event.pull_request.draft == false &&
!startsWith(github.event.pull_request.head.ref, 'release-please--')
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
Expand Down
19 changes: 14 additions & 5 deletions .github/workflows/promote.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,22 @@ jobs:
exit 0
fi

pending=$(gh pr list --base main --state open --label "autorelease: pending" --limit 1 --json number,title --jq '.[0] | select(.) | "#\(.number) \(.title)"')

if [ -n "$pending" ]; then
warning="**Merge $pending first.** It is the release pull request: it writes the changelog, bumps the version and cuts the tag on \`main\`. Merge it before this one and the tag lands on a commit \`release\` receives. Merge this one first and you ship the code untagged, and the version does not reach \`release\` until the next promotion."
else
warning="Nothing releasable is waiting — no version bump will ride along with this."
fi

body=$(cat <<EOF
Everything on \`main\` that \`release\` does not have yet. Merging this ships it: the
commits below land on \`release\`, and release-please then opens a \`chore(release)\` pull
request that writes the changelog, bumps the version and cuts the tag.
Everything on \`main\` that \`release\` does not have yet. Merging this is what puts it in
front of users, because \`release\` is what a deploy and a plain clone both point at.

$warning

Merge it with a **merge commit**, not a squash — release-please reads these subjects to
work out the version, and a squash would flatten them into one.
Merge it with a **merge commit**, not a squash: a squash would give \`release\` a new
commit of its own, and the tags on \`main\` would stop being ancestors of what you shipped.

$(git log origin/release..origin/main --no-merges --reverse --format='- %s')
EOF
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Release

on:
push:
branches: [release]
branches: [main]
workflow_dispatch:

concurrency:
Expand All @@ -24,7 +24,7 @@ jobs:
with:
config-file: .github/release-please-config.json
manifest-file: .github/.release-please-manifest.json
target-branch: release
target-branch: main
token: ${{ secrets.AUTOMATION_TOKEN || secrets.RELEASE_PLEASE_TOKEN || secrets.GITHUB_TOKEN }}

- name: Say what happened
Expand All @@ -34,7 +34,7 @@ jobs:
run: |
set -euo pipefail
if [ -n "${TAG:-}" ]; then
echo "Released [$TAG]($URL)." >> "$GITHUB_STEP_SUMMARY"
echo "Released [$TAG]($URL) — merge the promotion pull request to put it on \`release\`." >> "$GITHUB_STEP_SUMMARY"
else
echo "No tag this run — either the release pull request is still open, or nothing releasable landed." >> "$GITHUB_STEP_SUMMARY"
fi
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## [1.2.0](https://github.com/trycompai/crm/compare/v1.1.0...v1.2.0) (2026-08-07)


### Features

* **api:** add microsoft sign-in and outlook mailbox sync ([#73](https://github.com/trycompai/crm/issues/73)) ([2a0062f](https://github.com/trycompai/crm/commit/2a0062fb76ffdaa5bbbb3848a5573b8b53cd0036))

## [1.1.0](https://github.com/trycompai/crm/compare/v1.0.0...v1.1.0) (2026-08-06)


Expand Down
57 changes: 28 additions & 29 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,15 +80,13 @@ push a branch ──▶ PR opens by itself, titled from the diff
◀── click 1: squash into main
`release: promote main` opens or updates itself
two pull requests open, and stay open, together
`chore(release): 0.2.0` ──▶ main
`release: promote main` ──▶ release
◀── click 2: merge it (a merge commit, not a squash)
◀── click 2: the release PR — tag, notes, CHANGELOG.md
release PR opens against `release`
◀── click 3: merge it
tag + GitHub Release + CHANGELOG.md
◀── click 3: the promotion PR — `release` moves up
```

**Pushing any branch that isn't `main` or `release` opens a pull request into `main`.** You do not
Expand Down Expand Up @@ -131,37 +129,38 @@ because the squashed commit body is left empty on purpose — the title is the w
Nothing is released by merging to `main`, and nothing is pushed to `release` by hand. Both branches
only ever move through a pull request, and shipping is two of them.

**The promotion pull request — `release: promote main`.** Opened, and thereafter kept up to date,
every time something lands on `main`. Its body is the list of commits `release` does not have yet,
so it is a standing answer to "what is waiting to ship". **Merge it with a merge commit, not a
squash** — `release` is only allowed to merge that way for a reason: release-please reads those
individual commit subjects to work out the version, and a squash flattens them into one subject it
cannot parse.
Both open as soon as something lands on `main`, and both stay open and up to date until you use
them — so the version you are about to cut is readable *before* you decide to ship it.

**The release pull request — `chore(release): 0.2.0`, into `main`.**
[release-please](https://github.com/googleapis/release-please) accumulates every releasable commit
into this one. Merging it writes `CHANGELOG.md`, bumps the version, tags `v0.2.0` and publishes the
GitHub Release. The notes are reviewable before they are public, and a stack of merges is one
release rather than five.

**The release pull request — `chore(release): 0.2.0`.** Once the promotion lands,
[release-please](https://github.com/googleapis/release-please) reads the newly promoted commits and
opens this against `release`. Merging it writes `CHANGELOG.md`, bumps the version, tags `v0.2.0` and
publishes the GitHub Release. So the notes are reviewable before they are public, and a stack of
merges is one release rather than five.
**The promotion pull request — `release: promote main`, into `release`.** Its body is the list of
commits `release` does not have yet, so it is a standing answer to "what is waiting to ship".
**Merge it with a merge commit, not a squash** — `release` only permits that method, because a
squash would give `release` a commit of its own and the tags on `main` would stop being ancestors
of what you shipped.

`CHANGELOG.md`, the version in `package.json` and the release manifest therefore live on `release`
and are not carried back to `main`. That is deliberate: `release` is the default branch, so it is
the tree anyone landing on the repository reads, and `main` never touches those files — which is
also why the promotion merge never conflicts on them.
**Merge the release one first.** Then the tag sits on a commit the promotion carries over, and
`release` gets the code and its version together. The other order still works — it just ships
untagged code and leaves the bump for the next promotion.

Three consequences worth knowing:

- **A release PR with nothing in it is not a bug.** A run of `chore:` and `test:` commits bumps
nothing, so no PR appears. That is the type doing its job.
- **Neither automated PR runs CI.** A PR opened by `GITHUB_TOKEN` cannot trigger workflows — that is
GitHub's own loop guard, not something to work around. It does not matter on `release`, which has
no required checks: the code being promoted was already green on `main`, and the release PR only
ever touches `CHANGELOG.md`, the root `version`, and the manifest.
- **On `main` it does matter**, because `main` requires `check-types, lint, test` and
`conventional commit`. A branch you push yourself is fine — your push triggers CI and the auto-PR
inherits the result. Setting an `AUTOMATION_TOKEN` secret (a PAT or GitHub App token) makes the
GitHub's own loop guard, not something to work around. The promotion PR does not care, because
`release` has no required checks and the code was already green on `main`. The release PR targets
`main`, which does have them, so **an admin merges it past the missing checks** — safe, because it
only ever touches `CHANGELOG.md`, the root `version` and the manifest, none of which CI can judge.
- **`AUTOMATION_TOKEN` removes that click.** A PAT or GitHub App token in that secret makes the
automated PRs trigger CI like any other; every workflow already prefers it and falls back to
`GITHUB_TOKEN`. Until then, a repository admin can merge past a missing check.
`GITHUB_TOKEN`. A branch you push yourself never needed it — your own push triggers CI and the
auto-opened PR inherits the result.

`main` is expected to be green when it is promoted, and the thing that guarantees that is **branch
protection requiring the `check-types, lint, test` and `conventional commit` checks** — not the
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "crm",
"private": true,
"license": "MIT",
"version": "1.1.0",
"version": "1.2.0",
"scripts": {
"prepare": "git rev-parse --git-dir >/dev/null 2>&1 && git config core.hooksPath .githooks || true",
"build": "turbo run build",
Expand Down
Loading