diff --git a/.github/workflows/build-binaries.yml b/.github/workflows/build-binaries.yml index 15218e0..506b5a5 100644 --- a/.github/workflows/build-binaries.yml +++ b/.github/workflows/build-binaries.yml @@ -2,7 +2,12 @@ name: Build binaries # Freezes a standalone `becwright` binary per platform with PyInstaller. # These artifacts are what the npm packages ship, so users without Python can -# install becwright via npm/pnpm. Release publishing is wired in publish.yml. +# install becwright via npm/pnpm. Release publishing is wired in release.yml. +# +# macOS is built once as a universal2 binary (Intel + Apple Silicon) on the +# Apple Silicon runner, then served for both the darwin-arm64 and darwin-x64 npm +# packages — GitHub's Intel macOS runners are unreliable, so we don't depend on +# them. on: workflow_dispatch: workflow_call: # invoked by release.yml on a published release @@ -17,17 +22,46 @@ jobs: include: - { os: ubuntu-latest, target: linux-x64, ext: "" } - { os: ubuntu-24.04-arm, target: linux-arm64, ext: "" } - - { os: macos-14, target: darwin-arm64, ext: "" } + - { os: macos-14, target: darwin-universal2, ext: "" } - { os: windows-latest, target: win32-x64, ext: ".exe" } steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + + # --- Standard build (Linux / Windows): host-arch binary --- + - name: Set up Python + if: matrix.target != 'darwin-universal2' + uses: actions/setup-python@v5 with: python-version: "3.12" - name: Install + if: matrix.target != 'darwin-universal2' run: pip install ".[build]" - name: Build + if: matrix.target != 'darwin-universal2' run: pyinstaller --clean --noconfirm packaging/becwright.spec + + # --- macOS universal2 build (Intel + Apple Silicon in one binary) --- + # setup-python is arch-specific, so use the python.org universal2 framework + # build. pyyaml ships an arch-specific C extension; drop it so the bundle is + # pure-Python and the result is cleanly universal2 (becwright only needs + # yaml.safe_load/dump, which the pure-Python loader provides). + - name: Build universal2 (macOS) + if: matrix.target == 'darwin-universal2' + env: + BECWRIGHT_TARGET_ARCH: universal2 + PYVER: "3.12.8" + run: | + set -euo pipefail + curl -sSLo /tmp/python.pkg "https://www.python.org/ftp/python/${PYVER}/python-${PYVER}-macos11.pkg" + sudo installer -pkg /tmp/python.pkg -target / + PY=/Library/Frameworks/Python.framework/Versions/3.12/bin/python3 + "$PY" -m pip install --upgrade pip + "$PY" -m pip install ".[build]" + SITE="$("$PY" -c 'import sysconfig; print(sysconfig.get_paths()["purelib"])')" + find "$SITE" -name "_yaml*.so" -delete || true + "$PY" -m PyInstaller --clean --noconfirm packaging/becwright.spec + echo "--- lipo ---"; lipo -info dist/becwright + - name: Smoke test shell: bash run: | @@ -39,6 +73,16 @@ jobs: echo "forbid should have exited non-zero on a match"; exit 1 fi echo "frozen 'run' dispatch works" + + - name: Smoke test Intel slice via Rosetta (macOS universal2) + if: matrix.target == 'darwin-universal2' + run: | + set -euo pipefail + softwareupdate --install-rosetta --agree-to-license || true + arch -x86_64 dist/becwright --version + arch -x86_64 dist/becwright list >/dev/null + echo "Intel slice runs under Rosetta" + - uses: actions/upload-artifact@v4 with: name: becwright-${{ matrix.target }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6f75fa6..93a4275 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,7 +28,9 @@ jobs: run: | node npm/stage.mjs linux-x64 artifacts/becwright-linux-x64/becwright node npm/stage.mjs linux-arm64 artifacts/becwright-linux-arm64/becwright - node npm/stage.mjs darwin-arm64 artifacts/becwright-darwin-arm64/becwright + # One universal2 macOS binary serves both Apple Silicon and Intel. + node npm/stage.mjs darwin-arm64 artifacts/becwright-darwin-universal2/becwright + node npm/stage.mjs darwin-x64 artifacts/becwright-darwin-universal2/becwright node npm/stage.mjs win32-x64 artifacts/becwright-win32-x64/becwright.exe - name: Set version from tag run: node npm/set-version.mjs "${GITHUB_REF_NAME#v}" diff --git a/README.es.md b/README.es.md index 154f6fd..257e793 100644 --- a/README.es.md +++ b/README.es.md @@ -65,7 +65,7 @@ becwright init # detecta tu lenguaje, escribe .bec/rules.ya Instalado como devDependency, el hook de pre-commit resuelve el binario local desde `node_modules/.bin`, así funciona sin instalación global. Los paquetes npm -cubren `linux-x64`, `linux-arm64`, `darwin-arm64` y `win32-x64`; en +cubren `linux-x64`, `linux-arm64`, `darwin-x64`, `darwin-arm64` y `win32-x64`; en cualquier otra plataforma usá `pipx install becwright`. Comandos disponibles: diff --git a/README.md b/README.md index d624f5b..434a09e 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ becwright init # detects your language, writes .bec/rules.y Installed as a devDependency, the pre-commit hook resolves the local binary from `node_modules/.bin`, so it works without a global install. The npm packages cover -`linux-x64`, `linux-arm64`, `darwin-arm64` and `win32-x64`; on any +`linux-x64`, `linux-arm64`, `darwin-x64`, `darwin-arm64` and `win32-x64`; on any other platform use `pipx install becwright`. Available commands: diff --git a/documentation/releasing.es.md b/documentation/releasing.es.md index 133f4f5..fa30709 100644 --- a/documentation/releasing.es.md +++ b/documentation/releasing.es.md @@ -3,7 +3,7 @@ becwright se distribuye por tres canales desde un único release de GitHub: - **PyPI** — el paquete de Python (`pip` / `pipx`). -- **npm** — un paquete lanzador (`becwright`) más cuatro paquetes por plataforma +- **npm** — un paquete lanzador (`becwright`) más cinco paquetes por plataforma con `os`/`cpu` (`@becwright/`), cada uno con un binario precompilado, para que quien no usa Python pueda instalarlo. @@ -24,7 +24,7 @@ becwright se distribuye por tres canales desde un único release de GitHub: 2. Commit y crear un **release de GitHub** con tag `vX.Y.Z` (debe coincidir con `pyproject.toml`). 3. Publicar el release dispara `release.yml`, que: - - construye y prueba el binario en las cuatro plataformas, + - construye y prueba el binario en todas las plataformas (macOS como binario universal2), - stagea cada binario en su paquete `@becwright/` (`npm/stage.mjs`), - fija la versión de cada paquete npm desde el tag (`npm/set-version.mjs`), - publica primero los paquetes de plataforma y luego el lanzador, diff --git a/documentation/releasing.md b/documentation/releasing.md index aa7e28b..64ba4b3 100644 --- a/documentation/releasing.md +++ b/documentation/releasing.md @@ -3,7 +3,7 @@ becwright ships through three channels from a single GitHub release: - **PyPI** — the Python package (`pip` / `pipx`). -- **npm** — a launcher package (`becwright`) plus four `os`/`cpu`-gated platform +- **npm** — a launcher package (`becwright`) plus five `os`/`cpu`-gated platform packages (`@becwright/`) that each carry a prebuilt binary, so users without Python can install it. @@ -23,7 +23,7 @@ becwright ships through three channels from a single GitHub release: 2. Commit, then create a **GitHub release** whose tag is `vX.Y.Z` (must match `pyproject.toml`). 3. Publishing the release triggers `release.yml`, which: - - builds and smoke-tests the binary on all four platforms, + - builds and smoke-tests the binary on all platforms (macOS as a universal2 binary), - stages each binary into its `@becwright/` package (`npm/stage.mjs`), - sets every npm package version from the tag (`npm/set-version.mjs`), diff --git a/npm/becwright/README.md b/npm/becwright/README.md index 7e21532..d87b919 100644 --- a/npm/becwright/README.md +++ b/npm/becwright/README.md @@ -34,7 +34,7 @@ local binary from `node_modules/.bin`, so it works without a global install. ## Supported platforms -`linux-x64`, `linux-arm64`, `darwin-arm64`, `win32-x64`. Intel macOS: use `pipx install becwright`. +`linux-x64`, `linux-arm64`, `darwin-x64`, `darwin-arm64`, `win32-x64` (macOS is a universal2 binary). On other platforms, install via [pipx](https://pipx.pypa.io): `pipx install becwright`. diff --git a/npm/becwright/bin/becwright.js b/npm/becwright/bin/becwright.js index 4e8779e..7678e0f 100755 --- a/npm/becwright/bin/becwright.js +++ b/npm/becwright/bin/becwright.js @@ -22,8 +22,8 @@ function resolveBinary() { const binary = resolveBinary(); if (!binary) { console.error(`becwright: no prebuilt binary for ${TARGET}.`); - console.error("Supported: linux-x64, linux-arm64, darwin-arm64, win32-x64."); - console.error("On other platforms (e.g. Intel macOS) install via pipx: pipx install becwright"); + console.error("Supported: linux-x64, linux-arm64, darwin-x64, darwin-arm64, win32-x64."); + console.error("On other platforms install via pipx: pipx install becwright"); process.exit(1); } diff --git a/npm/becwright/package.json b/npm/becwright/package.json index 1d4f3c1..8fa491b 100644 --- a/npm/becwright/package.json +++ b/npm/becwright/package.json @@ -1,6 +1,6 @@ { "name": "becwright", - "version": "0.1.0", + "version": "0.2.1", "description": "Deterministically enforces constraints (BECs) on your code, blocking commits that violate them. No Python required.", "bin": { "becwright": "bin/becwright.js" @@ -25,9 +25,10 @@ "node": ">=16" }, "optionalDependencies": { - "@becwright/linux-x64": "0.1.0", - "@becwright/linux-arm64": "0.1.0", - "@becwright/darwin-arm64": "0.1.0", - "@becwright/win32-x64": "0.1.0" + "@becwright/linux-x64": "0.2.1", + "@becwright/linux-arm64": "0.2.1", + "@becwright/darwin-x64": "0.2.1", + "@becwright/darwin-arm64": "0.2.1", + "@becwright/win32-x64": "0.2.1" } } diff --git a/npm/platforms/darwin-arm64/package.json b/npm/platforms/darwin-arm64/package.json index 75c5e94..70f3d41 100644 --- a/npm/platforms/darwin-arm64/package.json +++ b/npm/platforms/darwin-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@becwright/darwin-arm64", - "version": "0.1.0", + "version": "0.2.1", "description": "macOS arm64 (Apple Silicon) binary for becwright.", "os": [ "darwin" diff --git a/npm/platforms/darwin-x64/package.json b/npm/platforms/darwin-x64/package.json new file mode 100644 index 0000000..b350ffb --- /dev/null +++ b/npm/platforms/darwin-x64/package.json @@ -0,0 +1,16 @@ +{ + "name": "@becwright/darwin-x64", + "version": "0.2.1", + "description": "macOS x64 (Intel) binary for becwright.", + "os": [ + "darwin" + ], + "cpu": [ + "x64" + ], + "files": [ + "becwright" + ], + "license": "MIT", + "homepage": "https://github.com/DataDave-Dev/becwright" +} diff --git a/npm/platforms/linux-arm64/package.json b/npm/platforms/linux-arm64/package.json index c581796..2376213 100644 --- a/npm/platforms/linux-arm64/package.json +++ b/npm/platforms/linux-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@becwright/linux-arm64", - "version": "0.1.0", + "version": "0.2.1", "description": "Linux arm64 binary for becwright.", "os": [ "linux" diff --git a/npm/platforms/linux-x64/package.json b/npm/platforms/linux-x64/package.json index 2e2c317..4298db1 100644 --- a/npm/platforms/linux-x64/package.json +++ b/npm/platforms/linux-x64/package.json @@ -1,6 +1,6 @@ { "name": "@becwright/linux-x64", - "version": "0.1.0", + "version": "0.2.1", "description": "Linux x64 binary for becwright.", "os": [ "linux" diff --git a/npm/platforms/win32-x64/package.json b/npm/platforms/win32-x64/package.json index 63844cb..f9c34b1 100644 --- a/npm/platforms/win32-x64/package.json +++ b/npm/platforms/win32-x64/package.json @@ -1,6 +1,6 @@ { "name": "@becwright/win32-x64", - "version": "0.1.0", + "version": "0.2.1", "description": "Windows x64 binary for becwright.", "os": [ "win32" diff --git a/packaging/becwright.spec b/packaging/becwright.spec index 417689c..9cb0e13 100644 --- a/packaging/becwright.spec +++ b/packaging/becwright.spec @@ -12,6 +12,10 @@ entry = os.path.join(SPECPATH, "becwright_entry.py") # noqa: F821 # PyInstaller's static analysis cannot find them. Collect them all explicitly. hiddenimports = collect_submodules("becwright.checks") +# On macOS we build a single universal2 binary (Intel + Apple Silicon) by setting +# BECWRIGHT_TARGET_ARCH=universal2 in CI; elsewhere it stays the host arch. +target_arch = os.environ.get("BECWRIGHT_TARGET_ARCH") or None + a = Analysis( [entry], pathex=[os.path.join(root, "src")], @@ -41,7 +45,7 @@ exe = EXE( console=True, disable_windowed_traceback=False, argv_emulation=False, - target_arch=None, + target_arch=target_arch, codesign_identity=None, entitlements_file=None, )