diff --git a/README.md b/README.md index 4524189..20792fb 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ LINEAR_ACCESS_KEY= ./linear-release sync ### AI-assisted setup -Use the Linear Release setup skill to generate CI configuration tailored to your project. It supports GitHub Actions, GitLab CI, CircleCI, and other platforms, and walks you through continuous vs. scheduled pipelines, monorepo path filtering, and more. +Use the Linear Release setup skill to generate CI configuration tailored to your project. It supports GitHub Actions, GitLab CI, CircleCI, RWX, and other platforms, and walks you through continuous vs. scheduled pipelines, monorepo path filtering, and more. Copy the [SKILL.md](./skills/linear-release-setup/SKILL.md) into your project, or install it with [skills.sh](https://skills.sh): diff --git a/examples/rwx-continuous/README.md b/examples/rwx-continuous/README.md new file mode 100644 index 0000000..99f8bf1 --- /dev/null +++ b/examples/rwx-continuous/README.md @@ -0,0 +1,24 @@ +# RWX — Continuous Pipeline + +Every deployment creates a completed release automatically. + +## When to use + +Use this when your team ships continuously — every push to main is a deploy, and each deploy should be tracked as its own release in Linear. + +## How it works + +On every push to `main`, the run downloads the Linear Release CLI in a separate task (so the install is cached and reused across runs) and then calls `sync`. This creates a new release from the commits since the last release and immediately marks it as complete. + +## Setup + +1. Create a release pipeline in Linear (Settings → Releases) and grab the access key. +2. Store the access key in an RWX vault: `rwx vaults secrets set linear-access-key=lin_pipeline_...`. The example references it as `${{ secrets.linear-access-key }}`. +3. Copy [`linear-release.yml`](linear-release.yml) into your `.rwx/` directory and edit the `repository` URL on the `code` task to match your repo. + +## Customization + +- **Branch name**: Change `event.git.branch == 'main'` in the trigger if your default branch is different. +- **Chaining after deploy**: Set `after: ` on the `linear-release` task so it only runs when deploy succeeds. The `linear-release-cli` install task runs in parallel with deploy regardless, so it doesn't slow the critical path. +- **ARM64 base images**: Swap `linear-release-linux-x64` for `linear-release-linux-arm64` in the install command. +- **Monorepo path filters**: Add `--include-paths` to the `sync` command to scope the release to specific directories. diff --git a/examples/rwx-continuous/linear-release.yml b/examples/rwx-continuous/linear-release.yml new file mode 100644 index 0000000..54d7313 --- /dev/null +++ b/examples/rwx-continuous/linear-release.yml @@ -0,0 +1,47 @@ +# Linear Release — RWX (Continuous Pipeline) +# +# Use when: Every deployment creates a completed release automatically. +# Trigger: On every push to the default branch. +# Customize: Repository URL, branch filter, path filters (--include-paths). +# +# Set the access key with: rwx vaults secrets set linear-access-key=lin_pipeline_... +# +# To chain after a deploy task in your existing pipeline, change the +# linear-release task to use `after: ` so it only runs +# after a successful deploy. + +on: + github: + push: + if: ${{ event.git.branch == 'main' }} + init: + commit-sha: ${{ event.git.sha }} + +base: + image: ubuntu:24.04 + config: rwx/base 1.0.2 + +tasks: + - key: code + call: git/clone 2.0.7 + with: + repository: https://github.com/YOUR-ORG/YOUR-REPO.git + ref: ${{ init.commit-sha }} + github-token: ${{ github.token }} + preserve-git-dir: true + + - key: linear-release-cli + run: | + mkdir -p bin + curl -fsSL \ + https://github.com/linear/linear-release/releases/latest/download/linear-release-linux-x64 \ + -o bin/linear-release + chmod +x bin/linear-release + echo "$PWD/bin" > $RWX_ENV/PATH + + - key: linear-release + use: [code, linear-release-cli] + cache: false + run: linear-release sync + env: + LINEAR_ACCESS_KEY: ${{ secrets.linear-access-key }} diff --git a/examples/rwx-scheduled/README.md b/examples/rwx-scheduled/README.md new file mode 100644 index 0000000..eae1f77 --- /dev/null +++ b/examples/rwx-scheduled/README.md @@ -0,0 +1,51 @@ +# RWX — Scheduled Pipeline + +Releases follow a branch cut model where changes collect over time and move through stages before shipping. + +## When to use + +Use this when your team cuts release branches for stabilization. Main collects changes into the current release, release branches sync with an explicit version and auto-promote on creation. + +## How it works + +- **Push to `main`**: Syncs issues to the current started release (no explicit version). +- **Push to `release/*`**: Derives the version from `event.git.branch` and syncs with that version. On branch creation (`event.github.push.created` is `true`), auto-promotes the release to "code freeze". +- **Dispatch triggers**: `linear-release-update` and `linear-release-complete` are dispatched manually for later stage transitions and final completion. + +### Triggering manual actions + +Use the [RWX CLI](https://www.rwx.com/docs/cli-reference/rwx-dispatch) to dispatch stage transitions and completion: + +```bash +# Move release 1.2.0 to QA +rwx dispatch linear-release-update --param release-version=1.2.0 --param stage=qa + +# Mark release 1.2.0 as complete +rwx dispatch linear-release-complete --param release-version=1.2.0 +``` + +You can also dispatch from the [RWX UI](https://cloud.rwx.com/mint/deep_link/runs) or via the [Dispatches API](https://www.rwx.com/docs/api/dispatches). + +## Setup + +1. Create a release pipeline in Linear (Settings → Releases) and grab the access key. +2. Store the access key in an RWX vault: `rwx vaults secrets set linear-access-key=lin_pipeline_...`. +3. Copy [`linear-release.yml`](linear-release.yml) into your `.rwx/` directory and edit the `repository` URL on the `code` task to match your repo. +4. Rename the dispatch keys (`linear-release-update`, `linear-release-complete`) to be unique within your RWX organization — e.g. prefix them with your app name. + +## Customization + +- **Branch patterns**: Change `release/` in the `starts-with` filter and the `${BRANCH#release/}` version derivation to match your release branch convention. +- **Stage names**: Replace `code freeze` with whatever your first stage is called. +- **Version derivation**: The example strips `release/` from the branch name. Adjust if your branch naming differs. +- **Dispatch keys**: RWX dispatch keys must be unique across your entire organization — prefix them with your app or service name to avoid collisions. +- **ARM64 base images**: Swap `linear-release-linux-x64` for `linear-release-linux-arm64` in the install command. + +## Monorepo note + +To path-scope the main-branch sync without affecting release branches, split the run definition into two files in `.rwx/`: + +1. **File 1 (main)**: Use the [`github/compare`](https://www.rwx.com/docs/packages/github/compare) package to skip when the relevant paths haven't changed. +2. **File 2 (release)**: Keep the `release/*` push trigger and dispatch triggers as-is. + +Add `--include-paths` to all `sync` commands. diff --git a/examples/rwx-scheduled/linear-release.yml b/examples/rwx-scheduled/linear-release.yml new file mode 100644 index 0000000..713d8e9 --- /dev/null +++ b/examples/rwx-scheduled/linear-release.yml @@ -0,0 +1,124 @@ +# Linear Release — RWX (Scheduled) +# +# Use when: Releases follow a branch cut model. Main collects changes into the +# current release, a release branch is cut for stabilization, and branch creation +# auto-promotes to "code freeze". +# +# Trigger: push to main (sync), push to release/* (sync + auto code freeze on +# creation), or `rwx dispatch linear-release-update`/`linear-release-complete` +# for later stages and final completion. +# +# Customize: Repository URL, branch patterns, stage names, version derivation, +# dispatch keys (RWX dispatch keys must be unique within your RWX organization, +# so prefix them — e.g. linear-release-update-). +# +# Set the access key with: rwx vaults secrets set linear-access-key=lin_pipeline_... + +on: + github: + push: + # Default branch: sync without --release-version (targets current started release) + - if: ${{ event.git.branch == 'main' }} + target: linear-release-sync-main + init: + commit-sha: ${{ event.git.sha }} + + # Release branch: sync with explicit version + auto-promote on creation + - if: ${{ starts-with(event.git.branch, 'release/') }} + target: linear-release-sync-release + init: + commit-sha: ${{ event.git.sha }} + branch: ${{ event.git.branch }} + branch-created: ${{ event.github.push.created }} + + dispatch: + # Move a release between stages (e.g. qa, in review, rc soak). + # Run with: rwx dispatch linear-release-update --param release-version=1.2.0 --param stage=qa + - key: linear-release-update + target: linear-release-update + params: + - key: release-version + name: Release version + description: The release version to update (e.g. 1.2.0). + required: true + - key: stage + name: Stage + description: The target deployment stage (e.g. qa, in review). + required: true + init: + commit-sha: ${{ event.git.sha }} + release-version: ${{ event.dispatch.params.release-version }} + stage: ${{ event.dispatch.params.stage }} + + # Mark a release as complete. + # Run with: rwx dispatch linear-release-complete --param release-version=1.2.0 + - key: linear-release-complete + target: linear-release-complete + params: + - key: release-version + name: Release version + description: The release version to complete (e.g. 1.2.0). + required: true + init: + commit-sha: ${{ event.git.sha }} + release-version: ${{ event.dispatch.params.release-version }} + +base: + image: ubuntu:24.04 + config: rwx/base 1.0.2 + +tasks: + - key: code + call: git/clone 2.0.7 + with: + repository: https://github.com/YOUR-ORG/YOUR-REPO.git + ref: ${{ init.commit-sha }} + github-token: ${{ github.token }} + preserve-git-dir: true + + - key: linear-release-cli + run: | + mkdir -p bin + curl -fsSL \ + https://github.com/linear/linear-release/releases/latest/download/linear-release-linux-x64 \ + -o bin/linear-release + chmod +x bin/linear-release + echo "$PWD/bin" > $RWX_ENV/PATH + + - key: linear-release-sync-main + use: [code, linear-release-cli] + cache: false + run: linear-release sync + env: + LINEAR_ACCESS_KEY: ${{ secrets.linear-access-key }} + + - key: linear-release-sync-release + use: [code, linear-release-cli] + cache: false + run: | + RELEASE_VERSION="${BRANCH#release/}" + linear-release sync --release-version="$RELEASE_VERSION" + if [ "$BRANCH_CREATED" = "true" ]; then + linear-release update --release-version="$RELEASE_VERSION" --stage="code freeze" + fi + env: + LINEAR_ACCESS_KEY: ${{ secrets.linear-access-key }} + BRANCH: ${{ init.branch }} + BRANCH_CREATED: ${{ init.branch-created }} + + - key: linear-release-update + use: [code, linear-release-cli] + cache: false + run: linear-release update --release-version="${RELEASE_VERSION}" --stage="${STAGE}" + env: + LINEAR_ACCESS_KEY: ${{ secrets.linear-access-key }} + RELEASE_VERSION: ${{ init.release-version }} + STAGE: ${{ init.stage }} + + - key: linear-release-complete + use: [code, linear-release-cli] + cache: false + run: linear-release complete --release-version="${RELEASE_VERSION}" + env: + LINEAR_ACCESS_KEY: ${{ secrets.linear-access-key }} + RELEASE_VERSION: ${{ init.release-version }} diff --git a/skills/linear-release-setup/SKILL.md b/skills/linear-release-setup/SKILL.md index 662fa46..3bee3ca 100644 --- a/skills/linear-release-setup/SKILL.md +++ b/skills/linear-release-setup/SKILL.md @@ -2,7 +2,7 @@ name: linear-release-setup description: Generate CI/CD configuration for Linear Release. Use when setting up release tracking, configuring CI pipelines for Linear, or integrating deployments - with Linear releases. Supports GitHub Actions, GitLab CI, CircleCI, and other platforms. + with Linear releases. Supports GitHub Actions, GitLab CI, CircleCI, RWX, and other platforms. --- # Linear Release Setup @@ -16,7 +16,7 @@ The [linear-release README](https://github.com/linear/linear-release/blob/main/R Before generating config, confirm: 1. **Pipeline exists in Linear** — the user must have created a release pipeline in Linear first (Settings → Releases). Each pipeline has its own access key. -2. **Detect CI platform** — look for `.github/workflows/*.yml` (GitHub Actions), `.gitlab-ci.yml` (GitLab CI), `.circleci/config.yml` (CircleCI), or other CI config. +2. **Detect CI platform** — look for `.github/workflows/*.yml` (GitHub Actions), `.gitlab-ci.yml` (GitLab CI), `.circleci/config.yml` (CircleCI), `.rwx/*.yml` (RWX), or other CI config. 3. **Detect default branch** — check `git symbolic-ref refs/remotes/origin/HEAD` or the CI config. Don't assume `main`. ### Step 2: Map pipelines, then ask @@ -55,6 +55,8 @@ Pick the matching example template, adapt it (branch patterns, stage names, path | GitLab CI | Scheduled | [`gitlab-ci-scheduled/`](https://github.com/linear/linear-release/blob/main/examples/gitlab-ci-scheduled) | | CircleCI | Continuous | [`circleci-continuous/`](https://github.com/linear/linear-release/blob/main/examples/circleci-continuous) | | CircleCI | Scheduled | [`circleci-scheduled/`](https://github.com/linear/linear-release/blob/main/examples/circleci-scheduled) | +| RWX | Continuous | [`rwx-continuous/`](https://github.com/linear/linear-release/blob/main/examples/rwx-continuous) | +| RWX | Scheduled | [`rwx-scheduled/`](https://github.com/linear/linear-release/blob/main/examples/rwx-scheduled) | Each scheduled example includes a **monorepo** note in the header explaining how to split workflows for path filtering per platform. @@ -65,6 +67,7 @@ Tell the user to add the `LINEAR_ACCESS_KEY` secret to their CI environment: - **GitHub Actions**: Repository Settings → Secrets and variables → Actions → New repository secret - **GitLab CI**: Settings → CI/CD → Variables - **CircleCI**: Project Settings → Environment Variables +- **RWX**: Store as a vault secret with `rwx vaults secrets set linear-access-key=...`, then reference as `${{ secrets.linear-access-key }}` in the run definition The access key is created in Linear from the pipeline's settings page. Each pipeline has its own access key. diff --git a/src/ci-env.test.ts b/src/ci-env.test.ts index 9231eb6..7365e5d 100644 --- a/src/ci-env.test.ts +++ b/src/ci-env.test.ts @@ -14,6 +14,7 @@ describe("detectCIEnvironment", () => { delete process.env.TF_BUILD; delete process.env.BUILDKITE; delete process.env.TEAMCITY_VERSION; + delete process.env.RWX; delete process.env.CI; }); @@ -61,6 +62,11 @@ describe("detectCIEnvironment", () => { expect(detectCIEnvironment()).toEqual({ name: "teamcity" }); }); + it("detects RWX", () => { + process.env.RWX = "true"; + expect(detectCIEnvironment()).toEqual({ name: "rwx" }); + }); + it("detects generic CI", () => { process.env.CI = "true"; expect(detectCIEnvironment()).toEqual({ name: "ci" }); diff --git a/src/ci-env.ts b/src/ci-env.ts index ef2157b..ca6ecea 100644 --- a/src/ci-env.ts +++ b/src/ci-env.ts @@ -31,6 +31,9 @@ export function detectCIEnvironment(): CIEnvironment | null { if (process.env.TEAMCITY_VERSION) { return { name: "teamcity" }; } + if (process.env.RWX === "true") { + return { name: "rwx" }; + } if (process.env.CI === "true") { return { name: "ci" }; } diff --git a/src/user-agent.test.ts b/src/user-agent.test.ts index 7bf4e08..c2e62b2 100644 --- a/src/user-agent.test.ts +++ b/src/user-agent.test.ts @@ -13,6 +13,7 @@ describe("buildUserAgent", () => { process.env = { ...originalEnv }; // Ensure our CI environment is not detected delete process.env.GITHUB_ACTIONS; + delete process.env.RWX; delete process.env.CI; }); @@ -36,4 +37,10 @@ describe("buildUserAgent", () => { const userAgent = buildUserAgent(); expect(userAgent).toBe("linear-release/1.2.3 (gitlab-ci)"); }); + + it("builds user agent for RWX", () => { + process.env.RWX = "true"; + const userAgent = buildUserAgent(); + expect(userAgent).toBe("linear-release/1.2.3 (rwx)"); + }); });