diff --git a/example.php b/example.php index accb310429..5807d89699 100644 --- a/example.php +++ b/example.php @@ -175,20 +175,6 @@ function configureSDK($sdk, $overrides = []) { / _ \ |_) | |_) \ V V /| | | | || __/ / /___/ /___/\/ /_ \_/ \_/ .__/| .__/ \_/\_/ |_| |_|\__\___| \____/\____/\____/ |_| |_| "); - // Generated formulas start with placeholder checksums. The generated CLI - // publish workflow rewrites them after the native release binaries exist. - foreach ([ - 'APPWRITE_CLI_HOMEBREW_MAC_ARM64_SHA256' => 'homebrewMacArm64Sha256', - 'APPWRITE_CLI_HOMEBREW_MAC_X64_SHA256' => 'homebrewMacX64Sha256', - 'APPWRITE_CLI_HOMEBREW_LINUX_ARM64_SHA256' => 'homebrewLinuxArm64Sha256', - 'APPWRITE_CLI_HOMEBREW_LINUX_X64_SHA256' => 'homebrewLinuxX64Sha256', - ] as $envKey => $paramKey) { - $sha256 = getenv($envKey); - - if ($sha256 !== false && $sha256 !== '') { - $language->setHomebrewSha256($paramKey, $sha256); - } - } $sdk = new SDK($language, new Swagger2($spec)); $sdk->setTest(false); diff --git a/src/SDK/Language/CLI.php b/src/SDK/Language/CLI.php index 545430d31b..a0e145e6ee 100644 --- a/src/SDK/Language/CLI.php +++ b/src/SDK/Language/CLI.php @@ -70,10 +70,8 @@ class CLI extends Node 'executableName' => 'executable', 'logo' => '', 'logoUnescaped' => '', - 'homebrewMacArm64Sha256' => '0000000000000000000000000000000000000000000000000000000000000000', - 'homebrewMacX64Sha256' => '0000000000000000000000000000000000000000000000000000000000000000', - 'homebrewLinuxArm64Sha256' => '0000000000000000000000000000000000000000000000000000000000000000', - 'homebrewLinuxX64Sha256' => '0000000000000000000000000000000000000000000000000000000000000000', + 'homebrewTapOwner' => 'appwrite', + 'homebrewTapName' => 'appwrite', ]; /** @@ -155,16 +153,16 @@ public function setLogoUnescaped(string $logo): self } /** - * Override a generated Homebrew formula checksum placeholder when a release - * build already knows the target binary SHA256. + * Configure the Homebrew tap (`/homebrew-`) hosting the CLI formula. * - * @param string $key - * @param string $sha256 + * @param string $owner Tap owner (e.g. "appwrite") + * @param string $name Tap short name without the `homebrew-` prefix * @return $this */ - public function setHomebrewSha256(string $key, string $sha256): self + public function setHomebrewTap(string $owner, string $name): self { - $this->setParam($key, $sha256); + $this->setParam('homebrewTapOwner', $owner); + $this->setParam('homebrewTapName', $name); return $this; } @@ -301,13 +299,6 @@ public function getFiles(): array 'template' => 'cli/docs/example.md.twig', ], - // Distribution - Formula (Homebrew) - [ - 'scope' => 'method', - 'destination' => 'Formula/{{ language.params.executableName }}.rb', - 'template' => 'cli/Formula/formula.rb.twig', - ], - // Distribution - Scoop (Windows) [ 'scope' => 'default', diff --git a/templates/cli/.github/workflows/publish.yml b/templates/cli/.github/workflows/publish.yml index 0c2fb8491c..0a91f735a1 100644 --- a/templates/cli/.github/workflows/publish.yml +++ b/templates/cli/.github/workflows/publish.yml @@ -17,6 +17,7 @@ jobs: # them on launch (oven-sh/bun#29120). Unpin once a Bun release includes the # upstream fix (oven-sh/bun#29122). CLI_BUN_VERSION: '1.3.11' + HOMEBREW_TAP_REPO: appwrite/homebrew-appwrite steps: - uses: actions/checkout@v4 with: @@ -75,33 +76,34 @@ jobs: GHR_REPLACE: false GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} - - name: Update Homebrew formula checksums + - name: Check out Homebrew tap + uses: actions/checkout@v4 + with: + repository: ${{ env.HOMEBREW_TAP_REPO }} + token: ${{ secrets.HOMEBREW_TAP_GH_TOKEN }} + path: homebrew-tap + fetch-depth: 0 + + - name: Update Homebrew formula in tap + id: tap + working-directory: homebrew-tap env: RELEASE_TAG: ${{ github.event.release.tag_name }} - TARGET_COMMITISH: ${{ github.event.release.target_commitish }} run: | set -euo pipefail - TARGET_BRANCH="$TARGET_COMMITISH" - if ! git ls-remote --exit-code --heads origin "$TARGET_BRANCH" >/dev/null 2>&1; then - TARGET_BRANCH="master" - fi - - git fetch origin "$TARGET_BRANCH" - git switch -C "$TARGET_BRANCH" "origin/$TARGET_BRANCH" - FORMULA_PATH="$(find Formula -maxdepth 1 -name '*.rb' | head -n 1)" if [ -z "$FORMULA_PATH" ]; then - echo "Formula file not found" + echo "No formula found in Homebrew tap" exit 1 fi - EXECUTABLE_NAME="$(basename "$FORMULA_PATH" .rb)" - export FORMULA_PATH RELEASE_TAG EXECUTABLE_NAME - export MAC_ARM64_SHA256="$(sha256sum "build/${EXECUTABLE_NAME}-cli-darwin-arm64" | awk '{print $1}')" - export MAC_X64_SHA256="$(sha256sum "build/${EXECUTABLE_NAME}-cli-darwin-x64" | awk '{print $1}')" - export LINUX_ARM64_SHA256="$(sha256sum "build/${EXECUTABLE_NAME}-cli-linux-arm64" | awk '{print $1}')" - export LINUX_X64_SHA256="$(sha256sum "build/${EXECUTABLE_NAME}-cli-linux-x64" | awk '{print $1}')" + + export FORMULA_PATH EXECUTABLE_NAME + export MAC_ARM64_SHA256="$(sha256sum "../build/${EXECUTABLE_NAME}-cli-darwin-arm64" | awk '{print $1}')" + export MAC_X64_SHA256="$(sha256sum "../build/${EXECUTABLE_NAME}-cli-darwin-x64" | awk '{print $1}')" + export LINUX_ARM64_SHA256="$(sha256sum "../build/${EXECUTABLE_NAME}-cli-linux-arm64" | awk '{print $1}')" + export LINUX_X64_SHA256="$(sha256sum "../build/${EXECUTABLE_NAME}-cli-linux-x64" | awk '{print $1}')" ruby <<'RUBY' formula_path = ENV.fetch("FORMULA_PATH") @@ -132,13 +134,49 @@ jobs: ruby -c "$FORMULA_PATH" + { + echo "executable=${EXECUTABLE_NAME}" + echo "formula_path=${FORMULA_PATH}" + } >> "$GITHUB_OUTPUT" + + - name: Open pull request on Homebrew tap + working-directory: homebrew-tap + env: + RELEASE_TAG: ${{ github.event.release.tag_name }} + EXECUTABLE_NAME: ${{ steps.tap.outputs.executable }} + FORMULA_PATH: ${{ steps.tap.outputs.formula_path }} + SOURCE_REPO: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + GH_TOKEN: ${{ secrets.HOMEBREW_TAP_GH_TOKEN }} + run: | + set -euo pipefail + if git diff --quiet -- "$FORMULA_PATH"; then - echo "Homebrew formula already up to date" + echo "Homebrew formula already up to date for ${RELEASE_TAG}" exit 0 fi - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + BASE_BRANCH="$(gh repo view --json defaultBranchRef --jq '.defaultBranchRef.name')" + BRANCH="release/${EXECUTABLE_NAME}-${RELEASE_TAG}" + + git config user.name "appwrite-bot" + git config user.email "bot@appwrite.io" + + git checkout -B "$BRANCH" git add "$FORMULA_PATH" - git commit -m "chore: update Homebrew formula for ${RELEASE_TAG}" - git push origin "$TARGET_BRANCH" + git commit -m "${EXECUTABLE_NAME} ${RELEASE_TAG}" + git push -f -u origin "$BRANCH" + + PR_TITLE="${EXECUTABLE_NAME} ${RELEASE_TAG}" + PR_BODY=$(printf 'Automated formula update for the `%s` CLI release [`%s`](%s/%s/releases/tag/%s).\n\nOpened automatically by the `%s` publish workflow after release binaries were uploaded.' "$EXECUTABLE_NAME" "$RELEASE_TAG" "$SERVER_URL" "$SOURCE_REPO" "$RELEASE_TAG" "${SOURCE_REPO#*/}") + + EXISTING_PR="$(gh pr list --head "$BRANCH" --state open --json number --jq '.[0].number' || true)" + if [ -n "$EXISTING_PR" ]; then + gh pr edit "$EXISTING_PR" --title "$PR_TITLE" --body "$PR_BODY" + else + gh pr create \ + --title "$PR_TITLE" \ + --body "$PR_BODY" \ + --base "$BASE_BRANCH" \ + --head "$BRANCH" + fi diff --git a/templates/cli/Formula/formula.rb.twig b/templates/cli/Formula/formula.rb.twig deleted file mode 100644 index d1a1c6f5ae..0000000000 --- a/templates/cli/Formula/formula.rb.twig +++ /dev/null @@ -1,68 +0,0 @@ -class {{ spec.title| caseUcfirst }} < Formula - desc "Command-line tool for interacting with the {{ spec.title| caseUcfirst }} API" - homepage "{{ sdk.url }}" - license "{{ sdk.license }}" - version "{{ sdk.version }}" - - def self.binary_arch - Hardware::CPU.arm? ? "arm64" : "x64" - end - - def self.binary_os - return "darwin" if OS.mac? - return "linux" if OS.linux? - - raise "Homebrew formula is only supported on macOS and Linux" - end - - def self.binary_name - "{{ language.params.executableName }}-cli-#{binary_os}-#{binary_arch}" - end - - def self.build_target - return "mac-#{binary_arch}" if OS.mac? - return "linux-#{binary_arch}" if OS.linux? - - raise "Homebrew formula is only supported on macOS and Linux" - end - - # Release automation injects per-target SHA256 values when publishing binaries. - on_macos do - if Hardware::CPU.arm? - url "https://github.com/{{ sdk.gitUserName }}/{{ sdk.gitRepoName | caseDash }}/releases/download/#{version}/{{ language.params.executableName }}-cli-darwin-arm64" - sha256 "{{ language.params.homebrewMacArm64Sha256 }}" - else - url "https://github.com/{{ sdk.gitUserName }}/{{ sdk.gitRepoName | caseDash }}/releases/download/#{version}/{{ language.params.executableName }}-cli-darwin-x64" - sha256 "{{ language.params.homebrewMacX64Sha256 }}" - end - end - - on_linux do - if Hardware::CPU.arm? - url "https://github.com/{{ sdk.gitUserName }}/{{ sdk.gitRepoName | caseDash }}/releases/download/#{version}/{{ language.params.executableName }}-cli-linux-arm64" - sha256 "{{ language.params.homebrewLinuxArm64Sha256 }}" - else - url "https://github.com/{{ sdk.gitUserName }}/{{ sdk.gitRepoName | caseDash }}/releases/download/#{version}/{{ language.params.executableName }}-cli-linux-x64" - sha256 "{{ language.params.homebrewLinuxX64Sha256 }}" - end - end - - head "https://github.com/{{ sdk.gitUserName }}/{{ sdk.gitRepoName | caseDash }}.git", branch: "master" do - depends_on "bun" => :build - end - - def install - if build.head? - system "bun", "install", "--frozen-lockfile" - system "bun", "run", self.class.build_target - bin.install "build/#{self.class.binary_name}" => "{{ language.params.executableName|caseLower }}" - return - end - - bin.install self.class.binary_name => "{{ language.params.executableName|caseLower }}" - end - - test do - assert_match "Usage:", shell_output("#{bin}/{{ language.params.executableName|caseLower }} --help") - end -end diff --git a/templates/cli/README.md.twig b/templates/cli/README.md.twig index ccdd4fff42..4f7504c8f5 100644 --- a/templates/cli/README.md.twig +++ b/templates/cli/README.md.twig @@ -50,12 +50,12 @@ If you do not have `npm` installed, you can always install the prebuilt binaries $ wget -q {{ sdk.url }}/cli/install.sh -O - | /bin/bash ``` -### MacOS via [Homebrew](https://brew.sh) +### MacOS / Linux via [Homebrew](https://brew.sh) ```bash -$ brew install {{ language.params.executableName }} +$ brew install {{ language.params.homebrewTapOwner }}/{{ language.params.homebrewTapName }}/{{ language.params.executableName }} ``` -Homebrew installs the native binary for your platform. +Homebrew pulls the formula from the [`{{ language.params.homebrewTapOwner }}/homebrew-{{ language.params.homebrewTapName }}`](https://github.com/{{ language.params.homebrewTapOwner }}/homebrew-{{ language.params.homebrewTapName }}) tap and downloads the native binary for your platform. ### Windows Via Powershell diff --git a/templates/cli/lib/commands/update.ts b/templates/cli/lib/commands/update.ts index dc77b319d3..a0155609db 100644 --- a/templates/cli/lib/commands/update.ts +++ b/templates/cli/lib/commands/update.ts @@ -15,6 +15,7 @@ import { import { EXECUTABLE_NAME, GITHUB_RELEASES_URL, + HOMEBREW_FORMULA, NPM_PACKAGE_NAME, SDK_TITLE, } from "../constants.js"; @@ -164,10 +165,10 @@ const updateViaNpm = async (): Promise => { */ const updateViaHomebrew = async (): Promise => { try { - await execCommand("brew", ["upgrade", "appwrite"]); + await execCommand("brew", ["upgrade", HOMEBREW_FORMULA]); console.log(""); success("Updated to latest version via Homebrew!"); - hint("Run 'appwrite --version' to verify the new version."); + hint(`Run '${EXECUTABLE_NAME} --version' to verify the new version.`); } catch (e: unknown) { const message = getErrorMessage(e); @@ -177,11 +178,13 @@ const updateViaHomebrew = async (): Promise => { ) { console.log(""); success("Latest version is already installed via Homebrew!"); - hint("The CLI is up to date. Run 'appwrite --version' to verify."); + hint( + `The CLI is up to date. Run '${EXECUTABLE_NAME} --version' to verify.`, + ); } else { console.log(""); error(`Failed to update via Homebrew: ${message}`); - hint("Try running: brew upgrade appwrite"); + hint(`Try running: brew upgrade ${HOMEBREW_FORMULA}`); } } }; @@ -258,7 +261,7 @@ const showManualInstructions = (latestVersion: string): void => { console.log(""); log(`${chalk.bold("Option 2: Homebrew")}`); - console.log(` brew upgrade appwrite`); + console.log(` brew upgrade ${HOMEBREW_FORMULA}`); console.log(""); if (process.platform !== "win32") { diff --git a/templates/cli/lib/constants.ts.twig b/templates/cli/lib/constants.ts.twig index 6c4e8f3859..a047f08f10 100644 --- a/templates/cli/lib/constants.ts.twig +++ b/templates/cli/lib/constants.ts.twig @@ -12,6 +12,10 @@ export const EXECUTABLE_NAME = '{{ language.params.executableName }}'; // 1 day export const UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; +// Homebrew — fully-qualified `//` reference +export const HOMEBREW_TAP = '{{ language.params.homebrewTapOwner }}/{{ language.params.homebrewTapName }}'; +export const HOMEBREW_FORMULA = `${HOMEBREW_TAP}/{{ language.params.executableName|caseLower }}`; + // NPM export const NPM_PACKAGE_NAME = '{{ language.params.npmPackage|caseDash }}'; export const NPM_REGISTRY_URL = `https://registry.npmjs.org/${NPM_PACKAGE_NAME}/latest`; diff --git a/templates/cli/lib/utils.ts b/templates/cli/lib/utils.ts index 519187ac80..ffaedd28f1 100644 --- a/templates/cli/lib/utils.ts +++ b/templates/cli/lib/utils.ts @@ -12,6 +12,7 @@ import { NPM_PACKAGE_NAME, DEFAULT_ENDPOINT, EXECUTABLE_NAME, + HOMEBREW_FORMULA, UPDATE_CHECK_INTERVAL_MS, } from "./constants.js"; @@ -102,7 +103,6 @@ type HomebrewInfoResponse = { }; const WINDOWS_EXECUTABLE_NAME = `${EXECUTABLE_NAME.toLowerCase()}.exe`; -const HOMEBREW_FORMULA_NAME = EXECUTABLE_NAME.toLowerCase(); const getExecutablePaths = (): { execPath: string; @@ -227,7 +227,7 @@ const getHomebrewLatestVersion = async ( try { const output = childProcess.execFileSync( "brew", - ["info", "--json=v2", HOMEBREW_FORMULA_NAME], + ["info", "--json=v2", HOMEBREW_FORMULA], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"],