feat: Add GitHub deployment artifacts (homebrew, npm) (gt-ufep6)
- Add .goreleaser.yml for multi-platform binary releases - Add npm-package/ with postinstall binary download - Add release.yml workflow for GoReleaser + npm publish + Homebrew tap 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
161
.github/workflows/release.yml
vendored
Normal file
161
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: release-${{ github.ref }}
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
goreleaser:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: '1.23'
|
||||||
|
|
||||||
|
- name: Install cross-compilation toolchains
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y gcc-mingw-w64-x86-64 gcc-aarch64-linux-gnu
|
||||||
|
|
||||||
|
- name: Run GoReleaser
|
||||||
|
uses: goreleaser/goreleaser-action@v6
|
||||||
|
with:
|
||||||
|
distribution: goreleaser
|
||||||
|
version: '~> v2'
|
||||||
|
args: >
|
||||||
|
release --clean
|
||||||
|
${{ github.repository != 'steveyegge/gastown' && '--skip=publish --skip=announce' || '' }}
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
publish-npm:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: goreleaser
|
||||||
|
if: ${{ github.repository == 'steveyegge/gastown' }}
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
id-token: write # Required for npm provenance/trusted publishing
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '22'
|
||||||
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
|
||||||
|
- name: Update npm for OIDC trusted publishing
|
||||||
|
run: npm install -g npm@latest # Requires npm >= 11.5.1 for trusted publishing
|
||||||
|
|
||||||
|
- name: Publish to npm
|
||||||
|
run: |
|
||||||
|
cd npm-package
|
||||||
|
npm publish --access public
|
||||||
|
# Uses OIDC trusted publishing - no token needed
|
||||||
|
# Provenance attestations are automatic with trusted publishing
|
||||||
|
|
||||||
|
update-homebrew:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: goreleaser
|
||||||
|
if: ${{ github.repository == 'steveyegge/gastown' }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Get release info
|
||||||
|
id: release
|
||||||
|
run: |
|
||||||
|
TAG="${GITHUB_REF#refs/tags/}"
|
||||||
|
echo "tag=${TAG}" >> $GITHUB_OUTPUT
|
||||||
|
echo "version=${TAG#v}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Download checksums
|
||||||
|
run: |
|
||||||
|
curl -sL "https://github.com/steveyegge/gastown/releases/download/${{ steps.release.outputs.tag }}/checksums.txt" -o checksums.txt
|
||||||
|
|
||||||
|
- name: Extract checksums
|
||||||
|
id: checksums
|
||||||
|
run: |
|
||||||
|
echo "darwin_amd64=$(grep 'darwin_amd64.tar.gz' checksums.txt | awk '{print $1}')" >> $GITHUB_OUTPUT
|
||||||
|
echo "darwin_arm64=$(grep 'darwin_arm64.tar.gz' checksums.txt | awk '{print $1}')" >> $GITHUB_OUTPUT
|
||||||
|
echo "linux_amd64=$(grep 'linux_amd64.tar.gz' checksums.txt | awk '{print $1}')" >> $GITHUB_OUTPUT
|
||||||
|
echo "linux_arm64=$(grep 'linux_arm64.tar.gz' checksums.txt | awk '{print $1}')" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Update Homebrew formula
|
||||||
|
run: |
|
||||||
|
mkdir -p Formula
|
||||||
|
cat > Formula/gt.rb <<'EOF'
|
||||||
|
class Gt < Formula
|
||||||
|
desc "Gas Town CLI - multi-agent workspace manager"
|
||||||
|
homepage "https://github.com/steveyegge/gastown"
|
||||||
|
version "${{ steps.release.outputs.version }}"
|
||||||
|
license "MIT"
|
||||||
|
|
||||||
|
on_macos do
|
||||||
|
if Hardware::CPU.arm?
|
||||||
|
url "https://github.com/steveyegge/gastown/releases/download/v#{version}/gastown_#{version}_darwin_arm64.tar.gz"
|
||||||
|
sha256 "${{ steps.checksums.outputs.darwin_arm64 }}"
|
||||||
|
else
|
||||||
|
url "https://github.com/steveyegge/gastown/releases/download/v#{version}/gastown_#{version}_darwin_amd64.tar.gz"
|
||||||
|
sha256 "${{ steps.checksums.outputs.darwin_amd64 }}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
on_linux do
|
||||||
|
if Hardware::CPU.arm? && Hardware::CPU.is_64_bit?
|
||||||
|
url "https://github.com/steveyegge/gastown/releases/download/v#{version}/gastown_#{version}_linux_arm64.tar.gz"
|
||||||
|
sha256 "${{ steps.checksums.outputs.linux_arm64 }}"
|
||||||
|
else
|
||||||
|
url "https://github.com/steveyegge/gastown/releases/download/v#{version}/gastown_#{version}_linux_amd64.tar.gz"
|
||||||
|
sha256 "${{ steps.checksums.outputs.linux_amd64 }}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def install
|
||||||
|
bin.install "gt"
|
||||||
|
end
|
||||||
|
|
||||||
|
test do
|
||||||
|
system "#{bin}/gt", "version"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
EOF
|
||||||
|
|
||||||
|
- name: Push to homebrew-gastown
|
||||||
|
env:
|
||||||
|
HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
|
||||||
|
run: |
|
||||||
|
if [ -z "$HOMEBREW_TAP_TOKEN" ]; then
|
||||||
|
echo "::warning::HOMEBREW_TAP_TOKEN not set - skipping Homebrew update"
|
||||||
|
echo "To enable automatic Homebrew updates:"
|
||||||
|
echo "1. Create a Personal Access Token with 'repo' scope"
|
||||||
|
echo "2. Add it as HOMEBREW_TAP_TOKEN in repository secrets"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
git clone "https://x-access-token:${HOMEBREW_TAP_TOKEN}@github.com/steveyegge/homebrew-gastown.git" tap
|
||||||
|
cp Formula/gt.rb tap/Formula/gt.rb
|
||||||
|
cd tap
|
||||||
|
git config user.name "github-actions[bot]"
|
||||||
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
|
git add Formula/gt.rb
|
||||||
|
git commit -m "Update gt to ${{ steps.release.outputs.version }}"
|
||||||
|
git push
|
||||||
180
.goreleaser.yml
Normal file
180
.goreleaser.yml
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
# GoReleaser configuration for Gas Town (gt)
|
||||||
|
# See https://goreleaser.com for documentation
|
||||||
|
|
||||||
|
version: 2
|
||||||
|
|
||||||
|
before:
|
||||||
|
hooks:
|
||||||
|
# Ensure dependencies are up to date
|
||||||
|
- go mod tidy
|
||||||
|
|
||||||
|
builds:
|
||||||
|
- id: gt-linux-amd64
|
||||||
|
main: ./cmd/gt
|
||||||
|
binary: gt
|
||||||
|
env:
|
||||||
|
- CGO_ENABLED=1
|
||||||
|
goos:
|
||||||
|
- linux
|
||||||
|
goarch:
|
||||||
|
- amd64
|
||||||
|
ldflags:
|
||||||
|
- -s -w
|
||||||
|
- -X github.com/steveyegge/gastown/internal/cmd.Version={{.Version}}
|
||||||
|
- -X github.com/steveyegge/gastown/internal/cmd.Build={{.ShortCommit}}
|
||||||
|
- -X github.com/steveyegge/gastown/internal/cmd.Commit={{.Commit}}
|
||||||
|
- -X github.com/steveyegge/gastown/internal/cmd.Branch={{.Branch}}
|
||||||
|
|
||||||
|
- id: gt-linux-arm64
|
||||||
|
main: ./cmd/gt
|
||||||
|
binary: gt
|
||||||
|
env:
|
||||||
|
- CGO_ENABLED=1
|
||||||
|
- CC=aarch64-linux-gnu-gcc
|
||||||
|
- CXX=aarch64-linux-gnu-g++
|
||||||
|
goos:
|
||||||
|
- linux
|
||||||
|
goarch:
|
||||||
|
- arm64
|
||||||
|
ldflags:
|
||||||
|
- -s -w
|
||||||
|
- -X github.com/steveyegge/gastown/internal/cmd.Version={{.Version}}
|
||||||
|
- -X github.com/steveyegge/gastown/internal/cmd.Build={{.ShortCommit}}
|
||||||
|
- -X github.com/steveyegge/gastown/internal/cmd.Commit={{.Commit}}
|
||||||
|
- -X github.com/steveyegge/gastown/internal/cmd.Branch={{.Branch}}
|
||||||
|
|
||||||
|
- id: gt-darwin-amd64
|
||||||
|
main: ./cmd/gt
|
||||||
|
binary: gt
|
||||||
|
env:
|
||||||
|
- CGO_ENABLED=1
|
||||||
|
goos:
|
||||||
|
- darwin
|
||||||
|
goarch:
|
||||||
|
- amd64
|
||||||
|
ldflags:
|
||||||
|
- -s -w
|
||||||
|
- -X github.com/steveyegge/gastown/internal/cmd.Version={{.Version}}
|
||||||
|
- -X github.com/steveyegge/gastown/internal/cmd.Build={{.ShortCommit}}
|
||||||
|
- -X github.com/steveyegge/gastown/internal/cmd.Commit={{.Commit}}
|
||||||
|
- -X github.com/steveyegge/gastown/internal/cmd.Branch={{.Branch}}
|
||||||
|
|
||||||
|
- id: gt-darwin-arm64
|
||||||
|
main: ./cmd/gt
|
||||||
|
binary: gt
|
||||||
|
env:
|
||||||
|
- CGO_ENABLED=1
|
||||||
|
goos:
|
||||||
|
- darwin
|
||||||
|
goarch:
|
||||||
|
- arm64
|
||||||
|
ldflags:
|
||||||
|
- -s -w
|
||||||
|
- -X github.com/steveyegge/gastown/internal/cmd.Version={{.Version}}
|
||||||
|
- -X github.com/steveyegge/gastown/internal/cmd.Build={{.ShortCommit}}
|
||||||
|
- -X github.com/steveyegge/gastown/internal/cmd.Commit={{.Commit}}
|
||||||
|
- -X github.com/steveyegge/gastown/internal/cmd.Branch={{.Branch}}
|
||||||
|
|
||||||
|
- id: gt-windows-amd64
|
||||||
|
main: ./cmd/gt
|
||||||
|
binary: gt
|
||||||
|
env:
|
||||||
|
- CGO_ENABLED=1
|
||||||
|
- CC=x86_64-w64-mingw32-gcc
|
||||||
|
- CXX=x86_64-w64-mingw32-g++
|
||||||
|
goos:
|
||||||
|
- windows
|
||||||
|
goarch:
|
||||||
|
- amd64
|
||||||
|
ldflags:
|
||||||
|
- -s -w
|
||||||
|
- -X github.com/steveyegge/gastown/internal/cmd.Version={{.Version}}
|
||||||
|
- -X github.com/steveyegge/gastown/internal/cmd.Build={{.ShortCommit}}
|
||||||
|
- -X github.com/steveyegge/gastown/internal/cmd.Commit={{.Commit}}
|
||||||
|
- -X github.com/steveyegge/gastown/internal/cmd.Branch={{.Branch}}
|
||||||
|
- -buildmode=exe
|
||||||
|
|
||||||
|
- id: gt-freebsd-amd64
|
||||||
|
main: ./cmd/gt
|
||||||
|
binary: gt
|
||||||
|
env:
|
||||||
|
- CGO_ENABLED=0
|
||||||
|
goos:
|
||||||
|
- freebsd
|
||||||
|
goarch:
|
||||||
|
- amd64
|
||||||
|
ldflags:
|
||||||
|
- -s -w
|
||||||
|
- -X github.com/steveyegge/gastown/internal/cmd.Version={{.Version}}
|
||||||
|
- -X github.com/steveyegge/gastown/internal/cmd.Build={{.ShortCommit}}
|
||||||
|
- -X github.com/steveyegge/gastown/internal/cmd.Commit={{.Commit}}
|
||||||
|
- -X github.com/steveyegge/gastown/internal/cmd.Branch={{.Branch}}
|
||||||
|
|
||||||
|
|
||||||
|
archives:
|
||||||
|
- id: gt-archive
|
||||||
|
format: tar.gz
|
||||||
|
name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
|
||||||
|
format_overrides:
|
||||||
|
- goos: windows
|
||||||
|
format: zip
|
||||||
|
files:
|
||||||
|
- LICENSE
|
||||||
|
- README.md
|
||||||
|
|
||||||
|
checksum:
|
||||||
|
name_template: "checksums.txt"
|
||||||
|
algorithm: sha256
|
||||||
|
|
||||||
|
snapshot:
|
||||||
|
version_template: "{{ incpatch .Version }}-next"
|
||||||
|
|
||||||
|
changelog:
|
||||||
|
sort: asc
|
||||||
|
filters:
|
||||||
|
exclude:
|
||||||
|
- "^docs:"
|
||||||
|
- "^test:"
|
||||||
|
- "^chore:"
|
||||||
|
- "Merge pull request"
|
||||||
|
- "Merge branch"
|
||||||
|
groups:
|
||||||
|
- title: "Features"
|
||||||
|
regexp: '^.*feat(\(\w+\))?:.*$'
|
||||||
|
order: 0
|
||||||
|
- title: "Bug Fixes"
|
||||||
|
regexp: '^.*fix(\(\w+\))?:.*$'
|
||||||
|
order: 1
|
||||||
|
- title: "Others"
|
||||||
|
order: 999
|
||||||
|
|
||||||
|
release:
|
||||||
|
github:
|
||||||
|
owner: steveyegge
|
||||||
|
name: gastown
|
||||||
|
draft: false
|
||||||
|
prerelease: auto
|
||||||
|
name_template: "v{{.Version}}"
|
||||||
|
header: |
|
||||||
|
## Gas Town v{{.Version}}
|
||||||
|
|
||||||
|
Pre-compiled binaries for Linux, macOS (Intel & Apple Silicon), and Windows.
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
**Homebrew (macOS/Linux):**
|
||||||
|
```bash
|
||||||
|
brew install steveyegge/gastown/gt
|
||||||
|
```
|
||||||
|
|
||||||
|
**npm (Node.js):**
|
||||||
|
```bash
|
||||||
|
npm install -g @gastown/gt
|
||||||
|
```
|
||||||
|
|
||||||
|
**Manual Install:**
|
||||||
|
Download the appropriate binary for your platform below, extract it, and place it in your PATH.
|
||||||
|
|
||||||
|
# Announce the release
|
||||||
|
announce:
|
||||||
|
skip: false
|
||||||
12
npm-package/.npmignore
Normal file
12
npm-package/.npmignore
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# Ignore test files
|
||||||
|
test/
|
||||||
|
|
||||||
|
# Ignore development files
|
||||||
|
*.md
|
||||||
|
!README.md
|
||||||
|
|
||||||
|
# Ignore any local binaries (downloaded at install time)
|
||||||
|
bin/gt
|
||||||
|
bin/gt.exe
|
||||||
|
bin/*.tar.gz
|
||||||
|
bin/*.zip
|
||||||
21
npm-package/LICENSE
Normal file
21
npm-package/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 Steve Yegge
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
42
npm-package/README.md
Normal file
42
npm-package/README.md
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# @gastown/gt
|
||||||
|
|
||||||
|
Gas Town CLI - multi-agent workspace manager for coordinating AI coding agents.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -g @gastown/gt
|
||||||
|
```
|
||||||
|
|
||||||
|
This will download the appropriate native binary for your platform during installation.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check version
|
||||||
|
gt version
|
||||||
|
|
||||||
|
# Initialize a new town
|
||||||
|
gt init
|
||||||
|
|
||||||
|
# View status
|
||||||
|
gt status
|
||||||
|
|
||||||
|
# List rigs
|
||||||
|
gt rigs
|
||||||
|
```
|
||||||
|
|
||||||
|
## Supported Platforms
|
||||||
|
|
||||||
|
- macOS (Intel and Apple Silicon)
|
||||||
|
- Linux (x64 and ARM64)
|
||||||
|
- Windows (x64)
|
||||||
|
|
||||||
|
## Manual Installation
|
||||||
|
|
||||||
|
If npm installation fails, you can download binaries directly from:
|
||||||
|
https://github.com/steveyegge/gastown/releases
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
54
npm-package/bin/gt.js
Normal file
54
npm-package/bin/gt.js
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const { spawn } = require('child_process');
|
||||||
|
const path = require('path');
|
||||||
|
const os = require('os');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
// Determine the platform-specific binary name
|
||||||
|
function getBinaryPath() {
|
||||||
|
const platform = os.platform();
|
||||||
|
const arch = os.arch();
|
||||||
|
|
||||||
|
let binaryName = 'gt';
|
||||||
|
if (platform === 'win32') {
|
||||||
|
binaryName = 'gt.exe';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Binary is stored in the package's bin directory
|
||||||
|
const binaryPath = path.join(__dirname, binaryName);
|
||||||
|
|
||||||
|
if (!fs.existsSync(binaryPath)) {
|
||||||
|
console.error(`Error: gt binary not found at ${binaryPath}`);
|
||||||
|
console.error('This may indicate that the postinstall script failed to download the binary.');
|
||||||
|
console.error(`Platform: ${platform}, Architecture: ${arch}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return binaryPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute the native binary with all arguments passed through
|
||||||
|
function main() {
|
||||||
|
const binaryPath = getBinaryPath();
|
||||||
|
|
||||||
|
// Spawn the native gt binary with all command-line arguments
|
||||||
|
const child = spawn(binaryPath, process.argv.slice(2), {
|
||||||
|
stdio: 'inherit',
|
||||||
|
env: process.env
|
||||||
|
});
|
||||||
|
|
||||||
|
child.on('error', (err) => {
|
||||||
|
console.error(`Error executing gt binary: ${err.message}`);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
child.on('exit', (code, signal) => {
|
||||||
|
if (signal) {
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
process.exit(code || 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
51
npm-package/package.json
Normal file
51
npm-package/package.json
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
{
|
||||||
|
"name": "@gastown/gt",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "Gas Town CLI - multi-agent workspace manager with native binary support",
|
||||||
|
"main": "bin/gt.js",
|
||||||
|
"bin": {
|
||||||
|
"gt": "bin/gt.js"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"postinstall": "node scripts/postinstall.js",
|
||||||
|
"test": "node scripts/test.js"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"multi-agent",
|
||||||
|
"workspace-manager",
|
||||||
|
"ai-agent",
|
||||||
|
"coding-agent",
|
||||||
|
"claude",
|
||||||
|
"git",
|
||||||
|
"orchestration"
|
||||||
|
],
|
||||||
|
"author": "Steve Yegge",
|
||||||
|
"license": "MIT",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/steveyegge/gastown.git",
|
||||||
|
"directory": "npm-package"
|
||||||
|
},
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/steveyegge/gastown/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/steveyegge/gastown#readme",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"os": [
|
||||||
|
"darwin",
|
||||||
|
"linux",
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"cpu": [
|
||||||
|
"x64",
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
"bin/",
|
||||||
|
"scripts/",
|
||||||
|
"README.md",
|
||||||
|
"LICENSE"
|
||||||
|
]
|
||||||
|
}
|
||||||
209
npm-package/scripts/postinstall.js
Normal file
209
npm-package/scripts/postinstall.js
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const https = require('https');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const os = require('os');
|
||||||
|
const { execSync } = require('child_process');
|
||||||
|
|
||||||
|
// Get package version to determine which release to download
|
||||||
|
const packageJson = require('../package.json');
|
||||||
|
const VERSION = packageJson.version;
|
||||||
|
|
||||||
|
// Determine platform and architecture
|
||||||
|
function getPlatformInfo() {
|
||||||
|
const platform = os.platform();
|
||||||
|
const arch = os.arch();
|
||||||
|
|
||||||
|
let platformName;
|
||||||
|
let archName;
|
||||||
|
let binaryName = 'gt';
|
||||||
|
|
||||||
|
// Map Node.js platform names to GitHub release names
|
||||||
|
switch (platform) {
|
||||||
|
case 'darwin':
|
||||||
|
platformName = 'darwin';
|
||||||
|
break;
|
||||||
|
case 'linux':
|
||||||
|
platformName = 'linux';
|
||||||
|
break;
|
||||||
|
case 'win32':
|
||||||
|
platformName = 'windows';
|
||||||
|
binaryName = 'gt.exe';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unsupported platform: ${platform}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map Node.js arch names to GitHub release names
|
||||||
|
switch (arch) {
|
||||||
|
case 'x64':
|
||||||
|
archName = 'amd64';
|
||||||
|
break;
|
||||||
|
case 'arm64':
|
||||||
|
archName = 'arm64';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unsupported architecture: ${arch}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { platformName, archName, binaryName };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download file from URL
|
||||||
|
function downloadFile(url, dest) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
console.log(`Downloading from: ${url}`);
|
||||||
|
const file = fs.createWriteStream(dest);
|
||||||
|
|
||||||
|
const request = https.get(url, (response) => {
|
||||||
|
// Handle redirects
|
||||||
|
if (response.statusCode === 301 || response.statusCode === 302) {
|
||||||
|
const redirectUrl = response.headers.location;
|
||||||
|
console.log(`Following redirect to: ${redirectUrl}`);
|
||||||
|
downloadFile(redirectUrl, dest).then(resolve).catch(reject);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.statusCode !== 200) {
|
||||||
|
reject(new Error(`Failed to download: HTTP ${response.statusCode}`));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
response.pipe(file);
|
||||||
|
|
||||||
|
file.on('finish', () => {
|
||||||
|
// Wait for file.close() to complete before resolving
|
||||||
|
// This is critical on Windows where the file may still be locked
|
||||||
|
file.close((err) => {
|
||||||
|
if (err) reject(err);
|
||||||
|
else resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
request.on('error', (err) => {
|
||||||
|
fs.unlink(dest, () => {});
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
file.on('error', (err) => {
|
||||||
|
fs.unlink(dest, () => {});
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract tar.gz file
|
||||||
|
function extractTarGz(tarGzPath, destDir, binaryName) {
|
||||||
|
console.log(`Extracting ${tarGzPath}...`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Use tar command to extract
|
||||||
|
execSync(`tar -xzf "${tarGzPath}" -C "${destDir}"`, { stdio: 'inherit' });
|
||||||
|
|
||||||
|
// The binary should now be in destDir
|
||||||
|
const extractedBinary = path.join(destDir, binaryName);
|
||||||
|
|
||||||
|
if (!fs.existsSync(extractedBinary)) {
|
||||||
|
throw new Error(`Binary not found after extraction: ${extractedBinary}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make executable on Unix-like systems
|
||||||
|
if (os.platform() !== 'win32') {
|
||||||
|
fs.chmodSync(extractedBinary, 0o755);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Binary extracted to: ${extractedBinary}`);
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error(`Failed to extract archive: ${err.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract zip file (for Windows)
|
||||||
|
function extractZip(zipPath, destDir, binaryName) {
|
||||||
|
console.log(`Extracting ${zipPath}...`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Use unzip command or powershell on Windows
|
||||||
|
if (os.platform() === 'win32') {
|
||||||
|
execSync(`powershell -command "Expand-Archive -Path '${zipPath}' -DestinationPath '${destDir}' -Force"`, { stdio: 'inherit' });
|
||||||
|
} else {
|
||||||
|
execSync(`unzip -o "${zipPath}" -d "${destDir}"`, { stdio: 'inherit' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// The binary should now be in destDir
|
||||||
|
const extractedBinary = path.join(destDir, binaryName);
|
||||||
|
|
||||||
|
if (!fs.existsSync(extractedBinary)) {
|
||||||
|
throw new Error(`Binary not found after extraction: ${extractedBinary}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Binary extracted to: ${extractedBinary}`);
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error(`Failed to extract archive: ${err.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main installation function
|
||||||
|
async function install() {
|
||||||
|
try {
|
||||||
|
const { platformName, archName, binaryName } = getPlatformInfo();
|
||||||
|
|
||||||
|
console.log(`Installing gt v${VERSION} for ${platformName}-${archName}...`);
|
||||||
|
|
||||||
|
// Construct download URL
|
||||||
|
// Format: https://github.com/steveyegge/gastown/releases/download/v0.1.0/gastown_0.1.0_darwin_amd64.tar.gz
|
||||||
|
const releaseVersion = VERSION;
|
||||||
|
const archiveExt = platformName === 'windows' ? 'zip' : 'tar.gz';
|
||||||
|
const archiveName = `gastown_${releaseVersion}_${platformName}_${archName}.${archiveExt}`;
|
||||||
|
const downloadUrl = `https://github.com/steveyegge/gastown/releases/download/v${releaseVersion}/${archiveName}`;
|
||||||
|
|
||||||
|
// Determine destination paths
|
||||||
|
const binDir = path.join(__dirname, '..', 'bin');
|
||||||
|
const archivePath = path.join(binDir, archiveName);
|
||||||
|
const binaryPath = path.join(binDir, binaryName);
|
||||||
|
|
||||||
|
// Ensure bin directory exists
|
||||||
|
if (!fs.existsSync(binDir)) {
|
||||||
|
fs.mkdirSync(binDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download the archive
|
||||||
|
console.log(`Downloading gt binary...`);
|
||||||
|
await downloadFile(downloadUrl, archivePath);
|
||||||
|
|
||||||
|
// Extract the archive based on platform
|
||||||
|
if (platformName === 'windows') {
|
||||||
|
extractZip(archivePath, binDir, binaryName);
|
||||||
|
} else {
|
||||||
|
extractTarGz(archivePath, binDir, binaryName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up archive
|
||||||
|
fs.unlinkSync(archivePath);
|
||||||
|
|
||||||
|
// Verify the binary works
|
||||||
|
try {
|
||||||
|
const output = execSync(`"${binaryPath}" version`, { encoding: 'utf8' });
|
||||||
|
console.log(`gt installed successfully: ${output.trim()}`);
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('Warning: Could not verify binary version');
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Error installing gt: ${err.message}`);
|
||||||
|
console.error('');
|
||||||
|
console.error('Installation failed. You can try:');
|
||||||
|
console.error('1. Installing manually from: https://github.com/steveyegge/gastown/releases');
|
||||||
|
console.error('2. Opening an issue: https://github.com/steveyegge/gastown/issues');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run installation if not in CI environment
|
||||||
|
if (!process.env.CI) {
|
||||||
|
install();
|
||||||
|
} else {
|
||||||
|
console.log('Skipping binary download in CI environment');
|
||||||
|
}
|
||||||
53
npm-package/scripts/test.js
Normal file
53
npm-package/scripts/test.js
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const { execSync } = require('child_process');
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
const os = require('os');
|
||||||
|
|
||||||
|
console.log('Running gt npm package tests...\n');
|
||||||
|
|
||||||
|
let passed = 0;
|
||||||
|
let failed = 0;
|
||||||
|
|
||||||
|
function test(name, fn) {
|
||||||
|
try {
|
||||||
|
fn();
|
||||||
|
console.log(`[PASS] ${name}`);
|
||||||
|
passed++;
|
||||||
|
} catch (err) {
|
||||||
|
console.log(`[FAIL] ${name}: ${err.message}`);
|
||||||
|
failed++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 1: Check binary exists
|
||||||
|
test('Binary exists in bin directory', () => {
|
||||||
|
const binaryName = os.platform() === 'win32' ? 'gt.exe' : 'gt';
|
||||||
|
const binaryPath = path.join(__dirname, '..', 'bin', binaryName);
|
||||||
|
if (!fs.existsSync(binaryPath)) {
|
||||||
|
throw new Error(`Binary not found at ${binaryPath}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test 2: Binary is executable (version check)
|
||||||
|
test('Binary executes and returns version', () => {
|
||||||
|
const binaryName = os.platform() === 'win32' ? 'gt.exe' : 'gt';
|
||||||
|
const binaryPath = path.join(__dirname, '..', 'bin', binaryName);
|
||||||
|
const output = execSync(`"${binaryPath}" version`, { encoding: 'utf8' });
|
||||||
|
if (!output.includes('gt version')) {
|
||||||
|
throw new Error(`Unexpected version output: ${output}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test 3: Wrapper script exists
|
||||||
|
test('Wrapper script (gt.js) exists', () => {
|
||||||
|
const wrapperPath = path.join(__dirname, '..', 'bin', 'gt.js');
|
||||||
|
if (!fs.existsSync(wrapperPath)) {
|
||||||
|
throw new Error(`Wrapper not found at ${wrapperPath}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Summary
|
||||||
|
console.log(`\n${passed} passed, ${failed} failed`);
|
||||||
|
process.exit(failed > 0 ? 1 : 0);
|
||||||
Reference in New Issue
Block a user