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
46 changes: 32 additions & 14 deletions .claude/commands/checkly-cli-update-dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,46 +4,59 @@

Update the monorepo's dependencies (including devDependencies) to the latest versions that remain compatible with our supported Node range. Optionally interpret `$ARGUMENTS` as a scope hint (e.g. specific package names, "dry run" to only report, or "cli only"). With no arguments, survey and update everything eligible across all workspace packages.

The guiding rule: **never raise our minimum supported Node version as a side effect of a dependency bump.** We are on the stable 8.x line; dropping Node 20 would be a breaking (v9) change, so a dependency whose newest version requires a higher Node than our floor must be held back to its latest still-compatible version.
Two guiding rules:

1. **Never raise our minimum supported Node version as a side effect of a dependency bump.** We are on the stable 8.x line; dropping Node 20 would be a breaking (v9) change, so a dependency whose newest version requires a higher Node than our floor must be held back to its latest still-compatible version.
2. **Respect the release-age embargo.** `pnpm-workspace.yaml` sets `minimumReleaseAge` (currently `2880` minutes = 2 days): pnpm refuses to install any version published within that window (a supply-chain safeguard). So the effective target for every package is the latest version that is **both** floor-compatible **and** older than the embargo. A version that is newer but still inside the embargo is not "held back" permanently — it is simply not eligible yet; note it and re-evaluate on the next run.

Because of rule 2, the npm registry's `latest` tag (and any tool that reads it directly, including the helper below and raw `pnpm outdated`) can **over-report** — it will name versions the embargo blocks. The authoritative target is whatever `pnpm update` actually resolves, since pnpm enforces the embargo. Never bypass it (e.g. with an explicit `pnpm add pkg@<too-new>` or the `minimumReleaseAgeExclude` setting) to force a version pnpm refused.

All paths below are relative to the repo root unless noted otherwise.

## Phase 0: Determine the Node floor
## Phase 0: Determine the constraints (Node floor + release-age embargo)

- [ ] Read `engines.node` from `packages/cli/package.json` and `packages/create-cli/package.json`. They should match. The current value is `^20.19.0 || >=22.12.0`, so the **minimum supported runtime is Node 20.19.0** — this is the version every dependency must remain installable on.
- [ ] Set `FLOOR` to that minimum (e.g. `20.19.0`). Do not hardcode it from this doc — read it live, since a future major may change it.
- [ ] Read `minimumReleaseAge` from `pnpm-workspace.yaml` and set `EMBARGO_MIN` to it (e.g. `2880`). Do not hardcode it from this doc — read it live. Any version published more recently than `EMBARGO_MIN` minutes ago is not installable yet.

## Phase 1: Survey what is outdated

- [ ] Run `pnpm outdated -r` to list outdated direct dependencies across all workspace packages.

## Phase 2: Choose target versions (compatibility-aware)
## Phase 2: Choose target versions (compatibility- and embargo-aware)

For each outdated package, pick the **latest version whose `engines.node` still satisfies `FLOOR`** — which may be the latest overall, or an older version if the newest major dropped support for our floor. Use this helper to compute it (requires the `semver` already installed under the CLI package):
For each outdated package, pick the **latest version that both satisfies `FLOOR` and clears the release-age embargo**. That may be the registry's latest overall, or something older — because the newest major dropped support for our floor, or because the newest release is still inside the embargo window. Use this helper to compute it (requires the `semver` already installed under the CLI package):

```bash
# Prints the latest non-prerelease version of each package compatible with FLOOR.
FLOOR=20.19.0 # read this from engines.node, do not assume
# Prints the latest non-prerelease version of each package that is BOTH
# floor-compatible AND older than the release-age embargo.
FLOOR=20.19.0 # read from engines.node, do not assume
EMBARGO_MIN=2880 # read minimumReleaseAge from pnpm-workspace.yaml, do not assume
SEMVER=$(ls -d node_modules/.pnpm/semver@* | head -1)
for p in "PACKAGE_NAME_1" "PACKAGE_NAME_2"; do
enc=$(echo "$p" | sed 's#/#%2f#')
curl -s "https://registry.npmjs.org/$enc" | \
SEMVERDIR="$PWD/$SEMVER" PKG="$p" FLOOR="$FLOOR" node -e '
SEMVERDIR="$PWD/$SEMVER" PKG="$p" FLOOR="$FLOOR" EMBARGO_MIN="$EMBARGO_MIN" node -e '
const semver=require(process.env.SEMVERDIR+"/node_modules/semver");
let d="";process.stdin.on("data",c=>d+=c).on("end",()=>{
const j=JSON.parse(d), floor=process.env.FLOOR, ok=[];
const j=JSON.parse(d), floor=process.env.FLOOR;
const cutoff=Date.now()-Number(process.env.EMBARGO_MIN)*60000, ok=[];
for(const [v,m] of Object.entries(j.versions)){
if(semver.prerelease(v)) continue;
const e=m.engines&&m.engines.node;
if(!e || semver.satisfies(floor,e)) ok.push(v);
if(e && !semver.satisfies(floor,e)) continue; // fails the Node floor
const t=j.time&&j.time[v];
if(t && Date.parse(t)>cutoff) continue; // still inside the embargo
ok.push(v);
}
ok.sort(semver.rcompare);
console.log(process.env.PKG+": latest compatible = "+(ok[0]||"NONE"));
console.log(process.env.PKG+": latest installable = "+(ok[0]||"NONE"));
});'
done
```

- [ ] For every outdated package, decide its target version with the helper above. If the latest compatible version is below the latest published version, **note the gap and the reason** (newer major requires Node > our floor).
- [ ] For every outdated package, decide its target version with the helper above. If the target is below the latest published version, **note the gap and which constraint caused it**: newer major requires Node > our floor (held back), or newest release is still inside the embargo (not eligible yet — re-evaluate next run).
- [ ] The helper is a cross-check, not the source of truth. `pnpm update` (Phase 3) enforces both constraints itself, so its resolved versions are authoritative; if they disagree with the helper, trust pnpm and investigate the discrepancy rather than forcing the helper's answer.

### Mandatory exclusions — do NOT update these

Expand All @@ -58,11 +71,16 @@ If `$ARGUMENTS` explicitly asks to update one of these, stop and confirm with th
```bash
pnpm update -r --latest <pkgA> <pkgB> ...
```
- [ ] For packages where the latest compatible version is **not** the latest published (held-back majors), pin the explicit range instead so `--latest` doesn't overshoot:
`--latest` will not overshoot into an embargoed version: pnpm enforces `minimumReleaseAge`, so it resolves each named package to the newest version that is both floor-compatible and past the embargo. If it appears to skip a version you expected, that version is almost certainly still inside the embargo — confirm the publish date rather than forcing it.
- [ ] For packages where the latest compatible version is **not** the latest published because the newest major raises our Node floor (held-back majors), pin the explicit range instead so `--latest` doesn't overshoot:
```bash
pnpm add -w -D "<pkg>@^<compatible-version>" # -w for root devDeps; target the owning package otherwise
```
- [ ] Confirm the resulting `package.json` ranges and `pnpm-lock.yaml` reflect the intended versions, and that the excluded packages (`@types/node`, `vitest`) are unchanged.
- [ ] `pnpm update --latest` also refreshes unrelated in-range transitive dependencies, which bloats the diff. For a minimal, reviewable change, once the `package.json` ranges are correct, restore the committed lockfile and let a plain install rewrite only what the new ranges require:
```bash
git checkout HEAD -- pnpm-lock.yaml && pnpm install
```
- [ ] Confirm the resulting `package.json` ranges and `pnpm-lock.yaml` reflect the intended versions; that the excluded packages (`@types/node`, `vitest`) are unchanged; that no embargoed version leaked into the lockfile; and that `pnpm install --frozen-lockfile` reports the lockfile is up to date.

## Phase 4: Verify

Expand All @@ -78,6 +96,6 @@ Run the full local pipeline and confirm each step is green before reporting succ
## Phase 5: Report

- [ ] Summarize in a table with these columns: **Package**, **Type** (`dependency` or `devDependency` — list whichever it is in the manifest), **Owning package** (`checkly`, `create-checkly`, or root), **From → To**, and **Notes**.
- [ ] In the same report, call out separately what was **held back** and why (which major dropped our Node floor), and the **intentional exclusions** (`@types/node`, `vitest`).
- [ ] In the same report, call out separately: what was **held back** and why (which major dropped our Node floor); what was **not eligible this week** because its newest release is still inside the release-age embargo (name the version and re-evaluate next run); and the **intentional exclusions** (`@types/node`, `vitest`).
- [ ] State the verification results plainly (build/lint/tests pass, no engine warnings).
- [ ] Do not commit or open a PR unless asked. If asked, use a `chore(deps):` conventional-commit subject and spell out the held-back/excluded packages in the body and PR description. The PR title should include the current date (e.g. `chore(deps): weekly CLI dependency update (2026-06-15)`).
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"lint-staged": "^16.4.0",
"rimraf": "^6.1.3",
"simple-git-hooks": "^2.13.1",
"typescript-eslint": "^8.62.0"
"typescript-eslint": "^8.62.1"
},
"simple-git-hooks": {
"commit-msg": "pnpm exec commitlint --edit",
Expand Down
8 changes: 4 additions & 4 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,10 @@
},
"homepage": "https://github.com/checkly/checkly-cli#readme",
"dependencies": {
"@oclif/core": "^4.11.11",
"@oclif/core": "^4.11.14",
"@oclif/plugin-help": "^6.2.53",
"@oclif/plugin-warn-if-update-available": "^3.1.68",
"@typescript-eslint/typescript-estree": "^8.62.0",
"@typescript-eslint/typescript-estree": "^8.62.1",
"acorn": "^8.17.0",
"acorn-walk": "^8.3.5",
"archiver": "^8.0.0",
Expand All @@ -138,7 +138,7 @@
"minimatch": "^10.2.5",
"mqtt": "^5.15.1",
"open": "^11.0.0",
"p-queue": "^9.3.0",
"p-queue": "^9.3.1",
"prompts": "^2.4.2",
"proxy-from-env": "^2.1.0",
"recast": "^0.23.12",
Expand All @@ -160,7 +160,7 @@
"config": "^4.4.2",
"cross-env": "^10.1.0",
"nanoid": "^5.1.16",
"oclif": "^4.23.23",
"oclif": "^4.23.24",
"rimraf": "^6.1.3",
"tar": "^7.5.19",
"typescript": "^6.0.3",
Expand Down
2 changes: 1 addition & 1 deletion packages/create-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
},
"homepage": "https://github.com/checkly/checkly-cli#readme",
"dependencies": {
"@oclif/core": "^4.11.11",
"@oclif/core": "^4.11.14",
"@oclif/plugin-help": "^6.2.53",
"@oclif/plugin-warn-if-update-available": "^3.1.68",
"axios": "^1.18.1",
Expand Down
Loading
Loading