Skip to content
Open
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
28 changes: 28 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -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
46 changes: 46 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -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 }}
25 changes: 6 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -78,26 +78,13 @@ Always use `altis-cli help` for the most up-to-date list of commands.
* `maintenance set <instance>` - Update maintenance contact. Example: `altis-cli instance maintenance set example-dev-01 --contact ops@example.com`
* `reports <instance>` - 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-
-/:- -///:.
```
76 changes: 76 additions & 0 deletions RELEASING.md
Original file line number Diff line number Diff line change
@@ -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.
24 changes: 23 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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 scripts/smoke.js"
},
"dependencies": {
"@automattic/vip-search-replace": "^2.0.0",
"@humanmade/ssm": "^0.0.1",
Expand Down Expand Up @@ -33,7 +55,7 @@
"yargs": "^18.0.0"
},
"bin": {
"altis-cli": "./bin/altis-cli.js"
"altis-cli": "bin/altis-cli.js"
},
"files": [
"bin",
Expand Down
12 changes: 12 additions & 0 deletions scripts/smoke.js
Original file line number Diff line number Diff line change
@@ -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']);
Loading