diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0099675d..160c1543 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -53,6 +53,86 @@ jobs: github_token: ${{ secrets.RELEASE_PR_TOKEN }} branch: ${{ fromJson(steps.release.outputs.pr).headBranchName }} + safari-package: + name: Safari Package + runs-on: macos-latest + needs: [release-pr] + if: ${{ (github.event_name == 'pull_request' || needs.release-pr.outputs.release_created == 'true') && always() }} + steps: + - name: Checkout + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + submodules: true + + - name: Install pnpm + uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 + + - name: Setup Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: 24.16.0 + cache: "pnpm" + + - name: Install dependencies + run: pnpm install + + - name: Build Safari app + run: make safari + + - name: Archive Safari app + run: | + cd dist/safari + ditto -c -k --keepParent "JabRef Browser Extension.app" "JabRef Browser Extension.app.zip" + + - name: Upload Safari Artifact + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: safari-unsigned-app + path: dist/safari/JabRef Browser Extension.app.zip + + safari-publish: + name: Safari Publish + runs-on: macos-26 + needs: [release-pr, safari-package] + if: ${{ github.event_name != 'pull_request' && needs.release-pr.outputs.release_created == 'true' }} + steps: + - name: Checkout + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + submodules: true + + - name: Install pnpm + uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 + + - name: Setup Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: 24.16.0 + cache: "pnpm" + + - name: Install dependencies + run: pnpm install + + - name: Build Safari project + run: make safari + + - name: Upload Safari to App Store Connect + uses: rxliuli/safari-webext-publish-action@624701331ecbeb38464a09d3cac6d246b6efb006 + with: + project-path: "dist/safari" + bundle-identifier: "org.jabref.JabRef-Browser-Extension" + team-id: ${{ secrets.APPLE_TEAM_ID }} + app-signing-identity: ${{ secrets.SAFARI_APP_SIGNING_IDENTITY }} + installer-signing-identity: ${{ secrets.SAFARI_INSTALLER_SIGNING_IDENTITY }} + env: + APPLE_CERTIFICATE_BASE64: ${{ secrets.APPLE_CERTIFICATE_BASE64 }} + APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} + APPLE_MACOS_PROVISIONING_PROFILE_BASE64: ${{ secrets.APPLE_MACOS_PROVISIONING_PROFILE_BASE64 }} + APPLE_MACOS_EXTENSION_PROVISIONING_PROFILE_BASE64: ${{ secrets.APPLE_MACOS_EXTENSION_PROVISIONING_PROFILE_BASE64 }} + APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }} + APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} + APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }} + publish: name: Publish runs-on: ubuntu-latest diff --git a/.github/workflows/safari-signing-test.yml b/.github/workflows/safari-signing-test.yml new file mode 100644 index 00000000..0a3b837c --- /dev/null +++ b/.github/workflows/safari-signing-test.yml @@ -0,0 +1,75 @@ +name: Safari Signing Test + +on: + workflow_dispatch: + inputs: + run_safari_signing_test: + description: Run the Safari signing and publish step + type: boolean + required: true + default: true + +permissions: + contents: read + +jobs: + safari-publish: + name: Safari Publish + runs-on: macos-26 + if: ${{ inputs.run_safari_signing_test }} + steps: + - name: Checkout + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + submodules: true + + - name: Install pnpm + uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 + + - name: Setup Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: 24.16.0 + cache: "pnpm" + + - name: Install dependencies + run: pnpm install + + - name: Build Safari project + run: make safari + + - name: Set unique build number + shell: bash + run: | + build_number="$(date -u +%Y%m%d%H%M%S)${GITHUB_RUN_ATTEMPT}" + project_pbxproj='dist/safari/JabRef Browser Extension.xcodeproj/project.pbxproj' + perl -0pi -e "s/CURRENT_PROJECT_VERSION = [0-9]+;/CURRENT_PROJECT_VERSION = ${build_number};/g" "$project_pbxproj" + + for plist in \ + 'dist/safari/JabRef Browser Extension/Info.plist' \ + 'dist/safari/JabRef Browser Extension Extension/Info.plist' \ + 'dist/safari/JabRef Browser Extension.app/Contents/Info.plist' \ + 'dist/safari/JabRef Browser Extension.app/Contents/PlugIns/JabRef Browser Extension Extension.appex/Contents/Info.plist' + do + if [ -f "$plist" ]; then + /usr/libexec/PlistBuddy -c "Set :CFBundleVersion ${build_number}" "$plist" >/dev/null 2>&1 || \ + /usr/libexec/PlistBuddy -c "Add :CFBundleVersion string ${build_number}" "$plist" + fi + done + + - name: Upload Safari to App Store Connect + uses: rxliuli/safari-webext-publish-action@624701331ecbeb38464a09d3cac6d246b6efb006 + with: + project-path: "dist/safari" + bundle-identifier: "org.jabref.JabRef-Browser-Extension" + team-id: ${{ secrets.APPLE_TEAM_ID }} + app-signing-identity: ${{ secrets.SAFARI_APP_SIGNING_IDENTITY }} + installer-signing-identity: ${{ secrets.SAFARI_INSTALLER_SIGNING_IDENTITY }} + env: + APPLE_CERTIFICATE_BASE64: ${{ secrets.APPLE_CERTIFICATE_BASE64 }} + APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} + APPLE_MACOS_PROVISIONING_PROFILE_BASE64: ${{ secrets.APPLE_MACOS_PROVISIONING_PROFILE_BASE64 }} + APPLE_MACOS_EXTENSION_PROVISIONING_PROFILE_BASE64: ${{ secrets.APPLE_MACOS_EXTENSION_PROVISIONING_PROFILE_BASE64 }} + APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }} + APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} + APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2a764541..0bc2a933 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -31,3 +31,27 @@ jobs: - name: Run tests run: pnpm test + + safari-build: + name: Safari Build + runs-on: macos-latest + steps: + - name: Checkout + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + submodules: true + + - name: Install pnpm + uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 + + - name: Setup Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: 24.16.0 + cache: "pnpm" + + - name: Install dependencies + run: pnpm install + + - name: Build Safari app + run: make safari diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d9355ec7..dae9f54c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,12 +7,27 @@ Preparation: 1. Install [Node.js](https://nodejs.org) (e.g., `choco install nodejs`) and [pnpm](https://pnpm.io) (e.g., `npm install -g pnpm`). 2. [Fork the repository](https://help.github.com/articles/fork-a-repo/). 3. Checkout the repository. -4. Install development dependencies via `pnpm install`. -5. Start browser with the add-on activated: +4. Initialize the git submodules via `git submodule update --init --recursive`. +5. Install development dependencies via `pnpm install`. +6. Start browser with the add-on activated: Firefox: `pnpm dev:firefox` Chrome: `pnpm dev:chrome` Opera: `pnpm dev:opera` Edge: `pnpm dev:edge` + Safari: `pnpm safari:xcode` (macOS with Xcode required) + +Safari local packaging flow: + +1. Build and generate the Xcode project: + `pnpm safari:xcode` +2. Open: + `dist/safari/JabRef Browser Extension.xcodeproj` +3. Run the `JabRef Browser Extension` scheme in Xcode +4. Enable the extension in Safari Settings +5. Optional signing: + `pnpm sign:safari-local IDENTITY="Developer ID Application: Your Name (TEAMID)"` +6. Optional notarization: + `pnpm notarize:safari-local PROFILE="profile-name"` Now just follow the typical steps to [contribute code](https://guides.github.com/activities/contributing-to-open-source/#contributing): @@ -43,3 +58,30 @@ The following commands are used to update the dependencies of the project; as we - https://developer.apple.com/app-store-connect/ - Remove the `key` field in `wxt.config.ts` and build again. Then upload to: - https://partner.microsoft.com/en-us/dashboard/microsoftedge/2045cdc1-808f-43c4-8091-43e2dcaff53d/packages + +## Safari CI and Notarization + +Safari CI currently has two jobs: + +1. `.github/workflows/test.yml` + - `safari-build` + - runs on `macos-latest` + - executes `make safari` +2. `.github/workflows/release.yml` + - `safari-package` + - builds and uploads the unsigned Safari app artifact + - `safari-publish` + - publishes the Safari project to App Store Connect for actual releases + +GitHub Actions secrets required for Safari publishing: + +- `APPLE_TEAM_ID` +- `APPLE_CERTIFICATE_BASE64` +- `APPLE_CERTIFICATE_PASSWORD` +- `SAFARI_APP_SIGNING_IDENTITY` +- `SAFARI_INSTALLER_SIGNING_IDENTITY` +- `APPLE_MACOS_PROVISIONING_PROFILE_BASE64` +- `APPLE_MACOS_EXTENSION_PROVISIONING_PROFILE_BASE64` +- `APPLE_API_KEY` +- `APPLE_API_KEY_ID` +- `APPLE_API_ISSUER` diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..a18ffa9a --- /dev/null +++ b/Makefile @@ -0,0 +1,37 @@ +DIST := dist +SAFARI_DIR := $(DIST)/safari +SAFARI_PROJECT := $(SAFARI_DIR)/JabRef Browser Extension.xcodeproj +SAFARI_BUNDLE := $(DIST)/safari-mv3 +SAFARI_APP := $(SAFARI_DIR)/JabRef Browser Extension.app +SAFARI_DERIVED_DATA := $(SAFARI_DIR)/build + +.PHONY: safari sign-safari-local notarize-safari-local clean-safari + +safari: + rm -rf "$(SAFARI_DIR)" + rm -rf "$(SAFARI_BUNDLE)" + mkdir -p "$(DIST)" + pnpm build:safari + mkdir -p "$(SAFARI_DIR)" + node scripts/prepare_safari_bundle.mjs + node scripts/patch_safari_project.mjs + xcodebuild -project "$(SAFARI_PROJECT)" \ + -scheme "JabRef Browser Extension" \ + -configuration Release \ + -derivedDataPath "$(SAFARI_DERIVED_DATA)" \ + CODE_SIGN_IDENTITY="" \ + CODE_SIGNING_REQUIRED=NO \ + CODE_SIGNING_ALLOWED=NO \ + build + ditto "$(SAFARI_DERIVED_DATA)/Build/Products/Release/JabRef Browser Extension.app" "$(SAFARI_APP)" + +sign-safari-local: + chmod +x scripts/sign_safari_local.sh + ./scripts/sign_safari_local.sh "$(IDENTITY)" + +notarize-safari-local: + chmod +x scripts/notarize_safari_local.sh + ./scripts/notarize_safari_local.sh "$(PROFILE)" + +clean-safari: + rm -rf "$(SAFARI_DIR)" diff --git a/README.md b/README.md index 3882df0a..86f74aaf 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # JabRef Browser Extension -> [Firefox](https://addons.mozilla.org/en-US/firefox/addon/jabref/?src=external-github) - [Chrome](https://chrome.google.com/webstore/detail/jabref-browser-extension/bifehkofibaamoeaopjglfkddgkijdlh) - [Edge](https://microsoftedge.microsoft.com/addons/detail/pgkajmkfgbehiomipedjhoddkejohfna) - [Vivaldi](https://chrome.google.com/webstore/detail/jabref-browser-extension/bifehkofibaamoeaopjglfkddgkijdlh) +> [Firefox](https://addons.mozilla.org/en-US/firefox/addon/jabref/?src=external-github) - [Chrome](https://chrome.google.com/webstore/detail/jabref-browser-extension/bifehkofibaamoeaopjglfkddgkijdlh) - [Edge](https://microsoftedge.microsoft.com/addons/detail/pgkajmkfgbehiomipedjhoddkejohfna) - [Vivaldi](https://chrome.google.com/webstore/detail/jabref-browser-extension/bifehkofibaamoeaopjglfkddgkijdlh) - Safari (build from source) Browser extension for users of the bibliographic reference manager [JabRef](https://www.jabref.org/). It automatically identifies and extracts bibliographic information on websites and sends them to JabRef with one click. @@ -15,10 +15,60 @@ _Please post any issues or suggestions [here on GitHub](https://github.com/JabRe Normally, you simply install the extension from the browser store and are ready to go. -> [Firefox](https://addons.mozilla.org/en-US/firefox/addon/jabref/?src=external-github) - [Chrome](https://chrome.google.com/webstore/detail/jabref-browser-extension/bifehkofibaamoeaopjglfkddgkijdlh) - [Edge](https://microsoftedge.microsoft.com/addons/detail/pgkajmkfgbehiomipedjhoddkejohfna) - [Vivaldi](https://chrome.google.com/webstore/detail/jabref-browser-extension/bifehkofibaamoeaopjglfkddgkijdlh) +> [Firefox](https://addons.mozilla.org/en-US/firefox/addon/jabref/?src=external-github) - [Chrome](https://chrome.google.com/webstore/detail/jabref-browser-extension/bifehkofibaamoeaopjglfkddgkijdlh) - [Edge](https://microsoftedge.microsoft.com/addons/detail/pgkajmkfgbehiomipedjhoddkejohfna) - [Vivaldi](https://chrome.google.com/webstore/detail/jabref-browser-extension/bifehkofibaamoeaopjglfkddgkijdlh) - Safari (build from source) Sometimes, a manual installation is necessary (e.g. if you use the portable version of JabRef). In this case, please follow the steps described [in the user manual](https://docs.jabref.org/import-export/import/jabref-browser-extension). +Safari builds are available for local development via WXT: + +- `pnpm build:safari` builds the Safari target into `.output/safari-mv3/` +- `pnpm dev:safari` builds the Safari development target + +For the Apple packaging step: + +- `pnpm safari:xcode` builds the Safari target and generates the Xcode project in `dist/safari/` through [`wxt-module-safari-xcode`](https://github.com/rxliuli/wxt-module-safari-xcode) +- `pnpm sign:safari-local IDENTITY="Developer ID Application: Your Name (TEAMID)"` signs the generated app +- `pnpm notarize:safari-local PROFILE="profile-name"` notarizes and zips the signed app + +WXT builds the extension bundle, and `wxt-module-safari-xcode` converts that bundle into the Xcode project and macOS app structure Apple expects. + +To test the Safari build locally: + +1. Run `pnpm safari:xcode` +2. Open `dist/safari/JabRef Browser Extension.xcodeproj` +3. Run the `JabRef Browser Extension` scheme in Xcode +4. Enable the extension in Safari Settings + +The generated macOS app bundle is placed at `dist/safari/JabRef Browser Extension.app`. + +Safari CI is split into two parts: + +1. `Tests` workflow: + - `safari-build` runs on `macos-latest` + - it executes `make safari` + - this validates the WXT build, Xcode packaging, and Safari app bundle path on pull requests and on `main` +2. `release` workflow: + - `safari-package` builds and uploads the unsigned Safari app artifact + - `safari-publish` rebuilds the Xcode project on `macos-26` and publishes it to App Store Connect with [`rxliuli/safari-webext-publish-action`](https://github.com/rxliuli/safari-webext-publish-action) +3. `Safari Signing Test` workflow: + - manual `workflow_dispatch` workflow with a `run_safari_signing_test` checkbox + - it builds the Safari project and runs the App Store signing/publish step without touching the release workflow + +The Safari publish job expects these GitHub Actions secrets: + +- `APPLE_TEAM_ID`: Apple Developer team ID +- `APPLE_CERTIFICATE_BASE64`: base64-encoded `.p12` certificate containing the App Store signing identities +- `APPLE_CERTIFICATE_PASSWORD`: password for that `.p12` +- `SAFARI_APP_SIGNING_IDENTITY`: full app signing identity, for example `Apple Distribution: JabRef e.V. (TEAMID)` +- `SAFARI_INSTALLER_SIGNING_IDENTITY`: full installer signing identity, for example `Mac Installer Distribution: JabRef e.V. (TEAMID)` +- `APPLE_MACOS_PROVISIONING_PROFILE_BASE64`: base64-encoded macOS App Store provisioning profile for the app bundle ID +- `APPLE_MACOS_EXTENSION_PROVISIONING_PROFILE_BASE64`: base64-encoded macOS App Store provisioning profile for the extension bundle ID +- `APPLE_API_KEY`: base64-encoded App Store Connect API key (`.p8`) +- `APPLE_API_KEY_ID`: App Store Connect API key ID +- `APPLE_API_ISSUER`: App Store Connect API issuer ID + +The local `pnpm sign:safari-local` and `pnpm notarize:safari-local` commands still exist for manual Developer ID packaging outside the App Store flow. + ## Usage After the installation, you should be able to import bibliographic references into JabRef directly from your browser. diff --git a/package.json b/package.json index a3315976..35f81f9c 100644 --- a/package.json +++ b/package.json @@ -25,19 +25,27 @@ "dev:chrome": "wxt -b chrome", "dev:opera": "wxt -b opera", "dev:edge": "wxt -b edge", + "dev:safari": "wxt -b safari", "build": "wxt build", "build:firefox": "wxt build -b firefox", + "build:safari": "wxt build -b safari", + "safari:xcode": "wxt build -b safari", + "sign:safari-local": "make sign-safari-local", + "notarize:safari-local": "make notarize-safari-local", "zip": "wxt zip", "zip:firefox": "wxt zip -b firefox", + "zip:safari": "wxt zip -b safari", "compile": "vue-tsc --noEmit", "lint": "pnpm lint:oxlint && pnpm lint:oxfmt", - "lint:oxlint": "oxlint", + "lint:oxlint": "oxlint --ignore-pattern 'sources/**' --ignore-pattern 'translators/zotero/**'", "lint:oxfmt": "oxfmt --check", "test": "vitest run", "test:legacy": "node test.js arXiv.org.js", "postinstall": "wxt prepare" }, "dependencies": { + "esbuild": "^0.28.1", + "spawn-sync": "^2.0.0", "vue": "3.5.35" }, "devDependencies": { @@ -51,7 +59,8 @@ "typescript": "6.0.3", "vitest": "4.1.8", "vue-tsc": "3.3.3", - "wxt": "0.20.26" + "wxt": "0.20.26", + "wxt-module-safari-xcode": "0.2.2" }, "packageManager": "pnpm@10.34.1" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8650b925..bb47db31 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,16 +8,22 @@ importers: .: dependencies: + esbuild: + specifier: ^0.28.1 + version: 0.28.1 + spawn-sync: + specifier: ^2.0.0 + version: 2.0.0 vue: specifier: 3.5.35 version: 3.5.35(typescript@6.0.3) devDependencies: '@tailwindcss/vite': specifier: 4.3.0 - version: 4.3.0(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)) + version: 4.3.0(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)) '@wxt-dev/module-vue': specifier: 1.0.3 - version: 1.0.3(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1))(vue@3.5.35(typescript@6.0.3))(wxt@0.20.26(@types/node@25.6.0)(eslint@9.39.0(jiti@2.6.1))(jiti@2.6.1)(rollup@4.59.0)) + version: 1.0.3(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1))(vue@3.5.35(typescript@6.0.3))(wxt@0.20.26(@types/node@25.6.0)(eslint@9.39.0(jiti@2.6.1))(jiti@2.6.1)(rollup@4.59.0)) globals: specifier: 17.6.0 version: 17.6.0 @@ -38,13 +44,16 @@ importers: version: 6.0.3 vitest: specifier: 4.1.8 - version: 4.1.8(@types/node@25.6.0)(jsdom@29.1.1)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)) + version: 4.1.8(@types/node@25.6.0)(jsdom@29.1.1)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)) vue-tsc: specifier: 3.3.3 version: 3.3.3(typescript@6.0.3) wxt: specifier: 0.20.26 version: 0.20.26(@types/node@25.6.0)(eslint@9.39.0(jiti@2.6.1))(jiti@2.6.1)(rollup@4.59.0) + wxt-module-safari-xcode: + specifier: 0.2.2 + version: 0.2.2(@types/node@25.6.0)(eslint@9.39.0(jiti@2.6.1))(jiti@2.6.1)(rollup@4.59.0) packages: @@ -169,156 +178,312 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.28.1': + resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/android-arm64@0.27.7': resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} engines: {node: '>=18'} cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.28.1': + resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm@0.27.7': resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} engines: {node: '>=18'} cpu: [arm] os: [android] + '@esbuild/android-arm@0.28.1': + resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-x64@0.27.7': resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} engines: {node: '>=18'} cpu: [x64] os: [android] + '@esbuild/android-x64@0.28.1': + resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/darwin-arm64@0.27.7': resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.28.1': + resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-x64@0.27.7': resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} engines: {node: '>=18'} cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.28.1': + resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/freebsd-arm64@0.27.7': resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.28.1': + resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-x64@0.27.7': resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.28.1': + resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/linux-arm64@0.27.7': resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} engines: {node: '>=18'} cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.28.1': + resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm@0.27.7': resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} engines: {node: '>=18'} cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.28.1': + resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-ia32@0.27.7': resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} engines: {node: '>=18'} cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.28.1': + resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-loong64@0.27.7': resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} engines: {node: '>=18'} cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.28.1': + resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-mips64el@0.27.7': resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.28.1': + resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-ppc64@0.27.7': resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.28.1': + resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-riscv64@0.27.7': resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.28.1': + resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-s390x@0.27.7': resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} engines: {node: '>=18'} cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.28.1': + resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-x64@0.27.7': resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} engines: {node: '>=18'} cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.28.1': + resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/netbsd-arm64@0.27.7': resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-arm64@0.28.1': + resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-x64@0.27.7': resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.28.1': + resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/openbsd-arm64@0.27.7': resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-arm64@0.28.1': + resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-x64@0.27.7': resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.28.1': + resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/openharmony-arm64@0.27.7': resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] + '@esbuild/openharmony-arm64@0.28.1': + resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + '@esbuild/sunos-x64@0.27.7': resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} engines: {node: '>=18'} cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.28.1': + resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/win32-arm64@0.27.7': resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} engines: {node: '>=18'} cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.28.1': + resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-ia32@0.27.7': resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} engines: {node: '>=18'} cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.28.1': + resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-x64@0.27.7': resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} engines: {node: '>=18'} cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.28.1': + resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.9.1': resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -1501,6 +1666,11 @@ packages: engines: {node: '>=18'} hasBin: true + esbuild@0.28.1: + resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} + engines: {node: '>=18'} + hasBin: true + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -2446,6 +2616,9 @@ packages: spawn-sync@1.0.15: resolution: {integrity: sha512-9DWBgrgYZzNghseho0JOuh+5fg9u6QWhAWa51QC7+U5rCheZ/j1DrEZnyE0RBBRqZ9uEXGPgSSM0nky6burpVw==} + spawn-sync@2.0.0: + resolution: {integrity: sha512-AGXIhH/XZVinFewojYTsG8uapHX2e7MjtFbmibvK9qqG4qGd9b6jelU1sTkCA0RVGHvN9exJYTBVbF1Ls2f69g==} + split2@4.2.0: resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} engines: {node: '>= 10.x'} @@ -2817,6 +2990,9 @@ packages: resolution: {integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==} engines: {node: '>=20'} + wxt-module-safari-xcode@0.2.2: + resolution: {integrity: sha512-NRywJk/RSv5IO8zql1+mh3Hu6bsRq4ZCfXxE1L5FB376axG+4xW63dVJ+LuGFMVOf4hOJcRc9vQs5ZnWGSDwzA==} + wxt@0.20.26: resolution: {integrity: sha512-PMGz7sAlONJgwBkOriInXOoEU6/jlGKrhSFvZfiBPHZocyYPfnw1lod9rGDra957H83WO+TnGjYwJiGYciSIqA==} engines: {bun: '>=1.2.0', node: '>=20.12.0'} @@ -2868,6 +3044,11 @@ packages: zod@4.4.1: resolution: {integrity: sha512-a6ENMBBGZBsnlSebQ/eKCguSBeGKSf4O7BPnqVPmYGtpBYI7VSqoVqw+QcB7kPRjbqPwhYTpFbVj/RqNz/CT0Q==} + zx@8.8.5: + resolution: {integrity: sha512-SNgDF5L0gfN7FwVOdEFguY3orU5AkfFZm9B5YSHog/UDHv+lvmd82ZAsOenOkQixigwH2+yyH198AwNdKhj+RA==} + engines: {node: '>= 12.17.0'} + hasBin: true + snapshots: '@1natsu/wait-element@4.2.0': @@ -2988,81 +3169,159 @@ snapshots: '@esbuild/aix-ppc64@0.27.7': optional: true + '@esbuild/aix-ppc64@0.28.1': + optional: true + '@esbuild/android-arm64@0.27.7': optional: true + '@esbuild/android-arm64@0.28.1': + optional: true + '@esbuild/android-arm@0.27.7': optional: true + '@esbuild/android-arm@0.28.1': + optional: true + '@esbuild/android-x64@0.27.7': optional: true + '@esbuild/android-x64@0.28.1': + optional: true + '@esbuild/darwin-arm64@0.27.7': optional: true + '@esbuild/darwin-arm64@0.28.1': + optional: true + '@esbuild/darwin-x64@0.27.7': optional: true + '@esbuild/darwin-x64@0.28.1': + optional: true + '@esbuild/freebsd-arm64@0.27.7': optional: true + '@esbuild/freebsd-arm64@0.28.1': + optional: true + '@esbuild/freebsd-x64@0.27.7': optional: true + '@esbuild/freebsd-x64@0.28.1': + optional: true + '@esbuild/linux-arm64@0.27.7': optional: true + '@esbuild/linux-arm64@0.28.1': + optional: true + '@esbuild/linux-arm@0.27.7': optional: true + '@esbuild/linux-arm@0.28.1': + optional: true + '@esbuild/linux-ia32@0.27.7': optional: true + '@esbuild/linux-ia32@0.28.1': + optional: true + '@esbuild/linux-loong64@0.27.7': optional: true + '@esbuild/linux-loong64@0.28.1': + optional: true + '@esbuild/linux-mips64el@0.27.7': optional: true + '@esbuild/linux-mips64el@0.28.1': + optional: true + '@esbuild/linux-ppc64@0.27.7': optional: true + '@esbuild/linux-ppc64@0.28.1': + optional: true + '@esbuild/linux-riscv64@0.27.7': optional: true + '@esbuild/linux-riscv64@0.28.1': + optional: true + '@esbuild/linux-s390x@0.27.7': optional: true + '@esbuild/linux-s390x@0.28.1': + optional: true + '@esbuild/linux-x64@0.27.7': optional: true + '@esbuild/linux-x64@0.28.1': + optional: true + '@esbuild/netbsd-arm64@0.27.7': optional: true + '@esbuild/netbsd-arm64@0.28.1': + optional: true + '@esbuild/netbsd-x64@0.27.7': optional: true + '@esbuild/netbsd-x64@0.28.1': + optional: true + '@esbuild/openbsd-arm64@0.27.7': optional: true + '@esbuild/openbsd-arm64@0.28.1': + optional: true + '@esbuild/openbsd-x64@0.27.7': optional: true + '@esbuild/openbsd-x64@0.28.1': + optional: true + '@esbuild/openharmony-arm64@0.27.7': optional: true + '@esbuild/openharmony-arm64@0.28.1': + optional: true + '@esbuild/sunos-x64@0.27.7': optional: true + '@esbuild/sunos-x64@0.28.1': + optional: true + '@esbuild/win32-arm64@0.27.7': optional: true + '@esbuild/win32-arm64@0.28.1': + optional: true + '@esbuild/win32-ia32@0.27.7': optional: true + '@esbuild/win32-ia32@0.28.1': + optional: true + '@esbuild/win32-x64@0.27.7': optional: true + '@esbuild/win32-x64@0.28.1': + optional: true + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.0(jiti@2.6.1))': dependencies: eslint: 9.39.0(jiti@2.6.1) @@ -3486,12 +3745,12 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.3.0 '@tailwindcss/oxide-win32-x64-msvc': 4.3.0 - '@tailwindcss/vite@4.3.0(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1))': + '@tailwindcss/vite@4.3.0(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1))': dependencies: '@tailwindcss/node': 4.3.0 '@tailwindcss/oxide': 4.3.0 tailwindcss: 4.3.0 - vite: 8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1) + vite: 8.0.10(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1) '@tybys/wasm-util@0.10.1': dependencies: @@ -3524,10 +3783,10 @@ snapshots: dependencies: undici-types: 7.19.2 - '@vitejs/plugin-vue@6.0.5(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1))(vue@3.5.35(typescript@6.0.3))': + '@vitejs/plugin-vue@6.0.5(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1))(vue@3.5.35(typescript@6.0.3))': dependencies: '@rolldown/pluginutils': 1.0.0-rc.2 - vite: 8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1) + vite: 8.0.10(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1) vue: 3.5.35(typescript@6.0.3) '@vitest/expect@4.1.8': @@ -3539,13 +3798,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.8(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1))': + '@vitest/mocker@4.1.8(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1))': dependencies: '@vitest/spy': 4.1.8 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1) + vite: 8.0.10(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1) '@vitest/pretty-format@4.1.8': dependencies: @@ -3662,9 +3921,9 @@ snapshots: '@types/filesystem': 0.0.36 '@types/har-format': 1.2.16 - '@wxt-dev/module-vue@1.0.3(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1))(vue@3.5.35(typescript@6.0.3))(wxt@0.20.26(@types/node@25.6.0)(eslint@9.39.0(jiti@2.6.1))(jiti@2.6.1)(rollup@4.59.0))': + '@wxt-dev/module-vue@1.0.3(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1))(vue@3.5.35(typescript@6.0.3))(wxt@0.20.26(@types/node@25.6.0)(eslint@9.39.0(jiti@2.6.1))(jiti@2.6.1)(rollup@4.59.0))': dependencies: - '@vitejs/plugin-vue': 6.0.5(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1))(vue@3.5.35(typescript@6.0.3)) + '@vitejs/plugin-vue': 6.0.5(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1))(vue@3.5.35(typescript@6.0.3)) wxt: 0.20.26(@types/node@25.6.0)(eslint@9.39.0(jiti@2.6.1))(jiti@2.6.1)(rollup@4.59.0) transitivePeerDependencies: - vite @@ -4039,6 +4298,35 @@ snapshots: '@esbuild/win32-ia32': 0.27.7 '@esbuild/win32-x64': 0.27.7 + esbuild@0.28.1: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.1 + '@esbuild/android-arm': 0.28.1 + '@esbuild/android-arm64': 0.28.1 + '@esbuild/android-x64': 0.28.1 + '@esbuild/darwin-arm64': 0.28.1 + '@esbuild/darwin-x64': 0.28.1 + '@esbuild/freebsd-arm64': 0.28.1 + '@esbuild/freebsd-x64': 0.28.1 + '@esbuild/linux-arm': 0.28.1 + '@esbuild/linux-arm64': 0.28.1 + '@esbuild/linux-ia32': 0.28.1 + '@esbuild/linux-loong64': 0.28.1 + '@esbuild/linux-mips64el': 0.28.1 + '@esbuild/linux-ppc64': 0.28.1 + '@esbuild/linux-riscv64': 0.28.1 + '@esbuild/linux-s390x': 0.28.1 + '@esbuild/linux-x64': 0.28.1 + '@esbuild/netbsd-arm64': 0.28.1 + '@esbuild/netbsd-x64': 0.28.1 + '@esbuild/openbsd-arm64': 0.28.1 + '@esbuild/openbsd-x64': 0.28.1 + '@esbuild/openharmony-arm64': 0.28.1 + '@esbuild/sunos-x64': 0.28.1 + '@esbuild/win32-arm64': 0.28.1 + '@esbuild/win32-ia32': 0.28.1 + '@esbuild/win32-x64': 0.28.1 + escalade@3.2.0: {} escape-goat@4.0.0: {} @@ -5044,6 +5332,8 @@ snapshots: concat-stream: 1.6.2 os-shim: 0.1.3 + spawn-sync@2.0.0: {} + split2@4.2.0: {} split@1.0.1: @@ -5257,10 +5547,23 @@ snapshots: fsevents: 2.3.3 jiti: 2.6.1 - vitest@4.1.8(@types/node@25.6.0)(jsdom@29.1.1)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)): + vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1): + dependencies: + lightningcss: 1.32.0 + picomatch: 4.0.4 + postcss: 8.5.15 + rolldown: 1.0.0-rc.17 + tinyglobby: 0.2.16 + optionalDependencies: + '@types/node': 25.6.0 + esbuild: 0.28.1 + fsevents: 2.3.3 + jiti: 2.6.1 + + vitest@4.1.8(@types/node@25.6.0)(jsdom@29.1.1)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)): dependencies: '@vitest/expect': 4.1.8 - '@vitest/mocker': 4.1.8(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)) + '@vitest/mocker': 4.1.8(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)) '@vitest/pretty-format': 4.1.8 '@vitest/runner': 4.1.8 '@vitest/snapshot': 4.1.8 @@ -5277,7 +5580,7 @@ snapshots: tinyexec: 1.1.2 tinyglobby: 0.2.16 tinyrainbow: 3.1.0 - vite: 8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1) + vite: 8.0.10(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 25.6.0 @@ -5401,6 +5704,29 @@ snapshots: is-wsl: 3.1.1 powershell-utils: 0.1.0 + wxt-module-safari-xcode@0.2.2(@types/node@25.6.0)(eslint@9.39.0(jiti@2.6.1))(jiti@2.6.1)(rollup@4.59.0): + dependencies: + zx: 8.8.5 + optionalDependencies: + wxt: 0.20.26(@types/node@25.6.0)(eslint@9.39.0(jiti@2.6.1))(jiti@2.6.1)(rollup@4.59.0) + transitivePeerDependencies: + - '@types/node' + - '@vitejs/devtools' + - canvas + - eslint + - jiti + - less + - oxc-parser + - rollup + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + wxt@0.20.26(@types/node@25.6.0)(eslint@9.39.0(jiti@2.6.1))(jiti@2.6.1)(rollup@4.59.0): dependencies: '@1natsu/wait-element': 4.2.0 @@ -5500,3 +5826,5 @@ snapshots: jszip: 3.10.1 zod@4.4.1: {} + + zx@8.8.5: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 00000000..37cf25d5 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + esbuild: true + spawn-sync: true diff --git a/scripts/JabRef Browser Extension Extension.entitlements b/scripts/JabRef Browser Extension Extension.entitlements new file mode 100644 index 00000000..ee95ab7e --- /dev/null +++ b/scripts/JabRef Browser Extension Extension.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.network.client + + + diff --git a/scripts/JabRef Browser Extension.entitlements b/scripts/JabRef Browser Extension.entitlements new file mode 100644 index 00000000..ee95ab7e --- /dev/null +++ b/scripts/JabRef Browser Extension.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.network.client + + + diff --git a/scripts/SafariWebExtensionHandler.swift b/scripts/SafariWebExtensionHandler.swift new file mode 100644 index 00000000..f3e2e2fd --- /dev/null +++ b/scripts/SafariWebExtensionHandler.swift @@ -0,0 +1,261 @@ +import Foundation +import SafariServices +import os.log + +private let profileKeyFallback = "profile" +private let messageKeyFallback = "message" +private let responseKeyFallback = "message" +private let nativeBridgeLogTag = "JBE_NATIVE_BRIDGE" + +private enum JabRefBridgeError: LocalizedError { + case jabRefNotFound([String]) + case invalidPayload + case invalidBibTeXPayload + case processLaunchFailed(String) + case processFailed(status: Int32, output: String) + + var errorDescription: String? { + switch self { + case .jabRefNotFound: + return "Could not find JabRef. Please check that it is installed correctly." + case .invalidPayload: + return "Unsupported native message payload." + case .invalidBibTeXPayload: + return "Native message is missing the BibTeX payload." + case .processLaunchFailed(let reason): + return reason + case .processFailed(_, let output): + return output.isEmpty ? "JabRef exited with an error." : output + } + } +} + +private struct JabRefBridge { + func handle(message: Any, profileIdentifier: UUID?) -> [String: Any] { + do { + guard let payload = message as? [String: Any] else { + os_log( + "%{public}@ INVALID_PAYLOAD profile=%{public}@", + log: .default, + type: .error, + nativeBridgeLogTag, + profileIdentifier?.uuidString ?? "none", + ) + throw JabRefBridgeError.invalidPayload + } + + let requestId = (payload["requestId"] as? String) ?? "none" + let payloadKeys = payload.keys.sorted().joined(separator: ",") + os_log( + "%{public}@ BEGIN requestId=%{public}@ profile=%{public}@ keys=%{public}@", + log: .default, + type: .default, + nativeBridgeLogTag, + requestId, + profileIdentifier?.uuidString ?? "none", + payloadKeys, + ) + + if let status = payload["status"] as? String, status == "validate" { + let jabRefURL = try findJabRef() + let output = try runJabRef(at: jabRefURL, arguments: ["--version"]) + return [ + "message": "jarFound", + "output": output, + "requestId": requestId, + ] + } + + guard let bibtex = payload["text"] as? String, !bibtex.isEmpty else { + throw JabRefBridgeError.invalidBibTeXPayload + } + + let jabRefURL = try findJabRef() + let output = try runJabRef(at: jabRefURL, arguments: ["--importBibtex", bibtex]) + return [ + "message": "ok", + "output": output, + "requestId": requestId, + ] + } catch let error as JabRefBridgeError { + return errorResponse(for: error) + } catch { + return [ + "message": "error", + "output": error.localizedDescription, + "stacktrace": String(describing: error), + ] + } + } + + private func errorResponse(for error: JabRefBridgeError) -> [String: Any] { + switch error { + case .jabRefNotFound(let attemptedPaths): + os_log( + "%{public}@ JAR_NOT_FOUND paths=%{public}@", + log: .default, + type: .error, + nativeBridgeLogTag, + attemptedPaths.joined(separator: " | "), + ) + return [ + "message": "jarNotFound", + "path": attemptedPaths.first ?? "", + "output": error.localizedDescription, + "stacktrace": attemptedPaths.joined(separator: "\n"), + ] + case .processFailed(_, let output): + os_log( + "%{public}@ PROCESS_FAILED output=%{public}@", + log: .default, + type: .error, + nativeBridgeLogTag, + output, + ) + return [ + "message": "error", + "output": output, + "stacktrace": error.localizedDescription, + ] + case .processLaunchFailed(let reason): + os_log( + "%{public}@ PROCESS_LAUNCH_FAILED reason=%{public}@", + log: .default, + type: .error, + nativeBridgeLogTag, + reason, + ) + return [ + "message": "error", + "output": reason, + "stacktrace": reason, + ] + default: + os_log( + "%{public}@ ERROR description=%{public}@", + log: .default, + type: .error, + nativeBridgeLogTag, + error.localizedDescription, + ) + return [ + "message": "error", + "output": error.localizedDescription, + "stacktrace": String(describing: error), + ] + } + } + + private func findJabRef() throws -> URL { + let fileManager = FileManager.default + let candidates = [ + "/Applications/JabRef.app/Contents/MacOS/JabRef", + NSHomeDirectory() + "/Applications/JabRef.app/Contents/MacOS/JabRef", + ] + + for candidate in candidates { + if fileManager.isExecutableFile(atPath: candidate) { + os_log( + "%{public}@ FOUND_JABREF path=%{public}@", + log: .default, + type: .default, + nativeBridgeLogTag, + candidate, + ) + return URL(fileURLWithPath: candidate) + } + } + + throw JabRefBridgeError.jabRefNotFound(candidates) + } + + private func runJabRef(at executableURL: URL, arguments: [String]) throws -> String { + os_log( + "%{public}@ RUN path=%{public}@ args=%{public}@", + log: .default, + type: .default, + nativeBridgeLogTag, + executableURL.path, + arguments.joined(separator: " "), + ) + let process = Process() + process.executableURL = executableURL + process.arguments = arguments + + let stdout = Pipe() + let stderr = Pipe() + process.standardOutput = stdout + process.standardError = stderr + + do { + try process.run() + } catch { + throw JabRefBridgeError.processLaunchFailed(error.localizedDescription) + } + + process.waitUntilExit() + + let outputData = stdout.fileHandleForReading.readDataToEndOfFile() + let errorData = stderr.fileHandleForReading.readDataToEndOfFile() + let output = String(data: outputData + errorData, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" + + guard process.terminationStatus == 0 else { + throw JabRefBridgeError.processFailed(status: process.terminationStatus, output: output) + } + + os_log( + "%{public}@ RUN_OK path=%{public}@", + log: .default, + type: .default, + nativeBridgeLogTag, + executableURL.path, + ) + + return output + } +} + +class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling { + private let bridge = JabRefBridge() + + func beginRequest(with context: NSExtensionContext) { + os_log("%{public}@ BEGIN_REQUEST", log: .default, type: .default, nativeBridgeLogTag) + guard let request = context.inputItems.first as? NSExtensionItem, + let userInfo = request.userInfo else { + os_log("%{public}@ MISSING_REQUEST", log: .default, type: .error, nativeBridgeLogTag) + context.completeRequest(returningItems: nil, completionHandler: nil) + return + } + + let profileIdentifier: UUID? + if #available(iOS 17.0, macOS 14.0, *) { + profileIdentifier = userInfo[SFExtensionProfileKey] as? UUID + } else { + profileIdentifier = userInfo[profileKeyFallback] as? UUID + } + + let message: Any? + if #available(iOS 15.0, macOS 11.0, *) { + message = userInfo[SFExtensionMessageKey] + } else { + message = userInfo[messageKeyFallback] + } + + guard let message else { + os_log("%{public}@ MISSING_MESSAGE", log: .default, type: .error, nativeBridgeLogTag) + context.completeRequest(returningItems: nil, completionHandler: nil) + return + } + + let responsePayload = bridge.handle(message: message, profileIdentifier: profileIdentifier) + let response = NSExtensionItem() + if #available(iOS 15.0, macOS 11.0, *) { + response.userInfo = [SFExtensionMessageKey: responsePayload] + } else { + response.userInfo = [responseKeyFallback: responsePayload] + } + + os_log("%{public}@ COMPLETE_REQUEST", log: .default, type: .default, nativeBridgeLogTag) + context.completeRequest(returningItems: [response], completionHandler: nil) + } +} diff --git a/scripts/notarize_safari_local.sh b/scripts/notarize_safari_local.sh new file mode 100755 index 00000000..81688e36 --- /dev/null +++ b/scripts/notarize_safari_local.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +set -e + +PROFILE="$1" +if [ -z "$PROFILE" ]; then + echo "Usage: $0 \"notarytool-profile-name\"" + echo "Create a profile with: xcrun notarytool store-credentials \"profile-name\" --apple-id \"your@apple.id\" --team-id \"TEAMID\" --password \"app-specific-password\"" + exit 1 +fi + +SAFARI_DIR="dist/safari" +APP_NAME="JabRef Browser Extension" +APP_PATH="$SAFARI_DIR/$APP_NAME.app" +ARCHIVE_PATH="$SAFARI_DIR/$APP_NAME.zip" +FINAL_ZIP="$SAFARI_DIR/jabref-browser-extension-safari.zip" + +if [ ! -d "$APP_PATH" ]; then + echo "Error: App bundle not found at $APP_PATH. Run 'make safari' and sign it first." + exit 1 +fi + +if ! codesign -vvv --deep --strict "$APP_PATH"; then + echo "Deep verification failed, retrying without --deep..." + codesign -vvv --strict "$APP_PATH" +fi + +rm -f "$ARCHIVE_PATH" +ditto -c -k --keepParent "$APP_PATH" "$ARCHIVE_PATH" + +xcrun notarytool submit "$ARCHIVE_PATH" --keychain-profile "$PROFILE" --wait +xcrun stapler staple "$APP_PATH" + +rm -f "$FINAL_ZIP" +ditto -c -k --keepParent "$APP_PATH" "$FINAL_ZIP" + +echo "Done! Notarized app is at $APP_PATH" +echo "Distribution zip is at $FINAL_ZIP" diff --git a/scripts/patch_safari_project.mjs b/scripts/patch_safari_project.mjs new file mode 100644 index 00000000..042950dc --- /dev/null +++ b/scripts/patch_safari_project.mjs @@ -0,0 +1,43 @@ +import fs from "node:fs/promises"; +import path from "node:path"; + +const root = process.cwd(); +const projectDir = path.join(root, "dist", "safari"); +const targetSwiftPath = path.join( + projectDir, + "JabRef Browser Extension Extension", + "SafariWebExtensionHandler.swift", +); +const sourceSwiftPath = path.join(root, "scripts", "SafariWebExtensionHandler.swift"); +const pbxprojPath = path.join(projectDir, "JabRef Browser Extension.xcodeproj", "project.pbxproj"); + +const BACKGROUND_HTML_FILE_REF = "9A0F0E0D0C0B0A0908070605"; +const BACKGROUND_HTML_BUILD_FILE = "9A0F0E0D0C0B0A0908070606"; + +await fs.copyFile(sourceSwiftPath, targetSwiftPath); + +let pbxproj = await fs.readFile(pbxprojPath, "utf8"); + +if (!pbxproj.includes("/* background.html */")) { + pbxproj = pbxproj.replace( + "/* End PBXBuildFile section */", + `\t\t${BACKGROUND_HTML_BUILD_FILE} /* background.html in Resources */ = {isa = PBXBuildFile; fileRef = ${BACKGROUND_HTML_FILE_REF} /* background.html */; };\n/* End PBXBuildFile section */`, + ); + + pbxproj = pbxproj.replace( + "/* End PBXFileReference section */", + `\t\t${BACKGROUND_HTML_FILE_REF} /* background.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; name = background.html; path = "../../safari-mv3/background.html"; sourceTree = ""; };\n/* End PBXFileReference section */`, + ); + + pbxproj = pbxproj.replace( + /(\t\t\t\t[A-F0-9]+ \/\* offscreen\.html \*\/,\n)(\t\t\t\t[A-F0-9]+ \/\* background\.js \*\/,)/, + `$1\t\t\t\t${BACKGROUND_HTML_FILE_REF} /* background.html */,\n$2`, + ); + + pbxproj = pbxproj.replace( + /(\t\t\t\t[A-F0-9]+ \/\* offscreen\.html in Resources \*\/,\n)(\t\t\t\t[A-F0-9]+ \/\* popup\.html in Resources \*\/,)/, + `$1\t\t\t\t${BACKGROUND_HTML_BUILD_FILE} /* background.html in Resources */,\n$2`, + ); + + await fs.writeFile(pbxprojPath, pbxproj); +} diff --git a/scripts/prepare_safari_bundle.mjs b/scripts/prepare_safari_bundle.mjs new file mode 100644 index 00000000..5cef010f --- /dev/null +++ b/scripts/prepare_safari_bundle.mjs @@ -0,0 +1,40 @@ +import fs from "node:fs/promises"; +import path from "node:path"; + +const root = process.cwd(); +const sourceDir = path.join(root, ".output", "safari-mv3"); +const targetDir = path.join(root, "dist", "safari-mv3"); + +await fs.rm(targetDir, { recursive: true, force: true }); +await fs.cp(sourceDir, targetDir, { recursive: true }); + +const manifestPath = path.join(targetDir, "manifest.json"); +const manifest = JSON.parse(await fs.readFile(manifestPath, "utf8")); + +delete manifest.key; +delete manifest.browser_specific_settings; +delete manifest.content_scripts; +if (Array.isArray(manifest.permissions)) { + manifest.permissions = manifest.permissions.filter((permission) => permission !== "offscreen"); +} + +const backgroundHtmlPath = path.join(targetDir, "background.html"); +const backgroundHtml = ` + + + + Background + + + + + +`; + +await fs.writeFile(backgroundHtmlPath, backgroundHtml); + +manifest.background = { + page: "background.html", +}; + +await fs.writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`); diff --git a/scripts/sign_safari_local.sh b/scripts/sign_safari_local.sh new file mode 100755 index 00000000..7c943c48 --- /dev/null +++ b/scripts/sign_safari_local.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +set -e + +IDENTITY="$1" +if [ -z "$IDENTITY" ]; then + echo "Usage: $0 \"Developer ID Application: Your Name (ID)\"" + exit 1 +fi + +SAFARI_DIR="dist/safari" +APP_NAME="JabRef Browser Extension" +APP_PATH="$SAFARI_DIR/$APP_NAME.app" + +if [ ! -d "$APP_PATH" ]; then + echo "Error: App bundle not found at $APP_PATH. Run 'make safari' first." + exit 1 +fi + +echo "Cleaning up generated cruft..." +find "$APP_PATH" -name ".DS_Store" -delete || true +find "$APP_PATH" -name "__pycache__" -type d -exec rm -rf {} + || true +find "$APP_PATH" -name "build" -type d -exec rm -rf {} + || true +find "$APP_PATH" -name "*.md" -delete || true + +echo "Signing native binaries..." +find "$APP_PATH" -type f \( -name "*.dylib" -o -name "*.node" -o -name "*.so" \) -exec codesign --force --options runtime --sign "$IDENTITY" --timestamp --verbose=4 {} \; + +EXTENSION_PATH=$(find "$APP_PATH" -name "*.appex" | head -n 1) +if [ -n "$EXTENSION_PATH" ]; then + EXTENSION_EXE=$(find "$EXTENSION_PATH" -path "*/Contents/MacOS/*" -type f | head -n 1) + if [ -n "$EXTENSION_EXE" ]; then + codesign --force --options runtime --entitlements "scripts/JabRef Browser Extension Extension.entitlements" --sign "$IDENTITY" --timestamp --verbose=4 "$EXTENSION_EXE" + fi + codesign --force --options runtime --entitlements "scripts/JabRef Browser Extension Extension.entitlements" --sign "$IDENTITY" --timestamp --verbose=4 "$EXTENSION_PATH" +fi + +MAIN_EXE="$APP_PATH/Contents/MacOS/$APP_NAME" +if [ -f "$MAIN_EXE" ]; then + codesign --force --options runtime --entitlements "scripts/JabRef Browser Extension.entitlements" --sign "$IDENTITY" --timestamp --verbose=4 "$MAIN_EXE" +fi + +codesign --force --options runtime --entitlements "scripts/JabRef Browser Extension.entitlements" --sign "$IDENTITY" --timestamp --verbose=4 "$APP_PATH" + +if ! codesign -vvv --deep --strict "$APP_PATH"; then + echo "Deep verification failed, retrying without --deep..." + codesign -vvv --strict "$APP_PATH" +fi + +echo "Done! Signed app is at $APP_PATH" diff --git a/src/entrypoints/background.ts b/src/entrypoints/background.ts index fda1a49a..77de5e53 100644 --- a/src/entrypoints/background.ts +++ b/src/entrypoints/background.ts @@ -7,6 +7,7 @@ export default defineBackground({ type: "module", main() { var tabInfo = new Map(); + const BIB_EXPORT_TIMEOUT_MS = 5000; /* Show/hide import button for all tabs (when add-on is loaded). @@ -145,6 +146,9 @@ export default defineBackground({ async function sendBibEntryHttp(bibtex) { const baseUrl = await getBaseUrl(); + await browser.runtime.sendMessage({ + popupLog: `Trying JabRef HTTP endpoint at ${baseUrl}`, + }); const health = await fetch(baseUrl, { method: "GET", cache: "no-store" }); if (!(health.ok || health.status === 404)) { @@ -161,13 +165,23 @@ export default defineBackground({ const body = await resp.text().catch(() => ""); throw new Error(`HTTP ${resp.status}${body ? `: ${body}` : ""}`); } + + await browser.runtime.sendMessage({ + popupLog: "JabRef accepted data over HTTP", + }); } async function sendBibEntryNative(bibtex) { + await browser.runtime.sendMessage({ + popupLog: "Trying native messaging to reach JabRef", + }); const response = await browser.runtime.sendNativeMessage("org.jabref.jabref", { text: bibtex, }); if (response?.message === "ok") { + await browser.runtime.sendMessage({ + popupLog: "JabRef accepted data over native messaging", + }); return; } @@ -190,13 +204,22 @@ export default defineBackground({ try { await sendBibEntryHttp(bibtex); + await browser.runtime.sendMessage({ + popupLog: "Send to JabRef finished", + }); await browser.runtime.sendMessage({ popupClose: "close" }); return; } catch (httpError) { console.warn("JabRef: HTTP send failed, falling back to native messaging", httpError); + await browser.runtime.sendMessage({ + popupLog: `HTTP send failed, falling back to native messaging: ${httpError}`, + }); } await sendBibEntryNative(bibtex); + await browser.runtime.sendMessage({ + popupLog: "Send to JabRef finished", + }); await browser.runtime.sendMessage({ popupClose: "close" }); } @@ -306,6 +329,18 @@ export default defineBackground({ return cfg.exportMode || "bibtex"; } + async function raceWithTimeout(promise, timeoutMs, label) { + return Promise.race([ + promise, + new Promise((_, reject) => { + setTimeout( + () => reject(new Error(`${label} timed out after ${timeoutMs} ms`)), + timeoutMs, + ); + }), + ]); + } + async function prepareForExport(items) { const { takeSnapshots } = await browser.storage.sync.get({ takeSnapshots: false }); @@ -387,11 +422,24 @@ export default defineBackground({ return; } const { url, items } = message; + await browser.runtime.sendMessage({ + popupLog: `Translator returned ${items.length} item(s) for ${url}`, + }); const conversionMode = await getConversionMode(); await prepareForExport(items); await browser.runtime.sendMessage({ onConvertToBibtex: "convertStarted" }); - const bib = await exportItems(items, conversionMode); + await browser.runtime.sendMessage({ + popupLog: `Starting BibTeX export in background for ${items.length} item(s)`, + }); + const bib = await raceWithTimeout( + exportItems(items, conversionMode), + BIB_EXPORT_TIMEOUT_MS, + "BibTeX export", + ); console.debug("JabRef: Exported BibTeX: %o", bib); + await browser.runtime.sendMessage({ + popupLog: `BibTeX export finished using mode ${conversionMode}`, + }); await sendBibTexToJabRef(bib); } else if (message.eval) { console.debug( @@ -411,6 +459,11 @@ export default defineBackground({ } } catch (e) { console.error("JabRef: Error handling message in background.js", e); + try { + await browser.runtime.sendMessage({ + popupLog: `Background error: ${e instanceof Error ? e.message : String(e)}`, + }); + } catch {} throw e; } }); diff --git a/src/entrypoints/content/index.js b/src/entrypoints/content/index.js index fc21230c..8252dbbe 100644 --- a/src/entrypoints/content/index.js +++ b/src/entrypoints/content/index.js @@ -6,6 +6,11 @@ export default defineContentScript({ matches: [], async main() { + if (globalThis.__JABREF_CONTENT_SCRIPT_INITIALIZED__) { + console.debug("[contentScript] already initialized"); + return; + } + globalThis.__JABREF_CONTENT_SCRIPT_INITIALIZED__ = true; console.debug("[contentScript] started"); browser.runtime.onMessage.addListener(async (msg, _sender, _sendResponse) => { @@ -76,7 +81,17 @@ export default defineContentScript({ ); const result = await translateEngine.translate(document, translators); console.debug("Content script obtained translation result %o", result); - await browser.runtime.sendMessage({ type: "offscreenResult", url, items: result.items }); + console.debug( + "Content script sending offscreenResult with %o item(s) for %o", + result.items?.length ?? 0, + url, + ); + const response = await browser.runtime.sendMessage({ + type: "offscreenResult", + url, + items: result.items, + }); + console.debug("Content script received background ack for offscreenResult %o", response); }); }, }); diff --git a/src/entrypoints/options/index.html b/src/entrypoints/options/index.html index e5c3f361..4e2c68d2 100644 --- a/src/entrypoints/options/index.html +++ b/src/entrypoints/options/index.html @@ -86,6 +86,18 @@ Connection Status Testing connection... + + + Diagnostics + + + Native messaging probe + + + Run test + No test run yet. + +
No test run yet.