--- name: patch-backstage description: >- Workflow to backport Backstage changes into RHDH by syncing a downstream maintenance branch and generating yarn patches. --- # RHDH Patch Generator ## Purpose Ship a fix on an RHDH **`release-*`** line **without** bumping published Backstage versions by adding **Yarn patches** (`.yarn/patches/`, `package.json` **`resolutions`**, lockfiles). - **`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. - **Build source:** **redhat-developer/backstage** at **`patch/release-`** only (not a separate upstream checkout). - **Cherry-pick** those SHAs onto maintenance **only** if the **pre-check** shows maintenance source still differs; otherwise **build + patch RHDH** only. ## Repos | Repo | Role | |------|------| | **redhat-developer/rhdh** | Patches live here. Sync **`release-`**; run **`yarn patch`** here. | | **redhat-developer/backstage** | Maintenance fork: **`patch/release-`**, optional cherry-pick, **`yarn build`** per package **`cd`**, copy **`dist/`** into RHDH patch temps. PRs from your fork. | | **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. | ## Parameters | Name | Required | Notes | |------|----------|--------| | **`RHDH_VERSION`** | Yes | e.g. `1.9` → **`release-1.9`**, **`patch/release-1.9`**. Do not infer from the current branch. | | **`RHDH_ROOT`** | No | Absolute path to the RHDH repo root (appears as **``** in examples). Inferred from context if omitted. | | **`COMMITS`** | Typical | Upstream SHAs (oldest first for cherry-pick). If no SHAs, manual **`dist`** / patch only. | | **`PACKAGES`** | If unclear | `@backstage/...` names. Derive from **`COMMITS`** (below) when paths map cleanly. | **Path map:** `@backstage/plugin-` → **`plugins//`**; other **`@backstage/`** → **`packages//`**. ### Deriving **`PACKAGES`** from **`COMMITS`** In the **maintenance** clone, after **`git fetch master`** so **`COMMITS`** exist locally: **`git show --name-only --pretty=format: `** (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. **Example:** `66e08b08f94a31cbf28b416c89b61549bc3b64a2` → **`@backstage/cli-common`**, **`@backstage/backend-plugin-api`**, **`@backstage/plugin-techdocs-node`**. ## Git remotes, hooks, and where this skill file lives **Map remotes by URL** in **each** clone (`git remote -v`); never assume **`upstream`** means a given org. - **RHDH core** (`release-*`): URL **redhat-developer/rhdh** → **`RHDH_CORE_REMOTE`**. - **Maintenance Backstage** (`patch/release-*`, fork push): URL **redhat-developer/backstage** + usually your fork as **`origin`**. - **Upstream Backstage** (fetch **`COMMITS`** only): URL **backstage/backstage** → **`BACKSTAGE_UPSTREAM_REMOTE`**. Exact maintenance tip: `git fetch https://github.com/redhat-developer/backstage.git patch/release-` then **`HUSKY=0 git checkout -B patch/release- FETCH_HEAD`** when Husky would otherwise run on checkout. **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. ```bash cd && HUSKY=0 git fetch release- && HUSKY=0 git checkout release- && HUSKY=0 git pull release- ``` **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. ## Agent execution - **Batch** related shell commands with **`&&`** and **`cd `**; 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). - **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**). - **Do not** invent remotes or wander with speculative **`find`**; **do** run steps this doc names (**`git remote -v`**, **`yarn why`**, etc.). ## Dist baseline - **`yarn patch`** overlays **`dist/`** on the **version RHDH already resolves** (lockfile), so **`PACKAGE_VERSION`** must come from **`yarn why`**, not from “what Backstage released.” - **Compile only** on **redhat-developer/backstage** **`patch/release-`** after fetching that ref from **redhat-developer** (local/fork tips can diverge by name). - **Do not** build from **backstage/backstage** release tags or other upstream checkouts to “match” versions unless this workflow is explicitly extended. ## Workflow (overview) 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). 2. **RHDH:** **`HUSKY=0`** fetch/checkout/pull **`release-`**. 3. **Maintenance:** Fetch **`patch/release-*`** from redhat-developer; **`HUSKY=0 checkout -B`**; **`git fetch 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`**). 4. **RHDH:** Remove stale **`.patch`** + **`resolutions`** for targets; **`yarn why`** → versions; **`yarn patch`** / replace **`dist`** / **`patch-commit`**; **clean up `resolutions`**; **`yarn install`**. 5. **Verify:** **`@patch:`** in each relevant **`yarn.lock`**; **`yarn why`** shows **`via patch:`**; commit artifacts. --- ## Step 1: Sync RHDH **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. 1. **`git remote -v`** → **`RHDH_CORE_REMOTE`** = remote for **redhat-developer/rhdh**. 2. **`HUSKY=0 git fetch … release-`** && **`HUSKY=0 git checkout …`** && **`HUSKY=0 git pull …`**. Fail if the branch is missing. 3. Set **`MAINTENANCE_BRANCH`** = **`patch/release-`** (used when opening a Backstage PR). ## Step 2: Maintenance clone **One** clone with **redhat-developer/backstage** + **backstage/backstage** remotes. ### 2.1 Sync `patch/release-*` ```bash git fetch https://github.com/redhat-developer/backstage.git patch/release- \ && HUSKY=0 git checkout -B patch/release- FETCH_HEAD ``` Then **`git fetch master`**. **backstage/backstage** lands merged work on **`master`**; **`COMMITS`** should be reachable from **`master`**. **Do not** check out upstream as the build tree. ### 2.2 Pre-check (skip cherry-pick when source already matches) For each SHA in **`COMMITS`** (oldest first): 1. **`git show --name-only --pretty=format: `** 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. 3. **`git diff HEAD -- `** (union paths if multiple SHAs). **Empty diff** → maintenance already has the functional fix; **do not cherry-pick** (avoids changelog/version noise). ### 2.3 Patch-only vs ask - **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. - **Ask** if intent is vague (“sync Backstage” only) or they may want a **maintenance PR** for traceability despite identical source. ### 2.4 Cherry-pick (when pre-check was non-empty) **`git cherry-pick `** (oldest first) onto **`patch/release-*`**. **Conflicts:** Prefer the **cherry-picked commit’s** content for conflicted **`src/`** (during cherry-pick, **`git checkout --theirs -- `** 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. ### 2.5 Push maintenance (optional) Push to **`FORK_REMOTE`** and open a PR to **redhat-developer/backstage** base **`MAINTENANCE_BRANCH`** (no direct push to **redhat-developer**). ### 2.6 Build **`PACKAGES`** For each package: **`cd`** **`plugins//`** or **`packages//`** → **`yarn build`**. If **`dist-types`** or build fails: maintenance repo root **`yarn install`** / **`yarn tsc`**, then retry per-package **`yarn build`**. ## Step 3: Prepare RHDH 1. Remove prior **`.patch`** files and matching **`resolutions`** entries for the packages you are refreshing. 2. **`yarn why @backstage/`** from **RHDH root**. Record **PACKAGE_VERSION** for Step 4. ## Step 4: Generate patches Run from **RHDH root**. **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. **Per package:** 1. **`yarn patch @npm:`** 2. **`rm -rf /dist`** && **`cp -r //.../dist /dist`** 3. **`yarn patch-commit -s `** in that workspace. ### After **`yarn patch-commit`** (cleanup — required) 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:`**). 2. **Delete** spurious range keys **`patch-commit`** added (e.g. **`@backstage/foo@^1.6.0`** → patch built from **`@npm:1.5.0`**). 3. **Install:** **`yarn install`**. ## Step 5: Verify (required) Incomplete until every patched package shows a **patch locator** in **`yarn.lock`** and **`yarn why`**. 1. **`yarn install`** in each touched project. 2. **`grep '@backstage/@patch' yarn.lock`**. 3. **`yarn why @backstage/`** in the same directory: must include **`via patch:`** / **`@…@patch:`**, not only **`via npm:`**. **Optional:** Spot-check **`node_modules/@backstage//dist/`**. **Commit:** **`.yarn/patches/*.patch`**, **`package.json`** **`resolutions`**, **`yarn.lock`**; PR to RHDH. **PR CI:** a change under **`.yarn/patches/`** makes PR CI build and test every package (not **`--affected`**) and run the **Backstage bump checks** job. See **`scripts/backstage-bump-check/README.md`**. It exercises the patched code only where existing tests reach it; E2E remains the integration gate. ### Final summary 1. Links: `https://github.com/backstage/backstage/commit/` for each **`COMMITS`** entry. 2. **`PACKAGES`**, patch locations, notable **`resolutions`** keys. 3. Cherry-pick skipped vs applied; conflicts/aborts if any. 4. **`MAINTENANCE_BRANCH`**, optional Backstage PR link; RHDH PR intent. 5. **`RHDH_VERSION`**. ## Safety - Do not invent **`patch/release-*`** if missing on **redhat-developer/backstage**. - No blind conflict resolution on cherry-picks; no direct push to **redhat-developer** remotes without process. ## Common issues (quick reference) | Symptom | What to do | |---------|------------| | Wrong maintenance tip | Fetch **`https://github.com/redhat-developer/backstage.git patch/release-`**, **`checkout -B … FETCH_HEAD`** | | **`bad object`** on cherry-pick / show | **`git fetch master`** | | Checkout/pull fails after Yarn / Husky | **`HUSKY=0`** on those git commands | | **`yarn why` / lockfile: no `@patch:`** | Remove bare semver **`resolutions`** for that pkg; use one **`patch:`** locator; drop wrong range keys; reinstall | | Build fails (missing types) | Maintenance root **`yarn install`** / **`yarn tsc`**, then **`cd`** package **`yarn build`** | | Patch wrong version | Use **`yarn why`** version, not RHDH meta-version | | Skill not in Cursor | **`.rulesync/skills/`** may not sync to **`.cursor/`**—see **Rulesync / Cursor** |