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