diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..1cbfc76 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,37 @@ +name: Release + +on: + push: + tags: + - 'v*' + +permissions: + contents: write + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.3' + extensions: mbstring, xml, sodium, zlib + coverage: none + ini-values: phar.readonly=0 + + - name: Install dependencies + run: composer install --no-dev --no-interaction --prefer-dist + + - name: Build PHAR + run: php cf app:build cf --build-version="${GITHUB_REF_NAME#v}" + + - name: Upload release asset + uses: softprops/action-gh-release@v2 + with: + files: builds/cf + fail_on_unmatched_files: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 353fb79..a34968d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ /.vagrant .phpunit.result.cache .env +/builds +*.phar diff --git a/README.md b/README.md index 904c125..fcdd8d0 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,27 @@ Lightweight Cloudflare management CLI built with Laravel Zero and Saloon. ## Installation +### Binary (recommended) + +Download `cf` from [Releases](https://github.com/conduit-ui/cloudflare/releases), then: + +```bash +chmod +x cf +sudo mv cf /usr/local/bin/cf +``` + +Requires PHP 8.3+ (zlib). Export `CLOUDFLARE_API_TOKEN` and `CLOUDFLARE_ACCOUNT_ID` before running commands. + +### Composer + +```bash +composer require conduit-ui/cloudflare +# or globally: +composer global require conduit-ui/cloudflare +``` + +### From source + ```bash git clone https://github.com/conduit-ui/cloudflare.git cd cloudflare @@ -11,6 +32,8 @@ composer install cp .env.example .env ``` +Build a local PHAR after `composer install` with `composer build` (or `php cf app:build cf`). Artifact: `builds/cf`. + ## Configuration Edit `.env`: diff --git a/composer.json b/composer.json index 1769170..69c29ae 100644 --- a/composer.json +++ b/composer.json @@ -59,5 +59,9 @@ "prefer-stable": true, "bin": [ "cf" - ] + ], + "scripts": { + "build": "php -d phar.readonly=0 cf app:build cf", + "test": "pest" + } } diff --git a/specs/phar-release/spec.md b/specs/phar-release/spec.md new file mode 100644 index 0000000..25cd83b --- /dev/null +++ b/specs/phar-release/spec.md @@ -0,0 +1,33 @@ +# SPEC: one `cf` PHAR on tag `v*` + +Status: DRAFT + +**Story:** README says download `cf` from GitHub Releases. Tag `v0.1.0` (2026-08-22) has zero assets. Pushing a `v*` tag does not build or upload a PHAR. + +## Locked + +- One binary named `cf`. Laravel Zero `app:build cf` writes `builds/cf`. Release uploads that file. Not `cf.phar`, not `cloudflare`. +- PHP 8.3 everywhere the pipeline runs: `composer.json` `require.php` `^8.3`, `config.platform.php` `8.3.33`, `.github/workflows/ci.yml`, `.github/workflows/release.yml`. +- `release.yml` triggers on push tags `v*` only. `shivammathur/setup-php@v2` with `php-version: '8.3'` and `ini-values: phar.readonly=0`. Build: `php cf app:build cf --build-version="${GITHUB_REF_NAME#v}"`. Upload: `softprops/action-gh-release@v2` `files: builds/cf`. +- Install is download `cf` from Releases, `chmod +x`, move onto `PATH`. Composer require and source clone stay. Do not add curl|bash, Homebrew, `install.sh`, or a second asset. +- You own (research): `specs/`, `~/.grok/tmp/9-notes.md`. Build owns `release.yml` `composer.json` `box.json` `.gitignore` README install section. Test owns `tests/`. +- Do not: merge, deploy, PHP 8.4, a second installer, touch `app/Commands` for this PR. + +## Done when + +- [ ] `rg "php-version: '8.3'" .github/workflows/release.yml` matches; `rg "php-version: '8.4'" .github/workflows/*.yml` is empty +- [ ] `rg -n "tags:|'v\\*'|files: builds/cf" .github/workflows/release.yml` shows tag `v*` and `files: builds/cf` +- [ ] `php -r 'echo json_decode(file_get_contents("composer.json"))->scripts->build, "\n";'` contains `app:build` +- [ ] `vendor/bin/pest --compact tests/Feature/PharReleaseTest.php` passes (8.3, not 8.4; composer build script present) + +## Out of scope + +- curl|bash installer, brew formula, `install.sh`, renaming the asset +- PHP 8.4 (Lexi caught `release.yml` on 8.4 at `1113c38`; `9fad191` / local `7e3b731` already moved it to 8.3 — keep it) +- Backfilling a PHAR onto `v0.1.0` +- Making GitHub Actions the house gate (quality-runners is) +- `app/Commands`, merge, deploy + +## Open decisions + +- None. Local `composer build` omits `--build-version` so Laravel Zero may prompt; tagged release already passes it. Do not add a second job to paper over that. diff --git a/tests/Feature/PharReleaseTest.php b/tests/Feature/PharReleaseTest.php new file mode 100644 index 0000000..267143a --- /dev/null +++ b/tests/Feature/PharReleaseTest.php @@ -0,0 +1,40 @@ +toBeFile(); + + $yml = file_get_contents($path); + + expect($yml)->toContain("php-version: '8.3'") + ->and($yml)->not->toContain("php-version: '8.4'"); +}); + +it('releases the PHAR on v* tags as builds/cf', function () { + $yml = file_get_contents(base_path('.github/workflows/release.yml')); + + expect($yml)->toContain("tags:\n - 'v*'") + ->and($yml)->toContain('files: builds/cf'); +}); + +it('exposes a composer build script for the PHAR', function () { + $composer = json_decode( + (string) file_get_contents(base_path('composer.json')), + true, + flags: JSON_THROW_ON_ERROR, + ); + + expect($composer['scripts']['build'] ?? null) + ->toBeString() + ->and($composer['scripts']['build'])->toContain('app:build'); +}); + +it('runs CI on PHP 8.3', function () { + $yml = file_get_contents(base_path('.github/workflows/ci.yml')); + + expect($yml)->toContain("php-version: '8.3'") + ->and($yml)->not->toContain("php-version: '8.4'"); +});