|
| 1 | +--- |
| 2 | +name: patch-backstage |
| 3 | +description: >- |
| 4 | + Workflow to backport Backstage changes into RHDH by syncing a downstream |
| 5 | + maintenance branch and generating yarn patches. |
| 6 | +--- |
| 7 | +# RHDH Patch Generator |
| 8 | + |
| 9 | +## Purpose |
| 10 | + |
| 11 | +Ship a fix on an RHDH **`release-*`** line **without** bumping published Backstage versions by adding **Yarn patches** (`.yarn/patches/`, `package.json` **`resolutions`**, lockfiles). |
| 12 | + |
| 13 | +- **`COMMITS`:** SHAs on **[backstage/backstage](https://github.com/backstage/backstage)** that are on **`master`** (merged fixes). Fetch **`master`** from **`BACKSTAGE_UPSTREAM_REMOTE`** so those objects exist in the **maintenance** clone for **`git show`** / cherry-pick. |
| 14 | +- **Build source:** **redhat-developer/backstage** at **`patch/release-<VERSION>`** only (not a separate upstream checkout). |
| 15 | +- **Cherry-pick** those SHAs onto maintenance **only** if the **pre-check** shows maintenance source still differs; otherwise **build + patch RHDH** only. |
| 16 | + |
| 17 | +## Repos |
| 18 | + |
| 19 | +| Repo | Role | |
| 20 | +|------|------| |
| 21 | +| **redhat-developer/rhdh** | Patches live here. Sync **`release-<VERSION>`**; run **`yarn patch`** here (root and/or **`dynamic-plugins/`**). | |
| 22 | +| **redhat-developer/backstage** | Maintenance fork: **`patch/release-<VERSION>`**, optional cherry-pick, **`yarn build`** per package **`cd`**, copy **`dist/`** into RHDH patch temps. PRs from your fork. | |
| 23 | +| **backstage/backstage** | Upstream object source only: remote on the **same** maintenance clone, **`git fetch`** for **`COMMITS`**. **Do not** use a second upstream checkout as the build tree. | |
| 24 | + |
| 25 | +## Parameters |
| 26 | + |
| 27 | +| Name | Required | Notes | |
| 28 | +|------|----------|--------| |
| 29 | +| **`RHDH_VERSION`** | Yes | e.g. `1.9` → **`release-1.9`**, **`patch/release-1.9`**. Do not infer from the current branch. | |
| 30 | +| **`RHDH_ROOT`** | No | Absolute path to the RHDH repo root (appears as **`<RHDH_ROOT>`** in examples). Inferred from context if omitted. | |
| 31 | +| **`COMMITS`** | Typical | Upstream SHAs (oldest first for cherry-pick). If no SHAs, manual **`dist`** / patch only. | |
| 32 | +| **`PACKAGES`** | If unclear | `@backstage/...` names. Derive from **`COMMITS`** (below) when paths map cleanly. | |
| 33 | + |
| 34 | +**Path map:** `@backstage/plugin-<id>` → **`plugins/<id>/`**; other **`@backstage/<id>`** → **`packages/<id>/`**. |
| 35 | + |
| 36 | +### Deriving **`PACKAGES`** from **`COMMITS`** |
| 37 | + |
| 38 | +In the **maintenance** clone, after **`git fetch <BACKSTAGE_UPSTREAM_REMOTE> master`** so **`COMMITS`** exist locally: **`git show --name-only --pretty=format: <SHA>`** (union for multiple SHAs). Map **`plugins/*`** and **`packages/*`** roots; read each **`package.json`** **`name`**; dedupe. Ignore-only changes (root lockfile, **`.changeset/`**, **`docs/`**, version-only **`package.json`**) → ask which packages to patch. |
| 39 | + |
| 40 | +**Example:** `66e08b08f94a31cbf28b416c89b61549bc3b64a2` → **`@backstage/cli-common`**, **`@backstage/backend-plugin-api`**, **`@backstage/plugin-techdocs-node`**. |
| 41 | + |
| 42 | +## Git remotes, hooks, and where this skill file lives |
| 43 | + |
| 44 | +**Map remotes by URL** in **each** clone (`git remote -v`); never assume **`upstream`** means a given org. |
| 45 | + |
| 46 | +- **RHDH core** (`release-*`): URL **redhat-developer/rhdh** → **`RHDH_CORE_REMOTE`**. |
| 47 | +- **Maintenance Backstage** (`patch/release-*`, fork push): URL **redhat-developer/backstage** + usually your fork as **`origin`**. |
| 48 | +- **Upstream Backstage** (fetch **`COMMITS`** only): URL **backstage/backstage** → **`BACKSTAGE_UPSTREAM_REMOTE`**. |
| 49 | + |
| 50 | +Exact maintenance tip: |
| 51 | +`git fetch https://github.com/redhat-developer/backstage.git patch/release-<RHDH_VERSION>` |
| 52 | +then **`HUSKY=0 git checkout -B patch/release-<RHDH_VERSION> FETCH_HEAD`** when Husky would otherwise run on checkout. |
| 53 | + |
| 54 | +**Silencing hooks:** For branch sync only (Steps 1–2: fetch/checkout/pull/**`checkout -B`**, and **`git cherry-pick`** when you are not relying on hook side effects), prefix with **`HUSKY=0`**. Omit **`HUSKY=0`** on **`git commit`** if you want **lint-staged** locally. |
| 55 | + |
| 56 | +```bash |
| 57 | +cd <RHDH_ROOT> && HUSKY=0 git fetch <RHDH_CORE_REMOTE> release-<V> && HUSKY=0 git checkout release-<V> && HUSKY=0 git pull <RHDH_CORE_REMOTE> release-<V> |
| 58 | +``` |
| 59 | + |
| 60 | +**Rulesync:** Edit **`SKILL.md` only** under **`.rulesync/skills/patch-backstage/`** (rulesync expects **one directory per skill** with **`SKILL.md`** inside; a flat **`*.md`** at **`skills/`** root is ignored). With **`"skills"`** and **`"simulateSkills": true`** in **`rulesync.jsonc`**, **`yarn rulesync:generate`** writes **both** **`.claude/skills/`** and **`.cursor/skills/`** from that tree (rulesync treats Cursor skill output as “simulated”). Stage and commit generated paths with **`.rulesync/`** after edits. |
| 61 | + |
| 62 | +## Agent execution |
| 63 | + |
| 64 | +- **Batch** related shell commands with **`&&`** and **`cd <RHDH_ROOT>`**; cwd may not persist between tool calls. Use **`network` / `git_write` / `all`** as needed (**`all`** for **`rm`/`cp`** into Yarn patch temps or stubborn sandboxes). |
| 65 | +- **Stop and ask** when **`RHDH_VERSION`**, **`COMMITS`**, clone paths, or workspace ownership is missing or ambiguous—not for a second confirmation when the user already asked for **yarn patches** for given SHAs (see **Pre-check → patch-only**). |
| 66 | +- **Do not** invent remotes or wander with speculative **`find`**; **do** run steps this doc names (**`git remote -v`**, **`yarn why`**, etc.). |
| 67 | + |
| 68 | +## Dist baseline |
| 69 | + |
| 70 | +- **`yarn patch`** overlays **`dist/`** on the **version RHDH already resolves** (lockfile), so **`PACKAGE_VERSION`** must come from **`yarn why`**, not from “what Backstage released.” |
| 71 | +- **Compile only** on **redhat-developer/backstage** **`patch/release-<RHDH_VERSION>`** after fetching that ref from **redhat-developer** (local/fork tips can diverge by name). |
| 72 | +- **Do not** build from **backstage/backstage** release tags or other upstream checkouts to “match” versions unless this workflow is explicitly extended. |
| 73 | + |
| 74 | +## Workflow (overview) |
| 75 | + |
| 76 | +1. **Pre-flight:** Clean trees and remotes (**Step 1** opening + **Git remotes**); set **`RHDH_CORE_REMOTE`**, **`BACKSTAGE_UPSTREAM_REMOTE`**, optional **`FORK_REMOTE`** (your Backstage fork for PRs). |
| 77 | +2. **RHDH:** **`HUSKY=0`** fetch/checkout/pull **`release-<RHDH_VERSION>`**. |
| 78 | +3. **Maintenance:** Fetch **`patch/release-*`** from redhat-developer; **`HUSKY=0 checkout -B`**; **`git fetch <BACKSTAGE_UPSTREAM_REMOTE> master`** (upstream integration branch for **`COMMITS`**); **pre-check**; cherry-pick **or** patch-only path; **`yarn build`** per **`PACKAGES`** (**`cd` + `yarn build`**, not root **`yarn workspace … build`**). |
| 79 | +4. **RHDH:** Remove stale **`.patch`** + **`resolutions`** for targets; **`yarn why`** → versions; **`yarn patch`** / replace **`dist`** / **`patch-commit`**; **clean up `resolutions`**; **`yarn install`** (root and **`dynamic-plugins/`** as needed). |
| 80 | +5. **Verify:** **`@patch:`** in each relevant **`yarn.lock`**; **`yarn why`** shows **`via patch:`**; commit artifacts. |
| 81 | + |
| 82 | +--- |
| 83 | + |
| 84 | +## Step 1: Sync RHDH |
| 85 | + |
| 86 | +**Pre-flight (both repos):** **`git status`** clean in **RHDH** and the **maintenance** Backstage clone (stash WIP or **`git merge --abort`** / **`git rebase --abort`** as needed). Do not run the workflow mid-conflict. |
| 87 | + |
| 88 | +1. **`git remote -v`** → **`RHDH_CORE_REMOTE`** = remote for **redhat-developer/rhdh**. |
| 89 | +2. **`HUSKY=0 git fetch … release-<RHDH_VERSION>`** && **`HUSKY=0 git checkout …`** && **`HUSKY=0 git pull …`**. Fail if the branch is missing. |
| 90 | +3. Set **`MAINTENANCE_BRANCH`** = **`patch/release-<RHDH_VERSION>`** (used when opening a Backstage PR). |
| 91 | + |
| 92 | +## Step 2: Maintenance clone |
| 93 | + |
| 94 | +**One** clone with **redhat-developer/backstage** + **backstage/backstage** remotes. |
| 95 | + |
| 96 | +### 2.1 Sync `patch/release-*` |
| 97 | + |
| 98 | +```bash |
| 99 | +git fetch https://github.com/redhat-developer/backstage.git patch/release-<RHDH_VERSION> \ |
| 100 | + && HUSKY=0 git checkout -B patch/release-<RHDH_VERSION> FETCH_HEAD |
| 101 | +``` |
| 102 | + |
| 103 | +Then **`git fetch <BACKSTAGE_UPSTREAM_REMOTE> master`**. **backstage/backstage** lands merged work on **`master`**; **`COMMITS`** should be reachable from **`master`**. **Do not** check out upstream as the build tree. |
| 104 | + |
| 105 | +### 2.2 Pre-check (skip cherry-pick when source already matches) |
| 106 | + |
| 107 | +For each SHA in **`COMMITS`** (oldest first): |
| 108 | + |
| 109 | +1. **`git show --name-only --pretty=format: <SHA>`** |
| 110 | +2. **Drop** bookkeeping-only paths: root **`package.json`**, **`CHANGELOG.md`**, package **`package.json`** version-only edits, **`.changeset/`**, **`docs/`**, lockfiles, etc. **Keep** **`src/`**, tests, fixtures tied to the fix. |
| 111 | +3. **`git diff <SHA> HEAD -- <kept paths>`** (union paths if multiple SHAs). |
| 112 | + |
| 113 | +**Empty diff** → maintenance already has the functional fix; **do not cherry-pick** (avoids changelog/version noise). |
| 114 | + |
| 115 | +### 2.3 Patch-only vs ask |
| 116 | + |
| 117 | +- **Proceed** without extra confirmation if the user already asked for **yarn patches** for **`COMMITS`** on this **`release-*`**: empty pre-check → go to **2.6 Build** and RHDH Steps 3–5; note in the **Final summary** that cherry-pick was skipped. |
| 118 | +- **Ask** if intent is vague (“sync Backstage” only) or they may want a **maintenance PR** for traceability despite identical source. |
| 119 | + |
| 120 | +### 2.4 Cherry-pick (when pre-check was non-empty) |
| 121 | + |
| 122 | +**`git cherry-pick <SHA>`** (oldest first) onto **`patch/release-*`**. |
| 123 | + |
| 124 | +**Conflicts:** Prefer the **cherry-picked commit’s** content for conflicted **`src/`** (during cherry-pick, **`git checkout --theirs -- <path>`** refers to that commit). If fixing conflicts would drop the functional fix, or conflicts are only **changelog/version** noise you should not merge, **`git cherry-pick --abort`**, note it in the **Final summary**, and coordinate with the user. Do not push a broken maintenance branch. |
| 125 | + |
| 126 | +### 2.5 Push maintenance (optional) |
| 127 | + |
| 128 | +Push to **`FORK_REMOTE`** and open a PR to **redhat-developer/backstage** base **`MAINTENANCE_BRANCH`** (no direct push to **redhat-developer**). |
| 129 | + |
| 130 | +### 2.6 Build **`PACKAGES`** |
| 131 | + |
| 132 | +For each package: **`cd`** **`plugins/<id>/`** or **`packages/<id>/`** → **`yarn build`**. If **`dist-types`** or build fails: maintenance repo root **`yarn install`** / **`yarn tsc`**, then retry per-package **`yarn build`**. |
| 133 | + |
| 134 | +## Step 3: Prepare RHDH |
| 135 | + |
| 136 | +**`dynamic-plugins/`** is a **separate** Yarn project: its **`yarn.lock`** and **`resolutions`** are independent of the root. |
| 137 | + |
| 138 | +1. Remove prior **`.patch`** files and matching **`resolutions`** entries for the packages you are refreshing (search **both** root and **`dynamic-plugins/package.json`**). |
| 139 | +2. **`yarn why @backstage/<pkg>`** from **RHDH root**; if empty, run from **`dynamic-plugins/`**. Record **PACKAGE_VERSION** for Step 4. |
| 140 | + |
| 141 | +## Step 4: Generate patches |
| 142 | + |
| 143 | +Run from **RHDH root** or **`dynamic-plugins/`** depending on where the dependency resolves. |
| 144 | + |
| 145 | +**Temp folder:** Each **`yarn patch …`** prints a **new** path—use it immediately for **`rm`**, **`cp`**, **`yarn patch-commit -s`** (same shell or paste path). Do not reuse an old temp. Sandboxes may need **`all`** for **`cp`** into system temp. |
| 146 | + |
| 147 | +**Per package:** |
| 148 | + |
| 149 | +1. **`yarn patch <package>@npm:<PACKAGE_VERSION>`** |
| 150 | +2. **`rm -rf <PATCH_TEMP>/dist`** && **`cp -r <backstage-clone>/<plugins|packages>/.../dist <PATCH_TEMP>/dist`** |
| 151 | +3. **`yarn patch-commit -s <PATCH_TEMP>`** in that workspace. |
| 152 | + |
| 153 | +If a package resolves in **both** trees at the **same** version, patch **both** places (or mirror the **`patch:`** resolution into **`dynamic-plugins/package.json`** and run **`yarn install`** there too). |
| 154 | + |
| 155 | +### After **`yarn patch-commit`** (cleanup — required) |
| 156 | + |
| 157 | +1. **Replace** any bare **`"@backstage/foo": "1.2.3"`** in **`resolutions`** with the **single** **`patch:`** locator Yarn printed—**do not** leave bare semver beside new patch keys (patch may not apply; Step 5 will show plain **`@npm:`**). |
| 158 | +2. **Delete** spurious range keys **`patch-commit`** added (e.g. **`@backstage/foo@^1.6.0`** → patch built from **`@npm:1.5.0`**), which mis-resolve other workspaces (**`dynamic-plugins/`** may need **`@backstage/backend-plugin-api@1.6.0`** untouched by root). |
| 159 | +3. **Install:** **`yarn install`** at root and/or **`dynamic-plugins/`**; |
| 160 | + |
| 161 | +## Step 5: Verify (required) |
| 162 | + |
| 163 | +Incomplete until every patched package shows a **patch locator** in **`yarn.lock`** and **`yarn why`**. |
| 164 | + |
| 165 | +1. **`yarn install`** in each touched project. |
| 166 | +2. **`grep '@backstage/<pkg>@patch' yarn.lock`** (run inside **`dynamic-plugins/`** for packages resolved only there). |
| 167 | +3. **`yarn why @backstage/<pkg>`** in the same directory: must include **`via patch:`** / **`@…@patch:`**, not only **`via npm:<version>`**. |
| 168 | + |
| 169 | +**Optional:** Spot-check **`node_modules/@backstage/<pkg>/dist/`**. |
| 170 | + |
| 171 | +**Commit:** **`.yarn/patches/*.patch`**, **`package.json`** **`resolutions`**, **`yarn.lock`** (root and/or **`dynamic-plugins/`**); PR to RHDH. |
| 172 | + |
| 173 | +### Final summary |
| 174 | + |
| 175 | +1. Links: `https://github.com/backstage/backstage/commit/<SHA>` for each **`COMMITS`** entry. |
| 176 | +2. **`PACKAGES`**, patch locations (root vs **`dynamic-plugins/`**), notable **`resolutions`** keys. |
| 177 | +3. Cherry-pick skipped vs applied; conflicts/aborts if any. |
| 178 | +4. **`MAINTENANCE_BRANCH`**, optional Backstage PR link; RHDH PR intent. |
| 179 | +5. **`RHDH_VERSION`**. |
| 180 | + |
| 181 | +## Safety |
| 182 | + |
| 183 | +- Do not invent **`patch/release-*`** if missing on **redhat-developer/backstage**. |
| 184 | +- No blind conflict resolution on cherry-picks; no direct push to **redhat-developer** remotes without process. |
| 185 | + |
| 186 | +## Common issues (quick reference) |
| 187 | + |
| 188 | +| Symptom | What to do | |
| 189 | +|---------|------------| |
| 190 | +| Wrong maintenance tip | Fetch **`https://github.com/redhat-developer/backstage.git patch/release-<V>`**, **`checkout -B … FETCH_HEAD`** | |
| 191 | +| **`bad object`** on cherry-pick / show | **`git fetch <BACKSTAGE_UPSTREAM_REMOTE> master`** | |
| 192 | +| Checkout/pull fails after Yarn / Husky | **`HUSKY=0`** on those git commands | |
| 193 | +| **`yarn why` / lockfile: no `@patch:`** | Remove bare semver **`resolutions`** for that pkg; use one **`patch:`** locator; drop wrong range keys; reinstall | |
| 194 | +| Build fails (missing types) | Maintenance root **`yarn install`** / **`yarn tsc`**, then **`cd`** package **`yarn build`** | |
| 195 | +| Patch wrong version | Use **`yarn why`** version, not RHDH meta-version | |
| 196 | +| Skill not in Cursor | **`.rulesync/skills/`** may not sync to **`.cursor/`**—see **Rulesync / Cursor** | |
0 commit comments