diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 5355e8ce..6fc167e2 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -12,7 +12,7 @@ - [ ] My pull request is programmed entirely or mostly with AI. - [ ] My pull request only partially uses AI. \ - **If the above is applicable, I have used AI for:** + **If the above is applicable, I have used AI for:** - [ ] I have only used AI for documentation of Firefox APIs or similar. **No code was written with it.** - [ ] My pull request does not use any AI. diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bdc8a4ae..3ce286cb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,14 +30,12 @@ jobs: - os: windows arch: x64 runner: windows-latest - generator: "Visual Studio 17 2022" vs_arch: x64 triplet: x64-windows-static - os: windows arch: arm64 runner: windows-11-arm - generator: "Visual Studio 17 2022" vs_arch: ARM64 triplet: arm64-windows-static @@ -69,7 +67,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -77,18 +75,47 @@ jobs: run: | git clone --recurse-submodules https://github.com/sineorg/installer.git external - # ---------- Linux OpenGL / X11 ---------- - - name: Install OpenGL dependencies (Linux) + # ---------- Linux OpenGL / Wayland / X11 ---------- + - name: Install GUI dependencies (Linux) if: matrix.os == 'linux' run: | sudo apt-get update sudo apt-get install -y \ + pkg-config \ libgl1-mesa-dev \ + libegl1-mesa-dev \ libx11-dev \ libxrandr-dev \ libxinerama-dev \ libxcursor-dev \ - libxi-dev + libxi-dev \ + libxext-dev \ + libxfixes-dev \ + libwayland-dev \ + libwayland-bin \ + wayland-protocols \ + libxkbcommon-dev \ + libxkbcommon-x11-dev \ + extra-cmake-modules + + - name: Enable Wayland feature for GLFW (Linux) + if: matrix.os == 'linux' + run: | + python3 -c " + import json + with open('external/vcpkg.json', 'r') as f: + data = json.load(f) + for i, dep in enumerate(data.get('dependencies', [])): + if dep == 'glfw3': + data['dependencies'][i] = {'name': 'glfw3', 'features': ['wayland']} + elif isinstance(dep, dict) and dep.get('name') == 'glfw3': + features = dep.get('features', []) + if 'wayland' not in features: + features.append('wayland') + dep['features'] = features + with open('external/vcpkg.json', 'w') as f: + json.dump(data, f, indent=2) + " # ---------- Ninja ---------- - name: Install Ninja (Linux) @@ -98,6 +125,13 @@ jobs: sudo apt-get install -y ninja-build # ---------- vcpkg ---------- + - name: Adjust config for macOS + if: matrix.os == 'macos' + run: | + git config --global http.lowSpeedLimit 1000 + git config --global http.lowSpeedTime 300 + git config --global http.postBuffer 524288000 + - name: Setup vcpkg uses: lukka/run-vcpkg@b1a0dd252f06b9e25b3c022a9a03bd7a427fb6a2 # v11.6 with: @@ -110,7 +144,6 @@ jobs: if: matrix.os == 'windows' run: | cmake -S external -B build ` - -G "$env:GENERATOR" ` -A "$env:ARCH" ` -DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_ROOT\scripts\buildsystems\vcpkg.cmake" ` -DVCPKG_TARGET_TRIPLET="$env:TRIPLET" ` @@ -118,7 +151,6 @@ jobs: -DBOOT_VERSION="$env:BOOT_VERSION" env: VCPKG_ROOT: ${{ github.workspace }}\vcpkg - GENERATOR: ${{ matrix.generator }} ARCH: ${{ matrix.vs_arch }} TRIPLET: ${{ matrix.triplet }} SINE_VERSION: ${{ inputs.sine_version }} @@ -129,8 +161,12 @@ jobs: run: | cmake -S external -B build \ -G "${GENERATOR}" \ + -DCMAKE_TOOLCHAIN_FILE="${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" \ + -DVCPKG_TARGET_TRIPLET="${VCPKG_DEFAULT_TRIPLET}" \ -DSINE_VERSION=${SINE_VERSION} \ - -DBOOT_VERSION=${BOOT_VERSION} + -DBOOT_VERSION=${BOOT_VERSION} \ + -DGLFW_BUILD_WAYLAND=ON \ + -DGLFW_BUILD_X11=ON env: VCPKG_ROOT: ${{ github.workspace }}/vcpkg VCPKG_DEFAULT_TRIPLET: ${{ matrix.triplet }} @@ -224,3 +260,75 @@ jobs: with: name: sine-osx-${{ matrix.arch }} path: artifacts/sine-osx-${{ matrix.arch }} + + post-build: + runs-on: ubuntu-latest + needs: build + name: Handle post-build steps + steps: + - name: Check signing requirements + id: status + env: + REF_NAME: ${{ github.ref_name }} + run: | + if [[ "$REF_NAME" == "main" ]]; then + echo "should-sign=yes" >> $GITHUB_OUTPUT + else + echo "should-sign=no" >> $GITHUB_OUTPUT + fi + + - name: Download all artifacts + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + path: ./artifacts + + - name: Attest artifacts + uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2 + with: + subject-path: | + ./artifacts/sine-linux-*/* + ./artifacts/sine-osx-*/* + ./artifacts/sine-win-*-unsigned/* + + - name: Prepare SignPath Workspace + if: steps.status.outputs.should-sign == 'yes' + run: | + mkdir -p ./unsigned + find ./artifacts/sine-win-*-unsigned/ -type f -exec cp {} ./unsigned/ \; + + - name: Upload Windows artifacts (for code signing) + if: steps.status.outputs.should-sign == 'yes' + id: upload-unsigned-zip + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: sine-package-for-signing + path: ./unsigned + + - name: Submit signing request + if: steps.status.outputs.should-sign == 'yes' + id: signpath-code-signing + uses: signpath/github-action-submit-signing-request@c92b958760219087e01f8d67a1669ed57afe2627 # v2.3 + with: + api-token: "${{ secrets.SIGNPATH_API_TOKEN }}" + organization-id: "1324b51a-cb3d-4d1e-aab7-27b1169bd402" + project-slug: "Sine" + signing-policy-slug: "release-signing" + github-artifact-id: "${{ steps.upload-unsigned-zip.outputs.artifact-id }}" + wait-for-completion: false + + - name: Save code signing info + if: steps.status.outputs.should-sign == 'yes' + env: + SIGNING_REQUEST_ID: ${{ steps.signpath-code-signing.outputs.signing-request-id }} + SIGNING_REQUEST_WEB_URL: ${{ steps.signpath-code-signing.outputs.signing-request-web-url }} + run: | + mkdir -p artifacts/info + echo "$SIGNING_REQUEST_ID" | tee artifacts/info/signing-request-id.txt + echo "$SIGNING_REQUEST_WEB_URL" | tee artifacts/info/signing-request-web-url.txt + + - name: Upload Artifact (signing info) + if: steps.status.outputs.should-sign == 'yes' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: info + path: ./artifacts/info diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 9c800eb2..1e7779a2 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -60,7 +60,7 @@ jobs: # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages steps: - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -72,7 +72,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0 + uses: github/codeql-action/init@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -100,6 +100,6 @@ jobs: exit 1 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0 + uses: github/codeql-action/analyze@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/pr-test.yml b/.github/workflows/pr-test.yml index cd621308..9b7d019e 100644 --- a/.github/workflows/pr-test.yml +++ b/.github/workflows/pr-test.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -26,7 +26,7 @@ jobs: bun-version: latest - name: Set up uv - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 + uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 # Run ci script to install bun and uv deps - name: Install dependencies @@ -45,19 +45,19 @@ jobs: security-events: write steps: - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - name: Run zizmor - uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6 + uses: zizmorcore/zizmor-action@3dc1ecc9bcb9e94e9b2c709687979e1298497054 # v0.6.2 test: name: Run tests runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -73,4 +73,7 @@ jobs: run: bunx browsercfg select --browser zen-twilight --download - name: Run tests - run: bun run test + run: | + # Disable mozilla sandbox to prevent hanging process + export MOZ_DISABLE_CONTENT_SANDBOX=1 + bun run test diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 46931478..5a2345fe 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,6 +11,15 @@ on: type: number description: "Version type (0: normal, 1: abnormal, 2: bootloader, see docs/updates.md)" required: true + run_id: + description: "GitHub Actions run_id when created the corresponding release" + required: false + type: string + publish_draft: + description: "If true, will publish draft release" + required: false + default: false + type: boolean permissions: id-token: write @@ -25,7 +34,7 @@ jobs: version: ${{ steps.format_inputs.outputs.sine_version }} steps: - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -101,7 +110,7 @@ jobs: VERSION_TYPE: ${{ inputs.version_type }} - name: Set up uv - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 + uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 - name: Generate packages run: | @@ -109,7 +118,7 @@ jobs: mv engine.zip artifacts/engine.zip - name: Attest artifacts - uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0 + uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2 with: subject-path: artifacts/* @@ -121,10 +130,20 @@ jobs: inputs: '{ "sine_version": "${{ steps.format_inputs.outputs.sine_version }}", "boot_version": "${{ steps.format_inputs.outputs.boot_version }}" }' wait-for-completion: true + - name: Trigger upload workflow + if: steps.extract_branch.outputs.suffix != 'c' + id: sign_job + uses: benc-uk/workflow-dispatch@31e2b3319479a63f0ab15bf800eff9e913504e26 # v1.3.2 + with: + workflow: upload-signed-files.yml + inputs: '{ "run_id": "${{ inputs.run_id }}" }' + wait-for-completion: true + - name: Download artifacts env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - RUN_ID: ${{ steps.build_job.outputs.runId }} + RUN_ID: ${{ steps.build_job.outputs.runId || steps.sign_job.outputs.runId }} + INPUT_RUN_ID: ${{ inputs.run_id }} run: | gh run download $RUN_ID -D build/ @@ -138,12 +157,22 @@ jobs: filename=$(basename "$file") foldername=$(basename "$folder") - if [ "$filename" = "$foldername" ]; then - extension="${filename##*.}" - tempname="${foldername}_file.$extension" - mv "$file" "artifacts/$tempname" - rmdir "$folder" - mv "artifacts/$tempname" "artifacts/$filename" + if [[ "$filename" == *"-unsigned.exe" ]]; then + new_name="${file/-unsigned/}" + mv "$file" "$new_name" + file="$new_name" + filename=$(basename "$file") + fi + + if [[ "$filename" == "engine.zip" || "$filename" == "updater.bat" || "$filename" == "updater.sh" ]]; then + echo "Preserving core asset: artifacts/$filename" + continue + fi + + if [[ "$filename" =~ ^sine-(linux|osx|win)- ]]; then + echo "Verifying attestation for $filename..." + gh attestation verify "$file" --repo CosmoCreeper/Sine || echo "Warning: Attestation verification failed for $filename" + echo "Processed 'artifacts/$filename'" else mv "$file" artifacts/ rmdir "$folder" @@ -178,12 +207,12 @@ jobs: deploy: needs: build-and-upload - if: success() + if: success() && inputs.publish_draft == true runs-on: ubuntu-latest environment: production steps: - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false diff --git a/.github/workflows/sync-locales.yml b/.github/workflows/sync-locales.yml index e8867f1f..09474a23 100644 --- a/.github/workflows/sync-locales.yml +++ b/.github/workflows/sync-locales.yml @@ -15,12 +15,12 @@ jobs: steps: - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - name: crowdin action - uses: crowdin/github-action@8868a33591d21088edfc398968173a3b98d51706 # v2.16.2 + uses: crowdin/github-action@8f01d54f70f1713ee3f09d82c2bbb2daeac28689 # v2.17.1 with: upload_sources: true upload_translations: false diff --git a/.github/workflows/upload-signed-files.yml b/.github/workflows/upload-signed-files.yml new file mode 100644 index 00000000..1bfd613b --- /dev/null +++ b/.github/workflows/upload-signed-files.yml @@ -0,0 +1,91 @@ +name: Upload signed executables + +on: + workflow_dispatch: + inputs: + run_id: + description: "GitHub Actions run_id when created the corresponding release" + required: true + type: string + +permissions: + id-token: write + attestations: write + +jobs: + download: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Download build info + shell: bash + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RUN_ID: ${{ inputs.run_id }} + run: gh run download "${RUN_ID}" --name "info" --dir info + + - name: Download signed files + shell: bash + timeout-minutes: 10 + env: + SIGNPATH_API_TOKEN: ${{ secrets.SIGNPATH_API_TOKEN }} + ORGANIZATION_ID: 1324b51a-cb3d-4d1e-aab7-27b1169bd402 + run: | + SIGNING_REQUEST_ID=$(cat info/signing-request-id.txt) + REQUEST_URL=https://app.signpath.io/API/v1/${ORGANIZATION_ID}/SigningRequests/${SIGNING_REQUEST_ID} + + # Check SignPath status + status=InProgress + while [ "$status" = "InProgress" ]; do + status=$(curl -f --silent -H "Authorization: Bearer ${SIGNPATH_API_TOKEN}" ${REQUEST_URL} \ + | jq -r .status) + + echo "SignPath status: $status" + case "$status" in + WaitingForApproval) + echo "Not approved yet. Approve it first:" + cat info/signing-request-web-url.txt + exit 1 + ;; + InProgress) + ;; + Completed) + break + ;; + *) + exit 1 + ;; + esac + + sleep 10 + done + + # Download the signed files + curl -f -H "Authorization: Bearer ${SIGNPATH_API_TOKEN}" \ + -o package.zip \ + ${REQUEST_URL}/SignedArtifact + unzip package.zip -d package + + rm -f package/*_pdb.* + + for i in package/sine-win-*-unsigned.exe; do + [ -f "$i" ] || continue + mv "$i" "${i/-unsigned/}" + done + + ls -l package + + - name: Upload signed executables + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: signed-package + path: | + ./package/* + + - name: Attest + uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2 + with: + subject-path: ./package/* diff --git a/.oxfmtrc.json b/.oxfmtrc.json index 8f98d570..a70a1412 100644 --- a/.oxfmtrc.json +++ b/.oxfmtrc.json @@ -1,5 +1,6 @@ { "$schema": "./node_modules/oxfmt/configuration_schema.json", "trailingComma": "es5", + "jsdoc": true, "ignorePatterns": ["src/assets/imports/"] } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5de943a0..24f95582 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,47 @@ # Contributing to Sine -If you wish to contribute to Sine, you can do so by forking this repository, making your changes, and creating a pull request to the **cosine** branch here. Do not make changes to the main branch as that's where we merge changes after they've been tested and verified on the cosine one. +We invite all users, whether unskilled or skilled in this region of programming, to contribute to Sine. We are a small team, mostly comprised of [@CosmoCreeper](https://github.com/CosmoCreeper) and a few other members. If you are found to be interested and skilled in contributing to Sine, you may be added to our official team. -- Please use our formatting configuration to format the files first. -- Please test your code before creating a pull request. If your pull request adds a feature or improves something that the user would be able to notice, please share a screen recording of the actual feature in your pull request. -- Please ensure that your pull request does not add any AI features and is not heavily vibecoded, as we are attempting to push against using AI for production development in our code. +## Setting up a developer environment + +Before you can write production-level code for Sine that runs in the browser, you must first configure your developer environment. + +### Prerequisites + +- **Windows**: [Git Bash](https://git-scm.com/install/windows) +- **Linux/macOS**: [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) +- [Bun](https://bun.sh) +- [uv](https://docs.astral.sh/uv/) + +### Steps + +If you're on Windows, open Git Bash. If you're on Linux/macOS, open your terminal (must have git installed). This is where we will perform all the actions required to get your developer environment set up. + +Navigate to the folder you want to place your Sine code clone into (you can use the `cd` command to do so). Now paste these commands into your terminal and press enter: + +```sh +git clone https://github.com/CosmoCreeper/Sine.git sine +cd sine +``` + +Now you have to use Bun to initialize the project's dependencies: + +```sh +bun run init +``` + +And now you can contribute in any way you like! + +Below are some of our common scripts for developing. + +```sh +bun lint # Lints all files +bun lint:fix # Fixes linter errors where possible + +bun fmt:check # Checks formatting +bun fmt # Formats files + +bun run test # Runs test suite + +bun dev # Executes code in an isolated browser env +``` diff --git a/README.md b/README.md index 49e1ac4f..488f34cd 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ That’s it! Sine should now be installed and ready to use. Sine boasts a powerful suite of easy-to-use tools for everyone, technical, or non-technical. Let's look through some of these features:

πŸ›’ A built-in marketplace.

- + Sine has a marketplace that is built-in to the settings gui for easy access. This marketplace is where the user adds and views Sine-compatible mods. **Am I limited to this settings gui?:** Absolutely not. Sine provides support for installing mods from [our website](https://sineorg.github.io/store/) too. @@ -71,7 +71,7 @@ Sine has a marketplace that is built-in to the settings gui for easy access. Thi

πŸ’» Easy to publish and update your mods.

- + Every time a pull request gets published to a repository, it seems like it just adds to the stack of never-ending overflow. Sine makes this process simple. All you have to do is make an issue with a template. Assuming your project is already Sine-compatible, it'll work just fine. Plus, the developers of Sine are active enough to handle your pull requests quickly. **What about updating?:** Sine does not require update requests and pulls them straight from your repository. This means that you will never have to worry about github issues being outdated or have to tell your user to update to the latest version. @@ -79,7 +79,7 @@ Every time a pull request gets published to a repository, it seems like it just

πŸš€ Test mods in a snap.

- + Sine makes the process of adding unpublished mods easy as long as they have a valid mod format. You simply type in the name of the repository (folder if needed) and Sine handles the rest.
@@ -107,6 +107,7 @@ Sine is designed to be highly compatible and as such, it offers support for user - [Discord](https://discord.gg/P76BvB2MXS) - [Reddit](https://reddit.com/r/sine_mods) - [x.com](https://x.com/sine_mods) +- [sineorg](https://github.com/sineorg) - [Documentation](https://github.com/sineorg/docs) - Marketplace: [Site](https://sineorg.github.io/store) | [Repository](https://github.com/sineorg/store) - [Releases](https://github.com/CosmoCreeper/Sine/releases) diff --git a/bun.lock b/bun.lock index bc9a62d1..286e7657 100644 --- a/bun.lock +++ b/bun.lock @@ -7,26 +7,26 @@ "devDependencies": { "@chromejs/browsercfg": "^0.1.8", "@types/bun": "latest", - "oxfmt": "^0.52.0", - "oxlint": "^1.67.0", - "oxlint-plugin-eslint": "^1.68.0", - "svgo": "^4.0.1", + "eslint-plugin-jsdoc": "^64.2.1", + "oxfmt": "^0.65.0", + "oxlint": "^1.80.0", + "oxlint-plugin-eslint": "^1.80.0", + "svgo": "^4.1.0", }, }, "tools/oxlint-plugin-mozilla": { "name": "mozilla", "version": "4.4.0", "dependencies": { - "@typescript-eslint/parser": "^8.60.1", - "eslint-scope": "^8.4.0", - "eslint-visitor-keys": "^4.2.1", + "@typescript-eslint/parser": "^8.67.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", "estraverse": "^5.3.0", - "htmlparser2": "^10.0.0", - "toml-eslint-parser": "1.0.0", + "htmlparser2": "^12.0.0", + "toml-eslint-parser": "1.0.3", }, "devDependencies": { - "eslint": "9.39.1", - "mocha": "11.7.5", + "eslint": "^10.9.0", }, }, }, @@ -49,23 +49,23 @@ "@chromejs/browsercfg-win32-x64": ["@chromejs/browsercfg-win32-x64@0.1.8", "", { "os": "win32", "cpu": "x64" }, "sha512-kAt23theSTylg3PdiHIngboLH7HmUSS8eck3TmiHyamv3H2W7Iw/xYQHsSMHltrsVpNvW87C5h+Z3SSygwgONg=="], - "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="], + "@es-joy/jsdoccomment": ["@es-joy/jsdoccomment@0.95.1", "", { "dependencies": { "@types/estree": "^1.0.9", "@typescript-eslint/types": "^8.67.0", "comment-parser": "1.4.8", "esquery": "^1.7.0", "jsdoc-type-pratt-parser": "~9.1.2" } }, "sha512-LO/RI08Fo9bhXwB7Od9G+1j3eSNq63+ZS5CQO8YLXHbDg6kx6S/DhTeY0+Fc9uZrjK1zZSyTx8Sg5gv5DIoCnA=="], - "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], + "@es-joy/resolve.exports": ["@es-joy/resolve.exports@1.2.0", "", {}, "sha512-Q9hjxWI5xBM+qW2enxfe8wDKdFWMfd0Z29k5ZJnuBqD/CasY5Zryj09aCA6owbGATWz+39p5uIdaHXpopOcG8g=="], - "@eslint/config-array": ["@eslint/config-array@0.21.2", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.5" } }, "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw=="], + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="], - "@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="], + "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], - "@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="], + "@eslint/config-array": ["@eslint/config-array@0.23.5", "", { "dependencies": { "@eslint/object-schema": "^3.0.5", "debug": "^4.3.1", "minimatch": "^10.2.4" } }, "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA=="], - "@eslint/eslintrc": ["@eslint/eslintrc@3.3.5", "", { "dependencies": { "ajv": "^6.14.0", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" } }, "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg=="], + "@eslint/config-helpers": ["@eslint/config-helpers@0.7.0", "", { "dependencies": { "@eslint/core": "^1.2.1" } }, "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw=="], - "@eslint/js": ["@eslint/js@9.39.1", "", {}, "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw=="], + "@eslint/core": ["@eslint/core@1.2.1", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ=="], - "@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="], + "@eslint/object-schema": ["@eslint/object-schema@3.0.5", "", {}, "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw=="], - "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="], + "@eslint/plugin-kit": ["@eslint/plugin-kit@0.7.2", "", { "dependencies": { "@eslint/core": "^1.2.1", "levn": "^0.4.1" } }, "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A=="], "@humanfs/core": ["@humanfs/core@0.19.2", "", { "dependencies": { "@humanfs/types": "^0.15.0" } }, "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA=="], @@ -77,191 +77,163 @@ "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], - "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], + "@oxfmt/binding-android-arm-eabi": ["@oxfmt/binding-android-arm-eabi@0.65.0", "", { "os": "android", "cpu": "arm" }, "sha512-M10Gs1SSpTNI6ahGx3M/OlIdUF4hkaP6OgUb+MS79t/Pgflk3r1nW5gPFqsZGUAXg0H1AfANT9AvLdBSTIhZKg=="], - "@oxfmt/binding-android-arm-eabi": ["@oxfmt/binding-android-arm-eabi@0.52.0", "", { "os": "android", "cpu": "arm" }, "sha512-17EMSJnQ9g+upVHrAUYDMfH5lvRKQ9Nvg8WtEoH72oDr1VpWz+7/o3tD97U1EToen2YAQ/68JmtDYkQUi20dfQ=="], + "@oxfmt/binding-android-arm64": ["@oxfmt/binding-android-arm64@0.65.0", "", { "os": "android", "cpu": "arm64" }, "sha512-6DXH5sftNlaHpWJG50hFMF+Qxtq5D2TmahvcDPxWNcGIf8qrC9Y0YgHYcYZ2hlWzaccKXh/f3GcssH8vtkl4JA=="], - "@oxfmt/binding-android-arm64": ["@oxfmt/binding-android-arm64@0.52.0", "", { "os": "android", "cpu": "arm64" }, "sha512-A2G1IdwGEW2lLJkIxcvuirRH1CzSl/e0NX11zTlW1gvxJThfwbI/BEoaKrTNpm7M2FchvIf6guvIQU7d5iz+OQ=="], + "@oxfmt/binding-darwin-arm64": ["@oxfmt/binding-darwin-arm64@0.65.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-K9m7lr53pcOLETNsC88sWes/GWHUGjZyHx95UhYcSXy0r30haLdeXlSufSenEAtoLaW753WN8/l4M7GYcRt6cg=="], - "@oxfmt/binding-darwin-arm64": ["@oxfmt/binding-darwin-arm64@0.52.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-f9+bLvOYxy7NttCLFTvQ7afmqDOWY4wIP9xdvfj5trQ1qj6f2UFAGwZESlfsMjvJNTyRpXfIlOanCI9FOvoeQA=="], + "@oxfmt/binding-darwin-x64": ["@oxfmt/binding-darwin-x64@0.65.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-sTNwIx1gre3MyiHOPLu7IGW4UyMScYL4DTmJT01p4vzB0En+OJUQz6KuH8t0PpsClRSaMuY3b0QmtoPItfO8Lg=="], - "@oxfmt/binding-darwin-x64": ["@oxfmt/binding-darwin-x64@0.52.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-YSTB9sJ5nnQd/Q0ddHkgof0ZCHPAnWZT1IW2SJ8omz7CP7KluJhO1fNHrpqdxCtpztJwSs4hY1uAee35wKxxaw=="], + "@oxfmt/binding-freebsd-x64": ["@oxfmt/binding-freebsd-x64@0.65.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-lYZMVIiIpnjGu5hJb2jxA8NYQ/e0OTGuaiAf4dqlGPNnPmUTu23FZRMltmjro/KkQm1uE4NT4n5yJ2zWmKcpfA=="], - "@oxfmt/binding-freebsd-x64": ["@oxfmt/binding-freebsd-x64@0.52.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-NIrRNTTPCs4UbmVs0bxLSCDlLCtIRMJIXklNKaXa5Oj2/K1UIMBvgE8+uPVo01Io3N9HF0+GAX+aAHjUgZS7vA=="], + "@oxfmt/binding-linux-arm-gnueabihf": ["@oxfmt/binding-linux-arm-gnueabihf@0.65.0", "", { "os": "linux", "cpu": "arm" }, "sha512-gIdXFAt/bURnjxuoedDEWdZ0PEWEmdDcm8qdpoFYYvW3QMk/5D4vUaH4mlMeRpeTdST4izUgHVO6RawQ4QulJw=="], - "@oxfmt/binding-linux-arm-gnueabihf": ["@oxfmt/binding-linux-arm-gnueabihf@0.52.0", "", { "os": "linux", "cpu": "arm" }, "sha512-JXUCde8mn3GpgQouz2PXUokgy/uT1QrRJBL2s983VWcSQp62wTFYiNXgTKdeo1Jgbr0IgUnKKvzIk/YBlj/nVQ=="], + "@oxfmt/binding-linux-arm-musleabihf": ["@oxfmt/binding-linux-arm-musleabihf@0.65.0", "", { "os": "linux", "cpu": "arm" }, "sha512-jJVyADto7gA2AaX5qAjAexrxx9PJQaKWOe8PICE7yKMbjBRyOHcmj9TtVJ+MZYDUQ3hodU0AcoTj0jFQ1W4C6Q=="], - "@oxfmt/binding-linux-arm-musleabihf": ["@oxfmt/binding-linux-arm-musleabihf@0.52.0", "", { "os": "linux", "cpu": "arm" }, "sha512-psbUXaRZ+V8DaXz10Qf7LSHtdtdKAmC8fxXgeU608jjzrmWK4quamZMOpl6sf+dikoFHA85uE93Q0BqxrCdQrQ=="], + "@oxfmt/binding-linux-arm64-gnu": ["@oxfmt/binding-linux-arm64-gnu@0.65.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-p3RFkB+u7u+8up99b/NEcI1hdpLDiGgJYNwDorB60n7eH+eKposAKuMBxx+NqB3b+sJP4CZmYDh9G7X62tUsKg=="], - "@oxfmt/binding-linux-arm64-gnu": ["@oxfmt/binding-linux-arm64-gnu@0.52.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-Jw7MgWUU9lcLCcy82updISP3EthTlfvAwR6gWNxPzqly7+fLvOi2gHQE9xXQjpqaVLm/8P+gOzlv9ODuoVlaaw=="], + "@oxfmt/binding-linux-arm64-musl": ["@oxfmt/binding-linux-arm64-musl@0.65.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-5Prb0uFzJHr+OUD/qS/TmU526wD+PaHDsm3KoRiUXbMIDpTSErjeQYkK3OQeshAvD/PuLa9WGEi9WPajjdOZJg=="], - "@oxfmt/binding-linux-arm64-musl": ["@oxfmt/binding-linux-arm64-musl@0.52.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-wZg6bLjDvh2KibyI3QFUYo8GTXneIFsd0JvehtvJiUmQ8WRPERgxd/VM4ctWb86U5FT1FkqgS8/wZKVB+AZScg=="], + "@oxfmt/binding-linux-ppc64-gnu": ["@oxfmt/binding-linux-ppc64-gnu@0.65.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-S8svxTp81obnF3admN9yd+u2rOYXtyzThLGBTg1PY6TPtGcC09BaaXLQD+TBSMa7yvqhCDZ8DFri+S/yG60qCg=="], - "@oxfmt/binding-linux-ppc64-gnu": ["@oxfmt/binding-linux-ppc64-gnu@0.52.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-IngE8uxhNvxcMrLjZNDo9xNLY7rEK33AKnaMd2B46he1e/mz2CfcW6If/U1wUjdRZddm1QzQaciqZkuMkdh1FA=="], + "@oxfmt/binding-linux-riscv64-gnu": ["@oxfmt/binding-linux-riscv64-gnu@0.65.0", "", { "os": "linux", "cpu": "none" }, "sha512-WtXBr75G/h2qOHy8SiGtC1R6aS3jt4mE52v1D8AtwMXIgoOmSNP9lKvbSaTRoL0e5wsMPoi6T72QWDYPu+S+nA=="], - "@oxfmt/binding-linux-riscv64-gnu": ["@oxfmt/binding-linux-riscv64-gnu@0.52.0", "", { "os": "linux", "cpu": "none" }, "sha512-H3+DdFMv/efN3Efmhsv18jDrpiWWqKG7wsfAlQBqAt6z/E2Bx+TwEj2Nowe51CPOWB8/mFBC2dAMSgVFLvvowA=="], + "@oxfmt/binding-linux-riscv64-musl": ["@oxfmt/binding-linux-riscv64-musl@0.65.0", "", { "os": "linux", "cpu": "none" }, "sha512-YwSLVvpaz4o/nv/miiPEBJz+eJ+VmbgNIrao6RccK9ce+L5EA8wP+ZD0uFeq6wKOza6zoWv/dR0sj6lip6R3EA=="], - "@oxfmt/binding-linux-riscv64-musl": ["@oxfmt/binding-linux-riscv64-musl@0.52.0", "", { "os": "linux", "cpu": "none" }, "sha512-zji+1kb7lJKohSDjzC1IsS+K/cKRs1hdVf0ZH0VbdbiakmtLvN9twBoXo/k8VdjFax7kfo+DyPxS7vv52br1aw=="], + "@oxfmt/binding-linux-s390x-gnu": ["@oxfmt/binding-linux-s390x-gnu@0.65.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-XQTPqgvyrgkKcFq+Tp2eK6JS7sqqJ+nRmy2Fav4j3I+i4dJoPJm7YwEdoeSDX9xkqj9jZ/lWfF3bXUWztIrn6A=="], - "@oxfmt/binding-linux-s390x-gnu": ["@oxfmt/binding-linux-s390x-gnu@0.52.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-hcLBYedpCy7ToUvvBidWk7+11Yhg1oAZ4+6hKPic/mQI6NaqXJSXMps5nFlwUuX2ewhtLZZDPg63TI042qGKBg=="], + "@oxfmt/binding-linux-x64-gnu": ["@oxfmt/binding-linux-x64-gnu@0.65.0", "", { "os": "linux", "cpu": "x64" }, "sha512-cjZlx6S/VkeCNWCbwZriTnLnZeTcV3DEyeRGSw/2wwLP9viq+C0bJ4bC1k/ZLkFxDcB1lUgSasPkYGP1bdraOg=="], - "@oxfmt/binding-linux-x64-gnu": ["@oxfmt/binding-linux-x64-gnu@0.52.0", "", { "os": "linux", "cpu": "x64" }, "sha512-IDO2loXK2OtTOhSPchU9MW25mWL2QCDGdJbjN8MXKZVS80qXe5gMTwQWu/gMJ3juoBHbkuUZNB2N1LHzNT7DoA=="], + "@oxfmt/binding-linux-x64-musl": ["@oxfmt/binding-linux-x64-musl@0.65.0", "", { "os": "linux", "cpu": "x64" }, "sha512-2azCjxdLtK4zCcIOU1dlXlU0xxfbPi6EjwWx7Ac7teWPidIIDOcIhudup83xNCKYhtqeVd/gaVDOxbUq4syXWA=="], - "@oxfmt/binding-linux-x64-musl": ["@oxfmt/binding-linux-x64-musl@0.52.0", "", { "os": "linux", "cpu": "x64" }, "sha512-mAV2Hjn0SatJ+KoAzKUC3eJhdJ8wv+3m1KyuS0dTsbF0c5weq+QrCt/DRZZM+uj/XiKzCDEUKYsBF30e2qkcyw=="], + "@oxfmt/binding-openharmony-arm64": ["@oxfmt/binding-openharmony-arm64@0.65.0", "", { "os": "none", "cpu": "arm64" }, "sha512-KXQ7xi1e/voP0IQaw6fG6XY4Z5+Llf1XmRSZS1t7pVFCecFJ0iXaboKmVwjFtp5MLlT5iWQrJ2U1C3GJdZ2u+Q=="], - "@oxfmt/binding-openharmony-arm64": ["@oxfmt/binding-openharmony-arm64@0.52.0", "", { "os": "none", "cpu": "arm64" }, "sha512-vd4npaUIwChxp7XzkqmepBWTT9YMcSe/NBApVGPC30/lLyOVaV3dvma1SKo03t8O73BPRAG7EyJzGlN5cJM5hQ=="], + "@oxfmt/binding-win32-arm64-msvc": ["@oxfmt/binding-win32-arm64-msvc@0.65.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-2FbbjG5jEqLSLKVJwBap84uJfpn5Y5A53KEO0aUNr+zeiRB9nyPUIFMcSbZVMFLitfBytFWRNngozXYjb6Rsbw=="], - "@oxfmt/binding-win32-arm64-msvc": ["@oxfmt/binding-win32-arm64-msvc@0.52.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-k2sz6gWQdMfh5HPpIS+Bw/0UEV/kaK2xuqJRrWL233sEHx9WLlsmvlPFM4HUNThkYbSN0U0vPW7LVKZWDS8hPQ=="], + "@oxfmt/binding-win32-ia32-msvc": ["@oxfmt/binding-win32-ia32-msvc@0.65.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-LJ+ZacAPSjegDOnSLyA1TMWAhdDrsK4el3REdr1oL2UtVBCMhO2II/Sb3cEW6mF2MfLhl8hDNCSvc7KSbgk3LQ=="], - "@oxfmt/binding-win32-ia32-msvc": ["@oxfmt/binding-win32-ia32-msvc@0.52.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-rhke69GTcArodLHpjMTfNnvjTEBryDeZcUCKK/VjXDMtfTULl6QRh0ymX5/hbCUv2WjYm9h/QbW++q2vE15gWQ=="], + "@oxfmt/binding-win32-x64-msvc": ["@oxfmt/binding-win32-x64-msvc@0.65.0", "", { "os": "win32", "cpu": "x64" }, "sha512-higu9cWEO6XXFzATD1jf0mCK34rNfN2H9JrJie7QB1IhleVpTh0QlLH9Ip2C1H/Nd5n0v5pvRtC+5R0uE4HpVg=="], - "@oxfmt/binding-win32-x64-msvc": ["@oxfmt/binding-win32-x64-msvc@0.52.0", "", { "os": "win32", "cpu": "x64" }, "sha512-q5xL7oeXkZdEtNZWBdvehJcmt+GRu9l2bK40yJs1jJXlqq+r0Hygb1rTjq+FM2o/2xyt4cufH6KRplHp3Jjsvw=="], + "@oxlint/binding-android-arm-eabi": ["@oxlint/binding-android-arm-eabi@1.80.0", "", { "os": "android", "cpu": "arm" }, "sha512-RM3Plj+biQpxa5d1GOOX6ciDlcUROmm4OZ/pLTpitkQt2mJv4jhtY4cbgaetOm5UKWZe05/TGQ6o1Vl8EOHkrA=="], - "@oxlint/binding-android-arm-eabi": ["@oxlint/binding-android-arm-eabi@1.67.0", "", { "os": "android", "cpu": "arm" }, "sha512-VrSi571rDv1N8HaEDM+DEX8nmT0y9jJo8tzzW13vsOWTx59xQczCIJx68n2zWOXRT5YKZsOZXp4qkHN/10x4mw=="], + "@oxlint/binding-android-arm64": ["@oxlint/binding-android-arm64@1.80.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YlO5JEf0Yr2bUUlu8O8daVcUxtcGGbcSmyV7E7nSbJbfAdxTE0PFPwgnIlw7wXJaTYjb+qs5hI5q3jxUkI7cAw=="], - "@oxlint/binding-android-arm64": ["@oxlint/binding-android-arm64@1.67.0", "", { "os": "android", "cpu": "arm64" }, "sha512-l6+NdYxMoRohix5r5bbigW16LPicceCwGcQ6LKKuE1kUdjgFfQolJjrJsQYPFetIs78Gxj/G/f5TEGoTCwj9nQ=="], + "@oxlint/binding-darwin-arm64": ["@oxlint/binding-darwin-arm64@1.80.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-BULDOyO3AhsmdWfQeIUCykDt3dd7XZBGLhp1eIh56skRv01O+cNjNPwXMIbeW1x4+pxcln5if72wcRgViVo7PA=="], - "@oxlint/binding-darwin-arm64": ["@oxlint/binding-darwin-arm64@1.67.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-jOzXxS1AxFxhImLIRbtGIMrEwaXcgMw3gR57WB1cRk8ai+vpr6726kxXqVvlNsrXtJ/FrmOm8RxlC0m8SW24Qg=="], + "@oxlint/binding-darwin-x64": ["@oxlint/binding-darwin-x64@1.80.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-YJ4JzLw7N5TDSQFlA0hAQGHvnDZgyypm1yunObVWcWiF9KM7eGCJKYKLgTC2Fi/57OdnBhbj4OkzPGdFQJ6HyA=="], - "@oxlint/binding-darwin-x64": ["@oxlint/binding-darwin-x64@1.67.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-3DFAVY94OqjIZHXIPz37yGRSWwOFTAqChQ64/M69GYLawzP0KiwdhDNfqdKKYT0bTR/DNxmMnQsj3ns+8+X/Lg=="], + "@oxlint/binding-freebsd-x64": ["@oxlint/binding-freebsd-x64@1.80.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-AYUIk5QnL0s8oWAYsREZwkRYy1SupJTXALo93J1TgzHywxQtdM99FecRMQ87MXEdPQ0j1TmEpeeq3fGNkpvMqg=="], - "@oxlint/binding-freebsd-x64": ["@oxlint/binding-freebsd-x64@1.67.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-e4dDKZuLu8TR9DEBssWSDahlPgZBwojTTHZUvnjBRJfJJbpxYCjfjKfi0Z1+CSLMiJBwI2yCDtRM1XJQaARjmg=="], + "@oxlint/binding-linux-arm-gnueabihf": ["@oxlint/binding-linux-arm-gnueabihf@1.80.0", "", { "os": "linux", "cpu": "arm" }, "sha512-9hBZVANupQ89W9dXyE0n8doCyaW5pDyGn3y6XlIMPZ+rIKuyqkr3SNUXmVJIhuvUq0NBU3RBiSXXE69l4XI6KA=="], - "@oxlint/binding-linux-arm-gnueabihf": ["@oxlint/binding-linux-arm-gnueabihf@1.67.0", "", { "os": "linux", "cpu": "arm" }, "sha512-BKytFdcQzbITV3xlnzDUDTEDtbUMCCiC4EaNTDZ4FyT8gdNvBC4gfiLucXp/sQl0XU3p7syTlorUWVVVBZab2g=="], + "@oxlint/binding-linux-arm-musleabihf": ["@oxlint/binding-linux-arm-musleabihf@1.80.0", "", { "os": "linux", "cpu": "arm" }, "sha512-SvS2uKqzY+pbfuvAHzH4338R6Zwo805GAwrIMVvK1KxoOWCIjZUdfzTCvilD7z6JK91v011+zYMryabhDo2AsQ=="], - "@oxlint/binding-linux-arm-musleabihf": ["@oxlint/binding-linux-arm-musleabihf@1.67.0", "", { "os": "linux", "cpu": "arm" }, "sha512-XYAv0esBDX7BpTzRDjVX2Vdj+zndd8ll2dFQiaeQ6zTZr7A8GRDTN7fH3FP3jU+O0vCDx85oH/EtG7BzPgAXuw=="], + "@oxlint/binding-linux-arm64-gnu": ["@oxlint/binding-linux-arm64-gnu@1.80.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-tCLadyqRVL3pQTRPNg7cjXKvcvS4fbyXeQHhKk5BTJ1oftQln5/yIIWbu/Xom/DX41zv2P9QGt6+D/TtQVtY3A=="], - "@oxlint/binding-linux-arm64-gnu": ["@oxlint/binding-linux-arm64-gnu@1.67.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-zizRMjA0i6u/2B0evgda04iycu+MoNuf1pBy6Eh+1CjC5wMEG7qN5zdDKTCvFc0KSYSDM9QTG3gjZHirgtQuKg=="], + "@oxlint/binding-linux-arm64-musl": ["@oxlint/binding-linux-arm64-musl@1.80.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-XfpCNRlOPcLlJl4Bn/FUhjqlR6BVavEykERBf/MV7YA9VZDa5g5znVqYhyviMafcxS9Pe/i/kPvHNO0U6svEHQ=="], - "@oxlint/binding-linux-arm64-musl": ["@oxlint/binding-linux-arm64-musl@1.67.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-zB/Tf6sUjmmvvbva9Gj3JTJ8rJ9t4I8/U0o6vSRtd0DRIsIuyegBwJAzhSUFQHdMijIRJkW0exs/yBhpw2S20w=="], + "@oxlint/binding-linux-ppc64-gnu": ["@oxlint/binding-linux-ppc64-gnu@1.80.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-3I4yMwcFG9NeO8ioY6JBBuKsIm5GL/x7MATt1S4tVWaxPu5HcJ+XnLUbcVBTxG8q2Wu56HSj+NmXQiVYb1lp6A=="], - "@oxlint/binding-linux-ppc64-gnu": ["@oxlint/binding-linux-ppc64-gnu@1.67.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-kgU40Gt74CK0TCsF51KZymkIwN9U0BajKsMijB52zPqOeZU9NAHkA/NSQkZDHEaCakx42DxhXkODiAqf2b4Gug=="], + "@oxlint/binding-linux-riscv64-gnu": ["@oxlint/binding-linux-riscv64-gnu@1.80.0", "", { "os": "linux", "cpu": "none" }, "sha512-E1wAKymkpe1/E8helzBKdm81OBOF+ezxRyXRMEuik3ZpWDER5CPOKZwF66RsdwW98uwZv8UTFremUQtC1CzdJA=="], - "@oxlint/binding-linux-riscv64-gnu": ["@oxlint/binding-linux-riscv64-gnu@1.67.0", "", { "os": "linux", "cpu": "none" }, "sha512-tOYhkk/iaG9aD3FvGpBFd1Lrw0x0RaVoJBxjUkfNzS50rC5NS5BteNCwgr8A2zCdADrIIoze6D7u6U5Ic++/iQ=="], + "@oxlint/binding-linux-riscv64-musl": ["@oxlint/binding-linux-riscv64-musl@1.80.0", "", { "os": "linux", "cpu": "none" }, "sha512-+gLRGD4sIo3+VA++iham5UxD9tKSoJ/VOrROCEXIcknrYtQg6iIQgvjN0cpiRF7N6UYC7pJbvHJlDnMge5LRpQ=="], - "@oxlint/binding-linux-riscv64-musl": ["@oxlint/binding-linux-riscv64-musl@1.67.0", "", { "os": "linux", "cpu": "none" }, "sha512-sEtywrPb+0b+tHYl1SDCrw903fiC4eyKoNqzP3v+f2JT3Xcv4NEYG+P8rj+eEnX7IWhqV/xj8/JmcmVj21CXaA=="], + "@oxlint/binding-linux-s390x-gnu": ["@oxlint/binding-linux-s390x-gnu@1.80.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-aR0PrzHj9leW3NmzBAAP4EzdoBNoJcs9sjnIQPIwyRnBGYrRbXUIpEB5Q39AqK3PLY5JK5uEhDQDiUa1QSAstw=="], - "@oxlint/binding-linux-s390x-gnu": ["@oxlint/binding-linux-s390x-gnu@1.67.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-BvR8Moa0zCLxroOx4vZaZN9nUfwAUpSTwjZdxZyKy4bv3PrzrXrxKR/ZQ0L9wNSvlPhnMJeZfa3q5w6ZCTuN6Q=="], + "@oxlint/binding-linux-x64-gnu": ["@oxlint/binding-linux-x64-gnu@1.80.0", "", { "os": "linux", "cpu": "x64" }, "sha512-vSVh5cSo3Xxs6ghBCcFJlpbkbENzDog1qXtoXLa/HC3aCrR4XO76GZbXmQoCPHnu99nQpdCeC3H9tdNICfDh7A=="], - "@oxlint/binding-linux-x64-gnu": ["@oxlint/binding-linux-x64-gnu@1.67.0", "", { "os": "linux", "cpu": "x64" }, "sha512-mm2cxM6fksOpq6l0uFws8BUGKAR4dNa/cZCn37Npq7PFbhD5HDJqWfnoIvTaeRKMy5XdS2tO0MA0qbHDrnXAAA=="], + "@oxlint/binding-linux-x64-musl": ["@oxlint/binding-linux-x64-musl@1.80.0", "", { "os": "linux", "cpu": "x64" }, "sha512-FfzBXpNQ8u7/ZI/p8bl73MeZ508Ax3hxWp3SiJpEFiC+BB9XcXy5FAZHTLKDPSzrUpxQZSZJAVdDmuJp/+HDBQ=="], - "@oxlint/binding-linux-x64-musl": ["@oxlint/binding-linux-x64-musl@1.67.0", "", { "os": "linux", "cpu": "x64" }, "sha512-WmbMuLapKyDlobMkXAaAL0Y+Uczh4LETfIfQsUpbId4Ip8Ai82/jqeYTOoUCkuuhBFapgqP253+d83tLKOksJg=="], + "@oxlint/binding-openharmony-arm64": ["@oxlint/binding-openharmony-arm64@1.80.0", "", { "os": "none", "cpu": "arm64" }, "sha512-zMzbkumtmprCgRwoYNzcB3iC39fXdJIMLMU33KdCjEGLlJGOEt1+LwQ4LF8ndLzAEKVz4BR0y3V6Xrkk3Nm3yA=="], - "@oxlint/binding-openharmony-arm64": ["@oxlint/binding-openharmony-arm64@1.67.0", "", { "os": "none", "cpu": "arm64" }, "sha512-9g/PqxYJelzzTAOR5Y+RiRqdeydhEuXv2KxNeFcAKQ7UsvnWSY1OP4MsuPMbTO2Pf70tz7mFhl1j13H3fyh+8g=="], + "@oxlint/binding-win32-arm64-msvc": ["@oxlint/binding-win32-arm64-msvc@1.80.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-ib6iRcrXsk4t1fm3iKcwksyWh1ZkZXC/2mEzakl0ai2+6HZunf1WWMZ/xP9EJAvw9g9K4UVTC3NF/+G2qLrbTQ=="], - "@oxlint/binding-win32-arm64-msvc": ["@oxlint/binding-win32-arm64-msvc@1.67.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-2VhwE6Gatb0vJGnN0TBuQMbKCOiZlSQ/zJvVWYLK4a9d4iDiJOen/yVQkGpmsJ90MuH66fzi0kEKI0jRQMDxGA=="], + "@oxlint/binding-win32-ia32-msvc": ["@oxlint/binding-win32-ia32-msvc@1.80.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-xhRWBMpLxZvgKAH6+DJZmpP+W8Y8UdQOSU1JfxSWNXsaBaRGW77j+1hCuNHlzj7OH4SPN8fYd1q0o2qrDtoVyw=="], - "@oxlint/binding-win32-ia32-msvc": ["@oxlint/binding-win32-ia32-msvc@1.67.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-EQ3VExXfeM1InbE5+JjufhZZTWy+kHUwgt3yZR7gQ47Je/mE0WspQPan0OJznh493L5anM210YNJtH1PXjTSFg=="], + "@oxlint/binding-win32-x64-msvc": ["@oxlint/binding-win32-x64-msvc@1.80.0", "", { "os": "win32", "cpu": "x64" }, "sha512-yAnO7lwBYQnz2pcfBPIGQQZWIX5zd5R/1aAKIF3oE+TVj7IhoHcROjOkz3sRDngzqhfPKfFaXqug5j5rE5dn6Q=="], - "@oxlint/binding-win32-x64-msvc": ["@oxlint/binding-win32-x64-msvc@1.67.0", "", { "os": "win32", "cpu": "x64" }, "sha512-bw24y+/1MHS4QDkons3YyHkPT9uCMoLHHgQhb+mb8NOjTYwub1CZ+K9Ngr8aO5DMrDrkqHwTzlTwFP2vS8Y/ZQ=="], + "@sindresorhus/base62": ["@sindresorhus/base62@1.0.0", "", {}, "sha512-TeheYy0ILzBEI/CO55CP6zJCSdSWeRtGnHy8U8dWSUH4I68iqTsy7HkMktR4xakThc9jotkPQUXT4ITdbV7cHA=="], - "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], + "@types/bun": ["@types/bun@1.4.0", "", { "dependencies": { "bun-types": "1.4.0" } }, "sha512-K+lZULY23vRgK/CfTjFIV+tyifaNdSMlPh9j+6mQ/cLfpOznLyAuzgV/JQysyECpkBQLVMSyvjlr2fBUSA9wFQ=="], - "@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="], + "@types/esrecurse": ["@types/esrecurse@4.3.1", "", {}, "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw=="], "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], - "@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="], + "@types/node": ["@types/node@25.9.3", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg=="], - "@typescript-eslint/parser": ["@typescript-eslint/parser@8.60.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.60.1", "@typescript-eslint/types": "8.60.1", "@typescript-eslint/typescript-estree": "8.60.1", "@typescript-eslint/visitor-keys": "8.60.1", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-A0M6ua6H252bVjPvvtSgl2QA4+ET9S5Mtkb2GDyTxIhH/C4qDItT7RQNO5PhMC6NXGYXOR9dIalcDDgBKT7oFA=="], + "@typescript-eslint/parser": ["@typescript-eslint/parser@8.67.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.67.0", "@typescript-eslint/types": "8.67.0", "@typescript-eslint/typescript-estree": "8.67.0", "@typescript-eslint/visitor-keys": "8.67.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-fUBfTuuEulWqX6V8+O3PtScV01tzYYRUDTAirHFKoRAt7nOzoGiPt0M/bB47wWNy0coOOcgEwAMUtBpykMxl6w=="], - "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.60.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.60.1", "@typescript-eslint/types": "^8.60.1", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-eXkTH2bxmXlqD1RnOPmLZ9ZM9D3VwSx04JOwBnP9RQ+yUA5a2Mu7SfW8uaV2Aon53NJzZlZYuX7tn91Izf+xaw=="], + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.67.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.67.0", "@typescript-eslint/types": "^8.67.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-cvE8c7ulYeXN9fYuszhCeCsbzyVEXuhrRCybnBre7TUmqb5nRmBfQAwCj0O3WJFDeyAZt4VYv51vMCC9LHSdYw=="], - "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.60.1", "", { "dependencies": { "@typescript-eslint/types": "8.60.1", "@typescript-eslint/visitor-keys": "8.60.1" } }, "sha512-gvI5OQoptnxQnchOirukCuQ55svJSTuD/4k5+pC267xyBtYry748R9/c3tYUzb/iE6RZfllRz2lVulLCHkTm4w=="], + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.67.0", "", { "dependencies": { "@typescript-eslint/types": "8.67.0", "@typescript-eslint/visitor-keys": "8.67.0" } }, "sha512-EgvsleTwS4E+WzzSvem8fAUubLwatMNF1B5hHSLQxcvs7q2dtRhGyujHwLJSYlG41niJ7GP24Aha2+0mb1b2kg=="], - "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.60.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-nh8w4qAteiKuZu3pSSzG/yGKpw0OlkrKnzFmbVRenKaD4qc+7i1GrmZaLVkr8rk4uipiPGMOW4YsM6WmKZ5CvA=="], + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.67.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-vV+LUSv5njUWsknE71fqKTlXUva+R76SaeORd6Zojcunk/6DvKFXONU3BrAs2H49mbygUXt6gbYunzwqNwlhdg=="], - "@typescript-eslint/types": ["@typescript-eslint/types@8.60.1", "", {}, "sha512-4h0tY8ppCkdCzcrl2YM5M3my0xsE1Tf8om3owEu5oPWmXwkKRmk0j0LGDzYBGUcAlesEbxBhazqu/K4cu3Ug7w=="], + "@typescript-eslint/types": ["@typescript-eslint/types@8.67.0", "", {}, "sha512-sBtgslww8nsMYUjhdPBiSyUqSzT8uR6g93A2QXnQC8+cGdjz0CyaOdqHDRJb1AtORbZCNUJBBeFA/tNR2uQmww=="], - "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.60.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.60.1", "@typescript-eslint/tsconfig-utils": "8.60.1", "@typescript-eslint/types": "8.60.1", "@typescript-eslint/visitor-keys": "8.60.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-alpRkfG8hlVE5kdJW2GkfgDgXxold3e8e4l6EnmhRmRLbekgAPCCGDVD++sABy9FcgPFroq+uFcCSM1vR57Cew=="], + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.67.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.67.0", "@typescript-eslint/tsconfig-utils": "8.67.0", "@typescript-eslint/types": "8.67.0", "@typescript-eslint/visitor-keys": "8.67.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-EKQBCE9yNlRJYm7jdTW5AhDacDUmSwQb0FAJAmK2EKYrNXIsa2vxcSZx6PvJ/dEdI6lS+Y9W+EXckLj0iPFGcw=="], - "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.60.1", "", { "dependencies": { "@typescript-eslint/types": "8.60.1", "eslint-visitor-keys": "^5.0.0" } }, "sha512-EbGRQg4FhrmwLodl+t3JNAnXHWVr9Vp+Zl1QBZVPY4ByfkzIT8cX3K6QWODHtkIZqqJVEWvhHSx3v5PDHsaQag=="], + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.67.0", "", { "dependencies": { "@typescript-eslint/types": "8.67.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-fkv8dHRDqfGtTHuJeebdrQ7cX6Ad4WAS00rgHh9UGvMycF1mjBfsxry1XsLIFhWZ6Judlh6UdzK+TYlbpCXgnA=="], - "acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], + "acorn": ["acorn@8.17.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg=="], "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], "ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="], - "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "are-docs-informative": ["are-docs-informative@0.1.1", "", {}, "sha512-sqRsNQBwbKLRX0jV5Cu5uzmtflf892n4Vukz7T659ebL4pz3mpOqCMU7lxMoBTFwnp10E3YB5ZcyHM41W5bcDA=="], - "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], - - "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], - - "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="], - "brace-expansion": ["brace-expansion@1.1.14", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g=="], - - "browser-stdout": ["browser-stdout@1.3.1", "", {}, "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw=="], - - "bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="], + "brace-expansion": ["brace-expansion@5.0.6", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g=="], - "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], - - "camelcase": ["camelcase@6.3.0", "", {}, "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="], - - "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], - - "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], - - "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], - - "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], - - "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + "bun-types": ["bun-types@1.4.0", "", { "dependencies": { "@types/node": "*" } }, "sha512-iIKw23BspnQQYd3prITOBxeUsxBHnwzX6YJfGMuNOZzeNcMmVqzIIVGRm1l69ogaPQmb4wB6BN8mA5bE9YuC5Q=="], "commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="], - "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + "comment-parser": ["comment-parser@1.4.8", "", {}, "sha512-rKZTGo4fzKYna8UcL0isTg5wkBNla7bxTypLwZQXjIdi++IdP1OJ41rI5Mti3/jltkPujbu4i9LIARYA+zpotQ=="], "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], - "css-select": ["css-select@5.2.2", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw=="], + "css-select": ["css-select@6.0.0", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^7.0.0", "domhandler": "^5.0.3", "domutils": "^3.2.2", "nth-check": "^2.1.1" } }, "sha512-rZZVSLle8v0+EY8QAkDWrKhpgt6SA5OtHsgBnsj6ZaLb5dmDVOWUDtQitd9ydxxvEjhewNudS6eTVU7uOyzvXw=="], "css-tree": ["css-tree@3.2.1", "", { "dependencies": { "mdn-data": "2.27.1", "source-map-js": "^1.2.1" } }, "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA=="], - "css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="], + "css-what": ["css-what@7.0.0", "", {}, "sha512-wD5oz5xibMOPHzy13CyGmogB3phdvcDaB5t0W/Nr5Z2O/agcB8YwOz6e2Lsp10pNDzBoDO9nVa3RGs/2BttpHQ=="], "csso": ["csso@5.0.5", "", { "dependencies": { "css-tree": "~2.2.0" } }, "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ=="], "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], - "decamelize": ["decamelize@4.0.0", "", {}, "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ=="], - "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], - "diff": ["diff@7.0.0", "", {}, "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw=="], - - "dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="], + "dom-serializer": ["dom-serializer@3.1.1", "", { "dependencies": { "domelementtype": "^3.0.0", "domhandler": "^6.0.0", "entities": "^8.0.0" } }, "sha512-4MEa38/QexBob6gFNwu+EGdWvhJ1OKuNwdYY3Y3NyeWDQfnGeDYQUDfIRzWu5B5gsv03so2Uxd28YC6zrsx3Lw=="], - "domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="], + "domelementtype": ["domelementtype@3.0.0", "", {}, "sha512-umCQid3jKbDmVjx8jGaW7uUykm4DEUeyV21hPxNMo2nV955DhUThwqyOIDtreepP31hl84X7G5U9ZfsWvIB3Pg=="], - "domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="], + "domhandler": ["domhandler@6.0.1", "", { "dependencies": { "domelementtype": "^3.0.0" } }, "sha512-gYzvtM72ZtxQO0T048kd6HWSbbGCNOUwcnfQ01cqIJ4X2IYKFFHZ5mKvrQETcFXxsRObZulDaKmy//R7TPtsBg=="], - "domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="], + "domutils": ["domutils@4.0.2", "", { "dependencies": { "dom-serializer": "^3.0.0", "domelementtype": "^3.0.0", "domhandler": "^6.0.0" } }, "sha512-qI4JLRKnSzqFqr7hAlS5xQDusBCjKSEG4t4+7aNrIQMHBcsC2TGEhuyABJdYkgSewL57PNLYEiibY2iPKhKpaA=="], - "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], + "entities": ["entities@8.0.0", "", {}, "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA=="], - "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + "escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], - "entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], + "eslint": ["eslint@10.9.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", "@eslint/config-helpers": "^0.7.0", "@eslint/core": "^1.2.1", "@eslint/plugin-kit": "^0.7.2", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-5KeEOJZBfEVA47boFiBsf+6MmmJpffM7qEBg4pLla2e4nlKgdKlqCW0oSLOGsT8Wl5uCGJptLV1bkaiShj90Gw=="], - "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + "eslint-plugin-jsdoc": ["eslint-plugin-jsdoc@64.2.1", "", { "dependencies": { "@es-joy/jsdoccomment": "~0.95.1", "@es-joy/resolve.exports": "1.2.0", "are-docs-informative": "^0.1.1", "comment-parser": "1.4.8", "debug": "^4.4.3", "escape-string-regexp": "^5.0.0", "espree": "^11.2.0", "esquery": "^1.7.0", "html-entities": "^2.6.0", "object-deep-merge": "^2.0.1", "parse-imports-exports": "^0.2.4", "semver": "^7.8.5", "spdx-expression-parse": "^5.0.0", "to-valid-identifier": "^1.0.0" }, "peerDependencies": { "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0" } }, "sha512-6GpSYxLPcbMw38S94Cngrgs1Zv8yLinQS1O17OxJVZ6deLbrMRCERUYQKceweSqEuG2kx5Amn4l3aKPkCp4geQ=="], - "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + "eslint-scope": ["eslint-scope@9.1.2", "", { "dependencies": { "@types/esrecurse": "^4.3.1", "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ=="], - "eslint": ["eslint@9.39.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.1", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g=="], + "eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], - "eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="], - - "eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], - - "espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="], + "espree": ["espree@11.2.0", "", { "dependencies": { "acorn": "^8.16.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^5.0.1" } }, "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw=="], "esquery": ["esquery@1.7.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g=="], @@ -283,51 +255,27 @@ "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], - "flat": ["flat@5.0.2", "", { "bin": { "flat": "cli.js" } }, "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ=="], - "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], "flatted": ["flatted@3.4.2", "", {}, "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA=="], - "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], - - "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], - - "glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], - "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], - "globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], - - "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], - - "he": ["he@1.2.0", "", { "bin": { "he": "bin/he" } }, "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="], + "html-entities": ["html-entities@2.6.0", "", {}, "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ=="], - "htmlparser2": ["htmlparser2@10.1.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.2.2", "entities": "^7.0.1" } }, "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ=="], + "htmlparser2": ["htmlparser2@12.0.0", "", { "dependencies": { "domelementtype": "^3.0.0", "domhandler": "^6.0.0", "domutils": "^4.0.2", "entities": "^8.0.0" } }, "sha512-Tz7u1i95/g2x2jz81+x0FBVhBhY5aRTvD3tXXdFaljuNdzDLJ8UGNRrTcj2cgQvAg3iW/h77Fz15nLW0L0CrZw=="], "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], - "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], - "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], - "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], - "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], - "is-path-inside": ["is-path-inside@3.0.3", "", {}, "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ=="], - - "is-plain-obj": ["is-plain-obj@2.1.0", "", {}, "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA=="], - - "is-unicode-supported": ["is-unicode-supported@0.1.0", "", {}, "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw=="], - "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], - "jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], - - "js-yaml": ["js-yaml@4.2.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw=="], + "jsdoc-type-pratt-parser": ["jsdoc-type-pratt-parser@9.1.2", "", { "dependencies": { "@types/estree": "^1.0.9" } }, "sha512-9EXymowgk1mb9RY1VxuwKc+AhaxfBk2CV0dWxgGM+l5RURTtiUoAx7MlKwcsiVcEXK5HEPa7FeH/tsRpqjEPRg=="], "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], @@ -341,19 +289,9 @@ "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], - "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], - - "log-symbols": ["log-symbols@4.1.0", "", { "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" } }, "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg=="], - - "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], - "mdn-data": ["mdn-data@2.27.1", "", {}, "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ=="], - "minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], - - "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], - - "mocha": ["mocha@11.7.5", "", { "dependencies": { "browser-stdout": "^1.3.1", "chokidar": "^4.0.1", "debug": "^4.3.5", "diff": "^7.0.0", "escape-string-regexp": "^4.0.0", "find-up": "^5.0.0", "glob": "^10.4.5", "he": "^1.2.0", "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "log-symbols": "^4.1.0", "minimatch": "^9.0.5", "ms": "^2.1.3", "picocolors": "^1.1.1", "serialize-javascript": "^6.0.2", "strip-json-comments": "^3.1.1", "supports-color": "^8.1.1", "workerpool": "^9.2.0", "yargs": "^17.7.2", "yargs-parser": "^21.1.1", "yargs-unparser": "^2.0.0" }, "bin": { "mocha": "bin/mocha.js", "_mocha": "bin/_mocha" } }, "sha512-mTT6RgopEYABzXWFx+GcJ+ZQ32kp4fMf0xvpZIIfSq9Z8lC/++MtcCnQ9t5FP2veYEP95FIYSvW+U9fV4xrlig=="], + "minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], "mozilla": ["mozilla@workspace:tools/oxlint-plugin-mozilla"], @@ -363,28 +301,28 @@ "nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="], + "object-deep-merge": ["object-deep-merge@2.0.1", "", {}, "sha512-aKttDKcU3pyZqKcCkDhsMn70WmZFG2JGDQLP9EcLyTSIFQRCPWLAmBZRLJnrVUrhPG1jETEEbfdgbNtJf1LyMg=="], + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], - "oxfmt": ["oxfmt@0.52.0", "", { "dependencies": { "tinypool": "2.1.0" }, "optionalDependencies": { "@oxfmt/binding-android-arm-eabi": "0.52.0", "@oxfmt/binding-android-arm64": "0.52.0", "@oxfmt/binding-darwin-arm64": "0.52.0", "@oxfmt/binding-darwin-x64": "0.52.0", "@oxfmt/binding-freebsd-x64": "0.52.0", "@oxfmt/binding-linux-arm-gnueabihf": "0.52.0", "@oxfmt/binding-linux-arm-musleabihf": "0.52.0", "@oxfmt/binding-linux-arm64-gnu": "0.52.0", "@oxfmt/binding-linux-arm64-musl": "0.52.0", "@oxfmt/binding-linux-ppc64-gnu": "0.52.0", "@oxfmt/binding-linux-riscv64-gnu": "0.52.0", "@oxfmt/binding-linux-riscv64-musl": "0.52.0", "@oxfmt/binding-linux-s390x-gnu": "0.52.0", "@oxfmt/binding-linux-x64-gnu": "0.52.0", "@oxfmt/binding-linux-x64-musl": "0.52.0", "@oxfmt/binding-openharmony-arm64": "0.52.0", "@oxfmt/binding-win32-arm64-msvc": "0.52.0", "@oxfmt/binding-win32-ia32-msvc": "0.52.0", "@oxfmt/binding-win32-x64-msvc": "0.52.0" }, "peerDependencies": { "svelte": "^5.0.0", "vite-plus": "*" }, "optionalPeers": ["svelte", "vite-plus"], "bin": { "oxfmt": "bin/oxfmt" } }, "sha512-nJlYM35F64zTDMecCNhoHNkf+D/eHv7xcjj9XDSj+bFAVtN93m7v8DQMdHd6nDG6Akf/kEYYHmDUBs2Dz27Sug=="], + "oxfmt": ["oxfmt@0.65.0", "", { "dependencies": { "tinypool": "2.1.0" }, "optionalDependencies": { "@oxfmt/binding-android-arm-eabi": "0.65.0", "@oxfmt/binding-android-arm64": "0.65.0", "@oxfmt/binding-darwin-arm64": "0.65.0", "@oxfmt/binding-darwin-x64": "0.65.0", "@oxfmt/binding-freebsd-x64": "0.65.0", "@oxfmt/binding-linux-arm-gnueabihf": "0.65.0", "@oxfmt/binding-linux-arm-musleabihf": "0.65.0", "@oxfmt/binding-linux-arm64-gnu": "0.65.0", "@oxfmt/binding-linux-arm64-musl": "0.65.0", "@oxfmt/binding-linux-ppc64-gnu": "0.65.0", "@oxfmt/binding-linux-riscv64-gnu": "0.65.0", "@oxfmt/binding-linux-riscv64-musl": "0.65.0", "@oxfmt/binding-linux-s390x-gnu": "0.65.0", "@oxfmt/binding-linux-x64-gnu": "0.65.0", "@oxfmt/binding-linux-x64-musl": "0.65.0", "@oxfmt/binding-openharmony-arm64": "0.65.0", "@oxfmt/binding-win32-arm64-msvc": "0.65.0", "@oxfmt/binding-win32-ia32-msvc": "0.65.0", "@oxfmt/binding-win32-x64-msvc": "0.65.0" }, "peerDependencies": { "svelte": "^5.0.0", "vite-plus": "*" }, "optionalPeers": ["svelte", "vite-plus"], "bin": { "oxfmt": "bin/oxfmt" } }, "sha512-SgS5VgnP42T0zl3zWD+xoH8FCqg1SAFnSRoOT/qeoa6gxcYIqrDMOmcXIg/EWSN92Du4ogB4riuKhKd6Y4CGhw=="], - "oxlint": ["oxlint@1.67.0", "", { "optionalDependencies": { "@oxlint/binding-android-arm-eabi": "1.67.0", "@oxlint/binding-android-arm64": "1.67.0", "@oxlint/binding-darwin-arm64": "1.67.0", "@oxlint/binding-darwin-x64": "1.67.0", "@oxlint/binding-freebsd-x64": "1.67.0", "@oxlint/binding-linux-arm-gnueabihf": "1.67.0", "@oxlint/binding-linux-arm-musleabihf": "1.67.0", "@oxlint/binding-linux-arm64-gnu": "1.67.0", "@oxlint/binding-linux-arm64-musl": "1.67.0", "@oxlint/binding-linux-ppc64-gnu": "1.67.0", "@oxlint/binding-linux-riscv64-gnu": "1.67.0", "@oxlint/binding-linux-riscv64-musl": "1.67.0", "@oxlint/binding-linux-s390x-gnu": "1.67.0", "@oxlint/binding-linux-x64-gnu": "1.67.0", "@oxlint/binding-linux-x64-musl": "1.67.0", "@oxlint/binding-openharmony-arm64": "1.67.0", "@oxlint/binding-win32-arm64-msvc": "1.67.0", "@oxlint/binding-win32-ia32-msvc": "1.67.0", "@oxlint/binding-win32-x64-msvc": "1.67.0" }, "peerDependencies": { "oxlint-tsgolint": ">=0.22.1", "vite-plus": "*" }, "optionalPeers": ["oxlint-tsgolint", "vite-plus"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-blwwaHPdoH8piQ5/z0KHeoHFR7FZgl12WluKJfu4qFLPkZl6mK04PkLE45Fw1NxfBRSlh40Gu7MkxHUw++ociQ=="], + "oxlint": ["oxlint@1.80.0", "", { "optionalDependencies": { "@oxlint/binding-android-arm-eabi": "1.80.0", "@oxlint/binding-android-arm64": "1.80.0", "@oxlint/binding-darwin-arm64": "1.80.0", "@oxlint/binding-darwin-x64": "1.80.0", "@oxlint/binding-freebsd-x64": "1.80.0", "@oxlint/binding-linux-arm-gnueabihf": "1.80.0", "@oxlint/binding-linux-arm-musleabihf": "1.80.0", "@oxlint/binding-linux-arm64-gnu": "1.80.0", "@oxlint/binding-linux-arm64-musl": "1.80.0", "@oxlint/binding-linux-ppc64-gnu": "1.80.0", "@oxlint/binding-linux-riscv64-gnu": "1.80.0", "@oxlint/binding-linux-riscv64-musl": "1.80.0", "@oxlint/binding-linux-s390x-gnu": "1.80.0", "@oxlint/binding-linux-x64-gnu": "1.80.0", "@oxlint/binding-linux-x64-musl": "1.80.0", "@oxlint/binding-openharmony-arm64": "1.80.0", "@oxlint/binding-win32-arm64-msvc": "1.80.0", "@oxlint/binding-win32-ia32-msvc": "1.80.0", "@oxlint/binding-win32-x64-msvc": "1.80.0" }, "peerDependencies": { "oxlint-tsgolint": ">=7.0.2001", "vite-plus": "*" }, "optionalPeers": ["oxlint-tsgolint", "vite-plus"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-5nTiSps4qdbCWLbxzuO00alHkEO2exR9YMN/ig6QXWrLsYSG0KaObOAM+l6oU2LcKPWoSAGYbkZIGEu1ViiWKA=="], - "oxlint-plugin-eslint": ["oxlint-plugin-eslint@1.68.0", "", {}, "sha512-VuHdZ4go8jb0IGBBonehCC2QTy8KsuK/vj3b0MQJ3S7LlIF+o6FfNbmsekKSBtEU0f0egxnbY8h5T23gj/KwfA=="], + "oxlint-plugin-eslint": ["oxlint-plugin-eslint@1.80.0", "", {}, "sha512-ElAyNkGRwxc+sMiSn/aJG0Qta7DkPqeLqDENz3iRm5UMgVaISO1Golm0vRo3UM8+DYQhHos2548gjS7lrjHc1w=="], "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], - "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], + "parse-imports-exports": ["parse-imports-exports@0.2.4", "", { "dependencies": { "parse-statements": "1.0.11" } }, "sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ=="], - "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], + "parse-statements": ["parse-statements@1.0.11", "", {}, "sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA=="], "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], - "path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], - "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], @@ -393,49 +331,33 @@ "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], - "randombytes": ["randombytes@2.1.0", "", { "dependencies": { "safe-buffer": "^5.1.0" } }, "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ=="], - - "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], - - "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], - - "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], + "reserved-identifiers": ["reserved-identifiers@1.2.0", "", {}, "sha512-yE7KUfFvaBFzGPs5H3Ops1RevfUEsDc5Iz65rOwWg4lE8HJSYtle77uul3+573457oHvBKuHYDl/xqUkKpEEdw=="], - "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + "sax": ["sax@1.6.1", "", {}, "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q=="], - "sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="], - - "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], - - "serialize-javascript": ["serialize-javascript@6.0.2", "", { "dependencies": { "randombytes": "^2.1.0" } }, "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g=="], + "semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], - "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], - "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], - "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - - "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + "spdx-exceptions": ["spdx-exceptions@2.5.0", "", {}, "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w=="], - "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "spdx-expression-parse": ["spdx-expression-parse@5.0.0", "", { "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "sha512-vngmw3Rgn+o2arXNbnZaj5UtOEBuWBfvaI+Wc8GFfykIhA5/vdK9/Sp/XkLv63dykz2rxKDvKEHupF5P0FORcQ=="], - "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "spdx-license-ids": ["spdx-license-ids@3.0.23", "", {}, "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw=="], - "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], - - "supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="], - - "svgo": ["svgo@4.0.1", "", { "dependencies": { "commander": "^11.1.0", "css-select": "^5.1.0", "css-tree": "^3.0.1", "css-what": "^6.1.0", "csso": "^5.0.5", "picocolors": "^1.1.1", "sax": "^1.5.0" }, "bin": "./bin/svgo.js" }, "sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w=="], + "svgo": ["svgo@4.1.0", "", { "dependencies": { "commander": "^11.1.0", "css-select": "^6.0.0", "css-tree": "^3.0.1", "css-what": "^7.0.0", "csso": "^5.0.5", "picocolors": "^1.1.1", "sax": "1.6.1" }, "bin": { "svgo": "bin/svgo.js" } }, "sha512-bkxnTg1kSU0guhIBmibA6UUhrQmPVA1XsQLN+ylCd+UWzbnLkySOcXpyk1mrl05f+pcaCx2eHb+sp6BgMZWX+Q=="], "tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="], "tinypool": ["tinypool@2.1.0", "", {}, "sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw=="], - "toml-eslint-parser": ["toml-eslint-parser@1.0.0", "", { "dependencies": { "eslint-visitor-keys": "^5.0.0" } }, "sha512-syGkwm4fa+uOzswyJQy6aFmmpcSBm/M0+F/iOAWfDkFuz33OnPMJkVQzaCk10WpqwzKcbaeAJWxfSGkkH8GiBw=="], + "to-valid-identifier": ["to-valid-identifier@1.0.0", "", { "dependencies": { "@sindresorhus/base62": "^1.0.0", "reserved-identifiers": "^1.0.0" } }, "sha512-41wJyvKep3yT2tyPqX/4blcfybknGB4D+oETKLs7Q76UiPqRpUJK3hr1nxelyYO0PHKVzJwlu0aCeEAsGI6rpw=="], + + "toml-eslint-parser": ["toml-eslint-parser@1.0.3", "", { "dependencies": { "eslint-visitor-keys": "^5.0.0" } }, "sha512-A5F0cM6+mDleacLIEUkmfpkBbnHJFV1d2rprHU2MXNk7mlxHq2zGojA+SRvQD1RoMo9gqjZPWEaKG4v1BQ48lw=="], "ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="], @@ -451,60 +373,26 @@ "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], - "workerpool": ["workerpool@9.3.4", "", {}, "sha512-TmPRQYYSAnnDiEB0P/Ytip7bFGvqnSU6I2BcuSw7Hx+JSg/DsUi5ebYfc8GYaSdpuvOcEs6dXxPurOYpe9QFwg=="], - - "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], - - "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], - - "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], - - "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], - - "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], - - "yargs-unparser": ["yargs-unparser@2.0.0", "", { "dependencies": { "camelcase": "^6.0.0", "decamelize": "^4.0.0", "flat": "^5.0.2", "is-plain-obj": "^2.1.0" } }, "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA=="], - "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], - "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], - - "@isaacs/cliui/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], - - "@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], + "css-select/domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="], - "@typescript-eslint/typescript-estree/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], - - "@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], - - "chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + "css-select/domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="], "csso/css-tree": ["css-tree@2.2.1", "", { "dependencies": { "mdn-data": "2.0.28", "source-map-js": "^1.0.1" } }, "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA=="], - "dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], - - "glob/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], - - "mocha/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], + "eslint/escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], - "toml-eslint-parser/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], + "css-select/domhandler/domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="], - "@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], + "css-select/domutils/dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="], - "@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], - - "@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], - - "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@5.0.6", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g=="], + "css-select/domutils/domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="], "csso/css-tree/mdn-data": ["mdn-data@2.0.28", "", {}, "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g=="], - "glob/minimatch/brace-expansion": ["brace-expansion@2.1.0", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w=="], - - "mocha/minimatch/brace-expansion": ["brace-expansion@2.1.0", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w=="], - - "@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], + "css-select/domutils/dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], } } diff --git a/docs/updates.md b/docs/updates.md index 9afe0786..209a73a9 100644 --- a/docs/updates.md +++ b/docs/updates.md @@ -17,3 +17,27 @@ Here are the standardized rules that the `engine.json` file and `release.yml` wo The `release.yml` workflow must handle all of these possible human mistakes, and ensure that these never happen in production. This workflow must allow for the only necessary human interaction to be to start the workflow with the proper options, while everything forwards must always remain automated. + +## Triggering a new release + +Due to our collaboration with SignPath, we now sign all of our stable releases. This results in our stable release workflow being different than our beta release workflow. + +### Beta (Cosine) + +- Initialize a new release with the tag of the new version you want to deploy, with a 'c' suffix. (meaning Cosine, e.g. v2.3.4.0c) +- Fill in details regarding the main goal of this release and the changelog. +- Save the release as a draft, **do not publish, releases are immutable once published**. +- Trigger the "Deploy latest assets" workflow (our main deploy workflow) on the Cosine branch with the release version (excluding 'v' prefix and 'c' suffix) and the version type (0, 1, 2). +- Once the asset deployment workflow is complete, you may publish the draft release (assuming all data appears as intended). + +### Stable + +- Initialize a new release with the tag of the new version you want to deploy. (e.g. v2.3.4.0) +- Fill in details regarding the main goal of this release and the changelog. +- Save the release as a draft, **do not publish, releases are immutable once published**. +- Trigger the "Build cross-platform installers" workflow on the main branch with release version, excluding 'v' prefix. (e.g. 2.3.4.0), as well as the version of the Sine bootloader that should be bundled with the release, excluding 'v' prefix (e.g. 0.1.4). +- The installer-building workflow should then submit the assets to SignPath for reviewal and signing. +- When SignPath approves and signs the assets (1-3 business days), the "Deploy latest assets" workflow will then be ready to be triggered. + Trigger it with the release version (excluding 'v' prefix), the version type (0, 1, 2), and the run_id for the build workflow specified earlier. +- Once the asset deployment workflow is complete, you may publish the draft release (assuming all data appears as intended), but it is recommended to first verify that the Windows installer does not have a SmartScreen prompt when downloaded and executed from the draft release. + If the SmartScreen prompt does appear, it means the installer was not properly signed, and a reviewal of our submission process must be performed, as well as a checkup with SignPath. diff --git a/engine.json b/engine.json index 5cb46b4c..3fcaafa1 100644 --- a/engine.json +++ b/engine.json @@ -5,6 +5,14 @@ "enginePath": "engine.zip", "profilePath": "profile.zip", "updates": [ + { + "version": "2.3.4.1c", + "type": 0 + }, + { + "version": "2.3.4.0c", + "type": 0 + }, { "version": "2.3.3.0", "type": 0 diff --git a/oxlint.config.ts b/oxlint.config.ts index 55f3d431..1c4f8e7c 100644 --- a/oxlint.config.ts +++ b/oxlint.config.ts @@ -1,29 +1,40 @@ /** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * @file Oxlint configuration. This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at + * http://mozilla.org/MPL/2.0/. */ import { defineConfig } from "oxlint"; -import type { OxlintConfig } from "oxlint"; +import type { OxlintConfig, OxlintGlobals } from "oxlint"; import mozilla from "./tools/oxlint-plugin-mozilla/lib/index.mjs"; +import browserWindow from "./tools/oxlint-plugin-mozilla/lib/environments/browser-window.mjs"; const mozillaConfig = (mozilla.configs as Record)["flat/recommended"]; +const requireJsdoc = (mozilla.configs as Record)["flat/require-jsdoc"]; +const validJsdoc = (mozilla.configs as Record)["flat/valid-jsdoc"]; + +const browserGlobals = browserWindow.globals as OxlintGlobals; export default defineConfig({ - extends: [mozillaConfig], - plugins: ["jsdoc"], + extends: [mozillaConfig, validJsdoc, requireJsdoc], jsPlugins: [ "./tools/oxlint-plugin-mozilla/lib/index.mjs", "./tools/oxlint-plugin-mpl/lib/index.mjs", ], + rules: { + "oxlint-plugin-mpl/require-mpl-header": "error", + }, overrides: [ { - files: ["**/*.{tsx,ts,mjs,js,jsx,cjs}"], - rules: { - "oxlint-plugin-mpl/require-mpl-header": "error", + files: ["src/core/settings.mjs"], + globals: { + gCategoryInits: "readonly", }, }, + { + files: ["src/core/browser.mjs", "src/services/updates.mjs"], + globals: Object.assign({}, browserGlobals, { gZenMods: "readonly" }), + }, ], ignorePatterns: ["src/assets/imports/"], }); diff --git a/package.json b/package.json index 224b4e52..34c2917a 100644 --- a/package.json +++ b/package.json @@ -30,9 +30,10 @@ "devDependencies": { "@chromejs/browsercfg": "^0.1.8", "@types/bun": "latest", - "oxfmt": "^0.52.0", - "oxlint": "^1.67.0", - "oxlint-plugin-eslint": "^1.68.0", - "svgo": "^4.0.1" + "eslint-plugin-jsdoc": "^64.2.1", + "oxfmt": "^0.65.0", + "oxlint": "^1.80.0", + "oxlint-plugin-eslint": "^1.80.0", + "svgo": "^4.1.0" } } diff --git a/src/actors/MarketplaceChild.sys.mjs b/src/actors/MarketplaceChild.sys.mjs index 75eb80ef..4545d832 100644 --- a/src/actors/MarketplaceChild.sys.mjs +++ b/src/actors/MarketplaceChild.sys.mjs @@ -1,15 +1,23 @@ /** + * @file Defines child window actor class for Sine store and Zen Mods site. + * @license * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -// =========================================================== -// This module interacts with the site in the JS Window Actor -// for the Zen Mods site. -// =========================================================== - +/** + * Child window actor class for Sine store and Zen Mods mod installation from site. + * + * @class + */ export class SineModsMarketplaceChild extends JSWindowActorChild { + /** + * Main entrypoint of class. Handles new window loading. + * + * @param {object} event - Event to handle. + * @param {string} event.type - The name of the event. (only care if DOMContentLoaded) + */ handleEvent(event) { if (event.type === "DOMContentLoaded") { const verifier = this.contentWindow.document.querySelector( @@ -24,6 +32,7 @@ export class SineModsMarketplaceChild extends JSWindowActorChild { } } + /** Executes main functions to load main child actor functionality. */ initiateModsMarketplace() { this.contentWindow.setTimeout(() => { this.addButtons(); @@ -31,19 +40,41 @@ export class SineModsMarketplaceChild extends JSWindowActorChild { }, 0); } + /** + * Returns install theme button on Zen Mods site. + * + * @returns {HTMLElement} Install theme button on Zen Mods site. + */ get actionButton() { - return this.contentWindow.document.getElementById("install-theme"); + return this.contentWindow.document.querySelector("#install-theme"); } + /** + * Returns uninstall theme button on Zen Mods site. + * + * @returns {HTMLElement} Uninstall theme button on Zen Mods site. + */ get actionButtonUninstall() { - return this.contentWindow.document.getElementById("install-theme-uninstall"); + return this.contentWindow.document.querySelector("#install-theme-uninstall"); } + /** + * Checks if a theme with an id is installed. + * + * @param {string} themeId - Theme id to check installation status of. + * @returns {boolean} Is theme installed boolean. + */ async isThemeInstalled(themeId) { return await this.sendQuery("SineModsMarketplace:IsModInstalled", { themeId }); } - getModId(event) { + /** + * Extracts mod id from event. + * + * @param {object} event - Event to extract mod id from. + * @returns {string} Mod id. + */ + static getModId(event) { if (event.target) { const button = event.target; button.disabled = true; @@ -55,13 +86,26 @@ export class SineModsMarketplaceChild extends JSWindowActorChild { return event.themeId ?? event.modId ?? event.id; } + /** + * Returns a single mod install button, given a mod id. + * + * @param {string} modId - Mod id to fetch install button of. + * @returns {HTMLElement} Install button element. + */ getInstallButton(modId) { return ( this.contentWindow.document.querySelector(`.action-install[theme-id="${modId}"]`) ?? - this.contentWindow.document.getElementById("install-theme") + this.contentWindow.document.querySelector("#install-theme") ); } + /** + * Message handler. + * + * @param {object} message - Message received. + * @param {string} message.name - Name of message received. + * @param {object} message.data - Optional data received for message. + */ async receiveMessage(message) { switch (message.name) { case "SineModsMarketplace:ModChanged": { @@ -95,9 +139,15 @@ export class SineModsMarketplaceChild extends JSWindowActorChild { break; } + + default: { + console.error("[Sine:MarketplaceChild]: Unknown message received."); + break; + } } } + /** Injects installation API into site. */ injectMarketplaceAPI() { // Remove the original Zen variable for injection. delete window.ZenInstallMod; @@ -107,12 +157,13 @@ export class SineModsMarketplaceChild extends JSWindowActorChild { }); } + /** Displays and adds functionality to install and uninstall buttons. */ async addButtons() { - this.contentWindow.document.getElementById("install-theme-error").classList.add("hidden"); + this.contentWindow.document.querySelector("#install-theme-error").classList.add("hidden"); const actionButtons = [ - ...this.contentWindow.document.getElementsByClassName("action-install"), - this.contentWindow.document.getElementById("install-theme"), + ...this.contentWindow.document.querySelectorAll(".action-install"), + this.contentWindow.document.querySelector("#install-theme"), ]; const promises = []; for (const actionButton of actionButtons) { @@ -140,14 +191,24 @@ export class SineModsMarketplaceChild extends JSWindowActorChild { await Promise.all(promises); } + /** + * Handles uninstall event for the corresponding buttons. + * + * @param {object} event - Event to extract mod id from. + */ handleModUninstallEvent(event) { - const modId = this.getModId(event); + const modId = this.constructor.getModId(event); this.sendAsyncMessage("SineModsMarketplace:UninstallMod", { modId }); } + /** + * Handles install event for the corresponding buttons. + * + * @param {object} event - Event to extract mod id from. + */ handleModInstallationEvent(event) { // Object can be an event or a theme id - const modId = this.getModId(event); + const modId = this.constructor.getModId(event); this.sendAsyncMessage("SineModsMarketplace:InstallMod", { modId }); } } diff --git a/src/actors/MarketplaceParent.sys.mjs b/src/actors/MarketplaceParent.sys.mjs index e61b60d3..e47d495c 100644 --- a/src/actors/MarketplaceParent.sys.mjs +++ b/src/actors/MarketplaceParent.sys.mjs @@ -1,19 +1,35 @@ /** + * @file Defines parent window actor class for Sine store and Zen Mods site. + * @license * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -// =========================================================== -// This module allows the JS Window Actor for the Zen Mods -// site to interact with global variables. -// =========================================================== - +/** + * Parent window actor class for Sine store and Zen Mods mod installation from site. + * + * @class + */ export class SineModsMarketplaceParent extends JSWindowActorParent { + /** + * Returns mod manager API. + * + * @returns {object} Mod manager API in the form of { utils, manager }. + */ get modsManager() { return this.browsingContext.topChromeWindow.SineAPI; } + /** + * Message handler. + * + * @param {object} message - Received message. + * @param {string} message.name - Name of received message. + * @param {object} message.data - Optional message data. + * @returns {boolean | null} Returns boolean for theme installation status, or null for other + * messages. + */ async receiveMessage(message) { switch (message.name) { case "SineModsMarketplace:InstallMod": { @@ -32,10 +48,6 @@ export class SineModsMarketplaceParent extends JSWindowActorParent { case "SineModsMarketplace:UninstallMod": { const modId = message.data.modId; - const mods = await this.modsManager.utils.getMods(); - - delete mods[modId]; - await this.modsManager.manager.removeMod(modId); await this.modsManager.manager.rebuildMods(false); @@ -49,10 +61,19 @@ export class SineModsMarketplaceParent extends JSWindowActorParent { return Boolean(themes?.[themeId]); } + default: { + console.error("[Sine:MarketplaceParent]: Unknown message received."); + break; + } } return null; } + /** + * Triggers a mod changed event on the parent class. + * + * @param {string} modId - Mod id to trigger mod changed event on. + */ updateChildProcesses(modId) { this.sendAsyncMessage("SineModsMarketplace:ModChanged", { modId }); } diff --git a/src/core/browser.mjs b/src/core/browser.mjs index 22821a47..83a07f6b 100644 --- a/src/core/browser.mjs +++ b/src/core/browser.mjs @@ -1,23 +1,20 @@ /** + * @file Initializes a browser window with necessary Sine features. + * @license * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -// =========================================================== -// Initializes a browser window instance with necessary -// variables and check for updates. -// =========================================================== - -import domUtils from "../utils/dom.mjs"; +import * as domUtils from "../utils/dom.mjs"; import updates from "../services/updates.mjs"; +import cmdPalette from "../services/cmdPalette.mjs"; -import injectCmdPalette from "../services/cmdPalette.js"; await domUtils.waitForElm("body"); domUtils.injectLocale("sine-toasts"); -injectCmdPalette(); +cmdPalette.register(); const ucAPI = ChromeUtils.importESModule( "chrome://userscripts/content/utils/uc_api.sys.mjs" @@ -42,7 +39,7 @@ if (ucAPI.utils.fork === "zen") { mod.preferences = "preferences.json"; } } - await IOUtils.writeJSON(utils.modsDataFile, { ...sineMods, ...zenMods }); + await IOUtils.writeJSON(utils.modsDataFile, Object.assign({}, sineMods, zenMods)); const zenModsPath = gZenMods.modsRootPath; const promises = Object.keys(zenMods).map(async (id) => { diff --git a/src/core/manager.sys.mjs b/src/core/manager.sys.mjs index 0ca2324e..fd369429 100644 --- a/src/core/manager.sys.mjs +++ b/src/core/manager.sys.mjs @@ -1,18 +1,20 @@ /** + * @file Manages mods, including basic Sine functionality. + * @license * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -// =========================================================== -// Manages mods and themes, allowing Sine to install, update, -// enable, disable, and configure them. -// =========================================================== - import utils from "./utils.sys.mjs"; -import domUtils from "../utils/dom.mjs"; +import * as domUtils from "../utils/dom.mjs"; import ucAPI from "../utils/uc_api.sys.mjs"; +/** + * Main mod manager class. + * + * @class + */ class Manager { preferences = ChromeUtils.importESModule("chrome://userscripts/content/core/preferences.sys.mjs"); marketplace = ChromeUtils.importESModule( @@ -21,16 +23,32 @@ class Manager { #stylesheetManager = ChromeUtils.importESModule( "chrome://userscripts/content/services/stylesheets.sys.mjs" ).default; - #unloadListeners = {}; - + #unloadListeners = new Map(); + + /** + * Adds an unload listener to #unloadListeners. + * + * @param {string} script - Script path to assign an unload listener to. + * @param {Window} window - Window to relate unload listener to. + * @param {() => void} callback - Callback to execute when unload listener is triggered. + */ addUnloadListener(script, window, callback) { - this.#unloadListeners[script] ??= new Map(); - const scriptListeners = this.#unloadListeners[script]; + if (!this.#unloadListeners.has(script)) { + this.#unloadListeners.set(script, new Map()); + } + const scriptListeners = this.#unloadListeners.get(script); scriptListeners.set(window, callback); } + /** + * Triggers an unload listener from #unloadListeners. + * + * @param {string} chromePath - Path of script to unload. + * @param {Window} window - Window to unload script from. + * @returns {boolean} Whether script remains loaded or not. + */ async triggerUnloadListener(chromePath, window) { - const listeners = this.#unloadListeners[chromePath]; + const listeners = this.#unloadListeners.get(chromePath); if (!listeners) return false; // If there is no match for the window, then the script was not loaded in this DOM. @@ -49,30 +67,42 @@ class Manager { listeners.delete(window); if (listeners.size === 0) { - delete this.#unloadListeners[chromePath]; + this.#unloadListeners.delete(chromePath); } // Script is no longer loaded, return false. return false; } + /** + * Removes all unload listeners related to a mod. Should be triggered when a mod is uninstalled or + * disabled. + * + * @param {string} modId - Id of mod to remove unload listeners of. + */ async removeUnloadListeners(modId) { const allUnloadPromises = []; - for (const [scriptName, listeners] of Object.entries(this.#unloadListeners)) { + for (const [scriptName, listeners] of this.#unloadListeners) { if (scriptName.startsWith(`chrome://sine/content/${modId}/`)) { for (const listener of listeners.values()) { if (listener) { allUnloadPromises.push(listener()); } } - delete this.#unloadListeners[scriptName]; + this.#unloadListeners.delete(scriptName); } } await Promise.all(allUnloadPromises); } + /** + * Removes all unload listeners related to a window. Should be executed when a window is closed or + * unloaded. + * + * @param {Window} window - Window to remove unload listeners from. + */ removeListenersForDOM(window) { - for (const listeners of Object.values(this.#unloadListeners)) { + for (const listeners of this.#unloadListeners.values()) { if (listeners.has(window)) { const windowListener = listeners.get(window); if (windowListener) { @@ -84,6 +114,12 @@ class Manager { } } + /** + * Appends Sine interfaces to a window object. Should be executed when a window that a mod may run + * in is opened. + * + * @param {Window} window - Window to append interface to. + */ appendInterfaceToDOM(window) { const addUnloadListener = this.addUnloadListener.bind(this); window.addUnloadListener = (callback, scriptPath) => { @@ -103,7 +139,13 @@ class Manager { window.triggerUnloadListener = this.triggerUnloadListener.bind(this); } - #registerChromeManifest(manifestPath, modId) { + /** + * Registers a mod's chrome manifest. + * + * @param {string} manifestPath - Chrome URI path to a manifest file. + * @param {string} modId - Mod id to register manifest of. + */ + static #registerChromeManifest(manifestPath, modId) { if (!manifestPath) return; const cmanifest = Services.dirsvc.get("UChrm", Ci.nsIFile); @@ -120,6 +162,12 @@ class Manager { } } + /** + * Rebuilds all mods. If both parameters are disabled, function will rebuild mod-related DOM data. + * + * @param {boolean} rebuildJS - If true, will load/reload JavaScript. + * @param {boolean} reloadStyles - If true, will load/reload styles. + */ async rebuildMods(rebuildJS = true, reloadStyles = true) { if (Services.prefs.getBoolPref("sine.mods.disable-all", false)) { return; @@ -137,7 +185,7 @@ class Manager { // Load chrome uris. for (const mod of Object.values(mods)) { - this.#registerChromeManifest(mod.chromeManifest, mod.id); + this.constructor.#registerChromeManifest(mod.chromeManifest, mod.id); } // Inject background modules. @@ -145,9 +193,9 @@ class Manager { if (scriptPath.endsWith(".sys.mjs")) { const chromePath = `chrome://sine/content/${scriptPath}`; - // TODO: Find a way to pass addUnloadListener to background scripts. + // TODO: Find a way to pass Sine interface to background scripts. Sandboxing execution? try { - if (scripts[scriptPath].enabled && !Object.hasOwn(this.#unloadListeners, chromePath)) { + if (scripts[scriptPath].enabled && !this.#unloadListeners.has(chromePath)) { // Null is being passed as window until a reference for such is found. this.addUnloadListener(chromePath, null, null); ChromeUtils.importESModule(chromePath); @@ -194,8 +242,16 @@ class Manager { await Promise.all(promises); } + /** + * Observes new chrome window events. + * + * @param {Window} subject - Window that is being observed. + * @param {string} topic - Topic specifying what the function is called for. + */ observe(subject, topic) { if (topic === "chrome-document-global-created" && subject) { + this.#stylesheetManager.onWindow(subject); + subject.addEventListener("load", async (event) => { const window = event.target.defaultView; @@ -210,9 +266,11 @@ class Manager { this.appendInterfaceToDOM(window); window.newDOM = true; - ChromeUtils.compileScript("chrome://userscripts/content/services/module_loader.mjs").then( - (script) => script.executeInGlobal(window) - ); + ChromeUtils.compileScript("chrome://userscripts/content/services/module_loader.mjs") + .then((script) => script.executeInGlobal(window)) + .catch((err) => + console.error(`[Sine:Manager]: Failed to compile module loader.\n${err}`) + ); for (const scriptPath of Object.keys(scripts)) { if (scriptPath.endsWith(".uc.js")) { @@ -222,8 +280,6 @@ class Manager { }); } } - - this.#stylesheetManager.onWindow(window); }); subject.addEventListener("beforeunload", (event) => { @@ -233,15 +289,23 @@ class Manager { } } + /** Initializes window listener, calling this.observe when a new chrome window is initialized. */ initWinListener() { Services.obs.addObserver(this, "chrome-document-global-created"); } + /** + * Uninstalls a mod. + * + * @param {string} id - Id of mod to uninstall. + */ async removeMod(id) { // Unload JS listeners first. await this.removeUnloadListeners(id); const installedMods = await utils.getMods(); + // TODO: Possibly optimize. + // oxlint-disable-next-line typescript/no-dynamic-delete delete installedMods[id]; await IOUtils.writeJSON(utils.modsDataFile, installedMods); @@ -250,7 +314,16 @@ class Manager { this.rebuildMods(false); } - #buildModXUL(document, modId, modData, modsChanged) { + /** + * Appends a mod interface to the DOM. (specifically the settings page) + * + * @param {HTMLDocument} document - Document to append mod XUL to. + * @param {string} modId - Id of mod to append to DOM. + * @param {object} modData - Mod data to append to DOM. + * @param {string[] | null} modsChanged - List of changed mod ids. + * @returns {HTMLElement} Element that was appended. + */ + static #buildModXUL(document, modId, modData, modsChanged) { const item = domUtils.appendXUL( document.querySelector("#sineModsList"), ` @@ -296,9 +369,9 @@ class Manager { const enableToggleLocale = "sine-mod-disable"; if (modData.enabled) { enableToggle.setAttribute("pressed", ""); - enableToggle.setAttribute("data-l10n-id", `${enableToggleLocale}-enabled`); + enableToggle.dataset.l10nId = `${enableToggleLocale}-enabled`; } else { - enableToggle.setAttribute("data-l10n-id", `${enableToggleLocale}-disabled`); + enableToggle.dataset.l10nId = `${enableToggleLocale}-disabled`; } if (modData.preferences) { @@ -317,8 +390,8 @@ class Manager { const configureBtn = document.createElement("button"); configureBtn.className = "sineItemConfigureButton"; - configureBtn.setAttribute("data-l10n-id", "sine-settings-button"); - configureBtn.setAttribute("data-l10n-attrs", "title"); + configureBtn.dataset.l10nId = "sine-settings-button"; + configureBtn.dataset.l10nAttrs = "title"; const sineItemActions = item.querySelector(".sineItemActions"); sineItemActions.insertBefore(configureBtn, sineItemActions.children[0]); @@ -328,16 +401,22 @@ class Manager { const updateToggleLocale = "sine-mod-update-disable"; if (modData["no-updates"]) { updateToggle.setAttribute("enabled", ""); - updateToggle.setAttribute("data-l10n-id", `${updateToggleLocale}-enabled`); + updateToggle.dataset.l10nId = `${updateToggleLocale}-enabled`; } else { - updateToggle.setAttribute("data-l10n-id", `${updateToggleLocale}-disabled`); + updateToggle.dataset.l10nId = `${updateToggleLocale}-disabled`; } return item; } + /** + * Loads all mods into the settings page DOM. + * + * @param {Window | null} specificWindow - Specific window to load mods into. + * @param {string[] | null} modsChanged - List of changed/updated mod ids. + */ async loadMods(specificWindow = null, modsChanged = null) { - let installedMods = await utils.getMods(); + const installedMods = await utils.getMods(); const pages = utils.getProcesses(specificWindow, ["settings", "preferences"]); for (const window of pages) { @@ -345,7 +424,14 @@ class Manager { document.querySelector("#sineModsList").innerHTML = ""; - if (!Services.prefs.getBoolPref("sine.mods.disable-all", false)) { + if (Services.prefs.getBoolPref("sine.mods.disable-all", false)) { + domUtils.appendXUL( + document.querySelector("#sineModsList"), + ``, + null, + window.MozXULElement + ); + } else { const sortedArr = Object.values(installedMods).toSorted((a, b) => a.name.localeCompare(b.name) ); @@ -353,21 +439,18 @@ class Manager { for (const key of ids) { const modData = installedMods[key]; // Create new item. - const item = this.#buildModXUL(document, key, modData, modsChanged); + const item = this.constructor.#buildModXUL(document, key, modData, modsChanged); const modVersion = modData.version ? ` (v${modData.version})` : ""; - item.querySelectorAll(".sineItemTitle").forEach((el) => { + for (const el of item.querySelectorAll(".sineItemTitle")) { el.textContent = modData.name + modVersion; - }); + } const toggle = item.querySelector(".sineItemPreferenceToggle"); toggle.addEventListener("toggle", async () => { - installedMods = await utils.getMods(); - const theme = await this.toggleTheme(installedMods, modData.id); - toggle.setAttribute( - "data-l10n-id", - `sine-mod-disable-${theme.enabled ? "enabled" : "disabled"}` - ); + const currMods = await utils.getMods(); + const theme = await this.toggleTheme(currMods, modData.id); + toggle.dataset.l10nId = `sine-mod-disable-${theme.enabled ? "enabled" : "disabled"}`; }); if (Object.hasOwn(modData, "preferences") && modData.preferences !== "") { @@ -382,7 +465,7 @@ class Manager { for (const pref of modPrefs) { const prefEl = this.preferences.parsePref(pref, this, window); if (prefEl) { - item.querySelector(".sineItemPreferenceDialogContent").appendChild(prefEl); + item.querySelector(".sineItemPreferenceDialogContent").append(prefEl); } } }; @@ -412,12 +495,12 @@ class Manager { updateButton.addEventListener("click", async () => { const latestMods = await utils.getMods(); latestMods[key]["no-updates"] = !latestMods[key]["no-updates"]; - if (!updateButton.getAttribute("enabled")) { - updateButton.setAttribute("enabled", true); - updateButton.setAttribute("data-l10n-id", "sine-mod-update-disable-enabled"); - } else { + if (updateButton.getAttribute("enabled")) { updateButton.removeAttribute("enabled"); - updateButton.setAttribute("data-l10n-id", "sine-mod-update-disable-disabled"); + updateButton.dataset.l10nId = "sine-mod-update-disable-disabled"; + } else { + updateButton.setAttribute("enabled", true); + updateButton.dataset.l10nId = "sine-mod-update-disable-enabled"; } await IOUtils.writeJSON(utils.modsDataFile, latestMods); }); @@ -457,17 +540,18 @@ class Manager { window.MozXULElement ); } - } else { - domUtils.appendXUL( - document.querySelector("#sineModsList"), - ``, - null, - window.MozXULElement - ); } } } + /** + * Processes an update for a specific mod. + * + * @param {object} currModData - Current data of mod to process. + * @param {object} currModsList - Current list of mod data. + * @param {object} marketplaceData - Current marketplace data. + * @returns {object} Mod update details. + */ async processModUpdate(currModData, currModsList, marketplaceData) { let newThemeData, githubAPI, originalData, homepage; @@ -480,7 +564,7 @@ class Manager { const minimalData = await this.createThemeJSON( currModData.homepage, currModsList, - typeof originalData !== "object" ? {} : originalData, + typeof originalData === "object" ? originalData : {}, true ); newThemeData = minimalData.theme; @@ -504,7 +588,7 @@ class Manager { const customData = await this.createThemeJSON( currModData.homepage, currModsList, - typeof newThemeData !== "object" ? {} : newThemeData, + typeof newThemeData === "object" ? newThemeData : {}, false, githubAPI ); @@ -528,6 +612,13 @@ class Manager { return { changed: true, modHasJS, id: currModData.id }; } + /** + * Checks for updates on all mods. + * + * @param {string | null} source - "auto" or null. If source is auto, will check if automatic + * updates are enabled. + * @returns {boolean} True if any mods have been updated. + */ async updateMods(source) { if (source === "auto" && !utils.autoUpdate) return false; @@ -577,14 +668,21 @@ class Manager { }); } - const modsHaveChanged = modsChanged.length !== 0; - if (modsHaveChanged) { - this.rebuildMods(); - this.loadMods(null, modsChanged); + if (modsChanged.length === 0) { + return false; } - return modsHaveChanged; + this.rebuildMods(); + this.loadMods(null, modsChanged); + return true; } + /** + * Installs a mod from a repository. + * + * @param {string} repo - GitHub repository to install from. + * @param {string | null} origin - Origin of mod, "store," or none. + * @param {boolean} reload - If true, will reload mods after installing. + */ async installMod(repo, origin, reload = true) { const currModsList = await utils.getMods(); @@ -599,7 +697,7 @@ class Manager { .fetch(`${utils.rawURL(repo)}theme.json`) .then( async (res) => - await this.createThemeJSON(repo, currModsList, typeof res !== "object" ? {} : res) + await this.createThemeJSON(repo, currModsList, typeof res === "object" ? res : {}) ); } @@ -621,19 +719,27 @@ class Manager { } } - parseGitHubUrl(url) { - url = url.replace(/(\?.+)?(\/+)?$/, ""); + /** + * Parses a GitHub URL into an object of data. + * + * @param {string} url - GitHub URL. + * @returns {object} Object of GitHub URL metadata. + */ + static parseGitHubUrl(url) { + url = url.replace(/(\?.+)?(\/+)?$/u, ""); const regexes = [ - /^(?:https?:\/\/)?github\.com\/([^/]+)\/([^/]+)$/, - /^(?:https?:\/\/)?github\.com\/([^/]+)\/([^/]+)\/tree\/([^/]+)(\/.*)?$/, - /^(?:https?:\/\/)?raw\.githubusercontent\.com\/([^/]+)\/([^/]+)\/refs\/heads\/([^/]+)(\/.*)?$/, - /^(?:https?:\/\/)?raw\.githubusercontent\.com\/([^/]+)\/([^/]+)\/([^/]+)(\/.*)?$/, - /^([^/]+)\/([^/]+)\/tree\/([^/]+)(\/.*)?$/, - /^([^/]+)\/([^/]+)$/, + /^(?:https?:\/\/)?github\.com\/([^/]+)\/([^/]+)$/u, + /^(?:https?:\/\/)?github\.com\/([^/]+)\/([^/]+)\/tree\/([^/]+)(\/.*)?$/u, + /^(?:https?:\/\/)?github\.com\/([^/]+)\/([^/]+)\/commit\/([^/]+)(\/.*)?$/u, + /^(?:https?:\/\/)?raw\.githubusercontent\.com\/([^/]+)\/([^/]+)\/refs\/heads\/([^/]+)(\/.*)?$/u, + /^(?:https?:\/\/)?raw\.githubusercontent\.com\/([^/]+)\/([^/]+)\/([^/]+)(\/.*)?$/u, + /^([^/]+)\/([^/]+)\/tree\/([^/]+)(\/.*)?$/u, + /^([^/]+)\/([^/]+)\/commit\/([^/]+)(\/.*)?$/u, + /^([^/]+)\/([^/]+)$/u, ]; - for (const regex of regexes) { + for (const [idx, regex] of regexes.entries()) { const match = url.match(regex); if (match) { const author = match[1]; @@ -642,7 +748,11 @@ class Manager { let branch = "main"; let folder = ""; if (match.length > 3) { - branch = match[3]; + if (idx === 2 || idx === 6) { + branch = match[3]; + } else { + branch = `refs/heads/${match[3]}`; + } folder = match[4] || ""; } @@ -650,7 +760,7 @@ class Manager { name: repo, author, branch, - folder: folder.replace(/^\/+/, ""), + folder: folder.replace(/^\/+/u, ""), }; } } @@ -658,11 +768,21 @@ class Manager { throw new Error("[Sine]: Unknown GitHub repo format, unable to parse."); } - findFile(modId, fileNames, modEntries, repo, customUrl) { + /** + * Finds a singular file entrypoint from a list of possible matches. + * + * @param {string} modId - Id of mod to filter with. + * @param {string[]} fileNames - List of file names to search for. + * @param {string[]} modEntries - List of item entries. + * @param {object} repo - Repository metadata. (fetched from this.parseGitHubUrl) + * @param {string | null} customUrl - Custom file name to search for. + * @returns {string} Valid and matching file path to use. + */ + static findFile(modId, fileNames, modEntries, repo, customUrl) { const repoFolder = repo.folder ? `${repo.folder}/` : ""; const fileEntries = modEntries.filter( (entry) => - (fileNames.filter((name) => entry.endsWith(name)).length !== 0 && + (fileNames.some((name) => entry.endsWith(name)) && entry.startsWith(`${modId}/${repoFolder}`)) || entry === `${modId}/${repoFolder}${customUrl}` ); @@ -693,17 +813,26 @@ class Manager { return relativePath.replace(`${modId}/`, ""); } + /** + * Syncs a mod data (updates or installs), given mod metadata. + * + * @param {string} repoLink - Link to repository to fetch from. + * @param {object} currModsList - List of currently installed mods and their metadata. + * @param {object} newThemeData - New mod metadata to apply. (fetched via repoLink) + * @param {object | null} currModData - Previous metadata. (applicable in updates) + * @returns {boolean} Returns whether the installed mod has JS. + */ async syncModData(repoLink, currModsList, newThemeData, currModData = false) { const themeFolder = utils.getModFolder(newThemeData.id); - const nestedPath = `main/mods/${newThemeData.id}`; if (repoLink === "{store}") { - repoLink = `sineorg/store/tree/${nestedPath}`; + const repoData = this.constructor.parseGitHubUrl(newThemeData.homepage); + repoLink = `${repoData.author}/${repoData.name}/commit/${newThemeData.commit}/${repoData.folder}`; newThemeData.origin = "store"; } else if (newThemeData.origin) { // Prevent mods from pretending to be verified and from the store. delete newThemeData.origin; } - let repo = this.parseGitHubUrl(repoLink); + let repo = this.constructor.parseGitHubUrl(repoLink); const tmpFolder = PathUtils.join(utils.modsDir, `tmp-${currModData.id}`); if (currModData) { @@ -711,11 +840,7 @@ class Manager { await IOUtils.remove(themeFolder, { recursive: true }); } - let zipUrl = `https://codeload.github.com/${repo.author}/${repo.name}/zip/refs/heads/${repo.branch}`; - if (newThemeData.origin === "store") { - repo = this.parseGitHubUrl(newThemeData.homepage); - zipUrl = `https://raw.githubusercontent.com/sineorg/store/${nestedPath}/mod.zip`; - } + let zipUrl = `https://codeload.github.com/${repo.author}/${repo.name}/zip/${repo.branch}`; const zipEntries = await ucAPI.unpackRemoteArchive({ url: zipUrl, id: newThemeData.id, @@ -748,7 +873,7 @@ class Manager { const normalizePath = (value) => typeof value === "string" && value.startsWith("https://") - ? this.parseGitHubUrl(value).folder + ? this.constructor.parseGitHubUrl(value).folder : value; customChrome = normalizePath(customChrome); @@ -756,14 +881,14 @@ class Manager { const customPreferences = normalizePath(preferences); newThemeData.style = {}; - newThemeData.style.chrome = this.findFile( + newThemeData.style.chrome = this.constructor.findFile( newThemeData.id, ["userChrome.css", "chrome.css"], zipEntries, repo, customChrome ); - newThemeData.style.content = this.findFile( + newThemeData.style.content = this.constructor.findFile( newThemeData.id, ["userContent.css"], zipEntries, @@ -771,7 +896,7 @@ class Manager { customContent ); - newThemeData.preferences = this.findFile( + newThemeData.preferences = this.constructor.findFile( newThemeData.id, ["preferences.json"], zipEntries, @@ -822,6 +947,13 @@ class Manager { return false; } + /** + * Toggles a mod to be enabled or disabled. + * + * @param {object} installedMods - Current list of installed mod metadata. + * @param {string} id - Id of mod to toggle. + * @returns {object} New mod metadata. + */ async toggleTheme(installedMods, id) { const themeData = installedMods[id]; @@ -843,9 +975,15 @@ class Manager { return themeData; } - translateToAPI(input) { - const trimmedInput = input.trim().replace(/\/+$/, ""); - const regex = /(?:https?:\/\/github\.com\/)?([\w\-.]+)\/([\w\-.]+)/i; + /** + * Parses a standard GitHub link into an API link. + * + * @param {string} input - GitHub link to parse. + * @returns {string} API version of link. + */ + static translateToAPI(input) { + const trimmedInput = input.trim().replace(/\/+$/u, ""); + const regex = /(?:https?:\/\/github\.com\/)?([\w\-.]+)\/([\w\-.]+)/iu; const match = trimmedInput.match(regex); if (!match) { return null; @@ -855,6 +993,17 @@ class Manager { return `https://api.github.com/repos/${user}/${returnRepo}`; } + /** + * Generates a list of mod metadata based on specified parameters from a repo and defaults. + * + * @param {string} repo - Repo link which data was fetched from. + * @param {object} themes - List of currently installed mod data. + * @param {object} theme - Specified mod data. (defaults to an empty object) + * @param {boolean} minimal - Whether generated data should be minimal. + * @param {object | null} githubAPI - Optional GitHub API fetched from a previous minimal call. + * @returns {object} If minimal is true, an object of theme data and the GitHub API, or just theme + * data. + */ async createThemeJSON(repo, themes, theme = {}, minimal = false, githubAPI = null) { const apiRequiringProperties = minimal ? ["updatedAt"] : ["description", "updatedAt"]; let needAPI = false; @@ -864,7 +1013,7 @@ class Manager { } } if (needAPI && !githubAPI) { - githubAPI = ucAPI.fetch(translateToAPI(repo)); + githubAPI = ucAPI.fetch(this.constructor.translateToAPI(repo)); } let promise; @@ -886,12 +1035,14 @@ class Manager { setProperty("homepage", repo); - const parsedRepo = this.parseGitHubUrl(repo); + const parsedRepo = this.constructor.parseGitHubUrl(repo); setProperty("name", parsedRepo.folder || parsedRepo.name); if (!Object.hasOwn(theme, "version")) { promise = (async () => { - const releasesData = await ucAPI.fetch(`${translateToAPI(repo)}/releases/latest`); + const releasesData = await ucAPI.fetch( + `${this.constructor.translateToAPI(repo)}/releases/latest` + ); setProperty( "version", Object.hasOwn(releasesData, "tag_name") diff --git a/src/core/preferences.sys.mjs b/src/core/preferences.sys.mjs index 74921326..8dd88e11 100644 --- a/src/core/preferences.sys.mjs +++ b/src/core/preferences.sys.mjs @@ -1,4 +1,6 @@ /** + * @file Defines mod preference management. + * @license * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. @@ -10,7 +12,7 @@ // =========================================================== import utils from "./utils.sys.mjs"; -import domUtils from "../utils/dom.mjs"; +import * as domUtils from "../utils/dom.mjs"; import ucAPI from "../utils/uc_api.sys.mjs"; const tagNames = { @@ -53,13 +55,13 @@ const evaluateConditions = (conditions, operator = "AND") => { return false; }); - return operator === "OR" ? results.some((r) => r) : results.every((r) => r); + return operator === "OR" ? results.some(Boolean) : results.every(Boolean); }; const updatePrefVisibility = (pref, document) => { const identifier = pref.id ?? pref.property; - const targetId = identifier.replace(/\./g, "-"); - const element = document.getElementById(targetId); + const targetId = identifier.replaceAll(".", "-"); + const element = document.querySelector("#" + targetId); if (element) { const shouldShow = evaluateConditions(pref.conditions, pref.operator || "OR"); @@ -67,14 +69,21 @@ const updatePrefVisibility = (pref, document) => { } }; +/** + * Sets up pref observers necessary for a mod preference's conditionals to function. + * + * @param {object} pref - Mod preference that contains the conditional observers. + * @param {Window} window - Window that preference is loaded in. + * @returns {object} Observer object used in listening. + */ export const setupPrefObserver = (pref, window) => { const document = window.document; const identifier = pref.id ?? pref.property; - const targetId = identifier.replace(/\./g, "-"); + const targetId = identifier.replaceAll(".", "-"); // Initially hide the element - const element = document.getElementById(targetId); + const element = document.querySelector("#" + targetId); if (element) { element.style.display = "none"; } @@ -84,14 +93,14 @@ export const setupPrefObserver = (pref, window) => { const collectProps = (conditions) => { const condArray = Array.isArray(conditions) ? conditions : [conditions]; - condArray.forEach((cond) => { + for (const cond of condArray) { if (cond.if || cond.not) { const condition = cond.if || cond.not; propsToObserve.add(condition.property); } else if (cond.conditions) { collectProps(cond.conditions); } - }); + } }; collectProps(pref.conditions); @@ -100,20 +109,20 @@ export const setupPrefObserver = (pref, window) => { const observer = { observe: (_, topic, data) => { if (topic === "nsPref:changed" && propsToObserve.has(data)) { - th.updatePrefVisibility(pref, document); + updatePrefVisibility(pref, document); } }, }; // Add observers for each property - propsToObserve.forEach((prop) => { + for (const prop of propsToObserve) { Services.prefs.addObserver(prop, observer); - }); + } window.addEventListener("beforeunload", () => { - propsToObserve.forEach((prop) => { + for (const prop of propsToObserve) { Services.prefs.removeObserver(prop, observer); - }); + } }); // Initial visibility check @@ -141,7 +150,7 @@ const buildPrefElement = (pref, document) => { const foundId = pref.id || pref.property; if (foundId) { - prefEl.id = foundId.replace(/\./g, "-"); + prefEl.id = foundId.replaceAll(".", "-"); } if (pref.label) { @@ -273,11 +282,11 @@ const applyDropdown = (pref, prefEl, ctx, manager, window) => { ` ${ - pref.placeholder !== false - ? ` + pref.placeholder === false + ? "" + : ` ` - : "" } ${pref.options .map((option) => ``) @@ -341,6 +350,9 @@ const applyPref = (pref, prefEl, manager, window) => { case "dropdown": applyDropdown(pref, prefEl, ctx, manager, window); break; + default: + console.error(`[Sine:Prefs]: Unknown pref type '${pref.type}', ignoring.`); + break; } if (pref.conditions) { @@ -348,6 +360,14 @@ const applyPref = (pref, prefEl, manager, window) => { } }; +/** + * Parses a singular mod preference. + * + * @param {object} pref - Mod preference to parse. + * @param {object} manager - Manager singleton class instance. + * @param {Window} window - Window that the preference will be injected in. + * @returns {Element} HTML/XUL element that can be easily appended to the DOM. + */ export const parsePref = (pref, manager, window) => { if (!validatePref(pref)) return null; // This function has several side-effects that affect pref properties diff --git a/src/core/settings.mjs b/src/core/settings.mjs index 9dcd3daf..54e8650f 100644 --- a/src/core/settings.mjs +++ b/src/core/settings.mjs @@ -1,16 +1,13 @@ /** + * @file Injects Sine into a settings page instance. + * @license * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -// =========================================================== -// Manages a settings page instance, injects Sine, and gives -// the user access to essential preferences. -// =========================================================== - -import domUtils from "../utils/dom.mjs"; -import injectCmdPalette from "../services/cmdPalette.js"; +import * as domUtils from "../utils/dom.mjs"; +import cmdPalette from "../services/cmdPalette.mjs"; import updates from "../services/updates.mjs"; const ucAPI = ChromeUtils.importESModule( @@ -23,8 +20,18 @@ delete window.manager; if (ucAPI.utils.fork === "zen") { document.querySelector("#category-zen-marketplace").remove(); - domUtils.waitForElm("#ZenMarketplaceCategory").then((el) => el.remove()); - domUtils.waitForElm("#zenMarketplaceGroup").then((el) => el.remove()); + domUtils + .waitForElm("#ZenMarketplaceCategory") + .then((el) => el.remove()) + .catch((err) => + console.error(`[Sine:Settings]: Failed to remove Zen marketplace category.\n${err}`) + ); + domUtils + .waitForElm("#zenMarketplaceGroup") + .then((el) => el.remove()) + .catch((err) => + console.error(`[Sine:Settings]: Failed to remove Zen marketplace group.\n${err}`) + ); } // Inject settings styles and locales @@ -70,15 +77,16 @@ if (generalCategory.tagName === "html:moz-page-nav-button") { // Add Sine to the initaliztion object gCategoryInits.set("paneSineMods", { _initted: true, + // eslint-disable-next-line no-empty-function init: () => {}, }); if (location.hash === "#zenMarketplace" || location.hash === "#sineMods") { sineIsActive = true; document.querySelector("#categories").selectItem(sineTab); - document.querySelectorAll('[data-category="paneGeneral"]').forEach((el) => { + for (const el of document.querySelectorAll('[data-category="paneGeneral"]')) { el.setAttribute("hidden", "true"); - }); + } } const sineGroupData = `data-category="paneSineMods" ${sineIsActive ? "" : 'hidden="true"'}`; @@ -223,13 +231,12 @@ const loadPrefs = async () => { if (pref.property === "sine.enable-dev") { prefEl.addEventListener("click", () => { - const commandPalette = - windowRoot.ownerGlobal.document.querySelector(".sineCommandPalette"); + const commandPalette = windowRoot.window.document.querySelector(".sineCommandPalette"); if (commandPalette) { commandPalette.remove(); } - injectCmdPalette(); + cmdPalette.register(); }); } @@ -237,7 +244,7 @@ const loadPrefs = async () => { ".sineItemPreferenceDialogContent" ); if (prefEl) { - newSettingsContent.appendChild(prefEl); + newSettingsContent.append(prefEl); } else if (pref.type === "button") { const getVersionLabel = () => `Current: ${utils.escapeHTML(updates.current)} | ` + @@ -274,13 +281,15 @@ const loadPrefs = async () => { prefEl.children[1].addEventListener("click", () => { buttonTrigger(async () => { await updates.checkForUpdates(); - Array.from(document.querySelectorAll("#version-indicator b")).forEach((el, idx) => { + for (const [idx, el] of document + .querySelectorAll("#version-indicator b") + .entries()) { if (idx === 0) { el.textContent = updates.current; } else { el.textContent = updates.latest; } - }); + } if (updates.current !== updates.latest) { newSettingsContent.querySelector("#install-update").style.display = "flex"; @@ -291,12 +300,12 @@ const loadPrefs = async () => { prefEl = domUtils.appendXUL( newSettingsContent, ` - - ` + + ` ); // eslint-disable-next-line consistent-function-scoping - let action = () => {}; + let action = () => console.error("[Sine:Settings]: Invalid button action."); if (pref.id === "restart") { action = ucAPI.utils.restart; } else if (pref.id === "install-update") { @@ -376,10 +385,9 @@ const groupToggle = document.querySelector(".sinePreferenceToggle"); groupToggle.addEventListener("toggle", () => { modsDisabled = !Services.prefs.getBoolPref("sine.mods.disable-all", false); Services.prefs.setBoolPref("sine.mods.disable-all", modsDisabled); - groupToggle.setAttribute( - "data-l10n-id", - modsDisabled ? "sine-mods-disable-all-disabled" : "sine-mods-disable-all-enabled" - ); + groupToggle.dataset.l10nId = modsDisabled + ? "sine-mods-disable-all-disabled" + : "sine-mods-disable-all-enabled"; manager.rebuildMods(); manager.loadMods(); }); @@ -391,10 +399,7 @@ autoUpdateButton.addEventListener("click", () => { } else { autoUpdateButton.removeAttribute("enabled"); } - autoUpdateButton.setAttribute( - "data-l10n-id", - `sine-mods-auto-update-${utils.autoUpdate ? "enabled" : "disabled"}` - ); + autoUpdateButton.dataset.l10nId = `sine-mods-auto-update-${utils.autoUpdate ? "enabled" : "disabled"}`; }); if (utils.autoUpdate) { autoUpdateButton.setAttribute("enabled", true); @@ -505,7 +510,7 @@ document.querySelector("#sineModExport").addEventListener("click", async () => { temporalAnchor.href = temporalUrl; temporalAnchor.download = "sine-mods-export.json"; - document.body.appendChild(temporalAnchor); + document.body.append(temporalAnchor); temporalAnchor.click(); temporalAnchor.remove(); } catch (error) { diff --git a/src/core/utils.sys.mjs b/src/core/utils.sys.mjs index bb4ab732..8e7054ad 100644 --- a/src/core/utils.sys.mjs +++ b/src/core/utils.sys.mjs @@ -1,57 +1,111 @@ /** + * @file Mod-related utilities designed for other scripts. + * @license * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -// =========================================================== -// Handles utilities that are necessary for Sine to determine -// where to place files and perform common actions. -// =========================================================== - import ucAPI from "../utils/uc_api.sys.mjs"; export default { + /** + * Returns the current installation branch status. + * + * @returns {boolean} True if the update branch is Cosine, false if Sine. + */ get cosine() { return Services.prefs.getBoolPref("sine.is-cosine", false); }, + /** + * Returns the brand to use based on current branch. + * + * @returns {string} "cosine" if branch is Cosine, "sine" if Sine. + */ get brand() { return this.cosine ? "cosine" : "sine"; }, + /** + * Returns git branch to fetch based on update branch. + * + * @returns {string} "cosine" if branch is Cosine, "main" if Sine. + */ get sineBranch() { return Services.prefs.getBoolPref("sine.is-cosine", false) ? "cosine" : "main"; }, + /** + * Returns the path to the JS directory. + * + * @returns {string} Path to JS directory. + */ get jsDir() { return PathUtils.join(ucAPI.utils.chromeDir, "JS"); }, + /** + * Returns the path to the mods directory. + * + * @returns {string} Path to mods directory. + */ get modsDir() { return PathUtils.join(ucAPI.utils.chromeDir, "sine-mods"); }, + /** + * Returns the path to the main userChrome style entrypoint. + * + * @returns {string} Path to userChrome entrypoint. + */ get chromeFile() { return PathUtils.join(this.modsDir, "chrome.css"); }, + /** + * Returns the path to the main userContent style entrypoint. + * + * @returns {string} Path to userContent entrypoint. + */ get contentFile() { return PathUtils.join(this.modsDir, "content.css"); }, + /** + * Returns the path to the installed mods metadata JSON file. + * + * @returns {string} Path to metadata JSON file. + */ get modsDataFile() { return PathUtils.join(this.modsDir, "mods.json"); }, + /** + * Returns the path to a mod's folder based on it's id. + * + * @param {string} id - Mod id to get the folder of. + * @returns {string} Path to mod folder. + */ getModFolder(id) { return PathUtils.join(this.modsDir, id); }, + /** + * Returns the installed mods metadata JSON. + * + * @returns {object} Installed mods metadata. + */ async getMods() { return await IOUtils.readJSON(this.modsDataFile); }, + /** + * Returns the JSON preferences for a mod. + * + * @param {object} mod - Mod metadata, including the name and id. + * @returns {object[]} The list of mod preferences. + */ async getModPreferences(mod) { try { return await IOUtils.readJSON( @@ -67,11 +121,17 @@ export default { } }, + /** + * Returns a raw GitHub user content link when given a repo link. + * + * @param {string} repo - GitHub repository link. + * @returns {string} Raw asset version of the repository link. + */ rawURL(repo) { if (repo.startsWith("[") && repo.endsWith(")") && repo.includes("](")) { - repo = repo.replace(/^\[[a-z]+\]\(/i, "").replace(/\)$/, ""); + repo = repo.replace(/^\[[a-z]+\]\(/iu, "").replace(/\)$/u, ""); } - repo = repo.replace(/^https:\/\/github.com\//, ""); + repo = repo.replace(/^https:\/\/github.com\//u, ""); let repoName; let branch; let folders = []; @@ -87,26 +147,34 @@ export default { folders = branchAndPath.slice(1).filter((folder) => folder !== ""); // Remove trailing slash from last folder if present - if (folders.length !== 0 && folders[folders.length - 1].endsWith("/")) { - folders[folders.length - 1] = folders[folders.length - 1].slice(0, -1); + const lastFolder = folders.at(folders.length - 1); + if (folders.length !== 0 && lastFolder.endsWith("/")) { + folders[folders.length - 1] = lastFolder.slice(0, -1); } } } else { branch = "main"; // Default branch if not specified // If there is no folder, use the whole repo name if (repo.endsWith("/")) { - repoName = repo.substring(0, repo.length - 1); + repoName = repo.slice(0, repo.length - 1); } else { repoName = repo; } } // Construct the folder path - const folderPath = folders.length !== 0 ? `/${folders.join("/")}` : ""; + const folderPath = folders.length === 0 ? "" : `/${folders.join("/")}`; return `https://raw.githubusercontent.com/${repoName}/${branch}${folderPath}/`; }, + /** + * Returns an array of processes that match required pathnames. + * + * @param {Window} window - Optional window to return. + * @param {string[]} processes - Array of process pathnames that must be matched. + * @returns {Array} Array of matching prcoesses. + */ getProcesses(window = null, processes = null) { if (window) { return [window]; @@ -138,49 +206,92 @@ export default { return pages; }, + /** + * Returns auto-update enabled status. + * + * @returns {boolean} Auto-update status. If true, enabled. If false, disabled. + */ get autoUpdate() { return Services.prefs.getBoolPref("sine.auto-updates", true); }, + /** + * Setter function for auto-update enabled status. + * + * @param {boolean} value - If true, will enable auto-updates, and vice-versa if false. + */ set autoUpdate(value) { Services.prefs.setBoolPref("sine.auto-updates", value); }, + /** + * Returns the enabled status of the unsafe JS preference. + * + * @returns {boolean} Status of unsafe JS preference. + */ get allowUnsafeJS() { return Services.prefs.getBoolPref("sine.allow-unsafe-js", false); }, + /** + * Escapes/sanitizes insecure HTML. + * + * @param {string} html - HTML to sanitize. + * @returns {string} Sanitized HTML. + */ escapeHTML(html) { return html - .replace(/&/g, "&") - .replace(//g, ">") - .replace(/"/g, """) - .replace(/'/g, "'"); + .replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">") + .replaceAll('"', """) + .replaceAll("'", "'"); }, - formatLabel(label) { - const ESCAPED_BOLD = "\uE001"; - const ESCAPED_ITALIC = "\uE002"; - const ESCAPED_UNDERLINE = "\uE003"; - - const escapeMarkdownChar = (c) => { - if (c === "**") return ESCAPED_BOLD; - if (c === "*") return ESCAPED_ITALIC; - return ESCAPED_UNDERLINE; - }; + /** + * Returns an escaped version of a markdown character. Used in formatLabel function. + * + * @param {string} char - Markdown character to escape. + * @returns {string} Escaped version of the markdown character. + */ + escapeMarkdownChar(char) { + if (char === "**") return "\uE001"; + if (char === "*") return "\uE002"; + return "\uE003"; + }, + /** + * Sanitizes and formats a preference label or other insecure labels. + * + * @param {string} label - Label to format and sanitize. + * @returns {string} Sanitized and formatted label HTML. + */ + formatLabel(label) { return this.escapeHTML(label) - .replace(/\\(\*\*|\*|~)/g, (_, c) => escapeMarkdownChar(c)) - .replace(/\*\*([^*]+)\*\*/g, "$1") - .replace(/\*([^*]+)\*/g, "$1") - .replace(/~([^~]+)~/g, "$1") - .replace(new RegExp(ESCAPED_BOLD, "g"), "**") - .replace(new RegExp(ESCAPED_ITALIC, "g"), "*") - .replace(new RegExp(ESCAPED_UNDERLINE, "g"), "~") - .replace(/\n/g, "
"); + .replaceAll(/\\(\*\*|\*|~)/gu, (_, c) => this.escapeMarkdownChar(c)) + .replaceAll(/\*\*([^*]+)\*\*/gu, "$1") + .replaceAll(/\*([^*]+)\*/gu, "$1") + .replaceAll(/~([^~]+)~/gu, "$1") + .replaceAll("\uE001", "**") + .replaceAll("\uE002", "*") + .replaceAll("\uE003", "~") + .replaceAll("\n", "
"); }, + /** + * Returns a list of valid and existing scripts that can be injected into the DOM. + * + * @param {object} options - Options of what to fetch. + * @param {object} options.mods - Optional mods metadata, skips the mod fetching process if + * passed. + * @param {boolean} options.onlyEnabled - If enabled, will only return enabled scripts. (currently + * only checks mod enabled status) + * @param {boolean} options.removeBgModules - If enabled, will exclude background modules + * (.sys.mjs) from results. + * @param {string} options.href - Optional href that scripts must be loadable in to be returned. + * @returns {object} Object of scripts, with the key being the path to it from the mods folder, + * and the value being the properties of it. + */ async getScripts(options = {}) { const flattenPathStructure = (scripts, parentKey = "", modId = "", result = {}) => { for (const key in scripts) { @@ -195,12 +306,12 @@ export default { newKey.endsWith(".uc.js") ) { script.include = (script.include?.length ? script.include : [".*"]).map((p) => - p.replace(/\*/g, ".*?") + p.replaceAll("*", ".*?") ); let exclude = ""; if (script.exclude?.length) { - script.exclude = script.exclude.map((p) => p.replace(/\*/g, ".*?")); + script.exclude = script.exclude.map((p) => p.replaceAll("*", ".*?")); exclude = `(?!${script.exclude.join("$|")}$)`; } else { script.exclude = []; @@ -208,7 +319,7 @@ export default { const locationRegex = new RegExp( `^${exclude}(${script.include.join("|") || ".*"})$`, - "i" + "iu" ); if (!options.href || locationRegex.test(options.href)) { diff --git a/src/services/cmdPalette.js b/src/services/cmdPalette.mjs similarity index 77% rename from src/services/cmdPalette.js rename to src/services/cmdPalette.mjs index 7ceec501..a12ea7c7 100644 --- a/src/services/cmdPalette.js +++ b/src/services/cmdPalette.mjs @@ -1,15 +1,12 @@ /** + * @file Injects a command palette into the browser's main window. + * @license * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -// =========================================================== -// Injects an easy-to-use command palette that allows users -// to easily manage themes and mods. -// =========================================================== - -import domUtils from "../utils/dom.mjs"; +import * as domUtils from "../utils/dom.mjs"; const manager = ChromeUtils.importESModule( "chrome://userscripts/content/core/manager.sys.mjs" @@ -19,22 +16,28 @@ const ucAPI = ChromeUtils.importESModule( "chrome://userscripts/content/utils/uc_api.sys.mjs" ).default; -export default () => { - if (Services.prefs.getBoolPref("sine.enable-dev", false)) { - domUtils.injectLocale("sine-cmdpalette", windowRoot.ownerGlobal.document); +export default { + // TODO: Split into several functions + /** Registers and injects the command palette. */ + register() { + if (!Services.prefs.getBoolPref("sine.enable-dev", false)) { + return; + } + + domUtils.injectLocale("sine-cmdpalette", windowRoot.window.document); const palette = domUtils.appendXUL( - window.windowRoot.ownerGlobal.document.body, + windowRoot.window.document.body, ` - - ` + + ` ); const contentDiv = palette.querySelector(".sineCommandInput"); @@ -69,10 +72,10 @@ export default () => { const searchOptions = () => { for (const child of optionsContainer.children) { - if (!child.textContent.toLowerCase().includes(input.value.toLowerCase())) { - child.setAttribute("hidden", ""); - } else { + if (child.textContent.toLowerCase().includes(input.value.toLowerCase())) { child.removeAttribute("hidden"); + } else { + child.setAttribute("hidden", ""); } } optionsContainer.querySelector("[selected]")?.removeAttribute("selected"); @@ -94,7 +97,7 @@ export default () => { `` ); - optionBtn.setAttribute("data-l10n-id", option.id); + optionBtn.dataset.l10nId = option.id; optionBtn.addEventListener("click", () => { option.action(); @@ -129,7 +132,7 @@ export default () => { } }); - windowRoot.ownerGlobal.document.addEventListener("keydown", (e) => { + windowRoot.window.document.addEventListener("keydown", (e) => { if (e.ctrlKey && e.shiftKey && e.key === "Y") { refreshCmds(options); @@ -144,7 +147,7 @@ export default () => { } }); - windowRoot.ownerGlobal.document.addEventListener("mousedown", (e) => { + windowRoot.window.document.addEventListener("mousedown", (e) => { let targetEl = e.target; while (targetEl) { if (targetEl === palette) { @@ -155,5 +158,5 @@ export default () => { closePalette(); }); - } + }, }; diff --git a/src/services/injectAPI.js b/src/services/injectAPI.sys.mjs similarity index 77% rename from src/services/injectAPI.js rename to src/services/injectAPI.sys.mjs index 0b66d210..6fa48644 100644 --- a/src/services/injectAPI.js +++ b/src/services/injectAPI.sys.mjs @@ -1,14 +1,11 @@ /** + * @file Injects Sine store and Zen Mods store window actors. + * @license * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -// =========================================================== -// Injects an API that allows installing mods from the -// Zen Mods store and Sine store website. -// =========================================================== - try { ChromeUtils.registerWindowActor("SineModsMarketplace", { parent: { @@ -20,6 +17,7 @@ try { DOMContentLoaded: {}, }, }, + safeForUntrustedWebProcess: true, matches: [ "https://sineorg.github.io/store/*", "https://zen-browser.app/*", diff --git a/src/services/marketplace.sys.mjs b/src/services/marketplace.sys.mjs index 6c04cedc..7eed9b61 100644 --- a/src/services/marketplace.sys.mjs +++ b/src/services/marketplace.sys.mjs @@ -1,23 +1,26 @@ /** + * @file Manages the settings page marketplace display. + * @license * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -// =========================================================== -// Manages the marketplace display section in the settings -// page, and handles filtering through it. -// =========================================================== - import ucAPI from "../utils/uc_api.sys.mjs"; import utils from "../core/utils.sys.mjs"; -import domUtils from "../utils/dom.mjs"; +import * as domUtils from "../utils/dom.mjs"; export default { items: null, filteredItems: null, page: 0, + /** + * Loads the current page into the marketplace display. + * + * @param {Window} specificWindow - Custom window to load the page in. + * @param {object} manager - Instance of the singleton Manager class. + */ async loadPage(specificWindow = null, manager = null) { const pages = utils.getProcesses(specificWindow, ["settings", "preferences"]); const installedMods = await utils.getMods(); @@ -112,9 +115,9 @@ export default { const newOpenButton = newItem.querySelector(".sineMarketplaceOpenButton"); newOpenButton.addEventListener("click", async () => { const themeMD = await ucAPI.fetch(data.readme).catch((err) => console.error(err)); - const relativeURL = data.readme.substring(0, data.readme.lastIndexOf("/") + 1); + const relativeURL = data.readme.slice(0, data.readme.lastIndexOf("/") + 1); - domUtils.parseMD(newItem.querySelector(".markdown-body"), themeMD, relativeURL, window); + domUtils.parseMD(newItem.querySelector(".markdown-body"), themeMD, relativeURL); dialog.showModal(); }); } @@ -142,13 +145,13 @@ export default { const navContainer = domUtils.appendXUL( document.querySelector("#sineInstallationGroup"), ` - - - - - `, + + + + + `, document.querySelectorAll("#sineInstallationGroup .description-deemphasized")[1] ); @@ -169,6 +172,12 @@ export default { } }, + /** + * Initializes marketplace data. + * + * @param {Window} window - Custom window to initialize it in. + * @param {object} manager - Instance of the singleton Manager class. + */ async init(window = null, manager = null) { let marketplaceURL = "https://sineorg.github.io/store/marketplace.json"; if (Services.prefs.getBoolPref("sine.allow-external-marketplace", false)) { diff --git a/src/services/module_loader.mjs b/src/services/module_loader.mjs index f7b249fe..3b17c964 100644 --- a/src/services/module_loader.mjs +++ b/src/services/module_loader.mjs @@ -1,14 +1,11 @@ /** + * @file Injects necessary userscripts and Sine scripts into any DOM it's loaded in. + * @license * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -// =========================================================== -// Injects scripts into detected DOMs, including both Sine -// scripts and userscripts. -// =========================================================== - { // eslint-disable-next-line consistent-function-scoping const importScript = (script) => { diff --git a/src/services/stylesheets.sys.mjs b/src/services/stylesheets.sys.mjs index 736af6bf..1725c598 100644 --- a/src/services/stylesheets.sys.mjs +++ b/src/services/stylesheets.sys.mjs @@ -1,23 +1,53 @@ /** + * @file Loads and manages stylesheets in DOMs. + * @license * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -// =========================================================== -// Loads and manages stylesheets in DOMs, as well as building -// a DOM for reading preferences. -// =========================================================== - import utils from "../core/utils.sys.mjs"; import ucAPI from "../utils/uc_api.sys.mjs"; -import domUtils from "../utils/dom.mjs"; +import * as domUtils from "../utils/dom.mjs"; +/** + * Main class for managing stylesheets. + * + * @class + */ class StylesheetManager { #chromeURI; #stylesheetData = {}; #modPrefs = {}; + /** Initializes the Sine listener for userContent emulation. */ + static initContentListener() { + try { + ChromeUtils.registerWindowActor("SineUserContent", { + child: { + esModuleURI: "chrome://userscripts/content/services/usercontent.sys.mjs", + events: { + DOMWindowCreated: {}, + }, + }, + allFrames: true, + matchesTarget: "content", + }); + } catch (err) { + console.warn(`Failed to register JSWindowActor: ${err}`); + } + } + + /** Initializes the class. */ + constructor() { + this.constructor.initContentListener(); + } + + /** + * Rebuilds entrypoint stylesheets (optionally), and updates stored preferences for mods. + * + * @param {boolean} writeStyles - If true, will write to entrypoint stylesheets. + */ async #rebuildStylesheets(writeStyles = true) { const installedMods = await utils.getMods(); @@ -25,7 +55,7 @@ class StylesheetManager { chrome: "", content: "", }; - this.#modPrefs = {}; + const modPrefs = {}; const promises = []; for (const id of Object.keys(installedMods).toSorted()) { @@ -43,7 +73,7 @@ class StylesheetManager { if (mod.preferences) { promises.push( (async () => { - this.#modPrefs[mod.name] = await utils.getModPreferences(mod); + modPrefs[mod.name] = await utils.getModPreferences(mod); })() ); } @@ -51,6 +81,8 @@ class StylesheetManager { } await Promise.all(promises); + this.#modPrefs = modPrefs; + if (writeStyles) { await IOUtils.writeUTF8(utils.chromeFile, data.chrome); await IOUtils.writeUTF8(utils.contentFile, data.content); @@ -62,64 +94,78 @@ class StylesheetManager { } } - #rebuildDOM(document) { - if (document) { - document.querySelectorAll(".sine-theme-strings, .sine-theme-styles").forEach((el) => { - el.remove(); + /** + * Rebuilds the DOM as it pertains to mods. This includes custom CSS variables and elements that + * store string-based mod pref values. + * + * @param {HTMLDocument} document - Document to rebuild mod DOM in. + */ + async #rebuildDOM(document) { + if (!document) { + return; + } + + if (!document.body) { + await new Promise((resolve) => { + document.addEventListener("DOMContentLoaded", resolve, { once: true }); }); + } + + for (const el of document.querySelectorAll(".sine-theme-strings, .sine-theme-styles")) { + el.remove(); + } - for (const name of Object.keys(this.#modPrefs)) { - const modPrefs = this.#modPrefs[name]; + for (const name of Object.keys(this.#modPrefs)) { + const modPrefs = this.#modPrefs[name]; - const themeSelector = `theme-${name.replace(/\s/g, "-")}`; + const themeSelector = `theme-${name.replaceAll(" ", "-")}`; - const rootPrefs = Object.values(modPrefs).filter( - (pref) => - pref.type === "dropdown" || - (pref.type === "string" && pref.processAs && pref.processAs === "root") + const rootPrefs = Object.values(modPrefs).filter( + (pref) => + pref.type === "dropdown" || + (pref.type === "string" && pref.processAs && pref.processAs === "root") + ); + if (rootPrefs.length) { + const themeEl = domUtils.appendXUL( + document.body, + `
` ); - if (rootPrefs.length) { - const themeEl = domUtils.appendXUL( - document.body, - `
` - ); - for (const pref of rootPrefs) { - if (Services.prefs.getPrefType(pref.property) > 0) { - const prefName = pref.property.replace(/\./g, "-"); - themeEl.setAttribute(prefName, ucAPI.prefs.get(pref.property)); - } + for (const pref of rootPrefs) { + if (Services.prefs.getPrefType(pref.property) > 0) { + const prefName = pref.property.replaceAll(".", "-"); + themeEl.setAttribute(prefName, ucAPI.prefs.get(pref.property)); } } + } - const varPrefs = Object.values(modPrefs).filter( - (pref) => - (pref.type === "dropdown" && pref.processAs && pref.processAs.includes("var")) || - pref.type === "string" - ); - if (varPrefs.length) { - const themeEl = domUtils.appendXUL( - document.head, - ` - - ` - ); - - for (const pref of varPrefs) { - if (Services.prefs.getPrefType(pref.property) > 0) { - const prefName = pref.property.replace(/\./g, "-"); - themeEl.textContent += `--${prefName}: ${ucAPI.prefs.get(pref.property)};`; - } + const varPrefs = Object.values(modPrefs).filter( + (pref) => + (pref.type === "dropdown" && pref.processAs && pref.processAs.includes("var")) || + pref.type === "string" + ); + if (varPrefs.length) { + let cssText = `:root {\n`; + for (const pref of varPrefs) { + if (Services.prefs.getPrefType(pref.property) > 0) { + const prefName = pref.property.replaceAll(".", "-"); + cssText += ` --${prefName}: ${ucAPI.prefs.get(pref.property)};\n`; } - - themeEl.textContent += "}"; } + cssText += `}`; + + const sheet = new document.defaultView.CSSStyleSheet(); + sheet.replaceSync(cssText); + document.adoptedStyleSheets.push(sheet); } } } + /** + * Applies the main userChrome stylesheet in a window. + * + * @param {Window} window - Window to apply stylesheet to. + */ async #applyToChromeWindow(window) { if (window?.windowUtils) { try { @@ -136,6 +182,12 @@ class StylesheetManager { } } + /** + * Handles new chrome windows by building the mod DOM and applying styles to it. + * + * @param {Event} event - Event containing the window. + * @param {boolean} reloadStyles - If true, will load styles into window. + */ handleEvent(event, reloadStyles) { if (reloadStyles) { this.#applyToChromeWindow(event.target.defaultView); @@ -143,66 +195,39 @@ class StylesheetManager { this.#rebuildDOM(event.target); } - listen(win, reloadStyles) { - if (win.document.readyState === "complete") { - this.handleEvent({ target: win.document }, reloadStyles); - } else { - win.addEventListener("DOMContentLoaded", (e) => this.handleEvent(e, reloadStyles), { - once: true, - }); - } - } - + /** + * Reloads all mod stylesheets (optionally) and DOMs. + * + * @param {boolean} reloadStyles - If true, will reload styles. + */ async rebuildMods(reloadStyles = true) { await this.#rebuildStylesheets(reloadStyles); - const ss = Cc["@mozilla.org/content/style-sheet-service;1"].getService(Ci.nsIStyleSheetService); - - const chromeDir = Services.dirsvc.get("UChrm", Ci.nsIFile); - - const cssConfigs = ["chrome", "content"]; + try { + this.#chromeURI = Services.io.newURI("chrome://sine/content/chrome.css"); - for (const config of cssConfigs) { - try { - const cssPath = chromeDir.clone(); - cssPath.append("sine-mods"); - cssPath.append(`${config}.css`); - - if (config === "chrome") { - this.#chromeURI = Services.io.newFileURI(cssPath); - - const windows = Services.wm.getEnumerator(null); - while (windows.hasMoreElements()) { - const window = windows.getNext(); - this.listen(window, reloadStyles); - - for (let i = 0; i < window.frames.length; i++) { - const frame = window[i]; - if (frame.location.href.startsWith("chrome://")) { - this.listen(frame, reloadStyles); - } - } - } - } else if (reloadStyles) { - const cssURI = Services.io.newFileURI(cssPath); - - if (ss.sheetRegistered(cssURI, ss.USER_SHEET)) { - ss.unregisterSheet(cssURI, ss.USER_SHEET); - } - - if (this.#stylesheetData.content) { - ss.loadAndRegisterSheet(cssURI, ss.USER_SHEET); - } - } - } catch (ex) { - console.error(`Failed to reload ${config}:`, ex); + const windows = Services.wm.getEnumerator(null); + while (windows.hasMoreElements()) { + const window = windows.getNext(); + this.handleEvent({ target: window.document }, reloadStyles); } + + Services.ppmm.broadcastAsyncMessage("RebuildUserStyles", {}); + } catch (ex) { + console.error(`Failed to reload styles:`, ex); } } + /** + * Handles new chrome windows directly, applying styles and DOMs to it. + * + * @param {Window} window - Window to listen on. + */ onWindow(window) { if (this.#chromeURI && window.location.href.startsWith("chrome://")) { - this.#rebuildStylesheets(false).then(() => this.#rebuildDOM(window.document)); + this.#rebuildStylesheets(false) + .then(() => this.#rebuildDOM(window.document)) + .catch((err) => console.error(err)); this.#applyToChromeWindow(window); } } diff --git a/src/services/updates.mjs b/src/services/updates.mjs index 1394333c..c60fd75e 100644 --- a/src/services/updates.mjs +++ b/src/services/updates.mjs @@ -1,14 +1,11 @@ /** + * @file Manages Sine auto-updates. + * @license * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -// =========================================================== -// Manages automatic and manually triggered updates for Sine, -// as well as handling the auto-installer and zip updating. -// =========================================================== - const ucAPI = ChromeUtils.importESModule( "chrome://userscripts/content/utils/uc_api.sys.mjs" ).default; @@ -19,13 +16,22 @@ export default { updaterName: `updater.${ucAPI.utils.os === "win" ? "bat" : "sh"}`, tmpFolder: PathUtils.join(ucAPI.utils.chromeDir, "tmp"), + /** + * Converts a version tag into an array of version numbers. + * + * @param {string} version - Version tag. + * @returns {Array} Array of version numbers. + */ convertToParts(version) { - return version - .replace("c", "") - .split(".") - .map((part) => Number(part)); + return version.replace("c", "").split(".").map(Number); }, + /** + * Converts a version tag into one to be displayed on a toast. + * + * @param {string} version - Version tag. + * @returns {string} Readable version tag. + */ toReadable(version) { const cosineStr = version.endsWith("c") ? "c" : ""; @@ -40,6 +46,7 @@ export default { return parts.join(".") + cosineStr; }, + /** Initializes updates object and update branch. */ async init() { if (this.current) return; @@ -51,6 +58,12 @@ export default { } }, + /** + * Performs a zip-based update. + * + * @param {object} engine - Entire engine.json object, including the update. + * @param {object} update - Update to be installed. + */ async zipUpdate(engine, update) { const engineLink = engine.releaseLink.replace("{version}", update.version) + @@ -84,6 +97,12 @@ export default { } }, + /** + * Updates Sine with it's auto-updater. + * + * @param {object} engine - Entire engine.json object, including the update. + * @param {object} update - Update to be installed. + */ async execUpdate(engine, update) { const updateLink = engine.releaseLink.replace("{version}", update.version) + this.updaterName; try { @@ -137,10 +156,10 @@ export default { // Wait until updater is complete using identifier. await new Promise((resolve) => { const checkExistence = async () => { - if (!(await IOUtils.exists(identifierPath))) { - resolve(); - } else { + if (await IOUtils.exists(identifierPath)) { setTimeout(checkExistence, 500); + } else { + resolve(); } }; checkExistence(); @@ -153,6 +172,14 @@ export default { } }, + /** + * Updates the Sine engine, delegating the process to either a zip-based update or an + * installer-based one. + * + * @param {object} engine - Entire engine.json object, including the update. + * @param {object} update - Update to be installed. + * @returns {boolean} Always true if update succeeded. + */ async updateEngine(engine, update) { Services.appinfo.invalidateCachesOnRestart(); @@ -175,6 +202,11 @@ export default { return true; }, + /** + * Returns latest engine data from the Sine repository. + * + * @returns {object} Latest engine data. + */ async fetch() { return await ucAPI .fetch( @@ -185,7 +217,13 @@ export default { .catch((err) => console.warn(err)); }, - // Determines if a semantic version is newer than another. + /** + * Determines if a semantic vesrion is newer than another. + * + * @param {string} newVersion - Tag of version to check. + * @param {string} originalVersion - Tag of current version. + * @returns {boolean} True if version to check is newer than current. + */ isNewer(newVersion, originalVersion) { newVersion = this.convertToParts(newVersion); originalVersion = this.convertToParts(originalVersion); @@ -196,6 +234,7 @@ export default { } } + // Removable: 2 updates after v2.3.3 /* * Older versions may have fewer segments (vX.X.X vs vX.X.X.X). * When orig[i] is undefined, `new[i] > undefined` is always false in JS, @@ -208,6 +247,11 @@ export default { return false; }, + /** + * Checks for updates. + * + * @param {boolean} isManualTrigger - True if update was triggered manually. + */ async checkForUpdates(isManualTrigger = false) { if (!this.current) { await this.init(); diff --git a/src/services/usercontent.sys.mjs b/src/services/usercontent.sys.mjs new file mode 100644 index 00000000..02ebe1ae --- /dev/null +++ b/src/services/usercontent.sys.mjs @@ -0,0 +1,65 @@ +/** + * @file Window actor for applying styles that emulate userContent. + * @license + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +/** Main child window actor class. */ +export class SineUserContentChild extends JSWindowActorChild { + #contentURI = null; + + /** Initializes message listener abilities. */ + constructor() { + super(); + Services.cpmm.addMessageListener("RebuildUserStyles", this); + } + + /** Handles unloading the message listener to prevent memory leaks. */ + didDestroy() { + Services.cpmm.removeMessageListener("RebuildUserStyles", this); + } + + /** + * Handles load events. + * + * @param {Event} event - Event that function was triggered for. + */ + handleEvent(event) { + if (event.type === "DOMWindowCreated") { + this.#contentURI = Services.io.newURI("chrome://sine/content/content.css"); + this.injectUserStyle(); + } + } + + /** + * Handles received messages, including rebuilding styles. + * + * @param {object} message - Message being received. + */ + receiveMessage(message) { + if (message.name === "RebuildUserStyles") { + this.removeUserStyle(); + this.injectUserStyle(); + } + } + + /** Injects userContent styles into the window. */ + injectUserStyle() { + try { + const utils = this.contentWindow.windowUtils; + utils.loadSheet(this.#contentURI, utils.USER_SHEET); + } catch (err) { + console.error("Failed to inject userContent style: ", err); + } + } + + /** Unloads userContent styles from the window. */ + removeUserStyle() { + const utils = this.contentWindow.windowUtils; + try { + utils.removeSheet(this.#contentURI, utils.USER_SHEET); + } catch {} + } +} diff --git a/src/sine.sys.mjs b/src/sine.sys.mjs index f498af40..bc043234 100644 --- a/src/sine.sys.mjs +++ b/src/sine.sys.mjs @@ -1,4 +1,6 @@ /** + * @file Main entrypoint for Sine initialization. + * @license * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. @@ -29,6 +31,7 @@ Services.prefs.clearUserPref("sine.fork-id"); Services.prefs.setStringPref("sine.fork-id", ucAPI.utils.fork); const Sine = { + /** Registers Sine locales. */ registerLocales() { const l10nReg = L10nRegistry.getInstance(); @@ -43,6 +46,7 @@ const Sine = { l10nReg.registerSources([src]); }, + /** Initializes necessary data and listeners. */ async init() { this.registerLocales(); @@ -59,7 +63,7 @@ const Sine = { manager.updateMods("auto"); // Inject https://zen-browser.app/mods/ API. - import("./services/injectAPI.js"); + import("./services/injectAPI.sys.mjs"); }, }; diff --git a/src/utils/dom.mjs b/src/utils/dom.mjs index f86cf361..1412619f 100644 --- a/src/utils/dom.mjs +++ b/src/utils/dom.mjs @@ -1,39 +1,44 @@ /** + * @file Defines DOM-related utilties. + * @license * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -// =========================================================== -// Initializes utilities related to the DOM, including parsing -// markdown and injecting locales. -// =========================================================== - -const parseMD = (element, markdown, relativeURL, windowObj = window) => { - const document = windowObj.document; +/** + * Parses markdown into valid HTML and appends it. + * + * @param {HTMLElement} element - Element to append markdown to. + * @param {string} markdown - Markdown string. + * @param {string} relativeURL - Base url to prepend to link paths. + */ +export const parseMD = (element, markdown, relativeURL) => { + const win = element.documentGlobal; + const doc = win.defaultView; - if (!document.querySelector('link[href*="marked_styles.css"]')) { - const link = document.createElement("link"); + if (!doc.querySelector('link[href*="marked_styles.css"]')) { + const link = doc.createElement("link"); link.rel = "stylesheet"; link.className = "marked-styles"; link.href = "chrome://userscripts/content/assets/imports/marked_styles.css"; - document.head.appendChild(link); + doc.head.append(link); } - if (!windowObj.marked) { + if (!win.marked) { Services.scriptloader.loadSubScriptWithOptions( "chrome://userscripts/content/assets/imports/marked_parser.js", { - target: windowObj, + target: win, } ); } - const renderer = new windowObj.marked.Renderer(); + const renderer = new win.marked.Renderer(); const fixURL = (href) => { - if (/^(https?:\/\/|\/\/)/i.test(href)) return href; - return `${relativeURL.replace(/\/$/, "")}/${href.replace(/^\//, "")}`; + if (/^(https?:\/\/|\/\/)/iu.test(href)) return href; + return `${relativeURL.replace(/\/$/u, "")}/${href.replace(/^\//u, "")}`; }; renderer.image = (href, title, text) => { @@ -43,29 +48,38 @@ const parseMD = (element, markdown, relativeURL, windowObj = window) => { renderer.link = (href, title, text) => { let finalHref = href; - if (!/^(https?:\/\/|\/\/)/i.test(href)) { + if (!/^(https?:\/\/|\/\/)/iu.test(href)) { const isRelativePath = - href.includes("/") || /\.(md|html|htm|png|jpg|jpeg|gif|svg|pdf)$/i.test(href); + href.includes("/") || /\.(md|html|htm|png|jpg|jpeg|gif|svg|pdf)$/iu.test(href); finalHref = isRelativePath ? fixURL(href) : `https://${href}`; } const titleAttr = title ? ` title="${title}"` : ""; return `${text}`; }; - windowObj.marked.setOptions({ + win.marked.setOptions({ gfm: true, renderer, }); // TODO: Find a reliable way to sanitize output // eslint-disable-next-line eslint-js/no-restricted-syntax - element.innerHTML = windowObj.marked + element.innerHTML = win.marked .parse(markdown) - .replace(/<(img|hr|br|input)([^>]*?)\s*\/?>/gi, "<$1$2 />") - .replace(/&(?![\w#]+;)/g, "&"); + .replaceAll(/<(img|hr|br|input)([^>]*?)\s*\/?>/giu, "<$1$2 />") + .replaceAll(/&(?![\w#]+;)/gu, "&"); }; -const appendXUL = (parentElement, xulString, insertBefore = null, XUL = false) => { +/** + * Appends XUL string into parent element. + * + * @param {HTMLElement} parentElement - Element to append XUL in. + * @param {string} xulString - XUL string to parse. + * @param {HTMLElement} insertBefore - If specified, will insert XUL before this element. + * @param {object | boolean} XUL - XUL object used for parsing. + * @returns {HTMLElement} Element appended. + */ +export const appendXUL = (parentElement, xulString, insertBefore = null, XUL = false) => { let element; if (XUL) { element = (typeof XUL === "function" ? XUL : window.MozXULElement).parseXULToFragment( @@ -85,13 +99,19 @@ const appendXUL = (parentElement, xulString, insertBefore = null, XUL = false) = if (insertBefore) { parentElement.insertBefore(element, insertBefore); } else { - parentElement.appendChild(element); + parentElement.append(element); } return element; }; -const waitForElm = (selector) => { +/** + * Waits for an element to match the selector passed, instantly returning if it already exists. + * + * @param {string} selector - Selector to wait for. + * @returns {HTMLElement} Element found that matches selector. + */ +export const waitForElm = (selector) => { return new Promise((resolve) => { const existing = document.querySelector(selector); if (existing) resolve(existing); @@ -112,8 +132,13 @@ const waitForElm = (selector) => { }; const supportedLocales = new Set(["en-US", "en", "pl", "ru"]); - -const injectLocale = (file, doc = document) => { +/** + * Injects a locale into a document. + * + * @param {string} file - Relative locale path to inject. (without the lang tag) + * @param {HTMLDocument} doc - Document to inject locale into. + */ +export const injectLocale = (file, doc = document) => { const pref = "intl.locale.requested"; let link = null; @@ -132,7 +157,7 @@ const injectLocale = (file, doc = document) => { link = doc.createElement("link"); link.setAttribute("rel", "localization"); link.setAttribute("href", `${locale}/${file}.ftl`); - doc.head.appendChild(link); + doc.head.append(link); }; register(); @@ -151,5 +176,3 @@ const injectLocale = (file, doc = document) => { { once: true } ); }; - -export default { parseMD, appendXUL, waitForElm, injectLocale }; diff --git a/src/utils/toasts.sys.mjs b/src/utils/toasts.sys.mjs index 2c8df90a..69718950 100644 --- a/src/utils/toasts.sys.mjs +++ b/src/utils/toasts.sys.mjs @@ -1,25 +1,35 @@ /** + * @file Manages and defines toast logic. + * @license * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -// =========================================================== -// Implements and manages toasts that are displayable to the -// user for alerts and notifications. -// =========================================================== - import ucAPI from "./uc_api.sys.mjs"; -import domUtils from "./dom.mjs"; +import * as domUtils from "./dom.mjs"; +/** Main class for managing toasts. */ export default class Toast { timeout = 3000; - constructor(options = {}, win = window) { + /** + * Triggers toast initialization. + * + * @param {object} options - Options to use to define toast. + * @param {Window} win - Window to inject toast in. + */ + constructor(options = {}, win) { this.preset = options.preset ?? 1; this.init(options, win); } + /** + * Initializes toast in DOM. + * + * @param {object} options - Options to use to define toast. + * @param {Window} win - Window to inject toast in. + */ async init(options, win) { const duplicates = Array.from(win.document.querySelectorAll(".sineToast")).filter( (toast) => @@ -54,6 +64,7 @@ export default class Toast { this.#setupTimeout(win); } + /** Animates the entry of the toast into the user's viewport. */ #animateEntry() { this.toast.entryAnimation = this.toast.animate( [{ transform: "translateY(120%) scale(0.8)" }, { transform: "translateY(0%) scale(1)" }], @@ -72,6 +83,7 @@ export default class Toast { } } + /** Sets up the hover effects of the toast. */ #setupHover() { let hoverAnimation = null; @@ -96,6 +108,12 @@ export default class Toast { }); } + /** + * Sets up the toast button for a quick action. + * + * @param {() => void} clickEvent - Event to trigger on click. + * @param {Window} win - Window that the toast lives in. + */ #setupButton(clickEvent, win) { const button = this.toast.querySelector("button"); if (!button) return; @@ -138,10 +156,7 @@ export default class Toast { const currentTransform = animationSetup(); buttonAnimation = button.animate( [{ transform: currentTransform }, { transform: `scale(0.95)` }], - { - ...animationBehavior, - duration: 100, - } + Object.assign({}, animationBehavior, { duration: 100 }) ); }); @@ -155,6 +170,11 @@ export default class Toast { }); } + /** + * Sets up the timeout for when the toast should disappear from the user's viewport. + * + * @param {Window} win - Window that the toast lives in. + */ #setupTimeout(win) { let timeoutId = null; @@ -176,12 +196,18 @@ export default class Toast { startTimeout(); } - async remove(toast = this.toast) { - toast.dataset.removing = "true"; + /** + * Removes a toast from the DOM, with it's removal animation. + * + * @param {HTMLElement} toast - Toast element to remove. + */ + async remove(toast) { + const targetToast = toast ?? this.toast; - toast.entryAnimation?.cancel(); + targetToast.dataset.removing = "true"; + targetToast.entryAnimation?.cancel(); - await toast.animate( + await targetToast.animate( [{ transform: "translateY(0%) scale(1)" }, { transform: "translateY(120%) scale(0.8)" }], { duration: 400, @@ -190,6 +216,6 @@ export default class Toast { } ).finished; - toast.remove(); + targetToast.remove(); } } diff --git a/src/utils/uc_api.sys.mjs b/src/utils/uc_api.sys.mjs index f68696b9..976c3c66 100644 --- a/src/utils/uc_api.sys.mjs +++ b/src/utils/uc_api.sys.mjs @@ -1,19 +1,17 @@ /** + * @file Common utilties API for userscripts and Sine. + * @license * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -// =========================================================== -// Performs and initializes functions that perform actions -// unrelated to mod management. -// =========================================================== - import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs"; import Toast from "./toasts.sys.mjs"; +/** A group of common userscript-related utilities. */ const utils = { - os: AppConstants.platform.substr(0, 3), + os: AppConstants.platform.slice(0, 3), chromeDir: PathUtils.join(PathUtils.profileDir, "chrome"), fork: { @@ -25,10 +23,21 @@ const utils = { thunderbird: "thunderbird", }[AppConstants.MOZ_APP_NAME] || "firefox", + /** Restarts Firefox. */ restart() { + // eslint-disable-next-line no-bitwise Services.startup.quit(Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eAttemptQuit); }, + /** + * Generates a UUID with a custom number of groups, chracters per group, and character selection. + * + * @param {number} groupLength - Number of characters per group. + * @param {number} numGroups - Number of groups where each group is deliminated by a dash. (e.g. + * {group}-{group}) + * @param {string} chars - Range of characters to choose from. + * @returns {string} Generated UUID. + */ generateUUID(groupLength = 9, numGroups = 3, chars = "abcdefghijklmnopqrstuvwxyz0123456789") { const generateGroup = () => { let group = ""; @@ -48,7 +57,14 @@ const utils = { }, }; +/** A group of utilities designed for managing about:config preferences. */ const prefs = { + /** + * Returns the value of a preference. + * + * @param {string} pref - Preference to fetch the value of. + * @returns {string | number | boolean} Value of the preference. + */ get(pref) { const prefType = Services.prefs.getPrefType(pref); @@ -63,6 +79,12 @@ const prefs = { return null; }, + /** + * Sets the value of a preference. + * + * @param {string} pref - Preference to set. + * @param {string | number | boolean} value - Value to set it to. + */ set(pref, value) { try { if (typeof value === "string") { @@ -82,6 +104,11 @@ export default { utils, prefs, + /** + * Opens a path in the system's native file explorer. + * + * @param {string} path - Path to open. + */ showInFileManager(path) { const file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); @@ -98,6 +125,11 @@ export default { } }, + /** + * Displays a toast in all main browser windows. + * + * @param {object} options - Options for the toast. + */ showToast(options) { const windows = Services.wm.getEnumerator("navigator:browser"); while (windows.hasMoreElements()) { @@ -106,6 +138,14 @@ export default { } }, + /** + * Fetches a URL, and parses to JSON if possible. WARNING: This function can lead to unexpected + * results if the data parses to JSON instead of expected text. Two new functions that assert + * either text or JSON must be developed in the future. + * + * @param {string} url - URL to fetch. + * @returns {string | object | Array} Parsed data. + */ async fetch(url) { const response = await fetch(url) .then((res) => res.text()) @@ -118,6 +158,12 @@ export default { return response; }, + /** + * Downloads and unpacks a remote archive (zip). + * + * @param {object} options - Options to use. + * @returns {Array} Entries in the zip. + */ async unpackRemoteArchive(options) { const resp = await fetch(options.url); const buf = await resp.arrayBuffer(); @@ -173,7 +219,7 @@ export default { } const outFile = parentDir.clone(); - outFile.append(segments[segments.length - 1]); + outFile.append(segments.at(segments.length - 1)); zipReader.extract(origEntryName, outFile); // https://bugzilla.mozilla.org/show_bug.cgi?id=935553 diff --git a/svgo.config.mjs b/svgo.config.mjs index 80dda80f..85b49154 100644 --- a/svgo.config.mjs +++ b/svgo.config.mjs @@ -1,7 +1,7 @@ /** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * @file Defines SVGO config for maximum SVG optimization. This Source Code Form is subject to the + * terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ export default { diff --git a/tests/paths.test.js b/tests/paths.test.js index 7592c6f8..0456cf12 100644 --- a/tests/paths.test.js +++ b/tests/paths.test.js @@ -1,4 +1,6 @@ /** + * @file Asserts that necessary paths are defined. + * @license * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. diff --git a/tools/oxlint-plugin-mozilla/.oxlintrc.json b/tools/oxlint-plugin-mozilla/.oxlintrc.json index 13b9c048..a0c6a5ff 100644 --- a/tools/oxlint-plugin-mozilla/.oxlintrc.json +++ b/tools/oxlint-plugin-mozilla/.oxlintrc.json @@ -1,7 +1,5 @@ +// Ignore most linting of this plugin. { "$schema": "../../node_modules/oxlint/configuration_schema.json", - "categories": { - "correctness": "error" - }, "ignorePatterns": ["tests/globals-data"] } diff --git a/tools/oxlint-plugin-mozilla/lib/configs/recommended.mjs b/tools/oxlint-plugin-mozilla/lib/configs/recommended.mjs index b14e2905..7badbc3c 100644 --- a/tools/oxlint-plugin-mozilla/lib/configs/recommended.mjs +++ b/tools/oxlint-plugin-mozilla/lib/configs/recommended.mjs @@ -9,28 +9,24 @@ import sysmjs from "../environments/sysmjs.mjs"; import sjs from "../environments/sjs.mjs"; /** - * The configuration is based on eslint:recommended config. It defines the - * recommended rules for all files, as well as for rules relating to modules - * and other module like files. + * The configuration is based on eslint:recommended config. It defines the recommended rules for all + * files, as well as for rules relating to modules and other module like files. * - * The configuration intentionally does not specify the globals for the - * majority of files. The globals will only be specified for Mozilla specific - * files (e.g. system modules). The subscriber to this configuration is expect - * to include the correct globals that they require in their project. + * The configuration intentionally does not specify the globals for the majority of files. The + * globals will only be specified for Mozilla specific files (e.g. system modules). The subscriber + * to this configuration is expect to include the correct globals that they require in their + * project. * - * The details for all the ESLint rules, and which ones are in the ESLint - * recommended configuration can be found here: + * The details for all the ESLint rules, and which ones are in the ESLint recommended configuration + * can be found here: * * https://eslint.org/docs/rules/ * * Rules that we've explicitly decided not to enable: * - * require-atomic-updates - bug 1551829. - * - This generates too many false positives that are not easy to work - * around, and false positives seem to be inherent in the rule. - * max-depth - * - Don't enforce the maximum depth that blocks can be nested. The - * complexity rule is a better rule to check this. + * Require-atomic-updates - bug 1551829. - This generates too many false positives that are not easy + * to work around, and false positives seem to be inherent in the rule. max-depth - Don't enforce + * the maximum depth that blocks can be nested. The complexity rule is a better rule to check this. */ export default defineConfig({ @@ -41,10 +37,31 @@ export default defineConfig({ correctness: "error", suspicious: "error", perf: "error", + pedantic: "error", + restriction: "error", + nursery: "error", }, plugins: ["unicorn", "oxc", "eslint", "promise"], - jsPlugins: ["oxlint-plugin-eslint"], + jsPlugins: [{ name: "eslint-js", specifier: "oxlint-plugin-eslint" }], rules: { + // Turn off unnecessary pedantic rules. + "max-depth": "off", + "max-lines": "off", + "max-lines-per-function": "off", + "no-warning-comments": "off", + "explicit-length-check": "off", + "no-inline-comments": "off", + "sort-vars": "off", + "unicorn/prefer-top-level-await": "off", + + // Turn off unnecessary restriction rules. + "no-async-await": "off", + "no-optional-chaining": "off", + "no-plusplus": "off", + // TODO: Enable this when ready to resolve. + "no-param-reassign": "off", + + // Require await for necessary functions. "require-await": "error", // This may conflict with formatters, so we turn it off. @@ -295,6 +312,12 @@ export default defineConfig({ "mozilla/reject-top-level-await": "error", }, }, + { + files: ["**/*Child.sys.mjs"], + env: { + browser: true, + }, + }, { files: ["**/*.mjs", "**/*.jsx", "**/?(*.)worker.?(m)js", "**/?(*.)serviceworker.?(m)js"], rules: { @@ -302,26 +325,36 @@ export default defineConfig({ // contained scopes. "no-redeclare": ["error", { builtinGlobals: true }], "no-shadow": ["error", { allow: ["event"], builtinGlobals: true }], - // Modules and workers are far easier to check for no-unused-vars on a - // global scope, than our content files. Hence we turn that on here. - "no-unused-vars": [ - "error", - { - argsIgnorePattern: "^_", - caughtErrors: "none", - vars: "all", - }, - ], }, }, { files: ["**/*.mjs"], excludeFiles: ["**/*.sys.mjs"], + env: { + browser: true, + }, + globals: Object.assign( + {}, + privileged.globals, + specific.globals, + // windowRoot is a global defined in browser envs. + { windowRoot: "readonly" } + ), rules: { "mozilla/reject-import-system-module-from-non-system": "error", "mozilla/reject-lazy-imports-into-globals": "error", }, }, + { + files: ["**/tests/*.test.js"], + globals: Object.assign( + {}, + privileged.globals, + specific.globals, + // Current browsercfg testing lib + { it: "readonly" } + ), + }, { files: ["**/*.mjs", "**/*.jsx"], rules: { diff --git a/tools/oxlint-plugin-mozilla/lib/configs/require-jsdoc.mjs b/tools/oxlint-plugin-mozilla/lib/configs/require-jsdoc.mjs index b91746d4..24e66035 100644 --- a/tools/oxlint-plugin-mozilla/lib/configs/require-jsdoc.mjs +++ b/tools/oxlint-plugin-mozilla/lib/configs/require-jsdoc.mjs @@ -5,19 +5,45 @@ import { defineConfig } from "oxlint"; export default defineConfig({ - name: "mozilla/require-jsdoc", plugins: ["jsdoc"], + // Fill in missing rules with eslint-plugin-jsdoc implementations + jsPlugins: [{ name: "jsdoc-js", specifier: "eslint-plugin-jsdoc" }], rules: { - "jsdoc/require-jsdoc": [ + "jsdoc-js/require-jsdoc": [ "error", { require: { ClassDeclaration: true, - FunctionDeclaration: false, + FunctionDeclaration: true, + MethodDefinition: true, }, + contexts: [ + "ExportNamedDeclaration > FunctionDeclaration", + "ExportDefaultDeclaration > FunctionDeclaration", + "ExportNamedDeclaration > VariableDeclaration > VariableDeclarator > ArrowFunctionExpression", + "ExportDefaultDeclaration > ArrowFunctionExpression", + "ExportNamedDeclaration > VariableDeclaration > VariableDeclarator > ObjectExpression Property[method=true]", + "ExportNamedDeclaration > VariableDeclaration > VariableDeclarator > ObjectExpression Property[kind=/^(get|set)$/]", + "ExportDefaultDeclaration > ObjectExpression Property[method=true]", + "ExportDefaultDeclaration > ObjectExpression Property[kind=/^(get|set)$/]", + "Program > VariableDeclaration > VariableDeclarator > ObjectExpression Property[method=true]", + "Program > VariableDeclaration > VariableDeclarator > ObjectExpression Property[kind=/^(get|set)$/]", + ], }, ], - "jsdoc/require-next-type": "error", + "jsdoc-js/require-file-overview": [ + "error", + { + tags: { + file: { + initialCommentsOnly: true, + mustExist: true, + preventDuplicates: true, + }, + }, + }, + ], + "jsdoc-js/require-description": "error", "jsdoc/require-param": "error", "jsdoc/require-param-name": "error", "jsdoc/require-param-type": "error", @@ -25,10 +51,11 @@ export default defineConfig({ "jsdoc/require-property-description": "error", "jsdoc/require-property-name": "error", "jsdoc/require-property-type": "error", - "jsdoc/require-returns-check": "error", - // "jsdoc/require-throws-type": "error", + "jsdoc/require-returns": "error", + "jsdoc/require-throws-type": "error", "jsdoc/require-yields": "error", - "jsdoc/require-yields-check": "error", "jsdoc/require-yields-type": "error", + "jsdoc-js/require-asterisk-prefix": "error", + "jsdoc-js/require-hyphen-before-param-description": ["error", "always"], }, }); diff --git a/tools/oxlint-plugin-mozilla/lib/configs/valid-jsdoc.mjs b/tools/oxlint-plugin-mozilla/lib/configs/valid-jsdoc.mjs index fffa22b9..5ef3c983 100644 --- a/tools/oxlint-plugin-mozilla/lib/configs/valid-jsdoc.mjs +++ b/tools/oxlint-plugin-mozilla/lib/configs/valid-jsdoc.mjs @@ -5,13 +5,12 @@ import { defineConfig } from "oxlint"; export default defineConfig({ - name: "mozilla/valid-jsdoc", plugins: ["jsdoc"], + // Fill in missing rules with eslint-plugin-jsdoc implementations + jsPlugins: [{ name: "jsdoc-js", specifier: "eslint-plugin-jsdoc" }], rules: { "jsdoc/check-access": "error", - // Handled by prettier - // "jsdoc/check-alignment": "error", - "jsdoc/check-param-names": "error", + "jsdoc-js/check-param-names": "error", "jsdoc/check-property-names": "error", "jsdoc/check-tag-names": [ "error", @@ -37,20 +36,24 @@ export default defineConfig({ ], }, ], - "jsdoc/check-types": "error", + "jsdoc-js/check-types": "error", + "jsdoc-js/check-syntax": "error", + "jsdoc-js/imports-as-dependencies": "error", + "jsdoc-js/no-undefined-types": "error", "jsdoc/empty-tags": "error", - "jsdoc/multiline-blocks": "error", - "jsdoc/no-bad-blocks": "error", - "jsdoc/no-multi-asterisks": ["error", { allowWhitespace: true }], - // "jsdoc/reject-function-type": "error", + "jsdoc-js/multiline-blocks": "error", + "jsdoc-js/no-bad-blocks": "error", + "jsdoc-js/no-multi-asterisks": ["error", { allowWhitespace: true }], + "jsdoc-js/reject-function-type": "error", "jsdoc/require-param-type": "error", "jsdoc/require-returns-type": "error", - "jsdoc/tag-lines": ["error", "any", { startLines: 1 }], - "jsdoc/valid-types": "error", + "jsdoc-js/tag-lines": ["error", "any", { startLines: 1 }], + "jsdoc-js/valid-types": "error", }, settings: { jsdoc: { + mode: "typescript", /* eslint-disable sort-keys */ tagNamePreference: { // We allow "return" or "returns" and "yield" or "yields" as they are @@ -65,6 +68,9 @@ export default defineConfig({ part: "csspart", cssprop: "cssproperty", tag: "tagname", + virtual: "abstract", + extends: "extends", + constructor: "constructor", }, /* eslint-enable sort-keys */ }, diff --git a/tools/oxlint-plugin-mozilla/lib/environments/browser-window.mjs b/tools/oxlint-plugin-mozilla/lib/environments/browser-window.mjs index 7cab0eed..b28c903f 100644 --- a/tools/oxlint-plugin-mozilla/lib/environments/browser-window.mjs +++ b/tools/oxlint-plugin-mozilla/lib/environments/browser-window.mjs @@ -1,7 +1,7 @@ /** - * @file Defines the environment when in the browser.xhtml window. - * Imports many globals from various files. - * + * @file Defines the environment when in the browser.xhtml window. Imports many globals from various + * files. + * @license * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. @@ -27,9 +27,7 @@ const EXTRA_SCRIPTS = [ "toolkit/content/editMenuOverlay.js", ]; -/** - * @type {{[key: string]: "readonly"|"writeable"|"off"}} - */ +/** @type {{ [key: string]: "readonly" | "writeable" | "off" }} */ const extraGlobals = { // Via Components.utils, defineModuleGetter, defineLazyModuleGetters or // defineLazyScriptGetter (and map to diff --git a/tools/oxlint-plugin-mozilla/lib/environments/chrome-script.mjs b/tools/oxlint-plugin-mozilla/lib/environments/chrome-script.mjs index ed5eebea..a3d70d7e 100644 --- a/tools/oxlint-plugin-mozilla/lib/environments/chrome-script.mjs +++ b/tools/oxlint-plugin-mozilla/lib/environments/chrome-script.mjs @@ -1,6 +1,6 @@ /** * @file Defines the environment for SpecialPowers chrome script. - * + * @license * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. diff --git a/tools/oxlint-plugin-mozilla/lib/environments/frame-script.mjs b/tools/oxlint-plugin-mozilla/lib/environments/frame-script.mjs index 3b7d0e5b..f8e839dd 100644 --- a/tools/oxlint-plugin-mozilla/lib/environments/frame-script.mjs +++ b/tools/oxlint-plugin-mozilla/lib/environments/frame-script.mjs @@ -1,6 +1,6 @@ /** * @file Defines the environment for frame scripts. - * + * @license * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. diff --git a/tools/oxlint-plugin-mozilla/lib/environments/privileged.mjs b/tools/oxlint-plugin-mozilla/lib/environments/privileged.mjs index f6f85606..0f09053a 100644 --- a/tools/oxlint-plugin-mozilla/lib/environments/privileged.mjs +++ b/tools/oxlint-plugin-mozilla/lib/environments/privileged.mjs @@ -1,6 +1,6 @@ /** * @file Defines the environment for privileges JS files. - * + * @license * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. diff --git a/tools/oxlint-plugin-mozilla/lib/environments/process-script.mjs b/tools/oxlint-plugin-mozilla/lib/environments/process-script.mjs index 615fe433..d645ee0d 100644 --- a/tools/oxlint-plugin-mozilla/lib/environments/process-script.mjs +++ b/tools/oxlint-plugin-mozilla/lib/environments/process-script.mjs @@ -1,6 +1,6 @@ /** * @file Defines the environment for process scripts. - * + * @license * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. diff --git a/tools/oxlint-plugin-mozilla/lib/environments/remote-page.mjs b/tools/oxlint-plugin-mozilla/lib/environments/remote-page.mjs index c2b5efb1..0c3437bb 100644 --- a/tools/oxlint-plugin-mozilla/lib/environments/remote-page.mjs +++ b/tools/oxlint-plugin-mozilla/lib/environments/remote-page.mjs @@ -1,6 +1,6 @@ /** * @file Defines the environment for remote page. - * + * @license * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. diff --git a/tools/oxlint-plugin-mozilla/lib/environments/simpletest.mjs b/tools/oxlint-plugin-mozilla/lib/environments/simpletest.mjs index f8d71ef2..fd35b695 100644 --- a/tools/oxlint-plugin-mozilla/lib/environments/simpletest.mjs +++ b/tools/oxlint-plugin-mozilla/lib/environments/simpletest.mjs @@ -1,7 +1,7 @@ /** - * @file Defines the environment for scripts that use the SimpleTest - * mochitest harness. Imports the globals from the relevant files. - * + * @file Defines the environment for scripts that use the SimpleTest mochitest harness. Imports the + * globals from the relevant files. + * @license * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. diff --git a/tools/oxlint-plugin-mozilla/lib/environments/sjs.mjs b/tools/oxlint-plugin-mozilla/lib/environments/sjs.mjs index 8314b7f6..0906809c 100644 --- a/tools/oxlint-plugin-mozilla/lib/environments/sjs.mjs +++ b/tools/oxlint-plugin-mozilla/lib/environments/sjs.mjs @@ -1,6 +1,6 @@ /** * @file Defines the environment for sjs files. - * + * @license * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. diff --git a/tools/oxlint-plugin-mozilla/lib/environments/special-powers-sandbox.mjs b/tools/oxlint-plugin-mozilla/lib/environments/special-powers-sandbox.mjs index 6d174c78..44d4cc42 100644 --- a/tools/oxlint-plugin-mozilla/lib/environments/special-powers-sandbox.mjs +++ b/tools/oxlint-plugin-mozilla/lib/environments/special-powers-sandbox.mjs @@ -1,6 +1,6 @@ /** * @file Defines the environment for SpecialPowers sandbox. - * + * @license * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. diff --git a/tools/oxlint-plugin-mozilla/lib/environments/specific.mjs b/tools/oxlint-plugin-mozilla/lib/environments/specific.mjs index e8559c39..c6f9a2aa 100644 --- a/tools/oxlint-plugin-mozilla/lib/environments/specific.mjs +++ b/tools/oxlint-plugin-mozilla/lib/environments/specific.mjs @@ -1,7 +1,7 @@ /** - * @file Defines the environment for the Firefox browser. Allows global - * variables which are non-standard and specific to Firefox. - * + * @file Defines the environment for the Firefox browser. Allows global variables which are + * non-standard and specific to Firefox. + * @license * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. diff --git a/tools/oxlint-plugin-mozilla/lib/environments/sysmjs.mjs b/tools/oxlint-plugin-mozilla/lib/environments/sysmjs.mjs index 01c67022..ba612652 100644 --- a/tools/oxlint-plugin-mozilla/lib/environments/sysmjs.mjs +++ b/tools/oxlint-plugin-mozilla/lib/environments/sysmjs.mjs @@ -1,6 +1,6 @@ /** * @file Defines the environment for sys module files. - * + * @license * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. diff --git a/tools/oxlint-plugin-mozilla/lib/environments/testharness.mjs b/tools/oxlint-plugin-mozilla/lib/environments/testharness.mjs index 67eef12a..24762813 100644 --- a/tools/oxlint-plugin-mozilla/lib/environments/testharness.mjs +++ b/tools/oxlint-plugin-mozilla/lib/environments/testharness.mjs @@ -1,8 +1,7 @@ /** - * @file Defines the environment for testharness.js files. This - * is automatically included in (x)html files including - * /resources/testharness.js. - * + * @file Defines the environment for testharness.js files. This is automatically included in (x)html + * files including /resources/testharness.js. + * @license * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. diff --git a/tools/oxlint-plugin-mozilla/lib/environments/utils.mjs b/tools/oxlint-plugin-mozilla/lib/environments/utils.mjs index 48d2b808..c6e7cebf 100644 --- a/tools/oxlint-plugin-mozilla/lib/environments/utils.mjs +++ b/tools/oxlint-plugin-mozilla/lib/environments/utils.mjs @@ -1,6 +1,6 @@ /** * @file Provides utilities for setting up environments. - * + * @license * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. @@ -12,20 +12,19 @@ import helpers from "../helpers.mjs"; import globals from "../globals.mjs"; /** - * @typedef {{[name: string]: "readonly"|"writeable"|"off"}} Globals - * A list of globals compatible with ESLint's configuration format. The type - * name "Globals" reflects the type in the ESLint definition. + * @typedef {{ [name: string]: "readonly" | "writeable" | "off" }} Globals A list of globals + * compatible with ESLint's configuration format. The type name "Globals" reflects the type in the + * ESLint definition. */ let savedGlobals = null; /** - * Loads environment items from the saved globals file. Used when eslint-plugin-mozilla - * is installed outside of the firefox repository. + * Loads environment items from the saved globals file. Used when eslint-plugin-mozilla is installed + * outside of the firefox repository. * * @param {string} environment - * @returns {{globals: {Globals}}} - * The globals for the given environment. + * @returns {{ globals: { Globals } }} The globals for the given environment. */ function getSavedEnvironmentItems(environment) { if (!savedGlobals) { @@ -41,12 +40,10 @@ function getSavedEnvironmentItems(environment) { /** * Obtains the globals for a list of files and if they are writeable or not. * - * @param {string} environmentName - * The name of the environment that globals are being obtained for. - * @param {string[]} files - * The array of files to get globals for. The paths are relative to the topsrcdir. - * @param {Globals} [extraGlobals] - * Any additional globals to add to the globals list. + * @param {string} environmentName The name of the environment that globals are being obtained for. + * @param {string[]} files The array of files to get globals for. The paths are relative to the + * topsrcdir. + * @param {Globals} [extraGlobals] Any additional globals to add to the globals list. */ function getGlobalsForScripts(environmentName, files, extraGlobals) { /** @type {ReturnType} */ @@ -63,7 +60,7 @@ function getGlobalsForScripts(environmentName, files, extraGlobals) { } } - /** @type {Globals}} */ + /** @type {Globals} } */ var globalObjects = { ...extraGlobals }; for (let { name: globalName, writable } of fileGlobals) { globalObjects[globalName] = writable ? "writeable" : "readonly"; @@ -72,20 +69,17 @@ function getGlobalsForScripts(environmentName, files, extraGlobals) { } /** - * Gets the complete globals list for a set of scripts, adding extra globals and - * environments as required. + * Gets the complete globals list for a set of scripts, adding extra globals and environments as + * required. * - * When run from within the Firefox repository, this will use process the given - * files. When run from outside the Firefox repository, this will use the - * cache of saved globals. + * When run from within the Firefox repository, this will use process the given files. When run from + * outside the Firefox repository, this will use the cache of saved globals. * * @param {object} options - * @param {string} options.environmentName - * The name of the environment we are getting the globals for. - * @param {string[]} options.files - * The array of files to process - * @param {Globals} [options.extraGlobals] - * Any additional globals to add to the globals list. + * @param {string} options.environmentName The name of the environment we are getting the globals + * for. + * @param {string[]} options.files The array of files to process + * @param {Globals} [options.extraGlobals] Any additional globals to add to the globals list. */ export function getScriptGlobals({ environmentName, files, extraGlobals = {} }) { if (helpers.isMozillaCentralBased()) { diff --git a/tools/oxlint-plugin-mozilla/lib/environments/xpcshell.mjs b/tools/oxlint-plugin-mozilla/lib/environments/xpcshell.mjs index e6a3ee65..27259bc4 100644 --- a/tools/oxlint-plugin-mozilla/lib/environments/xpcshell.mjs +++ b/tools/oxlint-plugin-mozilla/lib/environments/xpcshell.mjs @@ -1,6 +1,6 @@ /** * @file Defines the environment for xpcshell test files. - * + * @license * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. @@ -8,9 +8,7 @@ import { getScriptGlobals } from "./utils.mjs"; -/** - * @type {{[key: string]: "readonly"|"writeable"|"off"}} - */ +/** @type {{ [key: string]: "readonly" | "writeable" | "off" }} */ const extraGlobals = { // Defined in XPCShellImpl.cpp print: "readonly", diff --git a/tools/oxlint-plugin-mozilla/lib/globals.mjs b/tools/oxlint-plugin-mozilla/lib/globals.mjs index 2e60e4cf..944795e6 100644 --- a/tools/oxlint-plugin-mozilla/lib/globals.mjs +++ b/tools/oxlint-plugin-mozilla/lib/globals.mjs @@ -1,9 +1,9 @@ /** - * @file functions for scanning code for globals, and imported globals. - * When scanning files, `FileImportASTHandler` is used to build a tree of the - * imports. The tree is then used as an iteration point, and `GlobalsForNode` - * is used to build the list of globals for each individual file. - * + * @file Functions for scanning code for globals, and imported globals. When scanning files, + * `FileImportASTHandler` is used to build a tree of the imports. The tree is then used as an + * iteration point, and `GlobalsForNode` is used to build the list of globals for each individual + * file. + * @license * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. @@ -50,13 +50,12 @@ const subScriptMatches = [/Services\.scriptloader\.loadSubScript\("(.*?)", this\ const workerImportFilenameMatch = /(?:[^/]*\/)*([^/]*\.js)/; /** - * @typedef {{name: string, writable: boolean, explicit?: boolean}[]} GlobalsList - * A list of globals and if they are writeable or not. + * @typedef {{ name: string; writable: boolean; explicit?: boolean }[]} GlobalsList A list of + * globals and if they are writeable or not. */ /** - * Parses a list of "name:boolean_value" or/and "name" options divided by comma - * or whitespace. + * Parses a list of "name:boolean_value" or/and "name" options divided by comma or whitespace. * * This function was copied from eslint.js * @@ -94,20 +93,18 @@ function parseBooleanConfig(string, comment) { } /** - * When looking for globals in HTML files, it can be common to have more than - * one script tag with inline javascript. These will normally be called together, - * so we store the globals for just the last HTML file processed. + * When looking for globals in HTML files, it can be common to have more than one script tag with + * inline javascript. These will normally be called together, so we store the globals for just the + * last HTML file processed. */ var lastHTMLGlobals = {}; /** * Attempts to figure out the location for the given script. * - * @param {string} src - * The source path or url of the script to look for. - * @param {string} [dir] - * The directory of the current file. - * @returns {string?} + * @param {string} src The source path or url of the script to look for. + * @param {string} [dir] The directory of the current file. + * @returns {string | null} */ function getScriptLocation(src, dir) { let scriptName; @@ -140,12 +137,9 @@ function getScriptLocation(src, dir) { /** * Attempts to load the globals for a given script. * - * @param {string} src - * The source path or url of the script to look for. - * @param {string} type - * The type of the current file (script/module). - * @param {string} [dir] - * The directory of the current file. + * @param {string} src The source path or url of the script to look for. + * @param {string} type The type of the current file (script/module). + * @param {string} [dir] The directory of the current file. * @returns {GlobalsList} */ function getGlobalsForScript(src, type, dir) { @@ -169,16 +163,15 @@ function getGlobalsForScript(src, type, dir) { } /** - * A class that wraps the standard Map class with some utility functions to - * simplify adding and extracting details. + * A class that wraps the standard Map class with some utility functions to simplify adding and + * extracting details. * * @augments {Map>} */ class GraphMap extends Map { /** - * Adds a new element with a specified key and value to the Map. The value - * will be wrapped in a Set. - * If an element with the same key already exists, the set will be updated. + * Adds a new element with a specified key and value to the Map. The value will be wrapped in a + * Set. If an element with the same key already exists, the set will be updated. * * @param {string} key * @param {string} [value] @@ -193,15 +186,14 @@ class GraphMap extends Map { } /** - * Returns a Set of values that are collated from looking at the graph and - * sub-graphs from the given key. The initial key is also included in the - * result. + * Returns a Set of values that are collated from looking at the graph and sub-graphs from the + * given key. The initial key is also included in the result. * * @param {string} key */ getSubGraph(key) { let result = new Set([key]); - /** @type { (innerFile: string) => void} */ + /** @type {(innerFile: string) => void} */ let inner = (innerFile) => { result.add(innerFile); let initialGraph = this.get(innerFile); @@ -220,43 +212,34 @@ class GraphMap extends Map { } /** - * A map of file paths mapped to the files that they directly import via - * import-globals-from and other constructs. + * A map of file paths mapped to the files that they directly import via import-globals-from and + * other constructs. */ let fileImportGraph = new GraphMap(); /** - * A map of file paths mapped to the globals found in those files. These exclude - * globals imported via import-globals-from. + * A map of file paths mapped to the globals found in those files. These exclude globals imported + * via import-globals-from. * - * @type {Map} + * @type {Map} */ let fileGlobalsMap = new Map(); /** - * A class which acts as a AST walker for a files, to extract information - * about the files which are imported into the file. + * A class which acts as a AST walker for a files, to extract information about the files which are + * imported into the file. */ class FileImportASTHandler { - /** - * The context associated with the current processing. This is used to report - * any found issues. - */ + /** The context associated with the current processing. This is used to report any found issues. */ #context; - /** - * The directory name of the file being processed. - */ + /** The directory name of the file being processed. */ #dirname; - /** - * The absolute path of the file being processed. - */ + /** The absolute path of the file being processed. */ #path; /** - * @param {string} filePath - * The absolute path of the file being parsed. - * @param {object} context - * The context associated with the node. + * @param {string} filePath The absolute path of the file being parsed. + * @param {object} context The context associated with the node. */ constructor(filePath, context) { this.#path = filePath; @@ -356,10 +339,9 @@ class FileImportASTHandler { } /** - * An object that returns found globals for given AST node types. Each prototype - * property should be named for a node type and accepts a node parameter and a - * parents parameter which is a list of the parent nodes of the current node. - * Each returns an array of globals found. + * An object that returns found globals for given AST node types. Each prototype property should be + * named for a node type and accepts a node parameter and a parents parameter which is a list of the + * parent nodes of the current node. Each returns an array of globals found. */ class GlobalsForNode { Program(node) { @@ -390,14 +372,11 @@ class GlobalsForNode { } /** - * Attempts to convert an AssignmentExpression into a global variable - * definition if it applies to `this` in the global scope. - * - * @param {object} node - * The AST node to convert. - * @param {object} parents - * The details of any parents of the current node. + * Attempts to convert an AssignmentExpression into a global variable definition if it applies to + * `this` in the global scope. * + * @param {object} node The AST node to convert. + * @param {object} parents The details of any parents of the current node. * @returns {GlobalsList} */ AssignmentExpression(node, parents) { @@ -413,13 +392,11 @@ class GlobalsForNode { } /** - * Attempts to convert a CallExpression that looks like a module import - * into global variable definitions. + * Attempts to convert a CallExpression that looks like a module import into global variable + * definitions. * - * @param {object} node - * The AST node to convert. - * @param {object} parents - * The details of any parents of the current node. + * @param {object} node The AST node to convert. + * @param {object} parents The details of any parents of the current node. * @returns {GlobalsList} */ CallExpression(node, parents) { @@ -509,14 +486,10 @@ let globalUtils = { * Ensures that there is a complete file import tree for the given filePath. * * @param {object} options - * @param {string} options.filePath - * The path of the file to start from. - * @param {object} [options.context] - * The ESLint rule context for the current file being processed. - * @param {object} [options.astOptions] - * Any AST options that the parser needs. - * @returns {GraphMap} - * Returned to allow tests to check correct behaviour. + * @param {string} options.filePath The path of the file to start from. + * @param {object} [options.context] The ESLint rule context for the current file being processed. + * @param {object} [options.astOptions] Any AST options that the parser needs. + * @returns {GraphMap} Returned to allow tests to check correct behaviour. */ ensureFileTree({ filePath, context = null, astOptions = {} }) { if (fileImportGraph.has(filePath)) { @@ -546,17 +519,13 @@ let globalUtils = { }, /** - * Returns all globals for a given file. Recursively searches through - * import-globals-from directives and also includes globals defined by - * standard eslint directives. + * Returns all globals for a given file. Recursively searches through import-globals-from + * directives and also includes globals defined by standard eslint directives. * * @param {object} options - * @param {string} [options.filePath] - * The absolute path of the file to be parsed. - * @param {object} [options.context] - * The ESLint rule context for the current file being processed. - * @param {object} [options.astOptions] - * Any AST options that the parser needs. + * @param {string} [options.filePath] The absolute path of the file to be parsed. + * @param {object} [options.context] The ESLint rule context for the current file being processed. + * @param {object} [options.astOptions] Any AST options that the parser needs. */ getGlobalsForFile({ filePath, context = null, astOptions = {} }) { // First identify the full import graph. @@ -608,13 +577,10 @@ let globalUtils = { }, /** - * Returns all globals for a code. - * This is only for testing. + * Returns all globals for a code. This is only for testing. * - * @param {string} code - * The JS code - * @param {object} astOptions - * Extra options to pass to the parser. + * @param {string} code The JS code + * @param {object} astOptions Extra options to pass to the parser. */ getGlobalsForCode(code, astOptions = {}) { // Parse the content into an AST @@ -643,15 +609,14 @@ let globalUtils = { }, /** - * Returns all the globals for an html file that are defined by imported - * scripts (i.e.