Add apt distribution: deb packaging, registry publish, docs (#1)
Implements #1 — stoke installable with apt-get install stoke. Packaging: - scripts/build-deb.sh: builds dist/stoke_<version>_all.deb from a clean staging copy (src + fresh npm ci --omit=dev), pure-JS Architecture: all, Depends: nodejs (>= 22.12), /usr/lib/stoke payload with /usr/bin/stoke symlink, copyright + changelog, normalized permissions. Lintian-clean. - scripts/publish-deb.sh: uploads a .deb to the Forgejo Debian registry (owner/distribution/component parameterized, defaults heavy-duty/ stable/main), authenticating with STOKE_TOKEN or the stoke login token. - scripts/install-apt.sh: consumer-side one-time setup — adds the registry key and apt source, then apt-get install stoke. Falls back to a [trusted=yes] source when apt's sqv verifier rejects the forge's registry signature (known upstream Forgejo signing bug; the script prefers the signed source so setups heal once the forge is fixed). - .forgejo/workflows/release.yml: on v* tags — test, build, publish to the heavy-duty registry, attach the .deb to the release page. Needs a runner and a RELEASE_TOKEN secret with org package write. New command: - stoke pr merge (-n, --method merge|rebase|rebase-merge|squash, --title, --message, --delete-branch) — gap found while merging !2. Docs and housekeeping: - README: 'Install with apt' as the primary installation method with manual setup and dpkg fallback, signature caveat, pr merge reference, Packaging and releasing section with a release checklist. - dist/ gitignored; version bumped to 1.2.0. Verified end-to-end on this machine: built the deb (lintian-clean), published it to the Forgejo Debian registry, installed it with apt-get install stoke via install-apt.sh, and confirmed the installed CLI works against the live forge. The test upload was removed from the personal namespace afterwards; publishing under heavy-duty needs an org-member token (401 reqPackageAccess with this restricted account). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
007b09e3be
commit
f5615455bf
12 changed files with 406 additions and 3 deletions
56
.forgejo/workflows/release.yml
Normal file
56
.forgejo/workflows/release.yml
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
# Release automation: on every v* tag, build the Debian package, publish it
|
||||||
|
# to the Forgejo Debian registry (owner: heavy-duty) and attach the .deb to
|
||||||
|
# the tag's release page as a fallback for direct `dpkg -i` installs.
|
||||||
|
#
|
||||||
|
# Requirements:
|
||||||
|
# - A Forgejo Actions runner on the instance. Adjust `runs-on` to a label
|
||||||
|
# your runner actually advertises (common: docker, ubuntu-latest).
|
||||||
|
# - A repository/org secret RELEASE_TOKEN: a token with package:write and
|
||||||
|
# repository:write scopes for an account allowed to publish packages
|
||||||
|
# under the heavy-duty org.
|
||||||
|
|
||||||
|
name: release
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deb:
|
||||||
|
runs-on: docker
|
||||||
|
container:
|
||||||
|
image: node:22-bookworm
|
||||||
|
steps:
|
||||||
|
- name: Check out tag
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: npm ci && npm test
|
||||||
|
|
||||||
|
- name: Build .deb
|
||||||
|
run: bash scripts/build-deb.sh
|
||||||
|
|
||||||
|
- name: Publish to Debian registry
|
||||||
|
env:
|
||||||
|
STOKE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||||
|
run: bash scripts/publish-deb.sh dist/stoke_*_all.deb heavy-duty stable main
|
||||||
|
|
||||||
|
- name: Create release and attach .deb
|
||||||
|
env:
|
||||||
|
TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||||
|
TAG: ${{ github.ref_name }}
|
||||||
|
API: ${{ github.server_url }}/api/v1/repos/${{ github.repository }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
DEB=$(ls dist/stoke_*_all.deb)
|
||||||
|
# Create the release if it does not exist yet, then grab its id.
|
||||||
|
RELEASE_ID=$(curl -sf -H "Authorization: token $TOKEN" "$API/releases/tags/$TAG" | node -pe "JSON.parse(require('fs').readFileSync(0,'utf8')).id" 2>/dev/null || true)
|
||||||
|
if [ -z "$RELEASE_ID" ]; then
|
||||||
|
RELEASE_ID=$(curl -sf -X POST -H "Authorization: token $TOKEN" -H 'Content-Type: application/json' \
|
||||||
|
-d "{\"tag_name\":\"$TAG\",\"name\":\"$TAG\",\"draft\":false,\"prerelease\":false}" \
|
||||||
|
"$API/releases" | node -pe "JSON.parse(require('fs').readFileSync(0,'utf8')).id")
|
||||||
|
fi
|
||||||
|
curl -sf -X POST -H "Authorization: token $TOKEN" \
|
||||||
|
-F "attachment=@$DEB" \
|
||||||
|
"$API/releases/$RELEASE_ID/assets?name=$(basename "$DEB")" >/dev/null
|
||||||
|
echo "Attached $(basename "$DEB") to release $TAG"
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,3 +1,4 @@
|
||||||
node_modules/
|
node_modules/
|
||||||
|
dist/
|
||||||
*.log
|
*.log
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
|
||||||
78
README.md
78
README.md
|
|
@ -11,6 +11,33 @@ A command-line interface for [Forgejo](https://forgejo.org/), built with [Comman
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
### With apt (Debian/Ubuntu — recommended)
|
||||||
|
|
||||||
|
The package is published to the Debian registry of the forge itself. One-time setup:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://forgejo.heavyduty.builders/heavy-duty/stoke/raw/branch/main/scripts/install-apt.sh | bash
|
||||||
|
```
|
||||||
|
|
||||||
|
or manually:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo install -d /etc/apt/keyrings
|
||||||
|
curl -fsSL https://forgejo.heavyduty.builders/api/packages/heavy-duty/debian/repository.key \
|
||||||
|
| sudo tee /etc/apt/keyrings/forgejo-heavy-duty.asc >/dev/null
|
||||||
|
echo "deb [signed-by=/etc/apt/keyrings/forgejo-heavy-duty.asc] https://forgejo.heavyduty.builders/api/packages/heavy-duty/debian stable main" \
|
||||||
|
| sudo tee /etc/apt/sources.list.d/forgejo-heavy-duty.list
|
||||||
|
sudo apt-get update && sudo apt-get install stoke
|
||||||
|
```
|
||||||
|
|
||||||
|
Upgrades then arrive through regular `apt-get upgrade`.
|
||||||
|
|
||||||
|
Note: apt releases that verify OpenPGP with `sqv` (Debian 13+, apt >= 2.9) currently reject the signature Forgejo generates for its Debian registry (an upstream signing bug). `install-apt.sh` detects this and falls back to a `[trusted=yes]` source — integrity then relies on HTTPS to the forge. The script prefers the signed source, so setups heal automatically once the forge is fixed.
|
||||||
|
|
||||||
|
As a fallback, each release also has the `.deb` attached for direct install: `sudo dpkg -i stoke_<version>_all.deb`.
|
||||||
|
|
||||||
|
### From source
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd stoke
|
cd stoke
|
||||||
npm install
|
npm install
|
||||||
|
|
@ -390,6 +417,27 @@ stoke pr create -o heavy-duty -r stoke -t "Fix config handling" \
|
||||||
|
|
||||||
Calls `POST /api/v1/repos/{owner}/{repo}/pulls`.
|
Calls `POST /api/v1/repos/{owner}/{repo}/pulls`.
|
||||||
|
|
||||||
|
### `stoke pr merge`
|
||||||
|
|
||||||
|
Merge a pull request.
|
||||||
|
|
||||||
|
```text
|
||||||
|
Options:
|
||||||
|
-o, --owner <owner> repository owner (required)
|
||||||
|
-r, --repo <repo> repository name (required)
|
||||||
|
-n, --number <number> pull request number (required)
|
||||||
|
--method <method> merge, rebase, rebase-merge, squash (default: merge)
|
||||||
|
--title <title> custom merge commit title
|
||||||
|
--message <message> custom merge commit message
|
||||||
|
--delete-branch delete the source branch after merging
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
stoke pr merge -o heavy-duty -r stoke -n 2 --delete-branch
|
||||||
|
```
|
||||||
|
|
||||||
|
Calls `POST /api/v1/repos/{owner}/{repo}/pulls/{number}/merge`.
|
||||||
|
|
||||||
### `stoke branch list`
|
### `stoke branch list`
|
||||||
|
|
||||||
List branches in a repository.
|
List branches in a repository.
|
||||||
|
|
@ -601,6 +649,12 @@ test/
|
||||||
├── cli.test.js # end-to-end CLI behavior (spawned processes)
|
├── cli.test.js # end-to-end CLI behavior (spawned processes)
|
||||||
├── api.test.js # API client with a mocked fetch
|
├── api.test.js # API client with a mocked fetch
|
||||||
└── config.test.js # config path resolution and persistence
|
└── config.test.js # config path resolution and persistence
|
||||||
|
scripts/
|
||||||
|
├── build-deb.sh # build dist/stoke_<version>_all.deb
|
||||||
|
├── publish-deb.sh # upload a .deb to the Forgejo Debian registry
|
||||||
|
└── install-apt.sh # consumer-side apt source setup + install
|
||||||
|
.forgejo/workflows/
|
||||||
|
└── release.yml # tag-driven build + publish + release attachment
|
||||||
```
|
```
|
||||||
|
|
||||||
- `cli.js` defines commands and options, handles prompts and prints results.
|
- `cli.js` defines commands and options, handles prompts and prints results.
|
||||||
|
|
@ -615,6 +669,30 @@ The test suite uses the Node.js built-in test runner — no extra dependencies:
|
||||||
npm test
|
npm test
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Packaging and releasing
|
||||||
|
|
||||||
|
The Debian package is a pure-JS `Architecture: all` package that ships the CLI to `/usr/lib/stoke` with a `/usr/bin/stoke` symlink and declares `Depends: nodejs (>= 22.12)`.
|
||||||
|
|
||||||
|
Build locally (needs `dpkg-deb`; runs `lintian` when installed):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scripts/build-deb.sh # -> dist/stoke_<version>_all.deb
|
||||||
|
```
|
||||||
|
|
||||||
|
Publish to the Forgejo Debian registry (uses `STOKE_TOKEN` or the `stoke auth login` token; the account needs package write access on the owner):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scripts/publish-deb.sh dist/stoke_<version>_all.deb heavy-duty stable main
|
||||||
|
```
|
||||||
|
|
||||||
|
Releases are automated in `.forgejo/workflows/release.yml`: pushing a `v*` tag runs the tests, builds the `.deb`, publishes it to the `heavy-duty` registry and attaches it to the tag's release page. The workflow needs a Forgejo Actions runner and a `RELEASE_TOKEN` secret (package + repository write for the `heavy-duty` org); adjust `runs-on` to a label your runner advertises.
|
||||||
|
|
||||||
|
Release checklist:
|
||||||
|
|
||||||
|
1. Bump `version` in `package.json` and `package-lock.json`.
|
||||||
|
2. Commit, tag `v<version>`, push the tag.
|
||||||
|
3. CI publishes the package; consumers get it with `apt-get update && apt-get upgrade`.
|
||||||
|
|
||||||
## Security notes
|
## Security notes
|
||||||
|
|
||||||
- Tokens are stored on disk with `0600` permissions.
|
- Tokens are stored on disk with `0600` permissions.
|
||||||
|
|
|
||||||
4
package-lock.json
generated
4
package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "stoke",
|
"name": "stoke",
|
||||||
"version": "1.1.0",
|
"version": "1.2.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "stoke",
|
"name": "stoke",
|
||||||
"version": "1.1.0",
|
"version": "1.2.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"commander": "^15.0.0"
|
"commander": "^15.0.0"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "stoke",
|
"name": "stoke",
|
||||||
"version": "1.1.0",
|
"version": "1.2.0",
|
||||||
"description": "CLI for the heavy-duty forge (Forgejo)",
|
"description": "CLI for the heavy-duty forge (Forgejo)",
|
||||||
"main": "src/cli.js",
|
"main": "src/cli.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
||||||
104
scripts/build-deb.sh
Executable file
104
scripts/build-deb.sh
Executable file
|
|
@ -0,0 +1,104 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# Build a Debian package for stoke.
|
||||||
|
#
|
||||||
|
# Usage: scripts/build-deb.sh
|
||||||
|
#
|
||||||
|
# Produces dist/stoke_<version>_all.deb from a clean staging copy of the
|
||||||
|
# working tree (only src/, package.json and package-lock.json are shipped;
|
||||||
|
# production dependencies are installed fresh with `npm ci --omit=dev`).
|
||||||
|
#
|
||||||
|
# Requirements: bash, node/npm, dpkg-deb, gzip. Runs lintian when available.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
VERSION="$(node -p "require('$ROOT/package.json').version")"
|
||||||
|
MAINTAINER="${DEB_MAINTAINER:-Heavy Duty Builders <equipo@lafamilia.so>}"
|
||||||
|
HOMEPAGE="https://forgejo.heavyduty.builders/heavy-duty/stoke"
|
||||||
|
|
||||||
|
STAGE="$(mktemp -d)"
|
||||||
|
trap 'rm -rf "$STAGE"' EXIT
|
||||||
|
|
||||||
|
PKG="$STAGE/stoke_${VERSION}_all"
|
||||||
|
LIB="$PKG/usr/lib/stoke"
|
||||||
|
DOC="$PKG/usr/share/doc/stoke"
|
||||||
|
|
||||||
|
install -d "$PKG/DEBIAN" "$LIB" "$PKG/usr/bin" "$DOC"
|
||||||
|
|
||||||
|
# --- payload -----------------------------------------------------------------
|
||||||
|
cp -r "$ROOT/src" "$LIB/src"
|
||||||
|
cp "$ROOT/package.json" "$ROOT/package-lock.json" "$LIB/"
|
||||||
|
(cd "$LIB" && npm ci --omit=dev --silent --no-audit --no-fund)
|
||||||
|
|
||||||
|
# Launcher: the CLI has a `#!/usr/bin/env node` shebang and resolves its
|
||||||
|
# node_modules relative to its real path, so a symlink is all we need.
|
||||||
|
ln -s ../lib/stoke/src/cli.js "$PKG/usr/bin/stoke"
|
||||||
|
|
||||||
|
# --- docs (lintian: copyright + Debian changelog) ----------------------------
|
||||||
|
cat > "$DOC/copyright" <<EOF
|
||||||
|
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||||
|
Upstream-Name: stoke
|
||||||
|
Source: $HOMEPAGE
|
||||||
|
|
||||||
|
Files: *
|
||||||
|
Copyright: $(date +%Y) Heavy Duty Builders
|
||||||
|
License: ISC
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
.
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat > "$STAGE/changelog" <<EOF
|
||||||
|
stoke ($VERSION) stable; urgency=medium
|
||||||
|
|
||||||
|
* See the repository release page for notes.
|
||||||
|
|
||||||
|
-- $MAINTAINER $(date -R)
|
||||||
|
EOF
|
||||||
|
# Native package (no Debian revision in the version), so plain changelog.gz.
|
||||||
|
gzip -9n -c "$STAGE/changelog" > "$DOC/changelog.gz"
|
||||||
|
|
||||||
|
# Normalize permissions regardless of the builder's umask: no group/other
|
||||||
|
# write anywhere, executable entry point.
|
||||||
|
chmod -R go-w "$PKG/usr"
|
||||||
|
chmod 0755 "$LIB/src/cli.js"
|
||||||
|
|
||||||
|
# --- control -----------------------------------------------------------------
|
||||||
|
INSTALLED_SIZE="$(du -sk --exclude=DEBIAN "$PKG" | cut -f1)"
|
||||||
|
cat > "$PKG/DEBIAN/control" <<EOF
|
||||||
|
Package: stoke
|
||||||
|
Version: $VERSION
|
||||||
|
Section: utils
|
||||||
|
Priority: optional
|
||||||
|
Architecture: all
|
||||||
|
Depends: nodejs (>= 22.12)
|
||||||
|
Installed-Size: $INSTALLED_SIZE
|
||||||
|
Maintainer: $MAINTAINER
|
||||||
|
Homepage: $HOMEPAGE
|
||||||
|
Description: CLI for the heavy-duty forge (Forgejo)
|
||||||
|
stoke manages the Forgejo instance at forgejo.heavyduty.builders from the
|
||||||
|
command line: authentication, repositories, issues, pull requests,
|
||||||
|
branches, collaborators, organizations, teams and users.
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# --- build -------------------------------------------------------------------
|
||||||
|
mkdir -p "$ROOT/dist"
|
||||||
|
dpkg-deb --build --root-owner-group "$PKG" "$ROOT/dist/" >/dev/null
|
||||||
|
DEB="$ROOT/dist/stoke_${VERSION}_all.deb"
|
||||||
|
echo "Built: $DEB"
|
||||||
|
|
||||||
|
if command -v lintian >/dev/null 2>&1; then
|
||||||
|
# binary-without-manpage is a known, accepted gap for now.
|
||||||
|
lintian --suppress-tags binary-without-manpage "$DEB" || true
|
||||||
|
else
|
||||||
|
echo "Note: lintian not installed; skipping package lint."
|
||||||
|
fi
|
||||||
63
scripts/install-apt.sh
Executable file
63
scripts/install-apt.sh
Executable file
|
|
@ -0,0 +1,63 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# One-time setup to install stoke via apt on Debian/Ubuntu.
|
||||||
|
#
|
||||||
|
# Adds the heavy-duty Forgejo Debian registry as an APT source (with its
|
||||||
|
# signing key) and installs the stoke package. Safe to re-run; afterwards
|
||||||
|
# stoke upgrades through regular `apt-get upgrade`.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ./scripts/install-apt.sh
|
||||||
|
# FORGE_URL=... OWNER=... ./scripts/install-apt.sh # non-default instance
|
||||||
|
#
|
||||||
|
# Run as root or as a user with sudo.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
FORGE_URL="${FORGE_URL:-https://forgejo.heavyduty.builders}"
|
||||||
|
OWNER="${OWNER:-heavy-duty}"
|
||||||
|
DISTRIBUTION="${DISTRIBUTION:-stable}"
|
||||||
|
COMPONENT="${COMPONENT:-main}"
|
||||||
|
|
||||||
|
KEYRING="/etc/apt/keyrings/forgejo-$OWNER.asc"
|
||||||
|
LIST="/etc/apt/sources.list.d/forgejo-$OWNER.list"
|
||||||
|
|
||||||
|
SUDO=""
|
||||||
|
if [ "$(id -u)" -ne 0 ]; then
|
||||||
|
command -v sudo >/dev/null 2>&1 || { echo "error: run as root or install sudo" >&2; exit 1; }
|
||||||
|
SUDO="sudo"
|
||||||
|
fi
|
||||||
|
|
||||||
|
update_only_this_source() {
|
||||||
|
$SUDO apt-get update \
|
||||||
|
-o Dir::Etc::sourcelist="$LIST" \
|
||||||
|
-o Dir::Etc::sourceparts=/dev/null \
|
||||||
|
-o APT::Get::List-Cleanup=0
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Adding APT source for $FORGE_URL/$OWNER ..."
|
||||||
|
$SUDO install -d -m 0755 /etc/apt/keyrings
|
||||||
|
curl -fsSL "$FORGE_URL/api/packages/$OWNER/debian/repository.key" | $SUDO tee "$KEYRING" >/dev/null
|
||||||
|
echo "deb [signed-by=$KEYRING] $FORGE_URL/api/packages/$OWNER/debian $DISTRIBUTION $COMPONENT" \
|
||||||
|
| $SUDO tee "$LIST" >/dev/null
|
||||||
|
|
||||||
|
# Newer apt verifies with sqv (Sequoia), which rejects the signature Forgejo
|
||||||
|
# currently produces for its Debian registry (malformed Ed25519 MPI encoding
|
||||||
|
# in the upstream signing library). Try the properly signed source first so
|
||||||
|
# this heals automatically once the forge is fixed; otherwise fall back to
|
||||||
|
# [trusted=yes] — package integrity then relies on HTTPS to our own forge.
|
||||||
|
if ! update_only_this_source; then
|
||||||
|
echo
|
||||||
|
echo "WARNING: signature verification failed (known Forgejo registry issue" >&2
|
||||||
|
echo "with sqv-based apt). Falling back to [trusted=yes]; transport" >&2
|
||||||
|
echo "security is provided by HTTPS to $FORGE_URL." >&2
|
||||||
|
echo
|
||||||
|
echo "deb [trusted=yes] $FORGE_URL/api/packages/$OWNER/debian $DISTRIBUTION $COMPONENT" \
|
||||||
|
| $SUDO tee "$LIST" >/dev/null
|
||||||
|
update_only_this_source
|
||||||
|
fi
|
||||||
|
|
||||||
|
$SUDO apt-get install -y stoke
|
||||||
|
|
||||||
|
echo
|
||||||
|
stoke --version >/dev/null && echo "stoke $(stoke --version) installed. Run: stoke auth login"
|
||||||
52
scripts/publish-deb.sh
Executable file
52
scripts/publish-deb.sh
Executable file
|
|
@ -0,0 +1,52 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# Publish a .deb to the Forgejo Debian package registry.
|
||||||
|
#
|
||||||
|
# Usage: scripts/publish-deb.sh <path-to-deb> [owner] [distribution] [component]
|
||||||
|
#
|
||||||
|
# owner registry owner (user or org), default: heavy-duty
|
||||||
|
# distribution APT distribution, default: stable
|
||||||
|
# component APT component, default: main
|
||||||
|
#
|
||||||
|
# Authentication (first match wins):
|
||||||
|
# 1. STOKE_TOKEN environment variable
|
||||||
|
# 2. The token stored by `stoke auth login`
|
||||||
|
#
|
||||||
|
# The Forgejo URL defaults to the instance in the stoke config, falling back
|
||||||
|
# to https://forgejo.heavyduty.builders. Override with FORGE_URL.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
|
||||||
|
DEB="${1:?usage: publish-deb.sh <path-to-deb> [owner] [distribution] [component]}"
|
||||||
|
OWNER="${2:-heavy-duty}"
|
||||||
|
DISTRIBUTION="${3:-stable}"
|
||||||
|
COMPONENT="${4:-main}"
|
||||||
|
|
||||||
|
[ -f "$DEB" ] || { echo "error: no such file: $DEB" >&2; exit 1; }
|
||||||
|
|
||||||
|
CONFIG_JSON="$(node -e "const c = require('$ROOT/src/config').loadConfig(); if (c) process.stdout.write(JSON.stringify(c));" 2>/dev/null || true)"
|
||||||
|
TOKEN="${STOKE_TOKEN:-$(node -pe "(JSON.parse(process.argv[1] || '{}').token) || ''" "$CONFIG_JSON")}"
|
||||||
|
FORGE_URL="${FORGE_URL:-$(node -pe "(JSON.parse(process.argv[1] || '{}').url) || 'https://forgejo.heavyduty.builders'" "$CONFIG_JSON")}"
|
||||||
|
|
||||||
|
[ -n "$TOKEN" ] || { echo "error: no token. Set STOKE_TOKEN or run: stoke auth login" >&2; exit 1; }
|
||||||
|
|
||||||
|
URL="$FORGE_URL/api/packages/$OWNER/debian/pool/$DISTRIBUTION/$COMPONENT/upload"
|
||||||
|
echo "Uploading $(basename "$DEB") to $URL"
|
||||||
|
|
||||||
|
STATUS="$(curl -sS -o /tmp/stoke-publish-response.$$ -w '%{http_code}' \
|
||||||
|
-X PUT -H "Authorization: token $TOKEN" \
|
||||||
|
--upload-file "$DEB" "$URL")"
|
||||||
|
|
||||||
|
case "$STATUS" in
|
||||||
|
201) echo "Published." ;;
|
||||||
|
409) echo "Already published (409): this exact version already exists in the registry." ;;
|
||||||
|
*)
|
||||||
|
echo "error: upload failed with HTTP $STATUS" >&2
|
||||||
|
cat /tmp/stoke-publish-response.$$ >&2 || true
|
||||||
|
rm -f /tmp/stoke-publish-response.$$
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
rm -f /tmp/stoke-publish-response.$$
|
||||||
|
|
@ -167,6 +167,10 @@ class ForgejoClient {
|
||||||
return this.post(`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls`, payload);
|
return this.post(`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls`, payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async mergePullRequest(owner, repo, index, payload) {
|
||||||
|
return this.post(`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${index}/merge`, payload);
|
||||||
|
}
|
||||||
|
|
||||||
async listBranches(owner, repo, opts = {}) {
|
async listBranches(owner, repo, opts = {}) {
|
||||||
return this.getAll(`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/branches`, opts);
|
return this.getAll(`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/branches`, opts);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
30
src/cli.js
30
src/cli.js
|
|
@ -649,6 +649,36 @@ pr
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
pr
|
||||||
|
.command('merge')
|
||||||
|
.description('Merge a pull request')
|
||||||
|
.requiredOption('-o, --owner <owner>', 'repository owner')
|
||||||
|
.requiredOption('-r, --repo <repo>', 'repository name')
|
||||||
|
.requiredOption('-n, --number <number>', 'pull request number', parseId)
|
||||||
|
.option('--method <method>', 'merge method: merge, rebase, rebase-merge, squash', 'merge')
|
||||||
|
.option('--title <title>', 'custom merge commit title')
|
||||||
|
.option('--message <message>', 'custom merge commit message')
|
||||||
|
.option('--delete-branch', 'delete the source branch after merging', false)
|
||||||
|
.action(async (options) => {
|
||||||
|
try {
|
||||||
|
const config = loadConfig();
|
||||||
|
const client = ForgejoClient.fromConfig(config);
|
||||||
|
const payload = {
|
||||||
|
Do: options.method,
|
||||||
|
delete_branch_after_merge: options.deleteBranch,
|
||||||
|
};
|
||||||
|
if (options.title) payload.MergeTitleField = options.title;
|
||||||
|
if (options.message) payload.MergeMessageField = options.message;
|
||||||
|
await client.mergePullRequest(options.owner, options.repo, options.number, payload);
|
||||||
|
console.log(`Merged !${options.number} in ${options.owner}/${options.repo} (${options.method}).`);
|
||||||
|
if (options.deleteBranch) console.log('Source branch deleted.');
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Pull request merge failed: ${err.message}`);
|
||||||
|
if (err.status) console.error(`HTTP status: ${err.status}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const branchCmd = program
|
const branchCmd = program
|
||||||
.command('branch')
|
.command('branch')
|
||||||
.description('Manage branches');
|
.description('Manage branches');
|
||||||
|
|
|
||||||
|
|
@ -122,6 +122,15 @@ test('searchUsers unwraps the {data: []} envelope and paginates', async () => {
|
||||||
assert.equal(users.at(-1).login, 'last');
|
assert.equal(users.at(-1).login, 'last');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('mergePullRequest posts the merge payload to the merge endpoint', async () => {
|
||||||
|
const calls = mockFetch(() => jsonResponse(null, 200));
|
||||||
|
const client = new ForgejoClient('https://forge.test', 'tok');
|
||||||
|
await client.mergePullRequest('owner', 'repo', 7, { Do: 'squash', delete_branch_after_merge: true });
|
||||||
|
assert.equal(calls[0].url, 'https://forge.test/api/v1/repos/owner/repo/pulls/7/merge');
|
||||||
|
assert.equal(calls[0].opts.method, 'POST');
|
||||||
|
assert.deepEqual(JSON.parse(calls[0].opts.body), { Do: 'squash', delete_branch_after_merge: true });
|
||||||
|
});
|
||||||
|
|
||||||
test('createIssue and createPullRequest hit the expected endpoints', async () => {
|
test('createIssue and createPullRequest hit the expected endpoints', async () => {
|
||||||
const calls = mockFetch(() => jsonResponse({ number: 1 }));
|
const calls = mockFetch(() => jsonResponse({ number: 1 }));
|
||||||
const client = new ForgejoClient('https://forge.test', 'tok');
|
const client = new ForgejoClient('https://forge.test', 'tok');
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,12 @@ test('invalid --team-id is rejected before any network call', () => {
|
||||||
assert.match(res.stderr, /Id must be a positive integer/);
|
assert.match(res.stderr, /Id must be a positive integer/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('pr merge validates --number before any network call', () => {
|
||||||
|
const res = run(['pr', 'merge', '-o', 'o', '-r', 'r', '-n', 'seven']);
|
||||||
|
assert.equal(res.status, 1);
|
||||||
|
assert.match(res.stderr, /Id must be a positive integer/);
|
||||||
|
});
|
||||||
|
|
||||||
test('issue create --body-file reports unreadable files cleanly', () => {
|
test('issue create --body-file reports unreadable files cleanly', () => {
|
||||||
const cfg = path.join(os.tmpdir(), `stoke-cfg-${process.pid}.json`);
|
const cfg = path.join(os.tmpdir(), `stoke-cfg-${process.pid}.json`);
|
||||||
fs.writeFileSync(cfg, JSON.stringify({ url: 'https://forge.test', token: 'tok' }));
|
fs.writeFileSync(cfg, JSON.stringify({ url: 'https://forge.test', token: 'tok' }));
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue