From add69e1b8762903e3a964f3480950381bfbd4d4c Mon Sep 17 00:00:00 2001 From: Mike Little Date: Thu, 6 Aug 2026 14:29:08 +0100 Subject: [PATCH 1/2] Add GitHub Actions release process and publish metadata MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit altis-cli had no release process — the last npm publish was manual, ~3 years ago. This adds a Release workflow that publishes to npm on a GitHub Release (gated on tag==package.json version, a CLI smoke test, and an advisory audit), plus PR CI. package.json gains the metadata npm needs for a clean, provenanced publish and an accurate npmjs listing. Process and SemVer guidance live in RELEASING.md. Publishing still needs a human to add the NPM_TOKEN secret and cut the first release. Closes humanmade/product-dev#2158. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/ci.yml | 28 +++++++++++++ .github/workflows/release.yml | 46 +++++++++++++++++++++ README.md | 25 +++--------- RELEASING.md | 76 +++++++++++++++++++++++++++++++++++ package.json | 24 ++++++++++- 5 files changed, 179 insertions(+), 20 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/release.yml create mode 100644 RELEASING.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..ce76636 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,28 @@ +name: CI + +on: + pull_request: + push: + branches: [main] + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 20 + + - run: npm ci + + - name: Smoke test + run: npm run smoke + + - name: Security audit (advisory) + run: npm audit --audit-level=high + continue-on-error: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..d92b974 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,46 @@ +name: Release + +# Publishes to npm when a GitHub Release is published. +# See RELEASING.md for the full process and required NPM_TOKEN secret. +on: + release: + types: [published] + +permissions: + contents: read + id-token: write # Enables npm provenance. + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 20 + registry-url: 'https://registry.npmjs.org' + + - run: npm ci + + - name: Verify tag matches package.json version + run: | + tag="${GITHUB_REF_NAME#v}" + pkg="$(node -p "require('./package.json').version")" + if [ "$tag" != "$pkg" ]; then + echo "::error::Release tag ($tag) does not match package.json version ($pkg)." + exit 1 + fi + echo "Tag matches package.json version: $pkg" + + - name: Smoke test + run: npm run smoke + + - name: Security audit (advisory) + run: npm audit --audit-level=high + continue-on-error: true + + - name: Publish to npm + run: npm publish --provenance --access public + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/README.md b/README.md index 770a67b..f8e859c 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ CLI for running Altis utilities and commands. ## Installing -You need Node v18 or later. +You need Node v20 or later. ```sh # Install globally: @@ -78,26 +78,13 @@ Always use `altis-cli help` for the most up-to-date list of commands. * `maintenance set ` - Update maintenance contact. Example: `altis-cli instance maintenance set example-dev-01 --contact ops@example.com` * `reports ` - List reports. Example: `altis-cli instance reports example-dev-01` +## Releasing + +See [RELEASING.md](RELEASING.md) for the release process and versioning +guidelines. + ## Credits Created by Ryan McCue to make your day better. Licensed under the MIT license. Copyright 2017-2023 Human Made. - -``` - :+oo/ .hmNh oyy. /dMMm: /syo. - +dMMMMMMN. oMMMy :MMM+mMMMMMN oNMMMMm - mMNo-.dMMM+ dMMM+ oMMMMM+ dMMMmMdhMMMN - ++ sMMMo NMMM. yMMMM: hMMMM+ .MMMd - yMMM+ .MMMM:/+oNMMMs NMMMo :MMMs - hMMMo/oydMMMMMMMMMMMM. MMMN oMMM+ - /NMMMMMMNmMMMh-. .MMMd :MMMh yMMM- - +dMMMMMM/- oMMMo :MMMs +MMMo dMMM - oNMMy+MMMN sMMMo +MMM+ sMMM: mMMM -.mMMh. /MMMh sMMMo sMMM: +ddy hMMM- -hMMy sMMM+ +MMMh hMMM. :MMMNs+os -MMM- NMMN .MMMM: -/:. :hNMMMMh -dMMh:/mMMN: +MMMMy:..-/s. .. - yMMMMMMy. -hMMMMMMMNh- - -/:- -///:. -``` diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 0000000..64e51d5 --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,76 @@ +# Releasing altis-cli + +Releases are published to [npm](https://www.npmjs.com/package/altis-cli) +automatically by GitHub Actions whenever a **GitHub Release is published**. +`package.json` is the single source of truth for the version number. + +## Versioning (SemVer) + +We follow [Semantic Versioning](https://semver.org/): `MAJOR.MINOR.PATCH`. + +- **patch** (`x.y.Z`) — bug fixes and internal changes; no change to how the + CLI is used. +- **minor** (`x.Y.0`) — new commands, options, or output that are + backwards-compatible. +- **major** (`X.0.0`) — breaking changes: removed/renamed commands or flags, or + changed behaviour/output that could break existing scripts. + +## Cutting a release + +1. Make sure `main` is green in CI and you have the latest: + + ```sh + git checkout main && git pull + ``` + +2. Bump the version. This updates `package.json` and creates a matching + `vX.Y.Z` commit and git tag: + + ```sh + npm version patch # or: minor | major + ``` + +3. Push the commit and tag: + + ```sh + git push --follow-tags + ``` + +4. Create a **GitHub Release** for the new `vX.Y.Z` tag + (Releases → Draft a new release → choose the tag → add notes → Publish). + +Publishing the release triggers `.github/workflows/release.yml`, which: + +- installs dependencies (`npm ci`), +- **verifies the release tag matches `package.json`** (fails otherwise), +- runs the CLI smoke test, +- runs `npm audit` (advisory — does not block the release), +- publishes to npm with [provenance](https://docs.npmjs.com/generating-provenance-statements). + +## One-time setup: the `NPM_TOKEN` secret + +Publishing needs an npm access token stored as a repository secret named +`NPM_TOKEN`: + +1. On [npmjs.com](https://www.npmjs.com/) → **Access Tokens** → **Generate New + Token** → **Granular Access Token** (recommended) with **Read and write** + permission scoped to the `altis-cli` package. Use an **Automation** token so + it bypasses 2FA in CI. +2. In GitHub: **Settings → Secrets and variables → Actions → New repository + secret**, name it `NPM_TOKEN`, and paste the token. + +Provenance additionally requires the repository to be public and the +`repository` field in `package.json` to be set (both already true). + +## Rollback + +npm does **not** allow un-publishing a version after 72 hours (and discourages +it before that). To handle a bad release: + +- Mark it deprecated so users are warned: + + ```sh + npm deprecate altis-cli@X.Y.Z "Broken release — upgrade to X.Y.(Z+1)" + ``` + +- Fix forward: cut a new patch release with the fix. diff --git a/package.json b/package.json index 5a4f4b5..bfb90e7 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,28 @@ "name": "altis-cli", "type": "module", "version": "1.1.0", + "description": "Command-line tool for managing Altis Cloud hosting: stacks, backups, deploys, logs, X-Ray and more.", + "license": "MIT", + "homepage": "https://github.com/humanmade/altis-cli#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/humanmade/altis-cli.git" + }, + "bugs": { + "url": "https://github.com/humanmade/altis-cli/issues" + }, + "keywords": [ + "altis", + "cli", + "hosting", + "wordpress" + ], + "engines": { + "node": ">=20" + }, + "scripts": { + "smoke": "node bin/altis-cli.js --help" + }, "dependencies": { "@automattic/vip-search-replace": "^2.0.0", "@humanmade/ssm": "^0.0.1", @@ -33,7 +55,7 @@ "yargs": "^18.0.0" }, "bin": { - "altis-cli": "./bin/altis-cli.js" + "altis-cli": "bin/altis-cli.js" }, "files": [ "bin", From 68e3b2ca5e687844094004cc84ef103c54ac7b29 Mon Sep 17 00:00:00 2001 From: Mike Little Date: Fri, 7 Aug 2026 17:32:28 +0100 Subject: [PATCH 2/2] Make the release smoke test config-free MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The smoke test ran the CLI entry point, which loads config and runs the setup gate before parsing args — so an unconfigured non-TTY exits 1. Build the parser directly instead: still exercises the whole command tree via dynamic imports, without needing config. Entry-point gate fix tracked in #47. Co-Authored-By: Claude Opus 4.8 (1M context) --- package.json | 2 +- scripts/smoke.js | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 scripts/smoke.js diff --git a/package.json b/package.json index bfb90e7..3d61ed9 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "node": ">=20" }, "scripts": { - "smoke": "node bin/altis-cli.js --help" + "smoke": "node scripts/smoke.js" }, "dependencies": { "@automattic/vip-search-replace": "^2.0.0", diff --git a/scripts/smoke.js b/scripts/smoke.js new file mode 100644 index 0000000..170e694 --- /dev/null +++ b/scripts/smoke.js @@ -0,0 +1,12 @@ +// Release smoke test. +// +// Builds the full command parser and renders --help. This dynamically imports +// every command module, so a broken import or syntax error anywhere in the +// command tree fails the build. It deliberately does NOT go through +// bin/altis-cli.js, which gates on configuration/setup and exits non-zero when +// unconfigured (e.g. in CI) — see https://github.com/humanmade/altis-cli/issues/47. + +import configure from '../lib/commands/index.js'; + +const parser = await configure(); +await parser.parse(['--help']);