Compare commits

..

1 Commits

Author SHA1 Message Date
Claude
b235d82dcd fix: resolve latest tag and use versioned tarball for v2.99.0+
From v2.99.0, the CLI release publishes only version-prefixed tarballs
(supabase_<version>_<platform>_<arch>.tar.gz); the unversioned aliases
this action used to hit at releases/latest/download/ are gone, so all
'latest' (and lockfile-less) installs were 404-ing.

Resolve 'latest' to a concrete version by following the
github.com/supabase/cli/releases/latest redirect, then pick the
versioned or unversioned filename based on the resolved version.

Fixes supabase/cli#5257.
2026-05-18 11:18:16 +00:00
9 changed files with 403 additions and 590 deletions

View File

@@ -52,7 +52,6 @@ jobs:
- uses: ./ - uses: ./
with: with:
version: ${{ matrix.version }} version: ${{ matrix.version }}
github-token: ${{ github.token }}
- run: supabase -h - run: supabase -h
ci: ci:

View File

@@ -27,7 +27,7 @@ jobs:
- name: Generate token - name: Generate token
id: app-token id: app-token
if: ${{ steps.meta.outputs.update-type == null || steps.meta.outputs.update-type == 'version-update:semver-patch' || steps.meta.outputs.update-type == 'version-update:semver-minor' }} if: ${{ steps.meta.outputs.update-type == null || steps.meta.outputs.update-type == 'version-update:semver-patch' || steps.meta.outputs.update-type == 'version-update:semver-minor' }}
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
with: with:
app-id: ${{ secrets.APP_ID }} app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}

View File

@@ -71,7 +71,7 @@ jobs:
- name: Setup Ruby - name: Setup Ruby
id: setup-ruby id: setup-ruby
if: steps.license-inputs.outputs.changed == 'true' if: steps.license-inputs.outputs.changed == 'true'
uses: ruby/setup-ruby@97ecb7b512899eb71ab1bf2310a624c6f1589ac6 # v1.308.0 uses: ruby/setup-ruby@7372622e62b60b3cb750dcd2b9e32c247ffec26a # v1.302.0
with: with:
ruby-version: ruby ruby-version: ruby
@@ -116,7 +116,7 @@ jobs:
- name: Setup Ruby - name: Setup Ruby
id: setup-ruby id: setup-ruby
uses: ruby/setup-ruby@97ecb7b512899eb71ab1bf2310a624c6f1589ac6 # v1.308.0 uses: ruby/setup-ruby@7372622e62b60b3cb750dcd2b9e32c247ffec26a # v1.302.0
with: with:
ruby-version: ruby ruby-version: ruby

View File

@@ -28,10 +28,8 @@ steps:
``` ```
If `version` is omitted, the action checks the repository root for `bun.lock`, If `version` is omitted, the action checks the repository root for `bun.lock`,
`pnpm-lock.yaml`, or `package-lock.json` and installs the declared `supabase` `pnpm-lock.yaml`, or `package-lock.json` and uses the declared `supabase`
package version through npm. If the lockfile includes package integrity version. If no supported lockfile is present, it falls back to `latest`.
metadata, the action verifies it against the npm registry before installing. If
no supported lockfile is present, it falls back to `latest`.
A specific version of the `supabase` CLI can be installed: A specific version of the `supabase` CLI can be installed:
@@ -61,9 +59,8 @@ on Windows and macOS runners.
The action supports the following inputs: The action supports the following inputs:
| Name | Type | Description | Default | Required | | Name | Type | Description | Default | Required |
| -------------- | ------ | ---------------------------------------------------------------- | --------------------------------- | -------- | | --------- | ------ | ---------------------------------- | --------------------------------- | -------- |
| `version` | String | Supabase CLI version (or `latest`) | Root lockfile version or `latest` | false | | `version` | String | Supabase CLI version (or `latest`) | Root lockfile version or `latest` | false |
| `github-token` | String | Deprecated; no longer used now that installs resolve through npm | | false |
## Advanced Usage ## Advanced Usage
@@ -165,7 +162,6 @@ steps:
- uses: ./ - uses: ./
with: with:
version: latest version: latest
github-token: ${{ github.token }}
``` ```
The CI workflow provides fast smoke coverage across GitHub-hosted runners, and The CI workflow provides fast smoke coverage across GitHub-hosted runners, and

View File

@@ -5,9 +5,6 @@ inputs:
version: version:
description: Version of Supabase CLI to install. If omitted, detect from the root lockfile and otherwise use latest. description: Version of Supabase CLI to install. If omitted, detect from the root lockfile and otherwise use latest.
required: false required: false
github-token:
description: Deprecated. The action now installs through npm and does not use GitHub release API requests.
required: false
outputs: outputs:
version: version:
description: Version of installed Supabase CLI description: Version of installed Supabase CLI
@@ -15,100 +12,19 @@ outputs:
runs: runs:
using: composite using: composite
steps: steps:
- id: bun-download
name: Resolve Bun Download URL
shell: sh
working-directory: ${{ github.action_path }}
run: |
set -eu
if [ "${RUNNER_OS}" != "Linux" ]; then
exit 0
fi
# setup-bun does not detect Linux musl yet, so Alpine-like containers need the musl asset explicitly.
is_musl=false
if [ -f /etc/alpine-release ]; then
is_musl=true
elif command -v ldd >/dev/null 2>&1 && ldd --version 2>&1 | grep -qi musl; then
is_musl=true
fi
if [ "${is_musl}" != "true" ]; then
exit 0
fi
version="$(cat .bun-version)"
case "$(uname -m)" in
x86_64) arch="x64" ;;
aarch64|arm64) arch="aarch64" ;;
*)
echo "Unsupported Linux musl architecture: $(uname -m)" >&2
exit 1
;;
esac
echo "url=https://github.com/oven-sh/bun/releases/download/bun-v${version}/bun-linux-${arch}-musl.zip" >> "$GITHUB_OUTPUT"
- name: Install Alpine Runtime Dependencies
shell: sh
run: |
set -eu
if [ "${RUNNER_OS}" != "Linux" ]; then
exit 0
fi
is_musl=false
if [ -f /etc/alpine-release ]; then
is_musl=true
elif command -v ldd >/dev/null 2>&1 && ldd --version 2>&1 | grep -qi musl; then
is_musl=true
fi
if [ "${is_musl}" != "true" ]; then
exit 0
fi
# Bun's musl binary and the Supabase CLI shim both dynamically link libstdc++ and libgcc.
if command -v apk >/dev/null 2>&1; then
missing_packages=""
for package in libstdc++ libgcc; do
if ! apk info -e "${package}" >/dev/null 2>&1; then
missing_packages="${missing_packages} ${package}"
fi
done
if [ -z "${missing_packages}" ]; then
exit 0
fi
if [ "$(id -u)" != "0" ]; then
echo "::error::Alpine/musl containers need${missing_packages} to run Supabase CLI. Add 'apk add --no-cache${missing_packages}' before supabase/setup-cli, or run this job container as root."
exit 1
fi
apk add --no-cache ${missing_packages}
exit 0
fi
echo "::error::Linux musl containers need libstdc++ and libgcc to run Supabase CLI. Install them before supabase/setup-cli."
exit 1
- name: Setup Bun - name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with: with:
bun-version-file: ${{ github.action_path }}/.bun-version bun-version-file: ${{ github.action_path }}/.bun-version
bun-download-url: ${{ steps.bun-download.outputs.url }}
- name: Install Action Dependencies - name: Install Action Dependencies
shell: sh shell: bash
working-directory: ${{ github.action_path }} working-directory: ${{ github.action_path }}
run: bun install --frozen-lockfile --production run: bun install --frozen-lockfile --production
- id: setup-cli - id: setup-cli
name: Setup Supabase CLI name: Setup Supabase CLI
shell: sh shell: bash
working-directory: ${{ github.action_path }} working-directory: ${{ github.action_path }}
env: env:
INPUT_VERSION: ${{ inputs.version }} INPUT_VERSION: ${{ inputs.version }}

113
bun.lock
View File

@@ -6,14 +6,15 @@
"name": "setup-cli", "name": "setup-cli",
"dependencies": { "dependencies": {
"@actions/core": "^3.0.1", "@actions/core": "^3.0.1",
"@actions/tool-cache": "^4.0.0",
}, },
"devDependencies": { "devDependencies": {
"@tsconfig/bun": "^1.0.10", "@tsconfig/bun": "^1.0.10",
"@types/bun": "^1.3.14", "@types/bun": "^1.3.13",
"@typescript/native-preview": "^7.0.0-dev.20260410.1", "@typescript/native-preview": "^7.0.0-dev.20260410.1",
"oxfmt": "^0.49.0", "oxfmt": "^0.46.0",
"oxlint": "^1.64.0", "oxlint": "^1.61.0",
"oxlint-tsgolint": "^0.22.1", "oxlint-tsgolint": "^0.21.1",
}, },
}, },
}, },
@@ -26,97 +27,99 @@
"@actions/io": ["@actions/io@3.0.2", "", {}, "sha512-nRBchcMM+QK1pdjO7/idu86rbJI5YHUKCvKs0KxnSYbVe3F51UfGxuZX4Qy/fWlp6l7gWFwIkrOzN+oUK03kfw=="], "@actions/io": ["@actions/io@3.0.2", "", {}, "sha512-nRBchcMM+QK1pdjO7/idu86rbJI5YHUKCvKs0KxnSYbVe3F51UfGxuZX4Qy/fWlp6l7gWFwIkrOzN+oUK03kfw=="],
"@oxfmt/binding-android-arm-eabi": ["@oxfmt/binding-android-arm-eabi@0.49.0", "", { "os": "android", "cpu": "arm" }, "sha512-HbifJ84prIh9+55CTPAU35JdRQrwg47y16cGerCC+iejSKOuHXYo2WDql6l7cQlzrYVtc3f4UWY+dBj2lRmOeA=="], "@actions/tool-cache": ["@actions/tool-cache@4.0.0", "", { "dependencies": { "@actions/core": "^3.0.0", "@actions/exec": "^3.0.0", "@actions/http-client": "^4.0.0", "@actions/io": "^3.0.0", "semver": "^7.7.3" } }, "sha512-L8P9HbXvpvqjZDveb/fdsa55IVC0trfPgQ4ZwGo6r5af6YDVdM9vMGPZ7rgY2fAT9gGj4PSYd6bYlg3p3jD78A=="],
"@oxfmt/binding-android-arm64": ["@oxfmt/binding-android-arm64@0.49.0", "", { "os": "android", "cpu": "arm64" }, "sha512-Ef7SKJqAaH2d7E6eXZZa2OffIShbhFMxnGK0zd93p4qiyTJr75B0qf7lrPD+qQOwcf04BrjYJ0JUxq8d5+yZwg=="], "@oxfmt/binding-android-arm-eabi": ["@oxfmt/binding-android-arm-eabi@0.46.0", "", { "os": "android", "cpu": "arm" }, "sha512-b1doV4WRcJU+BESSlCvCjV+5CEr/T6h0frArAdV26Nir+gGNFNaylvDiiMPfF1pxeV0txZEs38ojzJaxBYg+ng=="],
"@oxfmt/binding-darwin-arm64": ["@oxfmt/binding-darwin-arm64@0.49.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-8x5DN9CsFfb432sHa9NyqX5XisGUdA53LPEGSdv/VniS+v4uEOR8Orv7A9QSB98Xxgp0t6r31DzQA/wpIobGqQ=="], "@oxfmt/binding-android-arm64": ["@oxfmt/binding-android-arm64@0.46.0", "", { "os": "android", "cpu": "arm64" }, "sha512-v6+HhjsoV3GO0u2u9jLSAZrvWfTraDxKofUIQ7/ktS7tzS+epVsxdHmeM+XxuNcAY/nWxxU1Sg4JcGTNRXraBA=="],
"@oxfmt/binding-darwin-x64": ["@oxfmt/binding-darwin-x64@0.49.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-e0+DSVzk4ewhMVKNYDaRTmP81jNMBWR1X9al0cVKWS+hDM/dElNqD5zjTOCuLOZc4oOdp2Gx2ldrVL+yYo9TZQ=="], "@oxfmt/binding-darwin-arm64": ["@oxfmt/binding-darwin-arm64@0.46.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-3eeooJGrqGIlI5MyryDZsAcKXSmKIgAD4yYtfRrRJzXZ0UTFZtiSveIur56YPrGMYZwT4XyVhHsMqrNwr1XeFA=="],
"@oxfmt/binding-freebsd-x64": ["@oxfmt/binding-freebsd-x64@0.49.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-W+mjtYtrQvFbXT/uNT+221OBhGRZ8UqNsLxjTWsjZ4GsQnRdvRC/N2NCK86BcamWr7lsTxwpwN3PULnr78sgcQ=="], "@oxfmt/binding-darwin-x64": ["@oxfmt/binding-darwin-x64@0.46.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-QG8BDM0CXWbu84k2SKmCqfEddPQPFiBicwtYnLqHRWZZl57HbtOLRMac/KTq2NO4AEc4ICCBpFxJIV9zcqYfkQ=="],
"@oxfmt/binding-linux-arm-gnueabihf": ["@oxfmt/binding-linux-arm-gnueabihf@0.49.0", "", { "os": "linux", "cpu": "arm" }, "sha512-Rtv6UevV7czDlLqil+NZUe4d8gs8jQo/zScSpumwyf7I+fSdLc+hc8AF3MQC7ymxSMMD9+vfiqQlsIf7wOAzXA=="], "@oxfmt/binding-freebsd-x64": ["@oxfmt/binding-freebsd-x64@0.46.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-9DdCqS/n2ncu/Chazvt3cpgAjAmIGQDz7hFKSrNItMApyV/Ja9mz3hD4JakIE3nS8PW9smEbPWnb389QLBY4nw=="],
"@oxfmt/binding-linux-arm-musleabihf": ["@oxfmt/binding-linux-arm-musleabihf@0.49.0", "", { "os": "linux", "cpu": "arm" }, "sha512-sBi+8C/Q/MdKa5FL8ibAUCdhFBGFH7HFN/Qoyd5xQbZ/0ky3NMPpKfIBpaH0lhK2dXkGLczVQUoZ+xuNSerCdQ=="], "@oxfmt/binding-linux-arm-gnueabihf": ["@oxfmt/binding-linux-arm-gnueabihf@0.46.0", "", { "os": "linux", "cpu": "arm" }, "sha512-Dgs7VeE2jT0LHMhw6tPEt0xQYe54kBqHEovmWsv4FVQlegCOvlIJNx0S8n4vj8WUtpT+Z6BD2HhKJPLglLxvZg=="],
"@oxfmt/binding-linux-arm64-gnu": ["@oxfmt/binding-linux-arm64-gnu@0.49.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-JIfWenFhlzx+O8YygyZhoHFzTsdgDhxhbDRnE2iJLnnM5pWKScFvPECO2vOlA7JqJ/9S1g3uzEKuRCkHFwTjvA=="], "@oxfmt/binding-linux-arm-musleabihf": ["@oxfmt/binding-linux-arm-musleabihf@0.46.0", "", { "os": "linux", "cpu": "arm" }, "sha512-Zxn3adhTH13JKnU4xXJj8FeEfF680XjXh3gSShKl57HCMBRde2tUJTgogV/1MSHA80PJEVrDa7r66TLVq3Ia7Q=="],
"@oxfmt/binding-linux-arm64-musl": ["@oxfmt/binding-linux-arm64-musl@0.49.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-iNzkMPG18jPkwBOZ4/HEjwqfzAjq4RrUQ0CgId/fC1ENvYD5jLVAaU/gWgpiqP1ys07kxSsSggDd1fp3E7mQHw=="], "@oxfmt/binding-linux-arm64-gnu": ["@oxfmt/binding-linux-arm64-gnu@0.46.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-+TWipjrgVM8D7aIdDD0tlr3teLTTvQTn7QTE5BpT10H1Fj82gfdn9X6nn2sDgx/MepuSCfSnzFNJq2paLL0OiA=="],
"@oxfmt/binding-linux-ppc64-gnu": ["@oxfmt/binding-linux-ppc64-gnu@0.49.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-BPHA/NN3LvoIXiid+iz3BHt5V0Rzx0tXAqRUovwE1NsbDaLG9e8mtv7evDGRIkVQacqTDBv0XL25THHsxSJosQ=="], "@oxfmt/binding-linux-arm64-musl": ["@oxfmt/binding-linux-arm64-musl@0.46.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-aAUPBWJ1lGwwnxZUEDLJ94+Iy6MuwJwPxUgO4sCA5mEEyDk7b+cDQ+JpX1VR150Zoyd+D49gsrUzpUK5h587Eg=="],
"@oxfmt/binding-linux-riscv64-gnu": ["@oxfmt/binding-linux-riscv64-gnu@0.49.0", "", { "os": "linux", "cpu": "none" }, "sha512-3Eroshe+s69htC9JIL0+zLGQczLtRKezkMhwqQC21VC5Z/fuLvzLfbAOLgJLUq601H8gDYjy7deYycfOBjCvWg=="], "@oxfmt/binding-linux-ppc64-gnu": ["@oxfmt/binding-linux-ppc64-gnu@0.46.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-ufBCJukyFX/UDrokP/r6BGDoTInnsDs7bxyzKAgMiZlt2Qu8GPJSJ6Zm6whIiJzKk0naxA8ilwmbO1LMw6Htxw=="],
"@oxfmt/binding-linux-riscv64-musl": ["@oxfmt/binding-linux-riscv64-musl@0.49.0", "", { "os": "linux", "cpu": "none" }, "sha512-fnaERGgsxGm0lKAmO72EYR4BA3qBnzBTJBTi6EtUMq1D4R7EexRBMU4voXnx4TXla3SEDl9x4uNp/18SbkPjGg=="], "@oxfmt/binding-linux-riscv64-gnu": ["@oxfmt/binding-linux-riscv64-gnu@0.46.0", "", { "os": "linux", "cpu": "none" }, "sha512-eqtlC2YmPqjun76R1gVfGLuKWx7NuEnLEAudZ7n6ipSKbCZTqIKSs1b5Y8K/JHZsRpLkeSmAAjig5HOIg8fQzQ=="],
"@oxfmt/binding-linux-s390x-gnu": ["@oxfmt/binding-linux-s390x-gnu@0.49.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-rBwasMl1Uul1MCCeTGEFKnOTL7VUxHf+634jWStrQAbzpBJgd5Yz5m4F7exVCsoI8PHn57dNjssXagXLCLB5yA=="], "@oxfmt/binding-linux-riscv64-musl": ["@oxfmt/binding-linux-riscv64-musl@0.46.0", "", { "os": "linux", "cpu": "none" }, "sha512-yccVOO2nMXkQLGgy0He3EQEwKD7NF0zEk+/OWmroznkqXyJdN6bfK0LtNnr6/14Bh3FjpYq7bP33l/VloCnxpA=="],
"@oxfmt/binding-linux-x64-gnu": ["@oxfmt/binding-linux-x64-gnu@0.49.0", "", { "os": "linux", "cpu": "x64" }, "sha512-BoC/F9xHe2y/deuBGA5Aw7bes07OD2gcL2wlpzTrfImR92vPP7S/k3LBTyspQZCNIVNdagkELcqKELwMLGIfAg=="], "@oxfmt/binding-linux-s390x-gnu": ["@oxfmt/binding-linux-s390x-gnu@0.46.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-aAf7fG23OQCey6VRPj9IeCraoYtpgtx0ZyJ1CXkPyT1wjzBE7c3xtuxHe/AdHaJfVVb/SXpSk8Gl1LzyQupSqw=="],
"@oxfmt/binding-linux-x64-musl": ["@oxfmt/binding-linux-x64-musl@0.49.0", "", { "os": "linux", "cpu": "x64" }, "sha512-umY6jFADAo/oztFKl8D/S6vSrG6oBpEskcentiRuz42kZVU2kfDXMWCYavxyZR2bwPjqkHpcHZ6EZFiH3Qj9ZA=="], "@oxfmt/binding-linux-x64-gnu": ["@oxfmt/binding-linux-x64-gnu@0.46.0", "", { "os": "linux", "cpu": "x64" }, "sha512-q0JPsTMyJNjYrBvYFDz4WbVsafNZaPCZv4RnFypRotLqpKROtBZcEaXQW4eb9YmvLU3NckVemLJnzkSZSdmOxw=="],
"@oxfmt/binding-openharmony-arm64": ["@oxfmt/binding-openharmony-arm64@0.49.0", "", { "os": "none", "cpu": "arm64" }, "sha512-J85zQMiw2pXiGPK+OusmDvSnJ/dgpgN7VgmB2zOBtgS8F+nsOUfSg9ZEBrwbQscjZ7tkPbm38CG4VF5f53MsiA=="], "@oxfmt/binding-linux-x64-musl": ["@oxfmt/binding-linux-x64-musl@0.46.0", "", { "os": "linux", "cpu": "x64" }, "sha512-7LsLY9Cw57GPkhSR+duI3mt9baRczK/DtHYSldQ4BEU92da9igBQNl4z7Vq5U9NNPsh1FmpKvv1q9WDtiUQR1A=="],
"@oxfmt/binding-win32-arm64-msvc": ["@oxfmt/binding-win32-arm64-msvc@0.49.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-38K67XR++CoFFORDd4sMFwUVAnD6msYBdGTei+qvKGrRPO6S2PbrYPNL/eQQ1RgnnxOegNba0YQwg6uRkNcw6A=="], "@oxfmt/binding-openharmony-arm64": ["@oxfmt/binding-openharmony-arm64@0.46.0", "", { "os": "none", "cpu": "arm64" }, "sha512-lHiBOz8Duaku7JtRNLlps3j++eOaICPZSd8FCVmTDM4DFOPT71Bjn7g6iar1z7StXlKRweUKxWUs4sA+zWGDXg=="],
"@oxfmt/binding-win32-ia32-msvc": ["@oxfmt/binding-win32-ia32-msvc@0.49.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-rXVe0HICwQF0dBgbQtBCoYf8x/SidPIdhyQl+iPuJlV7suV+qDv7yUEB3wQ4qC3nOeNxz287SwFXKzyr0kWgEg=="], "@oxfmt/binding-win32-arm64-msvc": ["@oxfmt/binding-win32-arm64-msvc@0.46.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-/5ktYUliP89RhgC37DBH1x20U5zPSZMy3cMEcO0j3793rbHP9MWsknBwQB6eozRzWmYrh0IFM/p20EbPvDlYlg=="],
"@oxfmt/binding-win32-x64-msvc": ["@oxfmt/binding-win32-x64-msvc@0.49.0", "", { "os": "win32", "cpu": "x64" }, "sha512-gwWLwSEmBBfIK/Wh7GGd658161o4RKAvHWRaRQbJm571iQXGKfyr7UKsI1vsWvDlNLc30CxJDc8mMmCvJ/kczQ=="], "@oxfmt/binding-win32-ia32-msvc": ["@oxfmt/binding-win32-ia32-msvc@0.46.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-3WTnoiuIr8XvV0DIY7SN+1uJSwKf4sPpcbHfobcRT9JutGcLaef/miyBB87jxd3aqH+mS0+G5lsgHuXLUwjjpQ=="],
"@oxlint-tsgolint/darwin-arm64": ["@oxlint-tsgolint/darwin-arm64@0.22.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-4150Lpgc1YM09GcjA6GSrra1JoPjC7aOpfywLjWEY4vW0Sd1qKzqHF1WRaiw0/qUZ40OATYdv3aRd7ipPkWQbw=="], "@oxfmt/binding-win32-x64-msvc": ["@oxfmt/binding-win32-x64-msvc@0.46.0", "", { "os": "win32", "cpu": "x64" }, "sha512-IXxiQpkYnOwNfP23vzwSfhdpxJzyiPTY7eTn6dn3DsriKddESzM8i6kfq9R7CD/PUJwCvQT22NgtygBeug3KoA=="],
"@oxlint-tsgolint/darwin-x64": ["@oxlint-tsgolint/darwin-x64@0.22.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-vFWcPWYOgZs4HWcgS1EjUZg33NLcNfEYU49KGImmCfZWkflENrmBYV4HN/C0YeAPum6ZZ/goPSvQrB/cOD+NfA=="], "@oxlint-tsgolint/darwin-arm64": ["@oxlint-tsgolint/darwin-arm64@0.21.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-7TLjyWe4wG9saJc992VWmaHq2hwKfOEEVTjheReXJXaDhavMZI4X9a6nKhbEng4IVkYtzjD2jw16vw2WFXLYLw=="],
"@oxlint-tsgolint/linux-arm64": ["@oxlint-tsgolint/linux-arm64@0.22.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-6LiUpP0Zir3+29FvBm7Y28q/dBjSHqTZ5MhG1Ckw4fGhI4cAvbcwXaKvbjx1TP7rRmBNOoq/M5xdpHjTb+GAew=="], "@oxlint-tsgolint/darwin-x64": ["@oxlint-tsgolint/darwin-x64@0.21.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-7wf9Wf75nTzA7zpL9myhFe2RKvfuqGUOADNvUooCjEWvh7hmPz3lSEqTMh5Z/VQhzsG04mM9ACyghxhRzq7zFw=="],
"@oxlint-tsgolint/linux-x64": ["@oxlint-tsgolint/linux-x64@0.22.1", "", { "os": "linux", "cpu": "x64" }, "sha512-fuX1hEQfpHauUbXADsfqVhRzrUrGabzGXbj5wsp2vKhV5uk/Rze8Mba9GdjFGECzvXudMGqHqxB4r6jGRdhxVA=="], "@oxlint-tsgolint/linux-arm64": ["@oxlint-tsgolint/linux-arm64@0.21.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-IPuQN/Vd0Rjklg/cCGBbQyUuRBp2f6LQXpZYwk5ivOR6V/+CgiYsv8pn/PVY7gjeyoNvPQrXB7xMjHUO2YZbdw=="],
"@oxlint-tsgolint/win32-arm64": ["@oxlint-tsgolint/win32-arm64@0.22.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SZidAj+jrbZf9ZjBEYW0tiNZ+KasqB2zgW26qdiPpQSF/DzURnPmXz651IeA9YsmbVdHGIooEHUmev6QJdquA=="], "@oxlint-tsgolint/linux-x64": ["@oxlint-tsgolint/linux-x64@0.21.1", "", { "os": "linux", "cpu": "x64" }, "sha512-d1niGuTbh2qiv7dR7tqkbOcM5cIR63of0lMBFdEQavL1KrJV8zuRdwdi68K7MNGdgoR+J5A9ajpGGvsHwp1bPg=="],
"@oxlint-tsgolint/win32-x64": ["@oxlint-tsgolint/win32-x64@0.22.1", "", { "os": "win32", "cpu": "x64" }, "sha512-QweSk9H5lFh5Y+WUf2Kq/OAN88V6+62ZwGhP38gqdRotI90luXSMkruFTj7Q2rYrzH4ZVNaSqx7NY8JpSfIzqg=="], "@oxlint-tsgolint/win32-arm64": ["@oxlint-tsgolint/win32-arm64@0.21.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-ICu9y2JLnFPvFqstnWPPNqBM8LK8BWw2OTeaR0UgEMm4hOSbrZAKv1/hwZYyiLqnCNjBL87AGSQIgTHCYlsipw=="],
"@oxlint/binding-android-arm-eabi": ["@oxlint/binding-android-arm-eabi@1.64.0", "", { "os": "android", "cpu": "arm" }, "sha512-2r6Nq3XXGLHEXKkSj8JtmJ6N4gDw431DPFOg0ZoJHlNjnG6HVMm/ksQ10m0HJ8WBvwgMe1L50UHPaYZutCRPCw=="], "@oxlint-tsgolint/win32-x64": ["@oxlint-tsgolint/win32-x64@0.21.1", "", { "os": "win32", "cpu": "x64" }, "sha512-cTEFCFjCj6iXfrSHcvajSPNqhEA4TxSzU3gFxbdGSAUTNXGToU99IbdhWAPSbhcucoym0XE4Zl7E41NiSkNTug=="],
"@oxlint/binding-android-arm64": ["@oxlint/binding-android-arm64@1.64.0", "", { "os": "android", "cpu": "arm64" }, "sha512-ePJMpePgg7fBv+L/hVx1xXRU5/5gd5m0obLA6hPEfLXF3GjpR8idIDbY1dhQYhyz1ms2wdTccSboo6KEd2Oxtg=="], "@oxlint/binding-android-arm-eabi": ["@oxlint/binding-android-arm-eabi@1.61.0", "", { "os": "android", "cpu": "arm" }, "sha512-6eZBPgiigK5txqoVgRqxbaxiom4lM8AP8CyKPPvpzKnQ3iFRFOIDc+0AapF+qsUSwjOzr5SGk4SxQDpQhkSJMQ=="],
"@oxlint/binding-darwin-arm64": ["@oxlint/binding-darwin-arm64@1.64.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-U4DMLQd10gJLuoSTLSGbfv3bGjTlUNsScm9Dgb8wwBqmCzidf1pE1pXV4doGNxqwH3KtVng1AGTINA0NvkGLvQ=="], "@oxlint/binding-android-arm64": ["@oxlint/binding-android-arm64@1.61.0", "", { "os": "android", "cpu": "arm64" }, "sha512-CkwLR69MUnyv5wjzebvbbtTSUwqLxM35CXE79bHqDIK+NtKmPEUpStTcLQRZMCo4MP0qRT6TXIQVpK0ZVScnMA=="],
"@oxlint/binding-darwin-x64": ["@oxlint/binding-darwin-x64@1.64.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-GoRIL48QWm4/TAvjN8pB1nAG+1/uqc9EdnWT9zqHeb6wsmjZtywj8VRe5aGW47Fdb64YtLOsdLqVxOvQuz98Wg=="], "@oxlint/binding-darwin-arm64": ["@oxlint/binding-darwin-arm64@1.61.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-8JbefTkbmvqkqWjmQrHke+MdpgT2UghhD/ktM4FOQSpGeCgbMToJEKdl9zwhr/YWTl92i4QI1KiTwVExpcUN8A=="],
"@oxlint/binding-freebsd-x64": ["@oxlint/binding-freebsd-x64@1.64.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-5dFkv4tkg7PxJJGS9/OjrJwjhuHczrd3OQOkRE0wHcLM+ncUnULtzEPWjqGOxTXxZnLWcB91bGiIznx89TVXyQ=="], "@oxlint/binding-darwin-x64": ["@oxlint/binding-darwin-x64@1.61.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-uWpoxDT47hTnDLcdEh5jVbso8rlTTu5o0zuqa9J8E0JAKmIWn7kGFEIB03Pycn2hd2vKxybPGLhjURy/9We5FQ=="],
"@oxlint/binding-linux-arm-gnueabihf": ["@oxlint/binding-linux-arm-gnueabihf@1.64.0", "", { "os": "linux", "cpu": "arm" }, "sha512-jsBqMLl/uOL5+Kq/+BtK9FrmiNGUbx8SiyZXv+WlUxA45KuwcLu9BfiSIL3I3DBDgWM3yZizDITnTK9BcqNBQg=="], "@oxlint/binding-freebsd-x64": ["@oxlint/binding-freebsd-x64@1.61.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-K/o4hEyW7flfMel0iBVznmMBt7VIMHGdjADocHKpK1DUF9erpWnJ+BSSWd2W0c8K3mPtpph+CuHzRU6CI3l9jQ=="],
"@oxlint/binding-linux-arm-musleabihf": ["@oxlint/binding-linux-arm-musleabihf@1.64.0", "", { "os": "linux", "cpu": "arm" }, "sha512-1lrj8At/Uuc9GhjrVFBQo0NEjfBrTkzpmtHIGAhNnIXqn1CAyGL+qrztUsXb2GIluJrpl9Q7qRLJOb/NqydacQ=="], "@oxlint/binding-linux-arm-gnueabihf": ["@oxlint/binding-linux-arm-gnueabihf@1.61.0", "", { "os": "linux", "cpu": "arm" }, "sha512-P6040ZkcyweJ0Po9yEFqJCdvZnf3VNCGs1SIHgXDf8AAQNC6ID/heXQs9iSgo2FH7gKaKq32VWc59XZwL34C5Q=="],
"@oxlint/binding-linux-arm64-gnu": ["@oxlint/binding-linux-arm64-gnu@1.64.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-HpSQbubwh03mMhAdy2BYtad/fsY8vDFHDAb6bUwuCYg2VD3xCQgn6ArKcO0oZyLCheacKTv4PrF3Mfu5hgoE2g=="], "@oxlint/binding-linux-arm-musleabihf": ["@oxlint/binding-linux-arm-musleabihf@1.61.0", "", { "os": "linux", "cpu": "arm" }, "sha512-bwxrGCzTZkuB+THv2TQ1aTkVEfv5oz8sl+0XZZCpoYzErJD8OhPQOTA0ENPd1zJz8QsVdSzSrS2umKtPq4/JXg=="],
"@oxlint/binding-linux-arm64-musl": ["@oxlint/binding-linux-arm64-musl@1.64.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-00QQ0h0Y7u0G69BgiH3+ky2aaq/QvkDL6DYok8htIuJHxybiux5aQ8jwmg8qIk9wha6UagUP2BAwAzbemcJbpg=="], "@oxlint/binding-linux-arm64-gnu": ["@oxlint/binding-linux-arm64-gnu@1.61.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-vkhb9/wKguMkLlrm3FoJW/Xmdv31GgYAE+x8lxxQ+7HeOxXUySI0q36a3NTVIuQUdLzxCI1zzMGsk1o37FOe3w=="],
"@oxlint/binding-linux-ppc64-gnu": ["@oxlint/binding-linux-ppc64-gnu@1.64.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-2GaimTV6EMW+s5HS0An3oGbQme3BgHswvfVdGk3EB57Xe9+/gyT+Qd7lNVzb3rtir52vbIPzXfaYArzs5b5zcw=="], "@oxlint/binding-linux-arm64-musl": ["@oxlint/binding-linux-arm64-musl@1.61.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-bl1dQh8LnVqsj6oOQAcxwbuOmNJkwc4p6o//HTBZhNTzJy21TLDwAviMqUFNUxDHkPGpmdKTSN4tWTjLryP8xg=="],
"@oxlint/binding-linux-riscv64-gnu": ["@oxlint/binding-linux-riscv64-gnu@1.64.0", "", { "os": "linux", "cpu": "none" }, "sha512-H46AtFb9wypjoVwGdlxrm0DsD809NGmtiK9HiyPKTxkSte2YjhC4S+00rOIrwCaxcyPiGid3Y3OMXp5KMAkGZw=="], "@oxlint/binding-linux-ppc64-gnu": ["@oxlint/binding-linux-ppc64-gnu@1.61.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-QoOX6KB2IiEpyOj/HKqaxi+NQHPnOgNgnr22n9N4ANJCzXkUlj1UmeAbFb4PpqdlHIzvGDM5xZ0OKtcLq9RhiQ=="],
"@oxlint/binding-linux-riscv64-musl": ["@oxlint/binding-linux-riscv64-musl@1.64.0", "", { "os": "linux", "cpu": "none" }, "sha512-HEgsidjjvvyzdg82icYkuFCf7REDV7B9JFwbIMbVwrKLBY0MrXX+bku3POn/hduZ2yW91IyVDUMq0Bf02KwXQw=="], "@oxlint/binding-linux-riscv64-gnu": ["@oxlint/binding-linux-riscv64-gnu@1.61.0", "", { "os": "linux", "cpu": "none" }, "sha512-1TGcTerjY6p152wCof3oKElccq3xHljS/Mucp04gV/4ATpP6nO7YNnp7opEg6SHkv2a57/b4b8Ndm9znJ1/qAw=="],
"@oxlint/binding-linux-s390x-gnu": ["@oxlint/binding-linux-s390x-gnu@1.64.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-Axvm8qryotmKN00P5w4JapaSjvP2LOSbdbBJiX+2SuHd3QzhW7TUc8skqgw+ahQZ5DmzEYeHCqauvW8f32Ns6Q=="], "@oxlint/binding-linux-riscv64-musl": ["@oxlint/binding-linux-riscv64-musl@1.61.0", "", { "os": "linux", "cpu": "none" }, "sha512-65wXEmZIrX2ADwC8i/qFL4EWLSbeuBpAm3suuX1vu4IQkKd+wLT/HU/BOl84kp91u2SxPkPDyQgu4yrqp8vwVA=="],
"@oxlint/binding-linux-x64-gnu": ["@oxlint/binding-linux-x64-gnu@1.64.0", "", { "os": "linux", "cpu": "x64" }, "sha512-cR60vSd7+m+KRZ3GQGfDxWwahW5RMXg0qlGvAluZr0fTUYvw0H9N9AXAF/M/PMqgytyqvVNmBAkJG9l7U30Y1g=="], "@oxlint/binding-linux-s390x-gnu": ["@oxlint/binding-linux-s390x-gnu@1.61.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-TVvhgMvor7Qa6COeXxCJ7ENOM+lcAOGsQ0iUdPSCv2hxb9qSHLQ4XF1h50S6RE1gBOJ0WV3rNukg4JJJP1LWRA=="],
"@oxlint/binding-linux-x64-musl": ["@oxlint/binding-linux-x64-musl@1.64.0", "", { "os": "linux", "cpu": "x64" }, "sha512-2u/aPZ9pEg7HnvZPDsHxUGNnrpr4qaHi+mCgLgpt+LYRzPrS4Px4wPfkIdRdr2GvKnaYyt+XSlto0Vm5sbStTg=="], "@oxlint/binding-linux-x64-gnu": ["@oxlint/binding-linux-x64-gnu@1.61.0", "", { "os": "linux", "cpu": "x64" }, "sha512-SjpS5uYuFoDnDdZPwZE59ndF95AsY47R5MliuneTWR1pDm2CxGJaYXbKULI71t5TVfLQUWmrHEGRL9xvuq6dnA=="],
"@oxlint/binding-openharmony-arm64": ["@oxlint/binding-openharmony-arm64@1.64.0", "", { "os": "none", "cpu": "arm64" }, "sha512-kfhkGfCdoXLSxEkrhDlJrvBYajGmq+ma4EMc53dsOWTq+rIBOlI0vTBmpZNnM5oH2LY/K/w1HAK+UQEgjgpVUg=="], "@oxlint/binding-linux-x64-musl": ["@oxlint/binding-linux-x64-musl@1.61.0", "", { "os": "linux", "cpu": "x64" }, "sha512-gGfAeGD4sNJGILZbc/yKcIimO9wQnPMoYp9swAaKeEtwsSQAbU+rsdQze5SBtIP6j0QDzeYd4XSSUCRCF+LIeQ=="],
"@oxlint/binding-win32-arm64-msvc": ["@oxlint/binding-win32-arm64-msvc@1.64.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-r/cNKBFieONoVu2bb1KkVouq9W+edDUgHumXJGphCRRj+U0xaD4nanrw8ZOqo0IsutPkEM4vCcGBpak6x5aXMg=="], "@oxlint/binding-openharmony-arm64": ["@oxlint/binding-openharmony-arm64@1.61.0", "", { "os": "none", "cpu": "arm64" }, "sha512-OlVT0LrG/ct33EVtWRyR+B/othwmDWeRxfi13wUdPeb3lAT5TgTcFDcfLfarZtzB4W1nWF/zICMgYdkggX2WmQ=="],
"@oxlint/binding-win32-ia32-msvc": ["@oxlint/binding-win32-ia32-msvc@1.64.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-tUw0xUUwEFVZbpJoeCblkv8SJA4Xz3CdXCJbAnBsiNLyxDrk2tLcxEAS6M73Q7hHHDg3OtwI8vZVK3t5RJt4Gw=="], "@oxlint/binding-win32-arm64-msvc": ["@oxlint/binding-win32-arm64-msvc@1.61.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-vI//NZPJk6DToiovPtaiwD4iQ7kO1r5ReWQD0sOOyKRtP3E2f6jxin4uvwi3OvDzHA2EFfd7DcZl5dtkQh7g1w=="],
"@oxlint/binding-win32-x64-msvc": ["@oxlint/binding-win32-x64-msvc@1.64.0", "", { "os": "win32", "cpu": "x64" }, "sha512-9CBR+LO0JVST87fNTzzNxS5I29jIUO5gxT9i9+M3SDHHALElj9sY1Prf12tad3vIRC6OD7Ehtvvh+sn13vSwHw=="], "@oxlint/binding-win32-ia32-msvc": ["@oxlint/binding-win32-ia32-msvc@1.61.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-0ySj4/4zd2XjePs3XAQq7IigIstN4LPQZgCyigX5/ERMLjdWAJfnxcTsrtxZxuij8guJW8foXuHmhGxW0H4dDA=="],
"@oxlint/binding-win32-x64-msvc": ["@oxlint/binding-win32-x64-msvc@1.61.0", "", { "os": "win32", "cpu": "x64" }, "sha512-0xgSiyeqDLDZxXoe9CVJrOx3TUVsfyoOY7cNi03JbItNcC9WCZqrSNdrAbHONxhSPaVh/lzfnDcON1RqSUMhHw=="],
"@tsconfig/bun": ["@tsconfig/bun@1.0.10", "", {}, "sha512-5AV5YknQjNyoYzZ/8NG0dawqew/wH+x7ANiCfCIn29qo0cdbd1EryvFD1k5NSZWLBMOI/fGqMIaxi58GPIP9Cg=="], "@tsconfig/bun": ["@tsconfig/bun@1.0.10", "", {}, "sha512-5AV5YknQjNyoYzZ/8NG0dawqew/wH+x7ANiCfCIn29qo0cdbd1EryvFD1k5NSZWLBMOI/fGqMIaxi58GPIP9Cg=="],
"@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="], "@types/bun": ["@types/bun@1.3.13", "", { "dependencies": { "bun-types": "1.3.13" } }, "sha512-9fqXWk5YIHGGnUau9TEi+qdlTYDAnOj+xLCmSTwXfAIqXr2x4tytJb43E9uCvt09zJURKXwAtkoH4nLQfzeTXw=="],
"@types/node": ["@types/node@20.19.37", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw=="], "@types/node": ["@types/node@20.19.37", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw=="],
@@ -136,13 +139,15 @@
"@typescript/native-preview-win32-x64": ["@typescript/native-preview-win32-x64@7.0.0-dev.20260410.1", "", { "os": "win32", "cpu": "x64" }, "sha512-dMFT4tdHBe2vVA2WPQMjorT+fzCURRtillevQzz8/bwCEz2uXSnpu4oLRLS5045ppGE0wCFELE+Hq5z2oRddDw=="], "@typescript/native-preview-win32-x64": ["@typescript/native-preview-win32-x64@7.0.0-dev.20260410.1", "", { "os": "win32", "cpu": "x64" }, "sha512-dMFT4tdHBe2vVA2WPQMjorT+fzCURRtillevQzz8/bwCEz2uXSnpu4oLRLS5045ppGE0wCFELE+Hq5z2oRddDw=="],
"bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="], "bun-types": ["bun-types@1.3.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-QXKeHLlOLqQX9LgYaHJfzdBaV21T63HhFJnvuRCcjZiaUDpbs5ED1MgxbMra71CsryN/1dAoXuJJJwIv/2drVA=="],
"oxfmt": ["oxfmt@0.49.0", "", { "dependencies": { "tinypool": "2.1.0" }, "optionalDependencies": { "@oxfmt/binding-android-arm-eabi": "0.49.0", "@oxfmt/binding-android-arm64": "0.49.0", "@oxfmt/binding-darwin-arm64": "0.49.0", "@oxfmt/binding-darwin-x64": "0.49.0", "@oxfmt/binding-freebsd-x64": "0.49.0", "@oxfmt/binding-linux-arm-gnueabihf": "0.49.0", "@oxfmt/binding-linux-arm-musleabihf": "0.49.0", "@oxfmt/binding-linux-arm64-gnu": "0.49.0", "@oxfmt/binding-linux-arm64-musl": "0.49.0", "@oxfmt/binding-linux-ppc64-gnu": "0.49.0", "@oxfmt/binding-linux-riscv64-gnu": "0.49.0", "@oxfmt/binding-linux-riscv64-musl": "0.49.0", "@oxfmt/binding-linux-s390x-gnu": "0.49.0", "@oxfmt/binding-linux-x64-gnu": "0.49.0", "@oxfmt/binding-linux-x64-musl": "0.49.0", "@oxfmt/binding-openharmony-arm64": "0.49.0", "@oxfmt/binding-win32-arm64-msvc": "0.49.0", "@oxfmt/binding-win32-ia32-msvc": "0.49.0", "@oxfmt/binding-win32-x64-msvc": "0.49.0" }, "peerDependencies": { "svelte": "^5.0.0" }, "optionalPeers": ["svelte"], "bin": { "oxfmt": "bin/oxfmt" } }, "sha512-IAHFMdlJSWe+oAr65dx22UvjCtV9DBMisAuLnKpDqMQrctzCkGnj3QRwNHm0d+uwSWPalsDF8ZYLz9rh6nH2IQ=="], "oxfmt": ["oxfmt@0.46.0", "", { "dependencies": { "tinypool": "2.1.0" }, "optionalDependencies": { "@oxfmt/binding-android-arm-eabi": "0.46.0", "@oxfmt/binding-android-arm64": "0.46.0", "@oxfmt/binding-darwin-arm64": "0.46.0", "@oxfmt/binding-darwin-x64": "0.46.0", "@oxfmt/binding-freebsd-x64": "0.46.0", "@oxfmt/binding-linux-arm-gnueabihf": "0.46.0", "@oxfmt/binding-linux-arm-musleabihf": "0.46.0", "@oxfmt/binding-linux-arm64-gnu": "0.46.0", "@oxfmt/binding-linux-arm64-musl": "0.46.0", "@oxfmt/binding-linux-ppc64-gnu": "0.46.0", "@oxfmt/binding-linux-riscv64-gnu": "0.46.0", "@oxfmt/binding-linux-riscv64-musl": "0.46.0", "@oxfmt/binding-linux-s390x-gnu": "0.46.0", "@oxfmt/binding-linux-x64-gnu": "0.46.0", "@oxfmt/binding-linux-x64-musl": "0.46.0", "@oxfmt/binding-openharmony-arm64": "0.46.0", "@oxfmt/binding-win32-arm64-msvc": "0.46.0", "@oxfmt/binding-win32-ia32-msvc": "0.46.0", "@oxfmt/binding-win32-x64-msvc": "0.46.0" }, "bin": { "oxfmt": "bin/oxfmt" } }, "sha512-CopwJOwPAjZ9p76fCvz+mSOJTw9/NY3cSksZK3VO/bUQ8UoEcketNgUuYS0UB3p+R9XnXe7wGGXUmyFxc7QxJA=="],
"oxlint": ["oxlint@1.64.0", "", { "optionalDependencies": { "@oxlint/binding-android-arm-eabi": "1.64.0", "@oxlint/binding-android-arm64": "1.64.0", "@oxlint/binding-darwin-arm64": "1.64.0", "@oxlint/binding-darwin-x64": "1.64.0", "@oxlint/binding-freebsd-x64": "1.64.0", "@oxlint/binding-linux-arm-gnueabihf": "1.64.0", "@oxlint/binding-linux-arm-musleabihf": "1.64.0", "@oxlint/binding-linux-arm64-gnu": "1.64.0", "@oxlint/binding-linux-arm64-musl": "1.64.0", "@oxlint/binding-linux-ppc64-gnu": "1.64.0", "@oxlint/binding-linux-riscv64-gnu": "1.64.0", "@oxlint/binding-linux-riscv64-musl": "1.64.0", "@oxlint/binding-linux-s390x-gnu": "1.64.0", "@oxlint/binding-linux-x64-gnu": "1.64.0", "@oxlint/binding-linux-x64-musl": "1.64.0", "@oxlint/binding-openharmony-arm64": "1.64.0", "@oxlint/binding-win32-arm64-msvc": "1.64.0", "@oxlint/binding-win32-ia32-msvc": "1.64.0", "@oxlint/binding-win32-x64-msvc": "1.64.0" }, "peerDependencies": { "oxlint-tsgolint": ">=0.22.1" }, "optionalPeers": ["oxlint-tsgolint"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-Star3SNpWPeWFPw7kRXIhXUSn6fdiAl25q15CQzH/9WaOtG6e9CWTc25vNZOCr4PE1yEP1GtKJKIKglhj3OmEQ=="], "oxlint": ["oxlint@1.61.0", "", { "optionalDependencies": { "@oxlint/binding-android-arm-eabi": "1.61.0", "@oxlint/binding-android-arm64": "1.61.0", "@oxlint/binding-darwin-arm64": "1.61.0", "@oxlint/binding-darwin-x64": "1.61.0", "@oxlint/binding-freebsd-x64": "1.61.0", "@oxlint/binding-linux-arm-gnueabihf": "1.61.0", "@oxlint/binding-linux-arm-musleabihf": "1.61.0", "@oxlint/binding-linux-arm64-gnu": "1.61.0", "@oxlint/binding-linux-arm64-musl": "1.61.0", "@oxlint/binding-linux-ppc64-gnu": "1.61.0", "@oxlint/binding-linux-riscv64-gnu": "1.61.0", "@oxlint/binding-linux-riscv64-musl": "1.61.0", "@oxlint/binding-linux-s390x-gnu": "1.61.0", "@oxlint/binding-linux-x64-gnu": "1.61.0", "@oxlint/binding-linux-x64-musl": "1.61.0", "@oxlint/binding-openharmony-arm64": "1.61.0", "@oxlint/binding-win32-arm64-msvc": "1.61.0", "@oxlint/binding-win32-ia32-msvc": "1.61.0", "@oxlint/binding-win32-x64-msvc": "1.61.0" }, "peerDependencies": { "oxlint-tsgolint": ">=0.18.0" }, "optionalPeers": ["oxlint-tsgolint"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-ZC0ALuhDZ6ivOFG+sy0D0pEDN49EvsId98zVlmYdkcXHsEM14m/qTNUEsUpiFiCVbpIxYtVBmmLE87nsbUHohQ=="],
"oxlint-tsgolint": ["oxlint-tsgolint@0.22.1", "", { "optionalDependencies": { "@oxlint-tsgolint/darwin-arm64": "0.22.1", "@oxlint-tsgolint/darwin-x64": "0.22.1", "@oxlint-tsgolint/linux-arm64": "0.22.1", "@oxlint-tsgolint/linux-x64": "0.22.1", "@oxlint-tsgolint/win32-arm64": "0.22.1", "@oxlint-tsgolint/win32-x64": "0.22.1" }, "bin": { "tsgolint": "bin/tsgolint.js" } }, "sha512-YUSGSLUnoolsu8gxISEDio3q1rtsCozwfOzASUn3DT2mR2EeQ93uEEnen7s+6LpF+lyTQFln1pQfqwBh/fsVEg=="], "oxlint-tsgolint": ["oxlint-tsgolint@0.21.1", "", { "optionalDependencies": { "@oxlint-tsgolint/darwin-arm64": "0.21.1", "@oxlint-tsgolint/darwin-x64": "0.21.1", "@oxlint-tsgolint/linux-arm64": "0.21.1", "@oxlint-tsgolint/linux-x64": "0.21.1", "@oxlint-tsgolint/win32-arm64": "0.21.1", "@oxlint-tsgolint/win32-x64": "0.21.1" }, "bin": { "tsgolint": "bin/tsgolint.js" } }, "sha512-O2hxiT14C2HJkwzBU6CQBFPoagSd/IcV+Tt3e3UUaXFwbW4BO5DSDPSSboc3UM5MIDY+MLyepvtQwBQafNxWdw=="],
"semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
"tinypool": ["tinypool@2.1.0", "", {}, "sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw=="], "tinypool": ["tinypool@2.1.0", "", {}, "sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw=="],
@@ -151,5 +156,7 @@
"undici": ["undici@6.24.1", "", {}, "sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA=="], "undici": ["undici@6.24.1", "", {}, "sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA=="],
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
"@actions/tool-cache/@actions/core": ["@actions/core@3.0.0", "", { "dependencies": { "@actions/exec": "^3.0.0", "@actions/http-client": "^4.0.0" } }, "sha512-zYt6cz+ivnTmiT/ksRVriMBOiuoUpDCJJlZ5KPl2/FRdvwU3f7MPh9qftvbkXJThragzUZieit2nyHUyw53Seg=="],
} }
} }

View File

@@ -24,15 +24,16 @@
"typecheck": "bun x tsgo -p tsconfig.json --noEmit" "typecheck": "bun x tsgo -p tsconfig.json --noEmit"
}, },
"dependencies": { "dependencies": {
"@actions/core": "^3.0.1" "@actions/core": "^3.0.1",
"@actions/tool-cache": "^4.0.0"
}, },
"devDependencies": { "devDependencies": {
"@tsconfig/bun": "^1.0.10", "@tsconfig/bun": "^1.0.10",
"@types/bun": "^1.3.14", "@types/bun": "^1.3.13",
"@typescript/native-preview": "^7.0.0-dev.20260410.1", "@typescript/native-preview": "^7.0.0-dev.20260410.1",
"oxfmt": "^0.49.0", "oxfmt": "^0.46.0",
"oxlint": "^1.64.0", "oxlint": "^1.61.0",
"oxlint-tsgolint": "^0.22.1" "oxlint-tsgolint": "^0.21.1"
}, },
"engines": { "engines": {
"bun": ">=1.3.10" "bun": ">=1.3.10"

View File

@@ -1,26 +1,22 @@
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import os from "node:os"; import os from "node:os";
import path from "node:path"; import path from "node:path";
import process from "node:process"; import process from "node:process";
import { fileURLToPath } from "node:url";
import { afterEach, expect, mock, spyOn, test } from "bun:test"; import { afterEach, expect, mock, spyOn, test } from "bun:test";
import * as core from "@actions/core"; import * as core from "@actions/core";
import * as tc from "@actions/tool-cache";
const repo = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
const defaultEntrypoint = fileURLToPath(new URL("./main.ts", import.meta.url));
const CLI_CONFIG_REGISTRY = "SUPABASE_INTERNAL_IMAGE_REGISTRY"; const CLI_CONFIG_REGISTRY = "SUPABASE_INTERNAL_IMAGE_REGISTRY";
const originalPath = process.env.PATH;
const originalRunnerTemp = process.env.RUNNER_TEMP;
const originalWorkspace = process.env.GITHUB_WORKSPACE; const originalWorkspace = process.env.GITHUB_WORKSPACE;
const tempDirs = new Set<string>(); const tempDirs = new Set<string>();
let mainModule: typeof import("./main.ts") | null = null; let mainModule: typeof import("./main.ts") | null = null;
afterEach(() => { afterEach(() => {
mock.restore(); mock.restore();
process.env.PATH = originalPath;
process.env.RUNNER_TEMP = originalRunnerTemp;
process.env.GITHUB_WORKSPACE = originalWorkspace; process.env.GITHUB_WORKSPACE = originalWorkspace;
delete process.env.FAKE_CLI_VERSION;
delete process.env.FAKE_NPM_INTEGRITY;
delete process.env.FAKE_NPM_LOG;
delete process.env.SUPABASE_SETUP_CLI_NPM;
for (const dir of tempDirs) { for (const dir of tempDirs) {
rmSync(dir, { force: true, recursive: true }); rmSync(dir, { force: true, recursive: true });
@@ -28,14 +24,32 @@ afterEach(() => {
tempDirs.clear(); tempDirs.clear();
}); });
function createTempDir(prefix: string): string { function createFakeCli(versionOutput: string): string {
const dir = mkdtempSync(path.join(os.tmpdir(), prefix)); const dir = mkdtempSync(path.join(os.tmpdir(), "setup-cli-"));
tempDirs.add(dir); tempDirs.add(dir);
if (process.platform === "win32") {
writeFileSync(
path.join(dir, "supabase.cmd"),
versionOutput ? `@echo off\r\necho ${versionOutput}\r\n` : "@echo off\r\n",
);
return dir;
}
const escapedOutput = versionOutput.replaceAll("'", "'\"'\"'");
writeFileSync(
path.join(dir, "supabase"),
versionOutput
? `#!/usr/bin/env bash\nprintf '%s\\n' '${escapedOutput}'\n`
: "#!/usr/bin/env bash\n",
);
Bun.spawnSync(["chmod", "+x", path.join(dir, "supabase")]);
return dir; return dir;
} }
function createWorkspace(files: Record<string, string>): string { function createWorkspace(files: Record<string, string>): string {
const dir = createTempDir("setup-cli-workspace-"); const dir = mkdtempSync(path.join(os.tmpdir(), "setup-cli-workspace-"));
tempDirs.add(dir);
for (const [relativePath, content] of Object.entries(files)) { for (const [relativePath, content] of Object.entries(files)) {
const filePath = path.join(dir, relativePath); const filePath = path.join(dir, relativePath);
@@ -51,7 +65,6 @@ function createBunLock(
options: { options: {
includeDependency?: boolean; includeDependency?: boolean;
includePackageEntry?: boolean; includePackageEntry?: boolean;
integrity?: string;
useDevDependency?: boolean; useDevDependency?: boolean;
} = {}, } = {},
): string { ): string {
@@ -77,7 +90,7 @@ ${
"supabase@${version}", "supabase@${version}",
"", "",
{}, {},
"${options.integrity ?? "sha512-bun"}" "sha512-test"
]` ]`
: "" : ""
} }
@@ -88,12 +101,7 @@ ${
function createPnpmLock( function createPnpmLock(
version: string, version: string,
options: { options: { asString?: boolean; includeVersion?: boolean; useDevDependency?: boolean } = {},
asString?: boolean;
includeVersion?: boolean;
integrity?: string;
useDevDependency?: boolean;
} = {},
): string { ): string {
const dependencyKey = options.useDevDependency ? "devDependencies" : "dependencies"; const dependencyKey = options.useDevDependency ? "devDependencies" : "dependencies";
@@ -111,11 +119,11 @@ ${options.includeVersion === false ? "" : ` version: ${version}`}`
packages: packages:
supabase@${version}: supabase@${version}:
resolution: resolution:
integrity: ${options.integrity ?? "sha512-pnpm"} integrity: sha512-test
`; `;
} }
function createPackageLock(version: string, integrity = "sha512-package-lock"): string { function createPackageLock(version: string): string {
return JSON.stringify( return JSON.stringify(
{ {
name: "app", name: "app",
@@ -127,7 +135,6 @@ function createPackageLock(version: string, integrity = "sha512-package-lock"):
}, },
}, },
"node_modules/supabase": { "node_modules/supabase": {
integrity,
version, version,
}, },
}, },
@@ -137,107 +144,31 @@ function createPackageLock(version: string, integrity = "sha512-package-lock"):
); );
} }
function createFakeNpm(): string { function createActionSpies(inputVersion: string, cliDir: string, expectedUrlFragment: string) {
const root = createTempDir("setup-cli-fake-npm-");
const binDir = path.join(root, "bin");
const scriptPath = path.join(root, "fake-npm.js");
mkdirSync(binDir, { recursive: true });
writeFileSync(
scriptPath,
`import { appendFileSync, mkdirSync, writeFileSync } from "node:fs";
import path from "node:path";
const args = process.argv.slice(2);
appendFileSync(process.env.FAKE_NPM_LOG, JSON.stringify(args) + "\\n");
if (args[0] === "view") {
console.log(JSON.stringify(process.env.FAKE_NPM_INTEGRITY ?? "sha512-test"));
process.exit(0);
}
if (args[0] !== "install") {
console.error("Unexpected npm command: " + args.join(" "));
process.exit(1);
}
const prefixIndex = args.indexOf("--prefix");
const prefix = prefixIndex === -1 ? undefined : args[prefixIndex + 1];
if (!prefix) {
console.error("Missing --prefix");
process.exit(1);
}
const binDir = path.join(prefix, "node_modules", ".bin");
mkdirSync(binDir, { recursive: true });
if (process.platform === "win32") {
writeFileSync(
path.join(binDir, "supabase.cmd"),
process.env.FAKE_CLI_VERSION ? "@echo off\\r\\necho " + process.env.FAKE_CLI_VERSION + "\\r\\n" : "@echo off\\r\\n",
);
} else {
writeFileSync(
path.join(binDir, "supabase"),
process.env.FAKE_CLI_VERSION
? "#!/usr/bin/env bash\\nprintf '%s\\\\n' '" + process.env.FAKE_CLI_VERSION.replaceAll("'", "'\\\\''") + "'\\n"
: "#!/usr/bin/env bash\\n",
{ mode: 0o755 },
);
}
`,
);
if (process.platform === "win32") {
writeFileSync(
path.join(binDir, "npm.cmd"),
`@echo off\r\n"${process.execPath}" "${scriptPath}" %*\r\n`,
);
} else {
writeFileSync(
path.join(binDir, "npm"),
`#!/usr/bin/env bash\nexec "${process.execPath}" "${scriptPath}" "$@"\n`,
{ mode: 0o755 },
);
}
return binDir;
}
function installFakeNpm(versionOutput = "supabase 2.101.0", integrity = "sha512-test"): string {
const binDir = createFakeNpm();
const logPath = path.join(createTempDir("setup-cli-fake-npm-log-"), "npm.log");
writeFileSync(logPath, "");
process.env.FAKE_CLI_VERSION = versionOutput;
process.env.FAKE_NPM_INTEGRITY = integrity;
process.env.FAKE_NPM_LOG = logPath;
process.env.PATH = `${binDir}${path.delimiter}${originalPath ?? ""}`;
process.env.RUNNER_TEMP = createTempDir("setup-cli-runner-temp-");
process.env.SUPABASE_SETUP_CLI_NPM = path.join(
binDir,
process.platform === "win32" ? "npm.cmd" : "npm",
);
return logPath;
}
function readNpmCalls(logPath: string): string[][] {
return readFileSync(logPath, "utf8")
.trim()
.split("\n")
.filter(Boolean)
.map((line) => JSON.parse(line) as string[]);
}
function createActionSpies(inputVersion: string) {
return { return {
getInput: spyOn(core, "getInput").mockReturnValue(inputVersion),
setOutput: spyOn(core, "setOutput").mockImplementation(() => {}),
addPath: spyOn(core, "addPath").mockImplementation(() => {}), addPath: spyOn(core, "addPath").mockImplementation(() => {}),
exportVariable: spyOn(core, "exportVariable").mockImplementation(() => {}), exportVariable: spyOn(core, "exportVariable").mockImplementation(() => {}),
getInput: spyOn(core, "getInput").mockReturnValue(inputVersion),
setFailed: spyOn(core, "setFailed").mockImplementation(() => {}), setFailed: spyOn(core, "setFailed").mockImplementation(() => {}),
setOutput: spyOn(core, "setOutput").mockImplementation(() => {}), downloadTool: spyOn(tc, "downloadTool").mockImplementation(async (url: string) => {
expect(url).toContain(expectedUrlFragment);
return path.join(os.tmpdir(), "supabase-cli.tar.gz");
}),
extractTar: spyOn(tc, "extractTar").mockImplementation(async () => cliDir),
}; };
} }
function mockLatestRelease(version: string) {
const response = new Response(null, {
status: 302,
headers: { location: `https://github.com/supabase/cli/releases/tag/v${version}` },
});
return spyOn(globalThis, "fetch").mockImplementation(
(async () => response) as unknown as typeof fetch,
);
}
async function getMainModule(): Promise<typeof import("./main.ts")> { async function getMainModule(): Promise<typeof import("./main.ts")> {
if (!mainModule) { if (!mainModule) {
mainModule = await import("./main.ts"); mainModule = await import("./main.ts");
@@ -246,52 +177,101 @@ async function getMainModule(): Promise<typeof import("./main.ts")> {
return mainModule; return mainModule;
} }
test("uses an explicit npm package version when provided", async () => { test("awaits the action entrypoint with omitted version and latest fallback", async () => {
const { resolvePackage } = await getMainModule(); process.env.GITHUB_WORKSPACE = repo;
const cliDir = createFakeCli("supabase 2.84.2");
expect(resolvePackage("v2.101.0")).toEqual({ mockLatestRelease("2.84.2");
spec: "supabase@2.101.0", let startDownload!: () => void;
version: "2.101.0", let finishDownload!: () => void;
const downloadStarted = new Promise<void>((resolve) => {
startDownload = resolve;
}); });
const downloadFinished = new Promise<string>((resolve) => {
finishDownload = () => resolve(path.join(os.tmpdir(), "supabase-cli.tar.gz"));
});
const spies = {
getInput: spyOn(core, "getInput").mockReturnValue(""),
setOutput: spyOn(core, "setOutput").mockImplementation(() => {}),
addPath: spyOn(core, "addPath").mockImplementation(() => {}),
exportVariable: spyOn(core, "exportVariable").mockImplementation(() => {}),
setFailed: spyOn(core, "setFailed").mockImplementation(() => {}),
downloadTool: spyOn(tc, "downloadTool").mockImplementation(async (url: string) => {
expect(url).toContain("/download/v2.84.2/supabase_");
startDownload();
return downloadFinished;
}),
extractTar: spyOn(tc, "extractTar").mockImplementation(async () => cliDir),
};
const originalArgv1 = process.argv[1];
process.argv[1] = defaultEntrypoint;
try {
let importSettled = false;
const entrypoint = import(`./main.ts?entrypoint=${Date.now()}`).finally(() => {
importSettled = true;
}); });
test("uses the root bun.lock resolution when version is omitted", async () => { await downloadStarted;
await Bun.sleep(0);
expect(importSettled).toBe(false);
finishDownload();
await entrypoint;
} finally {
process.argv[1] = originalArgv1 ?? "";
}
expect(spies.setOutput).toHaveBeenCalledWith("version", "supabase 2.84.2");
expect(spies.addPath).toHaveBeenCalledWith(cliDir);
expect(spies.exportVariable).toHaveBeenCalledWith(CLI_CONFIG_REGISTRY, "ghcr.io");
expect(spies.setFailed).not.toHaveBeenCalled();
});
test("uses the root bun.lock version when version is omitted", async () => {
process.env.GITHUB_WORKSPACE = createWorkspace({ process.env.GITHUB_WORKSPACE = createWorkspace({
"bun.lock": createBunLock("2.41.0", { integrity: "sha512-bun-lock" }), "bun.lock": createBunLock("2.41.0"),
}); });
const { resolvePackage } = await getMainModule(); const cliDir = createFakeCli("supabase 2.41.0");
const spies = createActionSpies("", cliDir, "/download/v2.41.0/supabase_");
const { run } = await getMainModule();
expect(resolvePackage("")).toEqual({ await run();
integrity: "sha512-bun-lock",
spec: "supabase@2.41.0", expect(spies.downloadTool).not.toHaveBeenCalledWith(expect.stringContaining("/latest/download/"));
version: "2.41.0", expect(spies.setOutput).toHaveBeenCalledWith("version", "supabase 2.41.0");
}); expect(spies.exportVariable).toHaveBeenCalledWith(CLI_CONFIG_REGISTRY, "ghcr.io");
expect(spies.setFailed).not.toHaveBeenCalled();
}); });
test("uses the root pnpm-lock.yaml resolution when version is omitted", async () => { test("uses the root pnpm-lock.yaml version when version is omitted", async () => {
process.env.GITHUB_WORKSPACE = createWorkspace({ process.env.GITHUB_WORKSPACE = createWorkspace({
"pnpm-lock.yaml": createPnpmLock("2.42.0", { integrity: "sha512-pnpm-lock" }), "pnpm-lock.yaml": createPnpmLock("2.42.0"),
}); });
const { resolvePackage } = await getMainModule(); const cliDir = createFakeCli("supabase 2.42.0");
const spies = createActionSpies("", cliDir, "/download/v2.42.0/supabase_");
const { run } = await getMainModule();
expect(resolvePackage("")).toEqual({ await run();
integrity: "sha512-pnpm-lock",
spec: "supabase@2.42.0", expect(spies.setOutput).toHaveBeenCalledWith("version", "supabase 2.42.0");
version: "2.42.0", expect(spies.exportVariable).toHaveBeenCalledWith(CLI_CONFIG_REGISTRY, "ghcr.io");
}); expect(spies.setFailed).not.toHaveBeenCalled();
}); });
test("uses the root package-lock.json resolution when version is omitted", async () => { test("uses the root package-lock.json version when version is omitted", async () => {
process.env.GITHUB_WORKSPACE = createWorkspace({ process.env.GITHUB_WORKSPACE = createWorkspace({
"package-lock.json": createPackageLock("2.43.0", "sha512-package-lock"), "package-lock.json": createPackageLock("2.43.0"),
}); });
const { resolvePackage } = await getMainModule(); const cliDir = createFakeCli("supabase 2.43.0");
const spies = createActionSpies("", cliDir, "/download/v2.43.0/supabase_");
const { run } = await getMainModule();
expect(resolvePackage("")).toEqual({ await run();
integrity: "sha512-package-lock",
spec: "supabase@2.43.0", expect(spies.setOutput).toHaveBeenCalledWith("version", "supabase 2.43.0");
version: "2.43.0", expect(spies.exportVariable).toHaveBeenCalledWith(CLI_CONFIG_REGISTRY, "ghcr.io");
}); expect(spies.setFailed).not.toHaveBeenCalled();
}); });
test("falls through malformed lockfiles and uses the next supported root lockfile", async () => { test("falls through malformed lockfiles and uses the next supported root lockfile", async () => {
@@ -299,47 +279,60 @@ test("falls through malformed lockfiles and uses the next supported root lockfil
"bun.lock": "{ not valid", "bun.lock": "{ not valid",
"package-lock.json": createPackageLock("2.44.0"), "package-lock.json": createPackageLock("2.44.0"),
}); });
const { resolvePackage } = await getMainModule(); const cliDir = createFakeCli("supabase 2.44.0");
const spies = createActionSpies("", cliDir, "/download/v2.44.0/supabase_");
const { run } = await getMainModule();
expect(resolvePackage("")).toEqual({ await run();
integrity: "sha512-package-lock",
spec: "supabase@2.44.0", expect(spies.setOutput).toHaveBeenCalledWith("version", "supabase 2.44.0");
version: "2.44.0", expect(spies.exportVariable).toHaveBeenCalledWith(CLI_CONFIG_REGISTRY, "ghcr.io");
}); expect(spies.setFailed).not.toHaveBeenCalled();
}); });
test("falls back to latest when version is omitted and no supported root lockfile is present", async () => { test("falls back to latest when version is omitted and no supported root lockfile is present", async () => {
process.env.GITHUB_WORKSPACE = createWorkspace({ process.env.GITHUB_WORKSPACE = createWorkspace({
"README.md": "# app\n", "README.md": "# app\n",
}); });
const { resolvePackage } = await getMainModule(); const cliDir = createFakeCli("supabase 2.84.2");
mockLatestRelease("2.84.2");
const spies = createActionSpies("", cliDir, "/download/v2.84.2/supabase_");
const { run } = await getMainModule();
expect(resolvePackage("")).toEqual({ await run();
spec: "supabase@latest",
version: "latest", expect(spies.setOutput).toHaveBeenCalledWith("version", "supabase 2.84.2");
}); expect(spies.exportVariable).toHaveBeenCalledWith(CLI_CONFIG_REGISTRY, "ghcr.io");
expect(spies.setFailed).not.toHaveBeenCalled();
}); });
test("falls back to latest when version is omitted and no workspace is available", async () => { test("falls back to latest when version is omitted and no workspace is available", async () => {
delete process.env.GITHUB_WORKSPACE; delete process.env.GITHUB_WORKSPACE;
const { resolvePackage } = await getMainModule(); const cliDir = createFakeCli("supabase 2.84.2");
mockLatestRelease("2.84.2");
const spies = createActionSpies("", cliDir, "/download/v2.84.2/supabase_");
const { run } = await getMainModule();
expect(resolvePackage("")).toEqual({ await run();
spec: "supabase@latest",
version: "latest", expect(spies.setOutput).toHaveBeenCalledWith("version", "supabase 2.84.2");
}); expect(spies.exportVariable).toHaveBeenCalledWith(CLI_CONFIG_REGISTRY, "ghcr.io");
expect(spies.setFailed).not.toHaveBeenCalled();
}); });
test("uses the declared bun.lock version when the resolved package entry is missing", async () => { test("uses the declared bun.lock version when the resolved package entry is missing", async () => {
process.env.GITHUB_WORKSPACE = createWorkspace({ process.env.GITHUB_WORKSPACE = createWorkspace({
"bun.lock": createBunLock("2.44.1", { includePackageEntry: false, useDevDependency: true }), "bun.lock": createBunLock("2.44.1", { includePackageEntry: false, useDevDependency: true }),
}); });
const { resolvePackage } = await getMainModule(); const cliDir = createFakeCli("supabase 2.44.1");
const spies = createActionSpies("", cliDir, "/download/v2.44.1/supabase_");
const { run } = await getMainModule();
expect(resolvePackage("")).toEqual({ await run();
spec: "supabase@2.44.1",
version: "2.44.1", expect(spies.setOutput).toHaveBeenCalledWith("version", "supabase 2.44.1");
}); expect(spies.exportVariable).toHaveBeenCalledWith(CLI_CONFIG_REGISTRY, "ghcr.io");
expect(spies.setFailed).not.toHaveBeenCalled();
}); });
test("falls through bun.lock without supabase and uses a pnpm string dependency version", async () => { test("falls through bun.lock without supabase and uses a pnpm string dependency version", async () => {
@@ -347,119 +340,73 @@ test("falls through bun.lock without supabase and uses a pnpm string dependency
"bun.lock": createBunLock("2.47.0", { includeDependency: false }), "bun.lock": createBunLock("2.47.0", { includeDependency: false }),
"pnpm-lock.yaml": createPnpmLock("2.47.0", { asString: true }), "pnpm-lock.yaml": createPnpmLock("2.47.0", { asString: true }),
}); });
const { resolvePackage } = await getMainModule(); const cliDir = createFakeCli("supabase 2.47.0");
const spies = createActionSpies("", cliDir, "/download/v2.47.0/supabase_");
const { run } = await getMainModule();
expect(resolvePackage("")).toEqual({ await run();
integrity: "sha512-pnpm",
spec: "supabase@2.47.0", expect(spies.setOutput).toHaveBeenCalledWith("version", "supabase 2.47.0");
version: "2.47.0", expect(spies.exportVariable).toHaveBeenCalledWith(CLI_CONFIG_REGISTRY, "ghcr.io");
expect(spies.setFailed).not.toHaveBeenCalled();
}); });
test("falls through malformed pnpm lockfiles and uses the next supported root lockfile", async () => {
process.env.GITHUB_WORKSPACE = createWorkspace({
"pnpm-lock.yaml": "not: [valid",
"package-lock.json": createPackageLock("2.48.0"),
});
const cliDir = createFakeCli("supabase 2.48.0");
const spies = createActionSpies("", cliDir, "/download/v2.48.0/supabase_");
const { run } = await getMainModule();
await run();
expect(spies.setOutput).toHaveBeenCalledWith("version", "supabase 2.48.0");
expect(spies.exportVariable).toHaveBeenCalledWith(CLI_CONFIG_REGISTRY, "ghcr.io");
expect(spies.setFailed).not.toHaveBeenCalled();
});
test("falls through unreadable bun.lock paths and malformed package-lock files to latest", async () => {
const workspace = createWorkspace({
"package-lock.json": "{ invalid",
});
mkdirSync(path.join(workspace, "bun.lock"), { recursive: true });
process.env.GITHUB_WORKSPACE = workspace;
const cliDir = createFakeCli("supabase 2.84.2");
mockLatestRelease("2.84.2");
const spies = createActionSpies("", cliDir, "/download/v2.84.2/supabase_");
const { run } = await getMainModule();
await run();
expect(spies.setOutput).toHaveBeenCalledWith("version", "supabase 2.84.2");
expect(spies.exportVariable).toHaveBeenCalledWith(CLI_CONFIG_REGISTRY, "ghcr.io");
expect(spies.setFailed).not.toHaveBeenCalled();
}); });
test("falls back to latest when a pnpm dependency entry has no concrete version", async () => { test("falls back to latest when a pnpm dependency entry has no concrete version", async () => {
process.env.GITHUB_WORKSPACE = createWorkspace({ process.env.GITHUB_WORKSPACE = createWorkspace({
"pnpm-lock.yaml": createPnpmLock("2.49.0", { includeVersion: false }), "pnpm-lock.yaml": createPnpmLock("2.49.0", { includeVersion: false }),
}); });
const { resolvePackage } = await getMainModule(); const cliDir = createFakeCli("supabase 2.84.2");
mockLatestRelease("2.84.2");
expect(resolvePackage("")).toEqual({ const spies = createActionSpies("", cliDir, "/download/v2.84.2/supabase_");
spec: "supabase@latest",
version: "latest",
});
});
test("installs the CLI with npm into an isolated prefix", async () => {
const logPath = installFakeNpm();
const { installCli } = await getMainModule();
const cliPath = await installCli({
spec: "supabase@2.101.0",
version: "2.101.0",
});
expect(cliPath).toContain(`${path.sep}node_modules${path.sep}.bin`);
expect(readNpmCalls(logPath)).toEqual([
[
"install",
"--prefix",
expect.any(String),
"--omit=dev",
"--no-audit",
"--no-fund",
"--no-package-lock",
"--ignore-scripts",
"supabase@2.101.0",
],
]);
});
test("verifies lockfile integrity before installing", async () => {
const logPath = installFakeNpm("supabase 2.101.0", "sha512-lock");
const { installCli } = await getMainModule();
await installCli({
integrity: "sha512-lock",
spec: "supabase@2.101.0",
version: "2.101.0",
});
expect(readNpmCalls(logPath)).toEqual([
["view", "supabase@2.101.0", "dist.integrity", "--json"],
[
"install",
"--prefix",
expect.any(String),
"--omit=dev",
"--no-audit",
"--no-fund",
"--no-package-lock",
"--ignore-scripts",
"supabase@2.101.0",
],
]);
});
test("fails when lockfile integrity does not match the registry", async () => {
installFakeNpm("supabase 2.101.0", "sha512-registry");
const { installCli } = await getMainModule();
try {
await installCli({
integrity: "sha512-lock",
spec: "supabase@2.101.0",
version: "2.101.0",
});
throw new Error("Expected installCli to reject");
} catch (error) {
expect(error).toEqual(
new Error("Lockfile integrity for supabase@2.101.0 does not match the npm registry"),
);
}
});
test("runs the action with a package-lock resolution", async () => {
const logPath = installFakeNpm("supabase 2.43.0", "sha512-package-lock");
process.env.GITHUB_WORKSPACE = createWorkspace({
"package-lock.json": createPackageLock("2.43.0", "sha512-package-lock"),
});
const spies = createActionSpies("");
const { run } = await getMainModule(); const { run } = await getMainModule();
await run(); await run();
expect(readNpmCalls(logPath)[0]).toEqual(["view", "supabase@2.43.0", "dist.integrity", "--json"]); expect(spies.setOutput).toHaveBeenCalledWith("version", "supabase 2.84.2");
expect(spies.setOutput).toHaveBeenCalledWith("version", "supabase 2.43.0");
expect(spies.addPath).toHaveBeenCalledWith(expect.stringContaining("node_modules"));
expect(spies.exportVariable).toHaveBeenCalledWith(CLI_CONFIG_REGISTRY, "ghcr.io"); expect(spies.exportVariable).toHaveBeenCalledWith(CLI_CONFIG_REGISTRY, "ghcr.io");
expect(spies.setFailed).not.toHaveBeenCalled(); expect(spies.setFailed).not.toHaveBeenCalled();
}); });
test("explicit version overrides detected root lockfiles", async () => { test("explicit version overrides detected root lockfiles", async () => {
installFakeNpm("supabase 1.0.0");
process.env.GITHUB_WORKSPACE = createWorkspace({ process.env.GITHUB_WORKSPACE = createWorkspace({
"bun.lock": createBunLock("2.45.0"), "bun.lock": createBunLock("2.45.0"),
}); });
const spies = createActionSpies("1.0.0"); const cliDir = createFakeCli("supabase 1.0.0");
const spies = createActionSpies("1.0.0", cliDir, "/download/v1.0.0/supabase_1.0.0_");
const { run } = await getMainModule(); const { run } = await getMainModule();
await run(); await run();
@@ -469,12 +416,58 @@ test("explicit version overrides detected root lockfiles", async () => {
expect(spies.setFailed).not.toHaveBeenCalled(); expect(spies.setFailed).not.toHaveBeenCalled();
}); });
test("fails when the installed CLI does not report a version", async () => { test("downloads the version-prefixed tarball for releases >= 2.99.0", async () => {
installFakeNpm(""); // Regression for supabase/cli#5257: from v2.99.0 onward the CLI only
process.env.GITHUB_WORKSPACE = createWorkspace({ // publishes supabase_<version>_<platform>_<arch>.tar.gz; the unversioned
"package-lock.json": createPackageLock("2.46.0", "sha512-test"), // alias is gone.
delete process.env.GITHUB_WORKSPACE;
const cliDir = createFakeCli("supabase 2.99.0");
mockLatestRelease("2.99.0");
const spies = createActionSpies("", cliDir, "/download/v2.99.0/supabase_2.99.0_");
const { run } = await getMainModule();
await run();
expect(spies.setOutput).toHaveBeenCalledWith("version", "supabase 2.99.0");
expect(spies.exportVariable).toHaveBeenCalledWith(CLI_CONFIG_REGISTRY, "ghcr.io");
expect(spies.setFailed).not.toHaveBeenCalled();
}); });
const spies = createActionSpies("");
test("downloads the version-prefixed tarball when explicit version >= 2.99.0 is requested", async () => {
delete process.env.GITHUB_WORKSPACE;
const cliDir = createFakeCli("supabase 2.99.0");
const fetchSpy = spyOn(globalThis, "fetch");
const spies = createActionSpies("2.99.0", cliDir, "/download/v2.99.0/supabase_2.99.0_");
const { run } = await getMainModule();
await run();
expect(fetchSpy).not.toHaveBeenCalled();
expect(spies.setOutput).toHaveBeenCalledWith("version", "supabase 2.99.0");
expect(spies.exportVariable).toHaveBeenCalledWith(CLI_CONFIG_REGISTRY, "ghcr.io");
expect(spies.setFailed).not.toHaveBeenCalled();
});
test("fails when the latest release redirect does not include a version tag", async () => {
delete process.env.GITHUB_WORKSPACE;
const cliDir = createFakeCli("supabase 2.99.0");
const response = new Response(null, { status: 302, headers: { location: "/releases" } });
spyOn(globalThis, "fetch").mockImplementation((async () => response) as unknown as typeof fetch);
const spies = createActionSpies("", cliDir, "/download/");
const { run } = await getMainModule();
await run();
expect(spies.downloadTool).not.toHaveBeenCalled();
expect(spies.setFailed).toHaveBeenCalled();
});
test("fails when the installed CLI does not report a version", async () => {
process.env.GITHUB_WORKSPACE = createWorkspace({
"package-lock.json": createPackageLock("2.46.0"),
});
const cliDir = createFakeCli("");
const spies = createActionSpies("", cliDir, "/download/v2.46.0/supabase_");
const { run } = await getMainModule(); const { run } = await getMainModule();
await run(); await run();

View File

@@ -1,21 +1,18 @@
import { semver } from "bun"; import { $, semver } from "bun";
import * as core from "@actions/core"; import * as core from "@actions/core";
import { existsSync, mkdtempSync, readFileSync } from "node:fs"; import * as tc from "@actions/tool-cache";
import os from "node:os"; import { existsSync, readFileSync } from "node:fs";
import path from "node:path"; import path from "node:path";
import { fileURLToPath } from "node:url"; import { fileURLToPath } from "node:url";
export const CLI_CONFIG_REGISTRY = "SUPABASE_INTERNAL_IMAGE_REGISTRY"; export const CLI_CONFIG_REGISTRY = "SUPABASE_INTERNAL_IMAGE_REGISTRY";
const REGISTRY_VERSION = "1.28.0"; const REGISTRY_VERSION = "1.28.0";
// Starting with this release, the CLI publishes only version-prefixed tarballs
// (e.g. supabase_2.99.0_linux_amd64.tar.gz); the unversioned aliases that used
// to live alongside them are no longer uploaded. See supabase/cli#5257.
const VERSIONED_ARCHIVE_VERSION = "2.99.0";
const DEFAULT_VERSION = "latest"; const DEFAULT_VERSION = "latest";
const NPM_PACKAGE = "supabase"; const LATEST_RELEASE_URL = "https://github.com/supabase/cli/releases/latest";
const NPM_EXECUTABLE_ENV = "SUPABASE_SETUP_CLI_NPM";
type PackageResolution = {
spec: string;
version: string;
integrity?: string;
};
type BunLock = { type BunLock = {
workspaces?: { workspaces?: {
@@ -33,12 +30,6 @@ type PnpmDependency =
version?: string; version?: string;
}; };
type PnpmPackage = {
resolution?: {
integrity?: string;
};
};
type PnpmLock = { type PnpmLock = {
importers?: { importers?: {
".": { ".": {
@@ -46,14 +37,21 @@ type PnpmLock = {
devDependencies?: Record<string, PnpmDependency>; devDependencies?: Record<string, PnpmDependency>;
}; };
}; };
packages?: Record<string, PnpmPackage>;
}; };
type PackageLock = { type PackageLock = {
packages?: Record<string, { integrity?: string; version?: string }>; packages?: Record<string, { version?: string }>;
dependencies?: Record<string, { integrity?: string; version?: string }>; dependencies?: Record<string, { version?: string }>;
}; };
function getArchivePlatform(platform: NodeJS.Platform): string {
return platform === "win32" ? "windows" : platform;
}
function getArchiveArch(arch: NodeJS.Architecture): string {
return arch === "x64" ? "amd64" : arch;
}
function extractConcreteVersion(raw: string | undefined): string | null { function extractConcreteVersion(raw: string | undefined): string | null {
if (!raw) { if (!raw) {
return null; return null;
@@ -63,20 +61,6 @@ function extractConcreteVersion(raw: string | undefined): string | null {
return match?.[0] ?? null; return match?.[0] ?? null;
} }
function normalizeVersion(version: string): string {
return version.replace(/^v/i, "");
}
function toPackageResolution(version: string, integrity?: string): PackageResolution {
const normalizedVersion = normalizeVersion(version);
return {
spec: `${NPM_PACKAGE}@${normalizedVersion}`,
version: normalizedVersion,
integrity,
};
}
function readWorkspaceLockfile(workspaceRoot: string, filename: string): string | null { function readWorkspaceLockfile(workspaceRoot: string, filename: string): string | null {
const filePath = path.join(workspaceRoot, filename); const filePath = path.join(workspaceRoot, filename);
@@ -91,7 +75,7 @@ function readWorkspaceLockfile(workspaceRoot: string, filename: string): string
} }
} }
function detectResolutionFromBunLock(workspaceRoot: string): PackageResolution | null { function detectVersionFromBunLock(workspaceRoot: string): string | null {
const text = readWorkspaceLockfile(workspaceRoot, "bun.lock"); const text = readWorkspaceLockfile(workspaceRoot, "bun.lock");
if (!text) { if (!text) {
@@ -102,28 +86,24 @@ function detectResolutionFromBunLock(workspaceRoot: string): PackageResolution |
const lockfile = JSON.parse(text.replace(/,\s*([}\]])/g, "$1")) as BunLock; const lockfile = JSON.parse(text.replace(/,\s*([}\]])/g, "$1")) as BunLock;
const rootWorkspace = lockfile.workspaces?.[""]; const rootWorkspace = lockfile.workspaces?.[""];
const declaredVersion = const declaredVersion =
rootWorkspace?.dependencies?.[NPM_PACKAGE] ?? rootWorkspace?.devDependencies?.[NPM_PACKAGE]; rootWorkspace?.dependencies?.supabase ?? rootWorkspace?.devDependencies?.supabase;
if (!declaredVersion) { if (!declaredVersion) {
return null; return null;
} }
const resolvedPackage = lockfile.packages?.[NPM_PACKAGE]; const resolvedPackage = lockfile.packages?.supabase;
if (Array.isArray(resolvedPackage) && typeof resolvedPackage[0] === "string") { if (Array.isArray(resolvedPackage) && typeof resolvedPackage[0] === "string") {
const version = extractConcreteVersion(resolvedPackage[0]); return extractConcreteVersion(resolvedPackage[0]);
const integrity = typeof resolvedPackage[3] === "string" ? resolvedPackage[3] : undefined;
return version ? toPackageResolution(version, integrity) : null;
} }
const version = extractConcreteVersion(declaredVersion); return extractConcreteVersion(declaredVersion);
return version ? toPackageResolution(version) : null;
} catch { } catch {
return null; return null;
} }
} }
function detectResolutionFromPnpmLock(workspaceRoot: string): PackageResolution | null { function detectVersionFromPnpmLock(workspaceRoot: string): string | null {
const text = readWorkspaceLockfile(workspaceRoot, "pnpm-lock.yaml"); const text = readWorkspaceLockfile(workspaceRoot, "pnpm-lock.yaml");
if (!text) { if (!text) {
@@ -134,29 +114,19 @@ function detectResolutionFromPnpmLock(workspaceRoot: string): PackageResolution
const lockfile = Bun.YAML.parse(text) as PnpmLock; const lockfile = Bun.YAML.parse(text) as PnpmLock;
const rootImporter = lockfile.importers?.["."]; const rootImporter = lockfile.importers?.["."];
const dependency = const dependency =
rootImporter?.dependencies?.[NPM_PACKAGE] ?? rootImporter?.devDependencies?.[NPM_PACKAGE]; rootImporter?.dependencies?.supabase ?? rootImporter?.devDependencies?.supabase;
const version =
typeof dependency === "string"
? extractConcreteVersion(dependency)
: extractConcreteVersion(dependency?.version);
if (!version) { if (typeof dependency === "string") {
return null; return extractConcreteVersion(dependency);
} }
const integrity = Object.entries(lockfile.packages ?? {}).find( return extractConcreteVersion(dependency?.version);
([packageKey]) =>
packageKey === `${NPM_PACKAGE}@${version}` ||
packageKey.startsWith(`/${NPM_PACKAGE}@${version}`),
)?.[1].resolution?.integrity;
return toPackageResolution(version, integrity);
} catch { } catch {
return null; return null;
} }
} }
function detectResolutionFromPackageLock(workspaceRoot: string): PackageResolution | null { function detectVersionFromPackageLock(workspaceRoot: string): string | null {
const text = readWorkspaceLockfile(workspaceRoot, "package-lock.json"); const text = readWorkspaceLockfile(workspaceRoot, "package-lock.json");
if (!text) { if (!text) {
@@ -165,134 +135,71 @@ function detectResolutionFromPackageLock(workspaceRoot: string): PackageResoluti
try { try {
const lockfile = JSON.parse(text) as PackageLock; const lockfile = JSON.parse(text) as PackageLock;
const packageEntry = lockfile.packages?.[`node_modules/${NPM_PACKAGE}`];
const dependencyEntry = lockfile.dependencies?.[NPM_PACKAGE];
const version =
extractConcreteVersion(packageEntry?.version) ??
extractConcreteVersion(dependencyEntry?.version);
return version return (
? toPackageResolution(version, packageEntry?.integrity ?? dependencyEntry?.integrity) extractConcreteVersion(lockfile.packages?.["node_modules/supabase"]?.version) ??
: null; extractConcreteVersion(lockfile.dependencies?.supabase?.version)
);
} catch { } catch {
return null; return null;
} }
} }
export function resolvePackage(inputVersion: string): PackageResolution { function resolveVersion(inputVersion: string): string {
const requestedVersion = inputVersion.trim(); const requestedVersion = inputVersion.trim();
if (requestedVersion) { if (requestedVersion) {
return toPackageResolution(requestedVersion); return requestedVersion;
} }
const workspaceRoot = process.env.GITHUB_WORKSPACE?.trim(); const workspaceRoot = process.env.GITHUB_WORKSPACE?.trim();
if (!workspaceRoot) { if (!workspaceRoot) {
return toPackageResolution(DEFAULT_VERSION); return DEFAULT_VERSION;
} }
return ( return (
detectResolutionFromBunLock(workspaceRoot) ?? detectVersionFromBunLock(workspaceRoot) ??
detectResolutionFromPnpmLock(workspaceRoot) ?? detectVersionFromPnpmLock(workspaceRoot) ??
detectResolutionFromPackageLock(workspaceRoot) ?? detectVersionFromPackageLock(workspaceRoot) ??
toPackageResolution(DEFAULT_VERSION) DEFAULT_VERSION
); );
} }
async function verifyExpectedIntegrity(resolution: PackageResolution): Promise<void> { export async function resolveLatestVersion(): Promise<string> {
if (!resolution.integrity) { const response = await fetch(LATEST_RELEASE_URL, { method: "HEAD", redirect: "manual" });
return; const location = response.headers.get("location");
const version = extractConcreteVersion(location ?? undefined);
if (!version) {
throw new Error(
`Could not resolve latest Supabase CLI version (status ${response.status}, location ${location ?? "<none>"})`,
);
} }
const output = await runNpm(["view", resolution.spec, "dist.integrity", "--json"]); return version;
const registryIntegrity = JSON.parse(output) as unknown;
if (registryIntegrity !== resolution.integrity) {
throw new Error(`Lockfile integrity for ${resolution.spec} does not match the npm registry`);
}
} }
function createInstallRoot(): string { export function getDownloadUrl(version: string): string {
const tempRoot = process.env.RUNNER_TEMP?.trim() || os.tmpdir(); const platform = getArchivePlatform(process.platform);
return mkdtempSync(path.join(tempRoot, "setup-cli-")); const arch = getArchiveArch(process.arch);
const versionedFilename = `supabase_${version}_${platform}_${arch}.tar.gz`;
const unversionedFilename = `supabase_${platform}_${arch}.tar.gz`;
// v2.99.0+ and the earliest releases (pre-v1.28.0) only publish version-prefixed
// tarballs; the intermediate releases publish unversioned aliases.
if (
semver.order(version, REGISTRY_VERSION) === -1 ||
semver.order(version, VERSIONED_ARCHIVE_VERSION) >= 0
) {
return `https://github.com/supabase/cli/releases/download/v${version}/${versionedFilename}`;
} }
async function runNpm(args: string[]): Promise<string> { return `https://github.com/supabase/cli/releases/download/v${version}/${unversionedFilename}`;
const executable = process.env[NPM_EXECUTABLE_ENV]?.trim() || "npm";
const proc = Bun.spawn([executable, ...args], {
env: process.env,
stderr: "pipe",
stdout: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([
new Response(proc.stdout).text(),
new Response(proc.stderr).text(),
proc.exited,
]);
if (exitCode !== 0) {
throw new Error(stderr.trim() || `npm ${args.join(" ")} failed`);
}
return stdout;
}
export async function installCli(resolution: PackageResolution): Promise<string> {
await verifyExpectedIntegrity(resolution);
const installRoot = createInstallRoot();
await runNpm([
"install",
"--prefix",
installRoot,
"--omit=dev",
"--no-audit",
"--no-fund",
"--no-package-lock",
"--ignore-scripts",
resolution.spec,
]);
return path.join(installRoot, "node_modules", ".bin");
}
function getCliExecutablePath(cliPath: string): string {
if (process.platform !== "win32") {
return path.join(cliPath, "supabase");
}
const cmdPath = path.join(cliPath, "supabase.cmd");
if (existsSync(cmdPath)) {
return cmdPath;
}
const exePath = path.join(cliPath, "supabase.exe");
if (existsSync(exePath)) {
return exePath;
}
return path.join(cliPath, "supabase");
} }
export async function determineInstalledVersion(cliPath: string): Promise<string> { export async function determineInstalledVersion(cliPath: string): Promise<string> {
const executable = getCliExecutablePath(cliPath); const version = (await $`${path.join(cliPath, "supabase")} --version`.text()).trim();
const proc = Bun.spawn([executable, "--version"], {
stderr: "pipe",
stdout: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([
new Response(proc.stdout).text(),
new Response(proc.stderr).text(),
proc.exited,
]);
if (exitCode !== 0) {
throw new Error(stderr.trim() || "Could not determine installed Supabase CLI version");
}
const version = stdout.trim();
if (!version) { if (!version) {
throw new Error("Could not determine installed Supabase CLI version"); throw new Error("Could not determine installed Supabase CLI version");
} }
@@ -300,24 +207,18 @@ export async function determineInstalledVersion(cliPath: string): Promise<string
return version; return version;
} }
function shouldUseGhcrRegistry(requestedVersion: string, installedVersion: string): boolean {
if (requestedVersion.toLowerCase() === DEFAULT_VERSION) {
return true;
}
const concreteVersion = extractConcreteVersion(installedVersion);
return concreteVersion !== null && semver.order(concreteVersion, REGISTRY_VERSION) >= 0;
}
export async function run(): Promise<void> { export async function run(): Promise<void> {
try { try {
const resolution = resolvePackage(core.getInput("version")); const requestedVersion = resolveVersion(core.getInput("version"));
const cliPath = await installCli(resolution); const version =
requestedVersion.toLowerCase() === "latest" ? await resolveLatestVersion() : requestedVersion;
const tarball = await tc.downloadTool(getDownloadUrl(version));
const cliPath = await tc.extractTar(tarball);
const installedVersion = await determineInstalledVersion(cliPath); const installedVersion = await determineInstalledVersion(cliPath);
core.setOutput("version", installedVersion); core.setOutput("version", installedVersion);
core.addPath(cliPath); core.addPath(cliPath);
if (shouldUseGhcrRegistry(resolution.version, installedVersion)) { if (semver.order(version, REGISTRY_VERSION) >= 0) {
core.exportVariable(CLI_CONFIG_REGISTRY, "ghcr.io"); core.exportVariable(CLI_CONFIG_REGISTRY, "ghcr.io");
} }
} catch (error) { } catch (error) {