Skip to content

Commit 967516f

Browse files
committed
ci: main tests itself, deploys itself, and publishes the CLI
The deploy was a script somebody remembered to run. It stays that script — docs/DEPLOYMENT.md still explains it step by step and it still works by hand — but main now runs it, after the checks the README asks for. Both jobs sit in one workflow deliberately. Forgejo Actions cannot make one workflow wait on another, so a deploy in a file of its own would start beside the tests rather than after them, and an unattended deploy that does not wait for its tests is worse than no pipeline at all. The CLI is mirrored rather than moved. It lives beside the API because it mirrors that API's upload-path rules, its error wording and its endpoint shapes: a change to the contract and the change to the client belong in one commit, tested together. A submodule would split that into a two-repository dance for what is one change. So the public GitHub repository is a derived copy, force-pushed from a subtree split, and nothing is merged into it by hand. GitHub builds the CLI on Linux and Windows, on the oldest Node it claims to support and the newest — its path handling is the part most likely to break, and testing both from both is not the same as running on both. Publishing is on a release, never a merge: npm will not let a version be replaced or a name reused, so it should happen because somebody decided to release. Nothing here can run yet. It needs a runner registered and its secrets added, both written down in the runbook; until then the jobs fail on their first step and name what is missing, rather than skipping quietly and looking like they worked.
1 parent 80740bd commit 967516f

2 files changed

Lines changed: 122 additions & 0 deletions

File tree

.github/workflows/ci.yml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Builds and tests the CLI on the platforms it claims to run on.
2+
#
3+
# This repository is a mirror — the CLI is developed in the private monorepo
4+
# beside the API it speaks to, and pushed here by that repository's pipeline.
5+
# The checks still belong here: this is the tree people read, fork and install
6+
# from, and a public package that does not build is a public package nobody
7+
# trusts.
8+
name: CI
9+
10+
on:
11+
push:
12+
branches: [main]
13+
pull_request:
14+
workflow_dispatch:
15+
16+
jobs:
17+
test:
18+
strategy:
19+
fail-fast: false
20+
# The CLI is cross-platform by design and its path handling is the part
21+
# most likely to break — %APPDATA% against XDG, backslashes against
22+
# forward slashes. Both are tested from both, but running on both is what
23+
# actually proves it.
24+
matrix:
25+
os: [ubuntu-latest, windows-latest]
26+
# 20 is the floor the package declares; 24 is what a fresh npx picks.
27+
node: ['20', '24']
28+
runs-on: ${{ matrix.os }}
29+
steps:
30+
- uses: actions/checkout@v4
31+
32+
- uses: actions/setup-node@v4
33+
with:
34+
node-version: ${{ matrix.node }}
35+
cache: npm
36+
37+
# No runtime dependencies, so this only fetches TypeScript and the types.
38+
- run: npm ci
39+
40+
- name: Typecheck
41+
run: npm run typecheck
42+
43+
- name: Build and test
44+
run: npm test
45+
46+
# Proves the thing npx would actually run does run, on both platforms.
47+
- name: The binary starts
48+
run: node dist/index.js --version

.github/workflows/publish.yml

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Publishes the CLI to npm.
2+
#
3+
# Deliberately not on every push to main. npm will not let a version be
4+
# replaced or a name be reused, so publishing is the one step here that cannot
5+
# be taken back — it should happen because somebody decided to release, not
6+
# because somebody merged.
7+
#
8+
# Cut a GitHub release whose tag is the version ("v0.2.0") and this runs.
9+
name: Publish
10+
11+
on:
12+
release:
13+
types: [published]
14+
workflow_dispatch:
15+
inputs:
16+
dryRun:
17+
description: 'Pack and check without publishing.'
18+
type: boolean
19+
default: true
20+
21+
permissions:
22+
contents: read
23+
# Lets npm record where the package was built from, which is what makes the
24+
# "Provenance" badge on the package page mean anything.
25+
id-token: write
26+
27+
jobs:
28+
publish:
29+
runs-on: ubuntu-latest
30+
steps:
31+
- uses: actions/checkout@v4
32+
33+
- uses: actions/setup-node@v4
34+
with:
35+
node-version: '24'
36+
registry-url: 'https://registry.npmjs.org'
37+
cache: npm
38+
39+
- run: npm ci
40+
41+
# Never publish something that was not tested, even though CI already ran
42+
# on this commit: a release can be cut from any commit, including one CI
43+
# never saw.
44+
- name: Build and test
45+
run: npm test
46+
47+
# A tag that disagrees with package.json publishes a version nobody asked
48+
# for, under a name the release notes do not match.
49+
- name: The tag and package.json agree
50+
if: github.event_name == 'release'
51+
run: |
52+
tag="${GITHUB_REF_NAME#v}"
53+
packaged="$(node -p "require('./package.json').version")"
54+
if [ "$tag" != "$packaged" ]; then
55+
echo "Release tag is $tag but package.json says $packaged."
56+
exit 1
57+
fi
58+
echo "Publishing $packaged."
59+
60+
- name: What would be published
61+
run: npm pack --dry-run
62+
63+
- name: Publish
64+
if: github.event_name == 'release' || inputs.dryRun == false
65+
env:
66+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
67+
run: |
68+
if [ -z "$NODE_AUTH_TOKEN" ]; then
69+
echo "Cannot publish: no NPM_TOKEN in this repository's Actions secrets."
70+
exit 1
71+
fi
72+
# --access public because the package is scoped, and a scoped package
73+
# is private by default however public the repository is.
74+
npm publish --access public --provenance

0 commit comments

Comments
 (0)