diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 185da252..e27bfe1b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,6 +9,9 @@ concurrency: group: release-${{ github.ref }} cancel-in-progress: false +# Least privilege by default; every job opts in to exactly what it needs. +permissions: {} + jobs: release-please: if: ${{ github.repository == 'Gitlawb/node' }} @@ -24,7 +27,7 @@ jobs: steps: - name: Run release-please id: release - uses: googleapis/release-please-action@16a9c90856f42705d54a6fda1823352bdc62cf38 # v4.3.0 + uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0 with: token: ${{ secrets.GITHUB_TOKEN }} @@ -41,6 +44,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: ${{ needs.release-please.outputs.tag_name }} + persist-credentials: false - name: Set up QEMU uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0 @@ -101,6 +105,9 @@ jobs: needs: release-please if: ${{ needs.release-please.outputs.release_created == 'true' }} runs-on: ${{ matrix.os }} + # Windows is best-effort: a failure there must not fail the job or block the + # downstream npm/Homebrew jobs that only consume the unix artifacts. + continue-on-error: ${{ startsWith(matrix.target, 'x86_64-pc-windows') }} permissions: contents: write strategy: @@ -115,11 +122,14 @@ jobs: os: macos-13 - target: aarch64-apple-darwin os: macos-14 + - target: x86_64-pc-windows-msvc + os: windows-latest steps: - name: Checkout release tag uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: ${{ needs.release-please.outputs.tag_name }} + persist-credentials: false - name: Set up Rust toolchain uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b # stable @@ -143,8 +153,22 @@ jobs: with: key: ${{ matrix.target }} + # The gitlawb-node daemon is a Linux/macOS service; on Windows we ship only + # the two CLI binaries. A Windows-only failure must not block the release. + - name: Determine binaries + shell: bash + run: | + BINS="gl git-remote-gitlawb gitlawb-node" + case "${{ matrix.target }}" in *windows*) BINS="gl git-remote-gitlawb" ;; esac + echo "BINS=$BINS" >> "$GITHUB_ENV" + - name: Build - run: cargo build --release --target ${{ matrix.target }} -p gl -p git-remote-gitlawb -p gitlawb-node + shell: bash + run: | + set -euo pipefail + args="" + for b in $BINS; do args="$args -p $b"; done + cargo build --release --target ${{ matrix.target }} $args # Run each packaged binary's --version so a broken release artifact fails the # build instead of shipping. Skipped for cross-compiled targets that can't run @@ -156,9 +180,11 @@ jobs: set -euo pipefail VERSION="${{ needs.release-please.outputs.version }}" BIN_DIR="target/${{ matrix.target }}/release" - for bin in gl git-remote-gitlawb gitlawb-node; do + EXE="" + case "${{ matrix.target }}" in *windows*) EXE=".exe" ;; esac + for bin in $BINS; do echo "== $bin --version ==" - out="$("$BIN_DIR/$bin" --version)" + out="$("$BIN_DIR/$bin$EXE" --version)" echo "$out" # Each binary prints " "; assert the released version is present. grep -qF "$VERSION" <<<"$out" || { @@ -168,26 +194,369 @@ jobs: done - name: Package - id: pkg shell: bash run: | set -euo pipefail - NAME="gitlawb-node-${{ needs.release-please.outputs.version }}-${{ matrix.target }}" + TARGET="${{ matrix.target }}" + NAME="gitlawb-node-${{ needs.release-please.outputs.version }}-${TARGET}" + BIN_DIR="target/${TARGET}/release" + EXE="" + case "$TARGET" in *windows*) EXE=".exe" ;; esac + + # Portable sha256 → " " so install scripts and brew can parse it. + sha256_file() { + local f="$1" + if command -v sha256sum >/dev/null 2>&1; then + sha256sum "$f" + elif command -v shasum >/dev/null 2>&1; then + shasum -a 256 "$f" + else + local h + h=$(certutil -hashfile "$f" SHA256 | sed -n 2p | tr -d ' \r') + printf '%s %s\n' "$h" "$f" + fi + } + mkdir -p "dist/$NAME" - cp "target/${{ matrix.target }}/release/gl" "dist/$NAME/" - cp "target/${{ matrix.target }}/release/git-remote-gitlawb" "dist/$NAME/" - cp "target/${{ matrix.target }}/release/gitlawb-node" "dist/$NAME/" + for bin in $BINS; do + cp "$BIN_DIR/$bin$EXE" "dist/$NAME/" + done cp README.md LICENSE-MIT LICENSE-APACHE "dist/$NAME/" + cd dist - tar czf "$NAME.tar.gz" "$NAME" - shasum -a 256 "$NAME.tar.gz" > "$NAME.tar.gz.sha256" - echo "asset=dist/$NAME.tar.gz" >> "$GITHUB_OUTPUT" - echo "checksum=dist/$NAME.tar.gz.sha256" >> "$GITHUB_OUTPUT" + case "$TARGET" in + *windows*) + # 7z is preinstalled on windows-latest runners. + 7z a -tzip "$NAME.zip" "$NAME" >/dev/null + sha256_file "$NAME.zip" > "$NAME.zip.sha256" + ;; + *) + tar czf "$NAME.tar.gz" "$NAME" + sha256_file "$NAME.tar.gz" > "$NAME.tar.gz.sha256" + ;; + esac - name: Attach to release uses: softprops/action-gh-release@72f2c25fcb47643c292f7107632f7a47c1df5cd8 # v2.3.2 with: tag_name: ${{ needs.release-please.outputs.tag_name }} files: | - ${{ steps.pkg.outputs.asset }} - ${{ steps.pkg.outputs.checksum }} + dist/*.tar.gz + dist/*.zip + dist/*.sha256 + + npm-publish: + name: Publish to npm + needs: [release-please, release-binaries] + if: ${{ needs.release-please.outputs.release_created == 'true' }} + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write # npm provenance + steps: + - name: Guard on secret + id: guard + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + run: | + if [ -z "${NPM_TOKEN:-}" ]; then + echo "::warning::NPM_TOKEN is not set — skipping npm publish." + echo "enabled=false" >> "$GITHUB_OUTPUT" + else + echo "enabled=true" >> "$GITHUB_OUTPUT" + fi + + - name: Checkout release tag + if: ${{ steps.guard.outputs.enabled == 'true' }} + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: ${{ needs.release-please.outputs.tag_name }} + persist-credentials: false + + - name: Lay in release binaries + if: ${{ steps.guard.outputs.enabled == 'true' }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION: ${{ needs.release-please.outputs.version }} + TAG: ${{ needs.release-please.outputs.tag_name }} + run: | + set -euo pipefail + # npm platform package -> Rust target triple (unix only; Windows is not + # published to npm). + MAP=" + gl-darwin-arm64:aarch64-apple-darwin + gl-darwin-x64:x86_64-apple-darwin + gl-linux-arm64:aarch64-unknown-linux-musl + gl-linux-x64:x86_64-unknown-linux-musl + " + mkdir -p _dl + for entry in $MAP; do + pkg="${entry%%:*}" + target="${entry#*:}" + archive="gitlawb-node-${VERSION}-${target}.tar.gz" + echo "==> $pkg <- $archive" + gh release download "$TAG" --repo "$GITHUB_REPOSITORY" \ + --pattern "$archive" --dir _dl --clobber + tar -xzf "_dl/$archive" -C _dl + src="_dl/gitlawb-node-${VERSION}-${target}" + cp "$src/gl" "npm/packages/$pkg/gl" + cp "$src/git-remote-gitlawb" "npm/packages/$pkg/git-remote-gitlawb" + chmod +x "npm/packages/$pkg/gl" "npm/packages/$pkg/git-remote-gitlawb" + done + + - name: Set versions + if: ${{ steps.guard.outputs.enabled == 'true' }} + env: + VERSION: ${{ needs.release-please.outputs.version }} + run: | + set -euo pipefail + node -e ' + const fs = require("fs"); + const v = process.env.VERSION; + for (const p of fs.readdirSync("npm/packages")) { + const f = `npm/packages/${p}/package.json`; + const pkg = JSON.parse(fs.readFileSync(f, "utf8")); + pkg.version = v; + if (pkg.optionalDependencies) { + for (const k of Object.keys(pkg.optionalDependencies)) { + pkg.optionalDependencies[k] = v; + } + } + fs.writeFileSync(f, JSON.stringify(pkg, null, 2) + "\n"); + } + ' + + - name: Publish + if: ${{ steps.guard.outputs.enabled == 'true' }} + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + VERSION: ${{ needs.release-please.outputs.version }} + run: | + set -euo pipefail + echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc + # Platform packages first, then the wrapper (so its optionalDependencies resolve). + # Skip versions already on the registry so a rerun after a partial publish + # is idempotent instead of erroring on the first existing package. + for pkg in gl-darwin-arm64 gl-darwin-x64 gl-linux-arm64 gl-linux-x64 gl; do + name="@gitlawb/$pkg" + if npm view "$name@$VERSION" version >/dev/null 2>&1; then + echo "==> $name@$VERSION already published, skipping" + continue + fi + echo "==> npm publish $name@$VERSION" + npm publish "npm/packages/$pkg" --provenance --access public + done + + homebrew-bump: + name: Bump Homebrew tap + needs: [release-please, release-binaries] + if: ${{ needs.release-please.outputs.release_created == 'true' }} + runs-on: ubuntu-latest + permissions: + contents: read # GITHUB_TOKEN downloads release assets; the tap push uses HOMEBREW_TAP_PAT + steps: + - name: Guard on secret + id: guard + env: + HOMEBREW_TAP_PAT: ${{ secrets.HOMEBREW_TAP_PAT }} + run: | + if [ -z "${HOMEBREW_TAP_PAT:-}" ]; then + echo "::warning::HOMEBREW_TAP_PAT is not set — skipping Homebrew bump." + echo "enabled=false" >> "$GITHUB_OUTPUT" + else + echo "enabled=true" >> "$GITHUB_OUTPUT" + fi + + - name: Checkout tap repo + if: ${{ steps.guard.outputs.enabled == 'true' }} + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + repository: Gitlawb/homebrew-tap + token: ${{ secrets.HOMEBREW_TAP_PAT }} + path: tap + persist-credentials: false + + - name: Regenerate formula + if: ${{ steps.guard.outputs.enabled == 'true' }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION: ${{ needs.release-please.outputs.version }} + TAG: ${{ needs.release-please.outputs.tag_name }} + run: | + set -euo pipefail + base="https://github.com/${GITHUB_REPOSITORY}/releases/download/${TAG}" + mkdir -p _sums + gh release download "$TAG" --repo "$GITHUB_REPOSITORY" \ + --pattern '*.tar.gz.sha256' --dir _sums --clobber + sha() { awk '{print $1}' "_sums/gitlawb-node-${VERSION}-$1.tar.gz.sha256"; } + SHA_MAC_ARM="$(sha aarch64-apple-darwin)" + SHA_MAC_X64="$(sha x86_64-apple-darwin)" + SHA_LNX_ARM="$(sha aarch64-unknown-linux-musl)" + SHA_LNX_X64="$(sha x86_64-unknown-linux-musl)" + + mkdir -p tap/Formula + cat > tap/Formula/gl.rb <> "$GITHUB_OUTPUT" + else + echo "enabled=true" >> "$GITHUB_OUTPUT" + fi + + - name: Checkout node (release tag) + if: ${{ steps.guard.outputs.enabled == 'true' }} + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: ${{ needs.release-please.outputs.tag_name }} + path: node + persist-credentials: false + + - name: Checkout web + if: ${{ steps.guard.outputs.enabled == 'true' }} + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + repository: Gitlawb/web + token: ${{ secrets.WEB_SYNC_PAT }} + path: web + persist-credentials: false + + # web-sync publishes install.ps1 + version.json, so the Windows ZIP must exist. + # Windows is best-effort for npm/Homebrew (continue-on-error), so verify the + # asset is present before advertising a release the site can't actually serve. + - name: Verify website release assets + if: ${{ steps.guard.outputs.enabled == 'true' }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION: ${{ needs.release-please.outputs.version }} + TAG: ${{ needs.release-please.outputs.tag_name }} + run: | + set -euo pipefail + gh release view "$TAG" --repo "$GITHUB_REPOSITORY" --json assets \ + --jq '.assets[].name' > /tmp/release-assets + for asset in \ + "gitlawb-node-${VERSION}-x86_64-pc-windows-msvc.zip" \ + "gitlawb-node-${VERSION}-x86_64-pc-windows-msvc.zip.sha256"; do + grep -Fxq "$asset" /tmp/release-assets || { + echo "::error::release asset missing: $asset (Windows build likely failed); not syncing web" + exit 1 + } + done + + - name: Sync and open PR + if: ${{ steps.guard.outputs.enabled == 'true' }} + env: + GH_TOKEN: ${{ secrets.WEB_SYNC_PAT }} + VERSION: ${{ needs.release-please.outputs.version }} + TAG: ${{ needs.release-please.outputs.tag_name }} + run: | + set -euo pipefail + # Canonical install scripts (source of truth lives in the node repo). + cp node/install.sh web/public/install.sh + cp node/install.ps1 web/public/install.ps1 + # Single version source the site can read. + printf '{\n "version": "%s",\n "tag": "%s"\n}\n' "$VERSION" "$TAG" > web/public/version.json + # One-time, idempotent drift fixes: drop orphaned binaries, fix stale link. + rm -rf web/public/bin + if [ -f web/public/skill.md ]; then + sed -i 's#github.com/gitlawb/releases#github.com/Gitlawb/node/releases#g' web/public/skill.md + fi + + cd web + # Stage first so brand-new (untracked) files are caught by the no-op check. + git add -A + if git diff --cached --quiet; then + echo "web already up to date." + exit 0 + fi + branch="release-sync/${TAG}" + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git checkout -b "$branch" + git commit -m "chore: sync gitlawb ${TAG} (install scripts + version)" + # Credentials are not persisted in .git/config; supply the token only for the push. + git push --force "https://x-access-token:${GH_TOKEN}@github.com/Gitlawb/web.git" "HEAD:$branch" + gh pr create --repo Gitlawb/web --head "$branch" \ + --title "Sync gitlawb ${TAG}" \ + --body "Automated sync from Gitlawb/node ${TAG}: install.sh, install.ps1, public/version.json, and one-time drift fixes (removed orphaned public/bin, fixed skill.md releases link). Review and merge to deploy via Vercel." \ + || { + # Only swallow the "PR already exists" case; surface auth/API failures + # (a failed gh pr list must not be misread as "PR exists"). + pr_count="$(gh pr list --repo Gitlawb/web --head "$branch" --state open --json number --jq 'length')" || exit 1 + if [ "$pr_count" != "0" ]; then + echo "PR for $branch already exists; branch was updated." + else + exit 1 + fi + } diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 343519b9..e74dc540 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.3.8" + ".": "0.3.9" } diff --git a/README.md b/README.md index e51a1eba..dbd94304 100644 --- a/README.md +++ b/README.md @@ -119,10 +119,18 @@ docker compose down ## Install the CLI -Use the installer once releases are available for your platform: - ```bash +# npm (macOS / Linux) +npm install -g @gitlawb/gl + +# Homebrew (macOS / Linux) +brew install gitlawb/tap/gl + +# curl (macOS / Linux) curl -fsSL https://gitlawb.com/install.sh | sh + +# PowerShell (Windows) +irm https://gitlawb.com/install.ps1 | iex ``` Or build from source: diff --git a/install.ps1 b/install.ps1 new file mode 100644 index 00000000..74058687 --- /dev/null +++ b/install.ps1 @@ -0,0 +1,124 @@ +<# +.SYNOPSIS + gitlawb installer for Windows - downloads pre-built binaries from GitHub Releases. +.EXAMPLE + irm https://gitlawb.com/install.ps1 | iex +.EXAMPLE + & ([scriptblock]::Create((irm https://gitlawb.com/install.ps1))) -Version v0.3.9 +#> +[CmdletBinding()] +param( + [string]$Version = "latest" +) + +$ErrorActionPreference = "Stop" + +# Windows PowerShell 5.1 may default to TLS 1.0; GitHub requires TLS 1.2+. +[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 + +$Repo = if ($env:GITLAWB_RELEASE_REPO) { $env:GITLAWB_RELEASE_REPO } else { "Gitlawb/node" } +$InstallDir = if ($env:GITLAWB_INSTALL_DIR) { $env:GITLAWB_INSTALL_DIR } else { "$env:LOCALAPPDATA\Programs\gitlawb" } + +$arch = if ($env:PROCESSOR_ARCHITEW6432) { + $env:PROCESSOR_ARCHITEW6432 +} else { + $env:PROCESSOR_ARCHITECTURE +} +if ($arch -ne "AMD64") { + throw "Unsupported architecture: $arch. Only x64 Windows binaries are published. Use WSL for arm64." +} +$target = "x86_64-pc-windows-msvc" + +if ($Version -eq "latest") { + Write-Host "Fetching latest release version..." + $rel = Invoke-RestMethod -Uri "https://api.github.com/repos/$Repo/releases/latest" -UseBasicParsing -Headers @{ "User-Agent" = "gitlawb-installer" } + $tag = $rel.tag_name +} elseif ($Version.StartsWith("v")) { + $tag = $Version +} else { + $tag = "v$Version" +} +$ver = $tag.TrimStart("v") + +$archive = "gitlawb-node-$ver-$target.zip" +$url = "https://github.com/$Repo/releases/download/$tag/$archive" + +Write-Host "Installing gitlawb $tag for windows/x64" +Write-Host " Archive: $archive" +Write-Host " Into: $InstallDir" + +$tmp = Join-Path ([System.IO.Path]::GetTempPath()) ("gitlawb-" + [System.Guid]::NewGuid().ToString("N")) +New-Item -ItemType Directory -Path $tmp | Out-Null +try { + $zipPath = Join-Path $tmp $archive + Write-Host "Downloading..." + Invoke-WebRequest -Uri $url -OutFile $zipPath -UseBasicParsing -Headers @{ "User-Agent" = "gitlawb-installer" } + + # Verify checksum. Every published asset has a matching .sha256, so fail closed + # if it cannot be fetched rather than installing an unverified binary. + $sumPath = "$zipPath.sha256" + Invoke-WebRequest -Uri "$url.sha256" -OutFile $sumPath -UseBasicParsing -Headers @{ "User-Agent" = "gitlawb-installer" } + $expected = ((Get-Content $sumPath -Raw).Trim() -split '\s+')[0].ToLower() + $actual = (Get-FileHash $zipPath -Algorithm SHA256).Hash.ToLower() + if ($expected -ne $actual) { + throw "checksum mismatch! expected $expected got $actual" + } + Write-Host " checksum OK" + + Write-Host "Extracting..." + $extract = Join-Path $tmp "extract" + Expand-Archive -Path $zipPath -DestinationPath $extract -Force + $pkgDir = Get-ChildItem -Path $extract -Directory | Select-Object -First 1 + + New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null + # The Windows archive always ships the two CLI binaries; the daemon is optional. + $requiredBins = @("gl.exe", "git-remote-gitlawb.exe") + $optionalBins = @("gitlawb-node.exe") + $missing = $requiredBins | Where-Object { + -not (Test-Path (Join-Path $pkgDir.FullName $_)) + } + if ($missing.Count -gt 0) { + throw "archive is missing required binaries: $($missing -join ', ')" + } + + $installed = @() + foreach ($bin in $requiredBins + $optionalBins) { + $src = Join-Path $pkgDir.FullName $bin + if (Test-Path $src) { + Copy-Item -Path $src -Destination (Join-Path $InstallDir $bin) -Force + $installed += $bin + } + } + + # Drop binaries no longer shipped (e.g. an optional one removed in a new release) + # so an upgrade in place never leaves a stale, mixed-version install behind. + foreach ($bin in $requiredBins + $optionalBins) { + if ($installed -notcontains $bin) { + $dst = Join-Path $InstallDir $bin + if (Test-Path $dst) { Remove-Item -Path $dst -Force } + } + } + + Write-Host "" + Write-Host "Installed gitlawb $tag" + foreach ($bin in $installed) { Write-Host " $bin -> $InstallDir\$bin" } +} +finally { + Remove-Item -Recurse -Force $tmp -ErrorAction SilentlyContinue +} + +# Add to the user PATH if missing. +$userPath = [Environment]::GetEnvironmentVariable("Path", "User") +if (($userPath -split ';') -notcontains $InstallDir) { + $newPath = if ([string]::IsNullOrEmpty($userPath)) { $InstallDir } else { "$userPath;$InstallDir" } + [Environment]::SetEnvironmentVariable("Path", $newPath, "User") + Write-Host "" + Write-Host "Added $InstallDir to your PATH. Restart your terminal, then run:" +} else { + Write-Host "" + Write-Host "Run:" +} +Write-Host " gl doctor" +Write-Host " gl quickstart" +Write-Host "" +Write-Host "Docs: https://docs.gitlawb.com" diff --git a/npm/.gitignore b/npm/.gitignore new file mode 100644 index 00000000..586a17aa --- /dev/null +++ b/npm/.gitignore @@ -0,0 +1,15 @@ +node_modules/ + +# Platform binaries — laid in by the release workflow at publish time, not committed. +packages/gl-darwin-arm64/gl +packages/gl-darwin-arm64/git-remote-gitlawb +packages/gl-darwin-x64/gl +packages/gl-darwin-x64/git-remote-gitlawb +packages/gl-linux-arm64/gl +packages/gl-linux-arm64/git-remote-gitlawb +packages/gl-linux-x64/gl +packages/gl-linux-x64/git-remote-gitlawb + +# Wrapper bin placeholders are overwritten by postinstall. +packages/gl/bin/gl +packages/gl/bin/git-remote-gitlawb diff --git a/npm/README.md b/npm/README.md new file mode 100644 index 00000000..c8c9c785 --- /dev/null +++ b/npm/README.md @@ -0,0 +1,37 @@ +# npm distribution for the gitlawb CLI + +This directory packages the `gl` and `git-remote-gitlawb` binaries as the +[`@gitlawb/gl`](https://www.npmjs.com/package/@gitlawb/gl) npm package. + +It uses the **optionalDependencies platform-package** pattern (the same one +esbuild/swc/turbo use): the wrapper `@gitlawb/gl` declares four per-platform +packages as `optionalDependencies`, each gated by `os`/`cpu`. npm installs only +the one matching the host, and the wrapper's `postinstall` (`install.js`) copies +the binary out of it into `bin/`. No binaries are downloaded at install time. + +```text +packages/ + gl/ @gitlawb/gl wrapper (postinstall, bin shims) + gl-darwin-arm64/ @gitlawb/gl-darwin-arm64 aarch64-apple-darwin + gl-darwin-x64/ @gitlawb/gl-darwin-x64 x86_64-apple-darwin + gl-linux-arm64/ @gitlawb/gl-linux-arm64 aarch64-unknown-linux-musl + gl-linux-x64/ @gitlawb/gl-linux-x64 x86_64-unknown-linux-musl +``` + +## Publishing + +Publishing is **automated** by `.github/workflows/release.yml` (the `npm-publish` +job) on every release-please release. That job: + +1. Downloads the four unix release tarballs from the `Gitlawb/node` GitHub release. +2. Lays `gl` + `git-remote-gitlawb` into each platform package. +3. Rewrites every `package.json` `version` (and the wrapper's + `optionalDependencies` pins) to the release version. +4. Publishes the four platform packages first, then the wrapper, with + `npm publish --provenance --access public`. + +The committed `version` fields here are placeholders; the workflow is the single +source of truth and always uses the release tag. There is no manual publish step. + +Windows is intentionally not published to npm — the wrapper points Windows users +at the curl/PowerShell installer instead. diff --git a/npm/packages/gl-darwin-arm64/package.json b/npm/packages/gl-darwin-arm64/package.json new file mode 100644 index 00000000..3138669a --- /dev/null +++ b/npm/packages/gl-darwin-arm64/package.json @@ -0,0 +1,21 @@ +{ + "name": "@gitlawb/gl-darwin-arm64", + "version": "0.3.9", + "description": "Gitlawb CLI binary for macOS Apple Silicon (arm64)", + "license": "MIT", + "os": ["darwin"], + "cpu": ["arm64"], + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + }, + "repository": { + "type": "git", + "url": "https://github.com/Gitlawb/node", + "directory": "npm/packages/gl-darwin-arm64" + }, + "files": [ + "gl", + "git-remote-gitlawb" + ] +} diff --git a/npm/packages/gl-darwin-x64/package.json b/npm/packages/gl-darwin-x64/package.json new file mode 100644 index 00000000..39a86c2b --- /dev/null +++ b/npm/packages/gl-darwin-x64/package.json @@ -0,0 +1,21 @@ +{ + "name": "@gitlawb/gl-darwin-x64", + "version": "0.3.9", + "description": "Gitlawb CLI binary for macOS Intel (x64)", + "license": "MIT", + "os": ["darwin"], + "cpu": ["x64"], + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + }, + "repository": { + "type": "git", + "url": "https://github.com/Gitlawb/node", + "directory": "npm/packages/gl-darwin-x64" + }, + "files": [ + "gl", + "git-remote-gitlawb" + ] +} diff --git a/npm/packages/gl-linux-arm64/package.json b/npm/packages/gl-linux-arm64/package.json new file mode 100644 index 00000000..8844718d --- /dev/null +++ b/npm/packages/gl-linux-arm64/package.json @@ -0,0 +1,21 @@ +{ + "name": "@gitlawb/gl-linux-arm64", + "version": "0.3.9", + "description": "Gitlawb CLI binary for Linux arm64", + "license": "MIT", + "os": ["linux"], + "cpu": ["arm64"], + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + }, + "repository": { + "type": "git", + "url": "https://github.com/Gitlawb/node", + "directory": "npm/packages/gl-linux-arm64" + }, + "files": [ + "gl", + "git-remote-gitlawb" + ] +} diff --git a/npm/packages/gl-linux-x64/package.json b/npm/packages/gl-linux-x64/package.json new file mode 100644 index 00000000..cb565f01 --- /dev/null +++ b/npm/packages/gl-linux-x64/package.json @@ -0,0 +1,21 @@ +{ + "name": "@gitlawb/gl-linux-x64", + "version": "0.3.9", + "description": "Gitlawb CLI binary for Linux x64", + "license": "MIT", + "os": ["linux"], + "cpu": ["x64"], + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + }, + "repository": { + "type": "git", + "url": "https://github.com/Gitlawb/node", + "directory": "npm/packages/gl-linux-x64" + }, + "files": [ + "gl", + "git-remote-gitlawb" + ] +} diff --git a/npm/packages/gl/README.md b/npm/packages/gl/README.md new file mode 100644 index 00000000..6d9a3f80 --- /dev/null +++ b/npm/packages/gl/README.md @@ -0,0 +1,67 @@ +# @gitlawb/gl + +The [gitlawb](https://gitlawb.com) CLI — decentralized git for AI agents and developers. + +## Install + +```bash +# macOS / Linux / WSL +npm install -g @gitlawb/gl +``` + +Also works with yarn, pnpm, and bun: + +```bash +# macOS / Linux / WSL +yarn global add @gitlawb/gl +pnpm add -g @gitlawb/gl +bun add -g @gitlawb/gl +``` + +Native Windows is not supported via npm — use the PowerShell installer below. + +### Other install methods + +```bash +# Homebrew +brew install gitlawb/tap/gl + +# curl (macOS / Linux) +curl -sSf https://gitlawb.com/install.sh | sh + +# PowerShell (Windows) +irm https://gitlawb.com/install.ps1 | iex +``` + +## Quick start + +```bash +gl identity new +gl register +gl doctor +gl repo create my-project --description "My first gitlawb repo" +git remote add gitlawb gitlawb://my-project +git push gitlawb main +``` + +## What's included + +- **`gl`** — the main CLI for identity, repos, PRs, bounties, and agents +- **`git-remote-gitlawb`** — git remote helper for `gitlawb://` URLs + +## Supported platforms + +| Platform | Architecture | Package | +|----------|-------------|---------| +| macOS | Apple Silicon (arm64) | `@gitlawb/gl-darwin-arm64` | +| macOS | Intel (x64) | `@gitlawb/gl-darwin-x64` | +| Linux | arm64 | `@gitlawb/gl-linux-arm64` | +| Linux | x64 | `@gitlawb/gl-linux-x64` | + +Windows is supported via the PowerShell installer or WSL. + +## Links + +- Docs: https://docs.gitlawb.com +- Site: https://gitlawb.com +- Source: https://github.com/Gitlawb/node diff --git a/npm/packages/gl/bin/.gitkeep b/npm/packages/gl/bin/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/npm/packages/gl/install.js b/npm/packages/gl/install.js new file mode 100644 index 00000000..2e2904da --- /dev/null +++ b/npm/packages/gl/install.js @@ -0,0 +1,89 @@ +#!/usr/bin/env node + +const { existsSync, copyFileSync, chmodSync, mkdirSync } = require("fs"); +const { join, dirname } = require("path"); + +const PLATFORM_PACKAGES = { + "darwin-arm64": "@gitlawb/gl-darwin-arm64", + "darwin-x64": "@gitlawb/gl-darwin-x64", + "linux-arm64": "@gitlawb/gl-linux-arm64", + "linux-x64": "@gitlawb/gl-linux-x64", +}; + +const BINARIES = ["gl", "git-remote-gitlawb"]; + +function getPlatformKey() { + const platform = process.platform; + const arch = process.arch; + + if (arch !== "arm64" && arch !== "x64") { + throw new Error( + `Unsupported architecture: ${arch}. Gitlawb CLI supports x64 and arm64.` + ); + } + + if (platform !== "darwin" && platform !== "linux") { + throw new Error( + `Unsupported platform: ${platform}. Gitlawb CLI supports macOS and Linux.\n` + + `For Windows, use WSL or the installer: https://gitlawb.com/install.ps1` + ); + } + + return `${platform}-${arch}`; +} + +function findPlatformPackage(packageName) { + try { + const pkgJson = require.resolve(`${packageName}/package.json`); + return dirname(pkgJson); + } catch { + return null; + } +} + +function install() { + const platformKey = getPlatformKey(); + const packageName = PLATFORM_PACKAGES[platformKey]; + + if (!packageName) { + console.error(`No binary package available for ${platformKey}`); + process.exit(1); + } + + const packageDir = findPlatformPackage(packageName); + + if (!packageDir) { + // On a supported platform the matching optional dependency must be present; + // fail loudly rather than leave @gitlawb/gl installed without its binaries. + console.error( + `@gitlawb/gl: Platform package ${packageName} not found.\n` + + `This can happen if optional dependencies were skipped.\n` + + `Install manually: curl -sSf https://gitlawb.com/install.sh | sh` + ); + process.exit(1); + } + + const binDir = join(__dirname, "bin"); + mkdirSync(binDir, { recursive: true }); + + for (const binary of BINARIES) { + const src = join(packageDir, binary); + const dest = join(binDir, binary); + + if (!existsSync(src)) { + throw new Error(`Binary ${binary} not found in ${packageName}`); + } + + copyFileSync(src, dest); + chmodSync(dest, 0o755); + } + + console.log(`@gitlawb/gl: Installed gitlawb CLI for ${platformKey}`); +} + +try { + install(); +} catch (err) { + console.error(`@gitlawb/gl: ${err.message}`); + process.exit(1); +} diff --git a/npm/packages/gl/package.json b/npm/packages/gl/package.json new file mode 100644 index 00000000..a5f7d372 --- /dev/null +++ b/npm/packages/gl/package.json @@ -0,0 +1,42 @@ +{ + "name": "@gitlawb/gl", + "version": "0.3.9", + "description": "Gitlawb CLI — decentralized git for AI agents and developers", + "license": "MIT", + "bin": { + "gl": "bin/gl", + "git-remote-gitlawb": "bin/git-remote-gitlawb" + }, + "scripts": { + "postinstall": "node install.js" + }, + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + }, + "repository": { + "type": "git", + "url": "https://github.com/Gitlawb/node", + "directory": "npm/packages/gl" + }, + "keywords": [ + "gitlawb", + "git", + "decentralized", + "cli", + "ai-agents", + "did", + "ucan" + ], + "files": [ + "bin", + "install.js", + "README.md" + ], + "optionalDependencies": { + "@gitlawb/gl-darwin-arm64": "0.3.9", + "@gitlawb/gl-darwin-x64": "0.3.9", + "@gitlawb/gl-linux-arm64": "0.3.9", + "@gitlawb/gl-linux-x64": "0.3.9" + } +}