Compare commits

..

1 Commits

Author SHA1 Message Date
Julien Goux
cd9b0fd6c9 fix: handle Supabase CLI v2.99 archives on v1 2026-05-18 13:19:23 +02:00
51 changed files with 46943 additions and 1763 deletions

View File

@@ -1 +0,0 @@
1.3.10

3
.gitattributes vendored Normal file
View File

@@ -0,0 +1,3 @@
* text=auto eol=lf
dist/** -diff linguist-generated=true

2
.github/CODEOWNERS vendored
View File

@@ -1 +1 @@
* @supabase/cli * @supabase/dev-workflows

5
.github/codeql/codeql-config.yml vendored Normal file
View File

@@ -0,0 +1,5 @@
name: JavaScript CodeQL Configuration
paths-ignore:
- node_modules
- dist

View File

@@ -4,33 +4,28 @@ updates:
directory: / directory: /
schedule: schedule:
interval: weekly interval: weekly
day: tuesday
time: "09:00"
timezone: Europe/Paris
open-pull-requests-limit: 1
groups: groups:
actions-minor-patch: actions-minor:
patterns:
- "*"
update-types: update-types:
- minor - minor
- patch - patch
- package-ecosystem: bun - package-ecosystem: npm
directory: / directory: /
schedule: schedule:
interval: weekly interval: daily
day: tuesday
time: "09:00"
timezone: Europe/Paris
open-pull-requests-limit: 1
cooldown:
semver-minor-days: 7
semver-patch-days: 2
groups: groups:
bun-minor-patch: npm-development:
patterns: dependency-type: development
- "*"
update-types: update-types:
- minor - minor
- patch - patch
npm-production:
dependency-type: production
update-types:
- patch
ignore:
# nodejs types is pinned to runtime version
- dependency-name: '@types/node'
update-types:
- version-update:semver-major

View File

@@ -1,68 +0,0 @@
name: CI
on:
pull_request:
types:
- opened
- synchronize
- reopened
- ready_for_review
- converted_to_draft
push:
branches:
- main
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
defaults:
run:
shell: bash
jobs:
validate:
if: ${{ github.event_name != 'pull_request' || !github.event.pull_request.draft }}
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version-file: .bun-version
- run: bun install --frozen-lockfile
- run: bun run ci
test:
if: ${{ github.event_name != 'pull_request' || !github.event.pull_request.draft }}
runs-on: ${{ matrix.os }}
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
os: [macos-latest, windows-latest, ubuntu-latest]
version: [1.178.2, latest, beta]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: ./
with:
version: ${{ matrix.version }}
- run: supabase -h
ci:
if: ${{ always() && github.event_name == 'pull_request' }}
name: CI
runs-on: ubuntu-latest
needs: [validate, test]
timeout-minutes: 5
steps:
- run: |
validate_result="${{ needs.validate.result }}"
test_result="${{ needs.test.result }}"
[[ "$validate_result" == "success" || "$validate_result" == "skipped" ]]
[[ "$test_result" == "success" || "$test_result" == "skipped" ]]

47
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@@ -0,0 +1,47 @@
name: CodeQL
on:
pull_request:
push:
branches:
- main
schedule:
- cron: '31 7 * * 3'
permissions:
actions: read
checks: write
contents: read
security-events: write
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
language:
- typescript
steps:
- name: Checkout
id: checkout
uses: actions/checkout@v4
- name: Initialize CodeQL
id: initialize
uses: github/codeql-action/init@v3
with:
config-file: .github/codeql/codeql-config.yml
languages: ${{ matrix.language }}
source-root: src
- name: Autobuild
id: autobuild
uses: github/codeql-action/autobuild@v3
- name: Perform CodeQL Analysis
id: analyze
uses: github/codeql-action/analyze@v3

View File

@@ -3,10 +3,6 @@ name: Dependabot auto-merge
on: pull_request on: pull_request
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions: permissions:
pull-requests: write pull-requests: write
contents: write contents: write
@@ -14,33 +10,29 @@ permissions:
jobs: jobs:
dependabot: dependabot:
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 10 # Checking the actor will prevent your Action run failing on non-Dependabot
# Only act on PRs opened by Dependabot from branches in this repository. # PRs but also ensures that it only does work for Dependabot PRs.
if: github.actor == 'dependabot[bot]' && github.repository == github.event.pull_request.head.repo.full_name if: ${{ github.actor == 'dependabot[bot]' }}
steps: steps:
# Metadata drives the non-major gating used for approval and auto-merge. # This first step will fail if there's no metadata and so the approval
# will not occur.
- id: meta - id: meta
uses: dependabot/fetch-metadata@25dd0e34f4fe68f24cc83900b1fe3fe149efef98 # v3.1.0 uses: dependabot/fetch-metadata@v2
with: with:
github-token: "${{ secrets.GITHUB_TOKEN }}" 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
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
# Here the PR gets approved.
- name: Approve a PR - name: Approve a PR
if: ${{ steps.meta.outputs.update-type == null || steps.meta.outputs.update-type == 'version-update:semver-patch' || steps.meta.outputs.update-type == 'version-update:semver-minor' }} if: ${{steps.meta.outputs.update-type != 'version-update:semver-major'}}
run: gh pr review --approve "$PR_URL" run: gh pr review --approve "$PR_URL"
env: env:
PR_URL: ${{ github.event.pull_request.html_url }} PR_URL: ${{ github.event.pull_request.html_url }}
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Finally, this sets the PR to allow auto-merging for patch and minor
# updates if all checks pass
- name: Enable auto-merge for Dependabot PRs - name: Enable auto-merge for Dependabot PRs
if: ${{ steps.meta.outputs.update-type == null || steps.meta.outputs.update-type == 'version-update:semver-patch' || steps.meta.outputs.update-type == 'version-update:semver-minor' }} if: ${{steps.meta.outputs.update-type != 'version-update:semver-major'}}
run: gh pr merge --auto --squash "$PR_URL" run: gh pr merge --auto --squash "$PR_URL"
env: env:
PR_URL: ${{ github.event.pull_request.html_url }} PR_URL: ${{ github.event.pull_request.html_url }}

View File

@@ -1,71 +0,0 @@
name: E2E
on:
pull_request:
types:
- opened
- synchronize
- reopened
- ready_for_review
- converted_to_draft
push:
branches:
- main
tags:
- "v[0-9]+.[0-9]+.[0-9]+"
schedule:
# * is a special character in YAML so you have to quote this string
- cron: "30 1,9 * * *"
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
defaults:
run:
shell: bash
permissions:
contents: read
jobs:
e2e: # make sure the action works on a clean machine without building
if: ${{ github.event_name != 'pull_request' || !github.event.pull_request.draft }}
runs-on: ubuntu-latest
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
version:
- 1.178.2
- 2.33.0
- latest
pg_major:
- 14
- 15
- 17
exclude:
- version: 1.178.2
pg_major: 17
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: ./
with:
version: ${{ matrix.version }}
- run: supabase init
- run: |
sed -i -E "s|^(major_version) .*|\\1 = ${{ matrix.pg_major }}|" supabase/config.toml
- run: supabase start
e2e-check:
if: ${{ always() && github.event_name == 'pull_request' }}
name: E2E
runs-on: ubuntu-latest
needs: [e2e]
timeout-minutes: 5
steps:
- run: |
e2e_result="${{ needs.e2e.result }}"
[[ "$e2e_result" == "success" || "$e2e_result" == "skipped" ]]

View File

@@ -1,6 +1,6 @@
# This workflow refreshes and checks dependency license records used in this # This workflow checks the statuses of cached dependencies used in this action
# action with the help of the Licensed tool. If any licenses are invalid or # with the help of the Licensed tool. If any licenses are invalid or missing,
# missing, this workflow will fail. See: https://github.com/licensee/licensed # this workflow will fail. See: https://github.com/licensee/licensed
name: Licensed name: Licensed
@@ -9,139 +9,61 @@ on:
push: push:
branches: branches:
- main - main
paths:
- .github/workflows/licensed.yml
- .licensed.yml
- .licenses/**
- bun.lock
- package.json
workflow_dispatch: workflow_dispatch:
concurrency: permissions:
group: ${{ github.workflow }}-${{ github.ref }} contents: write
cancel-in-progress: true
jobs: jobs:
check-licenses: licensed:
if: ${{ github.event_name != 'workflow_dispatch' }} name: Check Licenses
name: Licensed
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
pull-requests: read
steps:
- name: Detect license inputs
id: license-inputs
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
REPOSITORY: ${{ github.repository }}
run: |
if [[ "${{ github.event_name }}" != "pull_request" ]]; then
echo "changed=true" >> "$GITHUB_OUTPUT"
exit 0
fi
gh api "repos/${REPOSITORY}/pulls/${PR_NUMBER}/files" --paginate --jq '.[].filename' > changed-files.txt
if grep -Eq '^(\.github/workflows/licensed\.yml|\.licensed\.yml|\.licenses/.*|bun\.lock|package\.json)$' changed-files.txt; then
echo "changed=true" >> "$GITHUB_OUTPUT"
else
echo "changed=false" >> "$GITHUB_OUTPUT"
fi
- name: Checkout
id: checkout
if: steps.license-inputs.outputs.changed == 'true'
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Bun
id: setup-bun
if: steps.license-inputs.outputs.changed == 'true'
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version-file: .bun-version
- name: Install Dependencies
id: bun-install
if: steps.license-inputs.outputs.changed == 'true'
run: bun install --frozen-lockfile
- name: Setup Ruby
id: setup-ruby
if: steps.license-inputs.outputs.changed == 'true'
uses: ruby/setup-ruby@97ecb7b512899eb71ab1bf2310a624c6f1589ac6 # v1.308.0
with:
ruby-version: ruby
- uses: licensee/setup-licensed@0d52e575b3258417672be0dff2f115d7db8771d8 # v1.3.2
if: steps.license-inputs.outputs.changed == 'true'
with:
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'
run: licensed status
update-licenses:
if: ${{ github.event_name == 'workflow_dispatch' }}
name: Update Licenses
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: write
steps: steps:
- name: Checkout - name: Checkout
id: checkout id: checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@v4
- name: Setup Bun - name: Setup Node.js
id: setup-bun id: setup-node
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 uses: actions/setup-node@v4
with: with:
bun-version-file: .bun-version node-version-file: .node-version
cache: npm
- name: Install Dependencies - name: Install Dependencies
id: bun-install id: npm-ci
run: bun install --frozen-lockfile run: npm ci
- name: Setup Ruby - name: Setup Ruby
id: setup-ruby id: setup-ruby
uses: ruby/setup-ruby@97ecb7b512899eb71ab1bf2310a624c6f1589ac6 # v1.308.0 uses: ruby/setup-ruby@v1
with: with:
ruby-version: ruby ruby-version: ruby
- uses: licensee/setup-licensed@0d52e575b3258417672be0dff2f115d7db8771d8 # v1.3.2 - uses: licensee/setup-licensed@v1.3.2
with: with:
version: 4.x version: 4.x
github_token: ${{ secrets.GITHUB_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }}
- name: Update License Cache # If this is a workflow_dispatch event, update the cached licenses.
- if: ${{ github.event_name == 'workflow_dispatch' }}
name: Update Licenses
id: update-licenses id: update-licenses
run: licensed cache run: licensed cache
- name: Format License Files # Then, commit the updated licenses to the repository.
id: format-licenses - if: ${{ github.event_name == 'workflow_dispatch' }}
run: bun x oxfmt --write .licensed.yml .licenses name: Commit Licenses
- name: Commit Licenses
id: commit-licenses id: commit-licenses
run: | run: |
git config --local user.email "licensed-ci@users.noreply.github.com" git config --local user.email "licensed-ci@users.noreply.github.com"
git config --local user.name "licensed-ci" git config --local user.name "licensed-ci"
git add .licenses .licensed.yml git add .
if git diff --cached --quiet; then
echo "No license cache changes to commit."
exit 0
fi
git commit -m "Auto-update license files" git commit -m "Auto-update license files"
git push git push
# Last, check the status of the cached licenses.
- name: Check Licenses
id: check-licenses
run: licensed status

56
.github/workflows/linter.yml vendored Normal file
View File

@@ -0,0 +1,56 @@
# This workflow will lint the entire codebase using the
# `super-linter/super-linter` action.
#
# For more information, see the super-linter repository:
# https://github.com/super-linter/super-linter
name: Lint Codebase
on:
pull_request:
push:
branches:
- main
permissions:
contents: read
packages: read
statuses: write
jobs:
lint:
name: Lint Codebase
runs-on: ubuntu-latest
steps:
- name: Checkout
id: checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
id: setup-node
uses: actions/setup-node@v4
with:
node-version-file: .node-version
cache: npm
- name: Install Dependencies
id: install
run: npm ci
- name: Lint Codebase
id: super-linter
uses: super-linter/super-linter/slim@v8
env:
DEFAULT_BRANCH: main
FILTER_REGEX_EXCLUDE: dist/**/*
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
LINTER_RULES_PATH: ${{ github.workspace }}
VALIDATE_ALL_CODEBASE: true
VALIDATE_JAVASCRIPT_ES: false
VALIDATE_JAVASCRIPT_STANDARD: false
VALIDATE_JSCPD: false
VALIDATE_TYPESCRIPT_ES: false
VALIDATE_JSON: false
VALIDATE_TYPESCRIPT_STANDARD: false

45
.github/workflows/start.yml vendored Normal file
View File

@@ -0,0 +1,45 @@
name: CLI Start
on:
push:
branches:
- main
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
schedule:
# * is a special character in YAML so you have to quote this string
- cron: '30 1,9 * * *'
workflow_dispatch:
defaults:
run:
shell: bash
permissions:
contents: read
jobs:
e2e: # make sure the action works on a clean machine without building
runs-on: ubuntu-latest
strategy:
matrix:
version:
- 1.178.2
- 2.33.0
- latest
pg_major:
- 14
- 15
- 17
exclude:
- version: 1.178.2
pg_major: 17
steps:
- uses: actions/checkout@v4
- uses: ./
with:
version: ${{ matrix.version }}
- run: supabase init
- run:
sed -i -E "s|^(major_version) .*|\1 = ${{ matrix.pg_major }}|"
supabase/config.toml
- run: supabase start

68
.github/workflows/test.yml vendored Normal file
View File

@@ -0,0 +1,68 @@
name: 'build-test'
on:
pull_request:
push:
branches:
- main
permissions:
contents: read
defaults:
run:
shell: bash
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: .node-version
cache: npm
- run: npm ci
- run: npm run all
- id: diff
run: |
if [ ! -d dist/ ]; then
echo "Expected dist/ directory does not exist. See status below:"
ls -la ./
exit 1
fi
if [ "$(git diff --ignore-space-at-eol --text dist/ | wc -l)" -gt "0" ]; then
echo "Detected uncommitted changes after build. See status below:"
git diff --ignore-space-at-eol --text dist/
exit 1
fi
# Upload the mismatched version as a workflow artifact.
- if: ${{ failure() && steps.diff.outcome == 'failure' }}
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest, windows-latest, ubuntu-latest]
version: [1.0.0, latest]
steps:
- uses: actions/checkout@v4
- uses: ./
with:
version: ${{ matrix.version }}
- run: supabase -h
check:
if: ${{ always() && github.event.pull_request }}
runs-on: ubuntu-latest
needs: [test]
steps:
- run: |
result="${{ needs.test.result }}"
[[ $result == "success" || $result == "skipped" ]]

3
.gitignore vendored
View File

@@ -1,6 +1,7 @@
# Dependency directory # Dependency directory
node_modules node_modules
bun.lockb
# Rest pulled from https://github.com/github/gitignore/blob/master/Node.gitignore
# Logs # Logs
logs logs
*.log *.log

View File

@@ -13,4 +13,6 @@ allowed:
- other - other
ignored: ignored:
npm: [] npm:
# Used by Rollup.js when building in GitHub Actions
- '@rollup/rollup-linux-x64-gnu'

View File

@@ -1,20 +1,20 @@
--- ---
name: "@actions/core" name: "@actions/core"
version: 3.0.0 version: 1.11.1
type: npm type: npm
summary: Actions core lib summary: Actions core lib
homepage: https://github.com/actions/toolkit/tree/main/packages/core homepage: https://github.com/actions/toolkit/tree/main/packages/core
license: mit license: mit
licenses: licenses:
- sources: LICENSE.md - sources: LICENSE.md
text: |- text: |-
The MIT License (MIT) The MIT License (MIT)
Copyright 2019 GitHub Copyright 2019 GitHub
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
notices: [] notices: []

View File

@@ -1,20 +1,20 @@
--- ---
name: "@actions/exec" name: "@actions/exec"
version: 3.0.0 version: 1.1.1
type: npm type: npm
summary: Actions exec lib summary: Actions exec lib
homepage: https://github.com/actions/toolkit/tree/main/packages/exec homepage: https://github.com/actions/toolkit/tree/main/packages/exec
license: mit license: mit
licenses: licenses:
- sources: LICENSE.md - sources: LICENSE.md
text: |- text: |-
The MIT License (MIT) The MIT License (MIT)
Copyright 2019 GitHub Copyright 2019 GitHub
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
notices: [] notices: []

View File

@@ -1,32 +1,32 @@
--- ---
name: "@actions/http-client" name: "@actions/http-client"
version: 4.0.0 version: 2.2.3
type: npm type: npm
summary: Actions Http Client summary: Actions Http Client
homepage: https://github.com/actions/toolkit/tree/main/packages/http-client homepage: https://github.com/actions/toolkit/tree/main/packages/http-client
license: other license: other
licenses: licenses:
- sources: LICENSE - sources: LICENSE
text: | text: |
Actions Http Client for Node.js Actions Http Client for Node.js
Copyright (c) GitHub, Inc. Copyright (c) GitHub, Inc.
All rights reserved. All rights reserved.
MIT License MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction, associated documentation files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions: subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
notices: [] notices: []

View File

@@ -1,20 +1,20 @@
--- ---
name: "@actions/io" name: "@actions/io"
version: 3.0.2 version: 1.1.3
type: npm type: npm
summary: Actions io lib summary: Actions io lib
homepage: https://github.com/actions/toolkit/tree/main/packages/io homepage: https://github.com/actions/toolkit/tree/main/packages/io
license: mit license: mit
licenses: licenses:
- sources: LICENSE.md - sources: LICENSE.md
text: |- text: |-
The MIT License (MIT) The MIT License (MIT)
Copyright 2019 GitHub Copyright 2019 GitHub
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
notices: [] notices: []

View File

@@ -0,0 +1,20 @@
---
name: "@actions/tool-cache"
version: 2.0.2
type: npm
summary: Actions tool-cache lib
homepage: https://github.com/actions/toolkit/tree/main/packages/tool-cache
license: mit
licenses:
- sources: LICENSE.md
text: |-
The MIT License (MIT)
Copyright 2019 GitHub
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
notices: []

View File

@@ -0,0 +1,30 @@
---
name: "@fastify/busboy"
version: 2.1.1
type: npm
summary: A streaming parser for HTML form data for node.js
homepage:
license: mit
licenses:
- sources: LICENSE
text: |-
Copyright Brian White. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
notices: []

View File

@@ -0,0 +1,26 @@
---
name: semver
version: 6.3.1
type: npm
summary: The semantic version parser used by npm.
homepage:
license: isc
licenses:
- sources: LICENSE
text: |
The ISC License
Copyright (c) Isaac Z. Schlueter and Contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
notices: []

View File

@@ -0,0 +1,26 @@
---
name: semver
version: 7.7.2
type: npm
summary: The semantic version parser used by npm.
homepage:
license: isc
licenses:
- sources: LICENSE
text: |
The ISC License
Copyright (c) Isaac Z. Schlueter and Contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
notices: []

View File

@@ -6,30 +6,30 @@ summary: Node HTTP/HTTPS Agents for tunneling proxies
homepage: https://github.com/koichik/node-tunnel/ homepage: https://github.com/koichik/node-tunnel/
license: mit license: mit
licenses: licenses:
- sources: LICENSE - sources: LICENSE
text: | text: |
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2012 Koichi Kobayashi Copyright (c) 2012 Koichi Kobayashi
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions: furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software. all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
- sources: README.md - sources: README.md
text: Licensed under the [MIT](https://github.com/koichik/node-tunnel/blob/master/LICENSE) text: Licensed under the [MIT](https://github.com/koichik/node-tunnel/blob/master/LICENSE)
license. license.
notices: [] notices: []

View File

@@ -1,34 +1,34 @@
--- ---
name: undici name: undici
version: 6.24.1 version: 5.29.0
type: npm type: npm
summary: An HTTP/1.1 client, written from scratch for Node.js summary: An HTTP/1.1 client, written from scratch for Node.js
homepage: https://undici.nodejs.org homepage: https://undici.nodejs.org
license: mit license: mit
licenses: licenses:
- sources: LICENSE - sources: LICENSE
text: | text: |
MIT License MIT License
Copyright (c) Matteo Collina and Undici contributors Copyright (c) Matteo Collina and Undici contributors
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions: furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software. copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
- sources: README.md - sources: README.md
text: MIT text: MIT
notices: [] notices: []

24
.markdown-lint.yml Normal file
View File

@@ -0,0 +1,24 @@
# See: https://github.com/DavidAnson/markdownlint
# Unordered list style
MD004:
style: dash
# Disable line length for tables
MD013:
tables: false
# Ordered list item prefix
MD029:
style: one
# Spaces after list markers
MD030:
ul_single: 1
ol_single: 1
ul_multi: 1
ol_multi: 1
# Code block style
MD046:
style: fenced

1
.node-version Normal file
View File

@@ -0,0 +1 @@
20.19.4

5
.prettierignore Normal file
View File

@@ -0,0 +1,5 @@
.DS_Store
.licenses/
dist/
node_modules/
coverage/

16
.prettierrc.yml Normal file
View File

@@ -0,0 +1,16 @@
# See: https://prettier.io/docs/en/configuration
printWidth: 80
tabWidth: 2
useTabs: false
semi: false
singleQuote: true
quoteProps: as-needed
jsxSingleQuote: false
trailingComma: none
bracketSpacing: true
bracketSameLine: true
arrowParens: always
proseWrap: always
htmlWhitespaceSensitivity: css
endOfLine: lf

14
.yaml-lint.yml Normal file
View File

@@ -0,0 +1,14 @@
# See: https://yamllint.readthedocs.io/en/stable/
rules:
document-end: disable
document-start:
level: warning
present: false
line-length:
level: warning
max: 80
allow-non-breakable-words: true
allow-non-breakable-inline-mappings: true
ignore:
- .licenses/

1
CODEOWNERS Normal file
View File

@@ -0,0 +1 @@
* @sweatybridge

127
README.md
View File

@@ -1,12 +1,13 @@
# :gear: Supabase CLI Action # :gear: Supabase CLI Action
[![CI](https://github.com/supabase/setup-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/supabase/setup-cli/actions/workflows/ci.yml) [![CI](https://github.com/supabase/setup-cli/actions/workflows/start.yml/badge.svg)](https://github.com/supabase/setup-cli/actions/workflows/start.yml)
[![E2E](https://github.com/supabase/setup-cli/actions/workflows/e2e.yml/badge.svg)](https://github.com/supabase/setup-cli/actions/workflows/e2e.yml) [![Linter](https://github.com/supabase/setup-cli/actions/workflows/linter.yml/badge.svg)](https://github.com/supabase/setup-cli/actions/workflows/linter.yml)
[![CodeQL](https://github.com/supabase/setup-cli/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/supabase/setup-cli/actions/workflows/codeql-analysis.yml) [![CodeQL](https://github.com/supabase/setup-cli/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/supabase/setup-cli/actions/workflows/codeql-analysis.yml)
[![Coverage](./badges/coverage.svg)](https://github.com/supabase/setup-cli/actions/workflows/test.yml)
## About ## About
This composite action sets up the Supabase CLI, This action sets up the Supabase CLI,
[`supabase`](https://github.com/supabase/cli), on GitHub's hosted Actions [`supabase`](https://github.com/supabase/cli), on GitHub's hosted Actions
runners. Other CI runners like runners. Other CI runners like
[Bitbucket](https://bitbucket.org/supabase-cli/setup-cli/src/master/bitbucket-pipelines.yml) [Bitbucket](https://bitbucket.org/supabase-cli/setup-cli/src/master/bitbucket-pipelines.yml)
@@ -24,41 +25,23 @@ Setup the `supabase` CLI:
```yaml ```yaml
steps: steps:
- uses: supabase/setup-cli@v3 - uses: supabase/setup-cli@v1
``` ```
If `version` is omitted, the action checks the repository root for `bun.lock`, A specific version of the `supabase` CLI can be installed:
`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`.
The action provisions Node.js and npm internally, so runners do not need npm
preinstalled. Runners must be able to reach the npm registry to install the CLI
package.
A fixed npm-published version, `latest`, or `beta` of the `supabase` CLI can be
installed:
```yaml ```yaml
steps: steps:
- uses: supabase/setup-cli@v3 - uses: supabase/setup-cli@v1
with: with:
version: 2.84.2 version: 2.20.3
```
```yaml
steps:
- uses: supabase/setup-cli@v3
with:
version: beta
``` ```
Run `supabase db start` to execute all migrations on a fresh database: Run `supabase db start` to execute all migrations on a fresh database:
```yaml ```yaml
steps: steps:
- uses: supabase/setup-cli@v3 - uses: supabase/setup-cli@v1
with: with:
version: latest version: latest
- run: supabase init - run: supabase init
@@ -70,11 +53,11 @@ on Windows and macOS runners.
## Inputs ## Inputs
The action supports the following inputs: The actions supports the following inputs:
| Name | Type | Description | Default | Required | | Name | Type | Description | Default | Required |
| --------- | ------ | ---------------------------------------------------------------- | --------------------------------- | -------- | | --------- | ------ | ---------------------------------- | -------- | -------- |
| `version` | String | Supabase CLI `latest`, `beta`, or fixed version published to npm | Root lockfile version or `latest` | false | | `version` | String | Supabase CLI version (or `latest`) | `2.20.3` | false |
## Advanced Usage ## Advanced Usage
@@ -82,7 +65,7 @@ Check generated TypeScript types are up-to-date with Postgres schema:
```yaml ```yaml
steps: steps:
- uses: supabase/setup-cli@v3 - uses: supabase/setup-cli@v1
- run: supabase init - run: supabase init
- run: supabase db start - run: supabase db start
- name: Verify generated types match Postgres schema - name: Verify generated types match Postgres schema
@@ -104,62 +87,59 @@ env:
# Retrieve <project-id> from dashboard url: https://app.supabase.com/project/<project-id> # Retrieve <project-id> from dashboard url: https://app.supabase.com/project/<project-id>
PROJECT_ID: <project-id> PROJECT_ID: <project-id>
steps: steps:
- uses: supabase/setup-cli@v3 - uses: supabase/setup-cli@v1
- run: supabase link --project-ref $PROJECT_ID - run: supabase link --project-ref $PROJECT_ID
- run: supabase db push - run: supabase db push
```
Export local Supabase env vars for app tests:
```yaml
steps:
- uses: supabase/setup-cli@v3
- run: supabase init
- run: supabase start
- name: Export local Supabase env vars
run: |
# Customize the variable names as needed for your app.
supabase status -o env \
--override-name api.url=SUPABASE_URL \
--override-name auth.service_role_key=SUPABASE_SERVICE_ROLE_KEY \
>> .env.test
- run: bun test
``` ```
## Develop ## Develop
After you've cloned the repository to your local machine or codespace, you'll After you've cloned the repository to your local machine or codespace, you'll
need to perform a few setup steps before you can work on the action. need to perform some initial setup steps before you can develop your action.
> [!NOTE] > [!NOTE]
> >
> You'll need a recent version of [Bun](https://bun.sh) for local development. > You'll need to have a reasonably modern version of
> This repository includes a `.bun-version` file for tools that can auto-switch > [Node.js](https://nodejs.org) handy (20.x or later should work!). If you are
> Bun versions. > using a version manager like [`nodenv`](https://github.com/nodenv/nodenv) or
> [`fnm`](https://github.com/Schniz/fnm), this template has a `.node-version`
> file at the root of the repository that can be used to automatically switch to
> the correct version when you `cd` into the repository. Additionally, this
> `.node-version` file is used by GitHub Actions in any `actions/setup-node`
> actions.
1. :hammer_and_wrench: Install the dependencies 1. :hammer_and_wrench: Install the dependencies
```bash ```bash
bun install npm ci
```
1. :building_construction: Package the TypeScript for distribution
```bash
npm run bundle
``` ```
1. :white_check_mark: Run the tests 1. :white_check_mark: Run the tests
```bash ```bash
bun test $ npm test
PASS ./index.test.js
✓ gets download url to binary (3 ms)
✓ runs main action (891 ms)
...
``` ```
1. :mag: Run the full local CI suite ## Publish to a distribution branch
```bash Actions are run from this GitHub repository so we will checkin the packed `dist`
bun run ci folder.
```
## Publish
1. Create a new GitHub release 1. Create a new GitHub release
2. Rebase `v3` branch on `main` 2. Rebase `v1` branch on `main`
Your action is now published! :rocket: Your action is now published! :rocket:
@@ -168,17 +148,14 @@ See the
## Validate ## Validate
Validate changes by exercising the action from a workflow in this repository You can now validate the action by referencing `./` in a workflow in your
(see [ci.yml](.github/workflows/ci.yml) and [e2e.yml](.github/workflows/e2e.yml)). repository (see [test.yml](.github/workflows/test.yml))
```yaml ```yaml
steps: uses: ./
- uses: ./ with:
with: version: latest
version: latest
``` ```
The CI workflow provides fast smoke coverage across GitHub-hosted runners, and See the [actions tab](https://github.com/supabase/setup-cli/actions) for runs of
the E2E workflow verifies `supabase init` and `supabase start` against supported this action! :rocket:
Postgres versions. See the [actions tab](https://github.com/supabase/setup-cli/actions)
for recent runs.

97
__tests__/main.test.ts Normal file
View File

@@ -0,0 +1,97 @@
import { getDownloadArchive, getDownloadUrl } from '../src/utils'
import { CLI_CONFIG_REGISTRY } from '../src/main'
import * as os from 'os'
import * as process from 'process'
import * as cp from 'child_process'
import * as path from 'path'
import * as fs from 'fs'
import * as yaml from 'js-yaml'
import * as url from 'url'
import { afterEach, expect, jest, test } from '@jest/globals'
afterEach(() => {
jest.restoreAllMocks()
})
test('gets download url to binary', async () => {
const url = await getDownloadUrl('1.28.0')
expect(
url.startsWith(
'https://github.com/supabase/cli/releases/download/v1.28.0/supabase_'
)
).toBeTruthy()
expect(url.endsWith('.tar.gz')).toBeTruthy()
expect(url).not.toContain('_1.28.0_')
})
test('gets legacy download url to binary', async () => {
const url = await getDownloadUrl('0.1.0')
expect(
url.startsWith(
`https://github.com/supabase/cli/releases/download/v0.1.0/supabase_0.1.0_`
)
).toBeTruthy()
expect(url.endsWith('.tar.gz')).toBeTruthy()
})
test('gets download url to latest version', async () => {
jest.spyOn(globalThis, 'fetch').mockResolvedValue(
new Response(JSON.stringify({ tag_name: 'v2.99.0' }), {
status: 200,
statusText: 'OK'
})
)
const url = await getDownloadUrl('latest')
expect(url).toContain('/download/v2.99.0/supabase_2.99.0_')
expect(url).toMatch(/\.tar\.gz$|\.zip$/)
})
test('gets versioned archive url to binary from Supabase CLI v2.99.0', async () => {
const archive = await getDownloadArchive('2.99.0', 'linux', 'x64')
expect(archive).toEqual({
url: 'https://github.com/supabase/cli/releases/download/v2.99.0/supabase_2.99.0_linux_amd64.tar.gz',
format: 'tar'
})
})
test('gets versioned zip archive url on Windows from Supabase CLI v2.99.0', async () => {
const archive = await getDownloadArchive('2.99.0', 'win32', 'x64')
expect(archive).toEqual({
url: 'https://github.com/supabase/cli/releases/download/v2.99.0/supabase_2.99.0_windows_amd64.zip',
format: 'zip'
})
})
test('keeps unversioned archive url to binary before Supabase CLI v2.99.0', async () => {
const url = await getDownloadUrl('2.98.2')
expect(url).toContain('/download/v2.98.2/supabase_')
expect(url).not.toContain('supabase_2.98.2_')
expect(url).toMatch(/\.tar\.gz$/)
})
// shows how the runner will run a javascript action with env / stdout protocol
test('runs main action', () => {
const { env, execPath } = process
const repo = path.dirname(path.dirname(url.fileURLToPath(import.meta.url)))
const config = path.join(repo, 'action.yml')
const action = yaml.load(fs.readFileSync(config, 'utf8')) as {
inputs: { version: { default: string } }
}
const ip = path.join(repo, 'dist', 'index.js')
const stdout = cp
.execFileSync(execPath, [ip], {
env: {
...env,
RUNNER_TEMP: os.tmpdir(),
INPUT_VERSION: action.inputs.version.default
}
})
.toString()
expect
.stringContaining(`::set-env name=${CLI_CONFIG_REGISTRY}::`)
.asymmetricMatch(stdout)
})

View File

@@ -3,115 +3,12 @@ description: Setup Supabase CLI, supabase, on GitHub Actions runners
author: Supabase author: Supabase
inputs: inputs:
version: version:
description: Supabase CLI version to install. Supports latest, beta, or a fixed version published to npm. If omitted, detect from the root lockfile and otherwise use latest. description: Version of Supabase CLI to install
required: false required: false
default: 2.20.3
outputs: outputs:
version: version:
description: Version of installed Supabase CLI description: Version of installed Supabase CLI
value: ${{ steps.setup-cli.outputs.version }}
runs: runs:
using: composite using: node20
steps: main: dist/index.js
- 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 Node
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 24
- 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

1
badges/coverage.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="116" height="20" role="img" aria-label="Coverage: 51.42%"><title>Coverage: 51.42%</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="116" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="63" height="20" fill="#555"/><rect x="63" width="53" height="20" fill="#e05d44"/><rect width="116" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="325" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="530">Coverage</text><text x="325" y="140" transform="scale(.1)" fill="#fff" textLength="530">Coverage</text><text aria-hidden="true" x="885" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="430">51.42%</text><text x="885" y="140" transform="scale(.1)" fill="#fff" textLength="430">51.42%</text></g></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

155
bun.lock
View File

@@ -1,155 +0,0 @@
{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"name": "setup-cli",
"dependencies": {
"@actions/core": "^3.0.1",
},
"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",
},
},
},
"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/exec": ["@actions/exec@3.0.0", "", { "dependencies": { "@actions/io": "^3.0.2" } }, "sha512-6xH/puSoNBXb72VPlZVm7vQ+svQpFyA96qdDBvhB8eNZOE8LtPf9L4oAsfzK/crCL8YZ+19fKYVnM63Sl+Xzlw=="],
"@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=="],
"@oxfmt/binding-android-arm64": ["@oxfmt/binding-android-arm64@0.49.0", "", { "os": "android", "cpu": "arm64" }, "sha512-Ef7SKJqAaH2d7E6eXZZa2OffIShbhFMxnGK0zd93p4qiyTJr75B0qf7lrPD+qQOwcf04BrjYJ0JUxq8d5+yZwg=="],
"@oxfmt/binding-darwin-arm64": ["@oxfmt/binding-darwin-arm64@0.49.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-8x5DN9CsFfb432sHa9NyqX5XisGUdA53LPEGSdv/VniS+v4uEOR8Orv7A9QSB98Xxgp0t6r31DzQA/wpIobGqQ=="],
"@oxfmt/binding-darwin-x64": ["@oxfmt/binding-darwin-x64@0.49.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-e0+DSVzk4ewhMVKNYDaRTmP81jNMBWR1X9al0cVKWS+hDM/dElNqD5zjTOCuLOZc4oOdp2Gx2ldrVL+yYo9TZQ=="],
"@oxfmt/binding-freebsd-x64": ["@oxfmt/binding-freebsd-x64@0.49.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-W+mjtYtrQvFbXT/uNT+221OBhGRZ8UqNsLxjTWsjZ4GsQnRdvRC/N2NCK86BcamWr7lsTxwpwN3PULnr78sgcQ=="],
"@oxfmt/binding-linux-arm-gnueabihf": ["@oxfmt/binding-linux-arm-gnueabihf@0.49.0", "", { "os": "linux", "cpu": "arm" }, "sha512-Rtv6UevV7czDlLqil+NZUe4d8gs8jQo/zScSpumwyf7I+fSdLc+hc8AF3MQC7ymxSMMD9+vfiqQlsIf7wOAzXA=="],
"@oxfmt/binding-linux-arm-musleabihf": ["@oxfmt/binding-linux-arm-musleabihf@0.49.0", "", { "os": "linux", "cpu": "arm" }, "sha512-sBi+8C/Q/MdKa5FL8ibAUCdhFBGFH7HFN/Qoyd5xQbZ/0ky3NMPpKfIBpaH0lhK2dXkGLczVQUoZ+xuNSerCdQ=="],
"@oxfmt/binding-linux-arm64-gnu": ["@oxfmt/binding-linux-arm64-gnu@0.49.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-JIfWenFhlzx+O8YygyZhoHFzTsdgDhxhbDRnE2iJLnnM5pWKScFvPECO2vOlA7JqJ/9S1g3uzEKuRCkHFwTjvA=="],
"@oxfmt/binding-linux-arm64-musl": ["@oxfmt/binding-linux-arm64-musl@0.49.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-iNzkMPG18jPkwBOZ4/HEjwqfzAjq4RrUQ0CgId/fC1ENvYD5jLVAaU/gWgpiqP1ys07kxSsSggDd1fp3E7mQHw=="],
"@oxfmt/binding-linux-ppc64-gnu": ["@oxfmt/binding-linux-ppc64-gnu@0.49.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-BPHA/NN3LvoIXiid+iz3BHt5V0Rzx0tXAqRUovwE1NsbDaLG9e8mtv7evDGRIkVQacqTDBv0XL25THHsxSJosQ=="],
"@oxfmt/binding-linux-riscv64-gnu": ["@oxfmt/binding-linux-riscv64-gnu@0.49.0", "", { "os": "linux", "cpu": "none" }, "sha512-3Eroshe+s69htC9JIL0+zLGQczLtRKezkMhwqQC21VC5Z/fuLvzLfbAOLgJLUq601H8gDYjy7deYycfOBjCvWg=="],
"@oxfmt/binding-linux-riscv64-musl": ["@oxfmt/binding-linux-riscv64-musl@0.49.0", "", { "os": "linux", "cpu": "none" }, "sha512-fnaERGgsxGm0lKAmO72EYR4BA3qBnzBTJBTi6EtUMq1D4R7EexRBMU4voXnx4TXla3SEDl9x4uNp/18SbkPjGg=="],
"@oxfmt/binding-linux-s390x-gnu": ["@oxfmt/binding-linux-s390x-gnu@0.49.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-rBwasMl1Uul1MCCeTGEFKnOTL7VUxHf+634jWStrQAbzpBJgd5Yz5m4F7exVCsoI8PHn57dNjssXagXLCLB5yA=="],
"@oxfmt/binding-linux-x64-gnu": ["@oxfmt/binding-linux-x64-gnu@0.49.0", "", { "os": "linux", "cpu": "x64" }, "sha512-BoC/F9xHe2y/deuBGA5Aw7bes07OD2gcL2wlpzTrfImR92vPP7S/k3LBTyspQZCNIVNdagkELcqKELwMLGIfAg=="],
"@oxfmt/binding-linux-x64-musl": ["@oxfmt/binding-linux-x64-musl@0.49.0", "", { "os": "linux", "cpu": "x64" }, "sha512-umY6jFADAo/oztFKl8D/S6vSrG6oBpEskcentiRuz42kZVU2kfDXMWCYavxyZR2bwPjqkHpcHZ6EZFiH3Qj9ZA=="],
"@oxfmt/binding-openharmony-arm64": ["@oxfmt/binding-openharmony-arm64@0.49.0", "", { "os": "none", "cpu": "arm64" }, "sha512-J85zQMiw2pXiGPK+OusmDvSnJ/dgpgN7VgmB2zOBtgS8F+nsOUfSg9ZEBrwbQscjZ7tkPbm38CG4VF5f53MsiA=="],
"@oxfmt/binding-win32-arm64-msvc": ["@oxfmt/binding-win32-arm64-msvc@0.49.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-38K67XR++CoFFORDd4sMFwUVAnD6msYBdGTei+qvKGrRPO6S2PbrYPNL/eQQ1RgnnxOegNba0YQwg6uRkNcw6A=="],
"@oxfmt/binding-win32-ia32-msvc": ["@oxfmt/binding-win32-ia32-msvc@0.49.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-rXVe0HICwQF0dBgbQtBCoYf8x/SidPIdhyQl+iPuJlV7suV+qDv7yUEB3wQ4qC3nOeNxz287SwFXKzyr0kWgEg=="],
"@oxfmt/binding-win32-x64-msvc": ["@oxfmt/binding-win32-x64-msvc@0.49.0", "", { "os": "win32", "cpu": "x64" }, "sha512-gwWLwSEmBBfIK/Wh7GGd658161o4RKAvHWRaRQbJm571iQXGKfyr7UKsI1vsWvDlNLc30CxJDc8mMmCvJ/kczQ=="],
"@oxlint-tsgolint/darwin-arm64": ["@oxlint-tsgolint/darwin-arm64@0.22.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-4150Lpgc1YM09GcjA6GSrra1JoPjC7aOpfywLjWEY4vW0Sd1qKzqHF1WRaiw0/qUZ40OATYdv3aRd7ipPkWQbw=="],
"@oxlint-tsgolint/darwin-x64": ["@oxlint-tsgolint/darwin-x64@0.22.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-vFWcPWYOgZs4HWcgS1EjUZg33NLcNfEYU49KGImmCfZWkflENrmBYV4HN/C0YeAPum6ZZ/goPSvQrB/cOD+NfA=="],
"@oxlint-tsgolint/linux-arm64": ["@oxlint-tsgolint/linux-arm64@0.22.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-6LiUpP0Zir3+29FvBm7Y28q/dBjSHqTZ5MhG1Ckw4fGhI4cAvbcwXaKvbjx1TP7rRmBNOoq/M5xdpHjTb+GAew=="],
"@oxlint-tsgolint/linux-x64": ["@oxlint-tsgolint/linux-x64@0.22.1", "", { "os": "linux", "cpu": "x64" }, "sha512-fuX1hEQfpHauUbXADsfqVhRzrUrGabzGXbj5wsp2vKhV5uk/Rze8Mba9GdjFGECzvXudMGqHqxB4r6jGRdhxVA=="],
"@oxlint-tsgolint/win32-arm64": ["@oxlint-tsgolint/win32-arm64@0.22.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SZidAj+jrbZf9ZjBEYW0tiNZ+KasqB2zgW26qdiPpQSF/DzURnPmXz651IeA9YsmbVdHGIooEHUmev6QJdquA=="],
"@oxlint-tsgolint/win32-x64": ["@oxlint-tsgolint/win32-x64@0.22.1", "", { "os": "win32", "cpu": "x64" }, "sha512-QweSk9H5lFh5Y+WUf2Kq/OAN88V6+62ZwGhP38gqdRotI90luXSMkruFTj7Q2rYrzH4ZVNaSqx7NY8JpSfIzqg=="],
"@oxlint/binding-android-arm-eabi": ["@oxlint/binding-android-arm-eabi@1.64.0", "", { "os": "android", "cpu": "arm" }, "sha512-2r6Nq3XXGLHEXKkSj8JtmJ6N4gDw431DPFOg0ZoJHlNjnG6HVMm/ksQ10m0HJ8WBvwgMe1L50UHPaYZutCRPCw=="],
"@oxlint/binding-android-arm64": ["@oxlint/binding-android-arm64@1.64.0", "", { "os": "android", "cpu": "arm64" }, "sha512-ePJMpePgg7fBv+L/hVx1xXRU5/5gd5m0obLA6hPEfLXF3GjpR8idIDbY1dhQYhyz1ms2wdTccSboo6KEd2Oxtg=="],
"@oxlint/binding-darwin-arm64": ["@oxlint/binding-darwin-arm64@1.64.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-U4DMLQd10gJLuoSTLSGbfv3bGjTlUNsScm9Dgb8wwBqmCzidf1pE1pXV4doGNxqwH3KtVng1AGTINA0NvkGLvQ=="],
"@oxlint/binding-darwin-x64": ["@oxlint/binding-darwin-x64@1.64.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-GoRIL48QWm4/TAvjN8pB1nAG+1/uqc9EdnWT9zqHeb6wsmjZtywj8VRe5aGW47Fdb64YtLOsdLqVxOvQuz98Wg=="],
"@oxlint/binding-freebsd-x64": ["@oxlint/binding-freebsd-x64@1.64.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-5dFkv4tkg7PxJJGS9/OjrJwjhuHczrd3OQOkRE0wHcLM+ncUnULtzEPWjqGOxTXxZnLWcB91bGiIznx89TVXyQ=="],
"@oxlint/binding-linux-arm-gnueabihf": ["@oxlint/binding-linux-arm-gnueabihf@1.64.0", "", { "os": "linux", "cpu": "arm" }, "sha512-jsBqMLl/uOL5+Kq/+BtK9FrmiNGUbx8SiyZXv+WlUxA45KuwcLu9BfiSIL3I3DBDgWM3yZizDITnTK9BcqNBQg=="],
"@oxlint/binding-linux-arm-musleabihf": ["@oxlint/binding-linux-arm-musleabihf@1.64.0", "", { "os": "linux", "cpu": "arm" }, "sha512-1lrj8At/Uuc9GhjrVFBQo0NEjfBrTkzpmtHIGAhNnIXqn1CAyGL+qrztUsXb2GIluJrpl9Q7qRLJOb/NqydacQ=="],
"@oxlint/binding-linux-arm64-gnu": ["@oxlint/binding-linux-arm64-gnu@1.64.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-HpSQbubwh03mMhAdy2BYtad/fsY8vDFHDAb6bUwuCYg2VD3xCQgn6ArKcO0oZyLCheacKTv4PrF3Mfu5hgoE2g=="],
"@oxlint/binding-linux-arm64-musl": ["@oxlint/binding-linux-arm64-musl@1.64.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-00QQ0h0Y7u0G69BgiH3+ky2aaq/QvkDL6DYok8htIuJHxybiux5aQ8jwmg8qIk9wha6UagUP2BAwAzbemcJbpg=="],
"@oxlint/binding-linux-ppc64-gnu": ["@oxlint/binding-linux-ppc64-gnu@1.64.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-2GaimTV6EMW+s5HS0An3oGbQme3BgHswvfVdGk3EB57Xe9+/gyT+Qd7lNVzb3rtir52vbIPzXfaYArzs5b5zcw=="],
"@oxlint/binding-linux-riscv64-gnu": ["@oxlint/binding-linux-riscv64-gnu@1.64.0", "", { "os": "linux", "cpu": "none" }, "sha512-H46AtFb9wypjoVwGdlxrm0DsD809NGmtiK9HiyPKTxkSte2YjhC4S+00rOIrwCaxcyPiGid3Y3OMXp5KMAkGZw=="],
"@oxlint/binding-linux-riscv64-musl": ["@oxlint/binding-linux-riscv64-musl@1.64.0", "", { "os": "linux", "cpu": "none" }, "sha512-HEgsidjjvvyzdg82icYkuFCf7REDV7B9JFwbIMbVwrKLBY0MrXX+bku3POn/hduZ2yW91IyVDUMq0Bf02KwXQw=="],
"@oxlint/binding-linux-s390x-gnu": ["@oxlint/binding-linux-s390x-gnu@1.64.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-Axvm8qryotmKN00P5w4JapaSjvP2LOSbdbBJiX+2SuHd3QzhW7TUc8skqgw+ahQZ5DmzEYeHCqauvW8f32Ns6Q=="],
"@oxlint/binding-linux-x64-gnu": ["@oxlint/binding-linux-x64-gnu@1.64.0", "", { "os": "linux", "cpu": "x64" }, "sha512-cR60vSd7+m+KRZ3GQGfDxWwahW5RMXg0qlGvAluZr0fTUYvw0H9N9AXAF/M/PMqgytyqvVNmBAkJG9l7U30Y1g=="],
"@oxlint/binding-linux-x64-musl": ["@oxlint/binding-linux-x64-musl@1.64.0", "", { "os": "linux", "cpu": "x64" }, "sha512-2u/aPZ9pEg7HnvZPDsHxUGNnrpr4qaHi+mCgLgpt+LYRzPrS4Px4wPfkIdRdr2GvKnaYyt+XSlto0Vm5sbStTg=="],
"@oxlint/binding-openharmony-arm64": ["@oxlint/binding-openharmony-arm64@1.64.0", "", { "os": "none", "cpu": "arm64" }, "sha512-kfhkGfCdoXLSxEkrhDlJrvBYajGmq+ma4EMc53dsOWTq+rIBOlI0vTBmpZNnM5oH2LY/K/w1HAK+UQEgjgpVUg=="],
"@oxlint/binding-win32-arm64-msvc": ["@oxlint/binding-win32-arm64-msvc@1.64.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-r/cNKBFieONoVu2bb1KkVouq9W+edDUgHumXJGphCRRj+U0xaD4nanrw8ZOqo0IsutPkEM4vCcGBpak6x5aXMg=="],
"@oxlint/binding-win32-ia32-msvc": ["@oxlint/binding-win32-ia32-msvc@1.64.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-tUw0xUUwEFVZbpJoeCblkv8SJA4Xz3CdXCJbAnBsiNLyxDrk2tLcxEAS6M73Q7hHHDg3OtwI8vZVK3t5RJt4Gw=="],
"@oxlint/binding-win32-x64-msvc": ["@oxlint/binding-win32-x64-msvc@1.64.0", "", { "os": "win32", "cpu": "x64" }, "sha512-9CBR+LO0JVST87fNTzzNxS5I29jIUO5gxT9i9+M3SDHHALElj9sY1Prf12tad3vIRC6OD7Ehtvvh+sn13vSwHw=="],
"@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/node": ["@types/node@20.19.37", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw=="],
"@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=="],
"@typescript/native-preview-darwin-arm64": ["@typescript/native-preview-darwin-arm64@7.0.0-dev.20260410.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bpLYm6woXd8BECzV9AQvPqISVeohpekK1qwpRopvNIxydhRQ4fEjZsS7EtDYpqHAW4/u1uEv07P9/iS6TAL1fQ=="],
"@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-linux-arm": ["@typescript/native-preview-linux-arm@7.0.0-dev.20260410.1", "", { "os": "linux", "cpu": "arm" }, "sha512-NO6Ci65ADadOCr2ycTxOyCgC5kyk+Ryjl8k5c78mz9sKDxYqwEtryFFjLqitAG+rejtJbnUq897WRICjAOwslA=="],
"@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-linux-x64": ["@typescript/native-preview-linux-x64@7.0.0-dev.20260410.1", "", { "os": "linux", "cpu": "x64" }, "sha512-IofIUrMGjXmZKDEMaRgshzOne0EQZtx9vE/6URHfgmDnWLDKWzz9eQ2qWmvsFD2vOBbgc6GwVWEq6XTHMEfx2A=="],
"@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-win32-x64": ["@typescript/native-preview-win32-x64@7.0.0-dev.20260410.1", "", { "os": "win32", "cpu": "x64" }, "sha512-dMFT4tdHBe2vVA2WPQMjorT+fzCURRtillevQzz8/bwCEz2uXSnpu4oLRLS5045ppGE0wCFELE+Hq5z2oRddDw=="],
"bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="],
"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=="],
"oxlint": ["oxlint@1.64.0", "", { "optionalDependencies": { "@oxlint/binding-android-arm-eabi": "1.64.0", "@oxlint/binding-android-arm64": "1.64.0", "@oxlint/binding-darwin-arm64": "1.64.0", "@oxlint/binding-darwin-x64": "1.64.0", "@oxlint/binding-freebsd-x64": "1.64.0", "@oxlint/binding-linux-arm-gnueabihf": "1.64.0", "@oxlint/binding-linux-arm-musleabihf": "1.64.0", "@oxlint/binding-linux-arm64-gnu": "1.64.0", "@oxlint/binding-linux-arm64-musl": "1.64.0", "@oxlint/binding-linux-ppc64-gnu": "1.64.0", "@oxlint/binding-linux-riscv64-gnu": "1.64.0", "@oxlint/binding-linux-riscv64-musl": "1.64.0", "@oxlint/binding-linux-s390x-gnu": "1.64.0", "@oxlint/binding-linux-x64-gnu": "1.64.0", "@oxlint/binding-linux-x64-musl": "1.64.0", "@oxlint/binding-openharmony-arm64": "1.64.0", "@oxlint/binding-win32-arm64-msvc": "1.64.0", "@oxlint/binding-win32-ia32-msvc": "1.64.0", "@oxlint/binding-win32-x64-msvc": "1.64.0" }, "peerDependencies": { "oxlint-tsgolint": ">=0.22.1" }, "optionalPeers": ["oxlint-tsgolint"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-Star3SNpWPeWFPw7kRXIhXUSn6fdiAl25q15CQzH/9WaOtG6e9CWTc25vNZOCr4PE1yEP1GtKJKIKglhj3OmEQ=="],
"oxlint-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=="],
"tinypool": ["tinypool@2.1.0", "", {}, "sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw=="],
"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=="],
}
}

32627
dist/index.js generated vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/index.js.map generated vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,55 +1,42 @@
# `supabase/setup-cli` # `supabase-github-action`
The Supabase CLI Action provides an easy way to install the The Supabase GitHub Action provides an easy way to use the Supabase CLI on
[Supabase CLI](https://github.com/supabase/cli) on GitHub Actions runners. GitHub's hosted Actions runners.
The action supports `ubuntu-latest`, `windows-latest`, and `macos-latest`, and The action can be run on `ubuntu-latest`, `windows-latest`, and `macos-latest`
adds the requested `supabase` version to `PATH` for the rest of the job. GitHub Actions runners, and will install and expose a specified version of the
Supabase CLI on the runner environment.
If `version` is omitted, the action checks the repository root for `bun.lock`, ## Quick start
`pnpm-lock.yaml`, or `package-lock.json` and otherwise falls back to npm
`latest`.
The action provisions Node.js and npm internally; runners only need network This example shows how you can use the Supabase GitHub Action to test your
access to the npm registry. migrations on every Pull Request.
## Quick Start Inside your repository, create a new file inside the `.github/workflows` folder
called `test-migrations.yml`.
This example runs Supabase migrations on every pull request: Copy this snippet inside the file, and the action will run whenever a new PR is
created:
```yaml ```yaml
name: test-migrations name: 'test-migrations'
on: on:
pull_request: pull_request:
jobs: jobs:
test-migrations: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: supabase/setup-cli@v1
- uses: supabase/setup-cli@v3 with:
- run: supabase init version: latest
- run: supabase db start - run: supabase init
``` - run: supabase db start
To pin a fixed npm-published CLI version:
```yaml
- uses: supabase/setup-cli@v3
with:
version: 2.84.2
```
To test the current beta release:
```yaml
- uses: supabase/setup-cli@v3
with:
version: beta
``` ```
## Resources ## Resources
- **Source Code**: <https://github.com/supabase/setup-cli> - **Source Code**:
- **CLI Documentation**: <https://supabase.com/docs/guides/cli> <a href="https://github.com/supabase/supabase-github-action" target="_blank">github.com/supabase/supabase-github-action</a>
- **CLI Documentation**:
<a href="https://supabase.com/docs/guides/cli" target="_blank">supabase.com/docs/guides/cli</a>

81
eslint.config.mjs Normal file
View File

@@ -0,0 +1,81 @@
// See: https://eslint.org/docs/latest/use/configure/configuration-files
import { fixupPluginRules } from '@eslint/compat'
import { FlatCompat } from '@eslint/eslintrc'
import js from '@eslint/js'
import typescriptEslint from '@typescript-eslint/eslint-plugin'
import tsParser from '@typescript-eslint/parser'
import _import from 'eslint-plugin-import'
import jest from 'eslint-plugin-jest'
import prettier from 'eslint-plugin-prettier'
import globals from 'globals'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
allConfig: js.configs.all
})
export default [
{
ignores: ['**/coverage', '**/dist', '**/linter', '**/node_modules']
},
...compat.extends(
'eslint:recommended',
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
'plugin:jest/recommended',
'plugin:prettier/recommended'
),
{
plugins: {
import: fixupPluginRules(_import),
jest,
prettier,
'@typescript-eslint': typescriptEslint
},
languageOptions: {
globals: {
...globals.node,
...globals.jest,
Atomics: 'readonly',
SharedArrayBuffer: 'readonly'
},
parser: tsParser,
ecmaVersion: 2023,
sourceType: 'module',
parserOptions: {
project: ['tsconfig.eslint.json'],
tsconfigRootDir: '.'
}
},
settings: {
'import/resolver': {
typescript: {
alwaysTryTypes: true,
project: 'tsconfig.eslint.json'
}
}
},
rules: {
camelcase: 'off',
'eslint-comments/no-use': 'off',
'eslint-comments/no-unused-disable': 'off',
'i18n-text/no-en': 'off',
'import/no-namespace': 'off',
'no-console': 'off',
'no-shadow': 'off',
'no-unused-vars': 'off',
'prettier/prettier': 'error'
}
}
]

40
jest.config.js Normal file
View File

@@ -0,0 +1,40 @@
// See: https://jestjs.io/docs/configuration
/** @type {import('ts-jest').JestConfigWithTsJest} **/
export default {
clearMocks: true,
collectCoverage: true,
collectCoverageFrom: ['./src/**'],
coverageDirectory: './coverage',
coveragePathIgnorePatterns: ['/node_modules/', '/dist/'],
coverageReporters: ['json-summary', 'text', 'lcov'],
// Uncomment the below lines if you would like to enforce a coverage threshold
// for your action. This will fail the build if the coverage is below the
// specified thresholds.
// coverageThreshold: {
// global: {
// branches: 100,
// functions: 100,
// lines: 100,
// statements: 100
// }
// },
extensionsToTreatAsEsm: ['.ts'],
moduleFileExtensions: ['ts', 'js'],
preset: 'ts-jest',
reporters: ['default'],
resolver: 'ts-jest-resolver',
testEnvironment: 'node',
testMatch: ['**/*.test.ts'],
testPathIgnorePatterns: ['/dist/', '/node_modules/'],
transform: {
'^.+\\.ts$': [
'ts-jest',
{
tsconfig: 'tsconfig.eslint.json',
useESM: true
}
]
},
verbose: true
}

13198
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,40 +1,72 @@
{ {
"name": "setup-cli", "name": "setup-cli",
"version": "3.0.0",
"private": true,
"description": "Supabase CLI GitHub Action", "description": "Supabase CLI GitHub Action",
"keywords": [ "version": "1.6.0",
"actions"
],
"license": "MIT",
"author": "", "author": "",
"type": "module",
"private": true,
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/supabase/setup-cli.git" "url": "git+https://github.com/supabase/setup-cli.git"
}, },
"type": "module", "keywords": [
"scripts": { "actions"
"all": "bun run format && bun run lint && bun run coverage", ],
"ci": "bun run format:check && bun run lint && bun run coverage", "exports": {
"coverage": "bun test --coverage --coverage-reporter=text --coverage-reporter=lcov", ".": "./dist/index.js"
"format": "bun x oxfmt --write . '!coverage/**'",
"format:check": "bun x oxfmt --check . '!coverage/**'",
"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"
},
"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"
}, },
"engines": { "engines": {
"bun": ">=1.3.10" "node": ">=20"
},
"scripts": {
"bundle": "npm run format:write && npm run package",
"ci-test": "NODE_OPTIONS=--experimental-vm-modules NODE_NO_WARNINGS=1 npx jest",
"coverage": "npx make-coverage-badge --output-path ./badges/coverage.svg",
"format:write": "npx prettier --write .",
"format:check": "npx prettier --check .",
"lint": "npx eslint .",
"local-action": "npx @github/local-action . src/main.ts .env",
"package": "npx rollup --config rollup.config.ts --configPlugin @rollup/plugin-typescript",
"package:watch": "npm run package -- --watch",
"test": "NODE_OPTIONS=--experimental-vm-modules NODE_NO_WARNINGS=1 npx jest",
"all": "npm run format:write && npm run lint && npm run package && npm run test && npm run coverage"
},
"license": "MIT",
"dependencies": {
"@actions/core": "^1.11.1",
"@actions/tool-cache": "^2.0.2",
"semver": "^7.7.2"
},
"devDependencies": {
"@eslint/compat": "^1.3.1",
"@github/local-action": "^5.1.0",
"@jest/globals": "^30.0.5",
"@rollup/plugin-commonjs": "^28.0.6",
"@rollup/plugin-node-resolve": "^16.0.1",
"@rollup/plugin-typescript": "^12.1.4",
"@types/jest": "^30.0.0",
"@types/js-yaml": "^4.0.9",
"@types/node": "^20.19.9",
"@types/semver": "^7.7.0",
"@typescript-eslint/eslint-plugin": "^8.38.0",
"@typescript-eslint/parser": "^8.38.0",
"eslint": "^9.31.0",
"eslint-config-prettier": "^10.1.8",
"eslint-import-resolver-typescript": "^4.4.4",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-jest": "^29.0.1",
"eslint-plugin-prettier": "^5.5.3",
"jest": "^30.0.5",
"js-yaml": "^4.1.0",
"make-coverage-badge": "^1.2.0",
"prettier": "^3.6.2",
"prettier-eslint": "^16.4.2",
"rollup": "^4.45.1",
"ts-jest": "^29.4.0",
"ts-jest-resolver": "^2.0.1",
"typescript": "^5.8.3"
},
"optionalDependencies": {
"@rollup/rollup-linux-x64-gnu": "*"
} }
} }

18
rollup.config.ts Normal file
View File

@@ -0,0 +1,18 @@
// See: https://rollupjs.org/introduction/
import commonjs from '@rollup/plugin-commonjs'
import nodeResolve from '@rollup/plugin-node-resolve'
import typescript from '@rollup/plugin-typescript'
const config = {
input: 'src/index.ts',
output: {
esModule: true,
file: 'dist/index.js',
format: 'es',
sourcemap: true
},
plugins: [typescript(), nodeResolve({ preferBuiltins: true }), commonjs()]
}
export default config

8
src/index.ts Normal file
View File

@@ -0,0 +1,8 @@
/**
* The entrypoint for the action. This file simply imports and runs the action's
* main logic.
*/
import { run } from './main.js'
/* istanbul ignore next */
run()

View File

@@ -1,592 +0,0 @@
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import os from "node:os";
import path from "node:path";
import process from "node:process";
import { afterEach, expect, mock, spyOn, test } from "bun:test";
import * as core from "@actions/core";
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_BIN;
delete process.env.FAKE_NPM_INTEGRITY;
delete process.env.FAKE_NPM_LOG;
delete process.env.FAKE_NPM_PACKAGE_VERSION;
delete process.env.FAKE_NPM_POSTINSTALL;
delete process.env.SUPABASE_SETUP_CLI_NPM;
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;
}
function createWorkspace(files: Record<string, string>): string {
const dir = createTempDir("setup-cli-workspace-");
for (const [relativePath, content] of Object.entries(files)) {
const filePath = path.join(dir, relativePath);
mkdirSync(path.dirname(filePath), { recursive: true });
writeFileSync(filePath, content);
}
return dir;
}
function createBunLock(
version: string,
options: {
includeDependency?: boolean;
includePackageEntry?: boolean;
integrity?: string;
useDevDependency?: boolean;
} = {},
): string {
const includeDependency = options.includeDependency ?? true;
const includePackageEntry = options.includePackageEntry ?? true;
const dependencyKey = options.useDevDependency ? "devDependencies" : "dependencies";
return `{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"name": "app",
"${dependencyKey}": {
${includeDependency ? ` "supabase": "^${version}"` : ""}
}
}
},
"packages": {
${
includePackageEntry
? ` "supabase": [
"supabase@${version}",
"",
{},
"${options.integrity ?? "sha512-bun"}"
]`
: ""
}
}
}
`;
}
function createPnpmLock(
version: string,
options: {
asString?: boolean;
includeVersion?: boolean;
integrity?: string;
useDevDependency?: boolean;
} = {},
): string {
const dependencyKey = options.useDevDependency ? "devDependencies" : "dependencies";
return `lockfileVersion: "9.0"
importers:
.:
${dependencyKey}:
${
options.asString
? ` supabase: ${version}`
: ` supabase:
specifier: ^${version}
${options.includeVersion === false ? "" : ` version: ${version}`}`
}
packages:
supabase@${version}:
resolution:
integrity: ${options.integrity ?? "sha512-pnpm"}
`;
}
function createPackageLock(version: string, integrity = "sha512-package-lock"): string {
return JSON.stringify(
{
name: "app",
lockfileVersion: 3,
packages: {
"": {
dependencies: {
supabase: `^${version}`,
},
},
"node_modules/supabase": {
integrity,
version,
},
},
},
null,
2,
);
}
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") {
const bin =
process.env.FAKE_NPM_BIN === "missing"
? undefined
: { supabase: process.env.FAKE_NPM_BIN ?? "dist/supabase.js" };
const scripts = process.env.FAKE_NPM_POSTINSTALL
? { postinstall: process.env.FAKE_NPM_POSTINSTALL }
: {};
console.log(
JSON.stringify({
version: process.env.FAKE_NPM_PACKAGE_VERSION ?? "2.101.0",
bin,
scripts,
"dist.integrity": 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",
options: {
bin?: string;
integrity?: string;
packageVersion?: string;
postinstall?: string;
} = {},
): 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_BIN = options.bin ?? "dist/supabase.js";
process.env.FAKE_NPM_INTEGRITY = options.integrity ?? "sha512-test";
process.env.FAKE_NPM_LOG = logPath;
process.env.FAKE_NPM_PACKAGE_VERSION =
options.packageVersion ??
versionOutput.match(/\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?/)?.[0] ??
"2.101.0";
if (options.postinstall) {
process.env.FAKE_NPM_POSTINSTALL = options.postinstall;
}
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 viewMetadataCall(spec: string): string[] {
return ["view", spec, "version", "bin", "scripts", "dist.integrity", "--json"];
}
function createActionSpies(inputVersion: string) {
return {
addPath: spyOn(core, "addPath").mockImplementation(() => {}),
exportVariable: spyOn(core, "exportVariable").mockImplementation(() => {}),
getInput: spyOn(core, "getInput").mockReturnValue(inputVersion),
setFailed: spyOn(core, "setFailed").mockImplementation(() => {}),
setOutput: spyOn(core, "setOutput").mockImplementation(() => {}),
};
}
async function getMainModule(): Promise<typeof import("./main.ts")> {
if (!mainModule) {
mainModule = await 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("uses an explicit npm dist-tag when provided", async () => {
const { resolvePackage } = await getMainModule();
expect(resolvePackage("beta")).toEqual({
spec: "supabase@beta",
version: "beta",
});
});
test("rejects unsupported npm package selectors", async () => {
const { resolvePackage } = await getMainModule();
expect(() => resolvePackage("hotfix")).toThrow(
'Unsupported Supabase CLI version "hotfix". Use latest, beta, or a fixed npm package version like 2.101.0.',
);
});
test("uses the root bun.lock resolution when version is omitted", async () => {
process.env.GITHUB_WORKSPACE = createWorkspace({
"bun.lock": createBunLock("2.41.0", { integrity: "sha512-bun-lock" }),
});
const { resolvePackage } = await getMainModule();
expect(resolvePackage("")).toEqual({
integrity: "sha512-bun-lock",
spec: "supabase@2.41.0",
version: "2.41.0",
});
});
test("uses the root pnpm-lock.yaml resolution when version is omitted", async () => {
process.env.GITHUB_WORKSPACE = createWorkspace({
"pnpm-lock.yaml": createPnpmLock("2.42.0", { integrity: "sha512-pnpm-lock" }),
});
const { resolvePackage } = await getMainModule();
expect(resolvePackage("")).toEqual({
integrity: "sha512-pnpm-lock",
spec: "supabase@2.42.0",
version: "2.42.0",
});
});
test("uses the root package-lock.json resolution when version is omitted", async () => {
process.env.GITHUB_WORKSPACE = createWorkspace({
"package-lock.json": createPackageLock("2.43.0", "sha512-package-lock"),
});
const { resolvePackage } = await getMainModule();
expect(resolvePackage("")).toEqual({
integrity: "sha512-package-lock",
spec: "supabase@2.43.0",
version: "2.43.0",
});
});
test("falls through malformed lockfiles and uses the next supported root lockfile", async () => {
process.env.GITHUB_WORKSPACE = createWorkspace({
"bun.lock": "{ not valid",
"package-lock.json": createPackageLock("2.44.0"),
});
const { resolvePackage } = await getMainModule();
expect(resolvePackage("")).toEqual({
integrity: "sha512-package-lock",
spec: "supabase@2.44.0",
version: "2.44.0",
});
});
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();
expect(resolvePackage("")).toEqual({
spec: "supabase@latest",
version: "latest",
});
});
test("falls back to latest when version is omitted and no workspace is available", async () => {
delete process.env.GITHUB_WORKSPACE;
const { resolvePackage } = await getMainModule();
expect(resolvePackage("")).toEqual({
spec: "supabase@latest",
version: "latest",
});
});
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();
expect(resolvePackage("")).toEqual({
spec: "supabase@2.44.1",
version: "2.44.1",
});
});
test("falls through bun.lock without supabase and uses a pnpm string dependency version", async () => {
process.env.GITHUB_WORKSPACE = createWorkspace({
"bun.lock": createBunLock("2.47.0", { includeDependency: false }),
"pnpm-lock.yaml": createPnpmLock("2.47.0", { asString: true }),
});
const { resolvePackage } = await getMainModule();
expect(resolvePackage("")).toEqual({
integrity: "sha512-pnpm",
spec: "supabase@2.47.0",
version: "2.47.0",
});
});
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([
viewMetadataCall("supabase@2.101.0"),
[
"install",
"--prefix",
expect.any(String),
"--omit=dev",
"--include=optional",
"--no-audit",
"--no-fund",
"--no-package-lock",
"--ignore-scripts=true",
"supabase@2.101.0",
],
]);
});
test("allows install scripts for legacy npm packages that declare a postinstall", async () => {
const logPath = installFakeNpm("supabase 1.178.2", {
bin: "bin/supabase",
postinstall: "node scripts/postinstall.js",
});
const { installCli } = await getMainModule();
await installCli({
spec: "supabase@1.178.2",
version: "1.178.2",
});
expect(readNpmCalls(logPath)).toEqual([
viewMetadataCall("supabase@1.178.2"),
[
"install",
"--prefix",
expect.any(String),
"--omit=dev",
"--include=optional",
"--no-audit",
"--no-fund",
"--no-package-lock",
"--ignore-scripts=false",
"supabase@1.178.2",
],
]);
});
test("verifies lockfile integrity before installing", async () => {
const logPath = installFakeNpm("supabase 2.101.0", { integrity: "sha512-lock" });
const { installCli } = await getMainModule();
await installCli({
integrity: "sha512-lock",
spec: "supabase@2.101.0",
version: "2.101.0",
});
expect(readNpmCalls(logPath)).toEqual([
viewMetadataCall("supabase@2.101.0"),
[
"install",
"--prefix",
expect.any(String),
"--omit=dev",
"--include=optional",
"--no-audit",
"--no-fund",
"--no-package-lock",
"--ignore-scripts=true",
"supabase@2.101.0",
],
]);
});
test("fails when lockfile integrity does not match the registry", async () => {
installFakeNpm("supabase 2.101.0", { integrity: "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("fails when the npm package does not expose a Supabase CLI executable", async () => {
installFakeNpm("supabase 2.101.0", { bin: "missing" });
const { installCli } = await getMainModule();
try {
await installCli({
spec: "supabase@2.101.0",
version: "2.101.0",
});
throw new Error("Expected installCli to reject");
} catch (error) {
expect(error).toEqual(
new Error("The npm package supabase@2.101.0 does not expose a supabase executable"),
);
}
});
test("runs the action with a package-lock resolution", async () => {
const logPath = installFakeNpm("supabase 2.43.0", { integrity: "sha512-package-lock" });
process.env.GITHUB_WORKSPACE = createWorkspace({
"package-lock.json": createPackageLock("2.43.0", "sha512-package-lock"),
});
const spies = createActionSpies("");
const { run } = await getMainModule();
await run();
expect(readNpmCalls(logPath)[0]).toEqual(viewMetadataCall("supabase@2.43.0"));
expect(spies.setOutput).toHaveBeenCalledWith("version", "supabase 2.43.0");
expect(spies.addPath).toHaveBeenCalledWith(expect.stringContaining("node_modules"));
expect(spies.exportVariable).toHaveBeenCalledWith(CLI_CONFIG_REGISTRY, "ghcr.io");
expect(spies.setFailed).not.toHaveBeenCalled();
});
test("explicit version overrides detected root lockfiles", async () => {
installFakeNpm("supabase 1.1.6");
process.env.GITHUB_WORKSPACE = createWorkspace({
"bun.lock": createBunLock("2.45.0"),
});
const spies = createActionSpies("1.1.6");
const { run } = await getMainModule();
await run();
expect(spies.setOutput).toHaveBeenCalledWith("version", "supabase 1.1.6");
expect(spies.exportVariable).not.toHaveBeenCalled();
expect(spies.setFailed).not.toHaveBeenCalled();
});
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"),
});
const spies = createActionSpies("");
const { run } = await getMainModule();
await run();
expect(spies.setFailed).toHaveBeenCalledWith(
"Could not determine installed Supabase CLI version",
);
expect(spies.setOutput).not.toHaveBeenCalled();
expect(spies.addPath).not.toHaveBeenCalled();
expect(spies.exportVariable).not.toHaveBeenCalled();
});

View File

@@ -1,399 +1,42 @@
import { semver } from "bun"; import * as core from '@actions/core'
import * as core from "@actions/core"; import * as tc from '@actions/tool-cache'
import { existsSync, mkdtempSync, readFileSync } from "node:fs"; import { gte } from 'semver'
import os from "node:os"; import { getDownloadArchive, determineInstalledVersion } from './utils.js'
import path from "node:path";
import { fileURLToPath } from "node:url"; export const CLI_CONFIG_REGISTRY = 'SUPABASE_INTERNAL_IMAGE_REGISTRY'
export const CLI_CONFIG_REGISTRY = "SUPABASE_INTERNAL_IMAGE_REGISTRY"; /**
const REGISTRY_VERSION = "1.28.0"; * The main function for the action.
const DEFAULT_VERSION = "latest"; *
const NPM_PACKAGE = "supabase"; * @returns Resolves when the action is complete.
const NPM_EXECUTABLE_ENV = "SUPABASE_SETUP_CLI_NPM"; */
const SUPPORTED_DIST_TAGS = new Set([DEFAULT_VERSION, "beta"]);
const CONCRETE_VERSION_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
const CONCRETE_VERSION_EXTRACT_PATTERN = /\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?/;
type PackageResolution = {
spec: string;
version: string;
integrity?: string;
};
type PackageMetadata = {
version?: unknown;
bin?: unknown;
scripts?: unknown;
"dist.integrity"?: unknown;
};
type BunLock = {
workspaces?: {
"": {
dependencies?: Record<string, string>;
devDependencies?: Record<string, string>;
};
};
packages?: Record<string, unknown>;
};
type PnpmDependency =
| string
| {
version?: string;
};
type PnpmPackage = {
resolution?: {
integrity?: string;
};
};
type PnpmLock = {
importers?: {
".": {
dependencies?: Record<string, PnpmDependency>;
devDependencies?: Record<string, PnpmDependency>;
};
};
packages?: Record<string, PnpmPackage>;
};
type PackageLock = {
packages?: Record<string, { integrity?: string; version?: string }>;
dependencies?: Record<string, { integrity?: string; version?: string }>;
};
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null;
}
function extractConcreteVersion(raw: string | undefined): string | null {
if (!raw) {
return null;
}
const match = raw.match(CONCRETE_VERSION_EXTRACT_PATTERN);
return match?.[0] ?? null;
}
function normalizeVersion(version: string): string {
return version.replace(/^v/i, "");
}
function normalizeSupportedVersion(version: string): string {
const normalizedVersion = normalizeVersion(version.trim());
const distTag = normalizedVersion.toLowerCase();
if (SUPPORTED_DIST_TAGS.has(distTag)) {
return distTag;
}
if (CONCRETE_VERSION_PATTERN.test(normalizedVersion)) {
return normalizedVersion;
}
throw new Error(
`Unsupported Supabase CLI version "${version}". Use latest, beta, or a fixed npm package version like 2.101.0.`,
);
}
function toPackageResolution(version: string, integrity?: string): PackageResolution {
const normalizedVersion = normalizeSupportedVersion(version);
return {
spec: `${NPM_PACKAGE}@${normalizedVersion}`,
version: normalizedVersion,
integrity,
};
}
function readWorkspaceLockfile(workspaceRoot: string, filename: string): string | null {
const filePath = path.join(workspaceRoot, filename);
if (!existsSync(filePath)) {
return null;
}
try {
return readFileSync(filePath, "utf8");
} catch {
return null;
}
}
function detectResolutionFromBunLock(workspaceRoot: string): PackageResolution | null {
const text = readWorkspaceLockfile(workspaceRoot, "bun.lock");
if (!text) {
return null;
}
try {
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];
if (!declaredVersion) {
return null;
}
const resolvedPackage = lockfile.packages?.[NPM_PACKAGE];
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;
}
const version = extractConcreteVersion(declaredVersion);
return version ? toPackageResolution(version) : null;
} catch {
return null;
}
}
function detectResolutionFromPnpmLock(workspaceRoot: string): PackageResolution | null {
const text = readWorkspaceLockfile(workspaceRoot, "pnpm-lock.yaml");
if (!text) {
return null;
}
try {
const lockfile = Bun.YAML.parse(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);
if (!version) {
return null;
}
const integrity = Object.entries(lockfile.packages ?? {}).find(
([packageKey]) =>
packageKey === `${NPM_PACKAGE}@${version}` ||
packageKey.startsWith(`/${NPM_PACKAGE}@${version}`),
)?.[1].resolution?.integrity;
return toPackageResolution(version, integrity);
} catch {
return null;
}
}
function detectResolutionFromPackageLock(workspaceRoot: string): PackageResolution | null {
const text = readWorkspaceLockfile(workspaceRoot, "package-lock.json");
if (!text) {
return null;
}
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;
} catch {
return null;
}
}
export function resolvePackage(inputVersion: string): PackageResolution {
const requestedVersion = inputVersion.trim();
if (requestedVersion) {
return toPackageResolution(requestedVersion);
}
const workspaceRoot = process.env.GITHUB_WORKSPACE?.trim();
if (!workspaceRoot) {
return toPackageResolution(DEFAULT_VERSION);
}
return (
detectResolutionFromBunLock(workspaceRoot) ??
detectResolutionFromPnpmLock(workspaceRoot) ??
detectResolutionFromPackageLock(workspaceRoot) ??
toPackageResolution(DEFAULT_VERSION)
);
}
function verifyPackageIntegrity(resolution: PackageResolution, metadata: PackageMetadata): void {
if (!resolution.integrity) {
return;
}
const registryIntegrity = metadata["dist.integrity"];
if (registryIntegrity !== resolution.integrity) {
throw new Error(`Lockfile integrity for ${resolution.spec} does not match the npm registry`);
}
}
async function getPackageMetadata(resolution: PackageResolution): Promise<PackageMetadata> {
const output = await runNpm([
"view",
resolution.spec,
"version",
"bin",
"scripts",
"dist.integrity",
"--json",
]);
const metadata = JSON.parse(output) as unknown;
if (!isRecord(metadata)) {
throw new Error(`Could not read npm metadata for ${resolution.spec}`);
}
return metadata;
}
function verifyPackageMetadata(resolution: PackageResolution, metadata: PackageMetadata): void {
if (typeof metadata.version !== "string" || !CONCRETE_VERSION_PATTERN.test(metadata.version)) {
throw new Error(`Could not resolve a fixed npm version for ${resolution.spec}`);
}
const bin = metadata.bin;
const hasSupabaseBin =
typeof bin === "string" || (isRecord(bin) && typeof bin[NPM_PACKAGE] === "string");
if (!hasSupabaseBin) {
throw new Error(`The npm package ${resolution.spec} does not expose a supabase executable`);
}
}
function shouldIgnoreInstallScripts(metadata: PackageMetadata): boolean {
return !(isRecord(metadata.scripts) && typeof metadata.scripts.postinstall === "string");
}
function createInstallRoot(): string {
const tempRoot = process.env.RUNNER_TEMP?.trim() || os.tmpdir();
return mkdtempSync(path.join(tempRoot, "setup-cli-"));
}
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> {
const metadata = await getPackageMetadata(resolution);
verifyPackageMetadata(resolution, metadata);
verifyPackageIntegrity(resolution, metadata);
const installRoot = createInstallRoot();
await runNpm([
"install",
"--prefix",
installRoot,
"--omit=dev",
"--include=optional",
"--no-audit",
"--no-fund",
"--no-package-lock",
`--ignore-scripts=${shouldIgnoreInstallScripts(metadata)}`,
resolution.spec,
]);
return path.join(installRoot, "node_modules", ".bin");
}
function getCliExecutablePath(cliPath: string): string {
if (process.platform !== "win32") {
return path.join(cliPath, "supabase");
}
const cmdPath = path.join(cliPath, "supabase.cmd");
if (existsSync(cmdPath)) {
return cmdPath;
}
const exePath = path.join(cliPath, "supabase.exe");
if (existsSync(exePath)) {
return exePath;
}
return path.join(cliPath, "supabase");
}
export async function determineInstalledVersion(cliPath: string): Promise<string> {
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 version = stdout.trim();
if (!version) {
throw new Error("Could not determine installed Supabase CLI version");
}
return version;
}
function shouldUseGhcrRegistry(requestedVersion: string, installedVersion: string): boolean {
if (requestedVersion.toLowerCase() === DEFAULT_VERSION) {
return true;
}
const concreteVersion = extractConcreteVersion(installedVersion);
return concreteVersion !== null && semver.order(concreteVersion, REGISTRY_VERSION) >= 0;
}
export async function run(): Promise<void> { export async function run(): Promise<void> {
try { try {
const resolution = resolvePackage(core.getInput("version")); // Get version of tool to be installed
const cliPath = await installCli(resolution); const version = core.getInput('version')
const installedVersion = await determineInstalledVersion(cliPath);
core.setOutput("version", installedVersion);
core.addPath(cliPath);
if (shouldUseGhcrRegistry(resolution.version, installedVersion)) { // Download the specific version of the tool, e.g. as a tarball/zipball
core.exportVariable(CLI_CONFIG_REGISTRY, "ghcr.io"); const download = await getDownloadArchive(version)
const pathToArchive = await tc.downloadTool(download.url)
// Extract the tarball/zipball onto host runner
const pathToCLI =
download.format === 'zip'
? await tc.extractZip(pathToArchive)
: await tc.extractTar(pathToArchive)
// Expose the tool by adding it to the PATH
core.addPath(pathToCLI)
// Expose installed tool version
const determinedVersion = await determineInstalledVersion()
core.setOutput('version', determinedVersion)
// Use GHCR mirror by default
if (version.toLowerCase() === 'latest' || gte(version, '1.28.0')) {
core.exportVariable(CLI_CONFIG_REGISTRY, 'ghcr.io')
} }
} catch (error) { } catch (error) {
core.setFailed(error instanceof Error ? error.message : String(error)); if (error instanceof Error) core.setFailed(error.message)
} }
} }
if (process.argv[1] === fileURLToPath(import.meta.url)) {
await run();
}

114
src/utils.ts Normal file
View File

@@ -0,0 +1,114 @@
import { exec } from 'child_process'
import os from 'os'
import { gte, lt } from 'semver'
import { promisify } from 'util'
const doExec = promisify(exec)
const VERSIONED_ARCHIVE_VERSION = '2.99.0'
const LATEST_RELEASE_URL =
'https://api.github.com/repos/supabase/cli/releases/latest'
export type ArchiveFormat = 'tar' | 'zip'
export type DownloadArchive = {
url: string
format: ArchiveFormat
}
// arch in [arm, arm64, x64...] (https://nodejs.org/docs/latest-v16.x/api/os.html#osarch)
// return value in [amd64, arm64, arm]
const mapArch = (arch: string): string => {
const mappings: Record<string, string> = {
x64: 'amd64'
}
return mappings[arch] || arch
}
// os in [darwin, linux, win32...] (https://nodejs.org/docs/latest-v16.x/api/os.html#osplatform)
// return value in [darwin, linux, windows]
const mapOS = (platform: string): string => {
const mappings: Record<string, string> = {
win32: 'windows'
}
return mappings[platform] || platform
}
const normalizeVersion = (version: string): string => version.replace(/^v/i, '')
const resolveLatestVersion = async (): Promise<string> => {
const response = await fetch(LATEST_RELEASE_URL)
if (!response.ok) {
throw new Error(
`Failed to resolve latest Supabase CLI release: ${response.statusText}`
)
}
const release = (await response.json()) as { tag_name?: unknown }
if (typeof release.tag_name !== 'string') {
throw new Error(
'Failed to resolve latest Supabase CLI release: missing tag name'
)
}
return normalizeVersion(release.tag_name)
}
const getArchiveFormat = (version: string, platform: string): ArchiveFormat => {
if (platform === 'win32' && gte(version, VERSIONED_ARCHIVE_VERSION)) {
return 'zip'
}
return 'tar'
}
const getArchiveFilename = (
version: string,
platform: string,
arch: string
): string => {
const archivePlatform = mapOS(platform)
const archiveArch = mapArch(arch)
if (lt(version, '1.28.0')) {
return `supabase_${version}_${archivePlatform}_${archiveArch}.tar.gz`
}
if (gte(version, VERSIONED_ARCHIVE_VERSION)) {
const extension = platform === 'win32' ? 'zip' : 'tar.gz'
return `supabase_${version}_${archivePlatform}_${archiveArch}.${extension}`
}
return `supabase_${archivePlatform}_${archiveArch}.tar.gz`
}
export const getDownloadArchive = async (
version: string,
platform = os.platform(),
arch = os.arch()
): Promise<DownloadArchive> => {
const resolvedVersion =
version.toLowerCase() === 'latest'
? await resolveLatestVersion()
: normalizeVersion(version)
const filename = getArchiveFilename(resolvedVersion, platform, arch)
return {
url: `https://github.com/supabase/cli/releases/download/v${resolvedVersion}/${filename}`,
format: getArchiveFormat(resolvedVersion, platform)
}
}
export const getDownloadUrl = async (version: string): Promise<string> => {
const archive = await getDownloadArchive(version)
return archive.url
}
export const determineInstalledVersion = async (): Promise<string> => {
const { stdout } = await doExec('supabase --version')
const version = stdout.trim()
if (!version) {
throw new Error('Could not determine installed Supabase CLI version')
}
return version
}

23
tsconfig.base.json Normal file
View File

@@ -0,0 +1,23 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"declaration": false,
"declarationMap": false,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
"lib": ["ES2022"],
"module": "NodeNext",
"moduleResolution": "NodeNext",
"newLine": "lf",
"noImplicitAny": true,
"noUnusedLocals": true,
"noUnusedParameters": false,
"pretty": true,
"resolveJsonModule": true,
"strict": true,
"strictNullChecks": true,
"target": "ES2022"
}
}

17
tsconfig.eslint.json Normal file
View File

@@ -0,0 +1,17 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "./tsconfig.base.json",
"compilerOptions": {
"allowJs": true,
"noEmit": true
},
"exclude": ["dist", "node_modules"],
"include": [
"__fixtures__",
"__tests__",
"src",
"eslint.config.mjs",
"jest.config.js",
"rollup.config.ts"
]
}

View File

@@ -1,4 +1,11 @@
{ {
"extends": "@tsconfig/bun/tsconfig.json", "$schema": "https://json.schemastore.org/tsconfig",
"extends": "./tsconfig.base.json",
"compilerOptions": {
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "./dist"
},
"exclude": ["__fixtures__", "__tests__", "coverage", "dist", "node_modules"],
"include": ["src"] "include": ["src"]
} }