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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/release-shared.yml
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,7 @@ jobs:

- name: Configure git for tap push
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
gh auth setup-git
Expand All @@ -384,6 +385,7 @@ jobs:
- name: Update Homebrew formula
run: pnpm exec bun apps/cli/scripts/update-homebrew.ts --version "${VERSION}" --name "${BREW_NAME}"
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}

publish-scoop:
Expand Down Expand Up @@ -420,6 +422,7 @@ jobs:

- name: Configure git for bucket push
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
gh auth setup-git
Expand All @@ -429,6 +432,7 @@ jobs:
- name: Update Scoop manifest
run: pnpm exec bun apps/cli/scripts/update-scoop.ts --version "${VERSION}" --name "${SCOOP_NAME}"
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}

# Post-publish smoke test for the `supabase/setup-cli` GitHub Action against
Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ on:
type: boolean
default: true

# The release pipeline mutates external package registries, git tags, GitHub
# Releases, Homebrew, and Scoop. Keep one release per ref active at a time so
# duplicate push events cannot race the same computed version.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false

permissions:
contents: read

Expand Down
4 changes: 2 additions & 2 deletions apps/cli/docs/release-process.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ Create three empty repos on your own GitHub account:
| Homebrew tap | Must be named `homebrew-<anything>` (so `brew tap <owner>/<anything>` works) | `[avallete/homebrew-supabase-shim-poc](https://github.com/avallete/homebrew-supabase-shim-poc)` |
| Scoop bucket | None | `[avallete/scoop-bucket](https://github.com/avallete/scoop-bucket)` |

All three can be empty git trees — the updater scripts `gh repo clone` them into a tmpdir, write their generated file, commit, and push.
All three can be empty git trees. The downstream updater scripts clone the Homebrew tap / Scoop bucket into a tmpdir with git, write their generated file, commit, and push.

Authenticate the GitHub CLI once with write access to all three:

Expand Down Expand Up @@ -285,7 +285,7 @@ The matrix does not yet include `windows-11-arm` (gate 6) or an Alpine musl runn

### Post-publish: Homebrew + Scoop

Both updaters run automatically from `release-shared.yml`'s `publish-homebrew` and `publish-scoop` jobs after the GitHub Release is finalised. Each job mints a GitHub App token scoped to `homebrew-tap` / `scoop-bucket` (via `actions/create-github-app-token` with the `APP_ID` + `GH_APP_PRIVATE_KEY` secrets), runs `gh auth setup-git` + sets a `github-actions[bot]` git identity, then invokes `apps/cli/scripts/update-homebrew.ts` / `update-scoop.ts` with `--name <brew_name>` / `--name <scoop_name>`:
Both updaters run automatically from `release-shared.yml`'s `publish-homebrew` and `publish-scoop` jobs after the GitHub Release is finalised. Each job mints a GitHub App token scoped to `homebrew-tap` / `scoop-bucket` (via `actions/create-github-app-token` with the `GH_APP_CLIENT_ID` + `GH_APP_PRIVATE_KEY` secrets), runs `gh auth setup-git` + sets a `github-actions[bot]` git identity, then invokes `apps/cli/scripts/update-homebrew.ts` / `update-scoop.ts` with `--name <brew_name>` / `--name <scoop_name>`:

- `stable` → `--name supabase` (the default formula / manifest, what `brew install supabase` resolves)
- `beta` → `--name supabase-beta` (a separate formula / manifest for the prerelease channel)
Expand Down
25 changes: 19 additions & 6 deletions apps/cli/scripts/update-homebrew.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,21 +120,34 @@ if (local || dryRun) {
process.exit(0);
}

async function hasStagedChanges(repoDir: string, repoPath: string): Promise<boolean> {
const diff =
await $`git -C ${repoDir} diff --cached --quiet --exit-code -- ${repoPath}`.nothrow();
if (diff.exitCode === 0) return false;
if (diff.exitCode === 1) return true;
throw new Error(`Failed to inspect staged changes for ${repoPath}`);
}

// Clone tap repo, update formula, commit, push
const tmpDir = await mkdtemp(path.join(tmpdir(), "homebrew-tap-"));
try {
await $`gh repo clone ${tap} ${tmpDir}`;
const tapUrl = `https://github.com/${tap}.git`;
await $`git clone ${tapUrl} ${tmpDir}`;

const formulaDir = path.join(tmpDir, "Formula");
await $`mkdir -p ${formulaDir}`;
const tapFormulaPath = path.join(formulaDir, formulaFileName);
const tapFormulaRepoPath = `Formula/${formulaFileName}`;
await writeFile(tapFormulaPath, formula);

await $`git -C ${tmpDir} add Formula/${formulaFileName}`;
await $`git -C ${tmpDir} commit -m ${name + " " + version}`;
await $`git -C ${tmpDir} push`;

console.log(`Pushed formula update to ${tap}`);
await $`git -C ${tmpDir} add ${tapFormulaRepoPath}`;
if (await hasStagedChanges(tmpDir, tapFormulaRepoPath)) {
await $`git -C ${tmpDir} commit -m ${name + " " + version}`;
await $`git -C ${tmpDir} push`;
console.log(`Pushed formula update to ${tap}`);
} else {
console.log(`Formula ${formulaFileName} is already up to date in ${tap}`);
}
} finally {
await rm(tmpDir, { recursive: true });
}
22 changes: 17 additions & 5 deletions apps/cli/scripts/update-scoop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,19 +113,31 @@ if (local || dryRun) {
process.exit(0);
}

async function hasStagedChanges(repoDir: string, repoPath: string): Promise<boolean> {
const diff =
await $`git -C ${repoDir} diff --cached --quiet --exit-code -- ${repoPath}`.nothrow();
if (diff.exitCode === 0) return false;
if (diff.exitCode === 1) return true;
throw new Error(`Failed to inspect staged changes for ${repoPath}`);
}

// Clone bucket repo, update manifest, commit, push
const tmpDir = await mkdtemp(path.join(tmpdir(), "scoop-bucket-"));
try {
await $`gh repo clone ${bucket} ${tmpDir}`;
const bucketUrl = `https://github.com/${bucket}.git`;
await $`git clone ${bucketUrl} ${tmpDir}`;

const bucketManifestPath = path.join(tmpDir, manifestFileName);
await writeFile(bucketManifestPath, manifestJson);

await $`git -C ${tmpDir} add ${manifestFileName}`;
await $`git -C ${tmpDir} commit -m ${name + " " + version}`;
await $`git -C ${tmpDir} push`;

console.log(`Pushed manifest update to ${bucket}`);
if (await hasStagedChanges(tmpDir, manifestFileName)) {
await $`git -C ${tmpDir} commit -m ${name + " " + version}`;
await $`git -C ${tmpDir} push`;
console.log(`Pushed manifest update to ${bucket}`);
} else {
console.log(`Manifest ${manifestFileName} is already up to date in ${bucket}`);
}
} finally {
await rm(tmpDir, { recursive: true });
}
27 changes: 21 additions & 6 deletions apps/cli/tests/helpers/npm-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,16 @@ async function inspectVerdaccioTarball(storageDir: string, pkg: string): Promise
for (const line of binLines) console.log(line);
}

async function hasVerdaccioTarball(storageDir: string, pkg: string): Promise<boolean> {
const pkgStorage = path.join(storageDir, "@supabase", pkg);
try {
const files = await readdir(pkgStorage);
return files.some((f) => f.endsWith(".tgz"));
} catch {
return false;
}
}

export function describeError(e: unknown): string {
if (e instanceof Error) {
const parts = [e.stack ?? `${e.name}: ${e.message}`];
Expand Down Expand Up @@ -264,18 +274,23 @@ listen: 0.0.0.0:${PORT}
await using registry = await startVerdaccio(configPath, PORT);
console.log(`Registry ready at ${registry.url}\n`);

// Publish platform packages in parallel
const platformPackages = ALL_PACKAGES.filter((p) => p !== "cli");
console.log("Publishing platform packages...");
await Promise.all(
platformPackages.map(async (pkg) => {
const pkgDir = path.join(root, "packages", pkg);
for (const pkg of platformPackages) {
const pkgDir = path.join(root, "packages", pkg);
try {
await $`pnpm publish --registry ${registry.url} --tag ${tag} --no-git-checks`
.cwd(pkgDir)
.env(publishEnv);
console.log(` @supabase/${pkg}`);
}),
);
} catch (e) {
if (await hasVerdaccioTarball(storageDir, pkg)) {
console.log(` @supabase/${pkg} (already present in local registry)`);
continue;
}
throw e;
}
}

// Inspect what Verdaccio actually received — directly answers whether
// `publishConfig.executableFiles` is being applied to the published tarball.
Expand Down
Loading