Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
internal/legacy/archives/*
dist/
npm/dist/
php-*
completion

Expand Down
15 changes: 15 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -155,3 +155,18 @@ vendor-snapshot: check-vendor .goreleaser.vendor.yaml goreleaser internal/legacy
.PHONY: goreleaser-check
goreleaser-check: goreleaser ## Check the goreleaser configs
PHP_VERSION=$(PHP_VERSION) goreleaser check --config=.goreleaser.yaml

# ----- npm distribution -----
# See npm/README.md.

.PHONY: npm-pack
npm-pack: ## Build npm tarballs from existing GoReleaser archives in dist/
bash npm/scripts/build.sh

.PHONY: npm-publish
npm-publish: ## Publish npm tarballs (requires npm auth). NPM_TAG=latest|next, DRY_RUN=1 to dry-run
bash npm/scripts/publish.sh

.PHONY: npm-clean
npm-clean: ## Remove npm/dist working directory
rm -rf npm/dist
78 changes: 78 additions & 0 deletions npm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# npm distribution

Tooling to ship the Upsun CLI as an npm package, so users can run
`npm install -g upsun` or `npx upsun`. Implements the
`optionalDependencies` pattern used by esbuild, swc, biome, turbo, and
others: a small wrapper package selects the right platform-specific
package at install time, so each user only downloads the binary that
matches their OS and CPU. No postinstall script, no runtime download.

## Packages

| Package | Contents |
| ------------------------ | --------------------------------------- |
| `upsun` | wrapper, with the four platforms below as `optionalDependencies` |
| `@upsun/cli-linux-x64` | Linux amd64 binary |
| `@upsun/cli-linux-arm64` | Linux arm64 binary |
| `@upsun/cli-darwin` | macOS universal binary (x64 + arm64) |
| `@upsun/cli-win32-x64` | Windows amd64 binary |

## Layout

```
npm/
├── wrapper/ wrapper package source
│ ├── bin/upsun.js shim that resolves the platform package and execs the binary
│ ├── package.json.tmpl stamped with version at build time
│ └── README.md shipped to the registry as the wrapper README
├── platform-template/ common template for all platform-specific packages
│ ├── package.json.tmpl stamped per-target with name, version, os, cpu
│ └── README.md.tmpl
├── scripts/
│ ├── build.sh assembles tarballs from GoReleaser archives
│ └── publish.sh publishes tarballs in lockstep
└── dist/ build output (npm pack tarballs); gitignored
```

## Build

```sh
make snapshot-no-nfpm # or any goreleaser invocation that writes upsun_*.tar.gz/zip into dist/
make npm-pack # reads dist/, writes npm/dist/*.tgz
```

The build script resolves the version from the GoReleaser archive
filenames. Override with `VERSION=...` if you need to.

## Publish

```sh
make npm-publish # publish all five packages in lockstep
DRY_RUN=1 make npm-publish # validate without publishing
NPM_TAG=next make npm-publish # for prereleases
```

The script publishes platform packages first, then the wrapper, so the
registry is never in a state where the wrapper points at platform
packages that don't yet exist.

Auth is via the standard npm mechanism: `~/.npmrc` with a token, or the
`actions/setup-node` action in CI populating one for you from
`NODE_AUTH_TOKEN`. The `--access public` flag is set so first-time
publishes of scoped packages do not get marked private.

## Versioning

Every npm release uses the same version as the corresponding GitHub
release tag. Platform packages and the wrapper are always published in
lockstep at the same version; the wrapper's `optionalDependencies` pin
exact versions, so a mismatched set will not resolve.

## Known limitations

- `npm install --no-optional` (or `--omit=optional`) skips the platform
package, and the wrapper exits with a clear error pointing at the flag.
- `darwin-arm64` and `darwin-x64` share a single universal binary
package. This roughly doubles the macOS install size relative to
per-arch packages, but matches the artifact GoReleaser produces and
keeps the package set smaller.
7 changes: 7 additions & 0 deletions npm/platform-template/README.md.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# __PKG_NAME__

Platform-specific binary for the [Upsun CLI](https://github.com/upsun/cli).

This package is installed automatically by the `upsun` wrapper as an
`optionalDependency` matching your operating system and CPU. You do not
need to install it directly.
17 changes: 17 additions & 0 deletions npm/platform-template/package.json.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "__PKG_NAME__",
"version": "__VERSION__",
"description": "__DESCRIPTION__",
"homepage": "https://docs.upsun.com/anchors/cli/",
"repository": {
"type": "git",
"url": "git+https://github.com/upsun/cli.git"
},
"license": "MIT",
"files": [
"bin",
"README.md"
],
"os": __OS__,
"cpu": __CPU__
}
174 changes: 174 additions & 0 deletions npm/scripts/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
#!/usr/bin/env bash
# Assembles npm packages from GoReleaser archives.
#
# Inputs (env vars, all optional):
# DIST_DIR Directory containing GoReleaser archives. Default: <repo>/dist
# VERSION Package version. Default: derived from the first matching archive name.
# OUT_DIR Where to write per-package working dirs and tarballs. Default: npm/dist
#
# Produces:
# upsun (wrapper, with the four platforms below as optionalDependencies)
# @upsun/cli-linux-x64
# @upsun/cli-linux-arm64
# @upsun/cli-darwin (universal binary; covers x64 and arm64)
# @upsun/cli-win32-x64

set -euo pipefail

NPM_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
REPO_ROOT="$(cd "${NPM_DIR}/.." && pwd)"

DIST_DIR="${DIST_DIR:-${REPO_ROOT}/dist}"
OUT_DIR="${OUT_DIR:-${NPM_DIR}/dist}"

if [ ! -d "${DIST_DIR}" ]; then
echo "build.sh: DIST_DIR not found: ${DIST_DIR}" >&2
echo "Run 'goreleaser release --snapshot --clean' first, or point DIST_DIR at the archives." >&2
exit 1
fi

# Per-suffix metadata. Implemented as case statements rather than
# associative arrays so the script works on macOS's default Bash 3.2.
# The darwin entry has a permissive cpu list because macOS ships a
# single universal binary that runs on both Apple Silicon and Intel.
PLATFORMS=(linux-x64 linux-arm64 darwin win32-x64)

archive_glob_for() {
case "$1" in
linux-x64) echo "upsun_*_linux_amd64.tar.gz" ;;
linux-arm64) echo "upsun_*_linux_arm64.tar.gz" ;;
darwin) echo "upsun_*_darwin_all.tar.gz" ;;
win32-x64) echo "upsun_*_windows_amd64.zip" ;;
*) echo "build.sh: unsupported platform suffix: $1" >&2; exit 1 ;;
esac
}

bin_name_for() {
case "$1" in
linux-x64|linux-arm64|darwin) echo "upsun" ;;
win32-x64) echo "upsun.exe" ;;
*) echo "build.sh: unsupported platform suffix: $1" >&2; exit 1 ;;
esac
}

os_json_for() {
case "$1" in
linux-x64|linux-arm64) echo '["linux"]' ;;
darwin) echo '["darwin"]' ;;
win32-x64) echo '["win32"]' ;;
*) echo "build.sh: unsupported platform suffix: $1" >&2; exit 1 ;;
esac
}

cpu_json_for() {
case "$1" in
linux-x64) echo '["x64"]' ;;
linux-arm64) echo '["arm64"]' ;;
darwin) echo '["x64","arm64"]' ;;
win32-x64) echo '["x64"]' ;;
*) echo "build.sh: unsupported platform suffix: $1" >&2; exit 1 ;;
esac
}

description_for() {
case "$1" in
linux-x64) echo "Upsun CLI binary for Linux x64" ;;
linux-arm64) echo "Upsun CLI binary for Linux arm64" ;;
darwin) echo "Upsun CLI binary for macOS (universal)" ;;
win32-x64) echo "Upsun CLI binary for Windows x64" ;;
*) echo "build.sh: unsupported platform suffix: $1" >&2; exit 1 ;;
esac
}

if [ -z "${VERSION:-}" ]; then
shopt -s nullglob
matches=("${DIST_DIR}"/upsun_*_linux_amd64.tar.gz)
shopt -u nullglob
if [ ${#matches[@]} -eq 0 ]; then
echo "build.sh: no upsun_*_linux_amd64.tar.gz in ${DIST_DIR}; set VERSION explicitly" >&2
exit 1
fi
base="$(basename "${matches[0]}")"
# upsun_X.Y.Z_linux_amd64.tar.gz -> X.Y.Z
VERSION="${base#upsun_}"
VERSION="${VERSION%_linux_amd64.tar.gz}"
fi

echo "build.sh: VERSION=${VERSION}"

rm -rf "${OUT_DIR}"
mkdir -p "${OUT_DIR}"

build_platform_pkg() {
local suffix="$1"
local glob; glob="$(archive_glob_for "$suffix")"
local bin; bin="$(bin_name_for "$suffix")"
local name="@upsun/cli-${suffix}"

shopt -s nullglob
# shellcheck disable=SC2206 # intentional glob expansion
local archives=("${DIST_DIR}"/${glob})
shopt -u nullglob
if [ ${#archives[@]} -eq 0 ]; then
echo "build.sh: no archive matching ${glob} in ${DIST_DIR}" >&2
exit 1
fi
local archive="${archives[0]}"

local pkg_dir="${OUT_DIR}/${suffix}"
mkdir -p "${pkg_dir}/bin"

case "${archive}" in
*.tar.gz) tar -xzf "${archive}" -C "${pkg_dir}/bin" "${bin}" ;;
*.zip) unzip -p "${archive}" "${bin}" > "${pkg_dir}/bin/${bin}" ;;
*) echo "build.sh: unsupported archive: ${archive}" >&2; exit 1 ;;
esac
# The exec bit is meaningless on the Windows binary, so a chmod failure
# there is benign; on Unix targets a failure means the binary won't run.
if [ "${suffix}" = "win32-x64" ]; then
chmod +x "${pkg_dir}/bin/${bin}" || true
else
chmod +x "${pkg_dir}/bin/${bin}"
fi

sed \
-e "s|__PKG_NAME__|${name}|g" \
-e "s|__VERSION__|${VERSION}|g" \
-e "s|__DESCRIPTION__|$(description_for "$suffix")|g" \
-e "s|__OS__|$(os_json_for "$suffix")|g" \
-e "s|__CPU__|$(cpu_json_for "$suffix")|g" \
"${NPM_DIR}/platform-template/package.json.tmpl" > "${pkg_dir}/package.json"

sed -e "s|__PKG_NAME__|${name}|g" \
"${NPM_DIR}/platform-template/README.md.tmpl" > "${pkg_dir}/README.md"

(cd "${pkg_dir}" && npm pack --pack-destination "${OUT_DIR}" >/dev/null)
echo " packed ${name}@${VERSION}"
}

build_wrapper_pkg() {
local pkg_dir="${OUT_DIR}/wrapper"
mkdir -p "${pkg_dir}/bin"

sed -e "s|__VERSION__|${VERSION}|g" \
"${NPM_DIR}/wrapper/package.json.tmpl" > "${pkg_dir}/package.json"

cp "${NPM_DIR}/wrapper/bin/upsun.js" "${pkg_dir}/bin/upsun.js"
chmod +x "${pkg_dir}/bin/upsun.js"

cp "${NPM_DIR}/wrapper/README.md" "${pkg_dir}/README.md"

(cd "${pkg_dir}" && npm pack --pack-destination "${OUT_DIR}" >/dev/null)
echo " packed upsun@${VERSION}"
}

echo "build.sh: building platform packages"
for suffix in "${PLATFORMS[@]}"; do
build_platform_pkg "$suffix"
done

echo "build.sh: building wrapper package"
build_wrapper_pkg

echo "build.sh: done. Tarballs in ${OUT_DIR}:"
ls -1 "${OUT_DIR}"/*.tgz
Loading
Loading