|
| 1 | +# Releasing the Ionic CLI |
| 2 | + |
| 3 | +This runbook describes how the Ionic CLI is published and, specifically, **how |
| 4 | +to cut a major release** (e.g. `8.0.0`). It reflects the repository as of the |
| 5 | +`major-x` branch. |
| 6 | + |
| 7 | +## 1. How publishing works today |
| 8 | + |
| 9 | +The CLI is a **Lerna monorepo** with `independent` versioning |
| 10 | +([`lerna.json`](../lerna.json) → `"version": "independent"`). |
| 11 | +Each `@ionic/*` |
| 12 | +package is versioned on its own based on the commits that touched it. |
| 13 | + |
| 14 | +Release is **conventional-commit driven and automatic**: |
| 15 | + |
| 16 | +- [`.github/workflows/cd.yml`](workflows/cd.yml) runs **only on push to the |
| 17 | + `stable` branch**. |
| 18 | +- It runs `npm run publish:ci`, defined in [`package.json`](../package.json): |
| 19 | + |
| 20 | + - `lerna version --conventional-commits` computes each package's next version |
| 21 | + **from commit messages**, tags, and pushes the release commit. |
| 22 | + - `--create-release github` makes Lerna create the **GitHub Release** for each |
| 23 | + tag (see **3** — requires Lerna 5 and a `GH_TOKEN`). |
| 24 | + - `lerna exec ... npm publish --provenance` publishes the changed packages to |
| 25 | + npm. |
| 26 | +- `lerna.json` pins `"allowBranch": "stable"`, so `lerna version` refuses to run |
| 27 | + from any other branch. |
| 28 | +- After publishing, `cd.yml` builds and pushes a Docker image to GHCR (GitHub Container Registry). |
| 29 | + |
| 30 | +> **Key consequence:** the version bump is derived from commit messages, **not** |
| 31 | +> from `BREAKING.md`. `BREAKING.md` is human documentation only. To land |
| 32 | +> `@ionic/cli` on `8.0.0`, the branch history must contain **breaking-change |
| 33 | +> commits** — a `feat!:` / `fix!:` subject or a `BREAKING CHANGE:` footer — for |
| 34 | +> the `@ionic/cli` package. |
| 35 | +
|
| 36 | +## 2. Cutting the v8 major |
| 37 | + |
| 38 | +### 2a. Make sure the major bump will be computed |
| 39 | + |
| 40 | +Because versioning is conventional-commit driven: |
| 41 | + |
| 42 | +- The command-removal PRs (tracked separately) **must** use breaking-change |
| 43 | + commit syntax so Lerna computes a major bump for the affected package(s): |
| 44 | + |
| 45 | + ```bash |
| 46 | + feat(commands)!: remove deprecated Appflow/Enterprise/Cordova commands |
| 47 | + |
| 48 | + BREAKING CHANGE: The config, cordova, enterprise, git, init, link, |
| 49 | + live-update, login, logout, repair, signup, and ssh commands have been |
| 50 | + removed. See BREAKING.md. |
| 51 | + ``` |
| 52 | + |
| 53 | +- `independent` versioning means **only the packages whose commits include the |
| 54 | + breaking change get the major bump**. Confirm the removals live in the package |
| 55 | + that owns the command surface (typically `@ionic/cli`) so `@ionic/cli` is the |
| 56 | + one that reaches `8.0.0`. Utility `@ionic/*` packages bump independently based |
| 57 | + on their own commits. |
| 58 | + |
| 59 | +### 2b. Path to publish from `major-8.0` |
| 60 | + |
| 61 | +`cd.yml` only publishes from `stable`. The `major-8.0` work branch is **not** |
| 62 | +auto-published. Recommended path: |
| 63 | + |
| 64 | +1. Land all removal PRs (with breaking-change commits) onto `major-8.0`. |
| 65 | +2. Normal merge / rebase `major-8.0` → `develop`. |
| 66 | +3. Follow the normal `develop` → `stable` promotion. The push to `stable` |
| 67 | + triggers `cd.yml`, which computes the major bump, tags, publishes to npm, and |
| 68 | + creates the GitHub Releases. |
| 69 | + |
| 70 | +### 2c. Optional: ship a v8 release candidate first |
| 71 | + |
| 72 | +Use a prerelease on a `next` dist-tag before GA so consumers can test: |
| 73 | + |
| 74 | +```bash |
| 75 | +# A real prerelease under the `testing` dist-tag (publishes to npm, no git push): |
| 76 | +npm run publish:testing # publishes a `testing` prerelease with --no-push |
| 77 | + |
| 78 | +# A real RC under the `next` tag (run from an allowed branch): |
| 79 | +lerna publish premajor --preid rc --dist-tag next --conventional-commits |
| 80 | +``` |
| 81 | + |
| 82 | +> To cut an RC directly from `major-8.0`, temporarily relax `allowBranch` in |
| 83 | +> `lerna.json` (e.g. `["stable", "major-8.0"]`) or run the prerelease from |
| 84 | +> `stable`. Decide this deliberately — don't leave `major-8.0` permanently |
| 85 | +> publishable. |
| 86 | +
|
| 87 | +## 3. GitHub Release creation (Lerna 5) |
| 88 | + |
| 89 | +The CLI was upgraded from Lerna **3.13.3 → 5.x** so that releases are created on |
| 90 | +GitHub natively. This is wired via |
| 91 | +`--create-release github` in `publish:ci` (**1**). |
| 92 | + |
| 93 | +Requirements for `--create-release github` to work in CI: |
| 94 | + |
| 95 | +- It must run together with `--conventional-commits` (it is). |
| 96 | +- A **`GH_TOKEN`** must be present in the environment of the `lerna version` |
| 97 | + step. [`cd.yml`](workflows/cd.yml) provides it as `${{ github.token }}` — the |
| 98 | + built-in workflow token, which is sufficient because the workflow already |
| 99 | + grants `contents: write`. **No dedicated secret is required.** |
| 100 | + |
| 101 | +## 4. Pre-publish validation (always, but especially after the Lerna upgrade) |
| 102 | + |
| 103 | +These steps are **cross-platform** — every command is `npm`/`npx`-based and runs |
| 104 | +identically in PowerShell, `cmd`, bash, and zsh (Windows and macOS). For the Node |
| 105 | +version, don't rely on a number hardcoded here: the minimum is enforced by |
| 106 | +`engines.node` in each `package.json`, and the versions CI exercises are the |
| 107 | +`node:` matrix in [`ci.yml`](workflows/ci.yml) (the publish job pins its own under |
| 108 | +`node-version` in [`cd.yml`](workflows/cd.yml)). Use a version that satisfies both |
| 109 | +— checking those files keeps this runbook correct as the supported versions change. |
| 110 | + |
| 111 | +> **Order matters.** This is a `lerna bootstrap` monorepo: per-package |
| 112 | +> devDependencies (including `@types/node`) are installed and hoisted by |
| 113 | +> `npm run bootstrap`, **not** by `npm install`. You must run steps 0→1 before |
| 114 | +> any `build`/`lint`/`test`. Running `npm run build` on a freshly-cleaned tree |
| 115 | +> fails with `TS2688: Cannot find type definition file for 'node'` — that means |
| 116 | +> bootstrap was skipped, not a real code error. |
| 117 | +
|
| 118 | +```bash |
| 119 | +# 0. (optional) Pristine tree — npx rimraf is cross-platform, unlike rm -rf |
| 120 | +npx rimraf node_modules "packages/@ionic/*/node_modules" "packages/cli-scripts/node_modules" |
| 121 | + |
| 122 | +# 1. Install + bootstrap — REQUIRED before build/lint/test. |
| 123 | +# `npm install` only installs root devDeps (lerna, typescript); |
| 124 | +# `npm run bootstrap` installs+hoists every package's deps, then builds. |
| 125 | +npm install |
| 126 | +npm run bootstrap |
| 127 | + |
| 128 | +# 2. Confirm the Lerna upgrade — expect 5.x, and `--create-release` in the help |
| 129 | +npx lerna --version |
| 130 | +npx lerna version --help # look for: --create-release ... [choices: "gitlab","github"] |
| 131 | + |
| 132 | +# 3. Lint / test (bootstrap already built; re-run build explicitly if you like) |
| 133 | +npm run lint |
| 134 | +npm run test |
| 135 | +# Drift guard: a green build of @ionic/discover (no publisher.ts "broadcast" |
| 136 | +# TS2322) confirms netmask (^2.0.2, currently 2.1.x) resolved with its |
| 137 | +# bundled types and computeBroadcastAddress handles the nullable broadcast. |
| 138 | + |
| 139 | +# 4. Non-publishing version smoke test — exercises the Lerna 5 version path |
| 140 | +# WITHOUT publishing to npm or touching git. --allow-branch "*" overrides the |
| 141 | +# lerna.json `allowBranch: stable` guard for this one local run. |
| 142 | +# Single line + quoted "*" so it runs the same in PowerShell, cmd, bash, zsh. |
| 143 | +npx lerna version --conventional-commits --no-push --no-git-tag-version --allow-branch "*" --yes |
| 144 | +git checkout -- packages # discard the version/CHANGELOG file writes lerna just made |
| 145 | +``` |
| 146 | + |
| 147 | +> **`npm run publish:testing` is NOT a dry run — it publishes to npm.** |
| 148 | +> `lerna publish` runs an `npm publish` (the `@ionic/*` packages under the |
| 149 | +> `testing` dist-tag); `--no-push`/`--no-git-tag-version` only suppress *git* |
| 150 | +> actions, not the registry publish. It also only runs from `stable` (the |
| 151 | +> `allowBranch` guard). Use step 4 above for a safe local check. Run |
| 152 | +> `publish:testing` only when you actually intend to push a `testing` prerelease |
| 153 | +> to npm, with valid npm credentials. |
| 154 | +
|
| 155 | +The step-4 smoke test also reveals the **computed version bump** from the |
| 156 | +branch's conventional commits. Note it will show a `patch`/`minor` bump until |
| 157 | +the branch carries breaking-change commits (`feat!:` / `BREAKING CHANGE:`) — |
| 158 | +those, not `BREAKING.md`, are what make `@ionic/cli` land on `8.0.0` (see §1). |
| 159 | + |
| 160 | +> **Known platform note (pre-existing, unrelated to the release tooling):** on |
| 161 | +> **Windows**, one test in `integrations/cordova` asserts POSIX (`/`) path |
| 162 | +> separators and fails because `path.relative` returns `\` on Windows. It passes |
| 163 | +> on macOS/Linux (and therefore in CI). It is not introduced by these changes, |
| 164 | +> and the Cordova integration is slated for removal in v8. |
0 commit comments