Compare commits

..

1 Commits

Author SHA1 Message Date
Julien Goux
9da7121816 feat: caching 2026-04-10 11:53:47 +02:00
16 changed files with 168969 additions and 633 deletions

View File

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

View File

@@ -20,14 +20,14 @@ jobs:
steps:
# Metadata drives the non-major gating used for approval and auto-merge.
- id: meta
uses: dependabot/fetch-metadata@25dd0e34f4fe68f24cc83900b1fe3fe149efef98 # v3.1.0
uses: dependabot/fetch-metadata@ffa630c65fa7e0ecfa0625b5ceda64399aea1b36 # v3.0.0
with:
github-token: "${{ secrets.GITHUB_TOKEN }}"
- name: Generate 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' }}
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}

View File

@@ -1,6 +1,6 @@
# This workflow refreshes and checks dependency license records used in this
# action with the help of the Licensed tool. If any licenses are invalid or
# missing, this workflow will fail. See: https://github.com/licensee/licensed
# This workflow checks the statuses of cached dependencies used in this action
# with the help of the Licensed tool. If any licenses are invalid or missing,
# this workflow will fail. See: https://github.com/licensee/licensed
name: Licensed
@@ -71,7 +71,7 @@ jobs:
- name: Setup Ruby
id: setup-ruby
if: steps.license-inputs.outputs.changed == 'true'
uses: ruby/setup-ruby@97ecb7b512899eb71ab1bf2310a624c6f1589ac6 # v1.308.0
uses: ruby/setup-ruby@e65c17d16e57e481586a6a5a0282698790062f92 # v1.300.0
with:
ruby-version: ruby
@@ -81,11 +81,6 @@ jobs:
version: 4.x
github_token: ${{ secrets.GITHUB_TOKEN }}
- name: Refresh License Cache
id: refresh-license-cache
if: steps.license-inputs.outputs.changed == 'true'
run: licensed cache
- name: Check Licenses
id: check-licenses
if: steps.license-inputs.outputs.changed == 'true'
@@ -116,7 +111,7 @@ jobs:
- name: Setup Ruby
id: setup-ruby
uses: ruby/setup-ruby@97ecb7b512899eb71ab1bf2310a624c6f1589ac6 # v1.308.0
uses: ruby/setup-ruby@e65c17d16e57e481586a6a5a0282698790062f92 # v1.300.0
with:
ruby-version: ruby

View File

@@ -28,10 +28,8 @@ steps:
```
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`
package version through npm. If the lockfile includes package integrity
metadata, the action verifies it against the npm registry before installing. If
no supported lockfile is present, it falls back to `latest`.
`pnpm-lock.yaml`, or `package-lock.json` and uses the declared `supabase`
version. If no supported lockfile is present, it falls back to `latest`.
A specific version of the `supabase` CLI can be installed:
@@ -42,6 +40,22 @@ steps:
version: 2.84.2
```
Cache Docker images used by `supabase start` across workflow runs:
```yaml
steps:
- uses: actions/checkout@v6
- uses: supabase/setup-cli@v2
with:
version: 2.84.2
cache: true
- run: supabase start
```
The first run still pulls images from the registry. Later runs can restore the
same image set from the GitHub Actions cache before `supabase start` runs, and
the action saves newly pulled Supabase images at the end of a successful job.
Run `supabase db start` to execute all migrations on a fresh database:
```yaml
@@ -60,10 +74,18 @@ on Windows and macOS runners.
The action supports the following inputs:
| Name | Type | Description | Default | Required |
| -------------- | ------ | ---------------------------------------------------------------- | --------------------------------- | -------- |
| `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 |
| Name | Type | Description | Default | Required |
| ----------- | ------- | ------------------------------------------------------ | --------------------------------- | -------- |
| `version` | String | Supabase CLI version (or `latest`) | Root lockfile version or `latest` | false |
| `cache` | Boolean | Cache Docker images used by Supabase local development | `false` | false |
| `cache-key` | String | Explicit cache key for Supabase Docker images | Generated from runner and config | false |
The action exposes these outputs:
| Name | Description |
| ----------- | ------------------------------------------------------ |
| `version` | Version of installed Supabase CLI |
| `cache-hit` | Whether an exact Supabase Docker image cache was found |
## Advanced Usage
@@ -104,6 +126,8 @@ Export local Supabase env vars for app tests:
```yaml
steps:
- uses: supabase/setup-cli@v2
with:
cache: true
- run: supabase init
- run: supabase start
- name: Export local Supabase env vars
@@ -116,6 +140,22 @@ steps:
- run: bun test
```
Customize the Docker image cache key when the image set depends on your workflow
flags, generated config, or monorepo layout:
```yaml
steps:
- uses: actions/checkout@v6
- uses: supabase/setup-cli@v2
with:
cache: true
cache-key: supabase-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('supabase/config.toml') }}-start-all
- run: supabase start
```
Avoid running `docker system prune -a` before the job ends if you want the
post-action cache save to include images pulled by `supabase start`.
## Develop
After you've cloned the repository to your local machine or codespace, you'll
@@ -139,6 +179,12 @@ need to perform a few setup steps before you can work on the action.
bun test
```
1. :package: Build the bundled action entrypoints
```bash
bun run build
```
1. :mag: Run the full local CI suite
```bash
@@ -165,7 +211,6 @@ steps:
- uses: ./
with:
version: latest
github-token: ${{ github.token }}
```
The CI workflow provides fast smoke coverage across GitHub-hosted runners, and

View File

@@ -5,111 +5,20 @@ inputs:
version:
description: Version of Supabase CLI to install. If omitted, detect from the root lockfile and otherwise use latest.
required: false
github-token:
description: Deprecated. The action now installs through npm and does not use GitHub release API requests.
cache:
description: Cache Docker images used by Supabase local development commands.
required: false
default: "false"
cache-key:
description: Optional explicit cache key for Supabase Docker images.
required: false
outputs:
version:
description: Version of installed Supabase CLI
value: ${{ steps.setup-cli.outputs.version }}
cache-hit:
description: Whether an exact Supabase Docker image cache was restored.
runs:
using: composite
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
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version-file: ${{ github.action_path }}/.bun-version
bun-download-url: ${{ steps.bun-download.outputs.url }}
- name: Install Action Dependencies
shell: sh
working-directory: ${{ github.action_path }}
run: bun install --frozen-lockfile --production
- id: setup-cli
name: Setup Supabase CLI
shell: sh
working-directory: ${{ github.action_path }}
env:
INPUT_VERSION: ${{ inputs.version }}
run: bun src/main.ts
using: node24
main: dist/main.js
post: dist/post.js
post-if: success()

221
bun.lock
View File

@@ -5,151 +5,240 @@
"": {
"name": "setup-cli",
"dependencies": {
"@actions/core": "^3.0.1",
"@actions/cache": "^6.0.0",
"@actions/core": "^3.0.0",
"@actions/tool-cache": "^4.0.0",
"js-yaml": "^4.1.1",
"semver": "^7.7.4",
},
"devDependencies": {
"@tsconfig/bun": "^1.0.10",
"@types/bun": "^1.3.14",
"@typescript/native-preview": "^7.0.0-dev.20260410.1",
"oxfmt": "^0.49.0",
"oxlint": "^1.64.0",
"oxlint-tsgolint": "^0.22.1",
"@types/bun": "^1.3.11",
"@types/js-yaml": "^4.0.9",
"@types/node": "^24",
"@types/semver": "^7.7.1",
"@typescript/native-preview": "^7.0.0-dev.20260409.1",
"oxfmt": "^0.44.0",
"oxlint": "^1.59.0",
"oxlint-tsgolint": "^0.20.0",
},
},
},
"packages": {
"@actions/core": ["@actions/core@3.0.1", "", { "dependencies": { "@actions/exec": "^3.0.0", "@actions/http-client": "^4.0.0" } }, "sha512-a6d/Nwahm9fliVGRhdhofo40HjHQasUPusmc7vBfyky+7Z+P2A1J68zyFVaNcEclc/Se+eO595oAr5nwEIoIUA=="],
"@actions/cache": ["@actions/cache@6.0.0", "", { "dependencies": { "@actions/core": "^3.0.0", "@actions/exec": "^3.0.0", "@actions/glob": "^0.6.1", "@actions/http-client": "^4.0.0", "@actions/io": "^3.0.0", "@azure/core-rest-pipeline": "^1.22.0", "@azure/storage-blob": "^12.30.0", "@protobuf-ts/runtime-rpc": "^2.11.1", "semver": "^7.7.3" } }, "sha512-+tCs634SyGBQJ3KU1rtAVabmN/gYiT9WgzTSJzWwdPCLmM3zWrdbysaErKv8HyI6OozClrxNvDgPjJimbHZZvw=="],
"@actions/core": ["@actions/core@3.0.0", "", { "dependencies": { "@actions/exec": "^3.0.0", "@actions/http-client": "^4.0.0" } }, "sha512-zYt6cz+ivnTmiT/ksRVriMBOiuoUpDCJJlZ5KPl2/FRdvwU3f7MPh9qftvbkXJThragzUZieit2nyHUyw53Seg=="],
"@actions/exec": ["@actions/exec@3.0.0", "", { "dependencies": { "@actions/io": "^3.0.2" } }, "sha512-6xH/puSoNBXb72VPlZVm7vQ+svQpFyA96qdDBvhB8eNZOE8LtPf9L4oAsfzK/crCL8YZ+19fKYVnM63Sl+Xzlw=="],
"@actions/glob": ["@actions/glob@0.6.1", "", { "dependencies": { "@actions/core": "^3.0.0", "minimatch": "^3.0.4" } }, "sha512-K4+2Ac5ILcf2ySdJCha+Pop9NcKjxqCL4xL4zI50dgB2PbXgC0+AcP011xfH4Of6b4QEJJg8dyZYv7zl4byTsw=="],
"@actions/http-client": ["@actions/http-client@4.0.0", "", { "dependencies": { "tunnel": "^0.0.6", "undici": "^6.23.0" } }, "sha512-QuwPsgVMsD6qaPD57GLZi9sqzAZCtiJT8kVBCDpLtxhL5MydQ4gS+DrejtZZPdIYyB1e95uCK9Luyds7ybHI3g=="],
"@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=="],
"@azure/abort-controller": ["@azure/abort-controller@2.1.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA=="],
"@oxfmt/binding-darwin-arm64": ["@oxfmt/binding-darwin-arm64@0.49.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-8x5DN9CsFfb432sHa9NyqX5XisGUdA53LPEGSdv/VniS+v4uEOR8Orv7A9QSB98Xxgp0t6r31DzQA/wpIobGqQ=="],
"@azure/core-auth": ["@azure/core-auth@1.10.1", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-util": "^1.13.0", "tslib": "^2.6.2" } }, "sha512-ykRMW8PjVAn+RS6ww5cmK9U2CyH9p4Q88YJwvUslfuMmN98w/2rdGRLPqJYObapBCdzBVeDgYWdJnFPFb7qzpg=="],
"@oxfmt/binding-darwin-x64": ["@oxfmt/binding-darwin-x64@0.49.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-e0+DSVzk4ewhMVKNYDaRTmP81jNMBWR1X9al0cVKWS+hDM/dElNqD5zjTOCuLOZc4oOdp2Gx2ldrVL+yYo9TZQ=="],
"@azure/core-client": ["@azure/core-client@1.10.1", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.10.0", "@azure/core-rest-pipeline": "^1.22.0", "@azure/core-tracing": "^1.3.0", "@azure/core-util": "^1.13.0", "@azure/logger": "^1.3.0", "tslib": "^2.6.2" } }, "sha512-Nh5PhEOeY6PrnxNPsEHRr9eimxLwgLlpmguQaHKBinFYA/RU9+kOYVOQqOrTsCL+KSxrLLl1gD8Dk5BFW/7l/w=="],
"@oxfmt/binding-freebsd-x64": ["@oxfmt/binding-freebsd-x64@0.49.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-W+mjtYtrQvFbXT/uNT+221OBhGRZ8UqNsLxjTWsjZ4GsQnRdvRC/N2NCK86BcamWr7lsTxwpwN3PULnr78sgcQ=="],
"@azure/core-http-compat": ["@azure/core-http-compat@2.4.0", "", { "dependencies": { "@azure/abort-controller": "^2.1.2" }, "peerDependencies": { "@azure/core-client": "^1.10.0", "@azure/core-rest-pipeline": "^1.22.0" } }, "sha512-f1P96IB399YiN2ARYHP7EpZi3Bf3wH4SN2lGzrw7JVwm7bbsVYtf2iKSBwTywD2P62NOPZGHFSZi+6jjb75JuA=="],
"@oxfmt/binding-linux-arm-gnueabihf": ["@oxfmt/binding-linux-arm-gnueabihf@0.49.0", "", { "os": "linux", "cpu": "arm" }, "sha512-Rtv6UevV7czDlLqil+NZUe4d8gs8jQo/zScSpumwyf7I+fSdLc+hc8AF3MQC7ymxSMMD9+vfiqQlsIf7wOAzXA=="],
"@azure/core-lro": ["@azure/core-lro@2.7.2", "", { "dependencies": { "@azure/abort-controller": "^2.0.0", "@azure/core-util": "^1.2.0", "@azure/logger": "^1.0.0", "tslib": "^2.6.2" } }, "sha512-0YIpccoX8m/k00O7mDDMdJpbr6mf1yWo2dfmxt5A8XVZVVMz2SSKaEbMCeJRvgQ0IaSlqhjT47p4hVIRRy90xw=="],
"@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=="],
"@azure/core-paging": ["@azure/core-paging@1.6.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-YKWi9YuCU04B55h25cnOYZHxXYtEvQEbKST5vqRga7hWY9ydd3FZHdeQF8pyh+acWZvppw13M/LMGx0LABUVMA=="],
"@oxfmt/binding-linux-arm64-gnu": ["@oxfmt/binding-linux-arm64-gnu@0.49.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-JIfWenFhlzx+O8YygyZhoHFzTsdgDhxhbDRnE2iJLnnM5pWKScFvPECO2vOlA7JqJ/9S1g3uzEKuRCkHFwTjvA=="],
"@azure/core-rest-pipeline": ["@azure/core-rest-pipeline@1.23.0", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.10.0", "@azure/core-tracing": "^1.3.0", "@azure/core-util": "^1.13.0", "@azure/logger": "^1.3.0", "@typespec/ts-http-runtime": "^0.3.4", "tslib": "^2.6.2" } }, "sha512-Evs1INHo+jUjwHi1T6SG6Ua/LHOQBCLuKEEE6efIpt4ZOoNonaT1kP32GoOcdNDbfqsD2445CPri3MubBy5DEQ=="],
"@oxfmt/binding-linux-arm64-musl": ["@oxfmt/binding-linux-arm64-musl@0.49.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-iNzkMPG18jPkwBOZ4/HEjwqfzAjq4RrUQ0CgId/fC1ENvYD5jLVAaU/gWgpiqP1ys07kxSsSggDd1fp3E7mQHw=="],
"@azure/core-tracing": ["@azure/core-tracing@1.3.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-9MWKevR7Hz8kNzzPLfX4EAtGM2b8mr50HPDBvio96bURP/9C+HjdH3sBlLSNNrvRAr5/k/svoH457gB5IKpmwQ=="],
"@oxfmt/binding-linux-ppc64-gnu": ["@oxfmt/binding-linux-ppc64-gnu@0.49.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-BPHA/NN3LvoIXiid+iz3BHt5V0Rzx0tXAqRUovwE1NsbDaLG9e8mtv7evDGRIkVQacqTDBv0XL25THHsxSJosQ=="],
"@azure/core-util": ["@azure/core-util@1.13.1", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@typespec/ts-http-runtime": "^0.3.0", "tslib": "^2.6.2" } }, "sha512-XPArKLzsvl0Hf0CaGyKHUyVgF7oDnhKoP85Xv6M4StF/1AhfORhZudHtOyf2s+FcbuQ9dPRAjB8J2KvRRMUK2A=="],
"@oxfmt/binding-linux-riscv64-gnu": ["@oxfmt/binding-linux-riscv64-gnu@0.49.0", "", { "os": "linux", "cpu": "none" }, "sha512-3Eroshe+s69htC9JIL0+zLGQczLtRKezkMhwqQC21VC5Z/fuLvzLfbAOLgJLUq601H8gDYjy7deYycfOBjCvWg=="],
"@azure/core-xml": ["@azure/core-xml@1.5.1", "", { "dependencies": { "fast-xml-parser": "^5.5.9", "tslib": "^2.8.1" } }, "sha512-xcNRHqCoSp4AunOALEae6A8f3qATb83gSrm31Iqb01OzblvC3/W/bfXozcq78EzIdzZzuH1bZ2NvRR0TdX709w=="],
"@oxfmt/binding-linux-riscv64-musl": ["@oxfmt/binding-linux-riscv64-musl@0.49.0", "", { "os": "linux", "cpu": "none" }, "sha512-fnaERGgsxGm0lKAmO72EYR4BA3qBnzBTJBTi6EtUMq1D4R7EexRBMU4voXnx4TXla3SEDl9x4uNp/18SbkPjGg=="],
"@azure/logger": ["@azure/logger@1.3.0", "", { "dependencies": { "@typespec/ts-http-runtime": "^0.3.0", "tslib": "^2.6.2" } }, "sha512-fCqPIfOcLE+CGqGPd66c8bZpwAji98tZ4JI9i/mlTNTlsIWslCfpg48s/ypyLxZTump5sypjrKn2/kY7q8oAbA=="],
"@oxfmt/binding-linux-s390x-gnu": ["@oxfmt/binding-linux-s390x-gnu@0.49.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-rBwasMl1Uul1MCCeTGEFKnOTL7VUxHf+634jWStrQAbzpBJgd5Yz5m4F7exVCsoI8PHn57dNjssXagXLCLB5yA=="],
"@azure/storage-blob": ["@azure/storage-blob@12.31.0", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.9.0", "@azure/core-client": "^1.9.3", "@azure/core-http-compat": "^2.2.0", "@azure/core-lro": "^2.2.0", "@azure/core-paging": "^1.6.2", "@azure/core-rest-pipeline": "^1.19.1", "@azure/core-tracing": "^1.2.0", "@azure/core-util": "^1.11.0", "@azure/core-xml": "^1.4.5", "@azure/logger": "^1.1.4", "@azure/storage-common": "^12.3.0", "events": "^3.0.0", "tslib": "^2.8.1" } }, "sha512-DBgNv10aCSxopt92DkTDD0o9xScXeBqPKGmR50FPZQaEcH4JLQ+GEOGEDv19V5BMkB7kxr+m4h6il/cCDPvmHg=="],
"@oxfmt/binding-linux-x64-gnu": ["@oxfmt/binding-linux-x64-gnu@0.49.0", "", { "os": "linux", "cpu": "x64" }, "sha512-BoC/F9xHe2y/deuBGA5Aw7bes07OD2gcL2wlpzTrfImR92vPP7S/k3LBTyspQZCNIVNdagkELcqKELwMLGIfAg=="],
"@azure/storage-common": ["@azure/storage-common@12.3.0", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.9.0", "@azure/core-http-compat": "^2.2.0", "@azure/core-rest-pipeline": "^1.19.1", "@azure/core-tracing": "^1.2.0", "@azure/core-util": "^1.11.0", "@azure/logger": "^1.1.4", "events": "^3.3.0", "tslib": "^2.8.1" } }, "sha512-/OFHhy86aG5Pe8dP5tsp+BuJ25JOAl9yaMU3WZbkeoiFMHFtJ7tu5ili7qEdBXNW9G5lDB19trwyI6V49F/8iQ=="],
"@oxfmt/binding-linux-x64-musl": ["@oxfmt/binding-linux-x64-musl@0.49.0", "", { "os": "linux", "cpu": "x64" }, "sha512-umY6jFADAo/oztFKl8D/S6vSrG6oBpEskcentiRuz42kZVU2kfDXMWCYavxyZR2bwPjqkHpcHZ6EZFiH3Qj9ZA=="],
"@oxfmt/binding-android-arm-eabi": ["@oxfmt/binding-android-arm-eabi@0.44.0", "", { "os": "android", "cpu": "arm" }, "sha512-5UvghMd9SA/yvKTWCAxMAPXS1d2i054UeOf4iFjZjfayTwCINcC3oaSXjtbZfCaEpxgJod7XiOjTtby5yEv/BQ=="],
"@oxfmt/binding-openharmony-arm64": ["@oxfmt/binding-openharmony-arm64@0.49.0", "", { "os": "none", "cpu": "arm64" }, "sha512-J85zQMiw2pXiGPK+OusmDvSnJ/dgpgN7VgmB2zOBtgS8F+nsOUfSg9ZEBrwbQscjZ7tkPbm38CG4VF5f53MsiA=="],
"@oxfmt/binding-android-arm64": ["@oxfmt/binding-android-arm64@0.44.0", "", { "os": "android", "cpu": "arm64" }, "sha512-IVudM1BWfvrYO++Khtzr8q9n5Rxu7msUvoFMqzGJVdX7HfUXUDHwaH2zHZNB58svx2J56pmCUzophyaPFkcG/A=="],
"@oxfmt/binding-win32-arm64-msvc": ["@oxfmt/binding-win32-arm64-msvc@0.49.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-38K67XR++CoFFORDd4sMFwUVAnD6msYBdGTei+qvKGrRPO6S2PbrYPNL/eQQ1RgnnxOegNba0YQwg6uRkNcw6A=="],
"@oxfmt/binding-darwin-arm64": ["@oxfmt/binding-darwin-arm64@0.44.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-eWCLAIKAHfx88EqEP1Ga2yz7qVcqDU5lemn4xck+07bH182hDdprOHjbogyk0In1Djys3T0/pO2JepFnRJ41Mg=="],
"@oxfmt/binding-win32-ia32-msvc": ["@oxfmt/binding-win32-ia32-msvc@0.49.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-rXVe0HICwQF0dBgbQtBCoYf8x/SidPIdhyQl+iPuJlV7suV+qDv7yUEB3wQ4qC3nOeNxz287SwFXKzyr0kWgEg=="],
"@oxfmt/binding-darwin-x64": ["@oxfmt/binding-darwin-x64@0.44.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-eHTBznHLM49++dwz07MblQ2cOXyIgeedmE3Wgy4ptUESj38/qYZyRi1MPwC9olQJWssMeY6WI3UZ7YmU5ggvyQ=="],
"@oxfmt/binding-win32-x64-msvc": ["@oxfmt/binding-win32-x64-msvc@0.49.0", "", { "os": "win32", "cpu": "x64" }, "sha512-gwWLwSEmBBfIK/Wh7GGd658161o4RKAvHWRaRQbJm571iQXGKfyr7UKsI1vsWvDlNLc30CxJDc8mMmCvJ/kczQ=="],
"@oxfmt/binding-freebsd-x64": ["@oxfmt/binding-freebsd-x64@0.44.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-jLMmbj0u0Ft43QpkUVr/0v1ZfQCGWAvU+WznEHcN3wZC/q6ox7XeSJtk9P36CCpiDSUf3sGnzbIuG1KdEMEDJQ=="],
"@oxlint-tsgolint/darwin-arm64": ["@oxlint-tsgolint/darwin-arm64@0.22.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-4150Lpgc1YM09GcjA6GSrra1JoPjC7aOpfywLjWEY4vW0Sd1qKzqHF1WRaiw0/qUZ40OATYdv3aRd7ipPkWQbw=="],
"@oxfmt/binding-linux-arm-gnueabihf": ["@oxfmt/binding-linux-arm-gnueabihf@0.44.0", "", { "os": "linux", "cpu": "arm" }, "sha512-n+A/u/ByK1qV8FVGOwyaSpw5NPNl0qlZfgTBqHeGIqr8Qzq1tyWZ4lAaxPoe5mZqE3w88vn3+jZtMxriHPE7tg=="],
"@oxlint-tsgolint/darwin-x64": ["@oxlint-tsgolint/darwin-x64@0.22.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-vFWcPWYOgZs4HWcgS1EjUZg33NLcNfEYU49KGImmCfZWkflENrmBYV4HN/C0YeAPum6ZZ/goPSvQrB/cOD+NfA=="],
"@oxfmt/binding-linux-arm-musleabihf": ["@oxfmt/binding-linux-arm-musleabihf@0.44.0", "", { "os": "linux", "cpu": "arm" }, "sha512-5eax+FkxyCqAi3Rw0mrZFr7+KTt/XweFsbALR+B5ljWBLBl8nHe4ADrUnb1gLEfQCJLl+Ca5FIVD4xEt95AwIw=="],
"@oxlint-tsgolint/linux-arm64": ["@oxlint-tsgolint/linux-arm64@0.22.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-6LiUpP0Zir3+29FvBm7Y28q/dBjSHqTZ5MhG1Ckw4fGhI4cAvbcwXaKvbjx1TP7rRmBNOoq/M5xdpHjTb+GAew=="],
"@oxfmt/binding-linux-arm64-gnu": ["@oxfmt/binding-linux-arm64-gnu@0.44.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-58l8JaHxSGOmOMOG2CIrNsnkRJAj0YcHQCmvNACniOa/vd1iRHhlPajczegzS5jwMENlqgreyiTR9iNlke8qCw=="],
"@oxlint-tsgolint/linux-x64": ["@oxlint-tsgolint/linux-x64@0.22.1", "", { "os": "linux", "cpu": "x64" }, "sha512-fuX1hEQfpHauUbXADsfqVhRzrUrGabzGXbj5wsp2vKhV5uk/Rze8Mba9GdjFGECzvXudMGqHqxB4r6jGRdhxVA=="],
"@oxfmt/binding-linux-arm64-musl": ["@oxfmt/binding-linux-arm64-musl@0.44.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-AlObQIXyVRZ96LbtVljtFq0JqH5B92NU+BQeDFrXWBUWlCKAM0wF5GLfIhCLT5kQ3Sl+U0YjRJ7Alqj5hGQaCg=="],
"@oxlint-tsgolint/win32-arm64": ["@oxlint-tsgolint/win32-arm64@0.22.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SZidAj+jrbZf9ZjBEYW0tiNZ+KasqB2zgW26qdiPpQSF/DzURnPmXz651IeA9YsmbVdHGIooEHUmev6QJdquA=="],
"@oxfmt/binding-linux-ppc64-gnu": ["@oxfmt/binding-linux-ppc64-gnu@0.44.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-YcFE8/q/BbrCiIiM5piwbkA6GwJc5QqhMQp2yDrqQ2fuVkZ7CInb1aIijZ/k8EXc72qXMSwKpVlBv1w/MsGO/A=="],
"@oxlint-tsgolint/win32-x64": ["@oxlint-tsgolint/win32-x64@0.22.1", "", { "os": "win32", "cpu": "x64" }, "sha512-QweSk9H5lFh5Y+WUf2Kq/OAN88V6+62ZwGhP38gqdRotI90luXSMkruFTj7Q2rYrzH4ZVNaSqx7NY8JpSfIzqg=="],
"@oxfmt/binding-linux-riscv64-gnu": ["@oxfmt/binding-linux-riscv64-gnu@0.44.0", "", { "os": "linux", "cpu": "none" }, "sha512-eOdzs6RqkRzuqNHUX5C8ISN5xfGh4xDww8OEd9YAmc3OWN8oAe5bmlIqQ+rrHLpv58/0BuU48bxkhnIGjA/ATQ=="],
"@oxlint/binding-android-arm-eabi": ["@oxlint/binding-android-arm-eabi@1.64.0", "", { "os": "android", "cpu": "arm" }, "sha512-2r6Nq3XXGLHEXKkSj8JtmJ6N4gDw431DPFOg0ZoJHlNjnG6HVMm/ksQ10m0HJ8WBvwgMe1L50UHPaYZutCRPCw=="],
"@oxfmt/binding-linux-riscv64-musl": ["@oxfmt/binding-linux-riscv64-musl@0.44.0", "", { "os": "linux", "cpu": "none" }, "sha512-YBgNTxntD/QvlFUfgvh8bEdwOhXiquX8gaofZJAwYa/Xp1S1DQrFVZEeck7GFktr24DztsSp8N8WtWCBwxs0Hw=="],
"@oxlint/binding-android-arm64": ["@oxlint/binding-android-arm64@1.64.0", "", { "os": "android", "cpu": "arm64" }, "sha512-ePJMpePgg7fBv+L/hVx1xXRU5/5gd5m0obLA6hPEfLXF3GjpR8idIDbY1dhQYhyz1ms2wdTccSboo6KEd2Oxtg=="],
"@oxfmt/binding-linux-s390x-gnu": ["@oxfmt/binding-linux-s390x-gnu@0.44.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-GLIh1R6WHWshl/i4QQDNgj0WtT25aRO4HNUWEoitxiywyRdhTFmFEYT2rXlcl9U6/26vhmOqG5cRlMLG3ocaIA=="],
"@oxlint/binding-darwin-arm64": ["@oxlint/binding-darwin-arm64@1.64.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-U4DMLQd10gJLuoSTLSGbfv3bGjTlUNsScm9Dgb8wwBqmCzidf1pE1pXV4doGNxqwH3KtVng1AGTINA0NvkGLvQ=="],
"@oxfmt/binding-linux-x64-gnu": ["@oxfmt/binding-linux-x64-gnu@0.44.0", "", { "os": "linux", "cpu": "x64" }, "sha512-gZOpgTlOsLcLfAF9qgpTr7FIIFSKnQN3hDf/0JvQ4CIwMY7h+eilNjxq/CorqvYcEOu+LRt1W4ZS7KccEHLOdA=="],
"@oxlint/binding-darwin-x64": ["@oxlint/binding-darwin-x64@1.64.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-GoRIL48QWm4/TAvjN8pB1nAG+1/uqc9EdnWT9zqHeb6wsmjZtywj8VRe5aGW47Fdb64YtLOsdLqVxOvQuz98Wg=="],
"@oxfmt/binding-linux-x64-musl": ["@oxfmt/binding-linux-x64-musl@0.44.0", "", { "os": "linux", "cpu": "x64" }, "sha512-1CyS9JTB+pCUFYFI6pkQGGZaT/AY5gnhHVrQQLhFba6idP9AzVYm1xbdWfywoldTYvjxQJV6x4SuduCIfP3W+A=="],
"@oxlint/binding-freebsd-x64": ["@oxlint/binding-freebsd-x64@1.64.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-5dFkv4tkg7PxJJGS9/OjrJwjhuHczrd3OQOkRE0wHcLM+ncUnULtzEPWjqGOxTXxZnLWcB91bGiIznx89TVXyQ=="],
"@oxfmt/binding-openharmony-arm64": ["@oxfmt/binding-openharmony-arm64@0.44.0", "", { "os": "none", "cpu": "arm64" }, "sha512-bmEv70Ak6jLr1xotCbF5TxIKjsmQaiX+jFRtnGtfA03tJPf6VG3cKh96S21boAt3JZc+Vjx8PYcDuLj39vM2Pw=="],
"@oxlint/binding-linux-arm-gnueabihf": ["@oxlint/binding-linux-arm-gnueabihf@1.64.0", "", { "os": "linux", "cpu": "arm" }, "sha512-jsBqMLl/uOL5+Kq/+BtK9FrmiNGUbx8SiyZXv+WlUxA45KuwcLu9BfiSIL3I3DBDgWM3yZizDITnTK9BcqNBQg=="],
"@oxfmt/binding-win32-arm64-msvc": ["@oxfmt/binding-win32-arm64-msvc@0.44.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-yWzB+oCpSnP/dmw85eFLAT5o35Ve5pkGS2uF/UCISpIwDqf1xa7OpmtomiqY/Vzg8VyvMbuf6vroF2khF/+1Vg=="],
"@oxlint/binding-linux-arm-musleabihf": ["@oxlint/binding-linux-arm-musleabihf@1.64.0", "", { "os": "linux", "cpu": "arm" }, "sha512-1lrj8At/Uuc9GhjrVFBQo0NEjfBrTkzpmtHIGAhNnIXqn1CAyGL+qrztUsXb2GIluJrpl9Q7qRLJOb/NqydacQ=="],
"@oxfmt/binding-win32-ia32-msvc": ["@oxfmt/binding-win32-ia32-msvc@0.44.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-TcWpo18xEIE3AmIG2kpr3kz5IEhQgnx0lazl2+8L+3eTopOAUevQcmlr4nhguImNWz0OMeOZrYZOhJNCf16nlQ=="],
"@oxlint/binding-linux-arm64-gnu": ["@oxlint/binding-linux-arm64-gnu@1.64.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-HpSQbubwh03mMhAdy2BYtad/fsY8vDFHDAb6bUwuCYg2VD3xCQgn6ArKcO0oZyLCheacKTv4PrF3Mfu5hgoE2g=="],
"@oxfmt/binding-win32-x64-msvc": ["@oxfmt/binding-win32-x64-msvc@0.44.0", "", { "os": "win32", "cpu": "x64" }, "sha512-oj8aLkPJZppIM4CMQNsyir9ybM1Xw/CfGPTSsTnzpVGyljgfbdP0EVUlURiGM0BDrmw5psQ6ArmGCcUY/yABaQ=="],
"@oxlint/binding-linux-arm64-musl": ["@oxlint/binding-linux-arm64-musl@1.64.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-00QQ0h0Y7u0G69BgiH3+ky2aaq/QvkDL6DYok8htIuJHxybiux5aQ8jwmg8qIk9wha6UagUP2BAwAzbemcJbpg=="],
"@oxlint-tsgolint/darwin-arm64": ["@oxlint-tsgolint/darwin-arm64@0.20.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-KKQcIHZHMxqpHUA1VXIbOG6chNCFkUWbQy6M+AFVtPKkA/3xAeJkJ3njoV66bfzwPHRcWQO+kcj5XqtbkjakoA=="],
"@oxlint/binding-linux-ppc64-gnu": ["@oxlint/binding-linux-ppc64-gnu@1.64.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-2GaimTV6EMW+s5HS0An3oGbQme3BgHswvfVdGk3EB57Xe9+/gyT+Qd7lNVzb3rtir52vbIPzXfaYArzs5b5zcw=="],
"@oxlint-tsgolint/darwin-x64": ["@oxlint-tsgolint/darwin-x64@0.20.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-7HeVMuclGfG+NLZi2ybY0T4fMI7/XxO/208rJk+zEIloKkVnlh11Wd241JMGwgNFXn+MLJbOqOfojDb2Dt4L1g=="],
"@oxlint/binding-linux-riscv64-gnu": ["@oxlint/binding-linux-riscv64-gnu@1.64.0", "", { "os": "linux", "cpu": "none" }, "sha512-H46AtFb9wypjoVwGdlxrm0DsD809NGmtiK9HiyPKTxkSte2YjhC4S+00rOIrwCaxcyPiGid3Y3OMXp5KMAkGZw=="],
"@oxlint-tsgolint/linux-arm64": ["@oxlint-tsgolint/linux-arm64@0.20.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-zxhUwz+WSxE6oWlZLK2z2ps9yC6ebmgoYmjAl0Oa48+GqkZ56NVgo+wb8DURNv6xrggzHStQxqQxe3mK51HZag=="],
"@oxlint/binding-linux-riscv64-musl": ["@oxlint/binding-linux-riscv64-musl@1.64.0", "", { "os": "linux", "cpu": "none" }, "sha512-HEgsidjjvvyzdg82icYkuFCf7REDV7B9JFwbIMbVwrKLBY0MrXX+bku3POn/hduZ2yW91IyVDUMq0Bf02KwXQw=="],
"@oxlint-tsgolint/linux-x64": ["@oxlint-tsgolint/linux-x64@0.20.0", "", { "os": "linux", "cpu": "x64" }, "sha512-/1l6FnahC9im8PK+Ekkx/V3yetO/PzZnJegE2FXcv/iXEhbeVxP/ouiTYcUQu9shT1FWJCSNti1VJHH+21Y1dg=="],
"@oxlint/binding-linux-s390x-gnu": ["@oxlint/binding-linux-s390x-gnu@1.64.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-Axvm8qryotmKN00P5w4JapaSjvP2LOSbdbBJiX+2SuHd3QzhW7TUc8skqgw+ahQZ5DmzEYeHCqauvW8f32Ns6Q=="],
"@oxlint-tsgolint/win32-arm64": ["@oxlint-tsgolint/win32-arm64@0.20.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-oPZ5Yz8sVdo7P/5q+i3IKeix31eFZ55JAPa1+RGPoe9PoaYVsdMvR6Jvib6YtrqoJnFPlg3fjEjlEPL8VBKYJA=="],
"@oxlint/binding-linux-x64-gnu": ["@oxlint/binding-linux-x64-gnu@1.64.0", "", { "os": "linux", "cpu": "x64" }, "sha512-cR60vSd7+m+KRZ3GQGfDxWwahW5RMXg0qlGvAluZr0fTUYvw0H9N9AXAF/M/PMqgytyqvVNmBAkJG9l7U30Y1g=="],
"@oxlint-tsgolint/win32-x64": ["@oxlint-tsgolint/win32-x64@0.20.0", "", { "os": "win32", "cpu": "x64" }, "sha512-4stx8RHj3SP9vQyRF/yZbz5igtPvYMEUR8CUoha4BVNZihi39DpCR8qkU7lpjB5Ga1DRMo2pHaA4bdTOMaY4mw=="],
"@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-android-arm-eabi": ["@oxlint/binding-android-arm-eabi@1.59.0", "", { "os": "android", "cpu": "arm" }, "sha512-etYDw/UaEv936AQUd/CRMBVd+e+XuuU6wC+VzOv1STvsTyZenLChepLWqLtnyTTp4YMlM22ypzogDDwqYxv5cg=="],
"@oxlint/binding-openharmony-arm64": ["@oxlint/binding-openharmony-arm64@1.64.0", "", { "os": "none", "cpu": "arm64" }, "sha512-kfhkGfCdoXLSxEkrhDlJrvBYajGmq+ma4EMc53dsOWTq+rIBOlI0vTBmpZNnM5oH2LY/K/w1HAK+UQEgjgpVUg=="],
"@oxlint/binding-android-arm64": ["@oxlint/binding-android-arm64@1.59.0", "", { "os": "android", "cpu": "arm64" }, "sha512-TgLc7XVLKH2a4h8j3vn1MDjfK33i9MY60f/bKhRGWyVzbk5LCZ4X01VZG7iHrMmi5vYbAp8//Ponigx03CLsdw=="],
"@oxlint/binding-win32-arm64-msvc": ["@oxlint/binding-win32-arm64-msvc@1.64.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-r/cNKBFieONoVu2bb1KkVouq9W+edDUgHumXJGphCRRj+U0xaD4nanrw8ZOqo0IsutPkEM4vCcGBpak6x5aXMg=="],
"@oxlint/binding-darwin-arm64": ["@oxlint/binding-darwin-arm64@1.59.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-DXyFPf5ZKldMLloRHx/B9fsxsiTQomaw7cmEW3YIJko2HgCh+GUhp9gGYwHrqlLJPsEe3dYj9JebjX92D3j3AA=="],
"@oxlint/binding-win32-ia32-msvc": ["@oxlint/binding-win32-ia32-msvc@1.64.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-tUw0xUUwEFVZbpJoeCblkv8SJA4Xz3CdXCJbAnBsiNLyxDrk2tLcxEAS6M73Q7hHHDg3OtwI8vZVK3t5RJt4Gw=="],
"@oxlint/binding-darwin-x64": ["@oxlint/binding-darwin-x64@1.59.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-LgvrsdgVLX1qWqIEmNsSmMXJhpAWdtUQ0M+oR0CySwi+9IHWyOGuIL8w8+u/kbZNMyZr4WUyYB5i0+D+AKgkLg=="],
"@oxlint/binding-win32-x64-msvc": ["@oxlint/binding-win32-x64-msvc@1.64.0", "", { "os": "win32", "cpu": "x64" }, "sha512-9CBR+LO0JVST87fNTzzNxS5I29jIUO5gxT9i9+M3SDHHALElj9sY1Prf12tad3vIRC6OD7Ehtvvh+sn13vSwHw=="],
"@oxlint/binding-freebsd-x64": ["@oxlint/binding-freebsd-x64@1.59.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-bOJhqX/ny4hrFuTPlyk8foSRx/vLRpxJh0jOOKN2NWW6FScXHPAA5rQbrwdQPcgGB5V8Ua51RS03fke8ssBcug=="],
"@oxlint/binding-linux-arm-gnueabihf": ["@oxlint/binding-linux-arm-gnueabihf@1.59.0", "", { "os": "linux", "cpu": "arm" }, "sha512-vVUXxYMF9trXCsz4m9H6U0IjehosVHxBzVgJUxly1uz4W1PdDyicaBnpC0KRXsHYretLVe+uS9pJy8iM57Kujw=="],
"@oxlint/binding-linux-arm-musleabihf": ["@oxlint/binding-linux-arm-musleabihf@1.59.0", "", { "os": "linux", "cpu": "arm" }, "sha512-TULQW8YBPGRWg5yZpFPL54HLOnJ3/HiX6VenDPi6YfxB/jlItwSMFh3/hCeSNbh+DAMaE1Py0j5MOaivHkI/9Q=="],
"@oxlint/binding-linux-arm64-gnu": ["@oxlint/binding-linux-arm64-gnu@1.59.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-Gt54Y4eqSgYJ90xipm24xeyaPV854706o/kiT8oZvUt3VDY7qqxdqyGqchMaujd87ib+/MXvnl9WkK8Cc1BExg=="],
"@oxlint/binding-linux-arm64-musl": ["@oxlint/binding-linux-arm64-musl@1.59.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-3CtsKp7NFB3OfqQzbuAecrY7GIZeiv7AD+xutU4tefVQzlfmTI7/ygWLrvkzsDEjTlMq41rYHxgsn6Yh8tybmA=="],
"@oxlint/binding-linux-ppc64-gnu": ["@oxlint/binding-linux-ppc64-gnu@1.59.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-K0diOpT3ncDmOfl9I1HuvpEsAuTxkts0VYwIv/w6Xiy9CdwyPBVX88Ga9l8VlGgMrwBMnSY4xIvVlVY/fkQk7Q=="],
"@oxlint/binding-linux-riscv64-gnu": ["@oxlint/binding-linux-riscv64-gnu@1.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-xAU7+QDU6kTJJ7mJLOGgo7oOjtAtkKyFZ0Yjdb5cEo3DiCCPFLvyr08rWiQh6evZ7RiUTf+o65NY/bqttzJiQQ=="],
"@oxlint/binding-linux-riscv64-musl": ["@oxlint/binding-linux-riscv64-musl@1.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-KUmZmKlTTyauOnvUNVxK7G40sSSx0+w5l1UhaGsC6KPpOYHenx2oqJTnabmpLJicok7IC+3Y6fXAUOMyexaeJQ=="],
"@oxlint/binding-linux-s390x-gnu": ["@oxlint/binding-linux-s390x-gnu@1.59.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-4usRxC8gS0PGdkHnRmwJt/4zrQNZyk6vL0trCxwZSsAKM+OxhB8nKiR+mhjdBbl8lbMh2gc3bZpNN/ik8c4c2A=="],
"@oxlint/binding-linux-x64-gnu": ["@oxlint/binding-linux-x64-gnu@1.59.0", "", { "os": "linux", "cpu": "x64" }, "sha512-s/rNE2gDmbwAOOP493xk2X7M8LZfI1LJFSSW1+yanz3vuQCFPiHkx4GY+O1HuLUDtkzGlhtMrIcxxzyYLv308w=="],
"@oxlint/binding-linux-x64-musl": ["@oxlint/binding-linux-x64-musl@1.59.0", "", { "os": "linux", "cpu": "x64" }, "sha512-+yYj1udJa2UvvIUmEm0IcKgc0UlPMgz0nsSTvkPL2y6n0uU5LgIHSwVu4AHhrve6j9BpVSoRksnz8c9QcvITJA=="],
"@oxlint/binding-openharmony-arm64": ["@oxlint/binding-openharmony-arm64@1.59.0", "", { "os": "none", "cpu": "arm64" }, "sha512-bUplUb48LYsB3hHlQXP2ZMOenpieWoOyppLAnnAhuPag3MGPnt+7caxE3w/Vl9wpQsTA3gzLntQi9rxWrs7Xqg=="],
"@oxlint/binding-win32-arm64-msvc": ["@oxlint/binding-win32-arm64-msvc@1.59.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-/HLsLuz42rWl7h7ePdmMTpHm2HIDmPtcEMYgm5BBEHiEiuNOrzMaUpd2z7UnNni5LGN9obJy2YoAYBLXQwazrA=="],
"@oxlint/binding-win32-ia32-msvc": ["@oxlint/binding-win32-ia32-msvc@1.59.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-rUPy+JnanpPwV/aJCPnxAD1fW50+XPI0VkWr7f0vEbqcdsS8NpB24Rw6RsS7SdpFv8Dw+8ugCwao5nCFbqOUSg=="],
"@oxlint/binding-win32-x64-msvc": ["@oxlint/binding-win32-x64-msvc@1.59.0", "", { "os": "win32", "cpu": "x64" }, "sha512-xkE7puteDS/vUyRngLXW0t8WgdWoS/tfxXjhP/P7SMqPDx+hs44SpssO3h3qmTqECYEuXBUPzcAw5257Ka+ofA=="],
"@protobuf-ts/runtime": ["@protobuf-ts/runtime@2.11.1", "", {}, "sha512-KuDaT1IfHkugM2pyz+FwiY80ejWrkH1pAtOBOZFuR6SXEFTsnb/jiQWQ1rCIrcKx2BtyxnxW6BWwsVSA/Ie+WQ=="],
"@protobuf-ts/runtime-rpc": ["@protobuf-ts/runtime-rpc@2.11.1", "", { "dependencies": { "@protobuf-ts/runtime": "^2.11.1" } }, "sha512-4CqqUmNA+/uMz00+d3CYKgElXO9VrEbucjnBFEjqI4GuDrEQ32MaI3q+9qPBvIGOlL4PmHXrzM32vBPWRhQKWQ=="],
"@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.11", "", { "dependencies": { "bun-types": "1.3.11" } }, "sha512-5vPne5QvtpjGpsGYXiFyycfpDF2ECyPcTSsFBMa0fraoxiQyMJ3SmuQIGhzPg2WJuWxVBoxWJ2kClYTcw/4fAg=="],
"@types/node": ["@types/node@20.19.37", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw=="],
"@types/js-yaml": ["@types/js-yaml@4.0.9", "", {}, "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg=="],
"@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20260410.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20260410.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20260410.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20260410.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20260410.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20260410.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20260410.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20260410.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-K3TIwBw4XGQM33wW8KUqRU7r6ZY1IqB8chk1u1kT+CDj4iu+eQ6jCXgU7EDxmpJ++gbNcIf8iBYgWgYNssrhZQ=="],
"@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="],
"@typescript/native-preview-darwin-arm64": ["@typescript/native-preview-darwin-arm64@7.0.0-dev.20260410.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bpLYm6woXd8BECzV9AQvPqISVeohpekK1qwpRopvNIxydhRQ4fEjZsS7EtDYpqHAW4/u1uEv07P9/iS6TAL1fQ=="],
"@types/semver": ["@types/semver@7.7.1", "", {}, "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA=="],
"@typescript/native-preview-darwin-x64": ["@typescript/native-preview-darwin-x64@7.0.0-dev.20260410.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-V8bW8g5hgu+bAwGvTqF1kilkkoDgxhxi5egrdMUeWQkR+MIisoBQeaAupqMpLoSkqZsc/kKucM0zwBNC/KRU3Q=="],
"@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20260409.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20260409.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20260409.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20260409.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20260409.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20260409.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20260409.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20260409.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-CV1HEMGo1xCySwUJbCQOF+mmrTue8KTJ1Od2kKWhcbOpu8fPBfaqIpbAM6tGLcNEykEjMMTYHc/VTLbMgxdScQ=="],
"@typescript/native-preview-linux-arm": ["@typescript/native-preview-linux-arm@7.0.0-dev.20260410.1", "", { "os": "linux", "cpu": "arm" }, "sha512-NO6Ci65ADadOCr2ycTxOyCgC5kyk+Ryjl8k5c78mz9sKDxYqwEtryFFjLqitAG+rejtJbnUq897WRICjAOwslA=="],
"@typescript/native-preview-darwin-arm64": ["@typescript/native-preview-darwin-arm64@7.0.0-dev.20260409.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-GcRRnaoeZVrbC47woQ/2t3vPoQcTSjsWPEAQGtwNSdw7Z9TKxG4ES22ghJIQXd3ncTRCMJ+XELnnuqxVutkJ9w=="],
"@typescript/native-preview-linux-arm64": ["@typescript/native-preview-linux-arm64@7.0.0-dev.20260410.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-MOluRRAhv46s9ScFmePa0InMHmpZ/z0Evc11RrTKsg+bN8BR7sWoAtFq6IujEDK9WVP7YmEYtBRgEfMLuqVojw=="],
"@typescript/native-preview-darwin-x64": ["@typescript/native-preview-darwin-x64@7.0.0-dev.20260409.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-7s8DXAa0Xpu/8PEjYIc4I36Ju7eVpoz9k3E+3WQdOF8pIPWYohiOj+zi68m9XYQck+rnkjUFo26ThVKqVetoMA=="],
"@typescript/native-preview-linux-x64": ["@typescript/native-preview-linux-x64@7.0.0-dev.20260410.1", "", { "os": "linux", "cpu": "x64" }, "sha512-IofIUrMGjXmZKDEMaRgshzOne0EQZtx9vE/6URHfgmDnWLDKWzz9eQ2qWmvsFD2vOBbgc6GwVWEq6XTHMEfx2A=="],
"@typescript/native-preview-linux-arm": ["@typescript/native-preview-linux-arm@7.0.0-dev.20260409.1", "", { "os": "linux", "cpu": "arm" }, "sha512-fOa07JBUXQpEPq+024g346inYZ2xp63ELuoRq6J0jwDWQ/ftCCuvdQNMncwFhsm1qlMdKT3S68NrnSxX16hiaw=="],
"@typescript/native-preview-win32-arm64": ["@typescript/native-preview-win32-arm64@7.0.0-dev.20260410.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-TXmE+wovQqRo+qAhaewB0MPB9esgayvSHr6vFlCpHykHqbDl3FUucuC4F8yU6zVOA3UqXTk4/GHeLsAvU7YEgQ=="],
"@typescript/native-preview-linux-arm64": ["@typescript/native-preview-linux-arm64@7.0.0-dev.20260409.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-cGTzTUqRGlIDwdtkDy6qTrvrqpe27W4CdgnFn0FpxpiWnaIi3wqjlzQ1grtqrqainw/yuPy5hn/I86sQgN6nvA=="],
"@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-linux-x64": ["@typescript/native-preview-linux-x64@7.0.0-dev.20260409.1", "", { "os": "linux", "cpu": "x64" }, "sha512-lQrbc/BJKBxQrR1ttBDU5sYY1Hb2moFQgHL20T6nbapNqGpK4pzy64p+NK39O93D4omiCSk04pkchBCVrMPSAg=="],
"bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="],
"@typescript/native-preview-win32-arm64": ["@typescript/native-preview-win32-arm64@7.0.0-dev.20260409.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-kmCafMo1xZlYx+9WnfpeZJ2tnB/CcJdR8QPX7j9vqcpe51D7b7Intmr921dD48KGpVh5YgjQ1MEFE5mjGqGMaA=="],
"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=="],
"@typescript/native-preview-win32-x64": ["@typescript/native-preview-win32-x64@7.0.0-dev.20260409.1", "", { "os": "win32", "cpu": "x64" }, "sha512-WRd+JpQipTsE15QgYr3w7J0f1NKvGcq2QEgmcq8hB0WZA1X2WhQopNu+MpPQ3tdDD42VjMhm8ZoB8HpuOoXK5w=="],
"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=="],
"@typespec/ts-http-runtime": ["@typespec/ts-http-runtime@0.3.5", "", { "dependencies": { "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "tslib": "^2.6.2" } }, "sha512-yURCknZhvywvQItHMMmFSo+fq5arCUIyz/CVk7jD89MSai7dkaX8ufjCWp3NttLojoTVbcE72ri+be/TnEbMHw=="],
"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=="],
"agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
"brace-expansion": ["brace-expansion@1.1.13", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w=="],
"bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="],
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="],
"fast-xml-builder": ["fast-xml-builder@1.1.4", "", { "dependencies": { "path-expression-matcher": "^1.1.3" } }, "sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg=="],
"fast-xml-parser": ["fast-xml-parser@5.5.11", "", { "dependencies": { "fast-xml-builder": "^1.1.4", "path-expression-matcher": "^1.4.0", "strnum": "^2.2.3" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-QL0eb0YbSTVWF6tTf1+LEMSgtCEjBYPpnAjoLC8SscESlAjXEIRJ7cHtLG0pLeDFaZLa4VKZLArtA/60ZS7vyA=="],
"http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="],
"https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
"js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
"minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"oxfmt": ["oxfmt@0.44.0", "", { "dependencies": { "tinypool": "2.1.0" }, "optionalDependencies": { "@oxfmt/binding-android-arm-eabi": "0.44.0", "@oxfmt/binding-android-arm64": "0.44.0", "@oxfmt/binding-darwin-arm64": "0.44.0", "@oxfmt/binding-darwin-x64": "0.44.0", "@oxfmt/binding-freebsd-x64": "0.44.0", "@oxfmt/binding-linux-arm-gnueabihf": "0.44.0", "@oxfmt/binding-linux-arm-musleabihf": "0.44.0", "@oxfmt/binding-linux-arm64-gnu": "0.44.0", "@oxfmt/binding-linux-arm64-musl": "0.44.0", "@oxfmt/binding-linux-ppc64-gnu": "0.44.0", "@oxfmt/binding-linux-riscv64-gnu": "0.44.0", "@oxfmt/binding-linux-riscv64-musl": "0.44.0", "@oxfmt/binding-linux-s390x-gnu": "0.44.0", "@oxfmt/binding-linux-x64-gnu": "0.44.0", "@oxfmt/binding-linux-x64-musl": "0.44.0", "@oxfmt/binding-openharmony-arm64": "0.44.0", "@oxfmt/binding-win32-arm64-msvc": "0.44.0", "@oxfmt/binding-win32-ia32-msvc": "0.44.0", "@oxfmt/binding-win32-x64-msvc": "0.44.0" }, "bin": { "oxfmt": "bin/oxfmt" } }, "sha512-lnncqvHewyRvaqdrnntVIrZV2tEddz8lbvPsQzG/zlkfvgZkwy0HP1p/2u1aCDToeg1jb9zBpbJdfkV73Itw+w=="],
"oxlint": ["oxlint@1.59.0", "", { "optionalDependencies": { "@oxlint/binding-android-arm-eabi": "1.59.0", "@oxlint/binding-android-arm64": "1.59.0", "@oxlint/binding-darwin-arm64": "1.59.0", "@oxlint/binding-darwin-x64": "1.59.0", "@oxlint/binding-freebsd-x64": "1.59.0", "@oxlint/binding-linux-arm-gnueabihf": "1.59.0", "@oxlint/binding-linux-arm-musleabihf": "1.59.0", "@oxlint/binding-linux-arm64-gnu": "1.59.0", "@oxlint/binding-linux-arm64-musl": "1.59.0", "@oxlint/binding-linux-ppc64-gnu": "1.59.0", "@oxlint/binding-linux-riscv64-gnu": "1.59.0", "@oxlint/binding-linux-riscv64-musl": "1.59.0", "@oxlint/binding-linux-s390x-gnu": "1.59.0", "@oxlint/binding-linux-x64-gnu": "1.59.0", "@oxlint/binding-linux-x64-musl": "1.59.0", "@oxlint/binding-openharmony-arm64": "1.59.0", "@oxlint/binding-win32-arm64-msvc": "1.59.0", "@oxlint/binding-win32-ia32-msvc": "1.59.0", "@oxlint/binding-win32-x64-msvc": "1.59.0" }, "peerDependencies": { "oxlint-tsgolint": ">=0.18.0" }, "optionalPeers": ["oxlint-tsgolint"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-0xBLeGGjP4vD9pygRo8iuOkOzEU1MqOnfiOl7KYezL/QvWL8NUg6n03zXc7ZVqltiOpUxBk2zgHI3PnRIEdAvw=="],
"oxlint-tsgolint": ["oxlint-tsgolint@0.20.0", "", { "optionalDependencies": { "@oxlint-tsgolint/darwin-arm64": "0.20.0", "@oxlint-tsgolint/darwin-x64": "0.20.0", "@oxlint-tsgolint/linux-arm64": "0.20.0", "@oxlint-tsgolint/linux-x64": "0.20.0", "@oxlint-tsgolint/win32-arm64": "0.20.0", "@oxlint-tsgolint/win32-x64": "0.20.0" }, "bin": { "tsgolint": "bin/tsgolint.js" } }, "sha512-/Uc9TQyN1l8w9QNvXtVHYtz+SzDJHKpb5X0UnHodl0BVzijUPk0LPlDOHAvogd1UI+iy9ZSF6gQxEqfzUxCULQ=="],
"path-expression-matcher": ["path-expression-matcher@1.5.0", "", {}, "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ=="],
"semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
"strnum": ["strnum@2.2.3", "", {}, "sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg=="],
"tinypool": ["tinypool@2.1.0", "", {}, "sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw=="],
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"tunnel": ["tunnel@0.0.6", "", {}, "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="],
"undici": ["undici@6.24.1", "", {}, "sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA=="],
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
"bun-types/@types/node": ["@types/node@20.19.37", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw=="],
"bun-types/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
}
}

94560
dist/main.js vendored Normal file

File diff suppressed because one or more lines are too long

73430
dist/post.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -37,6 +37,21 @@ To pin a specific CLI version:
version: 2.84.2
```
To cache the Docker images pulled by `supabase start`:
```yaml
- uses: supabase/setup-cli@v2
with:
version: 2.84.2
cache: true
- run: supabase start
```
The first run pulls the images from the registry. Later runs can restore the
same image archive from the GitHub Actions cache before `supabase start` runs.
Use `cache-key` when your workflow flags or generated config change the image
set.
## Resources
- **Source Code**: <https://github.com/supabase/setup-cli>

View File

@@ -14,25 +14,33 @@
},
"type": "module",
"scripts": {
"all": "bun run format && bun run lint && bun run coverage",
"ci": "bun run format:check && bun run lint && bun run coverage",
"all": "bun run format && bun run lint && bun run coverage && bun run build",
"build": "bun build src/main.ts src/post.ts --target=node --format=esm --outdir=dist",
"ci": "bun run format:check && bun run lint && bun run coverage && bun run build",
"coverage": "bun test --coverage --coverage-reporter=text --coverage-reporter=lcov",
"format": "bun x oxfmt --write . '!coverage/**'",
"format:check": "bun x oxfmt --check . '!coverage/**'",
"format": "bun x oxfmt --write . '!coverage/**' '!dist/**'",
"format:check": "bun x oxfmt --check . '!coverage/**' '!dist/**'",
"lint": "bun x oxlint --deny-warnings --type-aware --type-check --tsconfig tsconfig.json src",
"test": "bun test",
"typecheck": "bun x tsgo -p tsconfig.json --noEmit"
},
"dependencies": {
"@actions/core": "^3.0.1"
"@actions/cache": "^6.0.0",
"@actions/core": "^3.0.0",
"@actions/tool-cache": "^4.0.0",
"js-yaml": "^4.1.1",
"semver": "^7.7.4"
},
"devDependencies": {
"@tsconfig/bun": "^1.0.10",
"@types/bun": "^1.3.14",
"@typescript/native-preview": "^7.0.0-dev.20260410.1",
"oxfmt": "^0.49.0",
"oxlint": "^1.64.0",
"oxlint-tsgolint": "^0.22.1"
"@types/bun": "^1.3.11",
"@types/js-yaml": "^4.0.9",
"@types/node": "^24",
"@types/semver": "^7.7.1",
"@typescript/native-preview": "^7.0.0-dev.20260409.1",
"oxfmt": "^0.44.0",
"oxlint": "^1.59.0",
"oxlint-tsgolint": "^0.20.0"
},
"engines": {
"bun": ">=1.3.10"

168
src/cache.test.ts Normal file
View File

@@ -0,0 +1,168 @@
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterEach, expect, mock, spyOn, test } from "bun:test";
import * as cacheAction from "@actions/cache";
import * as core from "@actions/core";
import {
collectDockerImageRefs,
createDockerImageCacheKey,
restoreDockerImageCache,
saveDockerImageCache,
} from "./cache";
const originalRunnerTemp = process.env.RUNNER_TEMP;
const originalWorkspace = process.env.GITHUB_WORKSPACE;
const tempDirs = new Set<string>();
afterEach(() => {
mock.restore();
process.env.RUNNER_TEMP = originalRunnerTemp;
process.env.GITHUB_WORKSPACE = originalWorkspace;
for (const dir of tempDirs) {
rmSync(dir, { force: true, recursive: true });
}
tempDirs.clear();
});
function createTempDir(prefix: string): string {
const dir = mkdtempSync(path.join(os.tmpdir(), prefix));
tempDirs.add(dir);
return dir;
}
test("creates a docker image cache key from runner, version, registry, and config", () => {
const workspace = createTempDir("setup-cli-workspace-");
mkdirSync(path.join(workspace, "supabase"), { recursive: true });
writeFileSync(path.join(workspace, "supabase", "config.toml"), 'project_id = "test"\n');
process.env.GITHUB_WORKSPACE = workspace;
const key = createDockerImageCacheKey("supabase 2.84.2", "ghcr.io");
expect(key).toStartWith(`supabase-cli-containers-v1-${process.platform}-${process.arch}-`);
expect(key).toContain("supabase-2.84.2-ghcr.io-");
});
test("collects images from labeled containers and Supabase image repositories", async () => {
const run = mock(async (_file: string, args: string[]) => {
if (args[0] === "ps") {
return {
stdout: "ghcr.io/supabase/postgres:15.8.1\ncustom/image:latest\n",
stderr: "",
};
}
return {
stdout:
"ghcr.io/supabase/studio:2026.04.08\npublic.ecr.aws/supabase/kong:2.8.1\nlibrary/postgres:16\n<none>:<none>\n",
stderr: "",
};
});
expect(await collectDockerImageRefs(run)).toEqual([
"custom/image:latest",
"ghcr.io/supabase/postgres:15.8.1",
"ghcr.io/supabase/studio:2026.04.08",
"public.ecr.aws/supabase/kong:2.8.1",
]);
});
test("restore skips docker and cache calls when cache input is disabled", async () => {
const run = mock(async () => ({ stdout: "", stderr: "" }));
const restoreCache = spyOn(cacheAction, "restoreCache").mockImplementation(async () => undefined);
const spies = {
getBooleanInput: spyOn(core, "getBooleanInput").mockImplementation(() => false),
getInput: spyOn(core, "getInput").mockImplementation(() => ""),
setOutput: spyOn(core, "setOutput").mockImplementation(() => {}),
saveState: spyOn(core, "saveState").mockImplementation(() => {}),
};
await restoreDockerImageCache("supabase 2.84.2", "ghcr.io", run);
expect(spies.setOutput).toHaveBeenCalledWith("cache-hit", "false");
expect(spies.saveState).toHaveBeenCalledWith("cache-enabled", "false");
expect(run).not.toHaveBeenCalled();
expect(restoreCache).not.toHaveBeenCalled();
});
test("restore loads a docker archive on exact cache hit", async () => {
const temp = createTempDir("setup-cli-runner-");
process.env.RUNNER_TEMP = temp;
const calls: string[][] = [];
const run = mock(async (_file: string, args: string[]) => {
calls.push(args);
return { stdout: "ok\n", stderr: "" };
});
const restoreCache = spyOn(cacheAction, "restoreCache").mockImplementation(
async (paths: string[], key: string) => {
writeFileSync(paths[0]!, "archive");
return key;
},
);
const spies = {
getBooleanInput: spyOn(core, "getBooleanInput").mockImplementation(() => true),
getInput: spyOn(core, "getInput").mockImplementation((name: string) =>
name === "cache-key" ? "cache-key" : "",
),
setOutput: spyOn(core, "setOutput").mockImplementation(() => {}),
saveState: spyOn(core, "saveState").mockImplementation(() => {}),
info: spyOn(core, "info").mockImplementation(() => {}),
warning: spyOn(core, "warning").mockImplementation(() => {}),
};
await restoreDockerImageCache("supabase 2.84.2", "ghcr.io", run);
expect(restoreCache).toHaveBeenCalledWith(
[path.join(temp, "setup-supabase-cli", "supabase-cli-docker-images.tar")],
"cache-key",
);
expect(spies.setOutput).toHaveBeenCalledWith("cache-hit", "true");
expect(spies.saveState).toHaveBeenCalledWith("cache-hit", "true");
expect(calls).toContainEqual([
"load",
"-i",
path.join(temp, "setup-supabase-cli", "supabase-cli-docker-images.tar"),
]);
expect(spies.warning).not.toHaveBeenCalled();
});
test("post saves collected Supabase docker images", async () => {
const temp = createTempDir("setup-cli-runner-");
process.env.RUNNER_TEMP = temp;
const calls: string[][] = [];
const run = mock(async (_file: string, args: string[]) => {
calls.push(args);
if (args[0] === "ps") {
return { stdout: "ghcr.io/supabase/postgres:15.8.1\n", stderr: "" };
}
if (args[0] === "image") {
return { stdout: "ghcr.io/supabase/studio:2026.04.08\n", stderr: "" };
}
return { stdout: "ok\n", stderr: "" };
});
const saveCache = spyOn(cacheAction, "saveCache").mockImplementation(async () => 1);
const state = new Map([
["cache-enabled", "true"],
["cache-hit", "false"],
["cache-primary-key", "cache-key"],
["cache-archive-path", path.join(temp, "setup-supabase-cli", "supabase-cli-docker-images.tar")],
]);
spyOn(core, "getState").mockImplementation((name: string) => state.get(name) ?? "");
spyOn(core, "info").mockImplementation(() => {});
spyOn(core, "warning").mockImplementation(() => {});
await saveDockerImageCache(run);
expect(calls).toContainEqual([
"save",
"-o",
path.join(temp, "setup-supabase-cli", "supabase-cli-docker-images.tar"),
"ghcr.io/supabase/postgres:15.8.1",
"ghcr.io/supabase/studio:2026.04.08",
]);
expect(saveCache).toHaveBeenCalledWith(
[path.join(temp, "setup-supabase-cli", "supabase-cli-docker-images.tar")],
"cache-key",
);
});

254
src/cache.ts Normal file
View File

@@ -0,0 +1,254 @@
import * as cache from "@actions/cache";
import * as core from "@actions/core";
import { execFile as execFileCallback } from "node:child_process";
import { createHash } from "node:crypto";
import { existsSync, mkdirSync, readFileSync } from "node:fs";
import os from "node:os";
import path from "node:path";
import { promisify } from "node:util";
const CLI_CONFIG_REGISTRY = "SUPABASE_INTERNAL_IMAGE_REGISTRY";
const CACHE_ARCHIVE = "supabase-cli-docker-images.tar";
const CACHE_DIR = "setup-supabase-cli";
const CACHE_KEY_VERSION = "v1";
const DEFAULT_REGISTRY = "public.ecr.aws";
const GHCR_REGISTRY = "ghcr.io";
const CACHE_INPUT = "cache";
const CACHE_KEY_INPUT = "cache-key";
const CACHE_HIT_OUTPUT = "cache-hit";
const STATE_ENABLED = "cache-enabled";
const STATE_PRIMARY_KEY = "cache-primary-key";
const STATE_ARCHIVE_PATH = "cache-archive-path";
const STATE_CACHE_HIT = "cache-hit";
const CLI_PROJECT_LABEL = "com.supabase.cli.project";
const SUPABASE_IMAGE_PREFIXES = ["ghcr.io/supabase/", "public.ecr.aws/supabase/", "supabase/"];
type ExecFile = (
file: string,
args: string[],
options?: { maxBuffer?: number },
) => Promise<{ stdout: string; stderr: string }>;
const execFile = promisify(execFileCallback) as ExecFile;
function sanitizeCacheKeyPart(value: string): string {
return value.replace(/[^A-Za-z0-9_.-]/g, "-").replace(/-+/g, "-");
}
function hashFile(filePath: string): string | null {
if (!existsSync(filePath)) {
return null;
}
try {
return createHash("sha256").update(readFileSync(filePath)).digest("hex");
} catch {
return null;
}
}
function getConfigHash(): string {
const workspaceRoot = process.env.GITHUB_WORKSPACE?.trim();
if (!workspaceRoot) {
return "no-config";
}
return hashFile(path.join(workspaceRoot, "supabase", "config.toml")) ?? "no-config";
}
function getCacheArchivePath(): string {
const runnerTemp = process.env.RUNNER_TEMP?.trim() || os.tmpdir();
return path.join(runnerTemp, CACHE_DIR, CACHE_ARCHIVE);
}
export function getImageRegistry(): string {
return process.env[CLI_CONFIG_REGISTRY]?.trim() || DEFAULT_REGISTRY;
}
export function getGhcrImageRegistry(): string {
return GHCR_REGISTRY;
}
export function createDockerImageCacheKey(installedVersion: string, registry: string): string {
return [
"supabase-cli-containers",
CACHE_KEY_VERSION,
sanitizeCacheKeyPart(process.platform),
sanitizeCacheKeyPart(process.arch),
sanitizeCacheKeyPart(installedVersion),
sanitizeCacheKeyPart(registry),
getConfigHash(),
].join("-");
}
function getPrimaryCacheKey(installedVersion: string, registry: string): string {
const cacheKeyInput = core.getInput(CACHE_KEY_INPUT).trim();
if (cacheKeyInput) {
return cacheKeyInput;
}
return createDockerImageCacheKey(installedVersion, registry);
}
async function isDockerAvailable(run: ExecFile = execFile): Promise<boolean> {
try {
await run("docker", ["version"]);
return true;
} catch {
return false;
}
}
async function runDocker(args: string[], run: ExecFile = execFile): Promise<string> {
const { stdout } = await run("docker", args, { maxBuffer: 1024 * 1024 * 16 });
return stdout;
}
function normalizeImageRefs(output: string): string[] {
return output
.split(/\r?\n/)
.map((line) => line.trim())
.filter((line) => line.length > 0 && !line.includes("<none>"));
}
function isSupabaseImageRef(ref: string): boolean {
return SUPABASE_IMAGE_PREFIXES.some((prefix) => ref.startsWith(prefix));
}
export async function collectDockerImageRefs(run: ExecFile = execFile): Promise<string[]> {
const refs = new Set<string>();
try {
const output = await runDocker(
["ps", "-a", "--filter", `label=${CLI_PROJECT_LABEL}`, "--format", "{{.Image}}"],
run,
);
for (const ref of normalizeImageRefs(output)) {
refs.add(ref);
}
} catch (error) {
core.warning(
`Could not list Supabase CLI containers for Docker image cache: ${error instanceof Error ? error.message : String(error)}`,
);
}
try {
const output = await runDocker(["image", "ls", "--format", "{{.Repository}}:{{.Tag}}"], run);
for (const ref of normalizeImageRefs(output).filter(isSupabaseImageRef)) {
refs.add(ref);
}
} catch (error) {
core.warning(
`Could not list Docker images for Supabase image cache: ${error instanceof Error ? error.message : String(error)}`,
);
}
return [...refs].sort();
}
export async function restoreDockerImageCache(
installedVersion: string,
registry: string,
run: ExecFile = execFile,
): Promise<void> {
const enabled = core.getBooleanInput(CACHE_INPUT);
core.setOutput(CACHE_HIT_OUTPUT, "false");
core.saveState(STATE_ENABLED, String(enabled));
if (!enabled) {
return;
}
const archivePath = getCacheArchivePath();
const primaryKey = getPrimaryCacheKey(installedVersion, registry);
mkdirSync(path.dirname(archivePath), { recursive: true });
core.saveState(STATE_PRIMARY_KEY, primaryKey);
core.saveState(STATE_ARCHIVE_PATH, archivePath);
core.saveState(STATE_CACHE_HIT, "false");
if (!(await isDockerAvailable(run))) {
core.warning("Docker is not available. Skipping Supabase Docker image cache restore.");
return;
}
let matchedKey: string | undefined;
try {
matchedKey = await cache.restoreCache([archivePath], primaryKey);
} catch (error) {
core.warning(
`Could not restore Supabase Docker image cache: ${error instanceof Error ? error.message : String(error)}`,
);
return;
}
if (!matchedKey) {
core.info("No Supabase Docker image cache found.");
return;
}
const cacheHit = matchedKey === primaryKey;
core.setOutput(CACHE_HIT_OUTPUT, String(cacheHit));
core.saveState(STATE_CACHE_HIT, String(cacheHit));
if (!existsSync(archivePath)) {
core.warning("Supabase Docker image cache was restored, but the archive is missing.");
return;
}
try {
await runDocker(["load", "-i", archivePath], run);
core.info("Loaded Supabase Docker images from cache.");
} catch (error) {
core.warning(
`Could not load Supabase Docker image cache: ${error instanceof Error ? error.message : String(error)}`,
);
}
}
export async function saveDockerImageCache(run: ExecFile = execFile): Promise<void> {
if (core.getState(STATE_ENABLED) !== "true") {
return;
}
if (core.getState(STATE_CACHE_HIT) === "true") {
core.info("Supabase Docker image cache hit. Skipping cache save.");
return;
}
const primaryKey = core.getState(STATE_PRIMARY_KEY);
const archivePath = core.getState(STATE_ARCHIVE_PATH) || getCacheArchivePath();
if (!primaryKey) {
core.warning("Supabase Docker image cache key is missing. Skipping cache save.");
return;
}
if (!(await isDockerAvailable(run))) {
core.warning("Docker is not available. Skipping Supabase Docker image cache save.");
return;
}
const imageRefs = await collectDockerImageRefs(run);
if (imageRefs.length === 0) {
core.warning("No Supabase Docker images found to cache.");
return;
}
mkdirSync(path.dirname(archivePath), { recursive: true });
try {
await runDocker(["save", "-o", archivePath, ...imageRefs], run);
} catch (error) {
core.warning(
`Could not create Supabase Docker image archive: ${error instanceof Error ? error.message : String(error)}`,
);
return;
}
try {
await cache.saveCache([archivePath], primaryKey);
core.info(`Saved ${imageRefs.length} Supabase Docker image(s) to cache.`);
} catch (error) {
core.warning(
`Could not save Supabase Docker image cache: ${error instanceof Error ? error.message : String(error)}`,
);
}
}

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 path from "node:path";
import process from "node:process";
import { fileURLToPath } from "node:url";
import { afterEach, expect, mock, spyOn, test } from "bun:test";
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 originalPath = process.env.PATH;
const originalRunnerTemp = process.env.RUNNER_TEMP;
const originalWorkspace = process.env.GITHUB_WORKSPACE;
const tempDirs = new Set<string>();
let mainModule: typeof import("./main.ts") | null = null;
afterEach(() => {
mock.restore();
process.env.PATH = originalPath;
process.env.RUNNER_TEMP = originalRunnerTemp;
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) {
rmSync(dir, { force: true, recursive: true });
@@ -28,14 +24,32 @@ afterEach(() => {
tempDirs.clear();
});
function createTempDir(prefix: string): string {
const dir = mkdtempSync(path.join(os.tmpdir(), prefix));
function createFakeCli(versionOutput: string): string {
const dir = mkdtempSync(path.join(os.tmpdir(), "setup-cli-"));
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;
}
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)) {
const filePath = path.join(dir, relativePath);
@@ -51,7 +65,6 @@ function createBunLock(
options: {
includeDependency?: boolean;
includePackageEntry?: boolean;
integrity?: string;
useDevDependency?: boolean;
} = {},
): string {
@@ -77,7 +90,7 @@ ${
"supabase@${version}",
"",
{},
"${options.integrity ?? "sha512-bun"}"
"sha512-test"
]`
: ""
}
@@ -88,12 +101,7 @@ ${
function createPnpmLock(
version: string,
options: {
asString?: boolean;
includeVersion?: boolean;
integrity?: string;
useDevDependency?: boolean;
} = {},
options: { asString?: boolean; includeVersion?: boolean; useDevDependency?: boolean } = {},
): string {
const dependencyKey = options.useDevDependency ? "devDependencies" : "dependencies";
@@ -111,11 +119,11 @@ ${options.includeVersion === false ? "" : ` version: ${version}`}`
packages:
supabase@${version}:
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(
{
name: "app",
@@ -127,7 +135,6 @@ function createPackageLock(version: string, integrity = "sha512-package-lock"):
},
},
"node_modules/supabase": {
integrity,
version,
},
},
@@ -137,104 +144,24 @@ function createPackageLock(version: string, integrity = "sha512-package-lock"):
);
}
function createFakeNpm(): 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) {
function createActionSpies(inputVersion: string, cliDir: string, expectedUrlFragment: string) {
return {
getInput: spyOn(core, "getInput").mockImplementation((name: string) =>
name === "version" ? inputVersion : "",
),
getBooleanInput: spyOn(core, "getBooleanInput").mockImplementation(() => false),
setOutput: spyOn(core, "setOutput").mockImplementation(() => {}),
addPath: spyOn(core, "addPath").mockImplementation(() => {}),
exportVariable: spyOn(core, "exportVariable").mockImplementation(() => {}),
getInput: spyOn(core, "getInput").mockReturnValue(inputVersion),
saveState: spyOn(core, "saveState").mockImplementation(() => {}),
info: spyOn(core, "info").mockImplementation(() => {}),
warning: spyOn(core, "warning").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),
};
}
@@ -246,52 +173,106 @@ async function getMainModule(): Promise<typeof import("./main.ts")> {
return mainModule;
}
test("uses an explicit npm package version when provided", async () => {
const { resolvePackage } = await getMainModule();
expect(resolvePackage("v2.101.0")).toEqual({
spec: "supabase@2.101.0",
version: "2.101.0",
test("awaits the action entrypoint with omitted version and latest fallback", async () => {
process.env.GITHUB_WORKSPACE = repo;
const cliDir = createFakeCli("supabase 2.84.2");
let startDownload!: () => void;
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").mockImplementation((name: string) =>
name === "version" ? "" : "",
),
getBooleanInput: spyOn(core, "getBooleanInput").mockImplementation(() => false),
setOutput: spyOn(core, "setOutput").mockImplementation(() => {}),
addPath: spyOn(core, "addPath").mockImplementation(() => {}),
exportVariable: spyOn(core, "exportVariable").mockImplementation(() => {}),
saveState: spyOn(core, "saveState").mockImplementation(() => {}),
info: spyOn(core, "info").mockImplementation(() => {}),
warning: spyOn(core, "warning").mockImplementation(() => {}),
setFailed: spyOn(core, "setFailed").mockImplementation(() => {}),
downloadTool: spyOn(tc, "downloadTool").mockImplementation(async (url: string) => {
expect(url).toContain("/latest/download/");
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;
});
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 resolution when version is omitted", async () => {
test("uses the root bun.lock version when version is omitted", async () => {
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({
integrity: "sha512-bun-lock",
spec: "supabase@2.41.0",
version: "2.41.0",
});
await run();
expect(spies.downloadTool).not.toHaveBeenCalledWith(expect.stringContaining("/latest/download/"));
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({
"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({
integrity: "sha512-pnpm-lock",
spec: "supabase@2.42.0",
version: "2.42.0",
});
await run();
expect(spies.setOutput).toHaveBeenCalledWith("version", "supabase 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({
"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({
integrity: "sha512-package-lock",
spec: "supabase@2.43.0",
version: "2.43.0",
});
await run();
expect(spies.setOutput).toHaveBeenCalledWith("version", "supabase 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 () => {
@@ -299,47 +280,58 @@ test("falls through malformed lockfiles and uses the next supported root lockfil
"bun.lock": "{ not valid",
"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({
integrity: "sha512-package-lock",
spec: "supabase@2.44.0",
version: "2.44.0",
});
await run();
expect(spies.setOutput).toHaveBeenCalledWith("version", "supabase 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 () => {
process.env.GITHUB_WORKSPACE = createWorkspace({
"README.md": "# app\n",
});
const { resolvePackage } = await getMainModule();
const cliDir = createFakeCli("supabase 2.84.2");
const spies = createActionSpies("", cliDir, "/latest/download/");
const { run } = await getMainModule();
expect(resolvePackage("")).toEqual({
spec: "supabase@latest",
version: "latest",
});
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 version is omitted and no workspace is available", async () => {
delete process.env.GITHUB_WORKSPACE;
const { resolvePackage } = await getMainModule();
const cliDir = createFakeCli("supabase 2.84.2");
const spies = createActionSpies("", cliDir, "/latest/download/");
const { run } = await getMainModule();
expect(resolvePackage("")).toEqual({
spec: "supabase@latest",
version: "latest",
});
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("uses the declared bun.lock version when the resolved package entry is missing", async () => {
process.env.GITHUB_WORKSPACE = createWorkspace({
"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({
spec: "supabase@2.44.1",
version: "2.44.1",
});
await run();
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 () => {
@@ -347,119 +339,71 @@ test("falls through bun.lock without supabase and uses a pnpm string dependency
"bun.lock": createBunLock("2.47.0", { includeDependency: false }),
"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({
integrity: "sha512-pnpm",
spec: "supabase@2.47.0",
version: "2.47.0",
await run();
expect(spies.setOutput).toHaveBeenCalledWith("version", "supabase 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");
const spies = createActionSpies("", cliDir, "/latest/download/");
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 () => {
process.env.GITHUB_WORKSPACE = createWorkspace({
"pnpm-lock.yaml": createPnpmLock("2.49.0", { includeVersion: false }),
});
const { resolvePackage } = await getMainModule();
expect(resolvePackage("")).toEqual({
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 cliDir = createFakeCli("supabase 2.84.2");
const spies = createActionSpies("", cliDir, "/latest/download/");
const { run } = await getMainModule();
await run();
expect(readNpmCalls(logPath)[0]).toEqual(["view", "supabase@2.43.0", "dist.integrity", "--json"]);
expect(spies.setOutput).toHaveBeenCalledWith("version", "supabase 2.43.0");
expect(spies.addPath).toHaveBeenCalledWith(expect.stringContaining("node_modules"));
expect(spies.setOutput).toHaveBeenCalledWith("version", "supabase 2.84.2");
expect(spies.exportVariable).toHaveBeenCalledWith(CLI_CONFIG_REGISTRY, "ghcr.io");
expect(spies.setFailed).not.toHaveBeenCalled();
});
test("explicit version overrides detected root lockfiles", async () => {
installFakeNpm("supabase 1.0.0");
process.env.GITHUB_WORKSPACE = createWorkspace({
"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();
await run();
@@ -470,11 +414,11 @@ test("explicit version overrides detected root lockfiles", async () => {
});
test("fails when the installed CLI does not report a version", async () => {
installFakeNpm("");
process.env.GITHUB_WORKSPACE = createWorkspace({
"package-lock.json": createPackageLock("2.46.0", "sha512-test"),
"package-lock.json": createPackageLock("2.46.0"),
});
const spies = createActionSpies("");
const cliDir = createFakeCli("");
const spies = createActionSpies("", cliDir, "/download/v2.46.0/supabase_");
const { run } = await getMainModule();
await run();

View File

@@ -1,21 +1,18 @@
import { semver } from "bun";
import * as core from "@actions/core";
import { existsSync, mkdtempSync, readFileSync } from "node:fs";
import os from "node:os";
import * as tc from "@actions/tool-cache";
import { load as loadYaml } from "js-yaml";
import * as semver from "semver";
import { execFile as execFileCallback } from "node:child_process";
import { existsSync, readFileSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { promisify } from "node:util";
import { getGhcrImageRegistry, getImageRegistry, restoreDockerImageCache } from "./cache";
export const CLI_CONFIG_REGISTRY = "SUPABASE_INTERNAL_IMAGE_REGISTRY";
const REGISTRY_VERSION = "1.28.0";
const DEFAULT_VERSION = "latest";
const NPM_PACKAGE = "supabase";
const NPM_EXECUTABLE_ENV = "SUPABASE_SETUP_CLI_NPM";
type PackageResolution = {
spec: string;
version: string;
integrity?: string;
};
const execFile = promisify(execFileCallback);
type BunLock = {
workspaces?: {
@@ -33,12 +30,6 @@ type PnpmDependency =
version?: string;
};
type PnpmPackage = {
resolution?: {
integrity?: string;
};
};
type PnpmLock = {
importers?: {
".": {
@@ -46,14 +37,21 @@ type PnpmLock = {
devDependencies?: Record<string, PnpmDependency>;
};
};
packages?: Record<string, PnpmPackage>;
};
type PackageLock = {
packages?: Record<string, { integrity?: string; version?: string }>;
dependencies?: Record<string, { integrity?: string; version?: string }>;
packages?: Record<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 {
if (!raw) {
return null;
@@ -63,20 +61,6 @@ function extractConcreteVersion(raw: string | undefined): string | 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 {
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");
if (!text) {
@@ -102,28 +86,24 @@ function detectResolutionFromBunLock(workspaceRoot: string): PackageResolution |
const lockfile = JSON.parse(text.replace(/,\s*([}\]])/g, "$1")) as BunLock;
const rootWorkspace = lockfile.workspaces?.[""];
const declaredVersion =
rootWorkspace?.dependencies?.[NPM_PACKAGE] ?? rootWorkspace?.devDependencies?.[NPM_PACKAGE];
rootWorkspace?.dependencies?.supabase ?? rootWorkspace?.devDependencies?.supabase;
if (!declaredVersion) {
return null;
}
const resolvedPackage = lockfile.packages?.[NPM_PACKAGE];
const resolvedPackage = lockfile.packages?.supabase;
if (Array.isArray(resolvedPackage) && typeof resolvedPackage[0] === "string") {
const version = extractConcreteVersion(resolvedPackage[0]);
const integrity = typeof resolvedPackage[3] === "string" ? resolvedPackage[3] : undefined;
return version ? toPackageResolution(version, integrity) : null;
return extractConcreteVersion(resolvedPackage[0]);
}
const version = extractConcreteVersion(declaredVersion);
return version ? toPackageResolution(version) : null;
return extractConcreteVersion(declaredVersion);
} catch {
return null;
}
}
function detectResolutionFromPnpmLock(workspaceRoot: string): PackageResolution | null {
function detectVersionFromPnpmLock(workspaceRoot: string): string | null {
const text = readWorkspaceLockfile(workspaceRoot, "pnpm-lock.yaml");
if (!text) {
@@ -131,32 +111,22 @@ function detectResolutionFromPnpmLock(workspaceRoot: string): PackageResolution
}
try {
const lockfile = Bun.YAML.parse(text) as PnpmLock;
const lockfile = loadYaml(text) as PnpmLock;
const rootImporter = lockfile.importers?.["."];
const dependency =
rootImporter?.dependencies?.[NPM_PACKAGE] ?? rootImporter?.devDependencies?.[NPM_PACKAGE];
const version =
typeof dependency === "string"
? extractConcreteVersion(dependency)
: extractConcreteVersion(dependency?.version);
rootImporter?.dependencies?.supabase ?? rootImporter?.devDependencies?.supabase;
if (!version) {
return null;
if (typeof dependency === "string") {
return extractConcreteVersion(dependency);
}
const integrity = Object.entries(lockfile.packages ?? {}).find(
([packageKey]) =>
packageKey === `${NPM_PACKAGE}@${version}` ||
packageKey.startsWith(`/${NPM_PACKAGE}@${version}`),
)?.[1].resolution?.integrity;
return toPackageResolution(version, integrity);
return extractConcreteVersion(dependency?.version);
} catch {
return null;
}
}
function detectResolutionFromPackageLock(workspaceRoot: string): PackageResolution | null {
function detectVersionFromPackageLock(workspaceRoot: string): string | null {
const text = readWorkspaceLockfile(workspaceRoot, "package-lock.json");
if (!text) {
@@ -165,133 +135,69 @@ function detectResolutionFromPackageLock(workspaceRoot: string): PackageResoluti
try {
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
? toPackageResolution(version, packageEntry?.integrity ?? dependencyEntry?.integrity)
: null;
return (
extractConcreteVersion(lockfile.packages?.["node_modules/supabase"]?.version) ??
extractConcreteVersion(lockfile.dependencies?.supabase?.version)
);
} catch {
return null;
}
}
export function resolvePackage(inputVersion: string): PackageResolution {
function resolveVersion(inputVersion: string): string {
const requestedVersion = inputVersion.trim();
if (requestedVersion) {
return toPackageResolution(requestedVersion);
return requestedVersion;
}
const workspaceRoot = process.env.GITHUB_WORKSPACE?.trim();
if (!workspaceRoot) {
return toPackageResolution(DEFAULT_VERSION);
return DEFAULT_VERSION;
}
return (
detectResolutionFromBunLock(workspaceRoot) ??
detectResolutionFromPnpmLock(workspaceRoot) ??
detectResolutionFromPackageLock(workspaceRoot) ??
toPackageResolution(DEFAULT_VERSION)
detectVersionFromBunLock(workspaceRoot) ??
detectVersionFromPnpmLock(workspaceRoot) ??
detectVersionFromPackageLock(workspaceRoot) ??
DEFAULT_VERSION
);
}
async function verifyExpectedIntegrity(resolution: PackageResolution): Promise<void> {
if (!resolution.integrity) {
return;
export function getDownloadUrl(version: string): string {
const platform = getArchivePlatform(process.platform);
const arch = getArchiveArch(process.arch);
const filename = `supabase_${platform}_${arch}.tar.gz`;
if (version.toLowerCase() === "latest") {
return `https://github.com/supabase/cli/releases/latest/download/${filename}`;
}
const output = await runNpm(["view", resolution.spec, "dist.integrity", "--json"]);
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`);
if (semver.compare(version, REGISTRY_VERSION) === -1) {
return `https://github.com/supabase/cli/releases/download/v${version}/supabase_${version}_${platform}_${arch}.tar.gz`;
}
return `https://github.com/supabase/cli/releases/download/v${version}/${filename}`;
}
function createInstallRoot(): string {
const tempRoot = process.env.RUNNER_TEMP?.trim() || os.tmpdir();
return mkdtempSync(path.join(tempRoot, "setup-cli-"));
}
function getSupabaseExecutable(cliPath: string): string {
const names =
process.platform === "win32" ? ["supabase.exe", "supabase.cmd", "supabase"] : ["supabase"];
async function runNpm(args: string[]): Promise<string> {
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;
for (const name of names) {
const executable = path.join(cliPath, name);
if (existsSync(executable)) {
return executable;
}
}
return path.join(cliPath, "supabase");
}
export async function determineInstalledVersion(cliPath: string): Promise<string> {
const executable = getCliExecutablePath(cliPath);
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 { stdout } = await execFile(getSupabaseExecutable(cliPath), ["--version"]);
const version = stdout.trim();
if (!version) {
throw new Error("Could not determine installed Supabase CLI version");
@@ -300,26 +206,20 @@ export async function determineInstalledVersion(cliPath: string): Promise<string
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> {
try {
const resolution = resolvePackage(core.getInput("version"));
const cliPath = await installCli(resolution);
const version = resolveVersion(core.getInput("version"));
const tarball = await tc.downloadTool(getDownloadUrl(version));
const cliPath = await tc.extractTar(tarball);
const installedVersion = await determineInstalledVersion(cliPath);
core.setOutput("version", installedVersion);
core.addPath(cliPath);
if (shouldUseGhcrRegistry(resolution.version, installedVersion)) {
core.exportVariable(CLI_CONFIG_REGISTRY, "ghcr.io");
if (version.toLowerCase() === "latest" || semver.compare(version, REGISTRY_VERSION) >= 0) {
core.exportVariable(CLI_CONFIG_REGISTRY, getGhcrImageRegistry());
}
await restoreDockerImageCache(installedVersion, getImageRegistry());
} catch (error) {
core.setFailed(error instanceof Error ? error.message : String(error));
}

17
src/post.ts Normal file
View File

@@ -0,0 +1,17 @@
import * as core from "@actions/core";
import { fileURLToPath } from "node:url";
import { saveDockerImageCache } from "./cache";
export async function run(): Promise<void> {
try {
await saveDockerImageCache();
} catch (error) {
core.warning(
`Supabase Docker image cache post step failed: ${error instanceof Error ? error.message : String(error)}`,
);
}
}
if (process.argv[1] === fileURLToPath(import.meta.url)) {
await run();
}

View File

@@ -1,4 +1,7 @@
{
"extends": "@tsconfig/bun/tsconfig.json",
"compilerOptions": {
"types": ["bun", "node"]
},
"include": ["src"]
}