diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..5ace460 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/auto-update.yml b/.github/workflows/auto-update.yml index 3a417ad..0d5fbf2 100644 --- a/.github/workflows/auto-update.yml +++ b/.github/workflows/auto-update.yml @@ -4,78 +4,124 @@ on: schedule: - cron: "0 0 * * *" jobs: - check-for-updates: + check-and-create-pr: runs-on: ubuntu-latest - outputs: - updated: ${{ steps.updated.outputs.updated }} + permissions: + contents: write + pull-requests: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: cachix/install-nix-action@v31 with: nix_path: nixpkgs=channel:nixos-unstable github_access_token: ${{ secrets.GITHUB_TOKEN }} + - run: nix flake update - run: ./update.sh - - id: updated - run: cat updated.txt >> "$GITHUB_OUTPUT" + - name: Check for changes + id: changes + run: | + if git diff --quiet flake.lock versions.json playwright-driver/browsers.json 2>/dev/null; then + echo "updated=false" >> "$GITHUB_OUTPUT" + else + echo "updated=true" >> "$GITHUB_OUTPUT" + fi + - name: Build PR summary + if: steps.changes.outputs.updated == 'true' + id: summary + run: | + old_driver=$(git show HEAD:versions.json | jq -r '.driver.version') + new_driver=$(jq -r '.driver.version' versions.json) + old_mcp=$(git show HEAD:versions.json | jq -r '.mcp.version') + new_mcp=$(jq -r '.mcp.version' versions.json) + old_lock=$(git show HEAD:flake.lock) + old_browsers=$(git show HEAD:playwright-driver/browsers.json) + new_browsers=$(cat playwright-driver/browsers.json) - check-linux: - if: needs.check-for-updates.outputs.updated == 'true' - needs: check-for-updates - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: cachix/install-nix-action@v31 - with: - nix_path: nixpkgs=channel:nixos-unstable - github_access_token: ${{ secrets.GITHUB_TOKEN }} - - run: ./update.sh - - run: nix build .#playwright-driver - - run: nix flake check + body="## Summary"$'\n\n' - check-darwin: - if: needs.check-for-updates.outputs.updated == 'true' - needs: check-for-updates - runs-on: macos-latest - steps: - - uses: actions/checkout@v4 - - uses: cachix/install-nix-action@v31 - with: - nix_path: nixpkgs=channel:nixos-unstable - github_access_token: ${{ secrets.GITHUB_TOKEN }} - - uses: nick-fields/retry@v3 - with: - timeout_minutes: 10 - retry_wait_seconds: 60 - max_attempts: 3 - command: ./update.sh - - run: nix build .#playwright-driver - - run: nix flake check + # Core packages section (only if something changed) + core_rows="" + if [ "$old_driver" != "$new_driver" ]; then + core_rows+="| Playwright driver | \`${old_driver}\` | \`${new_driver}\` |"$'\n' + fi + if [ "$old_mcp" != "$new_mcp" ]; then + core_rows+="| Playwright MCP | \`${old_mcp}\` | \`${new_mcp}\` |"$'\n' + fi + if [ -n "$core_rows" ]; then + body+="### Core packages"$'\n\n' + body+="| Package | Old | New |"$'\n' + body+="|---------|-----|-----|"$'\n' + body+="$core_rows"$'\n' + fi - push-updates: - needs: [check-for-updates, check-linux, check-darwin] - runs-on: ubuntu-latest + # Flake inputs section (dynamically read inputs from flake.lock) + flake_rows="" + for input in $(jq -r '.nodes.root.inputs | keys[]' flake.lock); do + old_rev=$(echo "$old_lock" | jq -r ".nodes[\"${input}\"].locked.rev // empty" | head -c 7) + new_rev=$(jq -r ".nodes[\"${input}\"].locked.rev // empty" flake.lock | head -c 7) + if [ "$old_rev" != "$new_rev" ]; then + flake_rows+="| ${input} | \`${old_rev}\` | \`${new_rev}\` |"$'\n' + fi + done + if [ -n "$flake_rows" ]; then + body+="### Flake inputs"$'\n\n' + body+="| Input | Old | New |"$'\n' + body+="|-------|-----|-----|"$'\n' + body+="$flake_rows"$'\n' + fi - permissions: - # Give the default GITHUB_TOKEN write permission to commit and push the - # added or changed files to the repository. - contents: write + # Browser versions section (only changed browsers) + browser_rows="" + for browser in chromium firefox webkit; do + old_ver=$(echo "$old_browsers" | jq -r ".browsers[\"${browser}\"].browserVersion // \"—\"") + new_ver=$(echo "$new_browsers" | jq -r ".browsers[\"${browser}\"].browserVersion // \"—\"") + if [ "$old_ver" != "$new_ver" ]; then + title=$(echo "$new_browsers" | jq -r ".browsers[\"${browser}\"].title // \"${browser}\"") + browser_rows+="| ${title} | \`${old_ver}\` | \`${new_ver}\` |"$'\n' + fi + done + if [ -n "$browser_rows" ]; then + body+="### Browser versions"$'\n\n' + body+="| Browser | Old | New |"$'\n' + body+="|---------|-----|-----|"$'\n' + body+="$browser_rows"$'\n' + fi - steps: - - uses: actions/checkout@v4 - - uses: cachix/install-nix-action@v31 - with: - nix_path: nixpkgs=channel:nixos-unstable - - run: ./update.sh - - name: Set commit and tagging message - id: commit_message_step + # Release notes (only for changed packages) + notes="" + if [ "$old_driver" != "$new_driver" ]; then + notes+="- [Playwright v${new_driver}](https://github.com/microsoft/playwright/releases/tag/v${new_driver})"$'\n' + fi + if [ "$old_mcp" != "$new_mcp" ]; then + notes+="- [Playwright MCP v${new_mcp}](https://github.com/microsoft/playwright-mcp/releases/tag/v${new_mcp})"$'\n' + fi + if [ -n "$notes" ]; then + body+="### Release notes"$'\n\n' + body+="$notes" + fi + + echo "$body" > /tmp/pr-body.md + - name: Create or update Pull Request + if: steps.changes.outputs.updated == 'true' + env: + GH_TOKEN: ${{ secrets.PAT_TOKEN }} run: | - echo 'commit_message<> $GITHUB_OUTPUT - echo "Update to version $(cat version.txt)" >> $GITHUB_OUTPUT - echo 'EOF' >> $GITHUB_OUTPUT - echo 'tagging_message<> $GITHUB_OUTPUT - cat version.txt >> $GITHUB_OUTPUT - echo 'EOF' >> $GITHUB_OUTPUT - - uses: stefanzweifel/git-auto-commit-action@v5 - with: - commit_message: ${{ steps.commit_message_step.outputs.commit_message }} - tagging_message: ${{ steps.commit_message_step.outputs.tagging_message }} + branch="auto-update/$(date +%Y-%m-%d)" + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git checkout -b "$branch" + git add flake.lock versions.json playwright-driver/browsers.json + git commit -m "Auto-update dependencies $(date +%Y-%m-%d)" -m "$(cat /tmp/pr-body.md)" + git remote set-url origin "https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git" + git push --force -u origin "$branch" + existing_pr=$(gh pr list --head "$branch" --json number --jq '.[0].number // empty') + if [ -n "$existing_pr" ]; then + gh pr edit "$existing_pr" \ + --title "Auto-update dependencies $(date +%Y-%m-%d)" \ + --body-file /tmp/pr-body.md + else + gh pr create \ + --title "Auto-update dependencies $(date +%Y-%m-%d)" \ + --label "auto-update" \ + --body-file /tmp/pr-body.md + fi diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..858c1ef --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,29 @@ +name: "CI" +on: + pull_request: +jobs: + check: + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v6 + - uses: cachix/install-nix-action@v31 + with: + nix_path: nixpkgs=channel:nixos-unstable + github_access_token: ${{ secrets.GITHUB_TOKEN }} + - run: nix build .#playwright-driver + - run: nix flake check + + automerge: + runs-on: ubuntu-latest + needs: [check] + permissions: write-all + if: contains(github.event.pull_request.labels.*.name, 'auto-update') + steps: + - name: automerge + uses: pascalgn/automerge-action@v0.16.4 + env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + MERGE_LABELS: "" diff --git a/README.md b/README.md index 4e92aea..82f7a65 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,36 @@ nix develop github:pietdevries94/playwright-web-flake which playwright && playwright --version && playwright open nixos.org ``` +### Playwright MCP Server + +Run the [Playwright MCP server](https://github.com/Microsoft/playwright-mcp) for use with AI assistants like Claude. + +```sh +nix shell github:pietdevries94/playwright-web-flake#playwright-mcp +``` + +#### Configuration + +Add the following to your MCP configuration file: + +```json +{ + "mcpServers": { + "playwright": { + "command": "nix", + "args": ["shell", "github:pietdevries94/playwright-web-flake#playwright-mcp", "--command", "playwright-mcp", "--headless"] + } + } +} +``` + +**Configuration file locations:** + +| Tool | Config File Path | +|------|------------------| +| Claude Desktop | `~/.config/claude/claude_desktop_config.json` (macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`) | +| Claude Code | `.mcp.json` (in project root) | + ### In a flake 1. Create a flake.nix with the content shown below. diff --git a/flake.lock b/flake.lock index 33aa5a6..3f06416 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1772773019, - "narHash": "sha256-E1bxHxNKfDoQUuvriG71+f+s/NT0qWkImXsYZNFFfCs=", + "lastModified": 1777268161, + "narHash": "sha256-bxrdOn8SCOv8tN4JbTF/TXq7kjo9ag4M+C8yzzIRYbE=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "aca4d95fce4914b3892661bcb80b8087293536c6", + "rev": "1c3fe55ad329cbcb28471bb30f05c9827f724c76", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 5ad3fd7..c9bac1f 100644 --- a/flake.nix +++ b/flake.nix @@ -18,11 +18,44 @@ pkgs = import nixpkgs { inherit system; }; + versions = pkgs.lib.importJSON ./versions.json; + playwright-driver-pkg = pkgs.callPackage ./playwright-driver/driver.nix { inherit versions; }; + playwright-mcp = pkgs.callPackage ./playwright-mcp/package.nix { + inherit (self.packages.${system}) playwright-driver; + inherit versions; + }; in { packages = { - playwright-test = (pkgs.callPackage ./playwright-driver/driver.nix { }).playwright-test; - playwright-driver = (pkgs.callPackage ./playwright-driver/driver.nix { }).playwright-core; + inherit playwright-mcp; + inherit (playwright-driver-pkg) playwright-test; + playwright-driver = playwright-driver-pkg.playwright-core; + }; + + checks = { + playwright-test = + pkgs.runCommand "check-playwright-test" + { + nativeBuildInputs = [ self.packages.${system}.playwright-test ]; + } + '' + playwright --version | grep "${self.packages.${system}.playwright-test.version}" + touch $out + ''; + playwright-driver = pkgs.runCommand "check-playwright-driver" { } '' + test -d "${self.packages.${system}.playwright-driver.browsers}" + test $(ls "${self.packages.${system}.playwright-driver.browsers}" | wc -l) -gt 0 + touch $out + ''; + playwright-mcp = + pkgs.runCommand "check-playwright-mcp" + { + nativeBuildInputs = [ self.packages.${system}.playwright-mcp ]; + } + '' + playwright-mcp --help + touch $out + ''; }; devShells.default = pkgs.mkShell { diff --git a/playwright-driver/chromium-headless-shell.nix b/playwright-driver/chromium-headless-shell.nix index 4d48679..c5ce728 100644 --- a/playwright-driver/chromium-headless-shell.nix +++ b/playwright-driver/chromium-headless-shell.nix @@ -22,6 +22,7 @@ libxkbcommon, nspr, nss, + hashes, ... }: let @@ -35,12 +36,7 @@ let } .${system} or throwSystem; stripRoot = false; - hash = - { - x86_64-linux = "sha256-kQCw0nQHHuUIfn8rGVcN7Ip6ZOk5c3Or+GG5RvSica4="; - aarch64-linux = "sha256-s2IIjSY5t9AtT05dUS0mp4fPlaixND9+Cg0+0S8Kkx8="; - } - .${system} or throwSystem; + hash = hashes.${system} or throwSystem; }; nativeBuildInputs = [ @@ -77,12 +73,7 @@ let } .${system} or throwSystem; stripRoot = false; - hash = - { - x86_64-darwin = "sha256-kzbLpzzMpBurQHyGaz561A0K46GzgWPP2JSQKRV6C+Y="; - aarch64-darwin = "sha256-67ekk37uq5ITq9ZvwPTZhhqEgQY17g/3KJ/vnqZz3h0="; - } - .${system} or throwSystem; + hash = hashes.${system} or throwSystem; }; in { diff --git a/playwright-driver/chromium.nix b/playwright-driver/chromium.nix index 3e09c07..fb8d66e 100644 --- a/playwright-driver/chromium.nix +++ b/playwright-driver/chromium.nix @@ -1,8 +1,6 @@ { - runCommand, makeWrapper, fontconfig_file, - chromium, fetchzip, revision, browserVersion, @@ -39,6 +37,7 @@ libXfixes, libXrandr, libxcb, + hashes, ... }: let @@ -51,12 +50,7 @@ let aarch64-linux = "https://cdn.playwright.dev/builds/chromium/${revision}/chromium-${suffix}.zip"; } .${system} or throwSystem; - hash = - { - x86_64-linux = "sha256-GtaJk9Qr1bTbDfpB+noGlLcIDEIS2uzDiV5rb2DZhVQ="; - aarch64-linux = "sha256-jsesQ6juzMPQzmn0Ygb8hmpxCeLHJVBL89qto5yuh5s="; - } - .${system} or throwSystem; + hash = hashes.${system} or throwSystem; }; nativeBuildInputs = [ @@ -124,12 +118,7 @@ let } .${system} or throwSystem; stripRoot = false; - hash = - { - x86_64-darwin = "sha256-CxnLSe+sZYskO7H5f3cA+BlWCZsFOPpp1gVG5s37r80="; - aarch64-darwin = "sha256-n3JLK+hJwzPihC2qSHrSCYPz3jonZNz7GMgXiPBLaS0="; - } - .${system} or throwSystem; + hash = hashes.${system} or throwSystem; }; in { diff --git a/playwright-driver/driver.nix b/playwright-driver/driver.nix index d2e1a81..ebf3888 100644 --- a/playwright-driver/driver.nix +++ b/playwright-driver/driver.nix @@ -2,8 +2,6 @@ lib, buildNpmPackage, stdenv, - chromium, - ffmpeg, jq, nodejs, fetchFromGitHub, @@ -11,8 +9,8 @@ callPackage, makeFontsConf, makeWrapper, - runCommand, cacert, + versions, }: let inherit (stdenv.hostPlatform) system; @@ -27,20 +25,20 @@ let } .${system} or throwSystem; - version = "1.59.0"; + inherit (versions.driver) version; src = fetchFromGitHub { owner = "Microsoft"; repo = "playwright"; rev = "v${version}"; - hash = "sha256-RgP43A/NfiaPd/CmIBc0uzK69/IB0RH9Liv7tK/5rH4="; + inherit (versions.driver) hash; }; babel-bundle = buildNpmPackage { pname = "babel-bundle"; inherit version src; sourceRoot = "${src.name}/packages/playwright/bundles/babel"; - npmDepsHash = "sha256-ByCy4go8PM0ksDg+2DcJPyoKG7Z0uIqKM647ZQwYwAE="; + npmDepsHash = versions.driver.npmDepsHashes."/packages/playwright/bundles/babel"; dontNpmBuild = true; installPhase = '' cp -r . "$out" @@ -50,7 +48,7 @@ let pname = "expect-bundle"; inherit version src; sourceRoot = "${src.name}/packages/playwright/bundles/expect"; - npmDepsHash = "sha256-PbPCsMqRkfU2c/mCsLSagew84XTgeO6H5+isNZQl2ek="; + npmDepsHash = versions.driver.npmDepsHashes."/packages/playwright/bundles/expect"; dontNpmBuild = true; installPhase = '' cp -r . "$out" @@ -60,7 +58,7 @@ let pname = "utils-bundle"; inherit version src; sourceRoot = "${src.name}/packages/playwright/bundles/utils"; - npmDepsHash = "sha256-BTaF1atpK+kG++ZJBUK4r3A7mbN2vv3xpDmb1NiNngE="; + npmDepsHash = versions.driver.npmDepsHashes."/packages/playwright/bundles/utils"; dontNpmBuild = true; installPhase = '' cp -r . "$out" @@ -70,7 +68,7 @@ let pname = "utils-bundle-core"; inherit version src; sourceRoot = "${src.name}/packages/playwright-core/bundles/utils"; - npmDepsHash = "sha256-O8X80rTT10ht97towSocANnGwH4fH1f3nZMSl8TOc+Y="; + npmDepsHash = versions.driver.npmDepsHashes."/packages/playwright-core/bundles/utils"; dontNpmBuild = true; installPhase = '' cp -r . "$out" @@ -80,7 +78,7 @@ let pname = "zip-bundle"; inherit version src; sourceRoot = "${src.name}/packages/playwright-core/bundles/zip"; - npmDepsHash = "sha256-5BHgCelIPh8ljIcdrO4AHafjqfLowDwJcpN+mD13Syw="; + npmDepsHash = versions.driver.npmDepsHashes."/packages/playwright-core/bundles/zip"; dontNpmBuild = true; installPhase = '' cp -r . "$out" @@ -91,8 +89,8 @@ let pname = "playwright"; inherit version src; - sourceRoot = "${src.name}"; # update.sh depends on sourceRoot presence - npmDepsHash = "sha256-GoKbwMBJR2UvN35xNz3+AqpC+jysy5La2Xpe2YU2InY="; + sourceRoot = "${src.name}"; + npmDepsHash = versions.driver.npmDepsHashes.""; nativeBuildInputs = [ cacert @@ -152,7 +150,7 @@ let }; }; - playwright-core = stdenv.mkDerivation (finalAttrs: { + playwright-core = stdenv.mkDerivation (_: { pname = "playwright-core"; inherit (playwright) version src meta; @@ -176,7 +174,7 @@ let }; }); - playwright-test = stdenv.mkDerivation (finalAttrs: { + playwright-test = stdenv.mkDerivation (_: { pname = "playwright-test"; inherit (playwright) version src; @@ -205,6 +203,7 @@ let chromium = callPackage ./chromium.nix { inherit suffix system throwSystem; inherit (playwright-core.passthru.browsersJSON.chromium) revision browserVersion; + hashes = versions.browsers.chromium; fontconfig_file = makeFontsConf { fontDirectories = [ ]; }; @@ -212,18 +211,22 @@ let chromium-headless-shell = callPackage ./chromium-headless-shell.nix { inherit suffix system throwSystem; inherit (playwright-core.passthru.browsersJSON.chromium) revision browserVersion; + hashes = versions.browsers."chromium-headless-shell"; }; firefox = callPackage ./firefox.nix { inherit suffix system throwSystem; inherit (playwright-core.passthru.browsersJSON.firefox) revision; + hashes = versions.browsers.firefox; }; webkit = callPackage ./webkit.nix { inherit suffix system throwSystem; inherit (playwright-core.passthru.browsersJSON.webkit) revision revisionOverrides; + hashes = versions.browsers.webkit; }; ffmpeg = callPackage ./ffmpeg.nix { inherit suffix system throwSystem; inherit (playwright-core.passthru.browsersJSON.ffmpeg) revision revisionOverrides; + hashes = versions.browsers.ffmpeg; }; }; @@ -234,9 +237,6 @@ let withWebkit ? true, # may require `export PLAYWRIGHT_HOST_PLATFORM_OVERRIDE="ubuntu-24.04"` withFfmpeg ? true, withChromiumHeadlessShell ? true, - fontconfig_file ? makeFontsConf { - fontDirectories = [ ]; - }, }: let browsers = @@ -264,6 +264,6 @@ let ); in { - playwright-core = playwright-core; - playwright-test = playwright-test; + inherit playwright-core; + inherit playwright-test; } diff --git a/playwright-driver/ffmpeg.nix b/playwright-driver/ffmpeg.nix index 2c28b0c..77d62ee 100644 --- a/playwright-driver/ffmpeg.nix +++ b/playwright-driver/ffmpeg.nix @@ -1,9 +1,9 @@ { - lib, fetchzip, suffix, revision, - revisionOverrides ? {}, + revisionOverrides ? { }, + hashes, system, throwSystem, }: @@ -27,12 +27,5 @@ in fetchzip { url = "https://cdn.playwright.dev/builds/ffmpeg/${revision'}/ffmpeg-${suffix}.zip"; stripRoot = false; - hash = - { - x86_64-linux = "sha256-AWTiui+ccKHxsIaQSgc5gWCJT5gYwIWzAEqSuKgVqZU="; - aarch64-linux = "sha256-1mOKO2lcnlwLsC6ob//xKnKrCOp94pw8X14uBxCdj0Q="; - x86_64-darwin = "sha256-ED6noxSDeEUt2DkIQ4gNe/kL+zHVeb2AD5klBk93F88="; - aarch64-darwin = "sha256-3Adnvb7zvMXKFOhb8uuj5kx0wEIFicmckYx9WLlNNf0="; - } - .${system} or throwSystem; + hash = hashes.${system} or throwSystem; } diff --git a/playwright-driver/firefox.nix b/playwright-driver/firefox.nix index 4a29ff3..539c56f 100644 --- a/playwright-driver/firefox.nix +++ b/playwright-driver/firefox.nix @@ -5,6 +5,7 @@ firefox-bin, suffix, revision, + hashes, system, throwSystem, }: @@ -15,12 +16,7 @@ let url = "https://cdn.playwright.dev/builds/firefox/${revision}/firefox-${ "ubuntu-22.04" + (lib.removePrefix "linux" suffix) }.zip"; - hash = - { - x86_64-linux = "sha256-vbFI+7y3zayi5HmR1cTHPbcFCi6bvF3OVoAu8MygyEo="; - aarch64-linux = "sha256-7+C3fsIRVo4pfFOPgN7gD0P+fxC6juTMaTzjthq+mno="; - } - .${system} or throwSystem; + hash = hashes.${system} or throwSystem; }; inherit (firefox-bin.unwrapped) @@ -39,12 +35,7 @@ let firefox-darwin = fetchzip { url = "https://cdn.playwright.dev/builds/firefox/${revision}/firefox-${suffix}.zip"; stripRoot = false; - hash = - { - x86_64-darwin = "sha256-OfNmamn82tJ8+eY6DC8a3AynmhObZ8E0GTegF8l7km4="; - aarch64-darwin = "sha256-WEYhmqGhGvO47i/OICJgXyqj64Wt52juJDEe7nD7HXU="; - } - .${system} or throwSystem; + hash = hashes.${system} or throwSystem; }; in { diff --git a/playwright-driver/webkit.nix b/playwright-driver/webkit.nix index 5bd8a23..d3cbae5 100644 --- a/playwright-driver/webkit.nix +++ b/playwright-driver/webkit.nix @@ -19,6 +19,7 @@ gst_all_1, harfbuzz, harfbuzzFull, + hyphen, icu70, lcms, libavif, @@ -48,7 +49,8 @@ zlib, suffix, revision, - revisionOverrides ? {}, + revisionOverrides ? { }, + hashes, system, throwSystem, }: @@ -77,7 +79,7 @@ let else suffix; libvpx' = libvpx.overrideAttrs ( - finalAttrs: previousAttrs: { + finalAttrs: _: { version = "1.12.0"; src = fetchFromGitHub { owner = "webmproject"; @@ -88,7 +90,7 @@ let } ); libavif' = libavif.overrideAttrs ( - finalAttrs: previousAttrs: { + finalAttrs: _: { version = "0.9.3"; src = fetchFromGitHub { owner = "AOMediaCodec"; @@ -102,7 +104,7 @@ let ); libjxl' = libjxl.overrideAttrs ( - finalAttrs: previousAttrs: { + finalAttrs: _: { version = "0.8.2"; src = fetchFromGitHub { owner = "libjxl"; @@ -152,12 +154,7 @@ let src = fetchzip { url = "https://cdn.playwright.dev/builds/webkit/${revision'}/webkit-${suffix'}.zip"; stripRoot = false; - hash = - { - x86_64-linux = "sha256-JdNYUUJPGRZ1Mdm2yTzFshsyntNlRqZCtyicLrrUk7g="; - aarch64-linux = "sha256-tQqaU1TuIfAohpMibWu9TVw9tVPZhXzYKZTZ5N7HqIk="; - } - .${system} or throwSystem; + hash = hashes.${system} or throwSystem; }; nativeBuildInputs = [ @@ -179,6 +176,7 @@ let gst_all_1.gstreamer harfbuzz harfbuzzFull + hyphen icu70 lcms libavif' @@ -233,12 +231,7 @@ let webkit-darwin = fetchzip { url = "https://cdn.playwright.dev/builds/webkit/${revision'}/webkit-${suffix'}.zip"; stripRoot = false; - hash = - { - x86_64-darwin = "sha256-zmxdNdptFJ+8sad6HICoJRNsVNdQ0j4kKKCPX9YsBE8="; - aarch64-darwin = "sha256-AbDHuUg8jLNPWur6hieDdY8Kc2+PmlXRJGD46yujam4="; - } - .${system} or throwSystem; + hash = hashes.${system} or throwSystem; }; in { diff --git a/playwright-mcp/package.nix b/playwright-mcp/package.nix new file mode 100644 index 0000000..8159268 --- /dev/null +++ b/playwright-mcp/package.nix @@ -0,0 +1,37 @@ +{ + lib, + buildNpmPackage, + fetchFromGitHub, + playwright-driver, + versions, +}: +buildNpmPackage rec { + pname = "playwright-mcp"; + inherit (versions.mcp) version; + + src = fetchFromGitHub { + owner = "Microsoft"; + repo = "playwright-mcp"; + tag = "v${version}"; + inherit (versions.mcp) hash; + }; + + inherit (versions.mcp) npmDepsHash; + + postInstall = '' + wrapProgram $out/bin/playwright-mcp \ + --set PLAYWRIGHT_BROWSERS_PATH ${playwright-driver.browsers} \ + --set-default PLAYWRIGHT_MCP_BROWSER chromium \ + --run 'if [ -z "$PLAYWRIGHT_MCP_USER_DATA_DIR" ]; then PLAYWRIGHT_MCP_USER_DATA_DIR="$(mktemp -d -t mcp-pw-XXXXXX)"; export PLAYWRIGHT_MCP_USER_DATA_DIR; trap "rm -rf \"$PLAYWRIGHT_MCP_USER_DATA_DIR\"" EXIT; fi' + ''; + + dontNpmBuild = true; + + meta = { + changelog = "https://github.com/Microsoft/playwright-mcp/releases/tag/v${version}"; + description = "Playwright MCP server"; + homepage = "https://github.com/Microsoft/playwright-mcp"; + license = lib.licenses.asl20; + mainProgram = "playwright-mcp"; + }; +} diff --git a/update.sh b/update.sh index ae34022..01b3e44 100755 --- a/update.sh +++ b/update.sh @@ -1,5 +1,5 @@ #!/usr/bin/env nix-shell -#!nix-shell -i bash -p curl gnused common-updater-scripts jq prefetch-npm-deps unzip nix-prefetch nix-prefetch-github +#!nix-shell -i bash -p curl jq prefetch-npm-deps unzip nix-prefetch nix-prefetch-github # shellcheck shell=bash set -euo pipefail set -x @@ -8,48 +8,26 @@ root="$(dirname "$(readlink -f "$0")")" repo_root="$(git -C "$root" rev-parse --show-toplevel)" cd "$repo_root" -github_api_curl_args=() -if [ -n "${GITHUB_TOKEN:-}" ]; then - github_api_curl_args=(-u ":$GITHUB_TOKEN") -fi - +versions_file="$root/versions.json" playwright_browsers_file="$root/playwright-driver/browsers.json" -playwright_driver_file="$root/playwright-driver/driver.nix" playwright_raw_repo_url="https://raw.githubusercontent.com/microsoft/playwright" driver_version=$(curl ${GITHUB_TOKEN:+" -u \":$GITHUB_TOKEN\""} -s https://api.github.com/repos/microsoft/playwright/releases/latest | jq -r '.tag_name | sub("^v"; "")') +mcp_version=$(curl ${GITHUB_TOKEN:+" -u \":$GITHUB_TOKEN\""} -s https://api.github.com/repos/microsoft/playwright-mcp/releases/latest | jq -r '.tag_name | sub("^v"; "")') browser_names=(chromium chromium-headless-shell firefox webkit ffmpeg) browser_platforms=(linux darwin) -github_api_get() { - curl "${github_api_curl_args[@]}" -fsSL "$1" -} - -major_minor() { - echo "${1%.*}" -} - -sed -i "s|version =\s*\"[^\$]*\"|version = \"$driver_version\"|" "$playwright_driver_file" +# Compute driver source hash driver_new_hash=$(nix-prefetch-github --rev "v$driver_version" "Microsoft" "playwright" | jq -r '.hash') -sed -i "s|hash =\s*\"[^\$]*\"|hash = \"$driver_new_hash\"|" "$playwright_driver_file" temp_dir=$(mktemp -d) -trap 'rm -rf "$temp_dir"' EXIT - -# update binaries of browsers, used by playwright. -replace_sha() { - local target_file="$1" - local attr_name="$2" - local new_hash="$3" - - sed -i "s|$attr_name = \".\{44,52\}\"|$attr_name = \"$new_hash\"|" "$target_file" -} +mcp_temp_dir="" +cleanup() { rm -rf "$temp_dir" "$mcp_temp_dir"; } +trap cleanup EXIT prefetch_browser() { local url="$1" local strip_root="$2" - # nix-prefetch is used to obtain sha with `stripRoot = false` - # doesn't work on macOS https://github.com/msteen/nix-prefetch/issues/53 nix-prefetch --option extra-experimental-features flakes -q "{ stdenv, fetchzip }: stdenv.mkDerivation { name=\"browser\"; src = fetchzip { url = \"$url\"; stripRoot = $strip_root; }; }" } @@ -60,7 +38,6 @@ get_revision() { local base_revision="$4" local override_key="" - # Determine the revision override key based on platform and arch if [ "$platform" = "darwin" ]; then if [ "$name" = "webkit" ]; then if [ "$arch" = "x86_64" ]; then @@ -77,7 +54,6 @@ get_revision() { fi fi - # Check for revision override if [ -n "$override_key" ]; then local override_revision override_revision="$(jq -r ".browsers[\"$name\"].revisionOverrides[\"$override_key\"] // empty" "$playwright_browsers_file")" @@ -101,8 +77,6 @@ browser_download_url() { local artifact local cft_platform - # Chromium and chromium-headless-shell use Chrome for Testing artifacts on - # Linux/macOS on x86_64 and aarch64-darwin. if [ "$name" = "chromium" ] || [ "$name" = "chromium-headless-shell" ]; then if [ "$name" = "chromium" ]; then artifact="chrome" @@ -126,7 +100,6 @@ browser_download_url() { fi fi - # Webkit on darwin uses a different CDN path if [ "$name" = "webkit" ] && [ "$platform" = "darwin" ]; then echo "https://cdn.playwright.dev/builds/${buildname}/${revision}/${name}-${suffix}.zip" return @@ -135,6 +108,9 @@ browser_download_url() { echo "https://cdn.playwright.dev/dbazure/download/playwright/builds/${buildname}/${revision}/${name}-${suffix}.zip" } +# Declare associative array for browser hashes +declare -A browser_hashes + update_browser() { local name="$1" local platform="$2" @@ -142,10 +118,7 @@ update_browser() { local suffix local aarch64_suffix local buildname - local revision local browser_version - local x86_64_url - local aarch64_url if [ "$platform" = "darwin" ]; then if [ "$name" = "webkit" ]; then @@ -177,20 +150,21 @@ update_browser() { base_revision="$(jq -r ".browsers[\"$buildname\"].revision" "$playwright_browsers_file")" browser_version="$(jq -r ".browsers[\"$buildname\"].browserVersion // empty" "$playwright_browsers_file")" - # Get platform-specific revisions (handles revisionOverrides) local x86_64_revision local aarch64_revision x86_64_revision="$(get_revision "$name" "$platform" "x86_64" "$base_revision")" aarch64_revision="$(get_revision "$name" "$platform" "aarch64" "$base_revision")" + local x86_64_url + local aarch64_url x86_64_url="$(browser_download_url "$name" "$buildname" "$platform" "x86_64" "$x86_64_revision" "$browser_version" "$suffix")" aarch64_url="$(browser_download_url "$name" "$buildname" "$platform" "aarch64" "$aarch64_revision" "$browser_version" "$aarch64_suffix")" - replace_sha "$root/playwright-driver/$name.nix" "x86_64-$platform" \ - "$(prefetch_browser "$x86_64_url" "$stripRoot")" - replace_sha "$root/playwright-driver/$name.nix" "aarch64-$platform" \ - "$(prefetch_browser "$aarch64_url" "$stripRoot")" + + browser_hashes["${name}.x86_64-${platform}"]="$(prefetch_browser "$x86_64_url" "$stripRoot")" + browser_hashes["${name}.aarch64-${platform}"]="$(prefetch_browser "$aarch64_url" "$stripRoot")" } +# Update browsers.json from upstream curl -fsSL \ "https://raw.githubusercontent.com/microsoft/playwright/v${driver_version}/packages/playwright-core/browsers.json" \ | jq ' @@ -203,34 +177,137 @@ curl -fsSL \ ) ' > "$playwright_browsers_file" +# Compute all browser hashes for platform in "${browser_platforms[@]}"; do for browser in "${browser_names[@]}"; do update_browser "$browser" "$platform" done done -# Update package-lock.json files for all npm deps that are built in playwright - -# Download `package-lock.json` for a given sourceRoot path and update its hash. -update_hash() { - local source_root_path="$1" - local download_url - local lock_file - local new_hash - local source_root_pattern +# Compute npm dependency hashes for driver bundles +declare -A npm_hashes +npm_source_roots=( + "/packages/playwright/bundles/babel" + "/packages/playwright/bundles/expect" + "/packages/playwright/bundles/utils" + "/packages/playwright-core/bundles/utils" + "/packages/playwright-core/bundles/zip" + "" +) - download_url="${playwright_raw_repo_url}/v${driver_version}${source_root_path}/package-lock.json" +for source_root_path in "${npm_source_roots[@]}"; do + if [ -n "$source_root_path" ]; then + download_url="${playwright_raw_repo_url}/v${driver_version}${source_root_path}/package-lock.json" + else + download_url="${playwright_raw_repo_url}/v${driver_version}/package-lock.json" + fi lock_file="${temp_dir}/$(echo "$source_root_path" | tr '/.' '__').package-lock.json" curl -fsSL -o "$lock_file" "$download_url" - new_hash=$(prefetch-npm-deps "$lock_file") - - source_root_pattern=$(printf '%s\n' "$source_root_path" | sed 's/[][\\/.*^$+?(){}|]/\\&/g') - sed -E -i "/sourceRoot = \"\\\$\\{src.name\\}${source_root_pattern}\";/,/npmDepsHash = / s#npmDepsHash = \"[^\"]*\";#npmDepsHash = \"${new_hash}\";#" "$playwright_driver_file" -} + # Use safe key by replacing / with _ for associative array + # Use "root" for empty path since bash doesn't allow empty string keys + if [ -n "$source_root_path" ]; then + safe_key=$(echo "$source_root_path" | tr '/' '_') + else + safe_key="root" + fi + npm_hashes["$safe_key"]=$(prefetch-npm-deps "$lock_file") +done -while IFS= read -r source_root_path; do - update_hash "$source_root_path" -done < <( - # shellcheck disable=SC2016 - sed -n 's#^[[:space:]]*sourceRoot = "${src.name}\(.*\)";.*$#\1#p' "$playwright_driver_file" -) +# Compute MCP hashes +echo "Updating playwright-mcp to v${mcp_version}..." +mcp_new_hash=$(nix-prefetch-github --rev "v${mcp_version}" "Microsoft" "playwright-mcp" | jq -r '.hash') +mcp_temp_dir=$(mktemp -d) +mcp_lock_url="https://raw.githubusercontent.com/microsoft/playwright-mcp/v${mcp_version}/package-lock.json" +curl -fsSL -o "$mcp_temp_dir/package-lock.json" "$mcp_lock_url" +mcp_npm_hash=$(prefetch-npm-deps "$mcp_temp_dir/package-lock.json") + +# Build versions.json with all computed values +jq -n \ + --arg driver_version "$driver_version" \ + --arg driver_hash "$driver_new_hash" \ + --arg npm_babel "${npm_hashes["_packages_playwright_bundles_babel"]}" \ + --arg npm_expect "${npm_hashes["_packages_playwright_bundles_expect"]}" \ + --arg npm_utils "${npm_hashes["_packages_playwright_bundles_utils"]}" \ + --arg npm_utils_core "${npm_hashes["_packages_playwright-core_bundles_utils"]}" \ + --arg npm_zip "${npm_hashes["_packages_playwright-core_bundles_zip"]}" \ + --arg npm_root "${npm_hashes["root"]}" \ + --arg chromium_x86_64_linux "${browser_hashes["chromium.x86_64-linux"]}" \ + --arg chromium_aarch64_linux "${browser_hashes["chromium.aarch64-linux"]}" \ + --arg chromium_x86_64_darwin "${browser_hashes["chromium.x86_64-darwin"]}" \ + --arg chromium_aarch64_darwin "${browser_hashes["chromium.aarch64-darwin"]}" \ + --arg chromium_hs_x86_64_linux "${browser_hashes["chromium-headless-shell.x86_64-linux"]}" \ + --arg chromium_hs_aarch64_linux "${browser_hashes["chromium-headless-shell.aarch64-linux"]}" \ + --arg chromium_hs_x86_64_darwin "${browser_hashes["chromium-headless-shell.x86_64-darwin"]}" \ + --arg chromium_hs_aarch64_darwin "${browser_hashes["chromium-headless-shell.aarch64-darwin"]}" \ + --arg firefox_x86_64_linux "${browser_hashes["firefox.x86_64-linux"]}" \ + --arg firefox_aarch64_linux "${browser_hashes["firefox.aarch64-linux"]}" \ + --arg firefox_x86_64_darwin "${browser_hashes["firefox.x86_64-darwin"]}" \ + --arg firefox_aarch64_darwin "${browser_hashes["firefox.aarch64-darwin"]}" \ + --arg webkit_x86_64_linux "${browser_hashes["webkit.x86_64-linux"]}" \ + --arg webkit_aarch64_linux "${browser_hashes["webkit.aarch64-linux"]}" \ + --arg webkit_x86_64_darwin "${browser_hashes["webkit.x86_64-darwin"]}" \ + --arg webkit_aarch64_darwin "${browser_hashes["webkit.aarch64-darwin"]}" \ + --arg ffmpeg_x86_64_linux "${browser_hashes["ffmpeg.x86_64-linux"]}" \ + --arg ffmpeg_aarch64_linux "${browser_hashes["ffmpeg.aarch64-linux"]}" \ + --arg ffmpeg_x86_64_darwin "${browser_hashes["ffmpeg.x86_64-darwin"]}" \ + --arg ffmpeg_aarch64_darwin "${browser_hashes["ffmpeg.aarch64-darwin"]}" \ + --arg mcp_version "$mcp_version" \ + --arg mcp_hash "$mcp_new_hash" \ + --arg mcp_npm_hash "$mcp_npm_hash" \ + '{ + driver: { + version: $driver_version, + hash: $driver_hash, + npmDepsHashes: { + "/packages/playwright/bundles/babel": $npm_babel, + "/packages/playwright/bundles/expect": $npm_expect, + "/packages/playwright/bundles/utils": $npm_utils, + "/packages/playwright-core/bundles/utils": $npm_utils_core, + "/packages/playwright-core/bundles/zip": $npm_zip, + "": $npm_root + } + }, + browsers: { + chromium: { + "x86_64-linux": $chromium_x86_64_linux, + "aarch64-linux": $chromium_aarch64_linux, + "x86_64-darwin": $chromium_x86_64_darwin, + "aarch64-darwin": $chromium_aarch64_darwin + }, + "chromium-headless-shell": { + "x86_64-linux": $chromium_hs_x86_64_linux, + "aarch64-linux": $chromium_hs_aarch64_linux, + "x86_64-darwin": $chromium_hs_x86_64_darwin, + "aarch64-darwin": $chromium_hs_aarch64_darwin + }, + firefox: { + "x86_64-linux": $firefox_x86_64_linux, + "aarch64-linux": $firefox_aarch64_linux, + "x86_64-darwin": $firefox_x86_64_darwin, + "aarch64-darwin": $firefox_aarch64_darwin + }, + webkit: { + "x86_64-linux": $webkit_x86_64_linux, + "aarch64-linux": $webkit_aarch64_linux, + "x86_64-darwin": $webkit_x86_64_darwin, + "aarch64-darwin": $webkit_aarch64_darwin + }, + ffmpeg: { + "x86_64-linux": $ffmpeg_x86_64_linux, + "aarch64-linux": $ffmpeg_aarch64_linux, + "x86_64-darwin": $ffmpeg_x86_64_darwin, + "aarch64-darwin": $ffmpeg_aarch64_darwin + } + }, + mcp: { + version: $mcp_version, + hash: $mcp_hash, + npmDepsHash: $mcp_npm_hash + } + }' > "$versions_file" + +echo "playwright-mcp updated to v${mcp_version}" +echo "All versions written to versions.json" + +# Write version for commit message +echo "${driver_version}" > version.txt diff --git a/versions.json b/versions.json new file mode 100644 index 0000000..003a859 --- /dev/null +++ b/versions.json @@ -0,0 +1,51 @@ +{ + "driver": { + "version": "1.59.1", + "hash": "sha256-kiH1jDyt5xMWc5C2dytoDC9fi1b5tWXZG8S6KEpuotM=", + "npmDepsHashes": { + "/packages/playwright/bundles/babel": "sha256-ByCy4go8PM0ksDg+2DcJPyoKG7Z0uIqKM647ZQwYwAE=", + "/packages/playwright/bundles/expect": "sha256-PbPCsMqRkfU2c/mCsLSagew84XTgeO6H5+isNZQl2ek=", + "/packages/playwright/bundles/utils": "sha256-BTaF1atpK+kG++ZJBUK4r3A7mbN2vv3xpDmb1NiNngE=", + "/packages/playwright-core/bundles/utils": "sha256-O8X80rTT10ht97towSocANnGwH4fH1f3nZMSl8TOc+Y=", + "/packages/playwright-core/bundles/zip": "sha256-5BHgCelIPh8ljIcdrO4AHafjqfLowDwJcpN+mD13Syw=", + "": "sha256-H3kKFthmZH4fqFPQA34w7iw2rubpEKLlJ9jaW6mpuyo=" + } + }, + "browsers": { + "chromium": { + "x86_64-linux": "sha256-GtaJk9Qr1bTbDfpB+noGlLcIDEIS2uzDiV5rb2DZhVQ=", + "aarch64-linux": "sha256-jsesQ6juzMPQzmn0Ygb8hmpxCeLHJVBL89qto5yuh5s=", + "x86_64-darwin": "sha256-CxnLSe+sZYskO7H5f3cA+BlWCZsFOPpp1gVG5s37r80=", + "aarch64-darwin": "sha256-n3JLK+hJwzPihC2qSHrSCYPz3jonZNz7GMgXiPBLaS0=" + }, + "chromium-headless-shell": { + "x86_64-linux": "sha256-kQCw0nQHHuUIfn8rGVcN7Ip6ZOk5c3Or+GG5RvSica4=", + "aarch64-linux": "sha256-s2IIjSY5t9AtT05dUS0mp4fPlaixND9+Cg0+0S8Kkx8=", + "x86_64-darwin": "sha256-kzbLpzzMpBurQHyGaz561A0K46GzgWPP2JSQKRV6C+Y=", + "aarch64-darwin": "sha256-67ekk37uq5ITq9ZvwPTZhhqEgQY17g/3KJ/vnqZz3h0=" + }, + "firefox": { + "x86_64-linux": "sha256-vbFI+7y3zayi5HmR1cTHPbcFCi6bvF3OVoAu8MygyEo=", + "aarch64-linux": "sha256-7+C3fsIRVo4pfFOPgN7gD0P+fxC6juTMaTzjthq+mno=", + "x86_64-darwin": "sha256-OfNmamn82tJ8+eY6DC8a3AynmhObZ8E0GTegF8l7km4=", + "aarch64-darwin": "sha256-WEYhmqGhGvO47i/OICJgXyqj64Wt52juJDEe7nD7HXU=" + }, + "webkit": { + "x86_64-linux": "sha256-JdNYUUJPGRZ1Mdm2yTzFshsyntNlRqZCtyicLrrUk7g=", + "aarch64-linux": "sha256-tQqaU1TuIfAohpMibWu9TVw9tVPZhXzYKZTZ5N7HqIk=", + "x86_64-darwin": "sha256-zmxdNdptFJ+8sad6HICoJRNsVNdQ0j4kKKCPX9YsBE8=", + "aarch64-darwin": "sha256-AbDHuUg8jLNPWur6hieDdY8Kc2+PmlXRJGD46yujam4=" + }, + "ffmpeg": { + "x86_64-linux": "sha256-AWTiui+ccKHxsIaQSgc5gWCJT5gYwIWzAEqSuKgVqZU=", + "aarch64-linux": "sha256-1mOKO2lcnlwLsC6ob//xKnKrCOp94pw8X14uBxCdj0Q=", + "x86_64-darwin": "sha256-ED6noxSDeEUt2DkIQ4gNe/kL+zHVeb2AD5klBk93F88=", + "aarch64-darwin": "sha256-3Adnvb7zvMXKFOhb8uuj5kx0wEIFicmckYx9WLlNNf0=" + } + }, + "mcp": { + "version": "0.0.71", + "hash": "sha256-C4tXXpIbGdgedy5IdUi2xeSQwAo4v+++PC7rznX2sMc=", + "npmDepsHash": "sha256-MKCldBLhxEa4g9A3crj48bRbBfbnGbEntvf3aTgqYII=" + } +}