[
  {
    "path": ".changes/config.json",
    "content": "{\n  \"gitSiteUrl\": \"https://www.github.com/crabnebula-dev/cargo-packager\",\n  \"timeout\": 3600000,\n  \"pkgManagers\": {\n    \"rust\": {\n      \"version\": true,\n      \"getPublishedVersion\": {\n        \"use\": \"fetch:check\",\n        \"options\": {\n          \"url\": \"https://crates.io/api/v1/crates/${ pkg.pkg }/${ pkg.pkgFile.version }\"\n        }\n      },\n      \"postversion\": [\n        {\n          \"command\": \"cargo generate-lockfile\",\n          \"dryRunCommand\": true,\n          \"runFromRoot\": true,\n          \"pipe\": false\n        }\n      ],\n      \"prepublish\": [\n        {\n          \"command\": \"cargo generate-lockfile\",\n          \"dryRunCommand\": true,\n          \"runFromRoot\": true,\n          \"pipe\": false\n        }\n      ],\n      \"publish\": [\n        {\n          \"command\": \"echo '<details>\\n<summary><em><h4>Cargo Publish</h4></em></summary>\\n\\n```'\",\n          \"dryRunCommand\": true,\n          \"pipe\": true\n        },\n        {\n          \"command\": \"cargo publish\",\n          \"dryRunCommand\": \"cargo publish --dry-run\",\n          \"pipe\": true\n        },\n        {\n          \"command\": \"echo '```\\n\\n</details>\\n'\",\n          \"dryRunCommand\": true,\n          \"pipe\": true\n        }\n      ],\n      \"postpublish\": [\n        \"git tag ${ pkg.pkg }-v${ pkgFile.versionMajor } -f\",\n        \"git tag ${ pkg.pkg }-v${ pkgFile.versionMajor }.${ pkgFile.versionMinor } -f\",\n        \"git push --tags -f\"\n      ]\n    },\n    \"javascript\": {\n      \"version\": true,\n      \"getPublishedVersion\": {\n        \"use\": \"fetch:check\",\n        \"options\": {\n          \"url\": \"https://registry.npmjs.com/${ pkg.pkg }/${ pkg.pkgFile.version }\"\n        }\n      },\n      \"prepublish\": [\n        {\n          \"command\": \"pnpm install\",\n          \"dryRunCommand\": true\n        },\n        {\n          \"command\": \"echo '<details>\\n<summary><em><h4>PNPM Audit</h4></em></summary>\\n\\n```'\",\n          \"dryRunCommand\": true,\n          \"pipe\": true\n        },\n        {\n          \"command\": \"pnpm audit\",\n          \"dryRunCommand\": true,\n          \"runFromRoot\": true,\n          \"pipe\": true\n        },\n        {\n          \"command\": \"echo '```\\n\\n</details>\\n'\",\n          \"dryRunCommand\": true,\n          \"pipe\": true\n        },\n        {\n          \"command\": \"npm pack\",\n          \"dryRunCommand\": true\n        }\n      ],\n      \"publish\": [\n        \"sleep 15s\",\n        {\n          \"command\": \"echo '<details>\\n<summary><em><h4>PNPM Publish</h4></em></summary>\\n\\n```'\",\n          \"dryRunCommand\": true,\n          \"pipe\": true\n        },\n        {\n          \"command\": \"pnpm publish --access public\",\n          \"dryRunCommand\": \"npm publish --dry-run --access public\",\n          \"pipe\": true\n        },\n        {\n          \"command\": \"echo '```\\n\\n</details>\\n'\",\n          \"dryRunCommand\": true,\n          \"pipe\": true\n        }\n      ],\n      \"postpublish\": [\n        \"git tag ${ pkg.pkg }-v${ pkgFile.versionMajor } -f\",\n        \"git tag ${ pkg.pkg }-v${ pkgFile.versionMajor }.${ pkgFile.versionMinor } -f\",\n        \"git push --tags -f\"\n      ]\n    }\n  },\n  \"packages\": {\n    \"cargo-packager-utils\": {\n      \"path\": \"./crates/utils\",\n      \"manager\": \"rust\"\n    },\n    \"cargo-packager\": {\n      \"path\": \"./crates/packager\",\n      \"manager\": \"rust\",\n      \"dependencies\": [\"cargo-packager-utils\"]\n    },\n    \"@crabnebula/packager\": {\n      \"path\": \"./bindings/packager/nodejs\",\n      \"manager\": \"javascript\",\n      \"dependencies\": [\"cargo-packager\", \"cargo-packager-utils\"],\n      \"prepublish\": [],\n      \"publish\": [],\n      \"postpublish\": []\n    },\n    \"cargo-packager-updater\": {\n      \"path\": \"./crates/updater\",\n      \"dependencies\": [\"cargo-packager-utils\"],\n      \"manager\": \"rust\"\n    },\n    \"@crabnebula/updater\": {\n      \"path\": \"./bindings/updater/nodejs\",\n      \"manager\": \"javascript\",\n      \"dependencies\": [\"cargo-packager-updater\", \"cargo-packager-utils\"],\n      \"prepublish\": [],\n      \"publish\": [],\n      \"postpublish\": []\n    },\n    \"cargo-packager-resource-resolver\": {\n      \"path\": \"./crates/resource-resolver\",\n      \"dependencies\": [\"cargo-packager-utils\"],\n      \"manager\": \"rust\"\n    },\n    \"@crabnebula/packager-resource-resolver\": {\n      \"path\": \"./bindings/resource-resolver/nodejs\",\n      \"manager\": \"javascript\",\n      \"dependencies\": [\n        \"cargo-packager-resource-resolver\",\n        \"cargo-packager-utils\"\n      ],\n      \"prepublish\": [],\n      \"publish\": [],\n      \"postpublish\": []\n    }\n  }\n}\n"
  },
  {
    "path": ".changes/readme.md",
    "content": "# Changes\n\n##### via https://github.com/jbolda/covector\n\nAs you create PRs and make changes that require a version bump, please add a new markdown file in this folder. You do not note the version _number_, but rather the type of bump that you expect: major, minor, or patch. The filename is not important, as long as it is a `.md`, but we recommend it represents the overall change for our sanity.\n\nWhen you select the version bump required, you do _not_ need to consider dependencies. Only note the package with the actual change, and any packages that depend on that package will be bumped automatically in the process.\n\nUse the following format:\n\n```md\n---\n\"cargo-packager\": patch\n---\n\nChange summary goes here\n```\n\nSummaries do not have a specific character limit, but are text only. These summaries are used within the (future implementation of) changelogs. They will give context to the change and also point back to the original PR if more details and context are needed.\n\nChanges will be designated as a `major`, `minor` or `patch` as further described in [semver](https://semver.org/).\n\nGiven a version number MAJOR.MINOR.PATCH, increment the:\n\n- MAJOR version when you make incompatible API changes,\n- MINOR version when you add functionality in a backwards compatible manner, and\n- PATCH version when you make backwards compatible bug fixes.\n\nAdditional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format, but will be discussed prior to usage (as extra steps will be necessary in consideration of merging and publishing).\n"
  },
  {
    "path": ".github/workflows/audit.yml",
    "content": "name: Audit Rust\n\non:\n  workflow_dispatch:\n  schedule:\n    - cron: \"0 0 * * *\"\n  push:\n    branches:\n      - main\n    paths:\n      - \".github/workflows/audit.yml\"\n      - \"**/Cargo.lock\"\n      - \"**/Cargo.toml\"\n  pull_request:\n    branches:\n      - main\n    paths:\n      - \".github/workflows/audit.yml\"\n      - \"**/Cargo.lock\"\n      - \"**/Cargo.toml\"\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  audit:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - uses: rustsec/audit-check@v1\n        with:\n          token: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/build-examples.yml",
    "content": "name: Package examples\n\non:\n  pull_request:\n    branches:\n      - main\n    paths:\n      - \".github/workflows/build-examples.yml\"\n      - \"crates/**\"\n      - \"examples/**\"\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  package:\n    if: ${{ !startsWith(github.head_ref, 'renovate/') }}\n    strategy:\n      fail-fast: false\n      matrix:\n        platform: [ubuntu-22.04, macos-latest, windows-latest]\n\n    runs-on: ${{ matrix.platform }}\n\n    steps:\n      - uses: actions/checkout@v6\n      - name: install webkit2gtk\n        if: matrix.platform == 'ubuntu-22.04'\n        run: |\n          sudo apt-get update\n          sudo apt-get install -y webkit2gtk-4.1 webkit2gtk-4.0 libayatana-appindicator3-dev libxdo-dev\n      - uses: dtolnay/rust-toolchain@stable\n      - uses: actions/setup-go@v6\n        with:\n          go-version: \"stable\"\n      - uses: denoland/setup-deno@v1\n      - uses: Swatinem/rust-cache@v2\n      - run: go install github.com/wailsapp/wails/v2/cmd/wails@latest\n      - run: cargo install cargo-binstall --locked\n      - run: cargo binstall tauri-cli --locked --force\n      - run: cargo r --package cargo-packager -- signer generate --password '123' --path ./signing-key -vvv\n      - run: cargo r --package cargo-packager -- --release --private-key ./signing-key --password '123' --formats all -vvv\n"
  },
  {
    "path": ".github/workflows/check-nodejs-bindings.yml",
    "content": "name: Check Node.js bindings\n\non:\n  push:\n    branches:\n      - main\n    paths:\n      - \".github/workflows/check-nodejs-bindings.yml\"\n      - \"crates/packager/**\"\n      - \"crates/updater/**\"\n      - \"crates/resource-resolver/**\"\n      - \"bindings/*/nodejs/**\"\n  pull_request:\n    branches:\n      - main\n    paths:\n      - \".github/workflows/check-nodejs-bindings.yml\"\n      - \"crates/packager/**\"\n      - \"crates/updater/**\"\n      - \"crates/resource-resolver/**\"\n      - \"bindings/*/nodejs/**\"\n\nenv:\n  RUST_BACKTRACE: 1\n  CARGO_PROFILE_DEV_DEBUG: 0 # This would add unnecessary bloat to the target folder, decreasing cache efficiency.\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  test:\n    runs-on: ${{ matrix.platform }}\n\n    strategy:\n      fail-fast: false\n      matrix:\n        platform: [ubuntu-latest, macos-latest, windows-latest]\n\n    steps:\n      - uses: actions/checkout@v6\n      - uses: dtolnay/rust-toolchain@stable\n      - uses: pnpm/action-setup@v4\n        with:\n          version: latest\n      - uses: actions/setup-node@v6\n      - uses: Swatinem/rust-cache@v2\n      - run: pnpm install\n      - run: pnpm build\n"
  },
  {
    "path": ".github/workflows/check.yml",
    "content": "name: Check\n\non:\n  push:\n    branches:\n      - main\n    paths:\n      - \".github/workflows/check.yml\"\n      - \"**/*.rs\"\n      - \"**/Cargo.toml\"\n  pull_request:\n    branches:\n      - main\n    paths:\n      - \".github/workflows/check.yml\"\n      - \"**/*.rs\"\n      - \"**/Cargo.toml\"\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  prettier:\n    if: ${{ !startsWith(github.head_ref, 'renovate/') }}\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v6\n      - uses: pnpm/action-setup@v4\n        with:\n          version: latest\n      - run: pnpm install\n      - run: pnpm format:check\n\n  rustfmt:\n    if: ${{ !startsWith(github.head_ref, 'renovate/') }}\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v6\n      - uses: dtolnay/rust-toolchain@stable\n        with:\n          components: rustfmt\n      - run: cargo fmt --all -- --check\n\n  clippy:\n    if: ${{ !startsWith(github.head_ref, 'renovate/') }}\n\n    strategy:\n      fail-fast: false\n      matrix:\n        platform: [ubuntu-latest, macos-latest, windows-latest]\n\n    runs-on: ${{ matrix.platform }}\n\n    steps:\n      - uses: actions/checkout@v6\n      - uses: dtolnay/rust-toolchain@stable\n        with:\n          components: clippy\n      - name: install webkit2gtk\n        if: matrix.platform == 'ubuntu-latest'\n        run: |\n          sudo apt-get update\n          sudo apt-get install -y  webkit2gtk-4.1 libayatana-appindicator3-dev libxdo-dev\n      - uses: Swatinem/rust-cache@v2\n      - run: cargo clippy --workspace --all-targets --all-features -- -D warnings\n\n  rust-test:\n    strategy:\n      fail-fast: false\n      matrix:\n        platform: [ubuntu-latest, macos-latest, windows-latest]\n\n    runs-on: ${{ matrix.platform }}\n\n    steps:\n      - uses: actions/checkout@v6\n      - name: install webkit2gtk\n        if: matrix.platform == 'ubuntu-latest'\n        run: |\n          sudo apt-get update\n          sudo apt-get install -y webkit2gtk-4.1 libayatana-appindicator3-dev libxdo-dev\n      - uses: dtolnay/rust-toolchain@stable\n      - uses: Swatinem/rust-cache@v2\n      - run: cargo test --workspace --lib --bins --tests --benches --all-features --no-fail-fast\n\n  deny:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - uses: EmbarkStudios/cargo-deny-action@v2\n"
  },
  {
    "path": ".github/workflows/covector-status.yml",
    "content": "name: Covector Status\non: [pull_request]\n\njobs:\n  covector:\n    if: ${{ !startsWith(github.head_ref, 'renovate/') }}\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n      - name: covector status\n        uses: jbolda/covector/packages/action@covector-v0\n        id: covector\n        with:\n          command: \"status\"\n"
  },
  {
    "path": ".github/workflows/covector-version-or-publish.yml",
    "content": "name: Covector Version or Publish\n\non:\n  push:\n    branches:\n      - main\n\njobs:\n  version-or-publish:\n    if: ${{ !startsWith(github.head_ref, 'renovate/') }}\n    runs-on: ubuntu-latest\n    permissions:\n      contents: write\n      pull-requests: write\n    timeout-minutes: 65\n    outputs:\n      change: ${{ steps.covector.outputs.change }}\n      commandRan: ${{ steps.covector.outputs.commandRan }}\n      successfulPublish: ${{ steps.covector.outputs.successfulPublish }}\n\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n\n      - name: cargo login\n        run: cargo login ${{ secrets.CRATES_IO_TOKEN  }}\n      - name: git config\n        run: |\n          git config --global user.name \"${{ github.event.pusher.name }}\"\n          git config --global user.email \"${{ github.event.pusher.email }}\"\n\n      - name: covector version or publish (publish when no change files present)\n        uses: jbolda/covector/packages/action@covector-v0\n        id: covector\n        with:\n          token: ${{ secrets.GITHUB_TOKEN }}\n          command: \"version-or-publish\"\n          createRelease: true\n\n      - name: Create Pull Request With Versions Bumped\n        if: steps.covector.outputs.commandRan == 'version'\n        uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7.0.11\n        with:\n          token: ${{ secrets.GITHUB_TOKEN }}\n          branch: release/version-updates\n          title: \"release: apply version updates from current changes\"\n          commit-message: \"release: apply version updates from current changes\"\n          labels: \"version updates\"\n          body: ${{ steps.covector.outputs.change }}\n          sign-commits: true\n\n      - name: Trigger `@crabnebula/packager` publishing workflow\n        if: |\n          steps.covector.outputs.successfulPublish == 'true' &&\n          contains(steps.covector.outputs.packagesPublished, '@crabnebula/packager')\n        uses: peter-evans/repository-dispatch@v3\n        with:\n          event-type: publish-packager-nodejs\n          client-payload: >-\n            {\"releaseId\": \"${{ steps.covector.outputs['-crabnebula-packager-releaseId'] }}\" }\n\n      - name: Trigger `@crabnebula/updater` publishing workflow\n        if: |\n          steps.covector.outputs.successfulPublish == 'true' &&\n          contains(steps.covector.outputs.packagesPublished, '@crabnebula/updater')\n        uses: peter-evans/repository-dispatch@v3\n        with:\n          event-type: publish-updater-nodejs\n          client-payload: >-\n            {\"releaseId\": \"${{ steps.covector.outputs['-crabnebula-updater-releaseId'] }}\" }\n\n      - name: Trigger `@crabnebula/packager-resource-resolver` publishing workflow\n        if: |\n          steps.covector.outputs.successfulPublish == 'true' &&\n          contains(steps.covector.outputs.packagesPublished, '@crabnebula/packager-resource-resolver')\n        uses: peter-evans/repository-dispatch@v3\n        with:\n          event-type: publish-packager-resource-resolver-nodejs\n          client-payload: >-\n            {\"releaseId\": \"${{ steps.covector.outputs['-crabnebula-packager-resource-resolver-releaseId'] }}\" }\n"
  },
  {
    "path": ".github/workflows/integration-tests.yml",
    "content": "name: integration tests\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n    branches:\n      - main\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  test-rust:\n    if: ${{ !startsWith(github.head_ref, 'renovate/') }}\n    runs-on: ${{ matrix.platform }}\n\n    strategy:\n      fail-fast: false\n      matrix:\n        platform: [ubuntu-latest, macos-latest, windows-latest]\n\n    steps:\n      - uses: actions/checkout@v6\n      - name: install fuse\n        if: matrix.platform == 'ubuntu-latest'\n        run: |\n          sudo apt-get update\n          sudo apt-get install -y fuse libfuse2\n      - uses: dtolnay/rust-toolchain@stable\n      - uses: Swatinem/rust-cache@v2\n      - run: cargo test --test '*' -- --ignored --nocapture\n\n  test-nodejs:\n    runs-on: ${{ matrix.platform }}\n\n    strategy:\n      fail-fast: false\n      matrix:\n        platform: [ubuntu-latest, macos-latest, windows-latest]\n\n    steps:\n      - uses: actions/checkout@v6\n      - uses: dtolnay/rust-toolchain@stable\n      - uses: pnpm/action-setup@v4\n        with:\n          version: latest\n      - uses: actions/setup-node@v6\n      - uses: Swatinem/rust-cache@v2\n      - run: pnpm install\n      - run: pnpm build\n      - name: packager integration tests\n        run: |\n          cd bindings/packager/nodejs\n          pnpm test\n        timeout-minutes: 30\n      - name: install fuse\n        if: matrix.platform == 'ubuntu-latest'\n        run: |\n          sudo apt-get update\n          sudo apt-get install -y fuse libfuse2\n      - name: updater integration tests\n        run: |\n          cd bindings/updater/nodejs\n          pnpm test\n        timeout-minutes: 30\n"
  },
  {
    "path": ".github/workflows/publish-packager-nodejs.yml",
    "content": "name: Publish `@crabnebula/packager`\n\nenv:\n  DEBUG: napi:*\n  APP_NAME: packager\n  MACOSX_DEPLOYMENT_TARGET: \"10.13\"\n\npermissions:\n  contents: write\n  id-token: write\n\non:\n  workflow_dispatch:\n    inputs:\n      releaseId:\n        description: \"ID of the `@crabnebula/packager` release\"\n        required: true\n  repository_dispatch:\n    types: [publish-packager-nodejs]\n\ndefaults:\n  run:\n    working-directory: bindings/packager/nodejs\n\njobs:\n  build:\n    strategy:\n      fail-fast: false\n      matrix:\n        settings:\n          - host: macos-latest\n            target: x86_64-apple-darwin\n            build: |\n              pnpm build\n              strip -x *.node\n          - host: windows-latest\n            build: pnpm build\n            target: x86_64-pc-windows-msvc\n          - host: windows-latest\n            build: pnpm build --target i686-pc-windows-msvc\n            target: i686-pc-windows-msvc\n          - host: ubuntu-latest\n            target: x86_64-unknown-linux-gnu\n            docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian\n            build: |\n              npm i -g --force corepack\n              cd bindings/packager/nodejs\n              set -e &&\n              pnpm build --target x86_64-unknown-linux-gnu &&\n              strip *.node\n          - host: ubuntu-latest\n            target: x86_64-unknown-linux-musl\n            docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine\n            build: |\n              cd bindings/packager/nodejs\n              set -e\n              pnpm build\n              strip *.node\n          - host: macos-latest\n            target: aarch64-apple-darwin\n            build: |\n              pnpm build --target aarch64-apple-darwin --features native-tls-vendored --cargo-flags=\"--no-default-features\"\n              strip -x *.node\n          - host: ubuntu-latest\n            target: aarch64-unknown-linux-gnu\n            docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian-aarch64\n            build: |\n              npm i -g --force corepack\n              cd bindings/packager/nodejs\n              set -e &&\n              pnpm build --target aarch64-unknown-linux-gnu &&\n              aarch64-unknown-linux-gnu-strip *.node\n          - host: ubuntu-latest\n            target: armv7-unknown-linux-gnueabihf\n            setup: |\n              sudo apt-get update\n              sudo apt-get install gcc-arm-linux-gnueabihf -y\n            build: |\n              pnpm build --target armv7-unknown-linux-gnueabihf\n              arm-linux-gnueabihf-strip *.node\n          - host: ubuntu-latest\n            target: aarch64-unknown-linux-musl\n            docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine\n            build: |\n              cd bindings/packager/nodejs\n              set -e &&\n              rustup target add aarch64-unknown-linux-musl &&\n              pnpm build --target aarch64-unknown-linux-musl &&\n              /aarch64-linux-musl-cross/bin/aarch64-linux-musl-strip *.node\n          - host: windows-latest\n            target: aarch64-pc-windows-msvc\n            build: pnpm build --target aarch64-pc-windows-msvc --features native-tls-vendored --cargo-flags=\"--no-default-features\"\n    name: stable - ${{ matrix.settings.target }} - node@18\n    runs-on: ${{ matrix.settings.host }}\n    steps:\n      - uses: actions/checkout@v6\n      - run: npm i -g --force corepack && corepack enable\n      - uses: pnpm/action-setup@v4\n        with:\n          version: latest\n          package_json_file: bindings/packager/nodejs\n      - uses: actions/setup-node@v6\n        if: ${{ !matrix.settings.docker }}\n      - name: Install\n        uses: dtolnay/rust-toolchain@stable\n        if: ${{ !matrix.settings.docker }}\n        with:\n          toolchain: stable\n          targets: ${{ matrix.settings.target }}\n      - name: Cache cargo\n        uses: actions/cache@v4\n        with:\n          path: |\n            ~/.cargo/registry/index/\n            ~/.cargo/registry/cache/\n            ~/.cargo/git/db/\n            .cargo-cache\n            target/\n          key: ${{ matrix.settings.target }}-cargo-${{ matrix.settings.host }}\n      - uses: goto-bus-stop/setup-zig@v2\n        if: ${{ matrix.settings.target == 'armv7-unknown-linux-gnueabihf' }}\n        with:\n          version: 0.11.0\n      - name: Setup toolchain\n        run: ${{ matrix.settings.setup }}\n        if: ${{ matrix.settings.setup }}\n        shell: bash\n      - name: Setup node x86\n        if: matrix.settings.target == 'i686-pc-windows-msvc'\n        run: pnpm config set supportedArchitectures.cpu \"ia32\"\n        shell: bash\n      - run: pnpm install\n      - name: Setup node x86\n        uses: actions/setup-node@v6\n        if: matrix.settings.target == 'i686-pc-windows-msvc'\n        with:\n          node-version: 18\n          architecture: x86\n      - name: Build in docker\n        uses: addnab/docker-run-action@v3\n        if: ${{ matrix.settings.docker }}\n        with:\n          image: ${{ matrix.settings.docker }}\n          options: \"--user 0:0 -v ${{ github.workspace }}/.cargo-cache/git/db:/usr/local/cargo/git/db -v ${{ github.workspace }}/.cargo/registry/cache:/usr/local/cargo/registry/cache -v ${{ github.workspace }}/.cargo/registry/index:/usr/local/cargo/registry/index -v ${{ github.workspace }}:/build -w /build\"\n          run: ${{ matrix.settings.build }}\n      - name: Build\n        run: ${{ matrix.settings.build }}\n        if: ${{ !matrix.settings.docker }}\n        shell: bash\n      - name: Upload artifact\n        uses: actions/upload-artifact@v4\n        with:\n          name: bindings-${{ matrix.settings.target }}\n          path: bindings/packager/nodejs/${{ env.APP_NAME }}.*.node\n          if-no-files-found: error\n  test-macOS-windows-binding:\n    name: Test bindings on ${{ matrix.settings.target }} - node@${{ matrix.node }}\n    needs:\n      - build\n    strategy:\n      fail-fast: false\n      matrix:\n        settings:\n          - host: macos-latest\n            target: x86_64-apple-darwin\n          - host: windows-latest\n            target: x86_64-pc-windows-msvc\n        node:\n          - \"18\"\n          - \"20\"\n    runs-on: ${{ matrix.settings.host }}\n    steps:\n      - uses: actions/checkout@v6\n      - run: npm i -g --force corepack && corepack enable\n      - uses: pnpm/action-setup@v4\n        with:\n          version: latest\n          package_json_file: bindings/packager/nodejs\n      - uses: actions/setup-node@v6\n        with:\n          node-version: ${{ matrix.node }}\n      - run: pnpm install\n      - name: Download artifacts\n        uses: actions/download-artifact@v4\n        with:\n          name: bindings-${{ matrix.settings.target }}\n          path: bindings/packager/nodejs\n      - name: List packages\n        run: ls -R .\n        shell: bash\n      - name: Test bindings\n        run: |\n          pnpm postbuild\n          pnpm test\n  test-linux-x64-gnu-binding:\n    name: Test bindings on Linux-x64-gnu - node@${{ matrix.node }}\n    needs:\n      - build\n    strategy:\n      fail-fast: false\n      matrix:\n        node:\n          - \"18\"\n          - \"20\"\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - run: npm i -g --force corepack && corepack enable\n      - uses: pnpm/action-setup@v4\n        with:\n          version: latest\n          package_json_file: bindings/packager/nodejs\n      - uses: actions/setup-node@v6\n        with:\n          node-version: ${{ matrix.node }}\n      - run: pnpm install\n      - name: Download artifacts\n        uses: actions/download-artifact@v4\n        with:\n          name: bindings-x86_64-unknown-linux-gnu\n          path: bindings/packager/nodejs\n      - name: List packages\n        run: ls -R .\n        shell: bash\n      - name: Test bindings\n        working-directory: .\n        run: docker run --rm -v $(pwd):/build -w /build node:${{ matrix.node }}-slim sh -c \"cd bindings/packager/nodejs && yarn postbuild && PACKAGER_FORMATS=deb yarn test\"\n  test-linux-x64-musl-binding:\n    name: Test bindings on x86_64-unknown-linux-musl - node@${{ matrix.node }}\n    needs:\n      - build\n    strategy:\n      fail-fast: false\n      matrix:\n        node:\n          - \"18\"\n          - \"20\"\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - run: npm i -g --force corepack && corepack enable\n      - uses: pnpm/action-setup@v4\n        with:\n          version: latest\n          package_json_file: bindings/packager/nodejs\n      - uses: actions/setup-node@v6\n        with:\n          node-version: ${{ matrix.node }}\n      - name: Install dependencies\n        run: |\n          pnpm config set supportedArchitectures.libc \"musl\"\n          pnpm install\n      - name: Download artifacts\n        uses: actions/download-artifact@v4\n        with:\n          name: bindings-x86_64-unknown-linux-musl\n          path: bindings/packager/nodejs\n      - name: List packages\n        run: ls -R .\n        shell: bash\n      - name: Test bindings\n        working-directory: .\n        run: docker run --rm -v $(pwd):/build -w /build node:${{ matrix.node }}-alpine sh -c \"cd bindings/packager/nodejs && yarn postbuild && PACKAGER_FORMATS=deb yarn test\"\n  test-linux-aarch64-gnu-binding:\n    name: Test bindings on aarch64-unknown-linux-gnu - node@${{ matrix.node }}\n    needs:\n      - build\n    strategy:\n      fail-fast: false\n      matrix:\n        node:\n          - \"18\"\n          - \"20\"\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - run: npm i -g --force corepack && corepack enable\n      - name: Download artifacts\n        uses: actions/download-artifact@v4\n        with:\n          name: bindings-aarch64-unknown-linux-gnu\n          path: bindings/packager/nodejs\n      - name: List packages\n        run: ls -R .\n        shell: bash\n      - uses: pnpm/action-setup@v4\n        with:\n          version: latest\n          package_json_file: bindings/packager/nodejs\n      - name: Install dependencies\n        run: |\n          pnpm config set supportedArchitectures.cpu \"arm64\"\n          pnpm config set supportedArchitectures.libc \"glibc\"\n          pnpm install\n      - name: Set up QEMU\n        uses: docker/setup-qemu-action@v3\n        with:\n          platforms: arm64\n      - run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes\n      - name: Setup and run tests\n        uses: addnab/docker-run-action@v3\n        with:\n          image: node:${{ matrix.node }}-slim\n          options: \"--platform linux/arm64 -v ${{ github.workspace }}:/build -w /build\"\n          run: |\n            export PACKAGER_FORMATS=deb\n            set -e\n            cd bindings/packager/nodejs\n            yarn postbuild\n            yarn test\n            ls -la\n  test-linux-aarch64-musl-binding:\n    name: Test bindings on aarch64-unknown-linux-musl - node@${{ matrix.node }}\n    needs:\n      - build\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - run: npm i -g --force corepack && corepack enable\n      - name: Download artifacts\n        uses: actions/download-artifact@v4\n        with:\n          name: bindings-aarch64-unknown-linux-musl\n          path: bindings/packager/nodejs\n      - name: List packages\n        run: ls -R .\n        shell: bash\n      - uses: pnpm/action-setup@v4\n        with:\n          version: latest\n          package_json_file: bindings/packager/nodejs\n      - name: Install dependencies\n        run: |\n          pnpm config set supportedArchitectures.cpu \"arm64\"\n          pnpm config set supportedArchitectures.libc \"musl\"\n          pnpm install\n      - name: Set up QEMU\n        uses: docker/setup-qemu-action@v3\n        with:\n          platforms: arm64\n      - run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes\n      - name: Setup and run tests\n        uses: addnab/docker-run-action@v3\n        with:\n          image: node:lts-alpine\n          options: \"--platform linux/arm64 -v ${{ github.workspace }}:/build -w /build\"\n          run: |\n            export PACKAGER_FORMATS=deb\n            set -e\n            cd bindings/packager/nodejs\n            yarn postbuild\n            yarn test\n  test-linux-arm-gnueabihf-binding:\n    name: Test bindings on armv7-unknown-linux-gnueabihf - node@${{ matrix.node }}\n    needs:\n      - build\n    strategy:\n      fail-fast: false\n      matrix:\n        node:\n          - \"18\"\n          - \"20\"\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - run: npm i -g --force corepack && corepack enable\n      - name: Download artifacts\n        uses: actions/download-artifact@v4\n        with:\n          name: bindings-armv7-unknown-linux-gnueabihf\n          path: bindings/packager/nodejs\n      - name: List packages\n        run: ls -R .\n        shell: bash\n      - uses: pnpm/action-setup@v4\n        with:\n          version: latest\n          package_json_file: bindings/packager/nodejs\n      - name: Install dependencies\n        run: |\n          pnpm config set supportedArchitectures.cpu \"arm\"\n          pnpm install\n      - name: Set up QEMU\n        uses: docker/setup-qemu-action@v3\n        with:\n          platforms: arm\n      - run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes\n      - name: Setup and run tests\n        uses: addnab/docker-run-action@v3\n        with:\n          image: node:${{ matrix.node }}-bullseye-slim\n          options: \"--platform linux/arm/v7 -v ${{ github.workspace }}:/build -w /build\"\n          run: |\n            export PACKAGER_FORMATS=deb\n            set -e\n            cd bindings/packager/nodejs\n            yarn postbuild\n            yarn test\n            ls -la\n  publish:\n    name: Publish\n    runs-on: ubuntu-latest\n    needs:\n      - test-macOS-windows-binding\n      - test-linux-x64-gnu-binding\n      - test-linux-x64-musl-binding\n      - test-linux-aarch64-gnu-binding\n      - test-linux-aarch64-musl-binding\n      - test-linux-arm-gnueabihf-binding\n    steps:\n      - uses: actions/checkout@v6\n      - run: npm i -g --force corepack && corepack enable\n      - uses: pnpm/action-setup@v4\n        with:\n          version: latest\n          package_json_file: bindings/packager/nodejs\n      - uses: actions/setup-node@v6\n      - run: pnpm install\n      - name: Download all artifacts\n        uses: actions/download-artifact@v4\n        with:\n          path: bindings/packager/nodejs/artifacts\n      - name: Move artifacts\n        run: pnpm artifacts\n      - name: List packages\n        run: ls -R ./npm\n        shell: bash\n      - name: build TS binding\n        run: pnpm postbuild\n      - name: Publish\n        run: |\n          echo \"//registry.npmjs.org/:_authToken=$NPM_TOKEN\" >> ~/.npmrc\n          npm publish --access public\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}\n          RELEASE_ID: ${{ github.event.client_payload.releaseId || inputs.releaseId }}\n"
  },
  {
    "path": ".github/workflows/publish-packager-resource-resolver-nodejs.yml",
    "content": "name: Publish `@crabnebula/packager-resource-resolver`\n\nenv:\n  DEBUG: napi:*\n  APP_NAME: packager-resource-resolver\n  MACOSX_DEPLOYMENT_TARGET: \"10.13\"\n\npermissions:\n  contents: write\n  id-token: write\n\non:\n  workflow_dispatch:\n    inputs:\n      releaseId:\n        description: \"ID of the `@crabnebula/packager-resource-resolver` release\"\n        required: true\n  repository_dispatch:\n    types: [publish-packager-resource-resolver-nodejs]\n\ndefaults:\n  run:\n    working-directory: bindings/resource-resolver/nodejs\n\njobs:\n  build:\n    strategy:\n      fail-fast: false\n      matrix:\n        settings:\n          - host: macos-latest\n            target: x86_64-apple-darwin\n            build: |\n              pnpm build\n              strip -x *.node\n          - host: windows-latest\n            build: pnpm build\n            target: x86_64-pc-windows-msvc\n          - host: windows-latest\n            build: pnpm build --target i686-pc-windows-msvc\n            target: i686-pc-windows-msvc\n          - host: ubuntu-latest\n            target: x86_64-unknown-linux-gnu\n            docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian\n            build: |\n              npm i -g --force corepack\n              cd bindings/resource-resolver/nodejs\n              set -e &&\n              pnpm build --target x86_64-unknown-linux-gnu &&\n              strip *.node\n          - host: ubuntu-latest\n            target: x86_64-unknown-linux-musl\n            docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine\n            build: |\n              cd bindings/resource-resolver/nodejs\n              set -e\n              pnpm build\n              strip *.node\n          - host: macos-latest\n            target: aarch64-apple-darwin\n            build: |\n              pnpm build --target aarch64-apple-darwin\n              strip -x *.node\n          - host: ubuntu-latest\n            target: aarch64-unknown-linux-gnu\n            docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian-aarch64\n            build: |\n              npm i -g --force corepack\n              cd bindings/resource-resolver/nodejs\n              set -e &&\n              pnpm build --target aarch64-unknown-linux-gnu &&\n              aarch64-unknown-linux-gnu-strip *.node\n          - host: ubuntu-latest\n            target: armv7-unknown-linux-gnueabihf\n            setup: |\n              sudo apt-get update\n              sudo apt-get install gcc-arm-linux-gnueabihf -y\n            build: |\n              npm i -g --force corepack\n              pnpm build --target armv7-unknown-linux-gnueabihf\n              arm-linux-gnueabihf-strip *.node\n          - host: ubuntu-latest\n            target: aarch64-unknown-linux-musl\n            docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine\n            build: |\n              npm i -g --force corepack\n              cd bindings/resource-resolver/nodejs\n              set -e &&\n              rustup target add aarch64-unknown-linux-musl &&\n              pnpm build --target aarch64-unknown-linux-musl &&\n              /aarch64-linux-musl-cross/bin/aarch64-linux-musl-strip *.node\n          - host: windows-latest\n            target: aarch64-pc-windows-msvc\n            build: pnpm build --target aarch64-pc-windows-msvc\n    name: stable - ${{ matrix.settings.target }} - node@18\n    runs-on: ${{ matrix.settings.host }}\n    steps:\n      - uses: actions/checkout@v6\n      - run: npm i -g --force corepack && corepack enable\n      - uses: pnpm/action-setup@v4\n        with:\n          version: latest\n          package_json_file: bindings/resource-resolver/nodejs\n      - uses: actions/setup-node@v6\n        if: ${{ !matrix.settings.docker }}\n      - name: Install\n        uses: dtolnay/rust-toolchain@stable\n        if: ${{ !matrix.settings.docker }}\n        with:\n          toolchain: stable\n          targets: ${{ matrix.settings.target }}\n      - name: Cache cargo\n        uses: actions/cache@v4\n        with:\n          path: |\n            ~/.cargo/registry/index/\n            ~/.cargo/registry/cache/\n            ~/.cargo/git/db/\n            .cargo-cache\n            target/\n          key: ${{ matrix.settings.target }}-cargo-${{ matrix.settings.host }}\n      - uses: goto-bus-stop/setup-zig@v2\n        if: ${{ matrix.settings.target == 'armv7-unknown-linux-gnueabihf' }}\n        with:\n          version: 0.11.0\n      - name: Setup toolchain\n        run: ${{ matrix.settings.setup }}\n        if: ${{ matrix.settings.setup }}\n        shell: bash\n      - name: Setup node x86\n        if: matrix.settings.target == 'i686-pc-windows-msvc'\n        run: pnpm config set supportedArchitectures.cpu \"ia32\"\n        shell: bash\n      - run: pnpm install\n      - name: Setup node x86\n        uses: actions/setup-node@v6\n        if: matrix.settings.target == 'i686-pc-windows-msvc'\n        with:\n          node-version: 18\n          architecture: x86\n      - name: Build in docker\n        uses: addnab/docker-run-action@v3\n        if: ${{ matrix.settings.docker }}\n        with:\n          image: ${{ matrix.settings.docker }}\n          options: \"--user 0:0 -v ${{ github.workspace }}/.cargo-cache/git/db:/usr/local/cargo/git/db -v ${{ github.workspace }}/.cargo/registry/cache:/usr/local/cargo/registry/cache -v ${{ github.workspace }}/.cargo/registry/index:/usr/local/cargo/registry/index -v ${{ github.workspace }}:/build -w /build\"\n          run: ${{ matrix.settings.build }}\n      - name: Build\n        run: ${{ matrix.settings.build }}\n        if: ${{ !matrix.settings.docker }}\n        shell: bash\n      - name: Upload artifact\n        uses: actions/upload-artifact@v4\n        with:\n          name: bindings-${{ matrix.settings.target }}\n          path: bindings/resource-resolver/nodejs/${{ env.APP_NAME }}.*.node\n          if-no-files-found: error\n  test-macOS-windows-binding:\n    name: Test bindings on ${{ matrix.settings.target }} - node@${{ matrix.node }}\n    needs:\n      - build\n    strategy:\n      fail-fast: false\n      matrix:\n        settings:\n          - host: macos-latest\n            target: x86_64-apple-darwin\n          - host: windows-latest\n            target: x86_64-pc-windows-msvc\n        node:\n          - \"18\"\n          - \"20\"\n    runs-on: ${{ matrix.settings.host }}\n    steps:\n      - uses: actions/checkout@v6\n      - run: npm i -g --force corepack && corepack enable\n      - uses: pnpm/action-setup@v4\n        with:\n          version: latest\n          package_json_file: bindings/resource-resolver/nodejs\n      - uses: actions/setup-node@v6\n        with:\n          node-version: ${{ matrix.node }}\n      - run: pnpm install\n      - name: Download artifacts\n        uses: actions/download-artifact@v4\n        with:\n          name: bindings-${{ matrix.settings.target }}\n          path: bindings/resource-resolver/nodejs\n      - name: List packages\n        run: ls -R .\n        shell: bash\n      - name: Test bindings\n        run: pnpm test\n  test-linux-x64-gnu-binding:\n    name: Test bindings on Linux-x64-gnu - node@${{ matrix.node }}\n    needs:\n      - build\n    strategy:\n      fail-fast: false\n      matrix:\n        node:\n          - \"18\"\n          - \"20\"\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - run: npm i -g --force corepack && corepack enable\n      - uses: pnpm/action-setup@v4\n        with:\n          version: latest\n          package_json_file: bindings/resource-resolver/nodejs\n      - uses: actions/setup-node@v6\n        with:\n          node-version: ${{ matrix.node }}\n      - run: pnpm install\n      - name: Download artifacts\n        uses: actions/download-artifact@v4\n        with:\n          name: bindings-x86_64-unknown-linux-gnu\n          path: bindings/resource-resolver/nodejs\n      - name: List packages\n        run: ls -R .\n        shell: bash\n      - name: Test bindings\n        working-directory: .\n        run: docker run --rm -v $(pwd):/build -w /build node:${{ matrix.node }}-slim sh -c \"cd bindings/resource-resolver/nodejs && yarn test\"\n  test-linux-x64-musl-binding:\n    name: Test bindings on x86_64-unknown-linux-musl - node@${{ matrix.node }}\n    needs:\n      - build\n    strategy:\n      fail-fast: false\n      matrix:\n        node:\n          - \"18\"\n          - \"20\"\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - run: npm i -g --force corepack && corepack enable\n      - uses: pnpm/action-setup@v4\n        with:\n          version: latest\n          package_json_file: bindings/resource-resolver/nodejs\n      - uses: actions/setup-node@v6\n        with:\n          node-version: ${{ matrix.node }}\n      - name: Install dependencies\n        run: |\n          pnpm config set supportedArchitectures.libc \"musl\"\n          pnpm install\n      - name: Download artifacts\n        uses: actions/download-artifact@v4\n        with:\n          name: bindings-x86_64-unknown-linux-musl\n          path: bindings/resource-resolver/nodejs\n      - name: List packages\n        run: ls -R .\n        shell: bash\n      - name: Test bindings\n        working-directory: .\n        run: docker run --rm -v $(pwd):/build -w /build node:${{ matrix.node }}-alpine sh -c \"cd bindings/resource-resolver/nodejs && yarn test\"\n  test-linux-aarch64-gnu-binding:\n    name: Test bindings on aarch64-unknown-linux-gnu - node@${{ matrix.node }}\n    needs:\n      - build\n    strategy:\n      fail-fast: false\n      matrix:\n        node:\n          - \"18\"\n          - \"20\"\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - run: npm i -g --force corepack && corepack enable\n      - name: Download artifacts\n        uses: actions/download-artifact@v4\n        with:\n          name: bindings-aarch64-unknown-linux-gnu\n          path: bindings/resource-resolver/nodejs\n      - name: List packages\n        run: ls -R .\n        shell: bash\n      - uses: pnpm/action-setup@v4\n        with:\n          version: latest\n          package_json_file: bindings/resource-resolver/nodejs\n      - name: Install dependencies\n        run: |\n          pnpm config set supportedArchitectures.cpu \"arm64\"\n          pnpm config set supportedArchitectures.libc \"glibc\"\n          pnpm install\n      - name: Set up QEMU\n        uses: docker/setup-qemu-action@v3\n        with:\n          platforms: arm64\n      - run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes\n      - name: Setup and run tests\n        uses: addnab/docker-run-action@v3\n        with:\n          image: node:${{ matrix.node }}-slim\n          options: \"--platform linux/arm64 -v ${{ github.workspace }}:/build -w /build\"\n          run: |\n            set -e\n            cd bindings/resource-resolver/nodejs\n            yarn test\n            ls -la\n  test-linux-aarch64-musl-binding:\n    name: Test bindings on aarch64-unknown-linux-musl - node@${{ matrix.node }}\n    needs:\n      - build\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - run: npm i -g --force corepack && corepack enable\n      - name: Download artifacts\n        uses: actions/download-artifact@v4\n        with:\n          name: bindings-aarch64-unknown-linux-musl\n          path: bindings/resource-resolver/nodejs\n      - name: List packages\n        run: ls -R .\n        shell: bash\n      - uses: pnpm/action-setup@v4\n        with:\n          version: latest\n          package_json_file: bindings/resource-resolver/nodejs\n      - name: Install dependencies\n        run: |\n          pnpm config set supportedArchitectures.cpu \"arm64\"\n          pnpm config set supportedArchitectures.libc \"musl\"\n          pnpm install\n      - name: Set up QEMU\n        uses: docker/setup-qemu-action@v3\n        with:\n          platforms: arm64\n      - run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes\n      - name: Setup and run tests\n        uses: addnab/docker-run-action@v3\n        with:\n          image: node:lts-alpine\n          options: \"--platform linux/arm64 -v ${{ github.workspace }}:/build -w /build\"\n          run: |\n            set -e\n            cd bindings/resource-resolver/nodejs\n            yarn test\n  test-linux-arm-gnueabihf-binding:\n    name: Test bindings on armv7-unknown-linux-gnueabihf - node@${{ matrix.node }}\n    needs:\n      - build\n    strategy:\n      fail-fast: false\n      matrix:\n        node:\n          - \"18\"\n          - \"20\"\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - run: npm i -g --force corepack && corepack enable\n      - name: Download artifacts\n        uses: actions/download-artifact@v4\n        with:\n          name: bindings-armv7-unknown-linux-gnueabihf\n          path: bindings/resource-resolver/nodejs\n      - name: List packages\n        run: ls -R .\n        shell: bash\n      - uses: pnpm/action-setup@v4\n        with:\n          version: latest\n          package_json_file: bindings/resource-resolver/nodejs\n      - name: Install dependencies\n        run: |\n          pnpm config set supportedArchitectures.cpu \"arm\"\n          pnpm install\n      - name: Set up QEMU\n        uses: docker/setup-qemu-action@v3\n        with:\n          platforms: arm\n      - run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes\n      - name: Setup and run tests\n        uses: addnab/docker-run-action@v3\n        with:\n          image: node:${{ matrix.node }}-bullseye-slim\n          options: \"--platform linux/arm/v7 -v ${{ github.workspace }}:/build -w /build\"\n          run: |\n            set -e\n            cd bindings/resource-resolver/nodejs\n            yarn test\n            ls -la\n  publish:\n    name: Publish\n    runs-on: ubuntu-latest\n    needs:\n      - build\n      - test-macOS-windows-binding\n      - test-linux-x64-gnu-binding\n      - test-linux-x64-musl-binding\n      - test-linux-aarch64-gnu-binding\n      - test-linux-aarch64-musl-binding\n      - test-linux-arm-gnueabihf-binding\n    steps:\n      - uses: actions/checkout@v6\n      - run: npm i -g --force corepack && corepack enable\n      - uses: pnpm/action-setup@v4\n        with:\n          version: latest\n          package_json_file: bindings/resource-resolver/nodejs\n      - uses: actions/setup-node@v6\n      - run: pnpm install\n      - name: Download all artifacts\n        uses: actions/download-artifact@v4\n        with:\n          path: bindings/resource-resolver/nodejs/artifacts\n      - name: Move artifacts\n        run: pnpm artifacts\n      - name: List packages\n        run: ls -R ./npm\n        shell: bash\n      - name: Publish\n        run: |\n          echo \"//registry.npmjs.org/:_authToken=$NPM_TOKEN\" >> ~/.npmrc\n          npm publish --access public\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}\n          RELEASE_ID: ${{ github.event.client_payload.releaseId || inputs.releaseId }}\n"
  },
  {
    "path": ".github/workflows/publish-updater-nodejs.yml",
    "content": "name: Publish `@crabnebula/updater`\n\nenv:\n  DEBUG: napi:*\n  APP_NAME: updater\n  MACOSX_DEPLOYMENT_TARGET: \"10.13\"\n\npermissions:\n  contents: write\n  id-token: write\n\non:\n  workflow_dispatch:\n    inputs:\n      releaseId:\n        description: \"ID of the `@crabnebula/updater` release\"\n        required: true\n  repository_dispatch:\n    types: [publish-updater-nodejs]\n\ndefaults:\n  run:\n    working-directory: bindings/updater/nodejs\n\njobs:\n  build:\n    strategy:\n      fail-fast: false\n      matrix:\n        settings:\n          - host: macos-latest\n            target: x86_64-apple-darwin\n            build: |\n              pnpm build\n              strip -x *.node\n          - host: windows-latest\n            build: pnpm build\n            target: x86_64-pc-windows-msvc\n          - host: windows-latest\n            build: pnpm build --target i686-pc-windows-msvc\n            target: i686-pc-windows-msvc\n          - host: ubuntu-latest\n            target: x86_64-unknown-linux-gnu\n            docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian\n            build: |\n              npm i -g --force corepack\n              cd bindings/updater/nodejs\n              set -e &&\n              pnpm build --target x86_64-unknown-linux-gnu &&\n              strip *.node\n          - host: ubuntu-latest\n            target: x86_64-unknown-linux-musl\n            docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine\n            build: |\n              cd bindings/updater/nodejs\n              set -e\n              pnpm build\n              strip *.node\n          - host: macos-latest\n            target: aarch64-apple-darwin\n            build: |\n              pnpm build --target aarch64-apple-darwin --features native-tls-vendored --cargo-flags=\"--no-default-features\"\n              strip -x *.node\n          - host: ubuntu-latest\n            target: aarch64-unknown-linux-gnu\n            docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian-aarch64\n            build: |\n              npm i -g --force corepack\n              cd bindings/updater/nodejs\n              set -e &&\n              pnpm build --target aarch64-unknown-linux-gnu &&\n              aarch64-unknown-linux-gnu-strip *.node\n          - host: ubuntu-latest\n            target: armv7-unknown-linux-gnueabihf\n            setup: |\n              sudo apt-get update\n              sudo apt-get install gcc-arm-linux-gnueabihf -y\n            build: |\n              pnpm build --target armv7-unknown-linux-gnueabihf\n              arm-linux-gnueabihf-strip *.node\n          - host: ubuntu-latest\n            target: aarch64-unknown-linux-musl\n            docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine\n            build: |\n              cd bindings/updater/nodejs\n              set -e &&\n              rustup target add aarch64-unknown-linux-musl &&\n              pnpm build --target aarch64-unknown-linux-musl &&\n              /aarch64-linux-musl-cross/bin/aarch64-linux-musl-strip *.node\n          - host: windows-latest\n            target: aarch64-pc-windows-msvc\n            build: pnpm build --target aarch64-pc-windows-msvc --features native-tls-vendored --cargo-flags=\"--no-default-features\"\n    name: stable - ${{ matrix.settings.target }} - node@18\n    runs-on: ${{ matrix.settings.host }}\n    steps:\n      - uses: actions/checkout@v6\n      - run: npm i -g --force corepack && corepack enable\n      - uses: pnpm/action-setup@v4\n        with:\n          version: latest\n          package_json_file: bindings/updater/nodejs\n      - uses: actions/setup-node@v6\n        if: ${{ !matrix.settings.docker }}\n      - name: Install\n        uses: dtolnay/rust-toolchain@stable\n        if: ${{ !matrix.settings.docker }}\n        with:\n          toolchain: stable\n          targets: ${{ matrix.settings.target }}\n      - name: Cache cargo\n        uses: actions/cache@v4\n        with:\n          path: |\n            ~/.cargo/registry/index/\n            ~/.cargo/registry/cache/\n            ~/.cargo/git/db/\n            .cargo-cache\n            target/\n          key: ${{ matrix.settings.target }}-cargo-${{ matrix.settings.host }}\n      - uses: goto-bus-stop/setup-zig@v2\n        if: ${{ matrix.settings.target == 'armv7-unknown-linux-gnueabihf' }}\n        with:\n          version: 0.11.0\n      - name: Setup toolchain\n        run: ${{ matrix.settings.setup }}\n        if: ${{ matrix.settings.setup }}\n        shell: bash\n      - name: Setup node x86\n        if: matrix.settings.target == 'i686-pc-windows-msvc'\n        run: pnpm config set supportedArchitectures.cpu \"ia32\"\n        shell: bash\n      - run: pnpm install\n      - name: Setup node x86\n        uses: actions/setup-node@v6\n        if: matrix.settings.target == 'i686-pc-windows-msvc'\n        with:\n          node-version: 18\n          architecture: x86\n      - name: Build in docker\n        uses: addnab/docker-run-action@v3\n        if: ${{ matrix.settings.docker }}\n        with:\n          image: ${{ matrix.settings.docker }}\n          options: \"--user 0:0 -v ${{ github.workspace }}/.cargo-cache/git/db:/usr/local/cargo/git/db -v ${{ github.workspace }}/.cargo/registry/cache:/usr/local/cargo/registry/cache -v ${{ github.workspace }}/.cargo/registry/index:/usr/local/cargo/registry/index -v ${{ github.workspace }}:/build -w /build\"\n          run: ${{ matrix.settings.build }}\n      - name: Build\n        run: ${{ matrix.settings.build }}\n        if: ${{ !matrix.settings.docker }}\n        shell: bash\n      - name: Upload artifact\n        uses: actions/upload-artifact@v4\n        with:\n          name: bindings-${{ matrix.settings.target }}\n          path: bindings/updater/nodejs/${{ env.APP_NAME }}.*.node\n          if-no-files-found: error\n  # FIXME: updater tests needs packager to be build first\n  #        so we need to figure how to build the packager here as well without duplicating\n  #        the publish-packager-nodejs workflow here\n  # test-macOS-windows-binding:\n  #   name: Test bindings on ${{ matrix.settings.target }} - node@${{ matrix.node }}\n  #   needs:\n  #     - build\n  #   strategy:\n  #     fail-fast: false\n  #     matrix:\n  #       settings:\n  #         - host: macos-latest\n  #           target: x86_64-apple-darwin\n  #         - host: windows-latest\n  #           target: x86_64-pc-windows-msvc\n  #       node:\n  #         - \"18\"\n  #         - \"20\"\n  #   runs-on: ${{ matrix.settings.host }}\n  #   steps:\n  #     - uses: actions/checkout@v4\n  #     - uses: pnpm/action-setup@v2\n  #       with:\n  #         version: latest\n  #         package_json_file: bindings/updater/nodejs\n  #     - uses: actions/setup-node@v4\n  #       with:\n  #         node-version: ${{ matrix.node }}\n  #     - run: pnpm install\n  #     - name: Download artifacts\n  #       uses: actions/download-artifact@v3\n  #       with:\n  #         name: bindings-${{ matrix.settings.target }}\n  #         path: bindings/updater/nodejs\n  #     - name: List packages\n  #       run: ls -R .\n  #       shell: bash\n  #     - name: Test bindings\n  #       run: pnpm test\n  # test-linux-x64-gnu-binding:\n  #   name: Test bindings on Linux-x64-gnu - node@${{ matrix.node }}\n  #   needs:\n  #     - build\n  #   strategy:\n  #     fail-fast: false\n  #     matrix:\n  #       node:\n  #         - \"18\"\n  #         - \"20\"\n  #   runs-on: ubuntu-latest\n  #   steps:\n  #     - uses: actions/checkout@v4\n  #     - uses: pnpm/action-setup@v2\n  #       with:\n  #         version: latest\n  #         package_json_file: bindings/updater/nodejs\n  #     - uses: actions/setup-node@v4\n  #       with:\n  #         node-version: ${{ matrix.node }}\n  #     - run: pnpm install\n  #     - name: Download artifacts\n  #       uses: actions/download-artifact@v3\n  #       with:\n  #         name: bindings-x86_64-unknown-linux-gnu\n  #         path: bindings/updater/nodejs\n  #     - name: List packages\n  #       run: ls -R .\n  #       shell: bash\n  #     - name: Test bindings\n  #       working-directory: .\n  #       run: docker run --rm -v $(pwd):/build -w /build node:${{ matrix.node }}-slim sh -c \"cd bindings/updater/nodejs && yarn test\"\n  # test-linux-x64-musl-binding:\n  #   name: Test bindings on x86_64-unknown-linux-musl - node@${{ matrix.node }}\n  #   needs:\n  #     - build\n  #   strategy:\n  #     fail-fast: false\n  #     matrix:\n  #       node:\n  #         - \"18\"\n  #         - \"20\"\n  #   runs-on: ubuntu-latest\n  #   steps:\n  #     - uses: actions/checkout@v4\n  #     - uses: pnpm/action-setup@v2\n  #       with:\n  #         version: latest\n  #         package_json_file: bindings/updater/nodejs\n  #     - uses: actions/setup-node@v4\n  #       with:\n  #         node-version: ${{ matrix.node }}\n  #     - name: Install dependencies\n  #       run: |\n  #         pnpm config set supportedArchitectures.libc \"musl\"\n  #         pnpm install\n  #     - name: Download artifacts\n  #       uses: actions/download-artifact@v3\n  #       with:\n  #         name: bindings-x86_64-unknown-linux-musl\n  #         path: bindings/updater/nodejs\n  #     - name: List packages\n  #       run: ls -R .\n  #       shell: bash\n  #     - name: Test bindings\n  #       working-directory: .\n  #       run: docker run --rm -v $(pwd):/build -w /build node:${{ matrix.node }}-alpine sh -c \"cd bindings/updater/nodejs && yarn test\"\n  # test-linux-aarch64-gnu-binding:\n  #   name: Test bindings on aarch64-unknown-linux-gnu - node@${{ matrix.node }}\n  #   needs:\n  #     - build\n  #   strategy:\n  #     fail-fast: false\n  #     matrix:\n  #       node:\n  #         - \"18\"\n  #         - \"20\"\n  #   runs-on: ubuntu-latest\n  #   steps:\n  #     - uses: actions/checkout@v4\n  #     - name: Download artifacts\n  #       uses: actions/download-artifact@v3\n  #       with:\n  #         name: bindings-aarch64-unknown-linux-gnu\n  #         path: bindings/updater/nodejs\n  #     - name: List packages\n  #       run: ls -R .\n  #       shell: bash\n  #     - uses: pnpm/action-setup@v2\n  #       with:\n  #         version: latest\n  #         package_json_file: bindings/updater/nodejs\n  #     - name: Install dependencies\n  #       run: |\n  #         pnpm config set supportedArchitectures.cpu \"arm64\"\n  #         pnpm config set supportedArchitectures.libc \"glibc\"\n  #         pnpm install\n  #     - name: Set up QEMU\n  #       uses: docker/setup-qemu-action@v3\n  #       with:\n  #         platforms: arm64\n  #     - run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes\n  #     - name: Setup and run tests\n  #       uses: addnab/docker-run-action@v3\n  #       with:\n  #         image: node:${{ matrix.node }}-slim\n  #         options: \"--platform linux/arm64 -v ${{ github.workspace }}:/build -w /build\"\n  #         run: |\n  #           set -e\n  #           cd bindings/updater/nodejs\n  #           yarn test\n  #           ls -la\n  # test-linux-aarch64-musl-binding:\n  #   name: Test bindings on aarch64-unknown-linux-musl - node@${{ matrix.node }}\n  #   needs:\n  #     - build\n  #   runs-on: ubuntu-latest\n  #   steps:\n  #     - uses: actions/checkout@v4\n  #     - name: Download artifacts\n  #       uses: actions/download-artifact@v3\n  #       with:\n  #         name: bindings-aarch64-unknown-linux-musl\n  #         path: bindings/updater/nodejs\n  #     - name: List packages\n  #       run: ls -R .\n  #       shell: bash\n  #     - uses: pnpm/action-setup@v2\n  #       with:\n  #         version: latest\n  #         package_json_file: bindings/updater/nodejs\n  #     - name: Install dependencies\n  #       run: |\n  #         pnpm config set supportedArchitectures.cpu \"arm64\"\n  #         pnpm config set supportedArchitectures.libc \"musl\"\n  #         pnpm install\n  #     - name: Set up QEMU\n  #       uses: docker/setup-qemu-action@v3\n  #       with:\n  #         platforms: arm64\n  #     - run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes\n  #     - name: Setup and run tests\n  #       uses: addnab/docker-run-action@v3\n  #       with:\n  #         image: node:lts-alpine\n  #         options: \"--platform linux/arm64 -v ${{ github.workspace }}:/build -w /build\"\n  #         run: |\n  #           set -e\n  #           cd bindings/updater/nodejs\n  #           yarn test\n  # test-linux-arm-gnueabihf-binding:\n  #   name: Test bindings on armv7-unknown-linux-gnueabihf - node@${{ matrix.node }}\n  #   needs:\n  #     - build\n  #   strategy:\n  #     fail-fast: false\n  #     matrix:\n  #       node:\n  #         - \"18\"\n  #         - \"20\"\n  #   runs-on: ubuntu-latest\n  #   steps:\n  #     - uses: actions/checkout@v4\n  #     - name: Download artifacts\n  #       uses: actions/download-artifact@v3\n  #       with:\n  #         name: bindings-armv7-unknown-linux-gnueabihf\n  #         path: bindings/updater/nodejs\n  #     - name: List packages\n  #       run: ls -R .\n  #       shell: bash\n  #     - uses: pnpm/action-setup@v2\n  #       with:\n  #         version: latest\n  #         package_json_file: bindings/updater/nodejs\n  #     - name: Install dependencies\n  #       run: |\n  #         pnpm config set supportedArchitectures.cpu \"arm\"\n  #         pnpm install\n  #     - name: Set up QEMU\n  #       uses: docker/setup-qemu-action@v3\n  #       with:\n  #         platforms: arm\n  #     - run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes\n  #     - name: Setup and run tests\n  #       uses: addnab/docker-run-action@v3\n  #       with:\n  #         image: node:${{ matrix.node }}-bullseye-slim\n  #         options: \"--platform linux/arm/v7 -v ${{ github.workspace }}:/build -w /build\"\n  #         run: |\n  #           set -e\n  #           cd bindings/updater/nodejs\n  #           yarn test\n  #           ls -la\n  publish:\n    name: Publish\n    runs-on: ubuntu-latest\n    needs:\n      - build\n      # - test-macOS-windows-binding\n      # - test-linux-x64-gnu-binding\n      # - test-linux-x64-musl-binding\n      # - test-linux-aarch64-gnu-binding\n      # - test-linux-aarch64-musl-binding\n      # - test-linux-arm-gnueabihf-binding\n    steps:\n      - uses: actions/checkout@v6\n      - run: npm i -g --force corepack && corepack enable\n      - uses: pnpm/action-setup@v4\n        with:\n          version: latest\n          package_json_file: bindings/updater/nodejs\n      - uses: actions/setup-node@v6\n      - run: pnpm install\n      - name: Download all artifacts\n        uses: actions/download-artifact@v4\n        with:\n          path: bindings/updater/nodejs/artifacts\n      - name: Move artifacts\n        run: pnpm artifacts\n      - name: List packages\n        run: ls -R ./npm\n        shell: bash\n      - name: Publish\n        run: |\n          echo \"//registry.npmjs.org/:_authToken=$NPM_TOKEN\" >> ~/.npmrc\n          npm publish --access public\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}\n          RELEASE_ID: ${{ github.event.client_payload.releaseId || inputs.releaseId }}\n"
  },
  {
    "path": ".gitignore",
    "content": "logs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nlerna-debug.log*\n\nnode_modules/\n\n*.tgz\n\n.yarn-integrity\n\n.env\n.env.test\n\ndist\nbuild\n\n.DS_Store\n\n/target\nCargo.lock\n\n.pnp.*\n.yarn/*\n!.yarn/patches\n!.yarn/plugins\n!.yarn/releases\n!.yarn/sdks\n!.yarn/versions\n\n*.node\n*.node.bak\n\nyarn.lock\npackage-lock.json\n\n.idea"
  },
  {
    "path": ".npmrc",
    "content": "enable-pre-post-scripts=true\nupdate-notifier=false\nengine-strict=true\nresolution-mode=highest"
  },
  {
    "path": ".prettierignore",
    "content": "dist\nbuild\n\n*.wxs\n*.nsi\n*.nsh\n*.sh\n*.desktop\n*.xml\n*.md\n\npnpm-lock.yaml\n\nschema.json\nbindings/updater/nodejs/index.js\nbindings/updater/nodejs/index.d.ts\nbindings/packager/nodejs/index.js\nbindings/packager/nodejs/index.d.ts\nbindings/packager/nodejs/src-ts/config.d.ts"
  },
  {
    "path": "Cargo.toml",
    "content": "[workspace]\nmembers = [\n    \"crates/*\",\n    \"examples/*\",\n    \"bindings/*/nodejs\",\n    \"crates/updater/tests/app\",\n]\nexclude = [\"examples/deno\", \"examples/wails\", \"examples/electron\"]\nresolver = \"2\"\n\n[workspace.package]\nauthors = [\"CrabNebula Ltd.\"]\nedition = \"2021\"\nlicense = \"Apache-2.0 OR MIT\"\nrepository = \"https://github.com/crabnebula-dev/cargo-packager\"\n\n[workspace.dependencies]\nthiserror = \"2\"\nserde = { version = \"1.0\", features = [\"derive\"] }\nserde_json = \"1.0\"\ndunce = \"1\"\nschemars = { version = \"0.8\", features = [\"url\", \"preserve_order\", \"derive\"] }\nclap = { version = \"4.5\", features = [\"derive\"] }\ndirs = \"6.0\"\nsemver = \"1\"\nbase64 = \"0.22\"\ntracing = \"0.1\"\ntime = \"0.3\"\ntar = \"0.4\"\nnapi = { version = \"2.16\", default-features = false }\nnapi-derive = \"2.16\"\nnapi-build = \"2.1\"\n\n[profile.release-size-optimized]\ninherits = \"release\"\ncodegen-units = 1\nlto = true\nincremental = false\nopt-level = \"s\"\n"
  },
  {
    "path": "LICENSE.spdx",
    "content": "SPDXVersion: SPDX-2.1\nDataLicense: CC0-1.0\nPackageName: cargo-packager\nDataFormat: SPDXRef-1\nPackageSupplier: Organization: CrabNebula Ltd.\nPackageHomePage: https://github.com/crabnebula-dev/cargo-packager\nPackageLicenseDeclared: Apache-2.0\nPackageLicenseDeclared: MIT\nPackageCopyrightText: 2023-2023, CrabNebula Ltd.\nPackageSummary: <text>Rust executable packager, bundler and updater. A tool and library to generate installers or app bundles for your executables.\nIt also has a comptabile updater through cargo-packager-updater crate.\n                </text>\nPackageComment: <text>The package includes the following libraries; see\nRelationship information.\n                </text>\nCreated: 2023-09-30T09:00:00Z\nPackageDownloadLocation: git://github.com/crabnebula-dev/cargo-packager\nPackageDownloadLocation: git+https://github.com/crabnebula-dev/cargo-packager.git\nPackageDownloadLocation: git+ssh://github.com/crabnebula-dev/cargo-packager.git\n"
  },
  {
    "path": "LICENSE_APACHE-2.0",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n"
  },
  {
    "path": "LICENSE_MIT",
    "content": "MIT License\n\nCopyright (c) 2023 - Present CrabNebula Ltd.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# cargo-packager\n\n<img src=\"https://raw.githubusercontent.com/crabnebula-dev/cargo-packager/main/.github/splash.png\" alt=\"cargo-packager splash\" />\n\nExecutable packager, bundler and updater. A cli tool and library to generate installers or app bundles for your executables.\nIt also has a compatible updater through [cargo-packager-updater](./crates/updater/).\n\n#### Supported packages:\n\n- macOS\n  - DMG (.dmg)\n  - Bundle (.app)\n- Linux\n  - Debian package (.deb)\n  - AppImage (.AppImage)\n  - Pacman (.tar.gz and PKGBUILD)\n- Windows\n  - NSIS (.exe)\n  - MSI using WiX Toolset (.msi)\n\n## Rust\n\n### CLI\n\nThe packager is distributed on crates.io as a cargo subcommand, you can install it using cargo:\n\n```sh\ncargo install cargo-packager --locked\n```\n\nYou then need to configure your app so the cli can recognize it. Configuration can be done in `Packager.toml` or `packager.json` in your project or modify Cargo.toml and include this snippet:\n\n```toml\n[package.metadata.packager]\nbefore-packaging-command = \"cargo build --release\"\n```\n\nOnce, you are done configuring your app, run:\n\n```sh\ncargo packager --release\n```\n\n### Configuration\n\nBy default, the packager reads its configuration from `Packager.toml` or `packager.json` if it exists, and from `package.metadata.packager` table in `Cargo.toml`.\nYou can also specify a custom configuration using the `-c/--config` cli argument.\n\nFor a full list of configuration options, see https://docs.rs/cargo-packager/latest/cargo_packager/config/struct.Config.html.\n\nYou could also use the [schema](./crates/packager/schema.json) file from GitHub to validate your configuration or have auto completions in your IDE.\n\n### Building your application before packaging\n\nBy default, the packager doesn't build your application, so if your app requires a compilation step, the packager has an option to specify a shell command to be executed before packaing your app, `beforePackagingCommand`.\n\n### Cargo profiles\n\nBy default, the packager looks for binaries built using the `debug` profile, if your `beforePackagingCommand` builds your app using `cargo build --release`, you will also need to\nrun the packager in release mode `cargo packager --release`, otherwise, if you have a custom cargo profile, you will need to specify it using `--profile` cli arg `cargo packager --profile custom-release-profile`.\n\n### Library\n\nThis crate is also published to crates.io as a library that you can integrate into your tooling, just make sure to disable the default-feature flags.\n\n```sh\ncargo add cargo-packager --no-default-features\n```\n\n#### Feature flags\n\n- **`cli`**: Enables the cli specifc features and dependencies. Enabled by default.\n- **`tracing`**: Enables `tracing` crate integration.\n\n## NPM (Node.js)\n\nCheckout the packager NPM cli [README](./bindings/packager/nodejs/README.md)\n\n## Examples\n\nThe [`examples`](./examples/) directory contains a number of varying examples, if you want to build them all run `cargo r -p cargo-packager -- --release` in the root of this repository. Just make sure to have the tooling for each example installed on your system. You can find what tooling they require by checking the README in each example. The README also contains a command to build this example alone if you wish.\n\nExamples list (non-exhaustive):\n\n- [`tauri`](./examples/tauri/)\n- [`wry`](./examples/wry/)\n- [`dioxus`](./examples/dioxus/)\n- [`egui`](./examples/egui/)\n- [`deno`](./examples/deno/)\n- [`slint`](./examples/slint/)\n- [`wails`](./examples/wails)\n\n## Licenses\n\nMIT or MIT/Apache 2.0 where applicable.\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\n**Do not report security vulnerabilities through public GitHub issues.**\n\n**Please use the [Private Vulnerability Disclosure](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability) feature of GitHub.**\n\nAlternatively, you can also send them by email to security@crabnebula.dev.\nYou can encrypt your mail using GnuPG if you want. \n\nSee the [security.txt](https://crabnebula.dev/.well-known/security.txt) from CrabNebula\n\n```\nContact: mailto:security@crabnebula.dev\nExpires: 2025-01-30T06:30:00.000Z\nEncryption: https://crabnebula.dev/.well-known/pgp.txt\nPreferred-Languages: en,de,fr\nCanonical: https://crabnebula.dev/.well-known/security.txt\n```\n\nInclude as much of the following information:\n\n- Type of issue (e.g. buffer overflow, privilege escalation, etc.)\n- The location of the affected source code (tag/branch/commit or direct URL)\n- Any special configuration required to reproduce the issue\n- The distribution affected or used for reproduction.\n- Step-by-step instructions to reproduce the issue\n- Impact of the issue, including how an attacker might exploit the issue\n- Preferred Languages\n\nWe prefer to receive reports in English. If necessary, we also understand French and German.\n"
  },
  {
    "path": "bindings/packager/nodejs/.cargo/config.toml",
    "content": "[target.aarch64-unknown-linux-gnu]\nlinker = \"aarch64-linux-gnu-gcc\"\n[target.aarch64-unknown-linux-musl]\nlinker = \"aarch64-linux-musl-gcc\"\nrustflags = [\"-C\", \"target-feature=-crt-static\"]\n[target.armv7-unknown-linux-gnueabihf]\nlinker = \"arm-linux-gnueabihf-gcc\""
  },
  {
    "path": "bindings/packager/nodejs/.npmignore",
    "content": "target\nCargo.lock\n.cargo\n.github\nnpm\n.eslintrc\n.prettierignore\nrustfmt.toml\nyarn.lock\n*.node\n.yarn\n__test__\nrenovate.json\n"
  },
  {
    "path": "bindings/packager/nodejs/.npmrc",
    "content": "enable-pre-post-scripts=true\n"
  },
  {
    "path": "bindings/packager/nodejs/CHANGELOG.md",
    "content": "# Changelog\n\n## \\[0.11.8]\n\n- [`6e6a10c`](https://www.github.com/crabnebula-dev/cargo-packager/commit/6e6a10cc1692973293966034dc4b798e3976d094) ([#321](https://www.github.com/crabnebula-dev/cargo-packager/pull/321)) Allow explicitly specifying the Package name for the .deb bundle.\n- [`8488d86`](https://www.github.com/crabnebula-dev/cargo-packager/commit/8488d868935166e873474743c346c2724205d73e) ([#377](https://www.github.com/crabnebula-dev/cargo-packager/pull/377)) Fixed a bug where \"binaries\" parameter in Cargo.toml would be ignored and all targets would be packaged.\n- [`b2b4916`](https://www.github.com/crabnebula-dev/cargo-packager/commit/b2b4916d1b062272fc7e34b5ed55b4fe8c8cd03a) ([#376](https://www.github.com/crabnebula-dev/cargo-packager/pull/376)) Fix bug that prevents reading macos signing certificates from environment variables.\n- [`c34de36`](https://www.github.com/crabnebula-dev/cargo-packager/commit/c34de365705db150eb101caa94adf42eff74f71a) ([#365](https://www.github.com/crabnebula-dev/cargo-packager/pull/365)) Change nsi template from using `association.ext` to `association.extensions`, to match struct field in `FileAssociation`.\n  This allows file associations to be generated in `.nsi` files, and therefore in the final NSIS installer.\n\n### Dependencies\n\n- Upgraded to `cargo-packager@0.11.8`\n\n## \\[0.11.7]\n\n- [`d49b606`](https://www.github.com/crabnebula-dev/cargo-packager/commit/d49b606ba8a612c833233ec8a6061481a2118639) ([#353](https://www.github.com/crabnebula-dev/cargo-packager/pull/353)) Allow using notarization credentials stored on the Keychain by providing the `APPLE_KEYCHAIN_PROFILE` environment variable. See `xcrun notarytool store-credentials` for more information.\n- [`b337564`](https://www.github.com/crabnebula-dev/cargo-packager/commit/b337564c0e5a9de966b4124890dddea1e353acb4) ([#362](https://www.github.com/crabnebula-dev/cargo-packager/pull/362)) Updated linuxdeploy's AppImage plugin to not require libfuse on the user's system anymore.\n\n### Dependencies\n\n- Upgraded to `cargo-packager@0.11.7`\n\n## \\[0.11.6]\n\n- [`b81b81f`](https://www.github.com/crabnebula-dev/cargo-packager/commit/b81b81fbd7fd185edfc7652f535d0cfacb786ac9) ([#354](https://www.github.com/crabnebula-dev/cargo-packager/pull/354)) Changed the download URL of a dependency of the AppImage bundler to Tauri's mirror to resolve 404 errors.\n- [`5205088`](https://www.github.com/crabnebula-dev/cargo-packager/commit/5205088cd78412fb6cbe5e48a715524fcc5a2ee7) ([#340](https://www.github.com/crabnebula-dev/cargo-packager/pull/340)) Enhance sign error message.\n\n### Dependencies\n\n- Upgraded to `cargo-packager@0.11.6`\n\n## \\[0.11.5]\n\n- [`17194a9`](https://www.github.com/crabnebula-dev/cargo-packager/commit/17194a92aabd59c9e075105072ff939f5d55a107) ([#313](https://www.github.com/crabnebula-dev/cargo-packager/pull/313)) Added `linux > generateDesktopEntry` config to allow disabling generating a .desktop file on Linux bundles (defaults to true).\n- [`17c52f0`](https://www.github.com/crabnebula-dev/cargo-packager/commit/17c52f057d78340983689af3c00b1f2aeff3c417) ([#289](https://www.github.com/crabnebula-dev/cargo-packager/pull/289)) Added support to embedding additional apps in the macOS app bundle.\n- [`17c52f0`](https://www.github.com/crabnebula-dev/cargo-packager/commit/17c52f057d78340983689af3c00b1f2aeff3c417) ([#289](https://www.github.com/crabnebula-dev/cargo-packager/pull/289)) Added support to adding an `embedded.provisionprofile` file to the macOS bundle.\n- [`e010574`](https://www.github.com/crabnebula-dev/cargo-packager/commit/e010574c2efa4a1aa6b8e475a62bec46f24f2bc5) ([#318](https://www.github.com/crabnebula-dev/cargo-packager/pull/318)) Add `background-app` config setting for macOS to set `LSUIElement` to `true`.\n\n### Dependencies\n\n- Upgraded to `cargo-packager@0.11.5`\n\n## \\[0.11.4]\n\n### Dependencies\n\n- Upgraded to `cargo-packager@0.11.4`\n\n## \\[0.11.3]\n\n### Dependencies\n\n- Upgraded to `cargo-packager@0.11.3`\n\n## \\[0.11.2]\n\n- [`fea80d5`](https://www.github.com/crabnebula-dev/cargo-packager/commit/fea80d5760882e6cdc21c8ed2f82d323e0598926) ([#264](https://www.github.com/crabnebula-dev/cargo-packager/pull/264)) Fix `pacman` package failing to install when source directory contained whitespace.\n\n### Dependencies\n\n- Upgraded to `cargo-packager@0.11.2`\n\n## \\[0.11.1]\n\n- [`4523722`](https://www.github.com/crabnebula-dev/cargo-packager/commit/4523722d0808faef4a91dbb227badd0354f4c71a) ([#283](https://www.github.com/crabnebula-dev/cargo-packager/pull/283)) Fixes resources paths on NSIS when cross compiling.\n\n### Dependencies\n\n- Upgraded to `cargo-packager@0.11.1`\n\n## \\[0.11.0]\n\n- [`41b05d0`](https://www.github.com/crabnebula-dev/cargo-packager/commit/41b05d08a635d593df4cf4eefbe921b92ace77b7) ([#277](https://www.github.com/crabnebula-dev/cargo-packager/pull/277)) Add `--target` flag to specify target triple to package.\n\n### Dependencies\n\n- Upgraded to `cargo-packager@0.11.0`\n\n## \\[0.10.3]\n\n### Dependencies\n\n- Upgraded to `cargo-packager@0.10.3`\n\n## \\[0.10.2]\n\n### Dependencies\n\n- Upgraded to `cargo-packager@0.10.2`\n- Upgraded to `cargo-packager-utils@0.1.1`\n\n## \\[0.10.1]\n\n- [`522f23b`](https://www.github.com/crabnebula-dev/cargo-packager/commit/522f23bd867b037eeec81c43295aafd38ebe60ec) ([#258](https://www.github.com/crabnebula-dev/cargo-packager/pull/258)) Update NSIS installer template URL.\n- [`bce99ae`](https://www.github.com/crabnebula-dev/cargo-packager/commit/bce99aecb4160291a026dcd4750055f9079099f8) ([#260](https://www.github.com/crabnebula-dev/cargo-packager/pull/260)) Fix NSIS uninstaller removing the uninstall directory even if it was not empty.\n\n### Dependencies\n\n- Upgraded to `cargo-packager@0.10.1`\n\n## \\[0.10.0]\n\n- [`c6207bb`](https://www.github.com/crabnebula-dev/cargo-packager/commit/c6207bba042a8a0184ddb7e12650a4cd8f415c23) ([#254](https://www.github.com/crabnebula-dev/cargo-packager/pull/254)) Allow Linux dependencies to be specified via a file path instead of just a direct String.\n  This enables the list of dependencies to by dynamically generated for both Debian `.deb` packages and pacman packages,\n  which can relieve the app developer from the burden of manually maintaining a fixed list of dependencies.\n- [`de4dcca`](https://www.github.com/crabnebula-dev/cargo-packager/commit/de4dccaca4ae758d3adde517cc415a002873e642) ([#256](https://www.github.com/crabnebula-dev/cargo-packager/pull/256)) Automatically add an Exec arg (field code) in the `.desktop` file.\n\n  This adds an `{exec_arg}` field to the default `main.desktop` template.\n  This field is populated with a sane default value, based on the\n  `deep_link_protocols` or `file_associations` in the `Config` struct.\n\n  This allows an installed Debian package to be invoked by other\n  applications with URLs or files as arguments, as expected.\n\n### Dependencies\n\n- Upgraded to `cargo-packager@0.10.0`\n\n## \\[0.9.1]\n\n- [`44a19ea`](https://www.github.com/crabnebula-dev/cargo-packager/commit/44a19eae1f5f26b1bd10ba84dd6eb3d856609a67) ([#246](https://www.github.com/crabnebula-dev/cargo-packager/pull/246)) On macOS, fix notarization skipping needed environment variables when macos specific config has been specified in the config file.\n\n### Dependencies\n\n- Upgraded to `cargo-packager@0.9.1`\n\n## \\[0.9.0]\n\n- [`ab53974`](https://www.github.com/crabnebula-dev/cargo-packager/commit/ab53974b683ce282202e1a550c551eed951e9ca7) ([#235](https://www.github.com/crabnebula-dev/cargo-packager/pull/235)) Added deep link support.\n\n### Dependencies\n\n- Upgraded to `cargo-packager@0.9.0`\n\n## \\[0.8.1]\n\n- [`1375380`](https://www.github.com/crabnebula-dev/cargo-packager/commit/1375380c7c9d2adf55ab18a2ce23917849967995)([#196](https://www.github.com/crabnebula-dev/cargo-packager/pull/196)) Always show shell commands output for `beforePackageCommand` and `beforeEachPackagingCommand` .\n\n### Dependencies\n\n- Upgraded to `cargo-packager@0.8.1`\n\n## \\[0.8.0]\n\n- [`2164d02`](https://www.github.com/crabnebula-dev/cargo-packager/commit/2164d022f5705e59a189007aec7c99cce98136d8)([#198](https://www.github.com/crabnebula-dev/cargo-packager/pull/198)) Allow packaging the macOS app bundle on Linux and Windows hosts (without codesign support).\n- [`3057a4a`](https://www.github.com/crabnebula-dev/cargo-packager/commit/3057a4a8440bc4dc897f3038ac821ed181644d43)([#197](https://www.github.com/crabnebula-dev/cargo-packager/pull/197)) Added `Config::binaries_dir` and `--binaries-dir` so you can specify the location of the binaries without modifying the output directory.\n- [`4c4d919`](https://www.github.com/crabnebula-dev/cargo-packager/commit/4c4d9194fb0bd2a814f46336747e643b1e208b52)([#195](https://www.github.com/crabnebula-dev/cargo-packager/pull/195)) Error out if we cannot find a configuration file.\n- [`b04332c`](https://www.github.com/crabnebula-dev/cargo-packager/commit/b04332c8fc61427dc002a40d9d46bc5f930025c2)([#194](https://www.github.com/crabnebula-dev/cargo-packager/pull/194)) Fixes a crash when packaging `.app` if an empty file is included in the bundle.\n- [`3057a4a`](https://www.github.com/crabnebula-dev/cargo-packager/commit/3057a4a8440bc4dc897f3038ac821ed181644d43)([#197](https://www.github.com/crabnebula-dev/cargo-packager/pull/197)) Added `--out-dir/-o` flags and removed the positional argument to specify where to ouput packages, use the newly added flags instead.\n\n### Dependencies\n\n- Upgraded to `cargo-packager@0.8.0`\n\n## \\[0.7.0]\n\n- [`cd8898a`](https://www.github.com/crabnebula-dev/cargo-packager/commit/cd8898a93b66a4aae050fa1006089c3c3b5646f9)([#187](https://www.github.com/crabnebula-dev/cargo-packager/pull/187)) Added codesign certificate and notarization credentials configuration options under the `macos` config (for programatic usage, taking precedence over environment variables).\n\n### Dependencies\n\n- Upgraded to `cargo-packager@0.7.0`\n\n## \\[0.6.1]\n\n### Dependencies\n\n- Upgraded to `cargo-packager@0.6.1`\n\n## \\[0.6.0]\n\n- [`57b379a`](https://www.github.com/crabnebula-dev/cargo-packager/commit/57b379ad1d9029e767848fda99d4eb6415afe51a)([#148](https://www.github.com/crabnebula-dev/cargo-packager/pull/148)) Added config option to control excluded libs when packaging AppImage\n- [`947e032`](https://www.github.com/crabnebula-dev/cargo-packager/commit/947e0328c89d6f043c3ef1b1db5d2252d4f072a5) Fix CLI failing with `Failed to read cargo metadata: cargo metadata` for non-rust projects.\n- Bumpt to `0.6.0` version directly to match the Rust crate version.\n\n### Dependencies\n\n- Upgraded to `cargo-packager@0.6.0`\n\n## \\[0.2.0]\n\n- [`9bdb953`](https://www.github.com/crabnebula-dev/cargo-packager/commit/9bdb953f1b48c8d69d86e9e42295cd36453c1648)([#137](https://www.github.com/crabnebula-dev/cargo-packager/pull/137)) Add Arch Linux package manager, `pacman` support for cargo packager.\n- [`a29943e`](https://www.github.com/crabnebula-dev/cargo-packager/commit/a29943e8c95d70e8b77c23021ce52f6ee13314c8)([#140](https://www.github.com/crabnebula-dev/cargo-packager/pull/140)) Fix codesigning failing on macOS under certain circumstances when the order in which files were signed was not\n  deterministic and nesting required signing files nested more deeply first.\n\n### Dependencies\n\n- Upgraded to `cargo-packager@0.5.0`\n- Upgraded to `cargo-packager-utils@0.1.0`\n\n## \\[0.1.5]\n\n- [`f08e4b8`](https://www.github.com/crabnebula-dev/cargo-packager/commit/f08e4b8972b072617fdb78f11e222427e49ebe8e) Fix the signing and notarization process for MacOS bundles\n- [`bfa3b00`](https://www.github.com/crabnebula-dev/cargo-packager/commit/bfa3b00cf1087b2ee1e93d9c57b6b577f6294891)([#126](https://www.github.com/crabnebula-dev/cargo-packager/pull/126)) Add `priority` and `section` options in Debian config\n\n### Dependencies\n\n- Upgraded to `cargo-packager@0.4.5`\n\n## \\[0.1.4]\n\n- [`3b3ce76`](https://www.github.com/crabnebula-dev/cargo-packager/commit/3b3ce76da0581cf8d553d6edeb0df24f896c62a6)([#128](https://www.github.com/crabnebula-dev/cargo-packager/pull/128)) Fix file download not working on macOS and Windows (arm).\n\n### Dependencies\n\n- Upgraded to `cargo-packager@0.4.4`\n\n## \\[0.1.3]\n\n- [`2a50c8e`](https://www.github.com/crabnebula-dev/cargo-packager/commit/2a50c8ea734193036db0ab461f9005ea904cf4b7)([#124](https://www.github.com/crabnebula-dev/cargo-packager/pull/124)) Fix packaing of external binaries.\n\n### Dependencies\n\n- Upgraded to `cargo-packager@0.4.3`\n\n## \\[0.1.2]\n\n- [`bd7e6fc`](https://www.github.com/crabnebula-dev/cargo-packager/commit/bd7e6fc102a74dc4da39848f44d04968b498b3cf)([#123](https://www.github.com/crabnebula-dev/cargo-packager/pull/123)) Fixes published package not including the build folder.\n\n### Dependencies\n\n- Upgraded to `cargo-packager@0.4.2`\n\n## \\[0.1.1]\n\n- [`7e05d24`](https://www.github.com/crabnebula-dev/cargo-packager/commit/7e05d24a697230b1f53ee5ee2f7d217047089d97)([#109](https://www.github.com/crabnebula-dev/cargo-packager/pull/109)) Check if required files/tools for packaging are outdated or mis-hashed and redownload them.\n- [`ea6c31b`](https://www.github.com/crabnebula-dev/cargo-packager/commit/ea6c31b1a3b56bb5408a78f1b2d6b2a2d9ce1161)([#114](https://www.github.com/crabnebula-dev/cargo-packager/pull/114)) Fix NSIS uninstaller leaving resources behind and failing to remove the installation directory.\n\n### Dependencies\n\n- Upgraded to `cargo-packager@0.4.1`\n\n## \\[0.1.0]\n\n- [`c4fa8fd`](https://www.github.com/crabnebula-dev/cargo-packager/commit/c4fa8fd6334b7fd0c32710ea2df0b54aa6bde713) Initial release.\n\n### Dependencies\n\n- Upgraded to `cargo-packager@0.4.0`\n"
  },
  {
    "path": "bindings/packager/nodejs/Cargo.toml",
    "content": "[package]\nname = \"crabnebula_packager\"\nversion = \"0.0.0\"\npublish = false\nedition = { workspace = true }\nlicense = { workspace = true }\nrepository = { workspace = true }\n\n[lib]\ncrate-type = [\"cdylib\"]\n\n[dependencies]\n# Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix\nnapi = { workspace = true, features = [\"napi4\"] }\nnapi-derive = { workspace = true }\ncargo-packager = { path = \"../../../crates/packager/\", default-features = false, features = [\"cli\"] }\ntracing = { workspace = true }\nserde_json = { workspace = true }\n\n[build-dependencies]\nnapi-build = { workspace = true }\n\n[features]\ndefault = [\"cargo-packager/rustls-tls\"]\nnative-tls = [\"cargo-packager/native-tls\"]\nnative-tls-vendored = [\"cargo-packager/native-tls-vendored\"]\n"
  },
  {
    "path": "bindings/packager/nodejs/README.md",
    "content": "# @crabnebula/packager\n\nExecutable packager, bundler and updater. A cli tool and library to generate installers or app bundles for your executables.\nIt also comes with useful addons:\n\n- an [updater](https://www.npmjs.com/package/@crabnebula/updater)\n- a [resource resolver](https://www.npmjs.com/package/@crabnebula/packager-resource-resolver)\n\n#### Supported packages:\n\n- macOS\n  - DMG (.dmg)\n  - Bundle (.app)\n- Linux\n  - Debian package (.deb)\n  - AppImage (.AppImage)\n  - Pacman (.tar.gz and PKGBUILD)\n- Windows\n  - NSIS (.exe)\n  - MSI using WiX Toolset (.msi)\n\n### CLI\n\nThe packager is distributed on NPM as a CLI, you can install it:\n\n```sh\n# pnpm\npnpm add -D @crabnebula/packager\n# yarn\nyarn add -D @crabnebula/packager\n# npm\nnpm i -D @crabnebula/packager\n```\n\nYou then need to configure your app so the CLI can recognize it.\nConfiguration can be done in `Packager.toml` or `packager.json` in your project or `packager` key in `packager.json`\nOnce, you are done configuring your app, run:\n\n```sh\n# pnpm\npnpm packager\n# yarn\nyarn packager\n# npm\nnpx packager\n```\n\n### Configuration\n\nBy default, the packager reads its configuration from `Packager.toml` or `packager.json` if it exists, and from `packager.json` key in `packager.json`,\nYou can also specify a custom configuration using the `-c/--config` cli argument.\n\nFor a full list of configuration options, see https://docs.rs/cargo-packager/latest/cargo_packager/config/struct.Config.html.\n\nYou could also use the [schema](./schema.json) file from GitHub to validate your configuration or have auto completions in your IDE.\n\n### Building your application before packaging\n\nBy default, the packager doesn't build your application, so if your app requires a compilation step, the packager has an option to specify a shell command to be executed before packaing your app, `beforePackagingCommand`.\n\n### Library\n\nThe packager is also a library that you can import and integrate into your tooling.\n\n## Licenses\n\nMIT or MIT/Apache 2.0 where applicable.\n"
  },
  {
    "path": "bindings/packager/nodejs/__test__/index.spec.mjs",
    "content": "import test from \"ava\";\nimport process from \"process\";\nimport { execSync } from \"child_process\";\n\nimport { packageApp } from \"../build/index.js\";\n\ntest(\"log error\", async (t) => {\n  process.env.CI = true;\n  process.chdir(\"../../../examples/electron\");\n  execSync(\"yarn install\");\n  t.is(\n    await packageApp(\n      {\n        formats: process.env.PACKAGER_FORMATS\n          ? process.env.PACKAGER_FORMATS.split(\",\")\n          : null,\n      },\n      { verbosity: 2 },\n    ),\n    undefined,\n  );\n});\n"
  },
  {
    "path": "bindings/packager/nodejs/build.rs",
    "content": "extern crate napi_build;\n\nfn main() {\n    napi_build::setup();\n}\n"
  },
  {
    "path": "bindings/packager/nodejs/generate-config-type.js",
    "content": "const { compileFromFile } = require(\"json-schema-to-typescript\");\nconst fs = require(\"fs\");\nconst path = require(\"path\");\n\n// compile from file\ncompileFromFile(\n  path.join(__dirname, \"../../../crates/packager/schema.json\"),\n).then((ts) => {\n  for (const dir of [\"src-ts\", \"build\"]) {\n    try {\n      fs.mkdirSync(dir);\n    } catch (_) {}\n    fs.writeFileSync(path.join(dir, \"config.d.ts\"), ts);\n  }\n});\n"
  },
  {
    "path": "bindings/packager/nodejs/index.d.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n\n/* auto-generated by NAPI-RS */\n\nexport function cli(args: Array<string>, binName?: string | undefined | null): void\nexport function packageApp(config: string): void\nexport function packageAndSignApp(config: string, signingConfig: string): void\nexport function initTracingSubscriber(verbosity: number): void\nexport function logError(error: string): void\n"
  },
  {
    "path": "bindings/packager/nodejs/index.js",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/* prettier-ignore */\n\n/* auto-generated by NAPI-RS */\n\nconst { existsSync, readFileSync } = require('fs')\nconst { join } = require('path')\n\nconst { platform, arch } = process\n\nlet nativeBinding = null\nlet localFileExisted = false\nlet loadError = null\n\nfunction isMusl() {\n  // For Node 10\n  if (!process.report || typeof process.report.getReport !== 'function') {\n    try {\n      const lddPath = require('child_process').execSync('which ldd').toString().trim()\n      return readFileSync(lddPath, 'utf8').includes('musl')\n    } catch (e) {\n      return true\n    }\n  } else {\n    const { glibcVersionRuntime } = process.report.getReport().header\n    return !glibcVersionRuntime\n  }\n}\n\nswitch (platform) {\n  case 'android':\n    switch (arch) {\n      case 'arm64':\n        localFileExisted = existsSync(join(__dirname, 'packager.android-arm64.node'))\n        try {\n          if (localFileExisted) {\n            nativeBinding = require('./packager.android-arm64.node')\n          } else {\n            nativeBinding = require('@crabnebula/packager-android-arm64')\n          }\n        } catch (e) {\n          loadError = e\n        }\n        break\n      case 'arm':\n        localFileExisted = existsSync(join(__dirname, 'packager.android-arm-eabi.node'))\n        try {\n          if (localFileExisted) {\n            nativeBinding = require('./packager.android-arm-eabi.node')\n          } else {\n            nativeBinding = require('@crabnebula/packager-android-arm-eabi')\n          }\n        } catch (e) {\n          loadError = e\n        }\n        break\n      default:\n        throw new Error(`Unsupported architecture on Android ${arch}`)\n    }\n    break\n  case 'win32':\n    switch (arch) {\n      case 'x64':\n        localFileExisted = existsSync(\n          join(__dirname, 'packager.win32-x64-msvc.node')\n        )\n        try {\n          if (localFileExisted) {\n            nativeBinding = require('./packager.win32-x64-msvc.node')\n          } else {\n            nativeBinding = require('@crabnebula/packager-win32-x64-msvc')\n          }\n        } catch (e) {\n          loadError = e\n        }\n        break\n      case 'ia32':\n        localFileExisted = existsSync(\n          join(__dirname, 'packager.win32-ia32-msvc.node')\n        )\n        try {\n          if (localFileExisted) {\n            nativeBinding = require('./packager.win32-ia32-msvc.node')\n          } else {\n            nativeBinding = require('@crabnebula/packager-win32-ia32-msvc')\n          }\n        } catch (e) {\n          loadError = e\n        }\n        break\n      case 'arm64':\n        localFileExisted = existsSync(\n          join(__dirname, 'packager.win32-arm64-msvc.node')\n        )\n        try {\n          if (localFileExisted) {\n            nativeBinding = require('./packager.win32-arm64-msvc.node')\n          } else {\n            nativeBinding = require('@crabnebula/packager-win32-arm64-msvc')\n          }\n        } catch (e) {\n          loadError = e\n        }\n        break\n      default:\n        throw new Error(`Unsupported architecture on Windows: ${arch}`)\n    }\n    break\n  case 'darwin':\n    localFileExisted = existsSync(join(__dirname, 'packager.darwin-universal.node'))\n    try {\n      if (localFileExisted) {\n        nativeBinding = require('./packager.darwin-universal.node')\n      } else {\n        nativeBinding = require('@crabnebula/packager-darwin-universal')\n      }\n      break\n    } catch {}\n    switch (arch) {\n      case 'x64':\n        localFileExisted = existsSync(join(__dirname, 'packager.darwin-x64.node'))\n        try {\n          if (localFileExisted) {\n            nativeBinding = require('./packager.darwin-x64.node')\n          } else {\n            nativeBinding = require('@crabnebula/packager-darwin-x64')\n          }\n        } catch (e) {\n          loadError = e\n        }\n        break\n      case 'arm64':\n        localFileExisted = existsSync(\n          join(__dirname, 'packager.darwin-arm64.node')\n        )\n        try {\n          if (localFileExisted) {\n            nativeBinding = require('./packager.darwin-arm64.node')\n          } else {\n            nativeBinding = require('@crabnebula/packager-darwin-arm64')\n          }\n        } catch (e) {\n          loadError = e\n        }\n        break\n      default:\n        throw new Error(`Unsupported architecture on macOS: ${arch}`)\n    }\n    break\n  case 'freebsd':\n    if (arch !== 'x64') {\n      throw new Error(`Unsupported architecture on FreeBSD: ${arch}`)\n    }\n    localFileExisted = existsSync(join(__dirname, 'packager.freebsd-x64.node'))\n    try {\n      if (localFileExisted) {\n        nativeBinding = require('./packager.freebsd-x64.node')\n      } else {\n        nativeBinding = require('@crabnebula/packager-freebsd-x64')\n      }\n    } catch (e) {\n      loadError = e\n    }\n    break\n  case 'linux':\n    switch (arch) {\n      case 'x64':\n        if (isMusl()) {\n          localFileExisted = existsSync(\n            join(__dirname, 'packager.linux-x64-musl.node')\n          )\n          try {\n            if (localFileExisted) {\n              nativeBinding = require('./packager.linux-x64-musl.node')\n            } else {\n              nativeBinding = require('@crabnebula/packager-linux-x64-musl')\n            }\n          } catch (e) {\n            loadError = e\n          }\n        } else {\n          localFileExisted = existsSync(\n            join(__dirname, 'packager.linux-x64-gnu.node')\n          )\n          try {\n            if (localFileExisted) {\n              nativeBinding = require('./packager.linux-x64-gnu.node')\n            } else {\n              nativeBinding = require('@crabnebula/packager-linux-x64-gnu')\n            }\n          } catch (e) {\n            loadError = e\n          }\n        }\n        break\n      case 'arm64':\n        if (isMusl()) {\n          localFileExisted = existsSync(\n            join(__dirname, 'packager.linux-arm64-musl.node')\n          )\n          try {\n            if (localFileExisted) {\n              nativeBinding = require('./packager.linux-arm64-musl.node')\n            } else {\n              nativeBinding = require('@crabnebula/packager-linux-arm64-musl')\n            }\n          } catch (e) {\n            loadError = e\n          }\n        } else {\n          localFileExisted = existsSync(\n            join(__dirname, 'packager.linux-arm64-gnu.node')\n          )\n          try {\n            if (localFileExisted) {\n              nativeBinding = require('./packager.linux-arm64-gnu.node')\n            } else {\n              nativeBinding = require('@crabnebula/packager-linux-arm64-gnu')\n            }\n          } catch (e) {\n            loadError = e\n          }\n        }\n        break\n      case 'arm':\n        localFileExisted = existsSync(\n          join(__dirname, 'packager.linux-arm-gnueabihf.node')\n        )\n        try {\n          if (localFileExisted) {\n            nativeBinding = require('./packager.linux-arm-gnueabihf.node')\n          } else {\n            nativeBinding = require('@crabnebula/packager-linux-arm-gnueabihf')\n          }\n        } catch (e) {\n          loadError = e\n        }\n        break\n      case 'riscv64':\n        if (isMusl()) {\n          localFileExisted = existsSync(\n            join(__dirname, 'packager.linux-riscv64-musl.node')\n          )\n          try {\n            if (localFileExisted) {\n              nativeBinding = require('./packager.linux-riscv64-musl.node')\n            } else {\n              nativeBinding = require('@crabnebula/packager-linux-riscv64-musl')\n            }\n          } catch (e) {\n            loadError = e\n          }\n        } else {\n          localFileExisted = existsSync(\n            join(__dirname, 'packager.linux-riscv64-gnu.node')\n          )\n          try {\n            if (localFileExisted) {\n              nativeBinding = require('./packager.linux-riscv64-gnu.node')\n            } else {\n              nativeBinding = require('@crabnebula/packager-linux-riscv64-gnu')\n            }\n          } catch (e) {\n            loadError = e\n          }\n        }\n        break\n      case 's390x':\n        localFileExisted = existsSync(\n          join(__dirname, 'packager.linux-s390x-gnu.node')\n        )\n        try {\n          if (localFileExisted) {\n            nativeBinding = require('./packager.linux-s390x-gnu.node')\n          } else {\n            nativeBinding = require('@crabnebula/packager-linux-s390x-gnu')\n          }\n        } catch (e) {\n          loadError = e\n        }\n        break\n      default:\n        throw new Error(`Unsupported architecture on Linux: ${arch}`)\n    }\n    break\n  default:\n    throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`)\n}\n\nif (!nativeBinding) {\n  if (loadError) {\n    throw loadError\n  }\n  throw new Error(`Failed to load native binding`)\n}\n\nconst { cli, packageApp, packageAndSignApp, initTracingSubscriber, logError } = nativeBinding\n\nmodule.exports.cli = cli\nmodule.exports.packageApp = packageApp\nmodule.exports.packageAndSignApp = packageAndSignApp\nmodule.exports.initTracingSubscriber = initTracingSubscriber\nmodule.exports.logError = logError\n"
  },
  {
    "path": "bindings/packager/nodejs/npm/darwin-arm64/README.md",
    "content": "# `@crabnebula/packager-darwin-arm64`\n\nThis is the **aarch64-apple-darwin** binary for `@crabnebula/packager`\n"
  },
  {
    "path": "bindings/packager/nodejs/npm/darwin-arm64/package.json",
    "content": "{\n  \"name\": \"@crabnebula/packager-darwin-arm64\",\n  \"version\": \"0.0.0\",\n  \"os\": [\n    \"darwin\"\n  ],\n  \"cpu\": [\n    \"arm64\"\n  ],\n  \"main\": \"packager.darwin-arm64.node\",\n  \"files\": [\n    \"packager.darwin-arm64.node\"\n  ],\n  \"license\": \"MIT\",\n  \"engines\": {\n    \"node\": \">= 10\"\n  }\n}\n"
  },
  {
    "path": "bindings/packager/nodejs/npm/darwin-x64/README.md",
    "content": "# `@crabnebula/packager-darwin-x64`\n\nThis is the **x86_64-apple-darwin** binary for `@crabnebula/packager`\n"
  },
  {
    "path": "bindings/packager/nodejs/npm/darwin-x64/package.json",
    "content": "{\n  \"name\": \"@crabnebula/packager-darwin-x64\",\n  \"version\": \"0.0.0\",\n  \"os\": [\n    \"darwin\"\n  ],\n  \"cpu\": [\n    \"x64\"\n  ],\n  \"main\": \"packager.darwin-x64.node\",\n  \"files\": [\n    \"packager.darwin-x64.node\"\n  ],\n  \"license\": \"MIT\",\n  \"engines\": {\n    \"node\": \">= 10\"\n  }\n}\n"
  },
  {
    "path": "bindings/packager/nodejs/npm/linux-arm-gnueabihf/README.md",
    "content": "# `@crabnebula/packager-linux-arm-gnueabihf`\n\nThis is the **armv7-unknown-linux-gnueabihf** binary for `@crabnebula/packager`\n"
  },
  {
    "path": "bindings/packager/nodejs/npm/linux-arm-gnueabihf/package.json",
    "content": "{\n  \"name\": \"@crabnebula/packager-linux-arm-gnueabihf\",\n  \"version\": \"0.0.0\",\n  \"os\": [\n    \"linux\"\n  ],\n  \"cpu\": [\n    \"arm\"\n  ],\n  \"main\": \"packager.linux-arm-gnueabihf.node\",\n  \"files\": [\n    \"packager.linux-arm-gnueabihf.node\"\n  ],\n  \"license\": \"MIT\",\n  \"engines\": {\n    \"node\": \">= 10\"\n  }\n}\n"
  },
  {
    "path": "bindings/packager/nodejs/npm/linux-arm64-gnu/README.md",
    "content": "# `@crabnebula/packager-linux-arm64-gnu`\n\nThis is the **aarch64-unknown-linux-gnu** binary for `@crabnebula/packager`\n"
  },
  {
    "path": "bindings/packager/nodejs/npm/linux-arm64-gnu/package.json",
    "content": "{\n  \"name\": \"@crabnebula/packager-linux-arm64-gnu\",\n  \"version\": \"0.0.0\",\n  \"os\": [\n    \"linux\"\n  ],\n  \"cpu\": [\n    \"arm64\"\n  ],\n  \"main\": \"packager.linux-arm64-gnu.node\",\n  \"files\": [\n    \"packager.linux-arm64-gnu.node\"\n  ],\n  \"license\": \"MIT\",\n  \"engines\": {\n    \"node\": \">= 10\"\n  },\n  \"libc\": [\n    \"glibc\"\n  ]\n}\n"
  },
  {
    "path": "bindings/packager/nodejs/npm/linux-arm64-musl/README.md",
    "content": "# `@crabnebula/packager-linux-arm64-musl`\n\nThis is the **aarch64-unknown-linux-musl** binary for `@crabnebula/packager`\n"
  },
  {
    "path": "bindings/packager/nodejs/npm/linux-arm64-musl/package.json",
    "content": "{\n  \"name\": \"@crabnebula/packager-linux-arm64-musl\",\n  \"version\": \"0.0.0\",\n  \"os\": [\n    \"linux\"\n  ],\n  \"cpu\": [\n    \"arm64\"\n  ],\n  \"main\": \"packager.linux-arm64-musl.node\",\n  \"files\": [\n    \"packager.linux-arm64-musl.node\"\n  ],\n  \"license\": \"MIT\",\n  \"engines\": {\n    \"node\": \">= 10\"\n  },\n  \"libc\": [\n    \"musl\"\n  ]\n}\n"
  },
  {
    "path": "bindings/packager/nodejs/npm/linux-x64-gnu/README.md",
    "content": "# `@crabnebula/packager-linux-x64-gnu`\n\nThis is the **x86_64-unknown-linux-gnu** binary for `@crabnebula/packager`\n"
  },
  {
    "path": "bindings/packager/nodejs/npm/linux-x64-gnu/package.json",
    "content": "{\n  \"name\": \"@crabnebula/packager-linux-x64-gnu\",\n  \"version\": \"0.0.0\",\n  \"os\": [\n    \"linux\"\n  ],\n  \"cpu\": [\n    \"x64\"\n  ],\n  \"main\": \"packager.linux-x64-gnu.node\",\n  \"files\": [\n    \"packager.linux-x64-gnu.node\"\n  ],\n  \"license\": \"MIT\",\n  \"engines\": {\n    \"node\": \">= 10\"\n  },\n  \"libc\": [\n    \"glibc\"\n  ]\n}\n"
  },
  {
    "path": "bindings/packager/nodejs/npm/linux-x64-musl/README.md",
    "content": "# `@crabnebula/packager-linux-x64-musl`\n\nThis is the **x86_64-unknown-linux-musl** binary for `@crabnebula/packager`\n"
  },
  {
    "path": "bindings/packager/nodejs/npm/linux-x64-musl/package.json",
    "content": "{\n  \"name\": \"@crabnebula/packager-linux-x64-musl\",\n  \"version\": \"0.0.0\",\n  \"os\": [\n    \"linux\"\n  ],\n  \"cpu\": [\n    \"x64\"\n  ],\n  \"main\": \"packager.linux-x64-musl.node\",\n  \"files\": [\n    \"packager.linux-x64-musl.node\"\n  ],\n  \"license\": \"MIT\",\n  \"engines\": {\n    \"node\": \">= 10\"\n  },\n  \"libc\": [\n    \"musl\"\n  ]\n}\n"
  },
  {
    "path": "bindings/packager/nodejs/npm/win32-arm64-msvc/README.md",
    "content": "# `@crabnebula/packager-win32-arm64-msvc`\n\nThis is the **aarch64-pc-windows-msvc** binary for `@crabnebula/packager`\n"
  },
  {
    "path": "bindings/packager/nodejs/npm/win32-arm64-msvc/package.json",
    "content": "{\n  \"name\": \"@crabnebula/packager-win32-arm64-msvc\",\n  \"version\": \"0.0.0\",\n  \"os\": [\n    \"win32\"\n  ],\n  \"cpu\": [\n    \"arm64\"\n  ],\n  \"main\": \"packager.win32-arm64-msvc.node\",\n  \"files\": [\n    \"packager.win32-arm64-msvc.node\"\n  ],\n  \"license\": \"MIT\",\n  \"engines\": {\n    \"node\": \">= 10\"\n  }\n}\n"
  },
  {
    "path": "bindings/packager/nodejs/npm/win32-ia32-msvc/README.md",
    "content": "# `@crabnebula/packager-win32-ia32-msvc`\n\nThis is the **i686-pc-windows-msvc** binary for `@crabnebula/packager`\n"
  },
  {
    "path": "bindings/packager/nodejs/npm/win32-ia32-msvc/package.json",
    "content": "{\n  \"name\": \"@crabnebula/packager-win32-ia32-msvc\",\n  \"version\": \"0.0.0\",\n  \"os\": [\n    \"win32\"\n  ],\n  \"cpu\": [\n    \"ia32\"\n  ],\n  \"main\": \"packager.win32-ia32-msvc.node\",\n  \"files\": [\n    \"packager.win32-ia32-msvc.node\"\n  ],\n  \"license\": \"MIT\",\n  \"engines\": {\n    \"node\": \">= 10\"\n  }\n}\n"
  },
  {
    "path": "bindings/packager/nodejs/npm/win32-x64-msvc/README.md",
    "content": "# `@crabnebula/packager-win32-x64-msvc`\n\nThis is the **x86_64-pc-windows-msvc** binary for `@crabnebula/packager`\n"
  },
  {
    "path": "bindings/packager/nodejs/npm/win32-x64-msvc/package.json",
    "content": "{\n  \"name\": \"@crabnebula/packager-win32-x64-msvc\",\n  \"version\": \"0.0.0\",\n  \"os\": [\n    \"win32\"\n  ],\n  \"cpu\": [\n    \"x64\"\n  ],\n  \"main\": \"packager.win32-x64-msvc.node\",\n  \"files\": [\n    \"packager.win32-x64-msvc.node\"\n  ],\n  \"license\": \"MIT\",\n  \"engines\": {\n    \"node\": \">= 10\"\n  }\n}\n"
  },
  {
    "path": "bindings/packager/nodejs/package.json",
    "content": "{\n  \"name\": \"@crabnebula/packager\",\n  \"version\": \"0.11.8\",\n  \"main\": \"build/index.js\",\n  \"module\": \"build/index.js\",\n  \"types\": \"build/index.d.ts\",\n  \"author\": {\n    \"name\": \"CrabNebula Ltd.\"\n  },\n  \"description\": \"Executable packager and bundler distributed as a CLI and library\",\n  \"bin\": {\n    \"packager\": \"./packager.js\"\n  },\n  \"napi\": {\n    \"name\": \"packager\",\n    \"triples\": {\n      \"additional\": [\n        \"aarch64-apple-darwin\",\n        \"aarch64-unknown-linux-gnu\",\n        \"aarch64-unknown-linux-musl\",\n        \"aarch64-pc-windows-msvc\",\n        \"armv7-unknown-linux-gnueabihf\",\n        \"x86_64-unknown-linux-musl\",\n        \"i686-pc-windows-msvc\"\n      ]\n    }\n  },\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"artifacts\": \"napi artifacts\",\n    \"build\": \"napi build --platform --profile release-size-optimized\",\n    \"build:debug\": \"napi build --platform && pnpm run postbuild\",\n    \"postbuild\": \"rm -rf ./build && node generate-config-type.js && tsc\",\n    \"prepublishOnly\": \"napi prepublish -t npm --gh-release-id $RELEASE_ID\",\n    \"test\": \"ava --no-worker-threads\",\n    \"universal\": \"napi universal\",\n    \"version\": \"napi version\"\n  },\n  \"dependencies\": {\n    \"@electron/get\": \"^3.0.0\",\n    \"deepmerge\": \"^4.3.1\",\n    \"extract-zip\": \"^2.0.1\",\n    \"fs-extra\": \"^11.1.1\",\n    \"galactus\": \"^1.0.0\"\n  },\n  \"devDependencies\": {\n    \"@napi-rs/cli\": \"^2.18.1\",\n    \"@types/fs-extra\": \"^11.0.3\",\n    \"@types/node\": \"^20.8.10\",\n    \"ava\": \"^6.2.0\",\n    \"json-schema-to-typescript\": \"^15.0.0\",\n    \"typescript\": \"^5.4.5\"\n  },\n  \"ava\": {\n    \"timeout\": \"3m\"\n  },\n  \"engines\": {\n    \"node\": \">= 10\"\n  }\n}\n"
  },
  {
    "path": "bindings/packager/nodejs/packager.js",
    "content": "#!/usr/bin/env node\n\nconst cli = require(\"./build\");\nconst path = require(\"path\");\n\nconst [bin, script, ...args] = process.argv;\nconst binStem = path.parse(bin).name.toLowerCase();\n\n// We want to make a helpful binary name for the underlying CLI helper, if we\n// can successfully detect what command likely started the execution.\nlet binName;\n\n// deno run -A --unstable --node-modules-dir npm:@crabnebula/packager\nif (bin === \"@crabnebula/packager\") {\n  binName = \"@crabnebula/packager\";\n}\n// Even if started by a package manager, the binary will be NodeJS.\n// Some distribution still use \"nodejs\" as the binary name.\nelse if (binStem.match(/(nodejs|node|bun)\\-?([0-9]*)*$/g)) {\n  const managerStem = process.env.npm_execpath\n    ? path.parse(process.env.npm_execpath).name.toLowerCase()\n    : null;\n  if (managerStem) {\n    let manager;\n    switch (managerStem) {\n      // Only supported package manager that has a different filename is npm.\n      case \"npm-cli\":\n        manager = \"npm\";\n        break;\n\n      // Yarn, pnpm, and bun have the same stem name as their bin.\n      // We assume all unknown package managers do as well.\n      default:\n        manager = managerStem;\n        break;\n    }\n\n    binName = `${manager} run ${process.env.npm_lifecycle_event}`;\n  } else {\n    // Assume running NodeJS if we didn't detect a manager from the env.\n    // We normalize the path to prevent the script's absolute path being used.\n    const scriptNormal = path.normalize(path.relative(process.cwd(), script));\n    binName = `${binStem} ${scriptNormal}`;\n  }\n} else {\n  // We don't know what started it, assume it's already stripped.\n  args.unshift(bin);\n}\n\ncli.cli(args, binName).catch((err) => {\n  cli.logError(err.message);\n  process.exit(1);\n});\n"
  },
  {
    "path": "bindings/packager/nodejs/schema.json",
    "content": "{\n  \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n  \"title\": \"Config\",\n  \"description\": \"The packaging config.\",\n  \"type\": \"object\",\n  \"properties\": {\n    \"$schema\": {\n      \"description\": \"The JSON schema for the config.\\n\\nSetting this field has no effect, this just exists so we can parse the JSON correctly when it has `$schema` field set.\",\n      \"type\": [\n        \"string\",\n        \"null\"\n      ]\n    },\n    \"name\": {\n      \"description\": \"The app name, this is just an identifier that could be used to filter which app to package using `--packages` cli arg when there is multiple apps in the workspace or in the same config.\\n\\nThis field resembles, the `name` field in `Cargo.toml` or `package.json`\\n\\nIf `unset`, the CLI will try to auto-detect it from `Cargo.toml` or `package.json` otherwise, it will keep it unset.\",\n      \"type\": [\n        \"string\",\n        \"null\"\n      ]\n    },\n    \"enabled\": {\n      \"description\": \"Whether this config is enabled or not. Defaults to `true`.\",\n      \"default\": true,\n      \"type\": \"boolean\"\n    },\n    \"productName\": {\n      \"description\": \"The package's product name, for example \\\"My Awesome App\\\".\",\n      \"default\": \"\",\n      \"type\": \"string\"\n    },\n    \"version\": {\n      \"description\": \"The package's version.\",\n      \"default\": \"\",\n      \"type\": \"string\"\n    },\n    \"binaries\": {\n      \"description\": \"The binaries to package.\",\n      \"default\": [],\n      \"type\": \"array\",\n      \"items\": {\n        \"$ref\": \"#/definitions/Binary\"\n      }\n    },\n    \"identifier\": {\n      \"description\": \"The application identifier in reverse domain name notation (e.g. `com.packager.example`). This string must be unique across applications since it is used in some system configurations. This string must contain only alphanumeric characters (A-Z, a-z, and 0-9), hyphens (-), and periods (.).\",\n      \"type\": [\n        \"string\",\n        \"null\"\n      ],\n      \"pattern\": \"^[a-zA-Z0-9-\\\\.]*$\"\n    },\n    \"beforePackagingCommand\": {\n      \"description\": \"The command to run before starting to package an application.\\n\\nThis runs only once.\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/definitions/HookCommand\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ]\n    },\n    \"beforeEachPackageCommand\": {\n      \"description\": \"The command to run before packaging each format for an application.\\n\\nThis will run multiple times depending on the formats specifed.\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/definitions/HookCommand\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ]\n    },\n    \"logLevel\": {\n      \"description\": \"The logging level.\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/definitions/LogLevel\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ]\n    },\n    \"formats\": {\n      \"description\": \"The packaging formats to create, if not present, [`PackageFormat::platform_default`] is used.\",\n      \"type\": [\n        \"array\",\n        \"null\"\n      ],\n      \"items\": {\n        \"$ref\": \"#/definitions/PackageFormat\"\n      }\n    },\n    \"outDir\": {\n      \"description\": \"The directory where the generated packages will be placed.\\n\\nIf [`Config::binaries_dir`] is not set, this is also where the [`Config::binaries`] exist.\",\n      \"default\": \"\",\n      \"type\": \"string\"\n    },\n    \"binariesDir\": {\n      \"description\": \"The directory where the [`Config::binaries`] exist.\\n\\nDefaults to [`Config::out_dir`].\",\n      \"default\": null,\n      \"type\": [\n        \"string\",\n        \"null\"\n      ]\n    },\n    \"targetTriple\": {\n      \"description\": \"The target triple we are packaging for.\\n\\nDefaults to the current OS target triple.\",\n      \"type\": [\n        \"string\",\n        \"null\"\n      ]\n    },\n    \"description\": {\n      \"description\": \"The package's description.\",\n      \"type\": [\n        \"string\",\n        \"null\"\n      ]\n    },\n    \"longDescription\": {\n      \"description\": \"The app's long description.\",\n      \"type\": [\n        \"string\",\n        \"null\"\n      ]\n    },\n    \"homepage\": {\n      \"description\": \"The package's homepage.\",\n      \"type\": [\n        \"string\",\n        \"null\"\n      ]\n    },\n    \"authors\": {\n      \"description\": \"The package's authors.\",\n      \"default\": null,\n      \"type\": [\n        \"array\",\n        \"null\"\n      ],\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"publisher\": {\n      \"description\": \"The app's publisher. Defaults to the second element in [`Config::identifier`](Config::identifier) string. Currently maps to the Manufacturer property of the Windows Installer.\",\n      \"type\": [\n        \"string\",\n        \"null\"\n      ]\n    },\n    \"licenseFile\": {\n      \"description\": \"A path to the license file.\",\n      \"type\": [\n        \"string\",\n        \"null\"\n      ]\n    },\n    \"copyright\": {\n      \"description\": \"The app's copyright.\",\n      \"type\": [\n        \"string\",\n        \"null\"\n      ]\n    },\n    \"category\": {\n      \"description\": \"The app's category.\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/definitions/AppCategory\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ]\n    },\n    \"icons\": {\n      \"description\": \"The app's icon list. Supports glob patterns.\",\n      \"type\": [\n        \"array\",\n        \"null\"\n      ],\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"fileAssociations\": {\n      \"description\": \"The file associations\",\n      \"type\": [\n        \"array\",\n        \"null\"\n      ],\n      \"items\": {\n        \"$ref\": \"#/definitions/FileAssociation\"\n      }\n    },\n    \"deepLinkProtocols\": {\n      \"description\": \"Deep-link protocols.\",\n      \"type\": [\n        \"array\",\n        \"null\"\n      ],\n      \"items\": {\n        \"$ref\": \"#/definitions/DeepLinkProtocol\"\n      }\n    },\n    \"resources\": {\n      \"description\": \"The app's resources to package. This a list of either a glob pattern, path to a file, path to a directory or an object of `src` and `target` paths. In the case of using an object, the `src` could be either a glob pattern, path to a file, path to a directory, and the `target` is a path inside the final resources folder in the installed package.\\n\\n## Format-specific:\\n\\n- **[PackageFormat::Nsis] / [PackageFormat::Wix]**: The resources are placed next to the executable in the root of the packager. - **[PackageFormat::Deb]**: The resources are placed in `usr/lib` of the package.\",\n      \"type\": [\n        \"array\",\n        \"null\"\n      ],\n      \"items\": {\n        \"$ref\": \"#/definitions/Resource\"\n      }\n    },\n    \"externalBinaries\": {\n      \"description\": \"Paths to external binaries to add to the package.\\n\\nThe path specified should not include `-<target-triple><.exe>` suffix, it will be auto-added when by the packager when reading these paths, so the actual binary name should have the target platform's target triple appended, as well as `.exe` for Windows.\\n\\nFor example, if you're packaging an external binary called `sqlite3`, the packager expects a binary named `sqlite3-x86_64-unknown-linux-gnu` on linux, and `sqlite3-x86_64-pc-windows-gnu.exe` on windows.\\n\\nIf you are building a universal binary for MacOS, the packager expects your external binary to also be universal, and named after the target triple, e.g. `sqlite3-universal-apple-darwin`. See <https://developer.apple.com/documentation/apple-silicon/building-a-universal-macos-binary>\",\n      \"type\": [\n        \"array\",\n        \"null\"\n      ],\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"windows\": {\n      \"description\": \"Windows-specific configuration.\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/definitions/WindowsConfig\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ]\n    },\n    \"macos\": {\n      \"description\": \"MacOS-specific configuration.\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/definitions/MacOsConfig\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ]\n    },\n    \"linux\": {\n      \"description\": \"Linux-specific configuration\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/definitions/LinuxConfig\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ]\n    },\n    \"deb\": {\n      \"description\": \"Debian-specific configuration.\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/definitions/DebianConfig\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ]\n    },\n    \"appimage\": {\n      \"description\": \"AppImage configuration.\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/definitions/AppImageConfig\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ]\n    },\n    \"pacman\": {\n      \"description\": \"Pacman configuration.\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/definitions/PacmanConfig\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ]\n    },\n    \"wix\": {\n      \"description\": \"WiX configuration.\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/definitions/WixConfig\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ]\n    },\n    \"nsis\": {\n      \"description\": \"Nsis configuration.\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/definitions/NsisConfig\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ]\n    },\n    \"dmg\": {\n      \"description\": \"Dmg configuration.\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/definitions/DmgConfig\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ]\n    }\n  },\n  \"additionalProperties\": false,\n  \"definitions\": {\n    \"Binary\": {\n      \"description\": \"A binary to package within the final package.\",\n      \"type\": \"object\",\n      \"required\": [\n        \"path\"\n      ],\n      \"properties\": {\n        \"path\": {\n          \"description\": \"Path to the binary (without `.exe` on Windows). If it's relative, it will be resolved from [`Config::out_dir`].\",\n          \"type\": \"string\"\n        },\n        \"main\": {\n          \"description\": \"Whether this is the main binary or not\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"HookCommand\": {\n      \"description\": \"Describes a shell command to be executed when a CLI hook is triggered.\",\n      \"anyOf\": [\n        {\n          \"description\": \"Run the given script with the default options.\",\n          \"type\": \"string\"\n        },\n        {\n          \"description\": \"Run the given script with custom options.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"script\"\n          ],\n          \"properties\": {\n            \"script\": {\n              \"description\": \"The script to execute.\",\n              \"type\": \"string\"\n            },\n            \"dir\": {\n              \"description\": \"The working directory.\",\n              \"type\": [\n                \"string\",\n                \"null\"\n              ]\n            }\n          }\n        }\n      ]\n    },\n    \"LogLevel\": {\n      \"description\": \"An enum representing the available verbosity levels of the logger.\",\n      \"oneOf\": [\n        {\n          \"description\": \"The \\\"error\\\" level.\\n\\nDesignates very serious errors.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"error\"\n          ]\n        },\n        {\n          \"description\": \"The \\\"warn\\\" level.\\n\\nDesignates hazardous situations.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"warn\"\n          ]\n        },\n        {\n          \"description\": \"The \\\"info\\\" level.\\n\\nDesignates useful information.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"info\"\n          ]\n        },\n        {\n          \"description\": \"The \\\"debug\\\" level.\\n\\nDesignates lower priority information.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"debug\"\n          ]\n        },\n        {\n          \"description\": \"The \\\"trace\\\" level.\\n\\nDesignates very low priority, often extremely verbose, information.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"trace\"\n          ]\n        }\n      ]\n    },\n    \"PackageFormat\": {\n      \"description\": \"Types of supported packages by [`cargo-packager`](https://docs.rs/cargo-packager).\",\n      \"oneOf\": [\n        {\n          \"description\": \"All available package formats for the current platform.\\n\\nSee [`PackageFormat::platform_all`]\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"all\"\n          ]\n        },\n        {\n          \"description\": \"The default list of package formats for the current platform.\\n\\nSee [`PackageFormat::platform_default`]\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"default\"\n          ]\n        },\n        {\n          \"description\": \"The macOS application bundle (.app).\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"app\"\n          ]\n        },\n        {\n          \"description\": \"The macOS DMG package (.dmg).\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"dmg\"\n          ]\n        },\n        {\n          \"description\": \"The Microsoft Software Installer (.msi) through WiX Toolset.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"wix\"\n          ]\n        },\n        {\n          \"description\": \"The NSIS installer (.exe).\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"nsis\"\n          ]\n        },\n        {\n          \"description\": \"The Linux Debian package (.deb).\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"deb\"\n          ]\n        },\n        {\n          \"description\": \"The Linux AppImage package (.AppImage).\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"appimage\"\n          ]\n        },\n        {\n          \"description\": \"The Linux Pacman package (.tar.gz and PKGBUILD)\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"pacman\"\n          ]\n        }\n      ]\n    },\n    \"AppCategory\": {\n      \"description\": \"The possible app categories. Corresponds to `LSApplicationCategoryType` on macOS and the GNOME desktop categories on Debian.\",\n      \"type\": \"string\",\n      \"enum\": [\n        \"Business\",\n        \"DeveloperTool\",\n        \"Education\",\n        \"Entertainment\",\n        \"Finance\",\n        \"Game\",\n        \"ActionGame\",\n        \"AdventureGame\",\n        \"ArcadeGame\",\n        \"BoardGame\",\n        \"CardGame\",\n        \"CasinoGame\",\n        \"DiceGame\",\n        \"EducationalGame\",\n        \"FamilyGame\",\n        \"KidsGame\",\n        \"MusicGame\",\n        \"PuzzleGame\",\n        \"RacingGame\",\n        \"RolePlayingGame\",\n        \"SimulationGame\",\n        \"SportsGame\",\n        \"StrategyGame\",\n        \"TriviaGame\",\n        \"WordGame\",\n        \"GraphicsAndDesign\",\n        \"HealthcareAndFitness\",\n        \"Lifestyle\",\n        \"Medical\",\n        \"Music\",\n        \"News\",\n        \"Photography\",\n        \"Productivity\",\n        \"Reference\",\n        \"SocialNetworking\",\n        \"Sports\",\n        \"Travel\",\n        \"Utility\",\n        \"Video\",\n        \"Weather\"\n      ]\n    },\n    \"FileAssociation\": {\n      \"description\": \"A file association configuration.\",\n      \"type\": \"object\",\n      \"required\": [\n        \"extensions\"\n      ],\n      \"properties\": {\n        \"extensions\": {\n          \"description\": \"File extensions to associate with this app. e.g. 'png'\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"mimeType\": {\n          \"description\": \"The mime-type e.g. 'image/png' or 'text/plain'. **Linux-only**.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"description\": {\n          \"description\": \"The association description. **Windows-only**. It is displayed on the `Type` column on Windows Explorer.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"name\": {\n          \"description\": \"The name. Maps to `CFBundleTypeName` on macOS. Defaults to the first item in `ext`\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"role\": {\n          \"description\": \"The app's role with respect to the type. Maps to `CFBundleTypeRole` on macOS. Defaults to [`BundleTypeRole::Editor`]\",\n          \"default\": \"editor\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/BundleTypeRole\"\n            }\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"BundleTypeRole\": {\n      \"description\": \"*macOS-only**. Corresponds to CFBundleTypeRole\",\n      \"oneOf\": [\n        {\n          \"description\": \"CFBundleTypeRole.Editor. Files can be read and edited.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"editor\"\n          ]\n        },\n        {\n          \"description\": \"CFBundleTypeRole.Viewer. Files can be read.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"viewer\"\n          ]\n        },\n        {\n          \"description\": \"CFBundleTypeRole.Shell\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"shell\"\n          ]\n        },\n        {\n          \"description\": \"CFBundleTypeRole.QLGenerator\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"qLGenerator\"\n          ]\n        },\n        {\n          \"description\": \"CFBundleTypeRole.None\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"none\"\n          ]\n        }\n      ]\n    },\n    \"DeepLinkProtocol\": {\n      \"description\": \"Deep link protocol\",\n      \"type\": \"object\",\n      \"required\": [\n        \"schemes\"\n      ],\n      \"properties\": {\n        \"schemes\": {\n          \"description\": \"URL schemes to associate with this app without `://`. For example `my-app`\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"name\": {\n          \"description\": \"The protocol name. **macOS-only** and maps to `CFBundleTypeName`. Defaults to `<bundle-id>.<schemes[0]>`\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"role\": {\n          \"description\": \"The app's role for these schemes. **macOS-only** and maps to `CFBundleTypeRole`.\",\n          \"default\": \"editor\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/BundleTypeRole\"\n            }\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"Resource\": {\n      \"description\": \"A path to a resource (with optional glob pattern) or an object of `src` and `target` paths.\",\n      \"anyOf\": [\n        {\n          \"description\": \"Supports glob patterns\",\n          \"type\": \"string\"\n        },\n        {\n          \"description\": \"An object descriping the src file or directory and its target location in the final package.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"src\",\n            \"target\"\n          ],\n          \"properties\": {\n            \"src\": {\n              \"description\": \"The src file or directory, supports glob patterns.\",\n              \"type\": \"string\"\n            },\n            \"target\": {\n              \"description\": \"A relative path from the root of the final package.\\n\\nIf `src` is a glob, this will always be treated as a directory where all globbed files will be placed under.\",\n              \"type\": \"string\"\n            }\n          }\n        }\n      ]\n    },\n    \"WindowsConfig\": {\n      \"description\": \"The Windows configuration.\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"digestAlgorithm\": {\n          \"description\": \"The file digest algorithm to use for creating file signatures. Required for code signing. SHA-256 is recommended.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"certificateThumbprint\": {\n          \"description\": \"The SHA1 hash of the signing certificate.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"tsp\": {\n          \"description\": \"Whether to use Time-Stamp Protocol (TSP, a.k.a. RFC 3161) for the timestamp server. Your code signing provider may use a TSP timestamp server, like e.g. SSL.com does. If so, enable TSP by setting to true.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"timestampUrl\": {\n          \"description\": \"Server to use during timestamping.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"allowDowngrades\": {\n          \"description\": \"Whether to validate a second app installation, blocking the user from installing an older version if set to `false`.\\n\\nFor instance, if `1.2.1` is installed, the user won't be able to install app version `1.2.0` or `1.1.5`.\\n\\nThe default value of this flag is `true`.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"signCommand\": {\n          \"description\": \"Specify a custom command to sign the binaries. This command needs to have a `%1` in it which is just a placeholder for the binary path, which we will detect and replace before calling the command.\\n\\nBy Default we use `signtool.exe` which can be found only on Windows so if you are on another platform and want to cross-compile and sign you will need to use another tool like `osslsigncode`.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"MacOsConfig\": {\n      \"description\": \"The macOS configuration.\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"frameworks\": {\n          \"description\": \"MacOS frameworks that need to be packaged with the app.\\n\\nEach string can either be the name of a framework (without the `.framework` extension, e.g. `\\\"SDL2\\\"`), in which case we will search for that framework in the standard install locations (`~/Library/Frameworks/`, `/Library/Frameworks/`, and `/Network/Library/Frameworks/`), or a path to a specific framework bundle (e.g. `./data/frameworks/SDL2.framework`).  Note that this setting just makes cargo-packager copy the specified frameworks into the OS X app bundle (under `Foobar.app/Contents/Frameworks/`); you are still responsible for:\\n\\n- arranging for the compiled binary to link against those frameworks (e.g. by emitting lines like `cargo:rustc-link-lib=framework=SDL2` from your `build.rs` script)\\n\\n- embedding the correct rpath in your binary (e.g. by running `install_name_tool -add_rpath \\\"@executable_path/../Frameworks\\\" path/to/binary` after compiling)\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"minimumSystemVersion\": {\n          \"description\": \"A version string indicating the minimum MacOS version that the packaged app supports (e.g. `\\\"10.11\\\"`). If you are using this config field, you may also want have your `build.rs` script emit `cargo:rustc-env=MACOSX_DEPLOYMENT_TARGET=10.11`.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"exceptionDomain\": {\n          \"description\": \"The exception domain to use on the macOS .app package.\\n\\nThis allows communication to the outside world e.g. a web server you're shipping.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"signingIdentity\": {\n          \"description\": \"Code signing identity.\\n\\nThis is typically of the form: `\\\"Developer ID Application: TEAM_NAME (TEAM_ID)\\\"`.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"providerShortName\": {\n          \"description\": \"Provider short name for notarization.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"entitlements\": {\n          \"description\": \"Path to the entitlements.plist file.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"infoPlistPath\": {\n          \"description\": \"Path to the Info.plist file for the package.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"embeddedProvisionprofilePath\": {\n          \"description\": \"Path to the embedded.provisionprofile file for the package.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"embeddedApps\": {\n          \"description\": \"Apps that need to be packaged within the app.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"backgroundApp\": {\n          \"description\": \"Whether this is a background application. If true, the app will not appear in the Dock.\\n\\nSets the `LSUIElement` flag in the macOS plist file.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"LinuxConfig\": {\n      \"description\": \"Linux configuration\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"generateDesktopEntry\": {\n          \"description\": \"Flag to indicate if desktop entry should be generated.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"DebianConfig\": {\n      \"description\": \"The Linux Debian configuration.\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"depends\": {\n          \"description\": \"The list of Debian dependencies.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/Dependencies\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"desktopTemplate\": {\n          \"description\": \"Path to a custom desktop file Handlebars template.\\n\\nAvailable variables: `categories`, `comment` (optional), `exec`, `icon` and `name`.\\n\\nDefault file contents: ```text [Desktop Entry] Categories={{categories}} {{#if comment}} Comment={{comment}} {{/if}} Exec={{exec}} {{exec_arg}} Icon={{icon}} Name={{name}} Terminal=false Type=Application {{#if mime_type}} MimeType={{mime_type}} {{/if}} ```\\n\\nThe `{{exec_arg}}` will be set to: * \\\"%F\\\", if at least one [Config::file_associations] was specified but no deep link protocols were given. * The \\\"%F\\\" arg means that your application can be invoked with multiple file paths. * \\\"%U\\\", if at least one [Config::deep_link_protocols] was specified. * The \\\"%U\\\" arg means that your application can be invoked with multiple URLs. * If both [Config::file_associations] and [Config::deep_link_protocols] were specified, the \\\"%U\\\" arg will be used, causing the file paths to be passed to your app as `file://` URLs. * An empty string \\\"\\\" (nothing) if neither are given. * This means that your application will never be invoked with any URLs or file paths.\\n\\nTo specify a custom `exec_arg`, just use plaintext directly instead of `{{exec_arg}}`: ```text Exec={{exec}} %u ```\\n\\nSee more here: <https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#exec-variables>.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"section\": {\n          \"description\": \"Define the section in Debian Control file. See : <https://www.debian.org/doc/debian-policy/ch-archive.html#s-subsections>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"priority\": {\n          \"description\": \"Change the priority of the Debian Package. By default, it is set to `optional`. Recognized Priorities as of now are :  `required`, `important`, `standard`, `optional`, `extra`\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"files\": {\n          \"description\": \"List of custom files to add to the deb package. Maps a dir/file to a dir/file inside the debian package.\",\n          \"type\": [\n            \"object\",\n            \"null\"\n          ],\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        },\n        \"packageName\": {\n          \"description\": \"Name to use for the `Package` field in the Debian Control file. Defaults to [`Config::product_name`] converted to kebab-case.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"Dependencies\": {\n      \"description\": \"A list of dependencies specified as either a list of Strings or as a path to a file that lists the dependencies, one per line.\",\n      \"anyOf\": [\n        {\n          \"description\": \"The list of dependencies provided directly as a vector of Strings.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        {\n          \"description\": \"A path to the file containing the list of dependences, formatted as one per line: ```text libc6 libxcursor1 libdbus-1-3 libasyncns0 ... ```\",\n          \"type\": \"string\"\n        }\n      ]\n    },\n    \"AppImageConfig\": {\n      \"description\": \"The Linux AppImage configuration.\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"libs\": {\n          \"description\": \"List of libs that exist in `/usr/lib*` to be include in the final AppImage. The libs will be searched for, using the command `find -L /usr/lib* -name <libname>`\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"bins\": {\n          \"description\": \"List of binary paths to include in the final AppImage. For example, if you want `xdg-open`, you'd specify `/usr/bin/xdg-open`\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"files\": {\n          \"description\": \"List of custom files to add to the appimage package. Maps a dir/file to a dir/file inside the appimage package.\",\n          \"type\": [\n            \"object\",\n            \"null\"\n          ],\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        },\n        \"linuxdeployPlugins\": {\n          \"description\": \"A map of [`linuxdeploy`](https://github.com/linuxdeploy/linuxdeploy) plugin name and its URL to be downloaded and executed while packaing the appimage. For example, if you want to use the [`gtk`](https://raw.githubusercontent.com/linuxdeploy/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh) plugin, you'd specify `gtk` as the key and its url as the value.\",\n          \"type\": [\n            \"object\",\n            \"null\"\n          ],\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        },\n        \"excludedLibs\": {\n          \"description\": \"List of globs of libraries to exclude from the final AppImage. For example, to exclude libnss3.so, you'd specify `libnss3*`\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"PacmanConfig\": {\n      \"description\": \"The Linux pacman configuration.\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"files\": {\n          \"description\": \"List of custom files to add to the pacman package. Maps a dir/file to a dir/file inside the pacman package.\",\n          \"type\": [\n            \"object\",\n            \"null\"\n          ],\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        },\n        \"depends\": {\n          \"description\": \"List of softwares that must be installed for the app to build and run.\\n\\nSee : <https://wiki.archlinux.org/title/PKGBUILD#depends>\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/Dependencies\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"provides\": {\n          \"description\": \"Additional packages that are provided by this app.\\n\\nSee : <https://wiki.archlinux.org/title/PKGBUILD#provides>\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"conflicts\": {\n          \"description\": \"Packages that conflict or cause problems with the app. All these packages and packages providing this item will need to be removed\\n\\nSee : <https://wiki.archlinux.org/title/PKGBUILD#conflicts>\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"replaces\": {\n          \"description\": \"Only use if this app replaces some obsolete packages. For example, if you rename any package.\\n\\nSee : <https://wiki.archlinux.org/title/PKGBUILD#replaces>\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"source\": {\n          \"description\": \"Source of the package to be stored at PKGBUILD. PKGBUILD is a bash script, so version can be referred as ${pkgver}\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"WixConfig\": {\n      \"description\": \"The wix format configuration\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"languages\": {\n          \"description\": \"The app languages to build. See <https://docs.microsoft.com/en-us/windows/win32/msi/localizing-the-error-and-actiontext-tables>.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"$ref\": \"#/definitions/WixLanguage\"\n          }\n        },\n        \"template\": {\n          \"description\": \"By default, the packager uses an internal template. This option allows you to define your own wix file.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"mergeModules\": {\n          \"description\": \"List of merge modules to include in your installer. For example, if you want to include [C++ Redis merge modules]\\n\\n[C++ Redis merge modules]: https://wixtoolset.org/docs/v3/howtos/redistributables_and_install_checks/install_vcredist/\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"fragmentPaths\": {\n          \"description\": \"A list of paths to .wxs files with WiX fragments to use.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"fragments\": {\n          \"description\": \"List of WiX fragments as strings. This is similar to `config.wix.fragments_paths` but is a string so you can define it inline in your config.\\n\\n```text <?xml version=\\\"1.0\\\" encoding=\\\"utf-8\\\"?> <Wix xmlns=\\\"http://schemas.microsoft.com/wix/2006/wi\\\"> <Fragment> <CustomAction Id=\\\"OpenNotepad\\\" Directory=\\\"INSTALLDIR\\\" Execute=\\\"immediate\\\" ExeCommand=\\\"cmd.exe /c notepad.exe\\\" Return=\\\"check\\\" /> <InstallExecuteSequence> <Custom Action=\\\"OpenNotepad\\\" After=\\\"InstallInitialize\\\" /> </InstallExecuteSequence> </Fragment> </Wix> ```\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"componentGroupRefs\": {\n          \"description\": \"The ComponentGroup element ids you want to reference from the fragments.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"componentRefs\": {\n          \"description\": \"The Component element ids you want to reference from the fragments.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"customActionRefs\": {\n          \"description\": \"The CustomAction element ids you want to reference from the fragments.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"featureGroupRefs\": {\n          \"description\": \"The FeatureGroup element ids you want to reference from the fragments.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"featureRefs\": {\n          \"description\": \"The Feature element ids you want to reference from the fragments.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"mergeRefs\": {\n          \"description\": \"The Merge element ids you want to reference from the fragments.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"bannerPath\": {\n          \"description\": \"Path to a bitmap file to use as the installation user interface banner. This bitmap will appear at the top of all but the first page of the installer.\\n\\nThe required dimensions are 493px × 58px.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"dialogImagePath\": {\n          \"description\": \"Path to a bitmap file to use on the installation user interface dialogs. It is used on the welcome and completion dialogs. The required dimensions are 493px × 312px.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"fipsCompliant\": {\n          \"description\": \"Enables FIPS compliant algorithms.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"WixLanguage\": {\n      \"description\": \"A wix language.\",\n      \"anyOf\": [\n        {\n          \"description\": \"Built-in wix language identifier.\",\n          \"type\": \"string\"\n        },\n        {\n          \"description\": \"Custom wix language.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"identifier\"\n          ],\n          \"properties\": {\n            \"identifier\": {\n              \"description\": \"Idenitifier of this language, for example `en-US`\",\n              \"type\": \"string\"\n            },\n            \"path\": {\n              \"description\": \"The path to a locale (`.wxl`) file. See <https://wixtoolset.org/documentation/manual/v3/howtos/ui_and_localization/build_a_localized_version.html>.\",\n              \"type\": [\n                \"string\",\n                \"null\"\n              ]\n            }\n          }\n        }\n      ]\n    },\n    \"NsisConfig\": {\n      \"description\": \"The NSIS format configuration.\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"compression\": {\n          \"description\": \"Set the compression algorithm used to compress files in the installer.\\n\\nSee <https://nsis.sourceforge.io/Reference/SetCompressor>\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/NsisCompression\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"template\": {\n          \"description\": \"A custom `.nsi` template to use.\\n\\nSee the default template here <https://github.com/crabnebula-dev/cargo-packager/blob/main/crates/packager/src/package/nsis/installer.nsi>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"preinstallSection\": {\n          \"description\": \"Logic of an NSIS section that will be ran before the install section.\\n\\nSee the available libraries, dlls and global variables here <https://github.com/crabnebula-dev/cargo-packager/blob/main/crates/packager/src/package/nsis/installer.nsi>\\n\\n### Example ```toml [package.metadata.packager.nsis] preinstall-section = \\\"\\\"\\\" ; Setup custom messages LangString webview2AbortError ${LANG_ENGLISH} \\\"Failed to install WebView2! The app can't run without it. Try restarting the installer.\\\" LangString webview2DownloadError ${LANG_ARABIC} \\\"خطأ: فشل تنزيل WebView2 - $0\\\"\\n\\nSection PreInstall ; <section logic here> SectionEnd\\n\\nSection AnotherPreInstall ; <section logic here> SectionEnd \\\"\\\"\\\" ```\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"headerImage\": {\n          \"description\": \"The path to a bitmap file to display on the header of installers pages.\\n\\nThe recommended dimensions are 150px x 57px.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"sidebarImage\": {\n          \"description\": \"The path to a bitmap file for the Welcome page and the Finish page.\\n\\nThe recommended dimensions are 164px x 314px.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"installerIcon\": {\n          \"description\": \"The path to an icon file used as the installer icon.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"installMode\": {\n          \"description\": \"Whether the installation will be for all users or just the current user.\",\n          \"default\": \"currentUser\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/NSISInstallerMode\"\n            }\n          ]\n        },\n        \"languages\": {\n          \"description\": \"A list of installer languages. By default the OS language is used. If the OS language is not in the list of languages, the first language will be used. To allow the user to select the language, set `display_language_selector` to `true`.\\n\\nSee <https://github.com/kichik/nsis/tree/9465c08046f00ccb6eda985abbdbf52c275c6c4d/Contrib/Language%20files> for the complete list of languages.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"customLanguageFiles\": {\n          \"description\": \"An key-value pair where the key is the language and the value is the path to a custom `.nsi` file that holds the translated text for cargo-packager's custom messages.\\n\\nSee <https://github.com/crabnebula-dev/cargo-packager/blob/main/crates/packager/src/nsis/languages/English.nsh> for an example `.nsi` file.\\n\\n**Note**: the key must be a valid NSIS language and it must be added to [`NsisConfig`]languages array,\",\n          \"type\": [\n            \"object\",\n            \"null\"\n          ],\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        },\n        \"displayLanguageSelector\": {\n          \"description\": \"Whether to display a language selector dialog before the installer and uninstaller windows are rendered or not. By default the OS language is selected, with a fallback to the first language in the `languages` array.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"appdataPaths\": {\n          \"description\": \"List of paths where your app stores data. This options tells the uninstaller to provide the user with an option (disabled by default) whether they want to rmeove your app data or keep it.\\n\\nThe path should use a constant from <https://nsis.sourceforge.io/Docs/Chapter4.html#varconstant> in addition to `$IDENTIFIER`, `$PUBLISHER` and `$PRODUCTNAME`, for example, if you store your app data in `C:\\\\\\\\Users\\\\\\\\<user>\\\\\\\\AppData\\\\\\\\Local\\\\\\\\<your-company-name>\\\\\\\\<your-product-name>` you'd need to specify ```toml [package.metadata.packager.nsis] appdata-paths = [\\\"$LOCALAPPDATA/$PUBLISHER/$PRODUCTNAME\\\"] ```\",\n          \"default\": null,\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"NsisCompression\": {\n      \"description\": \"Compression algorithms used in the NSIS installer.\\n\\nSee <https://nsis.sourceforge.io/Reference/SetCompressor>\",\n      \"oneOf\": [\n        {\n          \"description\": \"ZLIB uses the deflate algorithm, it is a quick and simple method. With the default compression level it uses about 300 KB of memory.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"zlib\"\n          ]\n        },\n        {\n          \"description\": \"BZIP2 usually gives better compression ratios than ZLIB, but it is a bit slower and uses more memory. With the default compression level it uses about 4 MB of memory.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"bzip2\"\n          ]\n        },\n        {\n          \"description\": \"LZMA (default) is a new compression method that gives very good compression ratios. The decompression speed is high (10-20 MB/s on a 2 GHz CPU), the compression speed is lower. The memory size that will be used for decompression is the dictionary size plus a few KBs, the default is 8 MB.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"lzma\"\n          ]\n        },\n        {\n          \"description\": \"Disable compression.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"off\"\n          ]\n        }\n      ]\n    },\n    \"NSISInstallerMode\": {\n      \"description\": \"Install Modes for the NSIS installer.\",\n      \"oneOf\": [\n        {\n          \"description\": \"Default mode for the installer.\\n\\nInstall the app by default in a directory that doesn't require Administrator access.\\n\\nInstaller metadata will be saved under the `HKCU` registry path.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"currentUser\"\n          ]\n        },\n        {\n          \"description\": \"Install the app by default in the `Program Files` folder directory requires Administrator access for the installation.\\n\\nInstaller metadata will be saved under the `HKLM` registry path.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"perMachine\"\n          ]\n        },\n        {\n          \"description\": \"Combines both modes and allows the user to choose at install time whether to install for the current user or per machine. Note that this mode will require Administrator access even if the user wants to install it for the current user only.\\n\\nInstaller metadata will be saved under the `HKLM` or `HKCU` registry path based on the user's choice.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"both\"\n          ]\n        }\n      ]\n    },\n    \"DmgConfig\": {\n      \"description\": \"The Apple Disk Image (.dmg) configuration.\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"background\": {\n          \"description\": \"Image to use as the background in dmg file. Accepted formats: `png`/`jpg`/`gif`.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"windowPosition\": {\n          \"description\": \"Position of volume window on screen.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/Position\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"windowSize\": {\n          \"description\": \"Size of volume window.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/Size\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"appPosition\": {\n          \"description\": \"Position of application file on window.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/Position\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"appFolderPosition\": {\n          \"description\": \"Position of application folder on window.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/Position\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"Position\": {\n      \"description\": \"Position coordinates struct.\",\n      \"type\": \"object\",\n      \"required\": [\n        \"x\",\n        \"y\"\n      ],\n      \"properties\": {\n        \"x\": {\n          \"description\": \"X coordinate.\",\n          \"type\": \"integer\",\n          \"format\": \"uint32\",\n          \"minimum\": 0.0\n        },\n        \"y\": {\n          \"description\": \"Y coordinate.\",\n          \"type\": \"integer\",\n          \"format\": \"uint32\",\n          \"minimum\": 0.0\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"Size\": {\n      \"description\": \"Size struct.\",\n      \"type\": \"object\",\n      \"required\": [\n        \"height\",\n        \"width\"\n      ],\n      \"properties\": {\n        \"width\": {\n          \"description\": \"Width.\",\n          \"type\": \"integer\",\n          \"format\": \"uint32\",\n          \"minimum\": 0.0\n        },\n        \"height\": {\n          \"description\": \"Height.\",\n          \"type\": \"integer\",\n          \"format\": \"uint32\",\n          \"minimum\": 0.0\n        }\n      },\n      \"additionalProperties\": false\n    }\n  }\n}"
  },
  {
    "path": "bindings/packager/nodejs/src/lib.rs",
    "content": "use napi::{Error, Result, Status};\n\n#[napi_derive::napi]\npub fn cli(args: Vec<String>, bin_name: Option<String>) -> Result<()> {\n    cargo_packager::cli::try_run(args, bin_name)\n        .map_err(|e| Error::new(Status::GenericFailure, e.to_string()))\n}\n\n#[napi_derive::napi]\npub fn package_app(config: String) -> Result<()> {\n    let config = serde_json::from_str(&config)\n        .map_err(|e| Error::new(Status::GenericFailure, e.to_string()))?;\n    cargo_packager::package(&config)\n        .map_err(|e| Error::new(Status::GenericFailure, e.to_string()))?;\n    Ok(())\n}\n\n#[napi_derive::napi]\npub fn package_and_sign_app(config: String, signing_config: String) -> Result<()> {\n    let config = serde_json::from_str(&config)\n        .map_err(|e| Error::new(Status::GenericFailure, e.to_string()))?;\n    let signing_config = serde_json::from_str(&signing_config)\n        .map_err(|e| Error::new(Status::GenericFailure, e.to_string()))?;\n    cargo_packager::package_and_sign(&config, &signing_config)\n        .map_err(|e| Error::new(Status::GenericFailure, e.to_string()))?;\n    Ok(())\n}\n\n#[napi_derive::napi]\npub fn init_tracing_subscriber(verbosity: u8) {\n    cargo_packager::init_tracing_subscriber(verbosity);\n}\n\n#[napi_derive::napi]\npub fn log_error(error: String) {\n    tracing::error!(\"{}\", error);\n}\n"
  },
  {
    "path": "bindings/packager/nodejs/src-ts/config.d.ts",
    "content": "/* eslint-disable */\n/**\n * This file was automatically generated by json-schema-to-typescript.\n * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,\n * and run json-schema-to-typescript to regenerate this file.\n */\n\n/**\n * Describes a shell command to be executed when a CLI hook is triggered.\n */\nexport type HookCommand =\n  | string\n  | {\n      /**\n       * The script to execute.\n       */\n      script: string;\n      /**\n       * The working directory.\n       */\n      dir?: string | null;\n      [k: string]: unknown;\n    };\n/**\n * An enum representing the available verbosity levels of the logger.\n */\nexport type LogLevel = \"error\" | \"warn\" | \"info\" | \"debug\" | \"trace\";\n/**\n * Types of supported packages by [`cargo-packager`](https://docs.rs/cargo-packager).\n */\nexport type PackageFormat = \"all\" | \"default\" | \"app\" | \"dmg\" | \"wix\" | \"nsis\" | \"deb\" | \"appimage\" | \"pacman\";\n/**\n * The possible app categories. Corresponds to `LSApplicationCategoryType` on macOS and the GNOME desktop categories on Debian.\n */\nexport type AppCategory =\n  | \"Business\"\n  | \"DeveloperTool\"\n  | \"Education\"\n  | \"Entertainment\"\n  | \"Finance\"\n  | \"Game\"\n  | \"ActionGame\"\n  | \"AdventureGame\"\n  | \"ArcadeGame\"\n  | \"BoardGame\"\n  | \"CardGame\"\n  | \"CasinoGame\"\n  | \"DiceGame\"\n  | \"EducationalGame\"\n  | \"FamilyGame\"\n  | \"KidsGame\"\n  | \"MusicGame\"\n  | \"PuzzleGame\"\n  | \"RacingGame\"\n  | \"RolePlayingGame\"\n  | \"SimulationGame\"\n  | \"SportsGame\"\n  | \"StrategyGame\"\n  | \"TriviaGame\"\n  | \"WordGame\"\n  | \"GraphicsAndDesign\"\n  | \"HealthcareAndFitness\"\n  | \"Lifestyle\"\n  | \"Medical\"\n  | \"Music\"\n  | \"News\"\n  | \"Photography\"\n  | \"Productivity\"\n  | \"Reference\"\n  | \"SocialNetworking\"\n  | \"Sports\"\n  | \"Travel\"\n  | \"Utility\"\n  | \"Video\"\n  | \"Weather\";\n/**\n * *macOS-only**. Corresponds to CFBundleTypeRole\n */\nexport type BundleTypeRole = \"editor\" | \"viewer\" | \"shell\" | \"qLGenerator\" | \"none\";\n/**\n * A path to a resource (with optional glob pattern) or an object of `src` and `target` paths.\n */\nexport type Resource =\n  | string\n  | {\n      /**\n       * The src file or directory, supports glob patterns.\n       */\n      src: string;\n      /**\n       * A relative path from the root of the final package.\n       *\n       * If `src` is a glob, this will always be treated as a directory where all globbed files will be placed under.\n       */\n      target: string;\n      [k: string]: unknown;\n    };\n/**\n * A list of dependencies specified as either a list of Strings or as a path to a file that lists the dependencies, one per line.\n */\nexport type Dependencies = string[] | string;\n/**\n * A wix language.\n */\nexport type WixLanguage =\n  | string\n  | {\n      /**\n       * Idenitifier of this language, for example `en-US`\n       */\n      identifier: string;\n      /**\n       * The path to a locale (`.wxl`) file. See <https://wixtoolset.org/documentation/manual/v3/howtos/ui_and_localization/build_a_localized_version.html>.\n       */\n      path?: string | null;\n      [k: string]: unknown;\n    };\n/**\n * Compression algorithms used in the NSIS installer.\n *\n * See <https://nsis.sourceforge.io/Reference/SetCompressor>\n */\nexport type NsisCompression = \"zlib\" | \"bzip2\" | \"lzma\" | \"off\";\n/**\n * Install Modes for the NSIS installer.\n */\nexport type NSISInstallerMode = \"currentUser\" | \"perMachine\" | \"both\";\n\n/**\n * The packaging config.\n */\nexport interface Config {\n  /**\n   * The JSON schema for the config.\n   *\n   * Setting this field has no effect, this just exists so we can parse the JSON correctly when it has `$schema` field set.\n   */\n  $schema?: string | null;\n  /**\n   * The app name, this is just an identifier that could be used to filter which app to package using `--packages` cli arg when there is multiple apps in the workspace or in the same config.\n   *\n   * This field resembles, the `name` field in `Cargo.toml` or `package.json`\n   *\n   * If `unset`, the CLI will try to auto-detect it from `Cargo.toml` or `package.json` otherwise, it will keep it unset.\n   */\n  name?: string | null;\n  /**\n   * Whether this config is enabled or not. Defaults to `true`.\n   */\n  enabled?: boolean;\n  /**\n   * The package's product name, for example \"My Awesome App\".\n   */\n  productName?: string;\n  /**\n   * The package's version.\n   */\n  version?: string;\n  /**\n   * The binaries to package.\n   */\n  binaries?: Binary[];\n  /**\n   * The application identifier in reverse domain name notation (e.g. `com.packager.example`). This string must be unique across applications since it is used in some system configurations. This string must contain only alphanumeric characters (A-Z, a-z, and 0-9), hyphens (-), and periods (.).\n   */\n  identifier?: string | null;\n  /**\n   * The command to run before starting to package an application.\n   *\n   * This runs only once.\n   */\n  beforePackagingCommand?: HookCommand | null;\n  /**\n   * The command to run before packaging each format for an application.\n   *\n   * This will run multiple times depending on the formats specifed.\n   */\n  beforeEachPackageCommand?: HookCommand | null;\n  /**\n   * The logging level.\n   */\n  logLevel?: LogLevel | null;\n  /**\n   * The packaging formats to create, if not present, [`PackageFormat::platform_default`] is used.\n   */\n  formats?: PackageFormat[] | null;\n  /**\n   * The directory where the generated packages will be placed.\n   *\n   * If [`Config::binaries_dir`] is not set, this is also where the [`Config::binaries`] exist.\n   */\n  outDir?: string;\n  /**\n   * The directory where the [`Config::binaries`] exist.\n   *\n   * Defaults to [`Config::out_dir`].\n   */\n  binariesDir?: string | null;\n  /**\n   * The target triple we are packaging for.\n   *\n   * Defaults to the current OS target triple.\n   */\n  targetTriple?: string | null;\n  /**\n   * The package's description.\n   */\n  description?: string | null;\n  /**\n   * The app's long description.\n   */\n  longDescription?: string | null;\n  /**\n   * The package's homepage.\n   */\n  homepage?: string | null;\n  /**\n   * The package's authors.\n   */\n  authors?: string[] | null;\n  /**\n   * The app's publisher. Defaults to the second element in [`Config::identifier`](Config::identifier) string. Currently maps to the Manufacturer property of the Windows Installer.\n   */\n  publisher?: string | null;\n  /**\n   * A path to the license file.\n   */\n  licenseFile?: string | null;\n  /**\n   * The app's copyright.\n   */\n  copyright?: string | null;\n  /**\n   * The app's category.\n   */\n  category?: AppCategory | null;\n  /**\n   * The app's icon list. Supports glob patterns.\n   */\n  icons?: string[] | null;\n  /**\n   * The file associations\n   */\n  fileAssociations?: FileAssociation[] | null;\n  /**\n   * Deep-link protocols.\n   */\n  deepLinkProtocols?: DeepLinkProtocol[] | null;\n  /**\n   * The app's resources to package. This a list of either a glob pattern, path to a file, path to a directory or an object of `src` and `target` paths. In the case of using an object, the `src` could be either a glob pattern, path to a file, path to a directory, and the `target` is a path inside the final resources folder in the installed package.\n   *\n   * ## Format-specific:\n   *\n   * - **[PackageFormat::Nsis] / [PackageFormat::Wix]**: The resources are placed next to the executable in the root of the packager. - **[PackageFormat::Deb]**: The resources are placed in `usr/lib` of the package.\n   */\n  resources?: Resource[] | null;\n  /**\n   * Paths to external binaries to add to the package.\n   *\n   * The path specified should not include `-<target-triple><.exe>` suffix, it will be auto-added when by the packager when reading these paths, so the actual binary name should have the target platform's target triple appended, as well as `.exe` for Windows.\n   *\n   * For example, if you're packaging an external binary called `sqlite3`, the packager expects a binary named `sqlite3-x86_64-unknown-linux-gnu` on linux, and `sqlite3-x86_64-pc-windows-gnu.exe` on windows.\n   *\n   * If you are building a universal binary for MacOS, the packager expects your external binary to also be universal, and named after the target triple, e.g. `sqlite3-universal-apple-darwin`. See <https://developer.apple.com/documentation/apple-silicon/building-a-universal-macos-binary>\n   */\n  externalBinaries?: string[] | null;\n  /**\n   * Windows-specific configuration.\n   */\n  windows?: WindowsConfig | null;\n  /**\n   * MacOS-specific configuration.\n   */\n  macos?: MacOsConfig | null;\n  /**\n   * Linux-specific configuration\n   */\n  linux?: LinuxConfig | null;\n  /**\n   * Debian-specific configuration.\n   */\n  deb?: DebianConfig | null;\n  /**\n   * AppImage configuration.\n   */\n  appimage?: AppImageConfig | null;\n  /**\n   * Pacman configuration.\n   */\n  pacman?: PacmanConfig | null;\n  /**\n   * WiX configuration.\n   */\n  wix?: WixConfig | null;\n  /**\n   * Nsis configuration.\n   */\n  nsis?: NsisConfig | null;\n  /**\n   * Dmg configuration.\n   */\n  dmg?: DmgConfig | null;\n}\n/**\n * A binary to package within the final package.\n */\nexport interface Binary {\n  /**\n   * Path to the binary (without `.exe` on Windows). If it's relative, it will be resolved from [`Config::out_dir`].\n   */\n  path: string;\n  /**\n   * Whether this is the main binary or not\n   */\n  main?: boolean;\n}\n/**\n * A file association configuration.\n */\nexport interface FileAssociation {\n  /**\n   * File extensions to associate with this app. e.g. 'png'\n   */\n  extensions: string[];\n  /**\n   * The mime-type e.g. 'image/png' or 'text/plain'. **Linux-only**.\n   */\n  mimeType?: string | null;\n  /**\n   * The association description. **Windows-only**. It is displayed on the `Type` column on Windows Explorer.\n   */\n  description?: string | null;\n  /**\n   * The name. Maps to `CFBundleTypeName` on macOS. Defaults to the first item in `ext`\n   */\n  name?: string | null;\n  /**\n   * The app's role with respect to the type. Maps to `CFBundleTypeRole` on macOS. Defaults to [`BundleTypeRole::Editor`]\n   */\n  role?: BundleTypeRole & string;\n}\n/**\n * Deep link protocol\n */\nexport interface DeepLinkProtocol {\n  /**\n   * URL schemes to associate with this app without `://`. For example `my-app`\n   */\n  schemes: string[];\n  /**\n   * The protocol name. **macOS-only** and maps to `CFBundleTypeName`. Defaults to `<bundle-id>.<schemes[0]>`\n   */\n  name?: string | null;\n  /**\n   * The app's role for these schemes. **macOS-only** and maps to `CFBundleTypeRole`.\n   */\n  role?: BundleTypeRole & string;\n}\n/**\n * The Windows configuration.\n */\nexport interface WindowsConfig {\n  /**\n   * The file digest algorithm to use for creating file signatures. Required for code signing. SHA-256 is recommended.\n   */\n  digestAlgorithm?: string | null;\n  /**\n   * The SHA1 hash of the signing certificate.\n   */\n  certificateThumbprint?: string | null;\n  /**\n   * Whether to use Time-Stamp Protocol (TSP, a.k.a. RFC 3161) for the timestamp server. Your code signing provider may use a TSP timestamp server, like e.g. SSL.com does. If so, enable TSP by setting to true.\n   */\n  tsp?: boolean;\n  /**\n   * Server to use during timestamping.\n   */\n  timestampUrl?: string | null;\n  /**\n   * Whether to validate a second app installation, blocking the user from installing an older version if set to `false`.\n   *\n   * For instance, if `1.2.1` is installed, the user won't be able to install app version `1.2.0` or `1.1.5`.\n   *\n   * The default value of this flag is `true`.\n   */\n  allowDowngrades?: boolean;\n  /**\n   * Specify a custom command to sign the binaries. This command needs to have a `%1` in it which is just a placeholder for the binary path, which we will detect and replace before calling the command.\n   *\n   * By Default we use `signtool.exe` which can be found only on Windows so if you are on another platform and want to cross-compile and sign you will need to use another tool like `osslsigncode`.\n   */\n  signCommand?: string | null;\n}\n/**\n * The macOS configuration.\n */\nexport interface MacOsConfig {\n  /**\n   * MacOS frameworks that need to be packaged with the app.\n   *\n   * Each string can either be the name of a framework (without the `.framework` extension, e.g. `\"SDL2\"`), in which case we will search for that framework in the standard install locations (`~/Library/Frameworks/`, `/Library/Frameworks/`, and `/Network/Library/Frameworks/`), or a path to a specific framework bundle (e.g. `./data/frameworks/SDL2.framework`).  Note that this setting just makes cargo-packager copy the specified frameworks into the OS X app bundle (under `Foobar.app/Contents/Frameworks/`); you are still responsible for:\n   *\n   * - arranging for the compiled binary to link against those frameworks (e.g. by emitting lines like `cargo:rustc-link-lib=framework=SDL2` from your `build.rs` script)\n   *\n   * - embedding the correct rpath in your binary (e.g. by running `install_name_tool -add_rpath \"@executable_path/../Frameworks\" path/to/binary` after compiling)\n   */\n  frameworks?: string[] | null;\n  /**\n   * A version string indicating the minimum MacOS version that the packaged app supports (e.g. `\"10.11\"`). If you are using this config field, you may also want have your `build.rs` script emit `cargo:rustc-env=MACOSX_DEPLOYMENT_TARGET=10.11`.\n   */\n  minimumSystemVersion?: string | null;\n  /**\n   * The exception domain to use on the macOS .app package.\n   *\n   * This allows communication to the outside world e.g. a web server you're shipping.\n   */\n  exceptionDomain?: string | null;\n  /**\n   * Code signing identity.\n   *\n   * This is typically of the form: `\"Developer ID Application: TEAM_NAME (TEAM_ID)\"`.\n   */\n  signingIdentity?: string | null;\n  /**\n   * Provider short name for notarization.\n   */\n  providerShortName?: string | null;\n  /**\n   * Path to the entitlements.plist file.\n   */\n  entitlements?: string | null;\n  /**\n   * Path to the Info.plist file for the package.\n   */\n  infoPlistPath?: string | null;\n  /**\n   * Path to the embedded.provisionprofile file for the package.\n   */\n  embeddedProvisionprofilePath?: string | null;\n  /**\n   * Apps that need to be packaged within the app.\n   */\n  embeddedApps?: string[] | null;\n  /**\n   * Whether this is a background application. If true, the app will not appear in the Dock.\n   *\n   * Sets the `LSUIElement` flag in the macOS plist file.\n   */\n  backgroundApp?: boolean;\n}\n/**\n * Linux configuration\n */\nexport interface LinuxConfig {\n  /**\n   * Flag to indicate if desktop entry should be generated.\n   */\n  generateDesktopEntry?: boolean;\n}\n/**\n * The Linux Debian configuration.\n */\nexport interface DebianConfig {\n  /**\n   * The list of Debian dependencies.\n   */\n  depends?: Dependencies | null;\n  /**\n   * Path to a custom desktop file Handlebars template.\n   *\n   * Available variables: `categories`, `comment` (optional), `exec`, `icon` and `name`.\n   *\n   * Default file contents: ```text [Desktop Entry] Categories={{categories}} {{#if comment}} Comment={{comment}} {{/if}} Exec={{exec}} {{exec_arg}} Icon={{icon}} Name={{name}} Terminal=false Type=Application {{#if mime_type}} MimeType={{mime_type}} {{/if}} ```\n   *\n   * The `{{exec_arg}}` will be set to: * \"%F\", if at least one [Config::file_associations] was specified but no deep link protocols were given. * The \"%F\" arg means that your application can be invoked with multiple file paths. * \"%U\", if at least one [Config::deep_link_protocols] was specified. * The \"%U\" arg means that your application can be invoked with multiple URLs. * If both [Config::file_associations] and [Config::deep_link_protocols] were specified, the \"%U\" arg will be used, causing the file paths to be passed to your app as `file://` URLs. * An empty string \"\" (nothing) if neither are given. * This means that your application will never be invoked with any URLs or file paths.\n   *\n   * To specify a custom `exec_arg`, just use plaintext directly instead of `{{exec_arg}}`: ```text Exec={{exec}} %u ```\n   *\n   * See more here: <https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#exec-variables>.\n   */\n  desktopTemplate?: string | null;\n  /**\n   * Define the section in Debian Control file. See : <https://www.debian.org/doc/debian-policy/ch-archive.html#s-subsections>\n   */\n  section?: string | null;\n  /**\n   * Change the priority of the Debian Package. By default, it is set to `optional`. Recognized Priorities as of now are :  `required`, `important`, `standard`, `optional`, `extra`\n   */\n  priority?: string | null;\n  /**\n   * List of custom files to add to the deb package. Maps a dir/file to a dir/file inside the debian package.\n   */\n  files?: {\n    [k: string]: string;\n  } | null;\n  /**\n   * Name to use for the `Package` field in the Debian Control file. Defaults to [`Config::product_name`] converted to kebab-case.\n   */\n  packageName?: string | null;\n}\n/**\n * The Linux AppImage configuration.\n */\nexport interface AppImageConfig {\n  /**\n   * List of libs that exist in `/usr/lib*` to be include in the final AppImage. The libs will be searched for, using the command `find -L /usr/lib* -name <libname>`\n   */\n  libs?: string[] | null;\n  /**\n   * List of binary paths to include in the final AppImage. For example, if you want `xdg-open`, you'd specify `/usr/bin/xdg-open`\n   */\n  bins?: string[] | null;\n  /**\n   * List of custom files to add to the appimage package. Maps a dir/file to a dir/file inside the appimage package.\n   */\n  files?: {\n    [k: string]: string;\n  } | null;\n  /**\n   * A map of [`linuxdeploy`](https://github.com/linuxdeploy/linuxdeploy) plugin name and its URL to be downloaded and executed while packaing the appimage. For example, if you want to use the [`gtk`](https://raw.githubusercontent.com/linuxdeploy/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh) plugin, you'd specify `gtk` as the key and its url as the value.\n   */\n  linuxdeployPlugins?: {\n    [k: string]: string;\n  } | null;\n  /**\n   * List of globs of libraries to exclude from the final AppImage. For example, to exclude libnss3.so, you'd specify `libnss3*`\n   */\n  excludedLibs?: string[] | null;\n}\n/**\n * The Linux pacman configuration.\n */\nexport interface PacmanConfig {\n  /**\n   * List of custom files to add to the pacman package. Maps a dir/file to a dir/file inside the pacman package.\n   */\n  files?: {\n    [k: string]: string;\n  } | null;\n  /**\n   * List of softwares that must be installed for the app to build and run.\n   *\n   * See : <https://wiki.archlinux.org/title/PKGBUILD#depends>\n   */\n  depends?: Dependencies | null;\n  /**\n   * Additional packages that are provided by this app.\n   *\n   * See : <https://wiki.archlinux.org/title/PKGBUILD#provides>\n   */\n  provides?: string[] | null;\n  /**\n   * Packages that conflict or cause problems with the app. All these packages and packages providing this item will need to be removed\n   *\n   * See : <https://wiki.archlinux.org/title/PKGBUILD#conflicts>\n   */\n  conflicts?: string[] | null;\n  /**\n   * Only use if this app replaces some obsolete packages. For example, if you rename any package.\n   *\n   * See : <https://wiki.archlinux.org/title/PKGBUILD#replaces>\n   */\n  replaces?: string[] | null;\n  /**\n   * Source of the package to be stored at PKGBUILD. PKGBUILD is a bash script, so version can be referred as ${pkgver}\n   */\n  source?: string[] | null;\n}\n/**\n * The wix format configuration\n */\nexport interface WixConfig {\n  /**\n   * The app languages to build. See <https://docs.microsoft.com/en-us/windows/win32/msi/localizing-the-error-and-actiontext-tables>.\n   */\n  languages?: WixLanguage[] | null;\n  /**\n   * By default, the packager uses an internal template. This option allows you to define your own wix file.\n   */\n  template?: string | null;\n  /**\n   * List of merge modules to include in your installer. For example, if you want to include [C++ Redis merge modules]\n   *\n   * [C++ Redis merge modules]: https://wixtoolset.org/docs/v3/howtos/redistributables_and_install_checks/install_vcredist/\n   */\n  mergeModules?: string[] | null;\n  /**\n   * A list of paths to .wxs files with WiX fragments to use.\n   */\n  fragmentPaths?: string[] | null;\n  /**\n   * List of WiX fragments as strings. This is similar to `config.wix.fragments_paths` but is a string so you can define it inline in your config.\n   *\n   * ```text <?xml version=\"1.0\" encoding=\"utf-8\"?> <Wix xmlns=\"http://schemas.microsoft.com/wix/2006/wi\"> <Fragment> <CustomAction Id=\"OpenNotepad\" Directory=\"INSTALLDIR\" Execute=\"immediate\" ExeCommand=\"cmd.exe /c notepad.exe\" Return=\"check\" /> <InstallExecuteSequence> <Custom Action=\"OpenNotepad\" After=\"InstallInitialize\" /> </InstallExecuteSequence> </Fragment> </Wix> ```\n   */\n  fragments?: string[] | null;\n  /**\n   * The ComponentGroup element ids you want to reference from the fragments.\n   */\n  componentGroupRefs?: string[] | null;\n  /**\n   * The Component element ids you want to reference from the fragments.\n   */\n  componentRefs?: string[] | null;\n  /**\n   * The CustomAction element ids you want to reference from the fragments.\n   */\n  customActionRefs?: string[] | null;\n  /**\n   * The FeatureGroup element ids you want to reference from the fragments.\n   */\n  featureGroupRefs?: string[] | null;\n  /**\n   * The Feature element ids you want to reference from the fragments.\n   */\n  featureRefs?: string[] | null;\n  /**\n   * The Merge element ids you want to reference from the fragments.\n   */\n  mergeRefs?: string[] | null;\n  /**\n   * Path to a bitmap file to use as the installation user interface banner. This bitmap will appear at the top of all but the first page of the installer.\n   *\n   * The required dimensions are 493px × 58px.\n   */\n  bannerPath?: string | null;\n  /**\n   * Path to a bitmap file to use on the installation user interface dialogs. It is used on the welcome and completion dialogs. The required dimensions are 493px × 312px.\n   */\n  dialogImagePath?: string | null;\n  /**\n   * Enables FIPS compliant algorithms.\n   */\n  fipsCompliant?: boolean;\n}\n/**\n * The NSIS format configuration.\n */\nexport interface NsisConfig {\n  /**\n   * Set the compression algorithm used to compress files in the installer.\n   *\n   * See <https://nsis.sourceforge.io/Reference/SetCompressor>\n   */\n  compression?: NsisCompression | null;\n  /**\n   * A custom `.nsi` template to use.\n   *\n   * See the default template here <https://github.com/crabnebula-dev/cargo-packager/blob/main/crates/packager/src/package/nsis/installer.nsi>\n   */\n  template?: string | null;\n  /**\n   * Logic of an NSIS section that will be ran before the install section.\n   *\n   * See the available libraries, dlls and global variables here <https://github.com/crabnebula-dev/cargo-packager/blob/main/crates/packager/src/package/nsis/installer.nsi>\n   *\n   * ### Example ```toml [package.metadata.packager.nsis] preinstall-section = \"\"\" ; Setup custom messages LangString webview2AbortError ${LANG_ENGLISH} \"Failed to install WebView2! The app can't run without it. Try restarting the installer.\" LangString webview2DownloadError ${LANG_ARABIC} \"خطأ: فشل تنزيل WebView2 - $0\"\n   *\n   * Section PreInstall ; <section logic here> SectionEnd\n   *\n   * Section AnotherPreInstall ; <section logic here> SectionEnd \"\"\" ```\n   */\n  preinstallSection?: string | null;\n  /**\n   * The path to a bitmap file to display on the header of installers pages.\n   *\n   * The recommended dimensions are 150px x 57px.\n   */\n  headerImage?: string | null;\n  /**\n   * The path to a bitmap file for the Welcome page and the Finish page.\n   *\n   * The recommended dimensions are 164px x 314px.\n   */\n  sidebarImage?: string | null;\n  /**\n   * The path to an icon file used as the installer icon.\n   */\n  installerIcon?: string | null;\n  /**\n   * Whether the installation will be for all users or just the current user.\n   */\n  installMode?: NSISInstallerMode & string;\n  /**\n   * A list of installer languages. By default the OS language is used. If the OS language is not in the list of languages, the first language will be used. To allow the user to select the language, set `display_language_selector` to `true`.\n   *\n   * See <https://github.com/kichik/nsis/tree/9465c08046f00ccb6eda985abbdbf52c275c6c4d/Contrib/Language%20files> for the complete list of languages.\n   */\n  languages?: string[] | null;\n  /**\n   * An key-value pair where the key is the language and the value is the path to a custom `.nsi` file that holds the translated text for cargo-packager's custom messages.\n   *\n   * See <https://github.com/crabnebula-dev/cargo-packager/blob/main/crates/packager/src/nsis/languages/English.nsh> for an example `.nsi` file.\n   *\n   * **Note**: the key must be a valid NSIS language and it must be added to [`NsisConfig`]languages array,\n   */\n  customLanguageFiles?: {\n    [k: string]: string;\n  } | null;\n  /**\n   * Whether to display a language selector dialog before the installer and uninstaller windows are rendered or not. By default the OS language is selected, with a fallback to the first language in the `languages` array.\n   */\n  displayLanguageSelector?: boolean;\n  /**\n   * List of paths where your app stores data. This options tells the uninstaller to provide the user with an option (disabled by default) whether they want to rmeove your app data or keep it.\n   *\n   * The path should use a constant from <https://nsis.sourceforge.io/Docs/Chapter4.html#varconstant> in addition to `$IDENTIFIER`, `$PUBLISHER` and `$PRODUCTNAME`, for example, if you store your app data in `C:\\\\Users\\\\<user>\\\\AppData\\\\Local\\\\<your-company-name>\\\\<your-product-name>` you'd need to specify ```toml [package.metadata.packager.nsis] appdata-paths = [\"$LOCALAPPDATA/$PUBLISHER/$PRODUCTNAME\"] ```\n   */\n  appdataPaths?: string[] | null;\n}\n/**\n * The Apple Disk Image (.dmg) configuration.\n */\nexport interface DmgConfig {\n  /**\n   * Image to use as the background in dmg file. Accepted formats: `png`/`jpg`/`gif`.\n   */\n  background?: string | null;\n  /**\n   * Position of volume window on screen.\n   */\n  windowPosition?: Position | null;\n  /**\n   * Size of volume window.\n   */\n  windowSize?: Size | null;\n  /**\n   * Position of application file on window.\n   */\n  appPosition?: Position | null;\n  /**\n   * Position of application folder on window.\n   */\n  appFolderPosition?: Position | null;\n}\n/**\n * Position coordinates struct.\n */\nexport interface Position {\n  /**\n   * X coordinate.\n   */\n  x: number;\n  /**\n   * Y coordinate.\n   */\n  y: number;\n}\n/**\n * Size struct.\n */\nexport interface Size {\n  /**\n   * Width.\n   */\n  width: number;\n  /**\n   * Height.\n   */\n  height: number;\n}\n"
  },
  {
    "path": "bindings/packager/nodejs/src-ts/index.ts",
    "content": "import cargoPackager from \"../index\";\nimport runPlugins from \"./plugins\";\nimport merge from \"deepmerge\";\nimport type { Config } from \"./config\";\n\nlet tracingEnabled = false;\n\nexport interface Options {\n  verbosity?: number;\n}\n\nexport interface SigningConfig {\n  /** The private key to use for signing. */\n  privateKey: string;\n  /**\n   * The private key password.\n   *\n   * If `null`, user will be prompted to write a password.\n   * You can skip the prompt by specifying an empty string.\n   */\n  password?: string;\n}\n\nasync function packageApp(config: Config = {}, options?: Options) {\n  const conf = await runPlugins();\n\n  let packagerConfig = config;\n  if (conf) {\n    packagerConfig = merge(conf, config);\n  }\n\n  if (!tracingEnabled) {\n    cargoPackager.initTracingSubscriber(options?.verbosity ?? 0);\n    tracingEnabled = true;\n  }\n\n  cargoPackager.packageApp(JSON.stringify(packagerConfig));\n}\n\nasync function packageAndSignApp(\n  config: Config = {},\n  signingConfig: SigningConfig,\n  options?: Options,\n) {\n  const conf = await runPlugins();\n\n  let packagerConfig = config;\n  if (conf) {\n    packagerConfig = merge(conf, config);\n  }\n\n  if (!tracingEnabled) {\n    cargoPackager.initTracingSubscriber(options?.verbosity ?? 0);\n    tracingEnabled = true;\n  }\n\n  cargoPackager.packageAndSignApp(\n    JSON.stringify(packagerConfig),\n    JSON.stringify(signingConfig),\n  );\n}\n\nasync function cli(args: string[], binName: string) {\n  const config = await runPlugins();\n  if (config) {\n    args.push(\"--config\");\n    args.push(JSON.stringify(config));\n  }\n  cargoPackager.cli(args, binName);\n}\n\nfunction logError(error: string) {\n  cargoPackager.logError(error);\n}\n\nexport { cli, packageApp, packageAndSignApp, logError };\n"
  },
  {
    "path": "bindings/packager/nodejs/src-ts/plugins/electron/index.ts",
    "content": "import type { Config, Resource } from \"../../config\";\nimport type { PackageJson } from \"..\";\nimport fs from \"fs-extra\";\nimport path from \"path\";\nimport os from \"os\";\nimport { download as downloadElectron } from \"@electron/get\";\nimport extractZip from \"extract-zip\";\nimport { Pruner, isModule, normalizePath } from \"./prune\";\n\nexport default async function run(\n  appPath: string,\n  packageJson: PackageJson,\n): Promise<Partial<Config> | null> {\n  let electronPath;\n  try {\n    electronPath = require.resolve(\"electron\", {\n      paths: [appPath],\n    });\n  } catch (e) {\n    return null;\n  }\n\n  const userConfig = packageJson.packager || {};\n\n  const electronPackageJson = JSON.parse(\n    (\n      await fs.readFile(\n        path.resolve(path.dirname(electronPath), \"package.json\"),\n      )\n    ).toString(),\n  );\n\n  const zipPath = await downloadElectron(electronPackageJson.version);\n  const zipDir = await fs.mkdtemp(path.join(os.tmpdir(), \".packager-electron\"));\n  await extractZip(zipPath, {\n    dir: zipDir,\n  });\n\n  const platformName = os.platform();\n  let resources: Resource[] = [];\n  let frameworks: string[] = [];\n  let debianFiles: {\n    [k: string]: string;\n  } | null = null;\n  let binaryPath;\n\n  const appTempPath = await fs.mkdtemp(\n    path.join(os.tmpdir(), packageJson.name || \"app-temp\"),\n  );\n\n  const pruner = new Pruner(appPath, true);\n\n  const outDir = userConfig.outDir ? path.resolve(userConfig.outDir) : null;\n  const ignoredDirs = outDir && outDir !== process.cwd() ? [outDir] : [];\n\n  await fs.copy(appPath, appTempPath, {\n    filter: async (file: string) => {\n      const fullPath = path.resolve(file);\n\n      if (ignoredDirs.includes(fullPath)) {\n        return false;\n      }\n\n      let name = fullPath.split(appPath)[1];\n      if (path.sep === \"\\\\\") {\n        name = normalizePath(name);\n      }\n\n      if (name.startsWith(\"/node_modules/\")) {\n        if (await isModule(file)) {\n          return await pruner.pruneModule(name);\n        }\n      }\n\n      return true;\n    },\n  });\n\n  switch (platformName) {\n    case \"darwin\":\n      var binaryName: string =\n        userConfig.name ||\n        packageJson.productName ||\n        packageJson.name ||\n        \"Electron\";\n\n      var standaloneElectronPath = path.join(zipDir, \"Electron.app\");\n\n      const resourcesPath = path.join(\n        standaloneElectronPath,\n        \"Contents/Resources\",\n      );\n      resources = resources.concat(\n        (await fs.readdir(resourcesPath))\n          .filter((p) => p !== \"default_app.asar\")\n          .map((p) => path.join(resourcesPath, p)),\n      );\n\n      resources.push({\n        src: appTempPath,\n        target: \"app\",\n      });\n\n      const frameworksPath = path.join(\n        standaloneElectronPath,\n        \"Contents/Frameworks\",\n      );\n      frameworks = (await fs.readdir(frameworksPath)).map((p) =>\n        path.join(frameworksPath, p),\n      );\n\n      binaryPath = path.join(\n        standaloneElectronPath,\n        `Contents/MacOS/${binaryName}`,\n      );\n\n      // rename the electron binary\n      await fs.rename(\n        path.join(standaloneElectronPath, \"Contents/MacOS/Electron\"),\n        binaryPath,\n      );\n\n      break;\n    case \"win32\":\n      var binaryName: string =\n        userConfig.name ||\n        packageJson.productName ||\n        packageJson.name ||\n        \"Electron\";\n      binaryPath = path.join(zipDir, `${binaryName}.exe`);\n\n      resources = resources.concat(\n        (await fs.readdir(zipDir))\n          // resources only contains the default_app.asar so we ignore it\n          .filter((p) => p !== \"resources\" && p !== \"electron.exe\")\n          .map((p) => path.join(zipDir, p)),\n      );\n\n      // rename the electron binary\n      await fs.rename(path.join(zipDir, \"electron.exe\"), binaryPath);\n\n      resources.push({\n        src: appTempPath,\n        target: \"resources/app\",\n      });\n\n      break;\n    default:\n      var binaryName = toKebabCase(\n        userConfig.name ||\n          packageJson.productName ||\n          packageJson.name ||\n          \"Electron\",\n      );\n\n      // rename the electron binary\n      await fs.rename(\n        path.join(zipDir, \"electron\"),\n        path.join(zipDir, binaryName),\n      );\n\n      const electronFiles = await fs.readdir(zipDir);\n\n      const binTmpDir = await fs.mkdtemp(\n        path.join(os.tmpdir(), `${packageJson.name || \"app-temp\"}-bin`),\n      );\n      binaryPath = path.join(binTmpDir, binaryName);\n      await fs.writeFile(binaryPath, binaryScript(binaryName));\n      await fs.chmod(binaryPath, 0o755);\n\n      // make linuxdeploy happy\n      process.env.LD_LIBRARY_PATH = process.env.LD_LIBRARY_PATH\n        ? `${process.env.LD_LIBRARY_PATH}:${zipDir}`\n        : zipDir;\n      // electron needs everything at the same level :)\n      // resources only contains the default_app.asar so we ignore it\n      debianFiles = electronFiles\n        .filter((f) => f !== \"resources\")\n        .reduce(\n          (acc, file) => ({\n            ...acc,\n            [path.join(zipDir, file)]: `usr/lib/${binaryName}/${file}`,\n          }),\n          {},\n        );\n      debianFiles[appTempPath] = `usr/lib/${binaryName}/resources/app`;\n  }\n\n  return {\n    name: packageJson.name,\n    productName: packageJson.productName || packageJson.name,\n    version: packageJson.version,\n    resources,\n    macos: {\n      frameworks,\n    },\n    deb: {\n      files: debianFiles,\n    },\n    appimage: {\n      files: debianFiles,\n    },\n    binaries: [\n      {\n        path: binaryPath,\n        main: true,\n      },\n    ],\n  };\n}\n\nconst toKebabCase = (str: string) =>\n  str\n    .split(/\\.?(?=[A-Z])/)\n    .join(\"-\")\n    .toLowerCase();\n\nfunction binaryScript(binaryName: string): string {\n  return `#!/usr/bin/env sh\n\nfull_path=$(realpath $0)\nbin_dir_path=$(dirname $full_path)\nusr_dir_path=$(dirname $bin_dir_path)\necho $usr_dir_path\n$usr_dir_path/lib/${binaryName}/${binaryName}\n`;\n}\n"
  },
  {
    "path": "bindings/packager/nodejs/src-ts/plugins/electron/prune.ts",
    "content": "// from https://github.com/electron/electron-packager/blob/741f3c349e7f9e11e5ae14593a3efa79d312dc4d/src/prune.js\n\nimport { DestroyerOfModules, ModuleMap, DepType, Module } from \"galactus\";\nimport fs from \"fs\";\nimport path from \"path\";\n\nconst ELECTRON_MODULES = [\n  \"electron\",\n  \"electron-nightly\",\n  \"electron-prebuilt\",\n  \"electron-prebuilt-compile\",\n];\n\nexport function normalizePath(path: string): string {\n  return path.replace(/\\\\/g, \"/\");\n}\n\nclass Pruner {\n  baseDir: string;\n  quiet: boolean;\n  galactus: DestroyerOfModules;\n  walkedTree: boolean;\n  modules?: Set<string>;\n\n  constructor(dir: string, quiet: boolean) {\n    this.baseDir = normalizePath(dir);\n    this.quiet = quiet;\n    this.galactus = new DestroyerOfModules({\n      rootDirectory: dir,\n      shouldKeepModuleTest: (module, isDevDep) =>\n        this.shouldKeepModule(module, isDevDep),\n    });\n    this.walkedTree = false;\n  }\n\n  setModules(moduleMap: ModuleMap) {\n    const modulePaths = Array.from(moduleMap.keys()).map(\n      (modulePath) => `/${normalizePath(modulePath)}`,\n    );\n    this.modules = new Set(modulePaths);\n    this.walkedTree = true;\n  }\n\n  async pruneModule(name: string) {\n    if (this.walkedTree) {\n      return this.isProductionModule(name);\n    } else {\n      const moduleMap = await this.galactus.collectKeptModules({\n        relativePaths: true,\n      });\n      this.setModules(moduleMap);\n      return this.isProductionModule(name);\n    }\n  }\n\n  shouldKeepModule(module: Module, isDevDep: boolean) {\n    if (isDevDep || module.depType === DepType.ROOT) {\n      return false;\n    }\n\n    if (ELECTRON_MODULES.includes(module.name)) {\n      if (!this.quiet)\n        console.warn(\n          `Found '${module.name}' but not as a devDependency, pruning anyway`,\n        );\n      return false;\n    }\n\n    return true;\n  }\n\n  isProductionModule(name: string): boolean {\n    return this.modules?.has(name) ?? false;\n  }\n}\n\nfunction isNodeModuleFolder(pathToCheck: string) {\n  return (\n    path.basename(path.dirname(pathToCheck)) === \"node_modules\" ||\n    (path.basename(path.dirname(pathToCheck)).startsWith(\"@\") &&\n      path.basename(path.resolve(pathToCheck, `..${path.sep}..`)) ===\n        \"node_modules\")\n  );\n}\n\nexport async function isModule(pathToCheck: string) {\n  return (\n    (await fs.existsSync(path.join(pathToCheck, \"package.json\"))) &&\n    isNodeModuleFolder(pathToCheck)\n  );\n}\n\nexport { Pruner };\n"
  },
  {
    "path": "bindings/packager/nodejs/src-ts/plugins/index.ts",
    "content": "import path from \"path\";\nimport fs from \"fs-extra\";\nimport type { Config } from \"../config\";\nimport electron from \"./electron\";\nimport merge from \"deepmerge\";\n\nexport interface PackageJson {\n  name?: string;\n  productName?: string;\n  version?: string;\n  packager: Partial<Config> | null | undefined;\n}\n\nfunction getPackageJsonPath(): string | null {\n  let appDir = process.cwd();\n\n  while (appDir.length && appDir[appDir.length - 1] !== path.sep) {\n    const filepath = path.join(appDir, \"package.json\");\n    if (fs.existsSync(filepath)) {\n      return filepath;\n    }\n\n    appDir = path.normalize(path.join(appDir, \"..\"));\n  }\n\n  return null;\n}\n\nexport default async function run(): Promise<Partial<Config> | null> {\n  const packageJsonPath = getPackageJsonPath();\n\n  if (packageJsonPath === null) {\n    return null;\n  }\n\n  const packageJson = JSON.parse(\n    (await fs.readFile(packageJsonPath)).toString(),\n  ) as PackageJson;\n\n  let config = packageJson.packager || null;\n\n  try {\n    const electronConfig = await electron(\n      path.dirname(packageJsonPath),\n      packageJson,\n    );\n\n    if (electronConfig) {\n      config = config ? merge(electronConfig, config) : electronConfig;\n    }\n\n    if (config?.outDir) {\n      await fs.ensureDir(config.outDir);\n    }\n\n    return config;\n  } catch {\n    return null;\n  }\n}\n"
  },
  {
    "path": "bindings/packager/nodejs/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es2016\",\n    \"lib\": [\"es6\"],\n    \"module\": \"commonjs\",\n    \"rootDir\": \"src-ts\",\n    \"resolveJsonModule\": true,\n    \"allowJs\": true,\n    \"outDir\": \"build\",\n    \"declaration\": true,\n    \"declarationDir\": \"build\",\n    \"esModuleInterop\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"strict\": true,\n    \"noImplicitAny\": true,\n    \"skipLibCheck\": true\n  },\n  \"exclude\": [\n    \"build/\",\n    \"__test__/\",\n    \"index.js\",\n    \"packager.js\",\n    \"generate-config-type.js\"\n  ]\n}\n"
  },
  {
    "path": "bindings/resource-resolver/nodejs/.cargo/config.toml",
    "content": "[target.aarch64-unknown-linux-musl]\nlinker = \"aarch64-linux-musl-gcc\"\nrustflags = [\"-C\", \"target-feature=-crt-static\"]"
  },
  {
    "path": "bindings/resource-resolver/nodejs/.npmignore",
    "content": "target\nCargo.lock\n.cargo\n.github\nnpm\n.eslintrc\n.prettierignore\nrustfmt.toml\nyarn.lock\n*.node\n.yarn\n__test__\nrenovate.json\n"
  },
  {
    "path": "bindings/resource-resolver/nodejs/CHANGELOG.md",
    "content": "# Changelog\n\n## \\[0.1.2]\n\n### Dependencies\n\n- Upgraded to `cargo-packager-resource-resolver@0.1.2`\n- Upgraded to `cargo-packager-utils@0.1.1`\n\n## \\[0.1.1]\n\n### Dependencies\n\n- Upgraded to `cargo-packager-resource-resolver@0.1.1`\n\n## \\[0.1.0]\n\n- [`cd0242b`](https://www.github.com/crabnebula-dev/cargo-packager/commit/cd0242b8a41b2f7ecb78dfbae04b3a2e1c72c931) Initial Release.\n\n### Dependencies\n\n- Upgraded to `cargo-packager-utils@0.1.0`\n- Upgraded to `cargo-packager-resource-resolver@0.1.0`\n"
  },
  {
    "path": "bindings/resource-resolver/nodejs/Cargo.toml",
    "content": "[package]\nname = \"crabnebula_packager_resource_resolver\"\nversion = \"0.0.0\"\npublish = false\nedition = { workspace = true }\nlicense = { workspace = true }\nrepository = { workspace = true }\n\n[lib]\ncrate-type = [\"cdylib\"]\n\n[dependencies]\n# Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix\nnapi = { workspace = true, features = [\"napi4\"] }\nnapi-derive = { workspace = true }\ncargo-packager-resource-resolver = { path = \"../../../crates/resource-resolver\" }\ndunce.workspace = true\n\n[build-dependencies]\nnapi-build = { workspace = true }\n"
  },
  {
    "path": "bindings/resource-resolver/nodejs/README.md",
    "content": "# @crabnebula/packager-resource-resolver\n\nResource resolver for apps that was packaged by [`@crabnebula/packager`](https://www.npmjs.com/package/@crabnebula/packager).\n\nIt resolves the root path which contains resources, which was set using the `resources` field of [cargo packager configuration](https://docs.rs/cargo-packager/latest/cargo_packager/config/struct.Config.html).\n\n## Get the resource path\n\n```ts\nimport {\n  resourcesDir,\n  PackageFormat,\n} from \"@crabnebula/packager-resource-resolver\";\n\nconst dir = resourcesDir(PackageFormat.Nsis);\n```\n\n## Licenses\n\nMIT or MIT/Apache 2.0 where applicable.\n"
  },
  {
    "path": "bindings/resource-resolver/nodejs/__test__/index.spec.mjs",
    "content": "import test from \"ava\";\n\nimport { resourcesDir, PackageFormat } from \"../index.js\";\n\ntest(\"resolve resource directory\", async (t) => {\n  const dir = resourcesDir(PackageFormat.Nsis);\n  t.is(typeof dir, \"string\");\n});\n"
  },
  {
    "path": "bindings/resource-resolver/nodejs/build.rs",
    "content": "extern crate napi_build;\n\nfn main() {\n    napi_build::setup();\n}\n"
  },
  {
    "path": "bindings/resource-resolver/nodejs/fix-types.js",
    "content": "// Due to a NAPI-rs bug? still unconfirmed\n// index.d.ts will contain a duplicate definition of `PackageFromat` enum\n// and we only need the second definition.\n// This script sole purpose is to remove the extra definition.\n\nconst { readFileSync, writeFileSync } = require(\"fs\");\nconst { join } = require(\"path\");\n\nconst typesPath = join(__dirname, \"index.d.ts\");\nconst types = readFileSync(typesPath, \"utf8\");\n\nlet out = \"\";\nlet inRemoval = false;\nfor (const line of types.split(\"\\n\")) {\n  if (inRemoval) {\n    if (line === \"}\") inRemoval = false;\n    continue;\n  }\n\n  const startOfRemoval = line.startsWith(\n    \"/** Types of supported packages by [`cargo-packager`](https://docs.rs/cargo-packager). */\",\n  );\n  if (startOfRemoval) {\n    inRemoval = true;\n    continue;\n  }\n\n  out += line + \"\\n\";\n}\n\nwriteFileSync(typesPath, out);\n\nconst problematicCode = `const { PackageFormat, PackageFormat, resourcesDir } = nativeBinding\n\nmodule.exports.PackageFormat = PackageFormat\nmodule.exports.PackageFormat = PackageFormat\nmodule.exports.resourcesDir = resourcesDir`;\nconst correctCode = `const { PackageFormat, resourcesDir } = nativeBinding\n\nmodule.exports.PackageFormat = PackageFormat\nmodule.exports.resourcesDir = resourcesDir`;\n\nconst indexPath = join(__dirname, \"index.js\");\nconst indexContent = readFileSync(indexPath, \"utf8\");\n\nwriteFileSync(indexPath, indexContent.replace(problematicCode, correctCode));\n"
  },
  {
    "path": "bindings/resource-resolver/nodejs/index.d.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n\n/* auto-generated by NAPI-RS */\n\n/** Types of supported packages by [`@crabnebula/packager`](https://www.npmjs.com/package/@crabnebula/packager) */\nexport const enum PackageFormat {\n  /** The macOS application bundle (.app). */\n  App = \"App\",\n  /** The macOS DMG package (.dmg). */\n  Dmg = \"Dmg\",\n  /** The Microsoft Software Installer (.msi) through WiX Toolset. */\n  Wix = \"Wix\",\n  /** The NSIS installer (.exe). */\n  Nsis = \"Nsis\",\n  /** The Linux Debian package (.deb). */\n  Deb = \"Deb\",\n  /** The Linux AppImage package (.AppImage). */\n  AppImage = \"AppImage\",\n}\n/** Retrieve the resource path of your app, packaged with cargo packager. */\nexport function resourcesDir(packageFormat: PackageFormat): string;\n"
  },
  {
    "path": "bindings/resource-resolver/nodejs/index.js",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/* prettier-ignore */\n\n/* auto-generated by NAPI-RS */\n\nconst { existsSync, readFileSync } = require('fs')\nconst { join } = require(\"path\");\n\nconst { platform, arch } = process;\n\nlet nativeBinding = null;\nlet localFileExisted = false;\nlet loadError = null;\n\nfunction isMusl() {\n  // For Node 10\n  if (!process.report || typeof process.report.getReport !== \"function\") {\n    try {\n      const lddPath = require(\"child_process\")\n        .execSync(\"which ldd\")\n        .toString()\n        .trim();\n      return readFileSync(lddPath, \"utf8\").includes(\"musl\");\n    } catch (e) {\n      return true;\n    }\n  } else {\n    const { glibcVersionRuntime } = process.report.getReport().header;\n    return !glibcVersionRuntime;\n  }\n}\n\nswitch (platform) {\n  case \"android\":\n    switch (arch) {\n      case \"arm64\":\n        localFileExisted = existsSync(\n          join(__dirname, \"packager-resource-resolver.android-arm64.node\"),\n        );\n        try {\n          if (localFileExisted) {\n            nativeBinding = require(\"./packager-resource-resolver.android-arm64.node\");\n          } else {\n            nativeBinding = require(\"@crabnebula/packager-resource-resolver-android-arm64\");\n          }\n        } catch (e) {\n          loadError = e;\n        }\n        break;\n      case \"arm\":\n        localFileExisted = existsSync(\n          join(__dirname, \"packager-resource-resolver.android-arm-eabi.node\"),\n        );\n        try {\n          if (localFileExisted) {\n            nativeBinding = require(\"./packager-resource-resolver.android-arm-eabi.node\");\n          } else {\n            nativeBinding = require(\"@crabnebula/packager-resource-resolver-android-arm-eabi\");\n          }\n        } catch (e) {\n          loadError = e;\n        }\n        break;\n      default:\n        throw new Error(`Unsupported architecture on Android ${arch}`);\n    }\n    break;\n  case \"win32\":\n    switch (arch) {\n      case \"x64\":\n        localFileExisted = existsSync(\n          join(__dirname, \"packager-resource-resolver.win32-x64-msvc.node\"),\n        );\n        try {\n          if (localFileExisted) {\n            nativeBinding = require(\"./packager-resource-resolver.win32-x64-msvc.node\");\n          } else {\n            nativeBinding = require(\"@crabnebula/packager-resource-resolver-win32-x64-msvc\");\n          }\n        } catch (e) {\n          loadError = e;\n        }\n        break;\n      case \"ia32\":\n        localFileExisted = existsSync(\n          join(__dirname, \"packager-resource-resolver.win32-ia32-msvc.node\"),\n        );\n        try {\n          if (localFileExisted) {\n            nativeBinding = require(\"./packager-resource-resolver.win32-ia32-msvc.node\");\n          } else {\n            nativeBinding = require(\"@crabnebula/packager-resource-resolver-win32-ia32-msvc\");\n          }\n        } catch (e) {\n          loadError = e;\n        }\n        break;\n      case \"arm64\":\n        localFileExisted = existsSync(\n          join(__dirname, \"packager-resource-resolver.win32-arm64-msvc.node\"),\n        );\n        try {\n          if (localFileExisted) {\n            nativeBinding = require(\"./packager-resource-resolver.win32-arm64-msvc.node\");\n          } else {\n            nativeBinding = require(\"@crabnebula/packager-resource-resolver-win32-arm64-msvc\");\n          }\n        } catch (e) {\n          loadError = e;\n        }\n        break;\n      default:\n        throw new Error(`Unsupported architecture on Windows: ${arch}`);\n    }\n    break;\n  case \"darwin\":\n    localFileExisted = existsSync(\n      join(__dirname, \"packager-resource-resolver.darwin-universal.node\"),\n    );\n    try {\n      if (localFileExisted) {\n        nativeBinding = require(\"./packager-resource-resolver.darwin-universal.node\");\n      } else {\n        nativeBinding = require(\"@crabnebula/packager-resource-resolver-darwin-universal\");\n      }\n      break;\n    } catch {}\n    switch (arch) {\n      case \"x64\":\n        localFileExisted = existsSync(\n          join(__dirname, \"packager-resource-resolver.darwin-x64.node\"),\n        );\n        try {\n          if (localFileExisted) {\n            nativeBinding = require(\"./packager-resource-resolver.darwin-x64.node\");\n          } else {\n            nativeBinding = require(\"@crabnebula/packager-resource-resolver-darwin-x64\");\n          }\n        } catch (e) {\n          loadError = e;\n        }\n        break;\n      case \"arm64\":\n        localFileExisted = existsSync(\n          join(__dirname, \"packager-resource-resolver.darwin-arm64.node\"),\n        );\n        try {\n          if (localFileExisted) {\n            nativeBinding = require(\"./packager-resource-resolver.darwin-arm64.node\");\n          } else {\n            nativeBinding = require(\"@crabnebula/packager-resource-resolver-darwin-arm64\");\n          }\n        } catch (e) {\n          loadError = e;\n        }\n        break;\n      default:\n        throw new Error(`Unsupported architecture on macOS: ${arch}`);\n    }\n    break;\n  case \"freebsd\":\n    if (arch !== \"x64\") {\n      throw new Error(`Unsupported architecture on FreeBSD: ${arch}`);\n    }\n    localFileExisted = existsSync(\n      join(__dirname, \"packager-resource-resolver.freebsd-x64.node\"),\n    );\n    try {\n      if (localFileExisted) {\n        nativeBinding = require(\"./packager-resource-resolver.freebsd-x64.node\");\n      } else {\n        nativeBinding = require(\"@crabnebula/packager-resource-resolver-freebsd-x64\");\n      }\n    } catch (e) {\n      loadError = e;\n    }\n    break;\n  case \"linux\":\n    switch (arch) {\n      case \"x64\":\n        if (isMusl()) {\n          localFileExisted = existsSync(\n            join(__dirname, \"packager-resource-resolver.linux-x64-musl.node\"),\n          );\n          try {\n            if (localFileExisted) {\n              nativeBinding = require(\"./packager-resource-resolver.linux-x64-musl.node\");\n            } else {\n              nativeBinding = require(\"@crabnebula/packager-resource-resolver-linux-x64-musl\");\n            }\n          } catch (e) {\n            loadError = e;\n          }\n        } else {\n          localFileExisted = existsSync(\n            join(__dirname, \"packager-resource-resolver.linux-x64-gnu.node\"),\n          );\n          try {\n            if (localFileExisted) {\n              nativeBinding = require(\"./packager-resource-resolver.linux-x64-gnu.node\");\n            } else {\n              nativeBinding = require(\"@crabnebula/packager-resource-resolver-linux-x64-gnu\");\n            }\n          } catch (e) {\n            loadError = e;\n          }\n        }\n        break;\n      case \"arm64\":\n        if (isMusl()) {\n          localFileExisted = existsSync(\n            join(__dirname, \"packager-resource-resolver.linux-arm64-musl.node\"),\n          );\n          try {\n            if (localFileExisted) {\n              nativeBinding = require(\"./packager-resource-resolver.linux-arm64-musl.node\");\n            } else {\n              nativeBinding = require(\"@crabnebula/packager-resource-resolver-linux-arm64-musl\");\n            }\n          } catch (e) {\n            loadError = e;\n          }\n        } else {\n          localFileExisted = existsSync(\n            join(__dirname, \"packager-resource-resolver.linux-arm64-gnu.node\"),\n          );\n          try {\n            if (localFileExisted) {\n              nativeBinding = require(\"./packager-resource-resolver.linux-arm64-gnu.node\");\n            } else {\n              nativeBinding = require(\"@crabnebula/packager-resource-resolver-linux-arm64-gnu\");\n            }\n          } catch (e) {\n            loadError = e;\n          }\n        }\n        break;\n      case \"arm\":\n        localFileExisted = existsSync(\n          join(\n            __dirname,\n            \"packager-resource-resolver.linux-arm-gnueabihf.node\",\n          ),\n        );\n        try {\n          if (localFileExisted) {\n            nativeBinding = require(\"./packager-resource-resolver.linux-arm-gnueabihf.node\");\n          } else {\n            nativeBinding = require(\"@crabnebula/packager-resource-resolver-linux-arm-gnueabihf\");\n          }\n        } catch (e) {\n          loadError = e;\n        }\n        break;\n      default:\n        throw new Error(`Unsupported architecture on Linux: ${arch}`);\n    }\n    break;\n  default:\n    throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`);\n}\n\nif (!nativeBinding) {\n  if (loadError) {\n    throw loadError;\n  }\n  throw new Error(`Failed to load native binding`);\n}\n\nconst { PackageFormat, resourcesDir } = nativeBinding;\n\nmodule.exports.PackageFormat = PackageFormat;\nmodule.exports.resourcesDir = resourcesDir;\n"
  },
  {
    "path": "bindings/resource-resolver/nodejs/npm/darwin-arm64/README.md",
    "content": "# `@crabnebula/packager-resource-resolver-darwin-arm64`\n\nThis is the **aarch64-apple-darwin** binary for `@crabnebula/packager-resource-resolver`\n"
  },
  {
    "path": "bindings/resource-resolver/nodejs/npm/darwin-arm64/package.json",
    "content": "{\n  \"name\": \"@crabnebula/packager-resource-resolver-darwin-arm64\",\n  \"version\": \"0.0.0\",\n  \"os\": [\n    \"darwin\"\n  ],\n  \"cpu\": [\n    \"arm64\"\n  ],\n  \"main\": \"packager-resource-resolver.darwin-arm64.node\",\n  \"files\": [\n    \"packager-resource-resolver.darwin-arm64.node\"\n  ],\n  \"license\": \"MIT\",\n  \"engines\": {\n    \"node\": \">= 10\"\n  }\n}\n"
  },
  {
    "path": "bindings/resource-resolver/nodejs/npm/darwin-x64/README.md",
    "content": "# `@crabnebula/packager-resource-resolver-darwin-x64`\n\nThis is the **x86_64-apple-darwin** binary for `@crabnebula/packager-resource-resolver`\n"
  },
  {
    "path": "bindings/resource-resolver/nodejs/npm/darwin-x64/package.json",
    "content": "{\n  \"name\": \"@crabnebula/packager-resource-resolver-darwin-x64\",\n  \"version\": \"0.0.0\",\n  \"os\": [\n    \"darwin\"\n  ],\n  \"cpu\": [\n    \"x64\"\n  ],\n  \"main\": \"packager-resource-resolver.darwin-x64.node\",\n  \"files\": [\n    \"packager-resource-resolver.darwin-x64.node\"\n  ],\n  \"license\": \"MIT\",\n  \"engines\": {\n    \"node\": \">= 10\"\n  }\n}\n"
  },
  {
    "path": "bindings/resource-resolver/nodejs/npm/linux-arm-gnueabihf/README.md",
    "content": "# `@crabnebula/packager-resource-resolver-linux-arm-gnueabihf`\n\nThis is the **armv7-unknown-linux-gnueabihf** binary for `@crabnebula/packager-resource-resolver`\n"
  },
  {
    "path": "bindings/resource-resolver/nodejs/npm/linux-arm-gnueabihf/package.json",
    "content": "{\n  \"name\": \"@crabnebula/packager-resource-resolver-linux-arm-gnueabihf\",\n  \"version\": \"0.0.0\",\n  \"os\": [\n    \"linux\"\n  ],\n  \"cpu\": [\n    \"arm\"\n  ],\n  \"main\": \"packager-resource-resolver.linux-arm-gnueabihf.node\",\n  \"files\": [\n    \"packager-resource-resolver.linux-arm-gnueabihf.node\"\n  ],\n  \"license\": \"MIT\",\n  \"engines\": {\n    \"node\": \">= 10\"\n  }\n}\n"
  },
  {
    "path": "bindings/resource-resolver/nodejs/npm/linux-arm64-gnu/README.md",
    "content": "# `@crabnebula/packager-resource-resolver-linux-arm64-gnu`\n\nThis is the **aarch64-unknown-linux-gnu** binary for `@crabnebula/packager-resource-resolver`\n"
  },
  {
    "path": "bindings/resource-resolver/nodejs/npm/linux-arm64-gnu/package.json",
    "content": "{\n  \"name\": \"@crabnebula/packager-resource-resolver-linux-arm64-gnu\",\n  \"version\": \"0.0.0\",\n  \"os\": [\n    \"linux\"\n  ],\n  \"cpu\": [\n    \"arm64\"\n  ],\n  \"main\": \"packager-resource-resolver.linux-arm64-gnu.node\",\n  \"files\": [\n    \"packager-resource-resolver.linux-arm64-gnu.node\"\n  ],\n  \"license\": \"MIT\",\n  \"engines\": {\n    \"node\": \">= 10\"\n  },\n  \"libc\": [\n    \"glibc\"\n  ]\n}\n"
  },
  {
    "path": "bindings/resource-resolver/nodejs/npm/linux-arm64-musl/README.md",
    "content": "# `@crabnebula/packager-resource-resolver-linux-arm64-musl`\n\nThis is the **aarch64-unknown-linux-musl** binary for `@crabnebula/packager-resource-resolver`\n"
  },
  {
    "path": "bindings/resource-resolver/nodejs/npm/linux-arm64-musl/package.json",
    "content": "{\n  \"name\": \"@crabnebula/packager-resource-resolver-linux-arm64-musl\",\n  \"version\": \"0.0.0\",\n  \"os\": [\n    \"linux\"\n  ],\n  \"cpu\": [\n    \"arm64\"\n  ],\n  \"main\": \"packager-resource-resolver.linux-arm64-musl.node\",\n  \"files\": [\n    \"packager-resource-resolver.linux-arm64-musl.node\"\n  ],\n  \"license\": \"MIT\",\n  \"engines\": {\n    \"node\": \">= 10\"\n  },\n  \"libc\": [\n    \"musl\"\n  ]\n}\n"
  },
  {
    "path": "bindings/resource-resolver/nodejs/npm/linux-x64-gnu/README.md",
    "content": "# `@crabnebula/packager-resource-resolver-linux-x64-gnu`\n\nThis is the **x86_64-unknown-linux-gnu** binary for `@crabnebula/packager-resource-resolver`\n"
  },
  {
    "path": "bindings/resource-resolver/nodejs/npm/linux-x64-gnu/package.json",
    "content": "{\n  \"name\": \"@crabnebula/packager-resource-resolver-linux-x64-gnu\",\n  \"version\": \"0.0.0\",\n  \"os\": [\n    \"linux\"\n  ],\n  \"cpu\": [\n    \"x64\"\n  ],\n  \"main\": \"packager-resource-resolver.linux-x64-gnu.node\",\n  \"files\": [\n    \"packager-resource-resolver.linux-x64-gnu.node\"\n  ],\n  \"license\": \"MIT\",\n  \"engines\": {\n    \"node\": \">= 10\"\n  },\n  \"libc\": [\n    \"glibc\"\n  ]\n}\n"
  },
  {
    "path": "bindings/resource-resolver/nodejs/npm/linux-x64-musl/README.md",
    "content": "# `@crabnebula/packager-resource-resolver-linux-x64-musl`\n\nThis is the **x86_64-unknown-linux-musl** binary for `@crabnebula/packager-resource-resolver`\n"
  },
  {
    "path": "bindings/resource-resolver/nodejs/npm/linux-x64-musl/package.json",
    "content": "{\n  \"name\": \"@crabnebula/packager-resource-resolver-linux-x64-musl\",\n  \"version\": \"0.0.0\",\n  \"os\": [\n    \"linux\"\n  ],\n  \"cpu\": [\n    \"x64\"\n  ],\n  \"main\": \"packager-resource-resolver.linux-x64-musl.node\",\n  \"files\": [\n    \"packager-resource-resolver.linux-x64-musl.node\"\n  ],\n  \"license\": \"MIT\",\n  \"engines\": {\n    \"node\": \">= 10\"\n  },\n  \"libc\": [\n    \"musl\"\n  ]\n}\n"
  },
  {
    "path": "bindings/resource-resolver/nodejs/npm/win32-arm64-msvc/README.md",
    "content": "# `@crabnebula/packager-resource-resolver-win32-arm64-msvc`\n\nThis is the **aarch64-pc-windows-msvc** binary for `@crabnebula/packager-resource-resolver`\n"
  },
  {
    "path": "bindings/resource-resolver/nodejs/npm/win32-arm64-msvc/package.json",
    "content": "{\n  \"name\": \"@crabnebula/packager-resource-resolver-win32-arm64-msvc\",\n  \"version\": \"0.0.0\",\n  \"os\": [\n    \"win32\"\n  ],\n  \"cpu\": [\n    \"arm64\"\n  ],\n  \"main\": \"packager-resource-resolver.win32-arm64-msvc.node\",\n  \"files\": [\n    \"packager-resource-resolver.win32-arm64-msvc.node\"\n  ],\n  \"license\": \"MIT\",\n  \"engines\": {\n    \"node\": \">= 10\"\n  }\n}\n"
  },
  {
    "path": "bindings/resource-resolver/nodejs/npm/win32-ia32-msvc/README.md",
    "content": "# `@crabnebula/packager-resource-resolver-win32-ia32-msvc`\n\nThis is the **i686-pc-windows-msvc** binary for `@crabnebula/packager-resource-resolver`\n"
  },
  {
    "path": "bindings/resource-resolver/nodejs/npm/win32-ia32-msvc/package.json",
    "content": "{\n  \"name\": \"@crabnebula/packager-resource-resolver-win32-ia32-msvc\",\n  \"version\": \"0.0.0\",\n  \"os\": [\n    \"win32\"\n  ],\n  \"cpu\": [\n    \"ia32\"\n  ],\n  \"main\": \"packager-resource-resolver.win32-ia32-msvc.node\",\n  \"files\": [\n    \"packager-resource-resolver.win32-ia32-msvc.node\"\n  ],\n  \"license\": \"MIT\",\n  \"engines\": {\n    \"node\": \">= 10\"\n  }\n}\n"
  },
  {
    "path": "bindings/resource-resolver/nodejs/npm/win32-x64-msvc/README.md",
    "content": "# `@crabnebula/packager-resource-resolver-win32-x64-msvc`\n\nThis is the **x86_64-pc-windows-msvc** binary for `@crabnebula/packager-resource-resolver`\n"
  },
  {
    "path": "bindings/resource-resolver/nodejs/npm/win32-x64-msvc/package.json",
    "content": "{\n  \"name\": \"@crabnebula/packager-resource-resolver-win32-x64-msvc\",\n  \"version\": \"0.0.0\",\n  \"os\": [\n    \"win32\"\n  ],\n  \"cpu\": [\n    \"x64\"\n  ],\n  \"main\": \"packager-resource-resolver.win32-x64-msvc.node\",\n  \"files\": [\n    \"packager-resource-resolver.win32-x64-msvc.node\"\n  ],\n  \"license\": \"MIT\",\n  \"engines\": {\n    \"node\": \">= 10\"\n  }\n}\n"
  },
  {
    "path": "bindings/resource-resolver/nodejs/package.json",
    "content": "{\n  \"name\": \"@crabnebula/packager-resource-resolver\",\n  \"version\": \"0.1.2\",\n  \"main\": \"./index.js\",\n  \"types\": \"./index.d.ts\",\n  \"napi\": {\n    \"name\": \"packager-resource-resolver\",\n    \"triples\": {\n      \"additional\": [\n        \"aarch64-apple-darwin\",\n        \"aarch64-unknown-linux-gnu\",\n        \"aarch64-unknown-linux-musl\",\n        \"aarch64-pc-windows-msvc\",\n        \"armv7-unknown-linux-gnueabihf\",\n        \"x86_64-unknown-linux-musl\",\n        \"i686-pc-windows-msvc\"\n      ]\n    }\n  },\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"artifacts\": \"napi artifacts\",\n    \"build\": \"napi build --platform --profile release-size-optimized\",\n    \"postbuild\": \"node ./fix-types.js\",\n    \"build:debug\": \"napi build --platform\",\n    \"prepublishOnly\": \"napi prepublish -t npm --gh-release-id $RELEASE_ID\",\n    \"test\": \"ava --no-worker-threads --timeout 30m\",\n    \"universal\": \"napi universal\",\n    \"version\": \"napi version\"\n  },\n  \"devDependencies\": {\n    \"@napi-rs/cli\": \"^2.18.1\",\n    \"@types/node\": \"^20.8.10\",\n    \"ava\": \"^6.0.0\"\n  },\n  \"ava\": {\n    \"timeout\": \"3m\"\n  },\n  \"engines\": {\n    \"node\": \">= 10\"\n  }\n}\n"
  },
  {
    "path": "bindings/resource-resolver/nodejs/src/lib.rs",
    "content": "use napi::{Result, Status};\n\nuse cargo_packager_resource_resolver::PackageFormat as ResolverPackageFormat;\n\n/// Types of supported packages by [`@crabnebula/packager`](https://www.npmjs.com/package/@crabnebula/packager)\n#[derive(Debug, Eq, PartialEq)]\n#[napi_derive::napi(string_enum)]\npub enum PackageFormat {\n    /// The macOS application bundle (.app).\n    App,\n    /// The macOS DMG package (.dmg).\n    Dmg,\n    /// The Microsoft Software Installer (.msi) through WiX Toolset.\n    Wix,\n    /// The NSIS installer (.exe).\n    Nsis,\n    /// The Linux Debian package (.deb).\n    Deb,\n    /// The Linux AppImage package (.AppImage).\n    AppImage,\n}\n\nimpl From<PackageFormat> for ResolverPackageFormat {\n    fn from(value: PackageFormat) -> Self {\n        match value {\n            PackageFormat::App => ResolverPackageFormat::App,\n            PackageFormat::Dmg => ResolverPackageFormat::Dmg,\n            PackageFormat::Wix => ResolverPackageFormat::Wix,\n            PackageFormat::Nsis => ResolverPackageFormat::Nsis,\n            PackageFormat::Deb => ResolverPackageFormat::Deb,\n            PackageFormat::AppImage => ResolverPackageFormat::AppImage,\n        }\n    }\n}\n\n/// Retrieve the resource path of your app, packaged with cargo packager.\n#[napi_derive::napi]\npub fn resources_dir(package_format: PackageFormat) -> Result<String> {\n    cargo_packager_resource_resolver::resources_dir(package_format.into())\n        .map_err(|e| napi::Error::new(Status::GenericFailure, e.to_string()))\n        .map(|p| dunce::simplified(&p).to_string_lossy().to_string())\n}\n"
  },
  {
    "path": "bindings/updater/nodejs/.cargo/config.toml",
    "content": "[target.aarch64-unknown-linux-musl]\nlinker = \"aarch64-linux-musl-gcc\"\nrustflags = [\"-C\", \"target-feature=-crt-static\"]"
  },
  {
    "path": "bindings/updater/nodejs/.npmignore",
    "content": "target\nCargo.lock\n.cargo\n.github\nnpm\n.eslintrc\n.prettierignore\nrustfmt.toml\nyarn.lock\n*.node\n.yarn\n__test__\nrenovate.json\n"
  },
  {
    "path": "bindings/updater/nodejs/CHANGELOG.md",
    "content": "# Changelog\n\n## \\[0.2.3]\n\n- [`d861f1a`](https://www.github.com/crabnebula-dev/cargo-packager/commit/d861f1a6b1dfe585014e04234b33d49b1a895219) ([#356](https://www.github.com/crabnebula-dev/cargo-packager/pull/356)) Pull enhancements from tauri-plugin-updater.\n\n### Dependencies\n\n- Upgraded to `cargo-packager-updater@0.2.3`\n\n## \\[0.2.2]\n\n### Dependencies\n\n- Upgraded to `cargo-packager-updater@0.2.2`\n\n## \\[0.2.1]\n\n### Dependencies\n\n- Upgraded to `cargo-packager-updater@0.2.1`\n- Upgraded to `cargo-packager-utils@0.1.1`\n\n## \\[0.2.0]\n\n- [`c16d17a`](https://www.github.com/crabnebula-dev/cargo-packager/commit/c16d17ae190f49be3f9e78c5441bee16c0f8fc69) Enable `rustls-tls` feature flag by default.\n\n### Dependencies\n\n- Upgraded to `cargo-packager-updater@0.2.0`\n\n## \\[0.1.4]\n\n- [`3ee2290`](https://www.github.com/crabnebula-dev/cargo-packager/commit/3ee2290df518103056b295dae426b38a65293048)([#147](https://www.github.com/crabnebula-dev/cargo-packager/pull/147)) Prevent powershell window from opening when the msi and nsis installer are executed.\n\n### Dependencies\n\n- Upgraded to `cargo-packager-updater@0.1.4`\n\n## \\[0.1.3]\n\n- [`0e00ca2`](https://www.github.com/crabnebula-dev/cargo-packager/commit/0e00ca25fc0e71cad4bb7085edda067a184e5ec7)([#146](https://www.github.com/crabnebula-dev/cargo-packager/pull/146)) Enable native certificates via `rustls-native-certs`.\n\n### Dependencies\n\n- Upgraded to `cargo-packager-updater@0.1.3`\n\n## \\[0.1.2]\n\n- [`005a55f`](https://www.github.com/crabnebula-dev/cargo-packager/commit/005a55fb27b92503b3d6f936cffb088ccf346c40)([#143](https://www.github.com/crabnebula-dev/cargo-packager/pull/143)) Fix the download callback parameters to be accurate to typescript definitions\n\n### Dependencies\n\n- Upgraded to `cargo-packager-utils@0.1.0`\n- Upgraded to `cargo-packager-updater@0.1.2`\n\n## \\[0.1.1]\n\n- [`feb53a2`](https://www.github.com/crabnebula-dev/cargo-packager/commit/feb53a2f16ef2c8d93ff2d73a4eb318490f33471)([#102](https://www.github.com/crabnebula-dev/cargo-packager/pull/102)) Fix NSIS updater failing to launch when using `basicUi` mode.\n\n### Dependencies\n\n- Upgraded to `cargo-packager-updater@0.1.1`\n\n## \\[0.1.0]\n\n- [`c4fa8fd`](https://www.github.com/crabnebula-dev/cargo-packager/commit/c4fa8fd6334b7fd0c32710ea2df0b54aa6bde713) Initial release.\n\n### Dependencies\n\n- Upgraded to `cargo-packager-updater@0.1.0`\n"
  },
  {
    "path": "bindings/updater/nodejs/Cargo.toml",
    "content": "[package]\nname = \"crabnebula_updater\"\nversion = \"0.0.0\"\npublish = false\nedition = { workspace = true }\nlicense = { workspace = true }\nrepository = { workspace = true }\n\n[lib]\ncrate-type = [\"cdylib\"]\n\n[dependencies]\n# Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix\nnapi = { workspace = true, features = [\"napi4\", \"async\"] }\nnapi-derive = { workspace = true }\ncargo-packager-updater = { path = \"../../../crates/updater/\", default-features = false }\nserde_json = { workspace = true }\ntime = { workspace = true, features = [\"formatting\"] }\n\n\n[build-dependencies]\nnapi-build = { workspace = true }\n\n[features]\ndefault = [\"cargo-packager-updater/rustls-tls\"]\nrustls-tls = [\"cargo-packager-updater/rustls-tls\"]\nnative-tls = [\"cargo-packager-updater/native-tls\"]\nnative-tls-vendored = [\"cargo-packager-updater/native-tls-vendored\"]\n"
  },
  {
    "path": "bindings/updater/nodejs/README.md",
    "content": "# @crabnebula/updater\n\nUpdater for apps that was packaged by [`@crabnebula/packager`](https://www.npmjs.com/package/@crabnebula/packager).\n\n```sh\n# pnpm\npnpm add @crabnebula/updater\n# yarn\nyarn add @crabnebula/updater\n# npm\nnpm i @crabnebula/updater\n```\n\n## Checking for an update\n\nyou can check for an update using `checkUpdate` function which require the current version of the app and\nan options object that specifies the endpoints to request updates from and the public key of the update signature.\n\n```js\nimport { checkUpdate } from \"@crabnebula/updater\";\n\nlet update = await checkUpdate(\"0.1.0\", {\n  endpoints: [\"http://myserver.com/updates\"],\n  pubkey: \"<pubkey here>\",\n});\n\nif (update !== null) {\n  update.downloadAndInstall();\n} else {\n  // there is no updates\n}\n```\n\n## Endpoints\n\nEach endpoint optionally could have `{{arch}}`, `{{target}}` or `{{current_version}}`\nwhich will be detected and replaced with the appropriate value before making a request to the endpoint.\n\n- `{{current_version}}`: The version of the app that is requesting the update.\n- `{{target}}`: The operating system name (one of `linux`, `windows` or `macos`).\n- `{{arch}}`: The architecture of the machine (one of `x86_64`, `i686`, `aarch64` or `armv7`).\n\nfor example:\n\n```\nhttps://releases.myapp.com/{{target}}/{{arch}}/{{current_version}}\n```\n\nwill turn into\n\n```\nhttps://releases.myapp.com/windows/x86_64/0.1.0\n```\n\nif you need more data, you can set additional request headers in the options object pass to `checkUpdate` to your liking.\n\n## Endpoint Response\n\nThe updater expects the endpoint to respond with 2 possible reponses:\n\n1.  [`204 No Content`](https://datatracker.ietf.org/doc/html/rfc2616#section-10.2.5) in case there is no updates available.\n2.  [`200 OK`](https://datatracker.ietf.org/doc/html/rfc2616#section-10.2.1) and a JSON response that could be either a JSON representing all available platform updates\n    or if using endpoints variables (see above) or a header to attach the current updater target,\n    then it can just return information for the requested target.\n\nThe JSON response is expected to have these fields set:\n\n- `version`: must be a valid semver, with or without a leading `v``, meaning that both `1.0.0`and`v1.0.0`are valid.\n- `url`or`platforms.[target].url`: must be a valid url to the update bundle.\n- `signature`or`platforms.[target].signature`: must be the content of the generated `.sig`file. The signature may change each time you run build your app so make sure to always update it.\n- `format`or`platforms.[target].format`: must be one of `app`, `appimage`, `nsis`or`wix`.\n\n> [!NOTE]\n> if using `platforms` object, each key is in the `OS-ARCH` format, where `OS` is one of `linux`, `macos` or `windows`, and `ARCH` is one of `x86_64`, `aarch64`, `i686` or `armv7`, see the example below.\n\nIt can also contain these optional fields:\n\n- `notes`: Here you can add notes about the update, like release notes.\n- `pub_date`: must be formatted according to [RFC 3339](https://datatracker.ietf.org/doc/html/rfc3339#section-5.8) if present.\n\nHere is an example of the two expected JSON formats:\n\n- **JSON for all platforms**\n\n  ```json\n  {\n    \"version\": \"v1.0.0\",\n    \"notes\": \"Test version\",\n    \"pub_date\": \"2020-06-22T19:25:57Z\",\n    \"platforms\": {\n      \"macos-x86_64\": {\n        \"signature\": \"Content of app.tar.gz.sig\",\n        \"url\": \"https://github.com/username/reponame/releases/download/v1.0.0/app-x86_64.app.tar.gz\",\n        \"format\": \"app\"\n      },\n      \"macos-aarch64\": {\n        \"signature\": \"Content of app.tar.gz.sig\",\n        \"url\": \"https://github.com/username/reponame/releases/download/v1.0.0/app-aarch64.app.tar.gz\",\n        \"format\": \"app\"\n      },\n      \"linux-x86_64\": {\n        \"signature\": \"Content of app.AppImage.sig\",\n        \"url\": \"https://github.com/username/reponame/releases/download/v1.0.0/app-amd64.AppImage.tar.gz\",\n        \"format\": \"appimage\"\n      },\n      \"windows-x86_64\": {\n        \"signature\": \"Content of app-setup.exe.sig or app.msi.sig, depending on the chosen format\",\n        \"url\": \"https://github.com/username/reponame/releases/download/v1.0.0/app-x64-setup.nsis.zip\",\n        \"format\": \"nsis or wix depending on the chosen format\"\n      }\n    }\n  }\n  ```\n\n- **JSON for one platform**\n\n  ```json\n  {\n    \"version\": \"0.2.0\",\n    \"pub_date\": \"2020-09-18T12:29:53+01:00\",\n    \"url\": \"https://mycompany.example.com/myapp/releases/myrelease.tar.gz\",\n    \"signature\": \"Content of the relevant .sig file\",\n    \"format\": \"app or nsis or wix or appimage depending on the release target and the chosen format\",\n    \"notes\": \"These are some release notes\"\n  }\n  ```\n\n## Update install mode on Windows\n\nYou can specify which install mode to use on Windows using `windows.installMode` in the options object which can be one of:\n\n- `\"Passive\"`: There will be a small window with a progress bar. The update will be installed without requiring any user interaction. Generally recommended and the default mode.\n- `\"BasicUi\"`: There will be a basic user interface shown which requires user interaction to finish the installation.\n- `\"Quiet\"`: There will be no progress feedback to the user. With this mode the installer cannot request admin privileges by itself so it only works in user-wide installations or when your app itself already runs with admin privileges. Generally not recommended.\n\n## Licenses\n\nMIT or MIT/Apache 2.0 where applicable.\n"
  },
  {
    "path": "bindings/updater/nodejs/__test__/app/index.html",
    "content": "<!doctype html>\n<html>\n  <head>\n    <meta charset=\"UTF-8\" />\n    <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->\n    <meta\n      http-equiv=\"Content-Security-Policy\"\n      content=\"default-src 'self'; script-src 'self'\"\n    />\n    <title>Hello World!</title>\n  </head>\n\n  <body>\n    <h1>Hello World!</h1>\n    We are using Node.js <span id=\"node-version\"></span>, Chromium\n    <span id=\"chrome-version\"></span>, and Electron\n    <span id=\"electron-version\"></span>.\n  </body>\n</html>\n"
  },
  {
    "path": "bindings/updater/nodejs/__test__/app/main.js",
    "content": "const { app } = require(\"electron\");\nconst { join } = require(\"node:path\");\nconst { checkUpdate } = require(\"@crabnebula/updater\");\n\nconst UPDATER_PUB_KEY =\n  \"dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDQ2Njc0OTE5Mzk2Q0ExODkKUldTSm9XdzVHVWxuUmtJdjB4RnRXZGVqR3NQaU5SVitoTk1qNFFWQ3pjL2hZWFVDOFNrcEVvVlcK\";\n\nconst CURRENT_VERSION = \"{{version}}\";\n\napp.whenReady().then(async () => {\n  console.log(CURRENT_VERSION);\n\n  const updaterFormat = process.env[\"UPDATER_FORMAT\"];\n  const appimg = process.env[\"APPIMAGE\"];\n  const isLinux = process.platfrom !== \"win32\" && process.platfrom !== \"darwin\";\n\n  try {\n    const update = await checkUpdate(CURRENT_VERSION, {\n      pubkey: UPDATER_PUB_KEY,\n      endpoints: [\"http://localhost:3007\"],\n      executablePath: isLinux && appimg ? appimg : undefined,\n      windows: {\n        installerArgs:\n          // /D sets the default installation directory ($INSTDIR),\n          // overriding InstallDir and InstallDirRegKey.\n          // It must be the last parameter used in the command line and must not contain any quotes, even if the path contains spaces.\n          // Only absolute paths are supported.\n          // NOTE: we only need this because this is an integration test and we don't want to install the app in the programs folder\n          updaterFormat === \"nsis\"\n            ? [`/D=${join(process.execPath, \"..\")}`]\n            : undefined,\n      },\n    });\n\n    if (update) {\n      try {\n        await update.downloadAndInstall();\n        process.exit(0);\n      } catch (e) {\n        console.error(e);\n        process.exit(1);\n      }\n    } else {\n      process.exit(0);\n    }\n  } catch (e) {\n    console.error(e);\n    process.exit(1);\n  }\n});\n"
  },
  {
    "path": "bindings/updater/nodejs/__test__/app/package.json",
    "content": "{\n  \"name\": \"electron-app\",\n  \"productName\": \"ElectronApp\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Hello World!\",\n  \"main\": \"main.js\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/crabnebula-dev/cargo-packager.git\"\n  },\n  \"author\": \"CrabNebula Ltd.\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/crabnebula-dev/cargo-packager/issues\"\n  },\n  \"homepage\": \"https://github.com/crabnebula-dev/cargo-packager#readme\",\n  \"dependencies\": {\n    \"@crabnebula/updater\": \"../..\"\n  },\n  \"devDependencies\": {\n    \"electron\": \"^35.7.5\"\n  },\n  \"packager\": {\n    \"outDir\": \"./dist\",\n    \"identifier\": \"com.electron.example\",\n    \"icons\": [\n      \"electron.png\"\n    ]\n  }\n}\n"
  },
  {
    "path": "bindings/updater/nodejs/__test__/app/preload.js",
    "content": "window.addEventListener(\"DOMContentLoaded\", () => {\n  const replaceText = (selector, text) => {\n    const element = document.getElementById(selector);\n    if (element) element.innerText = text;\n  };\n\n  for (const dependency of [\"chrome\", \"node\", \"electron\"]) {\n    replaceText(`${dependency}-version`, process.versions[dependency]);\n  }\n});\n"
  },
  {
    "path": "bindings/updater/nodejs/__test__/index.spec.mjs",
    "content": "import test from \"ava\";\nimport * as fs from \"fs/promises\";\nimport { existsSync } from \"fs\";\nimport * as path from \"path\";\nimport { execa } from \"execa\";\nimport { fileURLToPath } from \"url\";\nimport { App } from \"@tinyhttp/app\";\nimport { packageAndSignApp } from \"@crabnebula/packager\";\n\nconst sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));\nconst __dirname = fileURLToPath(new URL(\".\", import.meta.url));\nconst isWin = process.platform === \"win32\";\nconst isMac = process.platform === \"darwin\";\n\ntest(\"it updates correctly\", async (t) => {\n  const UPDATER_PRIVATE_KEY = await fs.readFile(\n    path.join(__dirname, \"../../../../crates/updater/tests/dummy.key\"),\n    {\n      encoding: \"utf8\",\n    },\n  );\n\n  process.chdir(path.join(__dirname, \"app\"));\n  await execa(\"yarn\", [\"install\"]);\n\n  const buildApp = async (version, updaterFormats) => {\n    const content = await fs.readFile(\"main.js\", {\n      encoding: \"utf8\",\n    });\n    await fs.writeFile(\"main.js\", content.replace(\"{{version}}\", version));\n\n    try {\n      await packageAndSignApp(\n        {\n          formats: updaterFormats,\n          version,\n        },\n        {\n          privateKey: UPDATER_PRIVATE_KEY,\n          password: \"\",\n        },\n        {\n          verbosity: 2,\n        },\n      );\n    } catch (e) {\n      console.error(\"failed to package app\");\n      console.error(e);\n    } finally {\n      const content = await fs.readFile(\"main.js\", {\n        encoding: \"utf8\",\n      });\n      await fs.writeFile(\"main.js\", content.replace(version, \"{{version}}\"));\n    }\n  };\n\n  // bundle app update\n  const formats = isWin ? [\"nsis\", \"wix\"] : isMac ? [\"app\"] : [\"appimage\"];\n  await buildApp(\"1.0.0\", formats);\n\n  const generatedPackages = isWin\n    ? [\n        [\"nsis\", path.join(\"dist\", `ElectronApp_1.0.0_x64-setup.exe`)],\n        [\"wix\", path.join(\"dist\", `ElectronApp_1.0.0_x64_en-US.msi`)],\n      ]\n    : isMac\n      ? [[\"app\", path.join(\"dist\", \"ElectronApp.app.tar.gz\")]]\n      : [[\"appimage\", path.join(\"dist\", `electron-app_1.0.0_x86_64.AppImage`)]];\n\n  for (let [format, updatePackagePath] of generatedPackages) {\n    const signaturePath = path.format({\n      name: updatePackagePath,\n      ext: \".sig\",\n    });\n    const signature = await fs.readFile(signaturePath, {\n      encoding: \"utf8\",\n    });\n\n    // on macOS, generated bundle doesn't have the version in its name\n    // so we need to move it otherwise it'll be overwritten when we build the next app\n    if (isMac) {\n      const info = path.parse(updatePackagePath);\n      const newPath = path.format({\n        dir: info.dir,\n        base: `update-1.0.0-${info.base}`,\n      });\n      await fs.rename(updatePackagePath, newPath);\n      updatePackagePath = newPath;\n    }\n\n    // start the updater server\n    const server = new App()\n      .get(\"/\", (_, res) => {\n        const platforms = {};\n        const target = `${isWin ? \"windows\" : isMac ? \"macos\" : \"linux\"}-${\n          process.arch === \"x64\"\n            ? \"x86_64\"\n            : process.arch === \"arm64\"\n              ? \"aarch64\"\n              : \"i686\"\n        }`;\n        platforms[target] = {\n          signature,\n          url: \"http://localhost:3007/download\",\n          format,\n        };\n        res.status(200).json({\n          version: \"1.0.0\",\n          date: new Date().toISOString(),\n          platforms,\n        });\n      })\n      .get(\"/download\", (_req, res) => {\n        res\n          .status(200)\n          .sendFile(path.join(__dirname, \"app\", updatePackagePath));\n      })\n      .listen(3007);\n\n    // bundle initial app version\n    await buildApp(\"0.1.0\", [format]);\n\n    // install the inital app on Windows to `installdir`\n    if (isWin) {\n      const installDir = path.join(__dirname, \"app\", \"dist\", \"installdir\");\n      if (existsSync(installDir))\n        await fs.rm(installDir, {\n          recursive: true,\n        });\n      await fs.mkdir(installDir);\n\n      const isNsis = format === \"nsis\";\n\n      const installerArg = `\"${path.join(\n        \"dist\",\n        isNsis\n          ? `ElectronApp_0.1.0_x64-setup.exe`\n          : `ElectronApp_0.1.0_x64_en-US.msi`,\n      )}\"`;\n\n      await execa(\"powershell.exe\", [\n        \"-NoProfile\",\n        \"-WindowStyle\",\n        \"Hidden\",\n        \"Start-Process\",\n        installerArg,\n        \"-Wait\",\n        \"-ArgumentList\",\n        `${isNsis ? \"/P\" : \"/passive\"}, ${\n          isNsis ? \"/D\" : \"INSTALLDIR\"\n        }=${installDir}`,\n      ]);\n    }\n\n    const app = path.join(\n      \"dist\",\n      isWin\n        ? \"installdir/ElectronApp.exe\"\n        : isMac\n          ? \"ElectronApp.app/Contents/MacOS/ElectronApp\"\n          : `electron-app_0.1.0_x86_64.AppImage`,\n    );\n\n    // save the current creation time\n    const stats = await fs.stat(app);\n    const ctime1 = stats.birthtime;\n\n    // run initial app\n    try {\n      await execa(app, {\n        stdio: \"inherit\",\n        // This is read by the updater app test\n        env: {\n          UPDATER_FORMAT: format,\n        },\n      });\n    } catch (e) {\n      console.error(`failed to start initial app: ${e}`);\n    }\n\n    // the test app is electron which is huge in size\n    // and the installation takes a who;e\n    // so wait 30 secs to make sure the installer has finished\n    await sleep(30000);\n\n    // wait until the update is finished and the new version has been installed\n    // before starting another updater test, this is because we use the same starting binary\n    // and we can't use it while the updater is installing it\n    let counter = 0;\n    while (true) {\n      // check if the main binary creation time has changed since `ctime1`\n      const stats = await fs.stat(app);\n      if (ctime1 !== stats.birthtime) {\n        try {\n          const { stdout, stderr } = await execa(app);\n\n          const lines = stdout.split(isWin ? \"\\r\\n\" : \"\\n\");\n          const version = lines.filter((l) => l)[0];\n\n          if (version === \"1.0.0\") {\n            console.log(`app is updated, new version: ${version}`);\n            break;\n          }\n\n          console.log(`unexpected output (stdout): ${stdout}`);\n          console.log(`stderr: ${stderr}`);\n        } catch (e) {\n          console.error(`failed to check if app was updated: ${e}`);\n        }\n      }\n\n      counter += 1;\n      if (counter == 10) {\n        console.error(\n          \"updater test timedout and couldn't verify the update has happened\",\n        );\n        break;\n      }\n\n      await sleep(5000);\n    }\n\n    server.close();\n  }\n\n  t.pass(\"Test successful\");\n});\n"
  },
  {
    "path": "bindings/updater/nodejs/build.rs",
    "content": "extern crate napi_build;\n\nfn main() {\n    napi_build::setup();\n}\n"
  },
  {
    "path": "bindings/updater/nodejs/index.d.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n\n/* auto-generated by NAPI-RS */\n\nexport const enum WindowsUpdateInstallMode {\n  /** Specifies there's a basic UI during the installation process, including a final dialog box at the end. */\n  BasicUi = 'BasicUi',\n  /**\n   * The quiet mode means there's no user interaction required.\n   * Requires admin privileges if the installer does.\n   */\n  Quiet = 'Quiet',\n  /** Specifies unattended mode, which means the installation only shows a progress bar. */\n  Passive = 'Passive'\n}\nexport interface UpdaterWindowsOptions {\n  /** Additional arguments given to the NSIS or WiX installer. */\n  installerArgs?: Array<string>\n  /** The installation mode for the update on Windows. Defaults to `passive`. */\n  installMode?: WindowsUpdateInstallMode\n}\nexport interface Options {\n  /** The updater endpoints. */\n  endpoints: Array<string>\n  /** Signature public key. */\n  pubkey: string\n  /** The Windows options for the updater. */\n  windows?: UpdaterWindowsOptions\n  /** The target of the executable. */\n  target?: string\n  /** Path to the executable file. */\n  executablePath?: string\n  /** Headers to use when checking and when downloading the update. */\n  headers?: Record<string, string>\n  /** Request timeout in milliseconds. */\n  timeout?: number\n}\n/** Supported update format */\nexport const enum UpdateFormat {\n  /** The NSIS installer (.exe). */\n  Nsis = 0,\n  /** The Microsoft Software Installer (.msi) through WiX Toolset. */\n  Wix = 1,\n  /** The Linux AppImage package (.AppImage). */\n  AppImage = 2,\n  /** The macOS application bundle (.app). */\n  App = 3\n}\nexport function checkUpdate(currentVersion: string, options: Options): Promise<Update | null>\nexport class Update {\n  /** Signing public key */\n  pubkey: string\n  /** Version used to check for update */\n  currentVersion: string\n  /** Version announced */\n  version: string\n  /** Target */\n  target: string\n  /** Extract path */\n  extractPath: string\n  /** Download URL announced */\n  downloadUrl: string\n  /** Signature announced */\n  signature: string\n  /** Request headers */\n  headers: Record<string, string>\n  /** Update format */\n  format: UpdateFormat\n  /** The Windows options for the updater. */\n  windows?: UpdaterWindowsOptions\n  /** Update description */\n  body?: string\n  /** Update publish date */\n  date?: string\n  /** Request timeout */\n  timeout?: number\n  download(onChunk?: (chunkLength: number, contentLength: number | null) => void, onDownloadFinished?: () => void): Promise<ArrayBuffer>\n  install(buffer: ArrayBuffer): Promise<void>\n  downloadAndInstall(onChunk?: (chunkLength: number, contentLength?: number) => void, onDownloadFinished?: () => void): Promise<void>\n}\n"
  },
  {
    "path": "bindings/updater/nodejs/index.js",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/* prettier-ignore */\n\n/* auto-generated by NAPI-RS */\n\nconst { existsSync, readFileSync } = require('fs')\nconst { join } = require('path')\n\nconst { platform, arch } = process\n\nlet nativeBinding = null\nlet localFileExisted = false\nlet loadError = null\n\nfunction isMusl() {\n  // For Node 10\n  if (!process.report || typeof process.report.getReport !== 'function') {\n    try {\n      const lddPath = require('child_process').execSync('which ldd').toString().trim()\n      return readFileSync(lddPath, 'utf8').includes('musl')\n    } catch (e) {\n      return true\n    }\n  } else {\n    const { glibcVersionRuntime } = process.report.getReport().header\n    return !glibcVersionRuntime\n  }\n}\n\nswitch (platform) {\n  case 'android':\n    switch (arch) {\n      case 'arm64':\n        localFileExisted = existsSync(join(__dirname, 'updater.android-arm64.node'))\n        try {\n          if (localFileExisted) {\n            nativeBinding = require('./updater.android-arm64.node')\n          } else {\n            nativeBinding = require('@crabnebula/updater-android-arm64')\n          }\n        } catch (e) {\n          loadError = e\n        }\n        break\n      case 'arm':\n        localFileExisted = existsSync(join(__dirname, 'updater.android-arm-eabi.node'))\n        try {\n          if (localFileExisted) {\n            nativeBinding = require('./updater.android-arm-eabi.node')\n          } else {\n            nativeBinding = require('@crabnebula/updater-android-arm-eabi')\n          }\n        } catch (e) {\n          loadError = e\n        }\n        break\n      default:\n        throw new Error(`Unsupported architecture on Android ${arch}`)\n    }\n    break\n  case 'win32':\n    switch (arch) {\n      case 'x64':\n        localFileExisted = existsSync(\n          join(__dirname, 'updater.win32-x64-msvc.node')\n        )\n        try {\n          if (localFileExisted) {\n            nativeBinding = require('./updater.win32-x64-msvc.node')\n          } else {\n            nativeBinding = require('@crabnebula/updater-win32-x64-msvc')\n          }\n        } catch (e) {\n          loadError = e\n        }\n        break\n      case 'ia32':\n        localFileExisted = existsSync(\n          join(__dirname, 'updater.win32-ia32-msvc.node')\n        )\n        try {\n          if (localFileExisted) {\n            nativeBinding = require('./updater.win32-ia32-msvc.node')\n          } else {\n            nativeBinding = require('@crabnebula/updater-win32-ia32-msvc')\n          }\n        } catch (e) {\n          loadError = e\n        }\n        break\n      case 'arm64':\n        localFileExisted = existsSync(\n          join(__dirname, 'updater.win32-arm64-msvc.node')\n        )\n        try {\n          if (localFileExisted) {\n            nativeBinding = require('./updater.win32-arm64-msvc.node')\n          } else {\n            nativeBinding = require('@crabnebula/updater-win32-arm64-msvc')\n          }\n        } catch (e) {\n          loadError = e\n        }\n        break\n      default:\n        throw new Error(`Unsupported architecture on Windows: ${arch}`)\n    }\n    break\n  case 'darwin':\n    localFileExisted = existsSync(join(__dirname, 'updater.darwin-universal.node'))\n    try {\n      if (localFileExisted) {\n        nativeBinding = require('./updater.darwin-universal.node')\n      } else {\n        nativeBinding = require('@crabnebula/updater-darwin-universal')\n      }\n      break\n    } catch {}\n    switch (arch) {\n      case 'x64':\n        localFileExisted = existsSync(join(__dirname, 'updater.darwin-x64.node'))\n        try {\n          if (localFileExisted) {\n            nativeBinding = require('./updater.darwin-x64.node')\n          } else {\n            nativeBinding = require('@crabnebula/updater-darwin-x64')\n          }\n        } catch (e) {\n          loadError = e\n        }\n        break\n      case 'arm64':\n        localFileExisted = existsSync(\n          join(__dirname, 'updater.darwin-arm64.node')\n        )\n        try {\n          if (localFileExisted) {\n            nativeBinding = require('./updater.darwin-arm64.node')\n          } else {\n            nativeBinding = require('@crabnebula/updater-darwin-arm64')\n          }\n        } catch (e) {\n          loadError = e\n        }\n        break\n      default:\n        throw new Error(`Unsupported architecture on macOS: ${arch}`)\n    }\n    break\n  case 'freebsd':\n    if (arch !== 'x64') {\n      throw new Error(`Unsupported architecture on FreeBSD: ${arch}`)\n    }\n    localFileExisted = existsSync(join(__dirname, 'updater.freebsd-x64.node'))\n    try {\n      if (localFileExisted) {\n        nativeBinding = require('./updater.freebsd-x64.node')\n      } else {\n        nativeBinding = require('@crabnebula/updater-freebsd-x64')\n      }\n    } catch (e) {\n      loadError = e\n    }\n    break\n  case 'linux':\n    switch (arch) {\n      case 'x64':\n        if (isMusl()) {\n          localFileExisted = existsSync(\n            join(__dirname, 'updater.linux-x64-musl.node')\n          )\n          try {\n            if (localFileExisted) {\n              nativeBinding = require('./updater.linux-x64-musl.node')\n            } else {\n              nativeBinding = require('@crabnebula/updater-linux-x64-musl')\n            }\n          } catch (e) {\n            loadError = e\n          }\n        } else {\n          localFileExisted = existsSync(\n            join(__dirname, 'updater.linux-x64-gnu.node')\n          )\n          try {\n            if (localFileExisted) {\n              nativeBinding = require('./updater.linux-x64-gnu.node')\n            } else {\n              nativeBinding = require('@crabnebula/updater-linux-x64-gnu')\n            }\n          } catch (e) {\n            loadError = e\n          }\n        }\n        break\n      case 'arm64':\n        if (isMusl()) {\n          localFileExisted = existsSync(\n            join(__dirname, 'updater.linux-arm64-musl.node')\n          )\n          try {\n            if (localFileExisted) {\n              nativeBinding = require('./updater.linux-arm64-musl.node')\n            } else {\n              nativeBinding = require('@crabnebula/updater-linux-arm64-musl')\n            }\n          } catch (e) {\n            loadError = e\n          }\n        } else {\n          localFileExisted = existsSync(\n            join(__dirname, 'updater.linux-arm64-gnu.node')\n          )\n          try {\n            if (localFileExisted) {\n              nativeBinding = require('./updater.linux-arm64-gnu.node')\n            } else {\n              nativeBinding = require('@crabnebula/updater-linux-arm64-gnu')\n            }\n          } catch (e) {\n            loadError = e\n          }\n        }\n        break\n      case 'arm':\n        localFileExisted = existsSync(\n          join(__dirname, 'updater.linux-arm-gnueabihf.node')\n        )\n        try {\n          if (localFileExisted) {\n            nativeBinding = require('./updater.linux-arm-gnueabihf.node')\n          } else {\n            nativeBinding = require('@crabnebula/updater-linux-arm-gnueabihf')\n          }\n        } catch (e) {\n          loadError = e\n        }\n        break\n      default:\n        throw new Error(`Unsupported architecture on Linux: ${arch}`)\n    }\n    break\n  default:\n    throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`)\n}\n\nif (!nativeBinding) {\n  if (loadError) {\n    throw loadError\n  }\n  throw new Error(`Failed to load native binding`)\n}\n\nconst { WindowsUpdateInstallMode, UpdateFormat, Update, checkUpdate } = nativeBinding\n\nmodule.exports.WindowsUpdateInstallMode = WindowsUpdateInstallMode\nmodule.exports.UpdateFormat = UpdateFormat\nmodule.exports.Update = Update\nmodule.exports.checkUpdate = checkUpdate\n"
  },
  {
    "path": "bindings/updater/nodejs/npm/darwin-arm64/README.md",
    "content": "# `@crabnebula/updater-darwin-arm64`\n\nThis is the **aarch64-apple-darwin** binary for `@crabnebula/updater`\n"
  },
  {
    "path": "bindings/updater/nodejs/npm/darwin-arm64/package.json",
    "content": "{\n  \"name\": \"@crabnebula/updater-darwin-arm64\",\n  \"version\": \"0.0.0\",\n  \"os\": [\n    \"darwin\"\n  ],\n  \"cpu\": [\n    \"arm64\"\n  ],\n  \"main\": \"updater.darwin-arm64.node\",\n  \"files\": [\n    \"updater.darwin-arm64.node\"\n  ],\n  \"license\": \"MIT\",\n  \"engines\": {\n    \"node\": \">= 10\"\n  }\n}\n"
  },
  {
    "path": "bindings/updater/nodejs/npm/darwin-x64/README.md",
    "content": "# `@crabnebula/updater-darwin-x64`\n\nThis is the **x86_64-apple-darwin** binary for `@crabnebula/updater`\n"
  },
  {
    "path": "bindings/updater/nodejs/npm/darwin-x64/package.json",
    "content": "{\n  \"name\": \"@crabnebula/updater-darwin-x64\",\n  \"version\": \"0.0.0\",\n  \"os\": [\n    \"darwin\"\n  ],\n  \"cpu\": [\n    \"x64\"\n  ],\n  \"main\": \"updater.darwin-x64.node\",\n  \"files\": [\n    \"updater.darwin-x64.node\"\n  ],\n  \"license\": \"MIT\",\n  \"engines\": {\n    \"node\": \">= 10\"\n  }\n}\n"
  },
  {
    "path": "bindings/updater/nodejs/npm/linux-arm-gnueabihf/README.md",
    "content": "# `@crabnebula/updater-linux-arm-gnueabihf`\n\nThis is the **armv7-unknown-linux-gnueabihf** binary for `@crabnebula/updater`\n"
  },
  {
    "path": "bindings/updater/nodejs/npm/linux-arm-gnueabihf/package.json",
    "content": "{\n  \"name\": \"@crabnebula/updater-linux-arm-gnueabihf\",\n  \"version\": \"0.0.0\",\n  \"os\": [\n    \"linux\"\n  ],\n  \"cpu\": [\n    \"arm\"\n  ],\n  \"main\": \"updater.linux-arm-gnueabihf.node\",\n  \"files\": [\n    \"updater.linux-arm-gnueabihf.node\"\n  ],\n  \"license\": \"MIT\",\n  \"engines\": {\n    \"node\": \">= 10\"\n  }\n}\n"
  },
  {
    "path": "bindings/updater/nodejs/npm/linux-arm64-gnu/README.md",
    "content": "# `@crabnebula/updater-linux-arm64-gnu`\n\nThis is the **aarch64-unknown-linux-gnu** binary for `@crabnebula/updater`\n"
  },
  {
    "path": "bindings/updater/nodejs/npm/linux-arm64-gnu/package.json",
    "content": "{\n  \"name\": \"@crabnebula/updater-linux-arm64-gnu\",\n  \"version\": \"0.0.0\",\n  \"os\": [\n    \"linux\"\n  ],\n  \"cpu\": [\n    \"arm64\"\n  ],\n  \"main\": \"updater.linux-arm64-gnu.node\",\n  \"files\": [\n    \"updater.linux-arm64-gnu.node\"\n  ],\n  \"license\": \"MIT\",\n  \"engines\": {\n    \"node\": \">= 10\"\n  },\n  \"libc\": [\n    \"glibc\"\n  ]\n}\n"
  },
  {
    "path": "bindings/updater/nodejs/npm/linux-arm64-musl/README.md",
    "content": "# `@crabnebula/updater-linux-arm64-musl`\n\nThis is the **aarch64-unknown-linux-musl** binary for `@crabnebula/updater`\n"
  },
  {
    "path": "bindings/updater/nodejs/npm/linux-arm64-musl/package.json",
    "content": "{\n  \"name\": \"@crabnebula/updater-linux-arm64-musl\",\n  \"version\": \"0.0.0\",\n  \"os\": [\n    \"linux\"\n  ],\n  \"cpu\": [\n    \"arm64\"\n  ],\n  \"main\": \"updater.linux-arm64-musl.node\",\n  \"files\": [\n    \"updater.linux-arm64-musl.node\"\n  ],\n  \"license\": \"MIT\",\n  \"engines\": {\n    \"node\": \">= 10\"\n  },\n  \"libc\": [\n    \"musl\"\n  ]\n}\n"
  },
  {
    "path": "bindings/updater/nodejs/npm/linux-x64-gnu/README.md",
    "content": "# `@crabnebula/updater-linux-x64-gnu`\n\nThis is the **x86_64-unknown-linux-gnu** binary for `@crabnebula/updater`\n"
  },
  {
    "path": "bindings/updater/nodejs/npm/linux-x64-gnu/package.json",
    "content": "{\n  \"name\": \"@crabnebula/updater-linux-x64-gnu\",\n  \"version\": \"0.0.0\",\n  \"os\": [\n    \"linux\"\n  ],\n  \"cpu\": [\n    \"x64\"\n  ],\n  \"main\": \"updater.linux-x64-gnu.node\",\n  \"files\": [\n    \"updater.linux-x64-gnu.node\"\n  ],\n  \"license\": \"MIT\",\n  \"engines\": {\n    \"node\": \">= 10\"\n  },\n  \"libc\": [\n    \"glibc\"\n  ]\n}\n"
  },
  {
    "path": "bindings/updater/nodejs/npm/linux-x64-musl/README.md",
    "content": "# `@crabnebula/updater-linux-x64-musl`\n\nThis is the **x86_64-unknown-linux-musl** binary for `@crabnebula/updater`\n"
  },
  {
    "path": "bindings/updater/nodejs/npm/linux-x64-musl/package.json",
    "content": "{\n  \"name\": \"@crabnebula/updater-linux-x64-musl\",\n  \"version\": \"0.0.0\",\n  \"os\": [\n    \"linux\"\n  ],\n  \"cpu\": [\n    \"x64\"\n  ],\n  \"main\": \"updater.linux-x64-musl.node\",\n  \"files\": [\n    \"updater.linux-x64-musl.node\"\n  ],\n  \"license\": \"MIT\",\n  \"engines\": {\n    \"node\": \">= 10\"\n  },\n  \"libc\": [\n    \"musl\"\n  ]\n}\n"
  },
  {
    "path": "bindings/updater/nodejs/npm/win32-arm64-msvc/README.md",
    "content": "# `@crabnebula/updater-win32-arm64-msvc`\n\nThis is the **aarch64-pc-windows-msvc** binary for `@crabnebula/updater`\n"
  },
  {
    "path": "bindings/updater/nodejs/npm/win32-arm64-msvc/package.json",
    "content": "{\n  \"name\": \"@crabnebula/updater-win32-arm64-msvc\",\n  \"version\": \"0.0.0\",\n  \"os\": [\n    \"win32\"\n  ],\n  \"cpu\": [\n    \"arm64\"\n  ],\n  \"main\": \"updater.win32-arm64-msvc.node\",\n  \"files\": [\n    \"updater.win32-arm64-msvc.node\"\n  ],\n  \"license\": \"MIT\",\n  \"engines\": {\n    \"node\": \">= 10\"\n  }\n}\n"
  },
  {
    "path": "bindings/updater/nodejs/npm/win32-ia32-msvc/README.md",
    "content": "# `@crabnebula/updater-win32-ia32-msvc`\n\nThis is the **i686-pc-windows-msvc** binary for `@crabnebula/updater`\n"
  },
  {
    "path": "bindings/updater/nodejs/npm/win32-ia32-msvc/package.json",
    "content": "{\n  \"name\": \"@crabnebula/updater-win32-ia32-msvc\",\n  \"version\": \"0.0.0\",\n  \"os\": [\n    \"win32\"\n  ],\n  \"cpu\": [\n    \"ia32\"\n  ],\n  \"main\": \"updater.win32-ia32-msvc.node\",\n  \"files\": [\n    \"updater.win32-ia32-msvc.node\"\n  ],\n  \"license\": \"MIT\",\n  \"engines\": {\n    \"node\": \">= 10\"\n  }\n}\n"
  },
  {
    "path": "bindings/updater/nodejs/npm/win32-x64-msvc/README.md",
    "content": "# `@crabnebula/updater-win32-x64-msvc`\n\nThis is the **x86_64-pc-windows-msvc** binary for `@crabnebula/updater`\n"
  },
  {
    "path": "bindings/updater/nodejs/npm/win32-x64-msvc/package.json",
    "content": "{\n  \"name\": \"@crabnebula/updater-win32-x64-msvc\",\n  \"version\": \"0.0.0\",\n  \"os\": [\n    \"win32\"\n  ],\n  \"cpu\": [\n    \"x64\"\n  ],\n  \"main\": \"updater.win32-x64-msvc.node\",\n  \"files\": [\n    \"updater.win32-x64-msvc.node\"\n  ],\n  \"license\": \"MIT\",\n  \"engines\": {\n    \"node\": \">= 10\"\n  }\n}\n"
  },
  {
    "path": "bindings/updater/nodejs/package.json",
    "content": "{\n  \"name\": \"@crabnebula/updater\",\n  \"version\": \"0.2.3\",\n  \"main\": \"./index.js\",\n  \"types\": \"./index.d.ts\",\n  \"napi\": {\n    \"name\": \"updater\",\n    \"triples\": {\n      \"additional\": [\n        \"aarch64-apple-darwin\",\n        \"aarch64-unknown-linux-gnu\",\n        \"aarch64-unknown-linux-musl\",\n        \"aarch64-pc-windows-msvc\",\n        \"armv7-unknown-linux-gnueabihf\",\n        \"x86_64-unknown-linux-musl\",\n        \"i686-pc-windows-msvc\"\n      ]\n    }\n  },\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"artifacts\": \"napi artifacts\",\n    \"build\": \"napi build --platform --profile release-size-optimized\",\n    \"build:debug\": \"napi build --platform\",\n    \"prepublishOnly\": \"napi prepublish -t npm --gh-release-id $RELEASE_ID\",\n    \"test\": \"ava --no-worker-threads --timeout 30m\",\n    \"universal\": \"napi universal\",\n    \"version\": \"napi version\"\n  },\n  \"devDependencies\": {\n    \"@napi-rs/cli\": \"^2.18.1\",\n    \"@tinyhttp/app\": \"^2.2.1\",\n    \"@types/node\": \"^20.8.10\",\n    \"ava\": \"^6.2.0\",\n    \"execa\": \"^9.0.0\",\n    \"@crabnebula/packager\": \"workspace:\"\n  },\n  \"ava\": {\n    \"timeout\": \"3m\"\n  },\n  \"engines\": {\n    \"node\": \">= 10\"\n  }\n}\n"
  },
  {
    "path": "bindings/updater/nodejs/src/from_impls.rs",
    "content": "use crate::{Options, Update, UpdateFormat, UpdaterWindowsOptions, WindowsUpdateInstallMode};\n\nimpl From<WindowsUpdateInstallMode> for cargo_packager_updater::WindowsUpdateInstallMode {\n    fn from(value: WindowsUpdateInstallMode) -> Self {\n        match value {\n            WindowsUpdateInstallMode::BasicUi => Self::BasicUi,\n            WindowsUpdateInstallMode::Quiet => Self::Quiet,\n            WindowsUpdateInstallMode::Passive => Self::Passive,\n        }\n    }\n}\n\nimpl From<cargo_packager_updater::WindowsUpdateInstallMode> for WindowsUpdateInstallMode {\n    fn from(value: cargo_packager_updater::WindowsUpdateInstallMode) -> Self {\n        match value {\n            cargo_packager_updater::WindowsUpdateInstallMode::BasicUi => Self::BasicUi,\n            cargo_packager_updater::WindowsUpdateInstallMode::Quiet => Self::Quiet,\n            cargo_packager_updater::WindowsUpdateInstallMode::Passive => Self::Passive,\n        }\n    }\n}\n\nimpl From<cargo_packager_updater::WindowsConfig> for UpdaterWindowsOptions {\n    fn from(value: cargo_packager_updater::WindowsConfig) -> Self {\n        Self {\n            installer_args: value.installer_args,\n            install_mode: value.install_mode.map(Into::into),\n        }\n    }\n}\nimpl From<UpdaterWindowsOptions> for cargo_packager_updater::WindowsConfig {\n    fn from(value: UpdaterWindowsOptions) -> Self {\n        Self {\n            installer_args: value.installer_args,\n            install_mode: value.install_mode.map(Into::into),\n        }\n    }\n}\n\nimpl From<Options> for cargo_packager_updater::Config {\n    fn from(value: Options) -> Self {\n        Self {\n            endpoints: value\n                .endpoints\n                .into_iter()\n                .filter_map(|e| e.parse().ok())\n                .collect(),\n            pubkey: value.pubkey,\n            windows: value.windows.map(Into::into),\n        }\n    }\n}\n\nimpl From<cargo_packager_updater::UpdateFormat> for UpdateFormat {\n    fn from(value: cargo_packager_updater::UpdateFormat) -> Self {\n        match value {\n            cargo_packager_updater::UpdateFormat::Nsis => Self::Nsis,\n            cargo_packager_updater::UpdateFormat::Wix => Self::Wix,\n            cargo_packager_updater::UpdateFormat::AppImage => Self::AppImage,\n            cargo_packager_updater::UpdateFormat::App => Self::App,\n        }\n    }\n}\nimpl From<UpdateFormat> for cargo_packager_updater::UpdateFormat {\n    fn from(value: UpdateFormat) -> Self {\n        match value {\n            UpdateFormat::Nsis => Self::Nsis,\n            UpdateFormat::Wix => Self::Wix,\n            UpdateFormat::AppImage => Self::AppImage,\n            UpdateFormat::App => Self::App,\n        }\n    }\n}\n\nimpl From<cargo_packager_updater::Update> for Update {\n    fn from(value: cargo_packager_updater::Update) -> Self {\n        Self {\n            pubkey: value.config.pubkey,\n            body: value.body,\n            current_version: value.current_version,\n            version: value.version,\n            date: value.date.and_then(|d| {\n                d.format(&time::format_description::well_known::Rfc3339)\n                    .ok()\n            }),\n            target: value.target,\n            extract_path: value.extract_path.to_string_lossy().to_string(),\n            download_url: value.download_url.to_string(),\n            signature: value.signature,\n            timeout: value.timeout.map(|t| t.as_millis() as u32),\n            headers: value\n                .headers\n                .into_iter()\n                .map(|(k, v)| {\n                    (\n                        k.map(|k| k.to_string()).unwrap_or_default(),\n                        v.to_str().unwrap_or_default().to_string(),\n                    )\n                })\n                .collect(),\n            format: value.format.into(),\n            windows: value.config.windows.map(Into::into),\n        }\n    }\n}\n"
  },
  {
    "path": "bindings/updater/nodejs/src/lib.rs",
    "content": "use std::{collections::HashMap, str::FromStr, time::Duration};\n\nuse cargo_packager_updater::{\n    http::{HeaderMap, HeaderName, HeaderValue},\n    semver::Version,\n    Updater, UpdaterBuilder,\n};\nuse napi::{\n    bindgen_prelude::AsyncTask,\n    threadsafe_function::{ErrorStrategy, ThreadsafeFunction, ThreadsafeFunctionCallMode},\n    Env, Error, JsArrayBuffer, Result, Status, Task,\n};\n\nmod from_impls;\n\n#[napi_derive::napi(string_enum)]\n#[derive(Default)]\npub enum WindowsUpdateInstallMode {\n    /// Specifies there's a basic UI during the installation process, including a final dialog box at the end.\n    BasicUi,\n    /// The quiet mode means there's no user interaction required.\n    /// Requires admin privileges if the installer does.\n    Quiet,\n    /// Specifies unattended mode, which means the installation only shows a progress bar.\n    #[default]\n    Passive,\n}\n\n#[derive(Clone)]\n#[napi_derive::napi(object)]\npub struct UpdaterWindowsOptions {\n    /// Additional arguments given to the NSIS or WiX installer.\n    pub installer_args: Option<Vec<String>>,\n    /// The installation mode for the update on Windows. Defaults to `passive`.\n    pub install_mode: Option<WindowsUpdateInstallMode>,\n}\n\n#[napi_derive::napi(object)]\npub struct Options {\n    /// The updater endpoints.\n    pub endpoints: Vec<String>,\n    /// Signature public key.\n    pub pubkey: String,\n    /// The Windows options for the updater.\n    pub windows: Option<UpdaterWindowsOptions>,\n    /// The target of the executable.\n    pub target: Option<String>,\n    /// Path to the executable file.\n    pub executable_path: Option<String>,\n    /// Headers to use when checking and when downloading the update.\n    pub headers: Option<HashMap<String, String>>,\n    /// Request timeout in milliseconds.\n    pub timeout: Option<u32>,\n}\n\nimpl Options {\n    fn into_updater(mut self, current_version: Version) -> Result<Updater> {\n        let target = self.target.take();\n        let executable_path = self.executable_path.take();\n        let headers = self.headers.take();\n        let timeout = self.timeout.take();\n        let config: cargo_packager_updater::Config = self.into();\n\n        let mut builder = UpdaterBuilder::new(current_version, config);\n        if let Some(target) = target {\n            builder = builder.target(target);\n        }\n        if let Some(executable_path) = executable_path {\n            builder = builder.executable_path(executable_path);\n        }\n        if let Some(timeout) = timeout {\n            builder = builder.timeout(Duration::from_millis(timeout as u64));\n        }\n        if let Some(headers) = headers {\n            for (key, value) in headers {\n                builder = builder.header(key, value).map_err(|e| {\n                    Error::new(\n                        Status::InvalidArg,\n                        format!(\"Failed to set header, probably invalid header values, {e}\"),\n                    )\n                })?;\n            }\n        }\n\n        builder.build().map_err(|e| {\n            Error::new(\n                Status::GenericFailure,\n                format!(\"Failed to construct updater, {e}\"),\n            )\n        })\n    }\n}\n\n/// Supported update format\n#[napi_derive::napi]\npub enum UpdateFormat {\n    /// The NSIS installer (.exe).\n    Nsis,\n    /// The Microsoft Software Installer (.msi) through WiX Toolset.\n    Wix,\n    /// The Linux AppImage package (.AppImage).\n    AppImage,\n    /// The macOS application bundle (.app).\n    App,\n}\n\n#[napi_derive::napi]\npub struct Update {\n    /// Signing public key\n    pub pubkey: String,\n    /// Version used to check for update\n    pub current_version: String,\n    /// Version announced\n    pub version: String,\n    /// Target\n    pub target: String,\n    /// Extract path\n    pub extract_path: String,\n    /// Download URL announced\n    pub download_url: String,\n    /// Signature announced\n    pub signature: String,\n    /// Request headers\n    pub headers: HashMap<String, String>,\n    /// Update format\n    pub format: UpdateFormat,\n    /// The Windows options for the updater.\n    pub windows: Option<UpdaterWindowsOptions>,\n    /// Update description\n    pub body: Option<String>,\n    /// Update publish date\n    pub date: Option<String>,\n    /// Request timeout\n    pub timeout: Option<u32>,\n}\n\nimpl Update {\n    fn create_update(&self) -> Result<cargo_packager_updater::Update> {\n        Ok(cargo_packager_updater::Update {\n            config: cargo_packager_updater::Config {\n                pubkey: self.pubkey.clone(),\n                windows: self.windows.clone().map(Into::into),\n                ..Default::default()\n            },\n            body: self.body.clone(),\n            current_version: self.current_version.clone(),\n            version: self.version.clone(),\n            date: None,\n            target: self.target.clone(),\n            extract_path: self.extract_path.clone().into(),\n            download_url: self.download_url.parse().map_err(|e| {\n                Error::new(\n                    Status::GenericFailure,\n                    format!(\"Internal error, couldn't convert string to Url struct, {e}\"),\n                )\n            })?,\n            signature: self.signature.clone(),\n            timeout: self.timeout.map(|t| Duration::from_millis(t as u64)),\n            headers: {\n                let mut map = HeaderMap::new();\n                for (key, value) in &self.headers {\n                    map.insert(HeaderName::from_str(key).map_err(|e| {\n                        Error::new(\n                            Status::GenericFailure,\n                            format!(\"Internal error, couldn't construct header name from str , {e}\"),\n                        )\n                    })?, HeaderValue::from_str(value).map_err(|e| {\n                        Error::new(\n                            Status::GenericFailure,\n                            format!(\"Internal error, couldn't construct header value from str , {e}\"),\n                        )\n                    })?);\n                }\n\n                map\n            },\n            format: self.format.into(),\n        })\n    }\n}\n\ntype TaskCallbackFunction<T> = Option<ThreadsafeFunction<T, ErrorStrategy::Fatal>>;\n\npub struct DownloadTask {\n    update: cargo_packager_updater::Update,\n    on_chunk: TaskCallbackFunction<(u32, Option<u32>)>,\n    on_download_finished: TaskCallbackFunction<()>,\n}\n\nimpl DownloadTask {\n    pub fn create(\n        update: &Update,\n        on_chunk: TaskCallbackFunction<(u32, Option<u32>)>,\n        on_download_finished: TaskCallbackFunction<()>,\n    ) -> Result<Self> {\n        Ok(Self {\n            update: update.create_update()?,\n            on_chunk,\n            on_download_finished,\n        })\n    }\n}\n\nimpl Task for DownloadTask {\n    type Output = Vec<u8>;\n    type JsValue = JsArrayBuffer;\n\n    fn compute(&mut self) -> Result<Self::Output> {\n        let on_chunk = |chunk_len: usize, content_len: Option<u64>| {\n            if let Some(on_chunk) = &self.on_chunk {\n                on_chunk.call(\n                    (chunk_len as _, content_len.map(|v| v as _)),\n                    ThreadsafeFunctionCallMode::NonBlocking,\n                );\n            }\n        };\n\n        let on_finish = || {\n            if let Some(on_download_finished) = &self.on_download_finished {\n                on_download_finished.call((), ThreadsafeFunctionCallMode::NonBlocking);\n            }\n        };\n\n        self.update\n            .download_extended(on_chunk, on_finish)\n            .map_err(|e| Error::new(Status::GenericFailure, e))\n    }\n\n    fn resolve(&mut self, env: Env, output: Self::Output) -> Result<Self::JsValue> {\n        let mut buffer = env.create_arraybuffer(output.len())?;\n        unsafe { std::ptr::copy(output.as_ptr(), buffer.as_mut_ptr(), output.len()) };\n\n        Ok(buffer.into_raw())\n    }\n}\n\npub struct InstallTask {\n    update: cargo_packager_updater::Update,\n    bytes: Option<Vec<u8>>,\n}\n\nimpl InstallTask {\n    pub fn create(update: &Update, bytes: Vec<u8>) -> Result<Self> {\n        Ok(Self {\n            update: update.create_update()?,\n            bytes: Some(bytes),\n        })\n    }\n}\n\nimpl Task for InstallTask {\n    type Output = ();\n    type JsValue = ();\n\n    fn compute(&mut self) -> Result<Self::Output> {\n        self.update\n            .install(self.bytes.take().unwrap())\n            .map_err(|e| Error::new(Status::GenericFailure, e))\n    }\n\n    fn resolve(&mut self, _env: Env, _output: Self::Output) -> Result<Self::JsValue> {\n        Ok(())\n    }\n}\n\npub struct DownloadAndInstallTask {\n    download_task: DownloadTask,\n}\n\nimpl DownloadAndInstallTask {\n    pub fn new(download_task: DownloadTask) -> Self {\n        Self { download_task }\n    }\n}\n\nimpl Task for DownloadAndInstallTask {\n    type Output = ();\n    type JsValue = ();\n\n    fn compute(&mut self) -> Result<Self::Output> {\n        let bytes = self.download_task.compute()?;\n        self.download_task\n            .update\n            .install(bytes)\n            .map_err(|e| Error::new(Status::GenericFailure, e))\n    }\n\n    fn resolve(&mut self, _env: Env, _output: Self::Output) -> Result<Self::JsValue> {\n        Ok(())\n    }\n}\n\npub struct CheckUpdateTask {\n    updater: Updater,\n}\n\nimpl CheckUpdateTask {\n    pub fn create(current_version: String, options: Options) -> Result<Self> {\n        let current_version = current_version.parse().map_err(|e| {\n            Error::new(\n                Status::InvalidArg,\n                format!(\"Failed to parse string as a valid semver, {e}\"),\n            )\n        })?;\n\n        let updater = options.into_updater(current_version)?;\n\n        Ok(Self { updater })\n    }\n}\n\nimpl Task for CheckUpdateTask {\n    type Output = Option<cargo_packager_updater::Update>;\n    type JsValue = Option<Update>;\n\n    fn compute(&mut self) -> Result<Self::Output> {\n        self.updater.check().map_err(|e| {\n            Error::new(\n                Status::GenericFailure,\n                format!(\"Failed to check for update, {e}\"),\n            )\n        })\n    }\n\n    fn resolve(&mut self, _env: Env, output: Self::Output) -> Result<Self::JsValue> {\n        Ok(output.map(Into::into))\n    }\n}\n\n#[napi_derive::napi]\nimpl Update {\n    #[napi(\n        ts_args_type = \"onChunk?: (chunkLength: number, contentLength: number | null) => void, onDownloadFinished?: () => void\",\n        ts_return_type = \"Promise<ArrayBuffer>\"\n    )]\n    pub fn download(\n        &self,\n        on_chunk: TaskCallbackFunction<(u32, Option<u32>)>,\n        on_download_finish: TaskCallbackFunction<()>,\n    ) -> Result<AsyncTask<DownloadTask>> {\n        DownloadTask::create(self, on_chunk, on_download_finish).map(AsyncTask::new)\n    }\n\n    #[napi(ts_return_type = \"Promise<void>\", ts_args_type = \"buffer: ArrayBuffer\")]\n    pub fn install(&self, bytes: JsArrayBuffer) -> Result<AsyncTask<InstallTask>> {\n        let bytes = bytes.into_value()?;\n        let bytes = bytes.as_ref().to_vec();\n        InstallTask::create(self, bytes).map(AsyncTask::new)\n    }\n\n    #[napi(\n        ts_args_type = \"onChunk?: (chunkLength: number, contentLength?: number) => void, onDownloadFinished?: () => void\",\n        ts_return_type = \"Promise<void>\"\n    )]\n    pub fn download_and_install(\n        &self,\n        on_chunk: TaskCallbackFunction<(u32, Option<u32>)>,\n        on_download_finish: TaskCallbackFunction<()>,\n    ) -> Result<AsyncTask<DownloadAndInstallTask>> {\n        let download_task = DownloadTask::create(self, on_chunk, on_download_finish)?;\n        Ok(AsyncTask::new(DownloadAndInstallTask::new(download_task)))\n    }\n}\n\n#[napi_derive::napi(ts_return_type = \"Promise<Update | null>\")]\npub fn check_update(\n    current_version: String,\n    options: Options,\n) -> Result<AsyncTask<CheckUpdateTask>> {\n    Ok(AsyncTask::new(CheckUpdateTask::create(\n        current_version,\n        options,\n    )?))\n}\n"
  },
  {
    "path": "crates/config-schema-generator/Cargo.toml",
    "content": "[package]\nname = \"cargo-packager-config-schema-generator\"\nversion = \"0.0.0\"\npublish = false\nauthors.workspace = true\nedition.workspace = true\nlicense.workspace = true\nrepository.workspace = true\n\n[build-dependencies]\ncargo-packager = { path = \"../packager\", features = [\"schema\"] }\nserde_json = { workspace = true }\nschemars = { workspace = true }\n"
  },
  {
    "path": "crates/config-schema-generator/build.rs",
    "content": "// Copyright 2023-2023 CrabNebula Ltd.\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{\n    error::Error,\n    fs::File,\n    io::{BufWriter, Write},\n    path::PathBuf,\n    process::Command,\n};\n\npub fn main() -> Result<(), Box<dyn Error>> {\n    let schema = schemars::schema_for!(cargo_packager::Config);\n    let schema_str = serde_json::to_string_pretty(&schema).unwrap();\n    let crate_dir = PathBuf::from(std::env::var(\"CARGO_MANIFEST_DIR\")?);\n    for path in [\n        \"../packager/schema.json\",\n        \"../../bindings/packager/nodejs/schema.json\",\n    ] {\n        let mut schema_file = BufWriter::new(File::create(crate_dir.join(path))?);\n        write!(schema_file, \"{schema_str}\")?;\n    }\n\n    let _ = Command::new(\"node\")\n        .arg(\"./generate-config-type.js\")\n        .current_dir(\"../../bindings/packager/nodejs\")\n        .output();\n\n    Ok(())\n}\n"
  },
  {
    "path": "crates/config-schema-generator/src/main.rs",
    "content": "// Copyright 2023-2023 CrabNebula Ltd.\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nfn main() {}\n"
  },
  {
    "path": "crates/packager/CHANGELOG.md",
    "content": "# Changelog\n\n## \\[0.11.8]\n\n- [`6e6a10c`](https://www.github.com/crabnebula-dev/cargo-packager/commit/6e6a10cc1692973293966034dc4b798e3976d094) ([#321](https://www.github.com/crabnebula-dev/cargo-packager/pull/321)) Allow explicitly specifying the Package name for the .deb bundle.\n- [`8488d86`](https://www.github.com/crabnebula-dev/cargo-packager/commit/8488d868935166e873474743c346c2724205d73e) ([#377](https://www.github.com/crabnebula-dev/cargo-packager/pull/377)) Fixed a bug where \"binaries\" parameter in Cargo.toml would be ignored and all targets would be packaged.\n- [`b2b4916`](https://www.github.com/crabnebula-dev/cargo-packager/commit/b2b4916d1b062272fc7e34b5ed55b4fe8c8cd03a) ([#376](https://www.github.com/crabnebula-dev/cargo-packager/pull/376)) Fix bug that prevents reading macos signing certificates from environment variables.\n- [`c34de36`](https://www.github.com/crabnebula-dev/cargo-packager/commit/c34de365705db150eb101caa94adf42eff74f71a) ([#365](https://www.github.com/crabnebula-dev/cargo-packager/pull/365)) Change nsi template from using `association.ext` to `association.extensions`, to match struct field in `FileAssociation`.\n  This allows file associations to be generated in `.nsi` files, and therefore in the final NSIS installer.\n\n## \\[0.11.7]\n\n- [`d49b606`](https://www.github.com/crabnebula-dev/cargo-packager/commit/d49b606ba8a612c833233ec8a6061481a2118639) ([#353](https://www.github.com/crabnebula-dev/cargo-packager/pull/353)) Allow using notarization credentials stored on the Keychain by providing the `APPLE_KEYCHAIN_PROFILE` environment variable. See `xcrun notarytool store-credentials` for more information.\n- [`b337564`](https://www.github.com/crabnebula-dev/cargo-packager/commit/b337564c0e5a9de966b4124890dddea1e353acb4) ([#362](https://www.github.com/crabnebula-dev/cargo-packager/pull/362)) Updated linuxdeploy's AppImage plugin to not require libfuse on the user's system anymore.\n\n## \\[0.11.6]\n\n- [`b81b81f`](https://www.github.com/crabnebula-dev/cargo-packager/commit/b81b81fbd7fd185edfc7652f535d0cfacb786ac9) ([#354](https://www.github.com/crabnebula-dev/cargo-packager/pull/354)) Changed the download URL of a dependency of the AppImage bundler to Tauri's mirror to resolve 404 errors.\n- [`735d6c4`](https://www.github.com/crabnebula-dev/cargo-packager/commit/735d6c4745911793cbcf5d929d8da288840bcf24) ([#345](https://www.github.com/crabnebula-dev/cargo-packager/pull/345)) Fixed a typo on the `digest_algorithm` config (was `digest-algorithim`).\n- [`5205088`](https://www.github.com/crabnebula-dev/cargo-packager/commit/5205088cd78412fb6cbe5e48a715524fcc5a2ee7) ([#340](https://www.github.com/crabnebula-dev/cargo-packager/pull/340)) Enhance sign error message.\n- [`55924d3`](https://www.github.com/crabnebula-dev/cargo-packager/commit/55924d3522c4ab1cfcb4436044e5ebad8adf241c) ([#334](https://www.github.com/crabnebula-dev/cargo-packager/pull/334)) Migrate from `winreg` crate to `windows-registry`. This adds new variants to the packager's `Error` type.\n\n## \\[0.11.5]\n\n- [`17194a9`](https://www.github.com/crabnebula-dev/cargo-packager/commit/17194a92aabd59c9e075105072ff939f5d55a107) ([#313](https://www.github.com/crabnebula-dev/cargo-packager/pull/313)) Added `linux > generateDesktopEntry` config to allow disabling generating a .desktop file on Linux bundles (defaults to true).\n- [`17c52f0`](https://www.github.com/crabnebula-dev/cargo-packager/commit/17c52f057d78340983689af3c00b1f2aeff3c417) ([#289](https://www.github.com/crabnebula-dev/cargo-packager/pull/289)) Added support to embedding additional apps in the macOS app bundle.\n- [`17c52f0`](https://www.github.com/crabnebula-dev/cargo-packager/commit/17c52f057d78340983689af3c00b1f2aeff3c417) ([#289](https://www.github.com/crabnebula-dev/cargo-packager/pull/289)) Added support to adding an `embedded.provisionprofile` file to the macOS bundle.\n- [`e010574`](https://www.github.com/crabnebula-dev/cargo-packager/commit/e010574c2efa4a1aa6b8e475a62bec46f24f2bc5) ([#318](https://www.github.com/crabnebula-dev/cargo-packager/pull/318)) Add `background-app` config setting for macOS to set `LSUIElement` to `true`.\n\n## \\[0.11.4]\n\n- [`29b60a9`](https://www.github.com/crabnebula-dev/cargo-packager/commit/29b60a97ec14ef87aee7537fa7fbd848f853ac32) ([#305](https://www.github.com/crabnebula-dev/cargo-packager/pull/305)) Fix AppImage bundle when main binary name has spaces.\n\n## \\[0.11.3]\n\n- [`82e690d`](https://www.github.com/crabnebula-dev/cargo-packager/commit/82e690dfce6109531391e683c8b486d0f39ea335) ([#300](https://www.github.com/crabnebula-dev/cargo-packager/pull/300)) Fix the `Exec` entry on the Linux .desktop file when the binary name contains spaces.\n\n## \\[0.11.2]\n\n- [`fea80d5`](https://www.github.com/crabnebula-dev/cargo-packager/commit/fea80d5760882e6cdc21c8ed2f82d323e0598926) ([#264](https://www.github.com/crabnebula-dev/cargo-packager/pull/264)) Fix `pacman` package failing to install when source directory contained whitespace.\n\n## \\[0.11.1]\n\n- [`4523722`](https://www.github.com/crabnebula-dev/cargo-packager/commit/4523722d0808faef4a91dbb227badd0354f4c71a) ([#283](https://www.github.com/crabnebula-dev/cargo-packager/pull/283)) Fixes resources paths on NSIS when cross compiling.\n\n## \\[0.11.0]\n\n- [`41b05d0`](https://www.github.com/crabnebula-dev/cargo-packager/commit/41b05d08a635d593df4cf4eefbe921b92ace77b7) ([#277](https://www.github.com/crabnebula-dev/cargo-packager/pull/277)) Respect `target-triple` config option when packaging rust binaries.\n- [`41b05d0`](https://www.github.com/crabnebula-dev/cargo-packager/commit/41b05d08a635d593df4cf4eefbe921b92ace77b7) ([#277](https://www.github.com/crabnebula-dev/cargo-packager/pull/277)) Add `--target` flag to specify target triple to package.\n\n## \\[0.10.3]\n\n- [`3ee764d`](https://www.github.com/crabnebula-dev/cargo-packager/commit/3ee764d9193ae22331aa5894a1821453e9542992) ([#270](https://www.github.com/crabnebula-dev/cargo-packager/pull/270)) Fixes AppImage bundling failing due to missing `/usr/lib64` directory.\n- [`ab41e6d`](https://www.github.com/crabnebula-dev/cargo-packager/commit/ab41e6d94af89ec721a0047636597682bd6d90f6) ([#269](https://www.github.com/crabnebula-dev/cargo-packager/pull/269)) Fix using the crate as a library without `cli` feature flag\n\n## \\[0.10.2]\n\n- [`f836afa`](https://www.github.com/crabnebula-dev/cargo-packager/commit/f836afa699b2da8a55432ce9de1cbccbffb705fb) ([#267](https://www.github.com/crabnebula-dev/cargo-packager/pull/267)) Include notarytool log output on error message in case notarization fails.\n- [`21441f3`](https://www.github.com/crabnebula-dev/cargo-packager/commit/21441f30c5a258b73926ba7a7d8126d6bf47a662) ([#262](https://www.github.com/crabnebula-dev/cargo-packager/pull/262)) Fixed dmg failed to bundle the application when out-dir does not exist.\n\n### Dependencies\n\n- Upgraded to `cargo-packager-utils@0.1.1`\n\n## \\[0.10.1]\n\n- [`522f23b`](https://www.github.com/crabnebula-dev/cargo-packager/commit/522f23bd867b037eeec81c43295aafd38ebe60ec) ([#258](https://www.github.com/crabnebula-dev/cargo-packager/pull/258)) Update NSIS installer template URL.\n- [`bce99ae`](https://www.github.com/crabnebula-dev/cargo-packager/commit/bce99aecb4160291a026dcd4750055f9079099f8) ([#260](https://www.github.com/crabnebula-dev/cargo-packager/pull/260)) Fix NSIS uninstaller removing the uninstall directory even if it was not empty.\n\n## \\[0.10.0]\n\n- [`c6207bb`](https://www.github.com/crabnebula-dev/cargo-packager/commit/c6207bba042a8a0184ddb7e12650a4cd8f415c23) ([#254](https://www.github.com/crabnebula-dev/cargo-packager/pull/254)) Allow Linux dependencies to be specified via a file path instead of just a direct String.\n  This enables the list of dependencies to by dynamically generated for both Debian `.deb` packages and pacman packages,\n  which can relieve the app developer from the burden of manually maintaining a fixed list of dependencies.\n- [`de4dcca`](https://www.github.com/crabnebula-dev/cargo-packager/commit/de4dccaca4ae758d3adde517cc415a002873e642) ([#256](https://www.github.com/crabnebula-dev/cargo-packager/pull/256)) Automatically add an Exec arg (field code) in the `.desktop` file.\n\n  This adds an `{exec_arg}` field to the default `main.desktop` template.\n  This field is populated with a sane default value, based on the\n  `deep_link_protocols` or `file_associations` in the `Config` struct.\n\n  This allows an installed Debian package to be invoked by other\n  applications with URLs or files as arguments, as expected.\n\n## \\[0.9.1]\n\n- [`44a19ea`](https://www.github.com/crabnebula-dev/cargo-packager/commit/44a19eae1f5f26b1bd10ba84dd6eb3d856609a67) ([#246](https://www.github.com/crabnebula-dev/cargo-packager/pull/246)) On macOS, fix notarization skipping needed environment variables when macos specific config has been specified in the config file.\n\n## \\[0.9.0]\n\n- [`ab53974`](https://www.github.com/crabnebula-dev/cargo-packager/commit/ab53974b683ce282202e1a550c551eed951e9ca7) ([#235](https://www.github.com/crabnebula-dev/cargo-packager/pull/235)) Added deep link support.\n\n## \\[0.8.1]\n\n- [`1375380`](https://www.github.com/crabnebula-dev/cargo-packager/commit/1375380c7c9d2adf55ab18a2ce23917849967995)([#196](https://www.github.com/crabnebula-dev/cargo-packager/pull/196)) Always show shell commands output for `beforePackageCommand` and `beforeEachPackagingCommand` .\n\n## \\[0.8.0]\n\n- [`2164d02`](https://www.github.com/crabnebula-dev/cargo-packager/commit/2164d022f5705e59a189007aec7c99cce98136d8)([#198](https://www.github.com/crabnebula-dev/cargo-packager/pull/198)) Allow packaging the macOS app bundle on Linux and Windows hosts (without codesign support).\n- [`3057a4a`](https://www.github.com/crabnebula-dev/cargo-packager/commit/3057a4a8440bc4dc897f3038ac821ed181644d43)([#197](https://www.github.com/crabnebula-dev/cargo-packager/pull/197)) Added `Config::binaries_dir` and `--binaries-dir` so you can specify the location of the binaries without modifying the output directory.\n- [`4c4d919`](https://www.github.com/crabnebula-dev/cargo-packager/commit/4c4d9194fb0bd2a814f46336747e643b1e208b52)([#195](https://www.github.com/crabnebula-dev/cargo-packager/pull/195)) Error out if we cannot find a configuration file.\n- [`b04332c`](https://www.github.com/crabnebula-dev/cargo-packager/commit/b04332c8fc61427dc002a40d9d46bc5f930025c2)([#194](https://www.github.com/crabnebula-dev/cargo-packager/pull/194)) Fixes a crash when packaging `.app` if an empty file is included in the bundle.\n- [`3057a4a`](https://www.github.com/crabnebula-dev/cargo-packager/commit/3057a4a8440bc4dc897f3038ac821ed181644d43)([#197](https://www.github.com/crabnebula-dev/cargo-packager/pull/197)) Added `--out-dir/-o` flags and removed the positional argument to specify where to ouput packages, use the newly added flags instead.\n- [`2164d02`](https://www.github.com/crabnebula-dev/cargo-packager/commit/2164d022f5705e59a189007aec7c99cce98136d8)([#198](https://www.github.com/crabnebula-dev/cargo-packager/pull/198)) Renamed `PackageOuput` to `PackageOutput` and added `PackageOutput::new`.\n\n## \\[0.7.0]\n\n- [`cd8898a`](https://www.github.com/crabnebula-dev/cargo-packager/commit/cd8898a93b66a4aae050fa1006089c3c3b5646f9)([#187](https://www.github.com/crabnebula-dev/cargo-packager/pull/187)) Added codesign certificate and notarization credentials configuration options under the `macos` config (for programatic usage, taking precedence over environment variables).\n\n## \\[0.6.1]\n\n- [`2f1029b`](https://www.github.com/crabnebula-dev/cargo-packager/commit/2f1029b2032ac44fd3f3df34307554feb17043b7)([#185](https://www.github.com/crabnebula-dev/cargo-packager/pull/185)) Fix bundling NSIS on Linux and macOS failing due to the verbose flag.\n\n## \\[0.6.0]\n\n- [`57b379a`](https://www.github.com/crabnebula-dev/cargo-packager/commit/57b379ad1d9029e767848fda99d4eb6415afe51a)([#148](https://www.github.com/crabnebula-dev/cargo-packager/pull/148)) Added config option to control excluded libs when packaging AppImage\n- [`947e032`](https://www.github.com/crabnebula-dev/cargo-packager/commit/947e0328c89d6f043c3ef1b1db5d2252d4f072a5) Fix CLI failing with `Failed to read cargo metadata: cargo metadata` for non-rust projects.\n\n## \\[0.5.0]\n\n- [`9bdb953`](https://www.github.com/crabnebula-dev/cargo-packager/commit/9bdb953f1b48c8d69d86e9e42295cd36453c1648)([#137](https://www.github.com/crabnebula-dev/cargo-packager/pull/137)) Add Arch Linux package manager, `pacman` support for cargo packager.\n- [`a29943e`](https://www.github.com/crabnebula-dev/cargo-packager/commit/a29943e8c95d70e8b77c23021ce52f6ee13314c8)([#140](https://www.github.com/crabnebula-dev/cargo-packager/pull/140)) Fix codesigning failing on macOS under certain circumstances when the order in which files were signed was not\n  deterministic and nesting required signing files nested more deeply first.\n\n### Dependencies\n\n- Upgraded to `cargo-packager-utils@0.1.0`\n\n## \\[0.4.5]\n\n- [`f08e4b8`](https://www.github.com/crabnebula-dev/cargo-packager/commit/f08e4b8972b072617fdb78f11e222427e49ebe8e) Fix the signing and notarization process for MacOS bundles\n- [`bfa3b00`](https://www.github.com/crabnebula-dev/cargo-packager/commit/bfa3b00cf1087b2ee1e93d9c57b6b577f6294891)([#126](https://www.github.com/crabnebula-dev/cargo-packager/pull/126)) Add `priority` and `section` options in Debian config\n\n## \\[0.4.4]\n\n- [`3b3ce76`](https://www.github.com/crabnebula-dev/cargo-packager/commit/3b3ce76da0581cf8d553d6edeb0df24f896c62a6)([#128](https://www.github.com/crabnebula-dev/cargo-packager/pull/128)) Fix file download not working on macOS and Windows (arm).\n\n## \\[0.4.3]\n\n- [`2a50c8e`](https://www.github.com/crabnebula-dev/cargo-packager/commit/2a50c8ea734193036db0ab461f9005ea904cf4b7)([#124](https://www.github.com/crabnebula-dev/cargo-packager/pull/124)) Fix packaing of external binaries.\n\n## \\[0.4.2]\n\n- [`c18bf3e`](https://www.github.com/crabnebula-dev/cargo-packager/commit/c18bf3e77f91c1c4797992b25902753deee5c986)([#117](https://www.github.com/crabnebula-dev/cargo-packager/pull/117)) Fix the `non-standard-file-perm` and `non-standard-dir-perm` issue in Debian packages\n\n## \\[0.4.1]\n\n- [`7b083a8`](https://www.github.com/crabnebula-dev/cargo-packager/commit/7b083a8c2ae709659c03a1069d96c3a8391e0674)([#99](https://www.github.com/crabnebula-dev/cargo-packager/pull/99)) Add glob patterns support for the icons option in config.\n- [`7e05d24`](https://www.github.com/crabnebula-dev/cargo-packager/commit/7e05d24a697230b1f53ee5ee2f7d217047089d97)([#109](https://www.github.com/crabnebula-dev/cargo-packager/pull/109)) Check if required files/tools for packaging are outdated or mis-hashed and redownload them.\n- [`ea6c31b`](https://www.github.com/crabnebula-dev/cargo-packager/commit/ea6c31b1a3b56bb5408a78f1b2d6b2a2d9ce1161)([#114](https://www.github.com/crabnebula-dev/cargo-packager/pull/114)) Fix NSIS uninstaller leaving resources behind and failing to remove the installation directory.\n\n## \\[0.4.0]\n\n- [`ecde3fb`](https://www.github.com/crabnebula-dev/cargo-packager/commit/ecde3fb71a8f120e71d4781c11214db750042cc4)([#58](https://www.github.com/crabnebula-dev/cargo-packager/pull/58)) Added `files` configuration under `AppImageConfig` for adding custom files on the AppImage's AppDir.\n- [`ecde3fb`](https://www.github.com/crabnebula-dev/cargo-packager/commit/ecde3fb71a8f120e71d4781c11214db750042cc4)([#58](https://www.github.com/crabnebula-dev/cargo-packager/pull/58)) Renamed binary `filename` property to `path`, which supports absolute paths.\n- [`f04c17f`](https://www.github.com/crabnebula-dev/cargo-packager/commit/f04c17f72a4af306f47065aff405c4bd0f7b6442)([#87](https://www.github.com/crabnebula-dev/cargo-packager/pull/87)) Add `config.dmg` to configure the DMG on macOS.\n- [`21a6c9e`](https://www.github.com/crabnebula-dev/cargo-packager/commit/21a6c9ef4ddbefe9a6e6c5abf287f2ad993edffb)([#84](https://www.github.com/crabnebula-dev/cargo-packager/pull/84)) Mark most of the types as `non_exhaustive` to allow adding more field later on without having to break downstream users use the newly added helper methods on these types to modify the corresponding fields in-place.\n- [`db75777`](https://www.github.com/crabnebula-dev/cargo-packager/commit/db75777d2799ca37217d568befad39b9377cfa2a) Add `config.windows.sign_command` which can be used to override signing command on windows and allows usage of tools other than `signtool.exe`.\n\n## \\[0.3.0]\n\n- [`65b8c20`](https://www.github.com/crabnebula-dev/cargo-packager/commit/65b8c20a96877038daa4907b80cd96f96e0bfe33)([#54](https://www.github.com/crabnebula-dev/cargo-packager/pull/54)) Code sign binaries and frameworks on macOS.\n- [`7ef6b7c`](https://www.github.com/crabnebula-dev/cargo-packager/commit/7ef6b7c0186e79243240cb2d1a1846fda41a1b54)([#50](https://www.github.com/crabnebula-dev/cargo-packager/pull/50)) Set `root` as the owner of control files and package files in `deb` package.\n- [`8cc5b05`](https://www.github.com/crabnebula-dev/cargo-packager/commit/8cc5b05eb3eb124b385d406329eee379349faa86)([#53](https://www.github.com/crabnebula-dev/cargo-packager/pull/53)) Fixed an error message that the source path does not exist when packaging .app\n- [`274a6be`](https://www.github.com/crabnebula-dev/cargo-packager/commit/274a6bec553f273934347a18e0d6e2e1ec61bbeb)([#49](https://www.github.com/crabnebula-dev/cargo-packager/pull/49)) Read `HTTP_PROXY` env var when downloading resources.\n- [`6ed1312`](https://www.github.com/crabnebula-dev/cargo-packager/commit/6ed1312926d70cf449e7beddacb56a17e51a25ac)([#52](https://www.github.com/crabnebula-dev/cargo-packager/pull/52)) Read the `APPLE_TEAM_ID` environment variable for macOS notarization arguments.\n- [`65b8c20`](https://www.github.com/crabnebula-dev/cargo-packager/commit/65b8c20a96877038daa4907b80cd96f96e0bfe33)([#54](https://www.github.com/crabnebula-dev/cargo-packager/pull/54)) Remove extended attributes on the macOS app bundle using `xattr -cr $PATH`.\n\n## \\[0.2.0]\n\n- [`dde1ab3`](https://www.github.com/crabnebula-dev/cargo-packager/commit/dde1ab34b59ee614fc24e47a5caa8ebc04d92a08)([#43](https://www.github.com/crabnebula-dev/cargo-packager/pull/43)) Remove the deprecated `cargo-packager-config` dependency.\n\n## \\[0.1.2]\n\n- [`1809f10`](https://www.github.com/crabnebula-dev/cargo-packager/commit/1809f10b4fd1720fd740196f67c3c980ade0a6bd) Respect the `config.enabled` option.\n\n### Dependencies\n\n- Upgraded to `cargo-packager-config@0.2.0`\n\n## \\[0.1.1]\n\n- [`2d8b8d7`](https://www.github.com/crabnebula-dev/cargo-packager/commit/2d8b8d7c1af73202639449a00dbc51bf171effc7) Initial Release\n"
  },
  {
    "path": "crates/packager/Cargo.toml",
    "content": "[package]\nname = \"cargo-packager\"\nversion = \"0.11.8\"\ndescription = \"Executable packager and bundler distributed as a CLI and library.\"\nauthors = [\n    \"CrabNebula Ltd.\",\n    \"Tauri Programme within The Commons Conservancy\",\n    \"George Burton <burtonageo@gmail.com>\",\n]\nkeywords = [\"bundle\", \"package\", \"cargo\"]\ncategories = [\n    \"command-line-interface\",\n    \"command-line-utilities\",\n    \"development-tools::cargo-plugins\",\n    \"development-tools::build-utils\",\n    \"os\",\n]\nedition = { workspace = true }\nlicense = { workspace = true }\nrepository = { workspace = true }\n\n[[bin]]\nname = \"cargo-packager\"\n# path = \"src/bins/cli.rs\"\nrequired-features = [\"cli\"]\n\n[package.metadata.docs.rs]\nrustdoc-args = [\"--cfg\", \"doc_cfg\"]\ndefault-target = \"x86_64-unknown-linux-gnu\"\ntargets = [\n    \"x86_64-pc-windows-msvc\",\n    \"x86_64-unknown-linux-gnu\",\n    \"x86_64-apple-darwin\",\n]\n\n[lints.rust]\n# cfg(doc_cfg) is used for docs.rs detection. see above\nunexpected_cfgs = { level = \"warn\", check-cfg = ['cfg(doc_cfg)'] }\n\n[features]\ndefault = [\"cli\", \"rustls-tls\"]\ncli = [\"clap\", \"dep:tracing-subscriber\"]\nschema = [\"schemars\", \"cargo-packager-utils/schema\"]\nclap = [\"dep:clap\", \"cargo-packager-utils/clap\"]\nnative-tls = [\"ureq/native-tls\"]\nnative-tls-vendored = [\"native-tls\", \"native-tls/vendored\"]\nrustls-tls = [\"ureq/tls\"]\n\n[dependencies]\nthiserror = { workspace = true }\nserde = { workspace = true }\nserde_json = { workspace = true }\ndunce = { workspace = true }\ndirs = { workspace = true }\nsemver = { workspace = true }\nbase64 = { workspace = true }\nclap = { workspace = true, optional = true, features = [\"env\"] }\ntracing = { workspace = true }\ntracing-subscriber = { version = \"0.3\", optional = true, features = [\n    \"env-filter\",\n] }\ntoml = \"0.9\"\ncargo_metadata = \"0.18\"\nureq = { version = \"2.10\", default-features = false }\nhex = \"0.4\"\nsha1 = \"0.10\"\nsha2 = \"0.10\"\nzip = { version = \"0.6\", default-features = false, features = [\"deflate\"] }\nhandlebars = \"6.0\"\nglob = \"0.3\"\nrelative-path = \"2\"\nwalkdir = \"2\"\nos_pipe = \"1\"\nminisign = \"0.7\"\ntar = { workspace = true }\nflate2 = \"1.0\"\nstrsim = \"0.11\"\nschemars = { workspace = true, optional = true }\nnative-tls = { version = \"0.2\", optional = true }\ncargo-packager-utils = { version = \"0.1.1\", path = \"../utils\", features = [\n    \"serde\",\n] }\nicns = { package = \"tauri-icns\", version = \"0.1\" }\ntime = { workspace = true, features = [\"formatting\"] }\nimage = { version = \"0.25\", default-features = false, features = [\"rayon\", \"bmp\", \"ico\", \"png\", \"jpeg\"] }\ntempfile = \"3\"\nplist = \"1\"\n\n[target.\"cfg(target_os = \\\"windows\\\")\".dependencies]\nwindows-registry = \"0.6\"\nonce_cell = \"1\"\nuuid = { version = \"1\", features = [\"v4\", \"v5\"] }\nregex = \"1\"\n\n[target.\"cfg(target_os = \\\"windows\\\")\".dependencies.windows-sys]\nversion = \"0.61\"\nfeatures = [\"Win32_System_SystemInformation\", \"Win32_System_Diagnostics_Debug\"]\n\n[target.\"cfg(any(target_os = \\\"linux\\\", target_os = \\\"dragonfly\\\", target_os = \\\"freebsd\\\", target_os = \\\"netbsd\\\", target_os = \\\"openbsd\\\"))\".dependencies]\nmd5 = \"0.8\"\nheck = \"0.5\"\nar = \"0.9\"\n"
  },
  {
    "path": "crates/packager/LICENSE_APACHE-2.0",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n"
  },
  {
    "path": "crates/packager/LICENSE_MIT",
    "content": "MIT License\n\nCopyright (c) 2023 - Present CrabNebula Ltd.\nCopyright (c) 2019 - 2023 Tauri Programme within The Commons Conservancy\nCopyright (c) 2017 - 2019 Cargo-Bundle developers\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "crates/packager/README.md",
    "content": "# cargo-packager\n\n<img src=\".github/splash.png\" alt=\"cargo-packager splash\" />\n\nExecutable packager, bundler and updater. A cli tool and library to generate installers or app bundles for your executables.\nIt also comes with useful addons:\n\n- an [updater](https://docs.rs/cargo-packager-updater)\n- a [resource resolver](https://docs.rs/cargo-packager-resource-resolver)\n\n#### Supported packages:\n\n- macOS\n  - DMG (.dmg)\n  - Bundle (.app)\n- Linux\n  - Debian package (.deb)\n  - AppImage (.AppImage)\n  - Pacman (.tar.gz and PKGBUILD)\n- Windows\n  - NSIS (.exe)\n  - MSI using WiX Toolset (.msi)\n\n### CLI\n\nThe packager is distributed on crates.io as a cargo subcommand, you can install it using cargo:\n\n```sh\ncargo install cargo-packager --locked\n```\n\nYou then need to configure your app so the cli can recognize it. Configuration can be done in `Packager.toml` or `packager.json` in your project or modify Cargo.toml and include this snippet:\n\n```toml\n[package.metadata.packager]\nbefore-packaging-command = \"cargo build --release\"\n```\n\nOnce, you are done configuring your app, run:\n\n```sh\ncargo packager --release\n```\n\n### Configuration\n\nBy default, the packager reads its configuration from `Packager.toml` or `packager.json` if it exists, and from `package.metadata.packager` table in `Cargo.toml`.\nYou can also specify a custom configuration using the `-c/--config` cli argument.\n\nFor a full list of configuration options, see https://docs.rs/cargo-packager/latest/cargo_packager/config/struct.Config.html.\n\nYou could also use the [schema](./schema.json) file from GitHub to validate your configuration or have auto completions in your IDE.\n\n### Building your application before packaging\n\nBy default, the packager doesn't build your application, so if your app requires a compilation step, the packager has an option to specify a shell command to be executed before packaing your app, `beforePackagingCommand`.\n\n### Cargo profiles\n\nBy default, the packager looks for binaries built using the `debug` profile, if your `beforePackagingCommand` builds your app using `cargo build --release`, you will also need to\nrun the packager in release mode `cargo packager --release`, otherwise, if you have a custom cargo profile, you will need to specify it using `--profile` cli arg `cargo packager --profile custom-release-profile`.\n\n### Library\n\nThis crate is also published to crates.io as a library that you can integrate into your tooling, just make sure to disable the default-feature flags.\n\n```sh\ncargo add cargo-packager --no-default-features\n```\n\n#### Feature flags\n\n- **`cli`**: Enables the cli specifc features and dependencies. Enabled by default.\n- **`tracing`**: Enables `tracing` crate integration.\n\n## Licenses\n\nMIT or MIT/Apache 2.0 where applicable.\n"
  },
  {
    "path": "crates/packager/schema.json",
    "content": "{\n  \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n  \"title\": \"Config\",\n  \"description\": \"The packaging config.\",\n  \"type\": \"object\",\n  \"properties\": {\n    \"$schema\": {\n      \"description\": \"The JSON schema for the config.\\n\\nSetting this field has no effect, this just exists so we can parse the JSON correctly when it has `$schema` field set.\",\n      \"type\": [\n        \"string\",\n        \"null\"\n      ]\n    },\n    \"name\": {\n      \"description\": \"The app name, this is just an identifier that could be used to filter which app to package using `--packages` cli arg when there is multiple apps in the workspace or in the same config.\\n\\nThis field resembles, the `name` field in `Cargo.toml` or `package.json`\\n\\nIf `unset`, the CLI will try to auto-detect it from `Cargo.toml` or `package.json` otherwise, it will keep it unset.\",\n      \"type\": [\n        \"string\",\n        \"null\"\n      ]\n    },\n    \"enabled\": {\n      \"description\": \"Whether this config is enabled or not. Defaults to `true`.\",\n      \"default\": true,\n      \"type\": \"boolean\"\n    },\n    \"productName\": {\n      \"description\": \"The package's product name, for example \\\"My Awesome App\\\".\",\n      \"default\": \"\",\n      \"type\": \"string\"\n    },\n    \"version\": {\n      \"description\": \"The package's version.\",\n      \"default\": \"\",\n      \"type\": \"string\"\n    },\n    \"binaries\": {\n      \"description\": \"The binaries to package.\",\n      \"default\": [],\n      \"type\": \"array\",\n      \"items\": {\n        \"$ref\": \"#/definitions/Binary\"\n      }\n    },\n    \"identifier\": {\n      \"description\": \"The application identifier in reverse domain name notation (e.g. `com.packager.example`). This string must be unique across applications since it is used in some system configurations. This string must contain only alphanumeric characters (A-Z, a-z, and 0-9), hyphens (-), and periods (.).\",\n      \"type\": [\n        \"string\",\n        \"null\"\n      ],\n      \"pattern\": \"^[a-zA-Z0-9-\\\\.]*$\"\n    },\n    \"beforePackagingCommand\": {\n      \"description\": \"The command to run before starting to package an application.\\n\\nThis runs only once.\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/definitions/HookCommand\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ]\n    },\n    \"beforeEachPackageCommand\": {\n      \"description\": \"The command to run before packaging each format for an application.\\n\\nThis will run multiple times depending on the formats specifed.\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/definitions/HookCommand\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ]\n    },\n    \"logLevel\": {\n      \"description\": \"The logging level.\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/definitions/LogLevel\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ]\n    },\n    \"formats\": {\n      \"description\": \"The packaging formats to create, if not present, [`PackageFormat::platform_default`] is used.\",\n      \"type\": [\n        \"array\",\n        \"null\"\n      ],\n      \"items\": {\n        \"$ref\": \"#/definitions/PackageFormat\"\n      }\n    },\n    \"outDir\": {\n      \"description\": \"The directory where the generated packages will be placed.\\n\\nIf [`Config::binaries_dir`] is not set, this is also where the [`Config::binaries`] exist.\",\n      \"default\": \"\",\n      \"type\": \"string\"\n    },\n    \"binariesDir\": {\n      \"description\": \"The directory where the [`Config::binaries`] exist.\\n\\nDefaults to [`Config::out_dir`].\",\n      \"default\": null,\n      \"type\": [\n        \"string\",\n        \"null\"\n      ]\n    },\n    \"targetTriple\": {\n      \"description\": \"The target triple we are packaging for.\\n\\nDefaults to the current OS target triple.\",\n      \"type\": [\n        \"string\",\n        \"null\"\n      ]\n    },\n    \"description\": {\n      \"description\": \"The package's description.\",\n      \"type\": [\n        \"string\",\n        \"null\"\n      ]\n    },\n    \"longDescription\": {\n      \"description\": \"The app's long description.\",\n      \"type\": [\n        \"string\",\n        \"null\"\n      ]\n    },\n    \"homepage\": {\n      \"description\": \"The package's homepage.\",\n      \"type\": [\n        \"string\",\n        \"null\"\n      ]\n    },\n    \"authors\": {\n      \"description\": \"The package's authors.\",\n      \"default\": null,\n      \"type\": [\n        \"array\",\n        \"null\"\n      ],\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"publisher\": {\n      \"description\": \"The app's publisher. Defaults to the second element in [`Config::identifier`](Config::identifier) string. Currently maps to the Manufacturer property of the Windows Installer.\",\n      \"type\": [\n        \"string\",\n        \"null\"\n      ]\n    },\n    \"licenseFile\": {\n      \"description\": \"A path to the license file.\",\n      \"type\": [\n        \"string\",\n        \"null\"\n      ]\n    },\n    \"copyright\": {\n      \"description\": \"The app's copyright.\",\n      \"type\": [\n        \"string\",\n        \"null\"\n      ]\n    },\n    \"category\": {\n      \"description\": \"The app's category.\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/definitions/AppCategory\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ]\n    },\n    \"icons\": {\n      \"description\": \"The app's icon list. Supports glob patterns.\",\n      \"type\": [\n        \"array\",\n        \"null\"\n      ],\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"fileAssociations\": {\n      \"description\": \"The file associations\",\n      \"type\": [\n        \"array\",\n        \"null\"\n      ],\n      \"items\": {\n        \"$ref\": \"#/definitions/FileAssociation\"\n      }\n    },\n    \"deepLinkProtocols\": {\n      \"description\": \"Deep-link protocols.\",\n      \"type\": [\n        \"array\",\n        \"null\"\n      ],\n      \"items\": {\n        \"$ref\": \"#/definitions/DeepLinkProtocol\"\n      }\n    },\n    \"resources\": {\n      \"description\": \"The app's resources to package. This a list of either a glob pattern, path to a file, path to a directory or an object of `src` and `target` paths. In the case of using an object, the `src` could be either a glob pattern, path to a file, path to a directory, and the `target` is a path inside the final resources folder in the installed package.\\n\\n## Format-specific:\\n\\n- **[PackageFormat::Nsis] / [PackageFormat::Wix]**: The resources are placed next to the executable in the root of the packager. - **[PackageFormat::Deb]**: The resources are placed in `usr/lib` of the package.\",\n      \"type\": [\n        \"array\",\n        \"null\"\n      ],\n      \"items\": {\n        \"$ref\": \"#/definitions/Resource\"\n      }\n    },\n    \"externalBinaries\": {\n      \"description\": \"Paths to external binaries to add to the package.\\n\\nThe path specified should not include `-<target-triple><.exe>` suffix, it will be auto-added when by the packager when reading these paths, so the actual binary name should have the target platform's target triple appended, as well as `.exe` for Windows.\\n\\nFor example, if you're packaging an external binary called `sqlite3`, the packager expects a binary named `sqlite3-x86_64-unknown-linux-gnu` on linux, and `sqlite3-x86_64-pc-windows-gnu.exe` on windows.\\n\\nIf you are building a universal binary for MacOS, the packager expects your external binary to also be universal, and named after the target triple, e.g. `sqlite3-universal-apple-darwin`. See <https://developer.apple.com/documentation/apple-silicon/building-a-universal-macos-binary>\",\n      \"type\": [\n        \"array\",\n        \"null\"\n      ],\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"windows\": {\n      \"description\": \"Windows-specific configuration.\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/definitions/WindowsConfig\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ]\n    },\n    \"macos\": {\n      \"description\": \"MacOS-specific configuration.\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/definitions/MacOsConfig\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ]\n    },\n    \"linux\": {\n      \"description\": \"Linux-specific configuration\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/definitions/LinuxConfig\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ]\n    },\n    \"deb\": {\n      \"description\": \"Debian-specific configuration.\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/definitions/DebianConfig\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ]\n    },\n    \"appimage\": {\n      \"description\": \"AppImage configuration.\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/definitions/AppImageConfig\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ]\n    },\n    \"pacman\": {\n      \"description\": \"Pacman configuration.\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/definitions/PacmanConfig\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ]\n    },\n    \"wix\": {\n      \"description\": \"WiX configuration.\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/definitions/WixConfig\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ]\n    },\n    \"nsis\": {\n      \"description\": \"Nsis configuration.\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/definitions/NsisConfig\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ]\n    },\n    \"dmg\": {\n      \"description\": \"Dmg configuration.\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/definitions/DmgConfig\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ]\n    }\n  },\n  \"additionalProperties\": false,\n  \"definitions\": {\n    \"Binary\": {\n      \"description\": \"A binary to package within the final package.\",\n      \"type\": \"object\",\n      \"required\": [\n        \"path\"\n      ],\n      \"properties\": {\n        \"path\": {\n          \"description\": \"Path to the binary (without `.exe` on Windows). If it's relative, it will be resolved from [`Config::out_dir`].\",\n          \"type\": \"string\"\n        },\n        \"main\": {\n          \"description\": \"Whether this is the main binary or not\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"HookCommand\": {\n      \"description\": \"Describes a shell command to be executed when a CLI hook is triggered.\",\n      \"anyOf\": [\n        {\n          \"description\": \"Run the given script with the default options.\",\n          \"type\": \"string\"\n        },\n        {\n          \"description\": \"Run the given script with custom options.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"script\"\n          ],\n          \"properties\": {\n            \"script\": {\n              \"description\": \"The script to execute.\",\n              \"type\": \"string\"\n            },\n            \"dir\": {\n              \"description\": \"The working directory.\",\n              \"type\": [\n                \"string\",\n                \"null\"\n              ]\n            }\n          }\n        }\n      ]\n    },\n    \"LogLevel\": {\n      \"description\": \"An enum representing the available verbosity levels of the logger.\",\n      \"oneOf\": [\n        {\n          \"description\": \"The \\\"error\\\" level.\\n\\nDesignates very serious errors.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"error\"\n          ]\n        },\n        {\n          \"description\": \"The \\\"warn\\\" level.\\n\\nDesignates hazardous situations.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"warn\"\n          ]\n        },\n        {\n          \"description\": \"The \\\"info\\\" level.\\n\\nDesignates useful information.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"info\"\n          ]\n        },\n        {\n          \"description\": \"The \\\"debug\\\" level.\\n\\nDesignates lower priority information.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"debug\"\n          ]\n        },\n        {\n          \"description\": \"The \\\"trace\\\" level.\\n\\nDesignates very low priority, often extremely verbose, information.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"trace\"\n          ]\n        }\n      ]\n    },\n    \"PackageFormat\": {\n      \"description\": \"Types of supported packages by [`cargo-packager`](https://docs.rs/cargo-packager).\",\n      \"oneOf\": [\n        {\n          \"description\": \"All available package formats for the current platform.\\n\\nSee [`PackageFormat::platform_all`]\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"all\"\n          ]\n        },\n        {\n          \"description\": \"The default list of package formats for the current platform.\\n\\nSee [`PackageFormat::platform_default`]\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"default\"\n          ]\n        },\n        {\n          \"description\": \"The macOS application bundle (.app).\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"app\"\n          ]\n        },\n        {\n          \"description\": \"The macOS DMG package (.dmg).\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"dmg\"\n          ]\n        },\n        {\n          \"description\": \"The Microsoft Software Installer (.msi) through WiX Toolset.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"wix\"\n          ]\n        },\n        {\n          \"description\": \"The NSIS installer (.exe).\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"nsis\"\n          ]\n        },\n        {\n          \"description\": \"The Linux Debian package (.deb).\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"deb\"\n          ]\n        },\n        {\n          \"description\": \"The Linux AppImage package (.AppImage).\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"appimage\"\n          ]\n        },\n        {\n          \"description\": \"The Linux Pacman package (.tar.gz and PKGBUILD)\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"pacman\"\n          ]\n        }\n      ]\n    },\n    \"AppCategory\": {\n      \"description\": \"The possible app categories. Corresponds to `LSApplicationCategoryType` on macOS and the GNOME desktop categories on Debian.\",\n      \"type\": \"string\",\n      \"enum\": [\n        \"Business\",\n        \"DeveloperTool\",\n        \"Education\",\n        \"Entertainment\",\n        \"Finance\",\n        \"Game\",\n        \"ActionGame\",\n        \"AdventureGame\",\n        \"ArcadeGame\",\n        \"BoardGame\",\n        \"CardGame\",\n        \"CasinoGame\",\n        \"DiceGame\",\n        \"EducationalGame\",\n        \"FamilyGame\",\n        \"KidsGame\",\n        \"MusicGame\",\n        \"PuzzleGame\",\n        \"RacingGame\",\n        \"RolePlayingGame\",\n        \"SimulationGame\",\n        \"SportsGame\",\n        \"StrategyGame\",\n        \"TriviaGame\",\n        \"WordGame\",\n        \"GraphicsAndDesign\",\n        \"HealthcareAndFitness\",\n        \"Lifestyle\",\n        \"Medical\",\n        \"Music\",\n        \"News\",\n        \"Photography\",\n        \"Productivity\",\n        \"Reference\",\n        \"SocialNetworking\",\n        \"Sports\",\n        \"Travel\",\n        \"Utility\",\n        \"Video\",\n        \"Weather\"\n      ]\n    },\n    \"FileAssociation\": {\n      \"description\": \"A file association configuration.\",\n      \"type\": \"object\",\n      \"required\": [\n        \"extensions\"\n      ],\n      \"properties\": {\n        \"extensions\": {\n          \"description\": \"File extensions to associate with this app. e.g. 'png'\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"mimeType\": {\n          \"description\": \"The mime-type e.g. 'image/png' or 'text/plain'. **Linux-only**.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"description\": {\n          \"description\": \"The association description. **Windows-only**. It is displayed on the `Type` column on Windows Explorer.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"name\": {\n          \"description\": \"The name. Maps to `CFBundleTypeName` on macOS. Defaults to the first item in `ext`\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"role\": {\n          \"description\": \"The app's role with respect to the type. Maps to `CFBundleTypeRole` on macOS. Defaults to [`BundleTypeRole::Editor`]\",\n          \"default\": \"editor\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/BundleTypeRole\"\n            }\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"BundleTypeRole\": {\n      \"description\": \"*macOS-only**. Corresponds to CFBundleTypeRole\",\n      \"oneOf\": [\n        {\n          \"description\": \"CFBundleTypeRole.Editor. Files can be read and edited.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"editor\"\n          ]\n        },\n        {\n          \"description\": \"CFBundleTypeRole.Viewer. Files can be read.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"viewer\"\n          ]\n        },\n        {\n          \"description\": \"CFBundleTypeRole.Shell\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"shell\"\n          ]\n        },\n        {\n          \"description\": \"CFBundleTypeRole.QLGenerator\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"qLGenerator\"\n          ]\n        },\n        {\n          \"description\": \"CFBundleTypeRole.None\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"none\"\n          ]\n        }\n      ]\n    },\n    \"DeepLinkProtocol\": {\n      \"description\": \"Deep link protocol\",\n      \"type\": \"object\",\n      \"required\": [\n        \"schemes\"\n      ],\n      \"properties\": {\n        \"schemes\": {\n          \"description\": \"URL schemes to associate with this app without `://`. For example `my-app`\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"name\": {\n          \"description\": \"The protocol name. **macOS-only** and maps to `CFBundleTypeName`. Defaults to `<bundle-id>.<schemes[0]>`\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"role\": {\n          \"description\": \"The app's role for these schemes. **macOS-only** and maps to `CFBundleTypeRole`.\",\n          \"default\": \"editor\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/BundleTypeRole\"\n            }\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"Resource\": {\n      \"description\": \"A path to a resource (with optional glob pattern) or an object of `src` and `target` paths.\",\n      \"anyOf\": [\n        {\n          \"description\": \"Supports glob patterns\",\n          \"type\": \"string\"\n        },\n        {\n          \"description\": \"An object descriping the src file or directory and its target location in the final package.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"src\",\n            \"target\"\n          ],\n          \"properties\": {\n            \"src\": {\n              \"description\": \"The src file or directory, supports glob patterns.\",\n              \"type\": \"string\"\n            },\n            \"target\": {\n              \"description\": \"A relative path from the root of the final package.\\n\\nIf `src` is a glob, this will always be treated as a directory where all globbed files will be placed under.\",\n              \"type\": \"string\"\n            }\n          }\n        }\n      ]\n    },\n    \"WindowsConfig\": {\n      \"description\": \"The Windows configuration.\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"digestAlgorithm\": {\n          \"description\": \"The file digest algorithm to use for creating file signatures. Required for code signing. SHA-256 is recommended.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"certificateThumbprint\": {\n          \"description\": \"The SHA1 hash of the signing certificate.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"tsp\": {\n          \"description\": \"Whether to use Time-Stamp Protocol (TSP, a.k.a. RFC 3161) for the timestamp server. Your code signing provider may use a TSP timestamp server, like e.g. SSL.com does. If so, enable TSP by setting to true.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"timestampUrl\": {\n          \"description\": \"Server to use during timestamping.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"allowDowngrades\": {\n          \"description\": \"Whether to validate a second app installation, blocking the user from installing an older version if set to `false`.\\n\\nFor instance, if `1.2.1` is installed, the user won't be able to install app version `1.2.0` or `1.1.5`.\\n\\nThe default value of this flag is `true`.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        },\n        \"signCommand\": {\n          \"description\": \"Specify a custom command to sign the binaries. This command needs to have a `%1` in it which is just a placeholder for the binary path, which we will detect and replace before calling the command.\\n\\nBy Default we use `signtool.exe` which can be found only on Windows so if you are on another platform and want to cross-compile and sign you will need to use another tool like `osslsigncode`.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"MacOsConfig\": {\n      \"description\": \"The macOS configuration.\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"frameworks\": {\n          \"description\": \"MacOS frameworks that need to be packaged with the app.\\n\\nEach string can either be the name of a framework (without the `.framework` extension, e.g. `\\\"SDL2\\\"`), in which case we will search for that framework in the standard install locations (`~/Library/Frameworks/`, `/Library/Frameworks/`, and `/Network/Library/Frameworks/`), or a path to a specific framework bundle (e.g. `./data/frameworks/SDL2.framework`).  Note that this setting just makes cargo-packager copy the specified frameworks into the OS X app bundle (under `Foobar.app/Contents/Frameworks/`); you are still responsible for:\\n\\n- arranging for the compiled binary to link against those frameworks (e.g. by emitting lines like `cargo:rustc-link-lib=framework=SDL2` from your `build.rs` script)\\n\\n- embedding the correct rpath in your binary (e.g. by running `install_name_tool -add_rpath \\\"@executable_path/../Frameworks\\\" path/to/binary` after compiling)\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"minimumSystemVersion\": {\n          \"description\": \"A version string indicating the minimum MacOS version that the packaged app supports (e.g. `\\\"10.11\\\"`). If you are using this config field, you may also want have your `build.rs` script emit `cargo:rustc-env=MACOSX_DEPLOYMENT_TARGET=10.11`.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"exceptionDomain\": {\n          \"description\": \"The exception domain to use on the macOS .app package.\\n\\nThis allows communication to the outside world e.g. a web server you're shipping.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"signingIdentity\": {\n          \"description\": \"Code signing identity.\\n\\nThis is typically of the form: `\\\"Developer ID Application: TEAM_NAME (TEAM_ID)\\\"`.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"providerShortName\": {\n          \"description\": \"Provider short name for notarization.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"entitlements\": {\n          \"description\": \"Path to the entitlements.plist file.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"infoPlistPath\": {\n          \"description\": \"Path to the Info.plist file for the package.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"embeddedProvisionprofilePath\": {\n          \"description\": \"Path to the embedded.provisionprofile file for the package.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"embeddedApps\": {\n          \"description\": \"Apps that need to be packaged within the app.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"backgroundApp\": {\n          \"description\": \"Whether this is a background application. If true, the app will not appear in the Dock.\\n\\nSets the `LSUIElement` flag in the macOS plist file.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"LinuxConfig\": {\n      \"description\": \"Linux configuration\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"generateDesktopEntry\": {\n          \"description\": \"Flag to indicate if desktop entry should be generated.\",\n          \"default\": true,\n          \"type\": \"boolean\"\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"DebianConfig\": {\n      \"description\": \"The Linux Debian configuration.\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"depends\": {\n          \"description\": \"The list of Debian dependencies.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/Dependencies\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"desktopTemplate\": {\n          \"description\": \"Path to a custom desktop file Handlebars template.\\n\\nAvailable variables: `categories`, `comment` (optional), `exec`, `icon` and `name`.\\n\\nDefault file contents: ```text [Desktop Entry] Categories={{categories}} {{#if comment}} Comment={{comment}} {{/if}} Exec={{exec}} {{exec_arg}} Icon={{icon}} Name={{name}} Terminal=false Type=Application {{#if mime_type}} MimeType={{mime_type}} {{/if}} ```\\n\\nThe `{{exec_arg}}` will be set to: * \\\"%F\\\", if at least one [Config::file_associations] was specified but no deep link protocols were given. * The \\\"%F\\\" arg means that your application can be invoked with multiple file paths. * \\\"%U\\\", if at least one [Config::deep_link_protocols] was specified. * The \\\"%U\\\" arg means that your application can be invoked with multiple URLs. * If both [Config::file_associations] and [Config::deep_link_protocols] were specified, the \\\"%U\\\" arg will be used, causing the file paths to be passed to your app as `file://` URLs. * An empty string \\\"\\\" (nothing) if neither are given. * This means that your application will never be invoked with any URLs or file paths.\\n\\nTo specify a custom `exec_arg`, just use plaintext directly instead of `{{exec_arg}}`: ```text Exec={{exec}} %u ```\\n\\nSee more here: <https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#exec-variables>.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"section\": {\n          \"description\": \"Define the section in Debian Control file. See : <https://www.debian.org/doc/debian-policy/ch-archive.html#s-subsections>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"priority\": {\n          \"description\": \"Change the priority of the Debian Package. By default, it is set to `optional`. Recognized Priorities as of now are :  `required`, `important`, `standard`, `optional`, `extra`\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"files\": {\n          \"description\": \"List of custom files to add to the deb package. Maps a dir/file to a dir/file inside the debian package.\",\n          \"type\": [\n            \"object\",\n            \"null\"\n          ],\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        },\n        \"packageName\": {\n          \"description\": \"Name to use for the `Package` field in the Debian Control file. Defaults to [`Config::product_name`] converted to kebab-case.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"Dependencies\": {\n      \"description\": \"A list of dependencies specified as either a list of Strings or as a path to a file that lists the dependencies, one per line.\",\n      \"anyOf\": [\n        {\n          \"description\": \"The list of dependencies provided directly as a vector of Strings.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        {\n          \"description\": \"A path to the file containing the list of dependences, formatted as one per line: ```text libc6 libxcursor1 libdbus-1-3 libasyncns0 ... ```\",\n          \"type\": \"string\"\n        }\n      ]\n    },\n    \"AppImageConfig\": {\n      \"description\": \"The Linux AppImage configuration.\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"libs\": {\n          \"description\": \"List of libs that exist in `/usr/lib*` to be include in the final AppImage. The libs will be searched for, using the command `find -L /usr/lib* -name <libname>`\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"bins\": {\n          \"description\": \"List of binary paths to include in the final AppImage. For example, if you want `xdg-open`, you'd specify `/usr/bin/xdg-open`\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"files\": {\n          \"description\": \"List of custom files to add to the appimage package. Maps a dir/file to a dir/file inside the appimage package.\",\n          \"type\": [\n            \"object\",\n            \"null\"\n          ],\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        },\n        \"linuxdeployPlugins\": {\n          \"description\": \"A map of [`linuxdeploy`](https://github.com/linuxdeploy/linuxdeploy) plugin name and its URL to be downloaded and executed while packaing the appimage. For example, if you want to use the [`gtk`](https://raw.githubusercontent.com/linuxdeploy/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh) plugin, you'd specify `gtk` as the key and its url as the value.\",\n          \"type\": [\n            \"object\",\n            \"null\"\n          ],\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        },\n        \"excludedLibs\": {\n          \"description\": \"List of globs of libraries to exclude from the final AppImage. For example, to exclude libnss3.so, you'd specify `libnss3*`\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"PacmanConfig\": {\n      \"description\": \"The Linux pacman configuration.\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"files\": {\n          \"description\": \"List of custom files to add to the pacman package. Maps a dir/file to a dir/file inside the pacman package.\",\n          \"type\": [\n            \"object\",\n            \"null\"\n          ],\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        },\n        \"depends\": {\n          \"description\": \"List of softwares that must be installed for the app to build and run.\\n\\nSee : <https://wiki.archlinux.org/title/PKGBUILD#depends>\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/Dependencies\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"provides\": {\n          \"description\": \"Additional packages that are provided by this app.\\n\\nSee : <https://wiki.archlinux.org/title/PKGBUILD#provides>\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"conflicts\": {\n          \"description\": \"Packages that conflict or cause problems with the app. All these packages and packages providing this item will need to be removed\\n\\nSee : <https://wiki.archlinux.org/title/PKGBUILD#conflicts>\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"replaces\": {\n          \"description\": \"Only use if this app replaces some obsolete packages. For example, if you rename any package.\\n\\nSee : <https://wiki.archlinux.org/title/PKGBUILD#replaces>\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"source\": {\n          \"description\": \"Source of the package to be stored at PKGBUILD. PKGBUILD is a bash script, so version can be referred as ${pkgver}\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"WixConfig\": {\n      \"description\": \"The wix format configuration\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"languages\": {\n          \"description\": \"The app languages to build. See <https://docs.microsoft.com/en-us/windows/win32/msi/localizing-the-error-and-actiontext-tables>.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"$ref\": \"#/definitions/WixLanguage\"\n          }\n        },\n        \"template\": {\n          \"description\": \"By default, the packager uses an internal template. This option allows you to define your own wix file.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"mergeModules\": {\n          \"description\": \"List of merge modules to include in your installer. For example, if you want to include [C++ Redis merge modules]\\n\\n[C++ Redis merge modules]: https://wixtoolset.org/docs/v3/howtos/redistributables_and_install_checks/install_vcredist/\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"fragmentPaths\": {\n          \"description\": \"A list of paths to .wxs files with WiX fragments to use.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"fragments\": {\n          \"description\": \"List of WiX fragments as strings. This is similar to `config.wix.fragments_paths` but is a string so you can define it inline in your config.\\n\\n```text <?xml version=\\\"1.0\\\" encoding=\\\"utf-8\\\"?> <Wix xmlns=\\\"http://schemas.microsoft.com/wix/2006/wi\\\"> <Fragment> <CustomAction Id=\\\"OpenNotepad\\\" Directory=\\\"INSTALLDIR\\\" Execute=\\\"immediate\\\" ExeCommand=\\\"cmd.exe /c notepad.exe\\\" Return=\\\"check\\\" /> <InstallExecuteSequence> <Custom Action=\\\"OpenNotepad\\\" After=\\\"InstallInitialize\\\" /> </InstallExecuteSequence> </Fragment> </Wix> ```\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"componentGroupRefs\": {\n          \"description\": \"The ComponentGroup element ids you want to reference from the fragments.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"componentRefs\": {\n          \"description\": \"The Component element ids you want to reference from the fragments.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"customActionRefs\": {\n          \"description\": \"The CustomAction element ids you want to reference from the fragments.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"featureGroupRefs\": {\n          \"description\": \"The FeatureGroup element ids you want to reference from the fragments.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"featureRefs\": {\n          \"description\": \"The Feature element ids you want to reference from the fragments.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"mergeRefs\": {\n          \"description\": \"The Merge element ids you want to reference from the fragments.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"bannerPath\": {\n          \"description\": \"Path to a bitmap file to use as the installation user interface banner. This bitmap will appear at the top of all but the first page of the installer.\\n\\nThe required dimensions are 493px × 58px.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"dialogImagePath\": {\n          \"description\": \"Path to a bitmap file to use on the installation user interface dialogs. It is used on the welcome and completion dialogs. The required dimensions are 493px × 312px.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"fipsCompliant\": {\n          \"description\": \"Enables FIPS compliant algorithms.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"WixLanguage\": {\n      \"description\": \"A wix language.\",\n      \"anyOf\": [\n        {\n          \"description\": \"Built-in wix language identifier.\",\n          \"type\": \"string\"\n        },\n        {\n          \"description\": \"Custom wix language.\",\n          \"type\": \"object\",\n          \"required\": [\n            \"identifier\"\n          ],\n          \"properties\": {\n            \"identifier\": {\n              \"description\": \"Idenitifier of this language, for example `en-US`\",\n              \"type\": \"string\"\n            },\n            \"path\": {\n              \"description\": \"The path to a locale (`.wxl`) file. See <https://wixtoolset.org/documentation/manual/v3/howtos/ui_and_localization/build_a_localized_version.html>.\",\n              \"type\": [\n                \"string\",\n                \"null\"\n              ]\n            }\n          }\n        }\n      ]\n    },\n    \"NsisConfig\": {\n      \"description\": \"The NSIS format configuration.\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"compression\": {\n          \"description\": \"Set the compression algorithm used to compress files in the installer.\\n\\nSee <https://nsis.sourceforge.io/Reference/SetCompressor>\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/NsisCompression\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"template\": {\n          \"description\": \"A custom `.nsi` template to use.\\n\\nSee the default template here <https://github.com/crabnebula-dev/cargo-packager/blob/main/crates/packager/src/package/nsis/installer.nsi>\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"preinstallSection\": {\n          \"description\": \"Logic of an NSIS section that will be ran before the install section.\\n\\nSee the available libraries, dlls and global variables here <https://github.com/crabnebula-dev/cargo-packager/blob/main/crates/packager/src/package/nsis/installer.nsi>\\n\\n### Example ```toml [package.metadata.packager.nsis] preinstall-section = \\\"\\\"\\\" ; Setup custom messages LangString webview2AbortError ${LANG_ENGLISH} \\\"Failed to install WebView2! The app can't run without it. Try restarting the installer.\\\" LangString webview2DownloadError ${LANG_ARABIC} \\\"خطأ: فشل تنزيل WebView2 - $0\\\"\\n\\nSection PreInstall ; <section logic here> SectionEnd\\n\\nSection AnotherPreInstall ; <section logic here> SectionEnd \\\"\\\"\\\" ```\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"headerImage\": {\n          \"description\": \"The path to a bitmap file to display on the header of installers pages.\\n\\nThe recommended dimensions are 150px x 57px.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"sidebarImage\": {\n          \"description\": \"The path to a bitmap file for the Welcome page and the Finish page.\\n\\nThe recommended dimensions are 164px x 314px.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"installerIcon\": {\n          \"description\": \"The path to an icon file used as the installer icon.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"installMode\": {\n          \"description\": \"Whether the installation will be for all users or just the current user.\",\n          \"default\": \"currentUser\",\n          \"allOf\": [\n            {\n              \"$ref\": \"#/definitions/NSISInstallerMode\"\n            }\n          ]\n        },\n        \"languages\": {\n          \"description\": \"A list of installer languages. By default the OS language is used. If the OS language is not in the list of languages, the first language will be used. To allow the user to select the language, set `display_language_selector` to `true`.\\n\\nSee <https://github.com/kichik/nsis/tree/9465c08046f00ccb6eda985abbdbf52c275c6c4d/Contrib/Language%20files> for the complete list of languages.\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"customLanguageFiles\": {\n          \"description\": \"An key-value pair where the key is the language and the value is the path to a custom `.nsi` file that holds the translated text for cargo-packager's custom messages.\\n\\nSee <https://github.com/crabnebula-dev/cargo-packager/blob/main/crates/packager/src/nsis/languages/English.nsh> for an example `.nsi` file.\\n\\n**Note**: the key must be a valid NSIS language and it must be added to [`NsisConfig`]languages array,\",\n          \"type\": [\n            \"object\",\n            \"null\"\n          ],\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        },\n        \"displayLanguageSelector\": {\n          \"description\": \"Whether to display a language selector dialog before the installer and uninstaller windows are rendered or not. By default the OS language is selected, with a fallback to the first language in the `languages` array.\",\n          \"default\": false,\n          \"type\": \"boolean\"\n        },\n        \"appdataPaths\": {\n          \"description\": \"List of paths where your app stores data. This options tells the uninstaller to provide the user with an option (disabled by default) whether they want to rmeove your app data or keep it.\\n\\nThe path should use a constant from <https://nsis.sourceforge.io/Docs/Chapter4.html#varconstant> in addition to `$IDENTIFIER`, `$PUBLISHER` and `$PRODUCTNAME`, for example, if you store your app data in `C:\\\\\\\\Users\\\\\\\\<user>\\\\\\\\AppData\\\\\\\\Local\\\\\\\\<your-company-name>\\\\\\\\<your-product-name>` you'd need to specify ```toml [package.metadata.packager.nsis] appdata-paths = [\\\"$LOCALAPPDATA/$PUBLISHER/$PRODUCTNAME\\\"] ```\",\n          \"default\": null,\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"type\": \"string\"\n          }\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"NsisCompression\": {\n      \"description\": \"Compression algorithms used in the NSIS installer.\\n\\nSee <https://nsis.sourceforge.io/Reference/SetCompressor>\",\n      \"oneOf\": [\n        {\n          \"description\": \"ZLIB uses the deflate algorithm, it is a quick and simple method. With the default compression level it uses about 300 KB of memory.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"zlib\"\n          ]\n        },\n        {\n          \"description\": \"BZIP2 usually gives better compression ratios than ZLIB, but it is a bit slower and uses more memory. With the default compression level it uses about 4 MB of memory.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"bzip2\"\n          ]\n        },\n        {\n          \"description\": \"LZMA (default) is a new compression method that gives very good compression ratios. The decompression speed is high (10-20 MB/s on a 2 GHz CPU), the compression speed is lower. The memory size that will be used for decompression is the dictionary size plus a few KBs, the default is 8 MB.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"lzma\"\n          ]\n        },\n        {\n          \"description\": \"Disable compression.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"off\"\n          ]\n        }\n      ]\n    },\n    \"NSISInstallerMode\": {\n      \"description\": \"Install Modes for the NSIS installer.\",\n      \"oneOf\": [\n        {\n          \"description\": \"Default mode for the installer.\\n\\nInstall the app by default in a directory that doesn't require Administrator access.\\n\\nInstaller metadata will be saved under the `HKCU` registry path.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"currentUser\"\n          ]\n        },\n        {\n          \"description\": \"Install the app by default in the `Program Files` folder directory requires Administrator access for the installation.\\n\\nInstaller metadata will be saved under the `HKLM` registry path.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"perMachine\"\n          ]\n        },\n        {\n          \"description\": \"Combines both modes and allows the user to choose at install time whether to install for the current user or per machine. Note that this mode will require Administrator access even if the user wants to install it for the current user only.\\n\\nInstaller metadata will be saved under the `HKLM` or `HKCU` registry path based on the user's choice.\",\n          \"type\": \"string\",\n          \"enum\": [\n            \"both\"\n          ]\n        }\n      ]\n    },\n    \"DmgConfig\": {\n      \"description\": \"The Apple Disk Image (.dmg) configuration.\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"background\": {\n          \"description\": \"Image to use as the background in dmg file. Accepted formats: `png`/`jpg`/`gif`.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"windowPosition\": {\n          \"description\": \"Position of volume window on screen.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/Position\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"windowSize\": {\n          \"description\": \"Size of volume window.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/Size\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"appPosition\": {\n          \"description\": \"Position of application file on window.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/Position\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"appFolderPosition\": {\n          \"description\": \"Position of application folder on window.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/Position\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"Position\": {\n      \"description\": \"Position coordinates struct.\",\n      \"type\": \"object\",\n      \"required\": [\n        \"x\",\n        \"y\"\n      ],\n      \"properties\": {\n        \"x\": {\n          \"description\": \"X coordinate.\",\n          \"type\": \"integer\",\n          \"format\": \"uint32\",\n          \"minimum\": 0.0\n        },\n        \"y\": {\n          \"description\": \"Y coordinate.\",\n          \"type\": \"integer\",\n          \"format\": \"uint32\",\n          \"minimum\": 0.0\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"Size\": {\n      \"description\": \"Size struct.\",\n      \"type\": \"object\",\n      \"required\": [\n        \"height\",\n        \"width\"\n      ],\n      \"properties\": {\n        \"width\": {\n          \"description\": \"Width.\",\n          \"type\": \"integer\",\n          \"format\": \"uint32\",\n          \"minimum\": 0.0\n        },\n        \"height\": {\n          \"description\": \"Height.\",\n          \"type\": \"integer\",\n          \"format\": \"uint32\",\n          \"minimum\": 0.0\n        }\n      },\n      \"additionalProperties\": false\n    }\n  }\n}"
  },
  {
    "path": "crates/packager/src/bin/cargo-packager.rs",
    "content": "// Copyright 2023-2023 CrabNebula Ltd.\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{env::args_os, ffi::OsStr, path::Path, process::exit};\n\nfn main() {\n    let mut args = args_os().peekable();\n    let bin_name = match args\n        .next()\n        .as_deref()\n        .map(Path::new)\n        .and_then(Path::file_stem)\n        .and_then(OsStr::to_str)\n    {\n        Some(\"cargo-packager\") => {\n            if args.peek().and_then(|s| s.to_str()) == Some(\"packager\") {\n                // remove the extra cargo subcommand\n                args.next();\n                Some(\"cargo packager\".into())\n            } else {\n                Some(\"cargo-packager\".into())\n            }\n        }\n        Some(stem) => Some(stem.to_string()),\n        None => {\n            eprintln!(\"cargo-packager wrapper unable to read first argument\");\n            exit(1);\n        }\n    };\n\n    cargo_packager::cli::run(args, bin_name)\n}\n"
  },
  {
    "path": "crates/packager/src/cli/config.rs",
    "content": "// Copyright 2023-2023 CrabNebula Ltd.\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{\n    fmt::Debug,\n    fs,\n    path::{Path, PathBuf},\n};\n\nuse super::{Error, Result};\nuse crate::{config::Binary, Config};\n\nimpl Config {\n    pub(crate) fn name(&self) -> &str {\n        self.name.as_deref().unwrap_or_default()\n    }\n\n    /// Whether this config should be packaged or skipped\n    pub(crate) fn should_pacakge(&self, cli: &super::Cli) -> bool {\n        // Should be packaged when it is enabled and this package was in the explicit packages list specified on the CLI,\n        // or the packages list specified on the CLI is empty which means build all\n        self.enabled\n            && cli\n                .packages\n                .as_ref()\n                .map(|packages| packages.iter().any(|p| p == self.name()))\n                .unwrap_or(true)\n    }\n}\n\nfn find_nearset_pkg_name(path: &Path) -> Result<Option<String>> {\n    fn find_nearset_pkg_name_inner() -> Result<Option<String>> {\n        if let Ok(contents) = fs::read_to_string(\"Cargo.toml\") {\n            let toml = toml::from_str::<toml::Table>(&contents)\n                .map_err(|e| Error::FailedToParseCargoToml(Box::new(e)))?;\n\n            if let Some(name) = toml.get(\"package\").and_then(|p| p.get(\"name\")) {\n                return Ok(Some(name.to_string()));\n            }\n        }\n\n        if let Ok(contents) = fs::read(\"package.json\") {\n            let json = serde_json::from_slice::<serde_json::Value>(&contents)\n                .map_err(Error::FailedToParsePacakgeJson)?;\n\n            if let Some(name) = json.get(\"name\") {\n                return Ok(Some(name.to_string()));\n            }\n        }\n\n        Ok(None)\n    }\n\n    let cwd = std::env::current_dir()?;\n    std::env::set_current_dir(path).map_err(|e| Error::IoWithPath(path.to_path_buf(), e))?;\n    let res = find_nearset_pkg_name_inner();\n    std::env::set_current_dir(&cwd).map_err(|e| Error::IoWithPath(cwd, e))?;\n    res\n}\n\n#[tracing::instrument(level = \"trace\")]\nfn parse_config_file<P: AsRef<Path> + Debug>(path: P) -> Result<Vec<(Option<PathBuf>, Config)>> {\n    let p = path.as_ref().to_path_buf();\n    let path = p.canonicalize().map_err(|e| Error::IoWithPath(p, e))?;\n    let content = fs::read_to_string(&path).map_err(|e| Error::IoWithPath(path.clone(), e))?;\n    let mut configs = match path.extension().and_then(|e| e.to_str()) {\n        Some(\"toml\") => {\n            if let Ok(configs) = toml::from_str::<Vec<Config>>(&content) {\n                configs\n                    .into_iter()\n                    .map(|c| (Some(path.clone()), c))\n                    .collect()\n            } else {\n                toml::from_str::<Config>(&content)\n                    .map_err(|e| Error::FailedToParseTomlConfigFromPath(path.clone(), Box::new(e)))\n                    .map(|config| vec![(Some(path), config)])?\n            }\n        }\n        _ => {\n            if let Ok(configs) = serde_json::from_str::<Vec<Config>>(&content) {\n                configs\n                    .into_iter()\n                    .map(|c| (Some(path.clone()), c))\n                    .collect()\n            } else {\n                serde_json::from_str::<Config>(&content)\n                    .map_err(|e| Error::FailedToParseJsonConfigFromPath(path.clone(), e))\n                    .map(|config| vec![(Some(path), config)])?\n            }\n        }\n    };\n\n    for (path, config) in &mut configs {\n        // fill config.name if unset\n        if config.name.is_none() {\n            // and config wasn't passed using `--config` cli arg\n            if let Some(path) = &path {\n                let name = find_nearset_pkg_name(path)?;\n                config.name = name;\n            }\n        }\n    }\n\n    Ok(configs)\n}\n\n#[tracing::instrument(level = \"trace\")]\nfn find_config_files() -> crate::Result<Vec<PathBuf>> {\n    let opts = glob::MatchOptions {\n        case_sensitive: false,\n        ..Default::default()\n    };\n\n    Ok([\n        glob::glob_with(\"**/packager.toml\", opts)?\n            .flatten()\n            .collect::<Vec<_>>(),\n        glob::glob_with(\"**/packager.json\", opts)?\n            .flatten()\n            .collect::<Vec<_>>(),\n    ]\n    .concat())\n}\n\n#[tracing::instrument(level = \"trace\")]\nfn load_configs_from_cargo_workspace(cli: &super::Cli) -> Result<Vec<(Option<PathBuf>, Config)>> {\n    let profile = if cli.release {\n        \"release\"\n    } else if let Some(profile) = &cli.profile {\n        profile.as_str()\n    } else {\n        \"debug\"\n    };\n\n    let mut metadata_cmd = cargo_metadata::MetadataCommand::new();\n    if let Some(manifest_path) = &cli.manifest_path {\n        metadata_cmd.manifest_path(manifest_path);\n    }\n\n    let metadata = match metadata_cmd.exec() {\n        Ok(m) => m,\n        Err(e) => {\n            tracing::debug!(\"cargo metadata failed: {e}\");\n            return Ok(Vec::new());\n        }\n    };\n\n    let mut configs = Vec::new();\n    for package in metadata.workspace_packages().iter() {\n        if let Some(config) = package.metadata.get(\"packager\") {\n            let mut config: Config = serde_json::from_value(config.to_owned())\n                .map_err(Error::FailedToParseJsonConfigCargoToml)?;\n\n            if config.name.is_none() {\n                config.name.replace(package.name.clone());\n            }\n            if config.product_name.is_empty() {\n                config.product_name.clone_from(&package.name);\n            }\n            if config.version.is_empty() {\n                config.version = package.version.to_string();\n            }\n            if config.identifier.is_none() {\n                let author = package\n                    .authors\n                    .first()\n                    .map(|a| {\n                        let a = a.replace(['_', ' ', '.'], \"-\").to_lowercase();\n                        a.strip_suffix('_').map(ToString::to_string).unwrap_or(a)\n                    })\n                    .unwrap_or_else(|| format!(\"{}-author\", package.name));\n                config\n                    .identifier\n                    .replace(format!(\"com.{}.{}\", author, package.name));\n            }\n\n            let mut cargo_out_dir = metadata.target_directory.as_std_path().to_path_buf();\n            if let Some(target_triple) = cli.target.as_ref().or(config.target_triple.as_ref()) {\n                cargo_out_dir.push(target_triple);\n            }\n            cargo_out_dir.push(profile);\n\n            if config.binaries_dir.is_none() {\n                config.binaries_dir.replace(cargo_out_dir.clone());\n            }\n            if config.out_dir.as_os_str().is_empty() {\n                config.out_dir = cargo_out_dir;\n            }\n\n            if config.description.is_none() {\n                config.description.clone_from(&package.description);\n            }\n            if config.authors.is_none() {\n                config.authors = Some(package.authors.clone());\n            }\n            if config.license_file.is_none() {\n                config.license_file = package\n                    .license_file\n                    .as_ref()\n                    .map(|p| p.as_std_path().to_owned());\n            }\n            // Auto-detect binaries if none were explicitly configured\n            if config.binaries.is_empty() {\n                let targets = package\n                    .targets\n                    .iter()\n                    .filter(|t| t.is_bin())\n                    .collect::<Vec<_>>();\n                for target in &targets {\n                    config.binaries.push(Binary {\n                        path: target.name.clone().into(),\n                        main: match targets.len() {\n                            1 => true,\n                            _ => target.name == package.name,\n                        },\n                    })\n                }\n            }\n            configs.push((\n                Some(package.manifest_path.as_std_path().to_path_buf()),\n                config,\n            ));\n        }\n    }\n\n    Ok(configs)\n}\n\npub fn detect_configs(cli: &super::Cli) -> Result<Vec<(Option<PathBuf>, Config)>> {\n    let configs = match &cli.config {\n        // if a raw json object\n        Some(c) if c.starts_with('{') => serde_json::from_str::<Config>(c)\n            .map(|c| vec![(None, c)])\n            .map_err(Error::FailedToParseJsonConfig)?,\n        // if a raw json array\n        Some(c) if c.starts_with('[') => serde_json::from_str::<Vec<Config>>(c)\n            .map_err(Error::FailedToParseJsonConfig)?\n            .into_iter()\n            .map(|c| (None, c))\n            .collect(),\n        // if a path to config file\n        Some(c) => parse_config_file(c)?,\n        // fallback to config files and cargo workspaces configs\n        _ => {\n            let config_files = find_config_files()?\n                .into_iter()\n                .filter_map(|c| parse_config_file(c).ok())\n                .collect::<Vec<_>>()\n                .concat();\n\n            let cargo_configs = load_configs_from_cargo_workspace(cli)?;\n\n            [config_files, cargo_configs]\n                .concat()\n                .into_iter()\n                .filter(|(_, c)| c.should_pacakge(cli))\n                .collect()\n        }\n    };\n\n    Ok(configs)\n}\n"
  },
  {
    "path": "crates/packager/src/cli/error.rs",
    "content": "// Copyright 2019-2023 Tauri Programme within The Commons Conservancy\n// Copyright 2023-2023 CrabNebula Ltd.\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::path::PathBuf;\n\nuse thiserror::Error;\n\n#[non_exhaustive]\n#[derive(Error, Debug)]\n/// Errors returned by cargo-packager.\npub enum Error {\n    /// Clap error.\n    #[error(transparent)]\n    #[cfg(feature = \"cli\")]\n    Clap(#[from] clap::error::Error),\n    /// Error while reading cargo metadata.\n    #[error(\"Failed to read cargo metadata: {0}\")]\n    Metadata(#[from] cargo_metadata::Error),\n    /// JSON parsing error.\n    #[error(transparent)]\n    Json(#[from] serde_json::Error),\n    /// TOML parsing error.\n    #[error(transparent)]\n    Toml(#[from] toml::de::Error),\n    /// JSON Config parsing error.\n    #[error(\"Failed to parse config: {0}\")]\n    FailedToParseJsonConfig(serde_json::Error),\n    #[error(\"Failed to deserialize config from `package.metadata.packager` in Cargo.toml: {0}\")]\n    FailedToParseJsonConfigCargoToml(serde_json::Error),\n    /// TOML Config parsing error.\n    #[error(\"Failed to parse config: {0}\")]\n    FailedToParseTomlConfig(Box<toml::de::Error>),\n    /// Cargo.toml parsing error.\n    #[error(\"Failed to parse Cargo.toml: {0}\")]\n    FailedToParseCargoToml(Box<toml::de::Error>),\n    /// package.json parsing error.\n    #[error(\"Failed to parse package.json: {0}\")]\n    FailedToParsePacakgeJson(serde_json::Error),\n    /// JSON Config parsing error.\n    #[error(\"Failed to parse config at {0}: {1}\")]\n    FailedToParseJsonConfigFromPath(PathBuf, serde_json::Error),\n    /// TOML Config parsing error.\n    #[error(\"Failed to parse config at {0}: {1}\")]\n    FailedToParseTomlConfigFromPath(PathBuf, Box<toml::de::Error>),\n    /// I/O errors with path.\n    #[error(\"I/O Error ({0}): {1}\")]\n    IoWithPath(PathBuf, std::io::Error),\n    /// I/O errors.\n    #[error(transparent)]\n    Io(#[from] std::io::Error),\n    /// Packaging error\n    #[error(transparent)]\n    Packaging(#[from] crate::Error),\n}\n\n/// Convenient type alias of Result type for cargo-packager.\npub type Result<T> = std::result::Result<T, Error>;\n"
  },
  {
    "path": "crates/packager/src/cli/mod.rs",
    "content": "// Copyright 2023-2023 CrabNebula Ltd.\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! The cli entry point\n\nuse std::{ffi::OsString, fmt::Write, fs, path::PathBuf};\n\nuse clap::{ArgAction, CommandFactory, FromArgMatches, Parser, Subcommand};\n\nuse crate::{\n    config::{LogLevel, PackageFormat},\n    init_tracing_subscriber, package, parse_log_level, sign_outputs, util, SigningConfig,\n};\n\nmod config;\nmod error;\nmod signer;\n\nuse self::error::{Error, Result};\n\n#[derive(Debug, Clone, Subcommand)]\nenum Commands {\n    Signer(signer::Options),\n}\n\n#[derive(Parser, Debug)]\n#[clap(\n    author,\n    version,\n    about,\n    bin_name(\"cargo-packager\"),\n    propagate_version(true),\n    no_binary_name(true)\n)]\npub(crate) struct Cli {\n    /// Enables verbose logging.\n    #[clap(short, long, global = true, action = ArgAction::Count)]\n    verbose: u8,\n    /// Disables logging\n    #[clap(short, long, global = true)]\n    quite: bool,\n\n    /// The package fromats to build.\n    #[clap(short, long, value_enum, value_delimiter = ',')]\n    formats: Option<Vec<PackageFormat>>,\n    /// A configuration to read, which could be a JSON file,\n    /// TOML file, or a raw JSON string.\n    ///\n    /// By default, cargo-packager looks for `{p,P}ackager.{toml,json}` and\n    /// `[package.metadata.packager]` in `Cargo.toml` files.\n    #[clap(short, long)]\n    config: Option<String>,\n    /// Load a private key from a file or a string to sign the generated ouptuts.\n    #[clap(short = 'k', long, env = \"CARGO_PACKAGER_SIGN_PRIVATE_KEY\")]\n    private_key: Option<String>,\n    /// The password for the signing private key.\n    #[clap(long, env = \"CARGO_PACKAGER_SIGN_PRIVATE_KEY_PASSWORD\")]\n    password: Option<String>,\n    /// Which packages to use from the current workspace.\n    #[clap(short, long, value_delimiter = ',')]\n    pub(crate) packages: Option<Vec<String>>,\n    /// The directory where the packages will be placed.\n    ///\n    /// If [`Config::binaries_dir`] is not defined, it is also the path where the binaries are located if they use relative paths.\n    #[clap(short, long, alias = \"out\")]\n    out_dir: Option<PathBuf>,\n    /// The directory where the [`Config::binaries`] exist.\n    ///\n    /// Defaults to [`Config::out_dir`]\n    #[clap(long)]\n    binaries_dir: Option<PathBuf>,\n    /// Package the release version of your app.\n    /// Ignored when `--config` is used.\n    #[clap(short, long, group = \"cargo-profile\")]\n    release: bool,\n    /// Cargo profile to use for packaging your app.\n    /// Ignored when `--config` is used.\n    #[clap(long, group = \"cargo-profile\")]\n    profile: Option<String>,\n    /// Path to Cargo.toml manifest path to use for reading the configuration.\n    /// Ignored when `--config` is used.\n    #[clap(long)]\n    manifest_path: Option<PathBuf>,\n    /// Target triple to use for detecting your app binaries.\n    #[clap(long)]\n    target: Option<String>,\n\n    #[command(subcommand)]\n    command: Option<Commands>,\n}\n\n#[tracing::instrument(level = \"trace\", skip(cli))]\nfn run_cli(cli: Cli) -> Result<()> {\n    tracing::trace!(cli= ?cli);\n\n    // run subcommand and exit if one was specified,\n    // otherwise run the default packaging command\n    if let Some(command) = cli.command {\n        match command {\n            Commands::Signer(opts) => signer::command(opts)?,\n        }\n        return Ok(());\n    }\n\n    let configs = config::detect_configs(&cli)?;\n\n    if configs.is_empty() {\n        tracing::error!(\"Couldn't detect a valid configuration file or all configurations are disabled! Nothing to do here.\");\n        std::process::exit(1);\n    }\n\n    let cli_out_dir = cli\n        .out_dir\n        .as_ref()\n        .map(|p| {\n            if p.exists() {\n                dunce::canonicalize(p).map_err(|e| Error::IoWithPath(p.clone(), e))\n            } else {\n                fs::create_dir_all(p).map_err(|e| Error::IoWithPath(p.clone(), e))?;\n                Ok(p.to_owned())\n            }\n        })\n        .transpose()?;\n\n    let private_key = match cli.private_key {\n        Some(path) if PathBuf::from(&path).exists() => Some(\n            fs::read_to_string(&path).map_err(|e| Error::IoWithPath(PathBuf::from(&path), e))?,\n        ),\n        k => k,\n    };\n\n    let signing_config = private_key.map(|k| SigningConfig {\n        private_key: k,\n        password: cli.password,\n    });\n\n    let mut outputs = Vec::new();\n    let mut signatures = Vec::new();\n    for (config_dir, mut config) in configs {\n        tracing::trace!(config = ?config);\n\n        if let Some(dir) = &cli_out_dir {\n            config.out_dir.clone_from(dir)\n        }\n\n        if let Some(formats) = &cli.formats {\n            config.formats.replace(formats.clone());\n        }\n\n        if let Some(target_triple) = &cli.target {\n            config.target_triple.replace(target_triple.clone());\n        }\n\n        if config.log_level.is_none() && !cli.quite {\n            let level = match parse_log_level(cli.verbose) {\n                tracing::Level::ERROR => LogLevel::Error,\n                tracing::Level::WARN => LogLevel::Warn,\n                tracing::Level::INFO => LogLevel::Info,\n                tracing::Level::DEBUG => LogLevel::Debug,\n                tracing::Level::TRACE => LogLevel::Trace,\n            };\n            config.log_level.replace(level);\n        }\n\n        if let Some(path) = config_dir {\n            // change the directory to the config being built\n            // so paths will be read relative to it\n            let parent = path\n                .parent()\n                .ok_or_else(|| crate::Error::ParentDirNotFound(path.clone()))?;\n            std::env::set_current_dir(parent)\n                .map_err(|e| Error::IoWithPath(parent.to_path_buf(), e))?;\n        }\n\n        // create the packages\n        let mut packages = package(&config)?;\n\n        // sign the packages\n        if let Some(signing_config) = &signing_config {\n            let s = sign_outputs(signing_config, &mut packages)?;\n            signatures.extend(s);\n        }\n\n        outputs.extend(packages);\n    }\n\n    // flatten paths\n    let outputs = outputs\n        .into_iter()\n        .flat_map(|o| o.paths)\n        .collect::<Vec<_>>();\n\n    // print information when finished\n    let len = outputs.len();\n    if len >= 1 {\n        let pluralised = if len == 1 { \"package\" } else { \"packages\" };\n        let mut printable_paths = String::new();\n        for path in outputs {\n            let _ = writeln!(printable_paths, \"        {}\", util::display_path(path));\n        }\n        tracing::info!(\n            \"Finished packaging {} {} at:\\n{}\",\n            len,\n            pluralised,\n            printable_paths\n        );\n    }\n\n    let len = signatures.len();\n    if len >= 1 {\n        let pluralised = if len == 1 { \"signature\" } else { \"signatures\" };\n        let mut printable_paths = String::new();\n        for path in signatures {\n            let _ = writeln!(printable_paths, \"        {}\", util::display_path(path));\n        }\n        tracing::info!(\n            \"Finished signing packages, {} {} at:\\n{}\",\n            len,\n            pluralised,\n            printable_paths\n        );\n    }\n\n    Ok(())\n}\n\n/// Run the packager CLI\npub fn run<I, A>(args: I, bin_name: Option<String>)\nwhere\n    I: IntoIterator<Item = A>,\n    A: Into<OsString> + Clone,\n{\n    if let Err(e) = try_run(args, bin_name) {\n        tracing::error!(\"{}\", e);\n        std::process::exit(1);\n    }\n}\n\n/// Try run the packager CLI\npub fn try_run<I, A>(args: I, bin_name: Option<String>) -> Result<()>\nwhere\n    I: IntoIterator<Item = A>,\n    A: Into<OsString> + Clone,\n{\n    let cli = match &bin_name {\n        Some(bin_name) => Cli::command().bin_name(bin_name),\n        None => Cli::command(),\n    };\n    let matches = cli.get_matches_from(args);\n    let cli = Cli::from_arg_matches(&matches).map_err(|e| {\n        e.format(&mut match &bin_name {\n            Some(bin_name) => Cli::command().bin_name(bin_name),\n            None => Cli::command(),\n        })\n    })?;\n\n    if !cli.quite {\n        init_tracing_subscriber(cli.verbose);\n        if std::env::var_os(\"CARGO_TERM_COLOR\").is_none() {\n            std::env::set_var(\"CARGO_TERM_COLOR\", \"always\");\n        }\n    }\n\n    run_cli(cli)\n}\n"
  },
  {
    "path": "crates/packager/src/cli/signer/generate.rs",
    "content": "// Copyright 2019-2023 Tauri Programme within The Commons Conservancy\n// Copyright 2023-2023 CrabNebula Ltd.\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::path::PathBuf;\n\nuse clap::Parser;\n\nuse crate::cli::Result;\n\n#[derive(Debug, Clone, Parser)]\n#[clap(about = \"Generate a new signing key to sign files\")]\npub struct Options {\n    /// Set a password for the new signing key.\n    #[clap(long, env = \"CARGO_PACKAGER_SIGN_PRIVATE_KEY_PASSWORD\")]\n    password: Option<String>,\n    #[clap(long)]\n    /// A path where the private key will be stored.\n    path: Option<PathBuf>,\n    /// Overwrite the private key even if it exists on the specified path.\n    #[clap(short, long)]\n    force: bool,\n    /// Run in CI mode and skip prompting for values.\n    #[clap(long)]\n    ci: bool,\n}\n\npub fn command(mut options: Options) -> Result<()> {\n    options.ci = options.ci || std::env::var(\"CI\").is_ok();\n    if options.ci && options.password.is_none() {\n        tracing::warn!(\"Generating a new private key without a password, for security reasons, we recommend setting a password instead.\");\n        options.password.replace(\"\".into());\n    }\n\n    tracing::info!(\"Generating a new signing key.\");\n    let keypair = crate::sign::generate_key(options.password)?;\n\n    match options.path {\n        Some(path) => {\n            let keys = crate::sign::save_keypair(&keypair, path, options.force)?;\n            tracing::info!(\n                \"Finished generating and saving the keys:\\n        {}\\n        {}\",\n                keys.0.display(),\n                keys.1.display()\n            );\n        }\n        None => {\n            tracing::info!(\"Finished generating secret key:\\n{}\", keypair.sk);\n            tracing::info!(\"Finished generating public key:\\n{}\", keypair.pk);\n        }\n    }\n\n    Ok(())\n}\n"
  },
  {
    "path": "crates/packager/src/cli/signer/mod.rs",
    "content": "// Copyright 2023-2023 CrabNebula Ltd.\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse clap::{Parser, Subcommand};\n\nuse super::Result;\n\nmod generate;\nmod sign;\n\n#[derive(Debug, Clone, Subcommand)]\nenum Commands {\n    Sign(sign::Options),\n    Generate(generate::Options),\n}\n\n#[derive(Debug, Clone, Parser)]\n#[clap(about = \"Sign a file or generate a new signing key to sign files\")]\npub struct Options {\n    #[command(subcommand)]\n    command: Commands,\n}\n\npub fn command(options: Options) -> Result<()> {\n    match options.command {\n        Commands::Sign(opts) => sign::command(opts),\n        Commands::Generate(opts) => generate::command(opts),\n    }\n}\n"
  },
  {
    "path": "crates/packager/src/cli/signer/sign.rs",
    "content": "// Copyright 2019-2023 Tauri Programme within The Commons Conservancy\n// Copyright 2023-2023 CrabNebula Ltd.\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{fs, path::PathBuf};\n\nuse clap::Parser;\n\nuse crate::cli::{Error, Result};\n\n#[derive(Debug, Clone, Parser)]\n#[clap(about = \"Sign a file\")]\npub struct Options {\n    /// Load the private key from a file or a string.\n    #[clap(short = 'k', long, env = \"CARGO_PACKAGER_SIGN_PRIVATE_KEY\")]\n    private_key: Option<String>,\n    /// The password for the private key.\n    #[clap(long, env = \"CARGO_PACKAGER_SIGN_PRIVATE_KEY_PASSWORD\")]\n    password: Option<String>,\n    /// The file to be signed.\n    file: PathBuf,\n}\n\npub fn command(options: Options) -> Result<()> {\n    let private_key = match options.private_key {\n        Some(path) if PathBuf::from(&path).exists() => {\n            fs::read_to_string(&path).map_err(|e| Error::IoWithPath(PathBuf::from(&path), e))?\n        }\n        Some(key) => key,\n        None => {\n            tracing::error!(\"--private-key was not specified, aborting signign.\");\n            std::process::exit(1);\n        }\n    };\n\n    let config = crate::sign::SigningConfig {\n        private_key,\n        password: Some(options.password.unwrap_or_default()),\n    };\n    let signature_path = crate::sign::sign_file(&config, options.file)?;\n\n    tracing::info!(\n        \"Signed the file successfully! find the signature at: {}\",\n        signature_path.display()\n    );\n\n    Ok(())\n}\n"
  },
  {
    "path": "crates/packager/src/codesign/macos.rs",
    "content": "// Copyright 2019-2023 Tauri Programme within The Commons Conservancy\n// Copyright 2023-2023 CrabNebula Ltd.\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{\n    cmp::Ordering,\n    ffi::OsString,\n    fs::File,\n    io::prelude::*,\n    path::{Path, PathBuf},\n    process::Command,\n};\n\nuse serde::Deserialize;\n\nuse crate::{config::MacOsNotarizationCredentials, shell::CommandExt, Config, Error};\n\nconst KEYCHAIN_ID: &str = \"cargo-packager.keychain\";\nconst KEYCHAIN_PWD: &str = \"cargo-packager\";\n\n// Import certificate from ENV variables.\n// APPLE_CERTIFICATE is the p12 certificate base64 encoded.\n// By example you can use; openssl base64 -in MyCertificate.p12 -out MyCertificate-base64.txt\n// Then use the value of the base64 in APPLE_CERTIFICATE env variable.\n// You need to set APPLE_CERTIFICATE_PASSWORD to the password you set when you exported your certificate.\n// https://help.apple.com/xcode/mac/current/#/dev154b28f09 see: `Export a signing certificate`\n#[tracing::instrument(level = \"trace\")]\npub fn setup_keychain(\n    certificate_encoded: OsString,\n    certificate_password: OsString,\n) -> crate::Result<()> {\n    // we delete any previous version of our keychain if present\n    delete_keychain();\n\n    tracing::info!(\"Setting up keychain from environment variables...\");\n\n    let keychain_list_output = Command::new(\"security\")\n        .args([\"list-keychain\", \"-d\", \"user\"])\n        .output()\n        .map_err(Error::FailedToListKeyChain)?;\n\n    let tmp_dir = tempfile::tempdir()?;\n\n    let cert_path = tmp_dir\n        .path()\n        .join(\"cert.p12\")\n        .to_string_lossy()\n        .to_string();\n    let cert_path_tmp = tmp_dir.path().join(\"cert.p12.tmp\");\n    let cert_path_tmp_str = cert_path_tmp.to_string_lossy().to_string();\n    let certificate_encoded = certificate_encoded\n        .to_str()\n        .expect(\"failed to convert APPLE_CERTIFICATE to string\")\n        .as_bytes();\n    let certificate_password = certificate_password\n        .to_str()\n        .expect(\"failed to convert APPLE_CERTIFICATE_PASSWORD to string\")\n        .to_string();\n\n    // as certificate contain whitespace decoding may be broken\n    // https://github.com/marshallpierce/rust-base64/issues/105\n    // we'll use builtin base64 command from the OS\n    let mut tmp_cert =\n        File::create(&cert_path_tmp).map_err(|e| Error::IoWithPath(cert_path_tmp, e))?;\n    tmp_cert.write_all(certificate_encoded)?;\n\n    Command::new(\"base64\")\n        .args([\"--decode\", \"-i\", &cert_path_tmp_str, \"-o\", &cert_path])\n        .output_ok()\n        .map_err(Error::FailedToDecodeCert)?;\n\n    Command::new(\"security\")\n        .args([\"create-keychain\", \"-p\", KEYCHAIN_PWD, KEYCHAIN_ID])\n        .output_ok()\n        .map_err(Error::FailedToCreateKeyChain)?;\n\n    Command::new(\"security\")\n        .args([\"unlock-keychain\", \"-p\", KEYCHAIN_PWD, KEYCHAIN_ID])\n        .output_ok()\n        .map_err(Error::FailedToUnlockKeyChain)?;\n\n    Command::new(\"security\")\n        .args([\n            \"import\",\n            &cert_path,\n            \"-k\",\n            KEYCHAIN_ID,\n            \"-P\",\n            &certificate_password,\n            \"-T\",\n            \"/usr/bin/codesign\",\n            \"-T\",\n            \"/usr/bin/pkgbuild\",\n            \"-T\",\n            \"/usr/bin/productbuild\",\n        ])\n        .output_ok()\n        .map_err(Error::FailedToImportCert)?;\n\n    Command::new(\"security\")\n        .args([\"set-keychain-settings\", \"-t\", \"3600\", \"-u\", KEYCHAIN_ID])\n        .output_ok()\n        .map_err(Error::FailedToSetKeychainSettings)?;\n\n    Command::new(\"security\")\n        .args([\n            \"set-key-partition-list\",\n            \"-S\",\n            \"apple-tool:,apple:,codesign:\",\n            \"-s\",\n            \"-k\",\n            KEYCHAIN_PWD,\n            KEYCHAIN_ID,\n        ])\n        .output_ok()\n        .map_err(Error::FailedToSetKeyPartitionList)?;\n\n    let current_keychains = String::from_utf8_lossy(&keychain_list_output.stdout)\n        .split('\\n')\n        .map(|line| {\n            line.trim_matches(|c: char| c.is_whitespace() || c == '\"')\n                .to_string()\n        })\n        .filter(|l| !l.is_empty())\n        .collect::<Vec<String>>();\n\n    Command::new(\"security\")\n        .args([\"list-keychain\", \"-d\", \"user\", \"-s\"])\n        .args(current_keychains)\n        .arg(KEYCHAIN_ID)\n        .output_ok()\n        .map_err(Error::FailedToListKeyChain)?;\n\n    Ok(())\n}\n\n#[tracing::instrument(level = \"trace\")]\npub fn delete_keychain() {\n    // delete keychain if needed and skip any error\n    let _ = Command::new(\"security\")\n        .arg(\"delete-keychain\")\n        .arg(KEYCHAIN_ID)\n        .output_ok();\n}\n\n#[derive(Debug, PartialEq, Eq)]\npub struct SignTarget {\n    pub path: PathBuf,\n    pub is_native_binary: bool,\n}\n\nimpl Ord for SignTarget {\n    fn cmp(&self, other: &Self) -> Ordering {\n        let self_count = self.path.components().count();\n        let other_count = other.path.components().count();\n\n        // Sort by path depth (note that we compare other to self, not self to other) so\n        // that longer paths have a smaller value!\n        other_count.cmp(&self_count)\n    }\n}\n\nimpl PartialOrd for SignTarget {\n    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {\n        Some(self.cmp(other))\n    }\n}\n\n#[tracing::instrument(level = \"trace\", skip(config))]\npub fn try_sign(targets: Vec<SignTarget>, identity: &str, config: &Config) -> crate::Result<()> {\n    let certificate_encoded = config\n        .macos()\n        .and_then(|m| m.signing_certificate.clone())\n        .or_else(|| std::env::var_os(\"APPLE_CERTIFICATE\"));\n\n    let certificate_password = config\n        .macos()\n        .and_then(|m| m.signing_certificate_password.clone())\n        .or_else(|| std::env::var_os(\"APPLE_CERTIFICATE_PASSWORD\"));\n\n    let packager_keychain = if let (Some(certificate_encoded), Some(certificate_password)) =\n        (certificate_encoded, certificate_password)\n    {\n        // setup keychain allow you to import your certificate\n        // for CI build\n        setup_keychain(certificate_encoded, certificate_password)?;\n        true\n    } else {\n        false\n    };\n\n    for target in targets {\n        sign(\n            &target.path,\n            identity,\n            config,\n            target.is_native_binary,\n            packager_keychain,\n        )?;\n    }\n\n    if packager_keychain {\n        // delete the keychain again after signing\n        delete_keychain();\n    }\n\n    Ok(())\n}\n\n#[tracing::instrument(level = \"trace\", skip(config))]\nfn sign(\n    path_to_sign: &Path,\n    identity: &str,\n    config: &Config,\n    is_native_binary: bool,\n    packager_keychain: bool,\n) -> crate::Result<()> {\n    tracing::info!(\n        \"Codesigning {} with identity \\\"{}\\\"\",\n        path_to_sign.display(),\n        identity\n    );\n\n    let mut args = vec![\"--force\", \"-s\", identity];\n\n    if packager_keychain {\n        args.push(\"--keychain\");\n        args.push(KEYCHAIN_ID);\n    }\n\n    if let Some(entitlements_path) = config.macos().and_then(|macos| macos.entitlements.as_ref()) {\n        args.push(\"--entitlements\");\n        args.push(entitlements_path);\n    }\n\n    if is_native_binary {\n        args.push(\"--options\");\n        args.push(\"runtime\");\n    }\n\n    args.push(\"--timestamp\");\n\n    Command::new(\"codesign\")\n        .args(args)\n        .arg(path_to_sign)\n        .output_ok()\n        .map_err(Error::FailedToRunCodesign)?;\n\n    Ok(())\n}\n\n#[derive(Deserialize, Debug)]\nstruct NotarytoolSubmitOutput {\n    id: String,\n    status: String,\n    message: String,\n}\n\n#[tracing::instrument(level = \"trace\", skip(config))]\npub fn notarize(\n    app_bundle_path: PathBuf,\n    auth: MacOsNotarizationCredentials,\n    config: &Config,\n) -> crate::Result<()> {\n    let bundle_stem = app_bundle_path\n        .file_stem()\n        .ok_or_else(|| Error::FailedToExtractFilename(app_bundle_path.clone()))?;\n\n    let tmp_dir = tempfile::tempdir()?;\n    let zip_path = tmp_dir\n        .path()\n        .join(format!(\"{}.zip\", bundle_stem.to_string_lossy()));\n\n    let app_bundle_path_str = app_bundle_path.to_string_lossy().to_string();\n    let zip_path_str = zip_path.to_string_lossy().to_string();\n    let zip_args = vec![\n        \"-c\",\n        \"-k\",\n        \"--keepParent\",\n        \"--sequesterRsrc\",\n        &app_bundle_path_str,\n        &zip_path_str,\n    ];\n\n    // use ditto to create a PKZip almost identical to Finder\n    // this remove almost 99% of false alarm in notarization\n    Command::new(\"ditto\")\n        .args(zip_args)\n        .output_ok()\n        .map_err(Error::FailedToRunDitto)?;\n\n    // sign the zip file\n    if let Some(identity) = &config\n        .macos()\n        .and_then(|macos| macos.signing_identity.as_ref())\n    {\n        try_sign(\n            vec![SignTarget {\n                path: zip_path.clone(),\n                is_native_binary: false,\n            }],\n            identity,\n            config,\n        )?;\n    };\n\n    let zip_path_str = zip_path.to_string_lossy().to_string();\n\n    let notarize_args = vec![\n        \"notarytool\",\n        \"submit\",\n        &zip_path_str,\n        \"--wait\",\n        \"--output-format\",\n        \"json\",\n    ];\n\n    tracing::info!(\"Notarizing {}\", app_bundle_path.display());\n\n    let output = Command::new(\"xcrun\")\n        .args(notarize_args)\n        .notarytool_args(&auth)\n        .output_ok()\n        .map_err(Error::FailedToRunXcrun)?;\n\n    if !output.status.success() {\n        return Err(Error::FailedToNotarize);\n    }\n\n    let output_str = String::from_utf8_lossy(&output.stdout);\n    if let Ok(submit_output) = serde_json::from_str::<NotarytoolSubmitOutput>(&output_str) {\n        let log_message = format!(\n            \"Finished with status {} for id {} ({})\",\n            submit_output.status, submit_output.id, submit_output.message\n        );\n        if submit_output.status == \"Accepted\" {\n            tracing::info!(\"Notarizing {}\", log_message);\n            staple_app(app_bundle_path)?;\n            Ok(())\n        } else if let Ok(output) = Command::new(\"xcrun\")\n            .args([\"notarytool\", \"log\"])\n            .arg(&submit_output.id)\n            .notarytool_args(&auth)\n            .output_ok()\n        {\n            Err(Error::NotarizeRejected(format!(\n                \"{log_message}\\nLog:\\n{}\",\n                String::from_utf8_lossy(&output.stdout),\n            )))\n        } else {\n            Err(Error::NotarizeRejected(log_message))\n        }\n    } else {\n        Err(Error::FailedToParseNotarytoolOutput(\n            output_str.into_owned(),\n        ))\n    }\n}\n\nfn staple_app(app_bundle_path: PathBuf) -> crate::Result<()> {\n    let filename = app_bundle_path\n        .file_name()\n        .ok_or_else(|| Error::FailedToExtractFilename(app_bundle_path.clone()))?\n        .to_string_lossy()\n        .to_string();\n\n    let app_bundle_path_dir = app_bundle_path\n        .parent()\n        .ok_or_else(|| Error::ParentDirNotFound(app_bundle_path.clone()))?;\n\n    Command::new(\"xcrun\")\n        .args(vec![\"stapler\", \"staple\", \"-v\", &filename])\n        .current_dir(app_bundle_path_dir)\n        .output_ok()\n        .map_err(Error::FailedToRunXcrun)?;\n\n    Ok(())\n}\n\npub trait NotarytoolCmdExt {\n    fn notarytool_args(&mut self, auth: &MacOsNotarizationCredentials) -> &mut Self;\n}\n\nimpl NotarytoolCmdExt for Command {\n    fn notarytool_args(&mut self, auth: &MacOsNotarizationCredentials) -> &mut Self {\n        match auth {\n            MacOsNotarizationCredentials::AppleId {\n                apple_id,\n                password,\n                team_id,\n            } => {\n                self.arg(\"--apple-id\")\n                    .arg(apple_id)\n                    .arg(\"--password\")\n                    .arg(password)\n                    .arg(\"--team-id\")\n                    .arg(team_id);\n\n                self\n            }\n            MacOsNotarizationCredentials::ApiKey {\n                key_id,\n                key_path,\n                issuer,\n            } => self\n                .arg(\"--key-id\")\n                .arg(key_id)\n                .arg(\"--key\")\n                .arg(key_path)\n                .arg(\"--issuer\")\n                .arg(issuer),\n            MacOsNotarizationCredentials::KeychainProfile { keychain_profile } => {\n                self.arg(\"--keychain-profile\").arg(keychain_profile)\n            }\n        }\n    }\n}\n\n#[tracing::instrument(level = \"trace\")]\npub fn notarize_auth() -> crate::Result<MacOsNotarizationCredentials> {\n    if let Some(keychain_profile) = std::env::var_os(\"APPLE_KEYCHAIN_PROFILE\") {\n        Ok(MacOsNotarizationCredentials::KeychainProfile { keychain_profile })\n    } else {\n        match (\n            std::env::var_os(\"APPLE_ID\"),\n            std::env::var_os(\"APPLE_PASSWORD\"),\n            std::env::var_os(\"APPLE_TEAM_ID\"),\n        ) {\n            (Some(apple_id), Some(password), Some(team_id)) => {\n                Ok(MacOsNotarizationCredentials::AppleId {\n                    apple_id,\n                    password,\n                    team_id,\n                })\n            }\n            _ => {\n                match (\n                    std::env::var_os(\"APPLE_API_KEY\"),\n                    std::env::var_os(\"APPLE_API_ISSUER\"),\n                    std::env::var(\"APPLE_API_KEY_PATH\"),\n                ) {\n                    (Some(key_id), Some(issuer), Ok(key_path)) => {\n                        Ok(MacOsNotarizationCredentials::ApiKey {\n                            key_id,\n                            key_path: key_path.into(),\n                            issuer,\n                        })\n                    }\n                    (Some(key_id), Some(issuer), Err(_)) => {\n                        let mut api_key_file_name = OsString::from(\"AuthKey_\");\n                        api_key_file_name.push(&key_id);\n                        api_key_file_name.push(\".p8\");\n                        let mut key_path = None;\n\n                        let mut search_paths = vec![\"./private_keys\".into()];\n                        if let Some(home_dir) = dirs::home_dir() {\n                            search_paths.push(home_dir.join(\"private_keys\"));\n                            search_paths.push(home_dir.join(\".private_keys\"));\n                            search_paths.push(home_dir.join(\".appstoreconnect/private_keys\"));\n                        }\n\n                        for folder in search_paths {\n                            if let Some(path) = find_api_key(folder, &api_key_file_name) {\n                                key_path = Some(path);\n                                break;\n                            }\n                        }\n\n                        if let Some(key_path) = key_path {\n                            Ok(MacOsNotarizationCredentials::ApiKey {\n                                key_id,\n                                key_path,\n                                issuer,\n                            })\n                        } else {\n                            Err(Error::ApiKeyMissing {\n                                filename: api_key_file_name\n                                    .into_string()\n                                    .expect(\"failed to convert api_key_file_name to string\"),\n                            })\n                        }\n                    }\n                    _ => Err(Error::MissingNotarizeAuthVars),\n                }\n            }\n        }\n    }\n}\n\nfn find_api_key(folder: PathBuf, file_name: &OsString) -> Option<PathBuf> {\n    let path = folder.join(file_name);\n    if path.exists() {\n        Some(path)\n    } else {\n        None\n    }\n}\n"
  },
  {
    "path": "crates/packager/src/codesign/mod.rs",
    "content": "// Copyright 2023-2023 CrabNebula Ltd.\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n#[cfg(target_os = \"macos\")]\npub mod macos;\n\npub mod windows;\n"
  },
  {
    "path": "crates/packager/src/codesign/windows.rs",
    "content": "// Copyright 2019-2023 Tauri Programme within The Commons Conservancy\n// Copyright 2023-2023 CrabNebula Ltd.\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{fmt::Debug, path::Path, process::Command};\n\n#[cfg(windows)]\nuse once_cell::sync::Lazy;\n#[cfg(windows)]\nuse std::path::PathBuf;\n\nuse crate::{config::Config, shell::CommandExt, util};\n\n#[cfg(windows)]\nuse crate::util::Bitness;\n\n#[derive(Debug)]\n#[allow(dead_code)]\npub struct SignParams {\n    pub product_name: String,\n    pub digest_algorithm: String,\n    pub certificate_thumbprint: String,\n    pub timestamp_url: Option<String>,\n    pub tsp: bool,\n    pub sign_command: Option<String>,\n}\n\nimpl Config {\n    pub(crate) fn can_sign(&self) -> bool {\n        self.windows()\n            .and_then(|w| w.certificate_thumbprint.as_ref())\n            .is_some()\n            || self.custom_sign_command()\n    }\n\n    pub(crate) fn custom_sign_command(&self) -> bool {\n        self.windows()\n            .and_then(|w| w.sign_command.as_ref())\n            .is_some()\n    }\n\n    pub(crate) fn sign_params(&self) -> SignParams {\n        let windows = self.windows();\n        SignParams {\n            product_name: self.product_name.clone(),\n            digest_algorithm: windows\n                .and_then(|w| w.digest_algorithm.as_ref())\n                .cloned()\n                .unwrap_or_else(|| \"sha256\".to_string()),\n            certificate_thumbprint: windows\n                .and_then(|w| w.certificate_thumbprint.as_ref())\n                .cloned()\n                .unwrap_or_default(),\n            timestamp_url: windows.and_then(|w| w.timestamp_url.as_ref()).cloned(),\n            tsp: windows.map(|w| w.tsp).unwrap_or_default(),\n            sign_command: windows.and_then(|w| w.sign_command.as_ref()).cloned(),\n        }\n    }\n}\n\n#[cfg(windows)]\nstatic SIGN_TOOL: Lazy<crate::Result<PathBuf>> = Lazy::new(|| {\n    let _s = tracing::span!(tracing::Level::TRACE, \"locate_signtool\");\n    const INSTALLED_ROOTS_REGKEY_PATH: &str = r\"SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots\";\n    const KITS_ROOT_REGVALUE_NAME: &str = r\"KitsRoot10\";\n\n    // Open 32-bit HKLM \"Installed Roots\" key\n    let installed_roots_key = windows_registry::LOCAL_MACHINE\n        .open(INSTALLED_ROOTS_REGKEY_PATH)\n        .map_err(|_| crate::Error::OpenRegistry(INSTALLED_ROOTS_REGKEY_PATH.to_string()))?;\n\n    // Get the Windows SDK root path\n    let kits_root_10_path: String = installed_roots_key\n        .get_string(KITS_ROOT_REGVALUE_NAME)\n        .map_err(|_| crate::Error::GetRegistryValue(KITS_ROOT_REGVALUE_NAME.to_string()))?;\n\n    // Construct Windows SDK bin path\n    let kits_root_10_bin_path = Path::new(&kits_root_10_path).join(\"bin\");\n\n    let mut installed_kits: Vec<String> = installed_roots_key\n        .keys()\n        .map_err(|_| crate::Error::FailedToEnumerateRegKeys)?\n        .collect();\n\n    // Sort installed kits\n    installed_kits.sort();\n\n    /* Iterate through installed kit version keys in reverse (from newest to oldest),\n    adding their bin paths to the list.\n    Windows SDK 10 v10.0.15063.468 and later will have their signtools located there. */\n    let mut kit_bin_paths: Vec<PathBuf> = installed_kits\n        .iter()\n        .rev()\n        .map(|kit| kits_root_10_bin_path.join(kit))\n        .collect();\n\n    /* Add kits root bin path.\n    For Windows SDK 10 versions earlier than v10.0.15063.468, signtool will be located there. */\n    kit_bin_paths.push(kits_root_10_bin_path);\n\n    // Choose which version of SignTool to use based on OS bitness\n    let arch_dir = match util::os_bitness().expect(\"failed to get os bitness\") {\n        Bitness::X86_32 => \"x86\",\n        Bitness::X86_64 => \"x64\",\n        _ => return Err(crate::Error::UnsupportedBitness),\n    };\n\n    /* Iterate through all bin paths, checking for existence of a SignTool executable. */\n    for kit_bin_path in &kit_bin_paths {\n        /* Construct SignTool path. */\n        let signtool_path = kit_bin_path.join(arch_dir).join(\"signtool.exe\");\n\n        /* Check if SignTool exists at this location. */\n        if signtool_path.exists() {\n            // SignTool found. Return it.\n            return Ok(signtool_path);\n        }\n    }\n\n    Err(crate::Error::SignToolNotFound)\n});\n\n#[cfg(windows)]\nfn signtool() -> Option<PathBuf> {\n    (*SIGN_TOOL).as_ref().ok().cloned()\n}\n\n#[tracing::instrument(level = \"trace\")]\npub fn sign_command_custom<P: AsRef<Path> + Debug>(\n    path: P,\n    command: &str,\n) -> crate::Result<Command> {\n    let mut args = command.trim().split(' ');\n\n    let bin = args\n        .next()\n        .expect(\"custom signing command doesn't contain a bin?\");\n\n    let mut cmd = Command::new(bin);\n\n    for arg in args {\n        if arg == \"%1\" {\n            cmd.arg(path.as_ref());\n        } else {\n            cmd.arg(arg);\n        }\n    }\n\n    Ok(cmd)\n}\n\n#[cfg(windows)]\n#[tracing::instrument(level = \"trace\")]\npub fn sign_command_default<P: AsRef<Path> + Debug>(\n    path: P,\n    params: &SignParams,\n) -> crate::Result<Command> {\n    let signtool = signtool().ok_or(crate::Error::SignToolNotFound)?;\n\n    let mut cmd = Command::new(signtool);\n    cmd.arg(\"sign\");\n    cmd.args([\"/fd\", &params.digest_algorithm]);\n    cmd.args([\"/sha1\", &params.certificate_thumbprint]);\n    cmd.args([\"/d\", &params.product_name]);\n\n    if let Some(ref timestamp_url) = params.timestamp_url {\n        if params.tsp {\n            cmd.args([\"/tr\", timestamp_url]);\n            cmd.args([\"/td\", &params.digest_algorithm]);\n        } else {\n            cmd.args([\"/t\", timestamp_url]);\n        }\n    }\n\n    cmd.arg(path.as_ref());\n\n    Ok(cmd)\n}\n\n#[tracing::instrument(level = \"trace\")]\npub fn sign_command<P: AsRef<Path> + Debug>(\n    path: P,\n    params: &SignParams,\n) -> crate::Result<Command> {\n    match &params.sign_command {\n        Some(custom_command) => sign_command_custom(path, custom_command),\n        #[cfg(windows)]\n        None => sign_command_default(path, params),\n\n        // should not be reachable\n        #[cfg(not(windows))]\n        None => Ok(Command::new(\"\")),\n    }\n}\n\n#[tracing::instrument(level = \"trace\")]\npub fn sign_custom<P: AsRef<Path> + Debug>(path: P, custom_command: &str) -> crate::Result<()> {\n    let path = path.as_ref();\n\n    tracing::info!(\n        \"Codesigning {} with a custom signing command\",\n        util::display_path(path),\n    );\n\n    let mut cmd = sign_command_custom(path, custom_command)?;\n\n    let output = cmd\n        .output_ok()\n        .map_err(crate::Error::CustomSignCommandFailed)?;\n\n    let stdout = String::from_utf8_lossy(output.stdout.as_slice());\n    tracing::info!(\"{:?}\", stdout);\n\n    Ok(())\n}\n\n#[tracing::instrument(level = \"trace\")]\n#[cfg(windows)]\npub fn sign_default<P: AsRef<Path> + Debug>(path: P, params: &SignParams) -> crate::Result<()> {\n    let signtool = signtool().ok_or(crate::Error::SignToolNotFound)?;\n    let path = path.as_ref();\n\n    tracing::info!(\n        \"Codesigning {} with certificate \\\"{}\\\"\",\n        util::display_path(path),\n        params.certificate_thumbprint\n    );\n\n    let mut cmd = sign_command_default(path, params)?;\n\n    tracing::debug!(\"Running signtool {:?}\", signtool);\n    let output = cmd.output_ok().map_err(crate::Error::SignToolFailed)?;\n\n    let stdout = String::from_utf8_lossy(output.stdout.as_slice());\n    tracing::debug!(\"{:?}\", stdout);\n\n    Ok(())\n}\n\n#[tracing::instrument(level = \"trace\")]\npub fn sign<P: AsRef<Path> + Debug>(path: P, params: &SignParams) -> crate::Result<()> {\n    match &params.sign_command {\n        Some(custom_command) => sign_custom(path, custom_command),\n        #[cfg(windows)]\n        None => sign_default(path, params),\n\n        // should not be reachable\n        #[cfg(not(windows))]\n        None => Ok(()),\n    }\n}\n\n#[tracing::instrument(level = \"trace\", skip(config))]\npub fn try_sign(\n    file_path: &std::path::PathBuf,\n    config: &crate::config::Config,\n) -> crate::Result<()> {\n    if config.can_sign() {\n        sign(file_path, &config.sign_params())?;\n    }\n    Ok(())\n}\n"
  },
  {
    "path": "crates/packager/src/config/builder.rs",
    "content": "use std::path::PathBuf;\n\nuse crate::{Config, PackageFormat};\n\nuse super::{\n    AppImageConfig, Binary, DebianConfig, FileAssociation, HookCommand, LogLevel, MacOsConfig,\n    NsisConfig, PacmanConfig, Resource, WindowsConfig, WixConfig,\n};\n\n/// A builder type for [`Config`].\n#[derive(Default)]\npub struct ConfigBuilder(Config);\n\nimpl ConfigBuilder {\n    /// Creates a new config builder.\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    /// Returns a reference to the config used by this builder.\n    pub fn config(&self) -> &Config {\n        &self.0\n    }\n\n    /// Sets [`Config::product_name`].\n    pub fn product_name<S: Into<String>>(mut self, product_name: S) -> Self {\n        self.0.product_name = product_name.into();\n        self\n    }\n\n    /// Sets [`Config::version`].\n    pub fn version<S: Into<String>>(mut self, version: S) -> Self {\n        self.0.version = version.into();\n        self\n    }\n\n    /// Sets [`Config::binaries`].\n    pub fn binaries<I: IntoIterator<Item = Binary>>(mut self, binaries: I) -> Self {\n        self.0.binaries = binaries.into_iter().collect();\n        self\n    }\n\n    /// Sets [`Config::identifier`].\n    pub fn identifier<S: Into<String>>(mut self, identifier: S) -> Self {\n        self.0.identifier.replace(identifier.into());\n        self\n    }\n\n    /// Sets [`Config::before_packaging_command`].\n    pub fn before_packaging_command(mut self, command: HookCommand) -> Self {\n        self.0.before_packaging_command.replace(command);\n        self\n    }\n\n    /// Sets [`Config::before_each_package_command`].\n    pub fn before_each_package_command(mut self, command: HookCommand) -> Self {\n        self.0.before_each_package_command.replace(command);\n        self\n    }\n\n    /// Sets [`Config::log_level`].\n    pub fn log_level(mut self, level: LogLevel) -> Self {\n        self.0.log_level.replace(level);\n        self\n    }\n\n    /// Sets [`Config::formats`].\n    pub fn formats<I: IntoIterator<Item = PackageFormat>>(mut self, formats: I) -> Self {\n        self.0.formats = Some(formats.into_iter().collect());\n        self\n    }\n\n    /// Sets [`Config::out_dir`].\n    pub fn out_dir<P: Into<PathBuf>>(mut self, path: P) -> Self {\n        self.0.out_dir = path.into();\n        self\n    }\n\n    /// Sets [`Config::target_triple`].\n    pub fn target_triple<S: Into<String>>(mut self, target_triple: S) -> Self {\n        self.0.target_triple.replace(target_triple.into());\n        self\n    }\n\n    /// Sets [`Config::description`].\n    pub fn description<S: Into<String>>(mut self, description: S) -> Self {\n        self.0.description.replace(description.into());\n        self\n    }\n\n    /// Sets [`Config::long_description`].\n    pub fn long_description<S: Into<String>>(mut self, long_description: S) -> Self {\n        self.0.long_description.replace(long_description.into());\n        self\n    }\n\n    /// Sets [`Config::homepage`].\n    pub fn homepage<S: Into<String>>(mut self, homepage: S) -> Self {\n        self.0.homepage.replace(homepage.into());\n        self\n    }\n\n    /// Sets [`Config::authors`].\n    pub fn authors<I, S>(mut self, authors: I) -> Self\n    where\n        I: IntoIterator<Item = S>,\n        S: Into<String>,\n    {\n        self.0\n            .authors\n            .replace(authors.into_iter().map(Into::into).collect());\n        self\n    }\n\n    /// Sets [`Config::publisher`].\n    pub fn publisher<S: Into<String>>(mut self, publisher: S) -> Self {\n        self.0.publisher.replace(publisher.into());\n        self\n    }\n\n    /// Sets [`Config::license_file`].\n    pub fn license_file<P: Into<PathBuf>>(mut self, license_file: P) -> Self {\n        self.0.license_file.replace(license_file.into());\n        self\n    }\n\n    /// Sets [`Config::copyright`].\n    pub fn copyright<S: Into<String>>(mut self, copyright: S) -> Self {\n        self.0.copyright.replace(copyright.into());\n        self\n    }\n\n    /// Sets [`Config::icons`].\n    pub fn icons<I, S>(mut self, icons: I) -> Self\n    where\n        I: IntoIterator<Item = S>,\n        S: Into<String>,\n    {\n        self.0\n            .icons\n            .replace(icons.into_iter().map(Into::into).collect());\n        self\n    }\n\n    /// Sets [`Config::file_associations`].\n    pub fn file_associations<I: IntoIterator<Item = FileAssociation>>(\n        mut self,\n        file_associations: I,\n    ) -> Self {\n        self.0\n            .file_associations\n            .replace(file_associations.into_iter().collect());\n        self\n    }\n\n    /// Sets [`Config::resources`].\n    pub fn resources<I: IntoIterator<Item = Resource>>(mut self, resources: I) -> Self {\n        self.0.resources.replace(resources.into_iter().collect());\n        self\n    }\n\n    /// Sets [`Config::external_binaries`].\n    pub fn external_binaries<I, P>(mut self, external_binaries: I) -> Self\n    where\n        I: IntoIterator<Item = P>,\n        P: Into<PathBuf>,\n    {\n        self.0\n            .external_binaries\n            .replace(external_binaries.into_iter().map(Into::into).collect());\n        self\n    }\n\n    /// Set the [Windows](Config::windows) specific configuration.\n    pub fn windows(mut self, windows: WindowsConfig) -> Self {\n        self.0.windows.replace(windows);\n        self\n    }\n\n    /// Set the [MacOS](Config::macos) specific configuration.\n    pub fn macos(mut self, macos: MacOsConfig) -> Self {\n        self.0.macos.replace(macos);\n        self\n    }\n\n    /// Set the [WiX](Config::wix) specific configuration.\n    pub fn wix(mut self, wix: WixConfig) -> Self {\n        self.0.wix.replace(wix);\n        self\n    }\n\n    /// Set the [Nsis](Config::nsis) specific configuration.\n    pub fn nsis(mut self, nsis: NsisConfig) -> Self {\n        self.0.nsis.replace(nsis);\n        self\n    }\n\n    /// Set the [Debian](Config::deb) specific configuration.\n    pub fn deb(mut self, deb: DebianConfig) -> Self {\n        self.0.deb.replace(deb);\n        self\n    }\n\n    /// Set the [Appimage](Config::appimage) specific configuration.\n    pub fn appimage(mut self, appimage: AppImageConfig) -> Self {\n        self.0.appimage.replace(appimage);\n        self\n    }\n\n    /// Set the [Pacman](Config::pacman) specific configuration.\n    pub fn pacman(mut self, pacman: PacmanConfig) -> Self {\n        self.0.pacman.replace(pacman);\n        self\n    }\n}\n"
  },
  {
    "path": "crates/packager/src/config/category.rs",
    "content": "// Copyright 2016-2019 Cargo-Bundle developers <https://github.com/burtonageo/cargo-bundle>\n// Copyright 2019-2023 Tauri Programme within The Commons Conservancy\n// Copyright 2023-2023 CrabNebula Ltd.\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{fmt, str::FromStr};\n\nuse serde::Serialize;\n\nconst CONFIDENCE_THRESHOLD: f64 = 0.8;\n\nconst MACOS_APP_CATEGORY_PREFIX: &str = \"public.app-category.\";\n\n/// The possible app categories.\n/// Corresponds to `LSApplicationCategoryType` on macOS and the GNOME desktop categories on Debian.\n#[allow(missing_docs)]\n#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[non_exhaustive]\npub enum AppCategory {\n    Business,\n    DeveloperTool,\n    Education,\n    Entertainment,\n    Finance,\n    Game,\n    ActionGame,\n    AdventureGame,\n    ArcadeGame,\n    BoardGame,\n    CardGame,\n    CasinoGame,\n    DiceGame,\n    EducationalGame,\n    FamilyGame,\n    KidsGame,\n    MusicGame,\n    PuzzleGame,\n    RacingGame,\n    RolePlayingGame,\n    SimulationGame,\n    SportsGame,\n    StrategyGame,\n    TriviaGame,\n    WordGame,\n    GraphicsAndDesign,\n    HealthcareAndFitness,\n    Lifestyle,\n    Medical,\n    Music,\n    News,\n    Photography,\n    Productivity,\n    Reference,\n    SocialNetworking,\n    Sports,\n    Travel,\n    Utility,\n    Video,\n    Weather,\n}\n\nimpl FromStr for AppCategory {\n    type Err = Option<&'static str>;\n\n    /// Given a string, returns the `AppCategory` it refers to, or the closest\n    /// string that the user might have intended (if any).\n    fn from_str(input: &str) -> Result<AppCategory, Self::Err> {\n        // Canonicalize input:\n        let mut input = input.to_ascii_lowercase();\n        if input.starts_with(MACOS_APP_CATEGORY_PREFIX) {\n            input = input\n                .split_at(MACOS_APP_CATEGORY_PREFIX.len())\n                .1\n                .to_string();\n        }\n        input = input.replace(' ', \"\");\n        input = input.replace('-', \"\");\n\n        // Find best match:\n        let mut best_confidence = 0.0;\n        let mut best_category: Option<AppCategory> = None;\n        for &(string, category) in CATEGORY_STRINGS.iter() {\n            if input == string {\n                return Ok(category);\n            }\n            let confidence = strsim::jaro_winkler(&input, string);\n            if confidence >= CONFIDENCE_THRESHOLD && confidence > best_confidence {\n                best_confidence = confidence;\n                best_category = Some(category);\n            }\n        }\n        Err(best_category.map(AppCategory::canonical))\n    }\n}\n\nimpl AppCategory {\n    /// Map an AppCategory to the string we recommend to use in Cargo.toml if\n    /// the users misspells the category name.\n    fn canonical(self) -> &'static str {\n        match self {\n            AppCategory::Business => \"Business\",\n            AppCategory::DeveloperTool => \"Developer Tool\",\n            AppCategory::Education => \"Education\",\n            AppCategory::Entertainment => \"Entertainment\",\n            AppCategory::Finance => \"Finance\",\n            AppCategory::Game => \"Game\",\n            AppCategory::ActionGame => \"Action Game\",\n            AppCategory::AdventureGame => \"Adventure Game\",\n            AppCategory::ArcadeGame => \"Arcade Game\",\n            AppCategory::BoardGame => \"Board Game\",\n            AppCategory::CardGame => \"Card Game\",\n            AppCategory::CasinoGame => \"Casino Game\",\n            AppCategory::DiceGame => \"Dice Game\",\n            AppCategory::EducationalGame => \"Educational Game\",\n            AppCategory::FamilyGame => \"Family Game\",\n            AppCategory::KidsGame => \"Kids Game\",\n            AppCategory::MusicGame => \"Music Game\",\n            AppCategory::PuzzleGame => \"Puzzle Game\",\n            AppCategory::RacingGame => \"Racing Game\",\n            AppCategory::RolePlayingGame => \"Role-Playing Game\",\n            AppCategory::SimulationGame => \"Simulation Game\",\n            AppCategory::SportsGame => \"Sports Game\",\n            AppCategory::StrategyGame => \"Strategy Game\",\n            AppCategory::TriviaGame => \"Trivia Game\",\n            AppCategory::WordGame => \"Word Game\",\n            AppCategory::GraphicsAndDesign => \"Graphics and Design\",\n            AppCategory::HealthcareAndFitness => \"Healthcare and Fitness\",\n            AppCategory::Lifestyle => \"Lifestyle\",\n            AppCategory::Medical => \"Medical\",\n            AppCategory::Music => \"Music\",\n            AppCategory::News => \"News\",\n            AppCategory::Photography => \"Photography\",\n            AppCategory::Productivity => \"Productivity\",\n            AppCategory::Reference => \"Reference\",\n            AppCategory::SocialNetworking => \"Social Networking\",\n            AppCategory::Sports => \"Sports\",\n            AppCategory::Travel => \"Travel\",\n            AppCategory::Utility => \"Utility\",\n            AppCategory::Video => \"Video\",\n            AppCategory::Weather => \"Weather\",\n        }\n    }\n\n    /// Map an AppCategory to the closest set of GNOME desktop registered\n    /// categories that matches that category.\n    pub fn gnome_desktop_categories(self) -> &'static str {\n        match &self {\n            AppCategory::Business => \"Office;\",\n            AppCategory::DeveloperTool => \"Development;\",\n            AppCategory::Education => \"Education;\",\n            AppCategory::Entertainment => \"Network;\",\n            AppCategory::Finance => \"Office;Finance;\",\n            AppCategory::Game => \"Game;\",\n            AppCategory::ActionGame => \"Game;ActionGame;\",\n            AppCategory::AdventureGame => \"Game;AdventureGame;\",\n            AppCategory::ArcadeGame => \"Game;ArcadeGame;\",\n            AppCategory::BoardGame => \"Game;BoardGame;\",\n            AppCategory::CardGame => \"Game;CardGame;\",\n            AppCategory::CasinoGame => \"Game;\",\n            AppCategory::DiceGame => \"Game;\",\n            AppCategory::EducationalGame => \"Game;Education;\",\n            AppCategory::FamilyGame => \"Game;\",\n            AppCategory::KidsGame => \"Game;KidsGame;\",\n            AppCategory::MusicGame => \"Game;\",\n            AppCategory::PuzzleGame => \"Game;LogicGame;\",\n            AppCategory::RacingGame => \"Game;\",\n            AppCategory::RolePlayingGame => \"Game;RolePlaying;\",\n            AppCategory::SimulationGame => \"Game;Simulation;\",\n            AppCategory::SportsGame => \"Game;SportsGame;\",\n            AppCategory::StrategyGame => \"Game;StrategyGame;\",\n            AppCategory::TriviaGame => \"Game;\",\n            AppCategory::WordGame => \"Game;\",\n            AppCategory::GraphicsAndDesign => \"Graphics;\",\n            AppCategory::HealthcareAndFitness => \"Science;\",\n            AppCategory::Lifestyle => \"Education;\",\n            AppCategory::Medical => \"Science;MedicalSoftware;\",\n            AppCategory::Music => \"AudioVideo;Audio;Music;\",\n            AppCategory::News => \"Network;News;\",\n            AppCategory::Photography => \"Graphics;Photography;\",\n            AppCategory::Productivity => \"Office;\",\n            AppCategory::Reference => \"Education;\",\n            AppCategory::SocialNetworking => \"Network;\",\n            AppCategory::Sports => \"Education;Sports;\",\n            AppCategory::Travel => \"Education;\",\n            AppCategory::Utility => \"Utility;\",\n            AppCategory::Video => \"AudioVideo;Video;\",\n            AppCategory::Weather => \"Science;\",\n        }\n    }\n\n    /// Map an AppCategory to the closest LSApplicationCategoryType value that\n    /// matches that category.\n    pub fn macos_application_category_type(self) -> &'static str {\n        match &self {\n            AppCategory::Business => \"public.app-category.business\",\n            AppCategory::DeveloperTool => \"public.app-category.developer-tools\",\n            AppCategory::Education => \"public.app-category.education\",\n            AppCategory::Entertainment => \"public.app-category.entertainment\",\n            AppCategory::Finance => \"public.app-category.finance\",\n            AppCategory::Game => \"public.app-category.games\",\n            AppCategory::ActionGame => \"public.app-category.action-games\",\n            AppCategory::AdventureGame => \"public.app-category.adventure-games\",\n            AppCategory::ArcadeGame => \"public.app-category.arcade-games\",\n            AppCategory::BoardGame => \"public.app-category.board-games\",\n            AppCategory::CardGame => \"public.app-category.card-games\",\n            AppCategory::CasinoGame => \"public.app-category.casino-games\",\n            AppCategory::DiceGame => \"public.app-category.dice-games\",\n            AppCategory::EducationalGame => \"public.app-category.educational-games\",\n            AppCategory::FamilyGame => \"public.app-category.family-games\",\n            AppCategory::KidsGame => \"public.app-category.kids-games\",\n            AppCategory::MusicGame => \"public.app-category.music-games\",\n            AppCategory::PuzzleGame => \"public.app-category.puzzle-games\",\n            AppCategory::RacingGame => \"public.app-category.racing-games\",\n            AppCategory::RolePlayingGame => \"public.app-category.role-playing-games\",\n            AppCategory::SimulationGame => \"public.app-category.simulation-games\",\n            AppCategory::SportsGame => \"public.app-category.sports-games\",\n            AppCategory::StrategyGame => \"public.app-category.strategy-games\",\n            AppCategory::TriviaGame => \"public.app-category.trivia-games\",\n            AppCategory::WordGame => \"public.app-category.word-games\",\n            AppCategory::GraphicsAndDesign => \"public.app-category.graphics-design\",\n            AppCategory::HealthcareAndFitness => \"public.app-category.healthcare-fitness\",\n            AppCategory::Lifestyle => \"public.app-category.lifestyle\",\n            AppCategory::Medical => \"public.app-category.medical\",\n            AppCategory::Music => \"public.app-category.music\",\n            AppCategory::News => \"public.app-category.news\",\n            AppCategory::Photography => \"public.app-category.photography\",\n            AppCategory::Productivity => \"public.app-category.productivity\",\n            AppCategory::Reference => \"public.app-category.reference\",\n            AppCategory::SocialNetworking => \"public.app-category.social-networking\",\n            AppCategory::Sports => \"public.app-category.sports\",\n            AppCategory::Travel => \"public.app-category.travel\",\n            AppCategory::Utility => \"public.app-category.utilities\",\n            AppCategory::Video => \"public.app-category.video\",\n            AppCategory::Weather => \"public.app-category.weather\",\n        }\n    }\n}\n\nimpl<'d> serde::Deserialize<'d> for AppCategory {\n    fn deserialize<D: serde::Deserializer<'d>>(deserializer: D) -> Result<AppCategory, D::Error> {\n        deserializer.deserialize_str(AppCategoryVisitor { did_you_mean: None })\n    }\n}\n\nstruct AppCategoryVisitor {\n    did_you_mean: Option<&'static str>,\n}\n\nimpl serde::de::Visitor<'_> for AppCategoryVisitor {\n    type Value = AppCategory;\n\n    fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {\n        match self.did_you_mean {\n            Some(string) => write!(\n                formatter,\n                \"a valid app category string (did you mean \\\"{string}\\\"?)\"\n            ),\n            None => write!(formatter, \"a valid app category string\"),\n        }\n    }\n\n    fn visit_str<E: serde::de::Error>(mut self, value: &str) -> Result<AppCategory, E> {\n        match AppCategory::from_str(value) {\n            Ok(category) => Ok(category),\n            Err(did_you_mean) => {\n                self.did_you_mean = did_you_mean;\n                let unexp = serde::de::Unexpected::Str(value);\n                Err(serde::de::Error::invalid_value(unexp, &self))\n            }\n        }\n    }\n}\n\nconst CATEGORY_STRINGS: &[(&str, AppCategory)] = &[\n    (\"actiongame\", AppCategory::ActionGame),\n    (\"actiongames\", AppCategory::ActionGame),\n    (\"adventuregame\", AppCategory::AdventureGame),\n    (\"adventuregames\", AppCategory::AdventureGame),\n    (\"arcadegame\", AppCategory::ArcadeGame),\n    (\"arcadegames\", AppCategory::ArcadeGame),\n    (\"boardgame\", AppCategory::BoardGame),\n    (\"boardgames\", AppCategory::BoardGame),\n    (\"business\", AppCategory::Business),\n    (\"cardgame\", AppCategory::CardGame),\n    (\"cardgames\", AppCategory::CardGame),\n    (\"casinogame\", AppCategory::CasinoGame),\n    (\"casinogames\", AppCategory::CasinoGame),\n    (\"developer\", AppCategory::DeveloperTool),\n    (\"developertool\", AppCategory::DeveloperTool),\n    (\"developertools\", AppCategory::DeveloperTool),\n    (\"development\", AppCategory::DeveloperTool),\n    (\"dicegame\", AppCategory::DiceGame),\n    (\"dicegames\", AppCategory::DiceGame),\n    (\"education\", AppCategory::Education),\n    (\"educationalgame\", AppCategory::EducationalGame),\n    (\"educationalgames\", AppCategory::EducationalGame),\n    (\"entertainment\", AppCategory::Entertainment),\n    (\"familygame\", AppCategory::FamilyGame),\n    (\"familygames\", AppCategory::FamilyGame),\n    (\"finance\", AppCategory::Finance),\n    (\"fitness\", AppCategory::HealthcareAndFitness),\n    (\"game\", AppCategory::Game),\n    (\"games\", AppCategory::Game),\n    (\"graphicdesign\", AppCategory::GraphicsAndDesign),\n    (\"graphicsanddesign\", AppCategory::GraphicsAndDesign),\n    (\"graphicsdesign\", AppCategory::GraphicsAndDesign),\n    (\"healthcareandfitness\", AppCategory::HealthcareAndFitness),\n    (\"healthcarefitness\", AppCategory::HealthcareAndFitness),\n    (\"kidsgame\", AppCategory::KidsGame),\n    (\"kidsgames\", AppCategory::KidsGame),\n    (\"lifestyle\", AppCategory::Lifestyle),\n    (\"logicgame\", AppCategory::PuzzleGame),\n    (\"medical\", AppCategory::Medical),\n    (\"medicalsoftware\", AppCategory::Medical),\n    (\"music\", AppCategory::Music),\n    (\"musicgame\", AppCategory::MusicGame),\n    (\"musicgames\", AppCategory::MusicGame),\n    (\"news\", AppCategory::News),\n    (\"photography\", AppCategory::Photography),\n    (\"productivity\", AppCategory::Productivity),\n    (\"puzzlegame\", AppCategory::PuzzleGame),\n    (\"puzzlegames\", AppCategory::PuzzleGame),\n    (\"racinggame\", AppCategory::RacingGame),\n    (\"racinggames\", AppCategory::RacingGame),\n    (\"reference\", AppCategory::Reference),\n    (\"roleplaying\", AppCategory::RolePlayingGame),\n    (\"roleplayinggame\", AppCategory::RolePlayingGame),\n    (\"roleplayinggames\", AppCategory::RolePlayingGame),\n    (\"rpg\", AppCategory::RolePlayingGame),\n    (\"simulationgame\", AppCategory::SimulationGame),\n    (\"simulationgames\", AppCategory::SimulationGame),\n    (\"socialnetwork\", AppCategory::SocialNetworking),\n    (\"socialnetworking\", AppCategory::SocialNetworking),\n    (\"sports\", AppCategory::Sports),\n    (\"sportsgame\", AppCategory::SportsGame),\n    (\"sportsgames\", AppCategory::SportsGame),\n    (\"strategygame\", AppCategory::StrategyGame),\n    (\"strategygames\", AppCategory::StrategyGame),\n    (\"travel\", AppCategory::Travel),\n    (\"triviagame\", AppCategory::TriviaGame),\n    (\"triviagames\", AppCategory::TriviaGame),\n    (\"utilities\", AppCategory::Utility),\n    (\"utility\", AppCategory::Utility),\n    (\"video\", AppCategory::Video),\n    (\"weather\", AppCategory::Weather),\n    (\"wordgame\", AppCategory::WordGame),\n    (\"wordgames\", AppCategory::WordGame),\n];\n\n#[cfg(test)]\nmod tests {\n    use super::AppCategory;\n    use std::str::FromStr;\n\n    #[test]\n    fn category_from_string_ok() {\n        // Canonical name of category works:\n        assert_eq!(\n            AppCategory::from_str(\"Education\"),\n            Ok(AppCategory::Education)\n        );\n        assert_eq!(\n            AppCategory::from_str(\"Developer Tool\"),\n            Ok(AppCategory::DeveloperTool)\n        );\n        // Lowercase, spaces, and hyphens are fine:\n        assert_eq!(\n            AppCategory::from_str(\" puzzle  game \"),\n            Ok(AppCategory::PuzzleGame)\n        );\n        assert_eq!(\n            AppCategory::from_str(\"Role-playing game\"),\n            Ok(AppCategory::RolePlayingGame)\n        );\n        // Using macOS LSApplicationCategoryType value is fine:\n        assert_eq!(\n            AppCategory::from_str(\"public.app-category.developer-tools\"),\n            Ok(AppCategory::DeveloperTool)\n        );\n        assert_eq!(\n            AppCategory::from_str(\"public.app-category.role-playing-games\"),\n            Ok(AppCategory::RolePlayingGame)\n        );\n        // Using GNOME category name is fine:\n        assert_eq!(\n            AppCategory::from_str(\"Development\"),\n            Ok(AppCategory::DeveloperTool)\n        );\n        assert_eq!(\n            AppCategory::from_str(\"LogicGame\"),\n            Ok(AppCategory::PuzzleGame)\n        );\n        // Using common abbreviations is fine:\n        assert_eq!(\n            AppCategory::from_str(\"RPG\"),\n            Ok(AppCategory::RolePlayingGame)\n        );\n    }\n\n    #[test]\n    fn category_from_string_did_you_mean() {\n        assert_eq!(AppCategory::from_str(\"gaming\"), Err(Some(\"Game\")));\n        assert_eq!(AppCategory::from_str(\"photos\"), Err(Some(\"Photography\")));\n        assert_eq!(\n            AppCategory::from_str(\"strategery\"),\n            Err(Some(\"Strategy Game\"))\n        );\n    }\n\n    #[test]\n    fn category_from_string_totally_wrong() {\n        assert_eq!(AppCategory::from_str(\"fhqwhgads\"), Err(None));\n        assert_eq!(AppCategory::from_str(\"WHARRGARBL\"), Err(None));\n    }\n\n    #[test]\n    fn ls_application_category_type_round_trip() {\n        let values = &[\n            \"public.app-category.business\",\n            \"public.app-category.developer-tools\",\n            \"public.app-category.education\",\n            \"public.app-category.entertainment\",\n            \"public.app-category.finance\",\n            \"public.app-category.games\",\n            \"public.app-category.action-games\",\n            \"public.app-category.adventure-games\",\n            \"public.app-category.arcade-games\",\n            \"public.app-category.board-games\",\n            \"public.app-category.card-games\",\n            \"public.app-category.casino-games\",\n            \"public.app-category.dice-games\",\n            \"public.app-category.educational-games\",\n            \"public.app-category.family-games\",\n            \"public.app-category.kids-games\",\n            \"public.app-category.music-games\",\n            \"public.app-category.puzzle-games\",\n            \"public.app-category.racing-games\",\n            \"public.app-category.role-playing-games\",\n            \"public.app-category.simulation-games\",\n            \"public.app-category.sports-games\",\n            \"public.app-category.strategy-games\",\n            \"public.app-category.trivia-games\",\n            \"public.app-category.word-games\",\n            \"public.app-category.graphics-design\",\n            \"public.app-category.healthcare-fitness\",\n            \"public.app-category.lifestyle\",\n            \"public.app-category.medical\",\n            \"public.app-category.music\",\n            \"public.app-category.news\",\n            \"public.app-category.photography\",\n            \"public.app-category.productivity\",\n            \"public.app-category.reference\",\n            \"public.app-category.social-networking\",\n            \"public.app-category.sports\",\n            \"public.app-category.travel\",\n            \"public.app-category.utilities\",\n            \"public.app-category.video\",\n            \"public.app-category.weather\",\n        ];\n        // Test that if the user uses an LSApplicationCategoryType string as\n        // the category string, they will get back that same string for the\n        // macOS app bundle LSApplicationCategoryType.\n        for &value in values.iter() {\n            let category = AppCategory::from_str(value).expect(value);\n            assert_eq!(category.macos_application_category_type(), value);\n        }\n    }\n}\n"
  },
  {
    "path": "crates/packager/src/config/mod.rs",
    "content": "// Copyright 2023-2023 CrabNebula Ltd.\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! Configuration type and associated utilities.\n\nuse std::{\n    collections::HashMap,\n    ffi::OsString,\n    fmt::{self, Display},\n    fs,\n    path::{Path, PathBuf},\n};\n\nuse relative_path::PathExt;\nuse serde::{Deserialize, Serialize};\n\nuse crate::{util, Error};\n\nmod builder;\nmod category;\n\npub use builder::*;\npub use category::AppCategory;\n\npub use cargo_packager_utils::PackageFormat;\n\n/// **macOS-only**. Corresponds to CFBundleTypeRole\n#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\n#[derive(Default)]\npub enum BundleTypeRole {\n    /// CFBundleTypeRole.Editor. Files can be read and edited.\n    #[default]\n    Editor,\n    /// CFBundleTypeRole.Viewer. Files can be read.\n    Viewer,\n    /// CFBundleTypeRole.Shell\n    Shell,\n    /// CFBundleTypeRole.QLGenerator\n    QLGenerator,\n    /// CFBundleTypeRole.None\n    None,\n}\n\nimpl Display for BundleTypeRole {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        match self {\n            Self::Editor => write!(f, \"Editor\"),\n            Self::Viewer => write!(f, \"Viewer\"),\n            Self::Shell => write!(f, \"Shell\"),\n            Self::QLGenerator => write!(f, \"QLGenerator\"),\n            Self::None => write!(f, \"None\"),\n        }\n    }\n}\n\n/// A file association configuration.\n#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\n#[non_exhaustive]\npub struct FileAssociation {\n    /// File extensions to associate with this app. e.g. 'png'\n    pub extensions: Vec<String>,\n    /// The mime-type e.g. 'image/png' or 'text/plain'. **Linux-only**.\n    #[serde(alias = \"mime-type\", alias = \"mime_type\")]\n    pub mime_type: Option<String>,\n    /// The association description. **Windows-only**. It is displayed on the `Type` column on Windows Explorer.\n    pub description: Option<String>,\n    /// The name. Maps to `CFBundleTypeName` on macOS. Defaults to the first item in `ext`\n    pub name: Option<String>,\n    /// The app's role with respect to the type. Maps to `CFBundleTypeRole` on macOS.\n    /// Defaults to [`BundleTypeRole::Editor`]\n    #[serde(default)]\n    pub role: BundleTypeRole,\n}\n\nimpl FileAssociation {\n    /// Creates a new [`FileAssociation`] using provided extensions.\n    pub fn new<I, S>(extensions: I) -> Self\n    where\n        I: IntoIterator<Item = S>,\n        S: Into<String>,\n    {\n        Self {\n            extensions: extensions.into_iter().map(Into::into).collect(),\n            mime_type: None,\n            description: None,\n            name: None,\n            role: BundleTypeRole::default(),\n        }\n    }\n\n    /// Set the extenstions to associate with this app. e.g. 'png'.\n    pub fn extensions<I, S>(mut self, extensions: I) -> Self\n    where\n        I: IntoIterator<Item = S>,\n        S: Into<String>,\n    {\n        self.extensions = extensions.into_iter().map(Into::into).collect();\n        self\n    }\n\n    /// Set the mime-type e.g. 'image/png' or 'text/plain'. **Linux-only**.\n    pub fn mime_type<S: Into<String>>(mut self, mime_type: S) -> Self {\n        self.mime_type.replace(mime_type.into());\n        self\n    }\n\n    /// Se the association description. **Windows-only**. It is displayed on the `Type` column on Windows Explorer.\n    pub fn description<S: Into<String>>(mut self, description: S) -> Self {\n        self.description.replace(description.into());\n        self\n    }\n\n    /// Set he name. Maps to `CFBundleTypeName` on macOS. Defaults to the first item in `ext`\n    pub fn name<S: Into<String>>(mut self, name: S) -> Self {\n        self.name.replace(name.into());\n        self\n    }\n\n    /// Set he app's role with respect to the type. Maps to `CFBundleTypeRole` on macOS.\n    /// Defaults to [`BundleTypeRole::Editor`]\n    pub fn role(mut self, role: BundleTypeRole) -> Self {\n        self.role = role;\n        self\n    }\n}\n\n/// Deep link protocol\n#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\n#[non_exhaustive]\npub struct DeepLinkProtocol {\n    /// URL schemes to associate with this app without `://`. For example `my-app`\n    pub schemes: Vec<String>,\n    /// The protocol name. **macOS-only** and maps to `CFBundleTypeName`. Defaults to `<bundle-id>.<schemes[0]>`\n    pub name: Option<String>,\n    /// The app's role for these schemes. **macOS-only** and maps to `CFBundleTypeRole`.\n    #[serde(default)]\n    pub role: BundleTypeRole,\n}\n\nimpl DeepLinkProtocol {\n    /// Creates a new [`DeepLinkProtocol``] using provided schemes.\n    pub fn new<I, S>(schemes: I) -> Self\n    where\n        I: IntoIterator<Item = S>,\n        S: Into<String>,\n    {\n        Self {\n            schemes: schemes.into_iter().map(Into::into).collect(),\n            name: None,\n            role: BundleTypeRole::default(),\n        }\n    }\n\n    /// Set the name. Maps to `CFBundleTypeName` on macOS. Defaults to the first item in `ext`\n    pub fn name<S: Into<String>>(mut self, name: S) -> Self {\n        self.name.replace(name.into());\n        self\n    }\n\n    /// Set he app's role with respect to the type. Maps to `CFBundleTypeRole` on macOS.\n    /// Defaults to [`BundleTypeRole::Editor`]\n    pub fn role(mut self, role: BundleTypeRole) -> Self {\n        self.role = role;\n        self\n    }\n}\n\n/// The Linux Debian configuration.\n#[derive(Clone, Debug, Default, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\n#[non_exhaustive]\npub struct DebianConfig {\n    /// The list of Debian dependencies.\n    pub depends: Option<Dependencies>,\n    /// Path to a custom desktop file Handlebars template.\n    ///\n    /// Available variables: `categories`, `comment` (optional), `exec`, `icon` and `name`.\n    ///\n    /// Default file contents:\n    /// ```text\n    /// [Desktop Entry]\n    /// Categories={{categories}}\n    /// {{#if comment}}\n    /// Comment={{comment}}\n    /// {{/if}}\n    /// Exec={{exec}} {{exec_arg}}\n    /// Icon={{icon}}\n    /// Name={{name}}\n    /// Terminal=false\n    /// Type=Application\n    /// {{#if mime_type}}\n    /// MimeType={{mime_type}}\n    /// {{/if}}\n    /// ```\n    ///\n    /// The `{{exec_arg}}` will be set to:\n    /// * \"%F\", if at least one [Config::file_associations] was specified but no deep link protocols were given.\n    ///   * The \"%F\" arg means that your application can be invoked with multiple file paths.\n    /// * \"%U\", if at least one [Config::deep_link_protocols] was specified.\n    ///   * The \"%U\" arg means that your application can be invoked with multiple URLs.\n    ///   * If both [Config::file_associations] and [Config::deep_link_protocols] were specified,\n    ///     the \"%U\" arg will be used, causing the file paths to be passed to your app as `file://` URLs.\n    /// * An empty string \"\" (nothing) if neither are given.\n    ///   * This means that your application will never be invoked with any URLs or file paths.\n    ///\n    /// To specify a custom `exec_arg`, just use plaintext directly instead of `{{exec_arg}}`:\n    /// ```text\n    /// Exec={{exec}} %u\n    /// ```\n    ///\n    /// See more here: <https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#exec-variables>.\n    #[serde(alias = \"desktop-template\", alias = \"desktop_template\")]\n    pub desktop_template: Option<PathBuf>,\n    /// Define the section in Debian Control file. See : <https://www.debian.org/doc/debian-policy/ch-archive.html#s-subsections>\n    pub section: Option<String>,\n    /// Change the priority of the Debian Package. By default, it is set to `optional`.\n    /// Recognized Priorities as of now are :  `required`, `important`, `standard`, `optional`, `extra`\n    pub priority: Option<String>,\n    /// List of custom files to add to the deb package.\n    /// Maps a dir/file to a dir/file inside the debian package.\n    pub files: Option<HashMap<String, String>>,\n    /// Name to use for the `Package` field in the Debian Control file.\n    /// Defaults to [`Config::product_name`] converted to kebab-case.\n    #[serde(alias = \"package-name\", alias = \"package_name\")]\n    pub package_name: Option<String>,\n}\n\nimpl DebianConfig {\n    /// Creates a new [`DebianConfig`].\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    /// Set the list of Debian dependencies directly using an iterator of strings.\n    pub fn depends<I, S>(mut self, depends: I) -> Self\n    where\n        I: IntoIterator<Item = S>,\n        S: Into<String>,\n    {\n        self.depends.replace(Dependencies::List(\n            depends.into_iter().map(Into::into).collect(),\n        ));\n        self\n    }\n\n    /// Set the list of Debian dependencies indirectly via a path to a file,\n    /// which must contain one dependency (a package name) per line.\n    pub fn depends_path<P>(mut self, path: P) -> Self\n    where\n        P: Into<PathBuf>,\n    {\n        self.depends.replace(Dependencies::Path(path.into()));\n        self\n    }\n\n    /// Set the path to a custom desktop file Handlebars template.\n    ///\n    /// Available variables: `categories`, `comment` (optional), `exec`, `icon` and `name`.\n    ///\n    /// Default file contents:\n    /// ```text\n    /// [Desktop Entry]\n    /// Categories={{categories}}\n    /// {{#if comment}}\n    /// Comment={{comment}}\n    /// {{/if}}\n    /// Exec={{exec}} {{exec_arg}}\n    /// Icon={{icon}}\n    /// Name={{name}}\n    /// Terminal=false\n    /// Type=Application\n    /// {{#if mime_type}}\n    /// MimeType={{mime_type}}\n    /// {{/if}}\n    /// ```\n    pub fn desktop_template<P: Into<PathBuf>>(mut self, desktop_template: P) -> Self {\n        self.desktop_template.replace(desktop_template.into());\n        self\n    }\n\n    /// Define the section in Debian Control file. See : <https://www.debian.org/doc/debian-policy/ch-archive.html#s-subsections>\n    pub fn section<S: Into<String>>(mut self, section: S) -> Self {\n        self.section.replace(section.into());\n        self\n    }\n\n    /// Change the priority of the Debian Package. By default, it is set to `optional`.\n    /// Recognized Priorities as of now are :  `required`, `important`, `standard`, `optional`, `extra`\n    pub fn priority<S: Into<String>>(mut self, priority: S) -> Self {\n        self.priority.replace(priority.into());\n        self\n    }\n\n    /// Set the list of custom files to add to the deb package.\n    /// Maps a dir/file to a dir/file inside the debian package.\n    pub fn files<I, S, T>(mut self, files: I) -> Self\n    where\n        I: IntoIterator<Item = (S, T)>,\n        S: Into<String>,\n        T: Into<String>,\n    {\n        self.files.replace(\n            files\n                .into_iter()\n                .map(|(k, v)| (k.into(), v.into()))\n                .collect(),\n        );\n        self\n    }\n}\n\n/// A list of dependencies specified as either a list of Strings\n/// or as a path to a file that lists the dependencies, one per line.\n#[derive(Debug, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(untagged)]\n#[non_exhaustive]\npub enum Dependencies {\n    /// The list of dependencies provided directly as a vector of Strings.\n    List(Vec<String>),\n    /// A path to the file containing the list of dependences, formatted as one per line:\n    /// ```text\n    /// libc6\n    /// libxcursor1\n    /// libdbus-1-3\n    /// libasyncns0\n    /// ...\n    /// ```\n    Path(PathBuf),\n}\nimpl Dependencies {\n    /// Returns the dependencies as a list of Strings.\n    pub fn to_list(&self) -> crate::Result<Vec<String>> {\n        match self {\n            Self::List(v) => Ok(v.clone()),\n            Self::Path(path) => {\n                let trimmed_lines = fs::read_to_string(path)\n                    .map_err(|e| Error::IoWithPath(path.clone(), e))?\n                    .lines()\n                    .filter_map(|line| {\n                        let trimmed = line.trim();\n                        if !trimmed.is_empty() {\n                            Some(trimmed.to_owned())\n                        } else {\n                            None\n                        }\n                    })\n                    .collect();\n                Ok(trimmed_lines)\n            }\n        }\n    }\n}\n\n/// The Linux AppImage configuration.\n#[derive(Clone, Debug, Default, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\n#[non_exhaustive]\npub struct AppImageConfig {\n    /// List of libs that exist in `/usr/lib*` to be include in the final AppImage.\n    /// The libs will be searched for, using the command\n    /// `find -L /usr/lib* -name <libname>`\n    pub libs: Option<Vec<String>>,\n    /// List of binary paths to include in the final AppImage.\n    /// For example, if you want `xdg-open`, you'd specify `/usr/bin/xdg-open`\n    pub bins: Option<Vec<String>>,\n    /// List of custom files to add to the appimage package.\n    /// Maps a dir/file to a dir/file inside the appimage package.\n    pub files: Option<HashMap<String, String>>,\n    /// A map of [`linuxdeploy`](https://github.com/linuxdeploy/linuxdeploy)\n    /// plugin name and its URL to be downloaded and executed while packaing the appimage.\n    /// For example, if you want to use the\n    /// [`gtk`](https://raw.githubusercontent.com/linuxdeploy/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh) plugin,\n    /// you'd specify `gtk` as the key and its url as the value.\n    #[serde(alias = \"linuxdeploy-plugins\", alias = \"linuxdeploy_plugins\")]\n    pub linuxdeploy_plugins: Option<HashMap<String, String>>,\n    /// List of globs of libraries to exclude from the final AppImage.\n    /// For example, to exclude libnss3.so, you'd specify `libnss3*`\n    #[serde(alias = \"excluded-libraries\", alias = \"excluded_libraries\")]\n    pub excluded_libs: Option<Vec<String>>,\n}\n\nimpl AppImageConfig {\n    /// Creates a new [`DebianConfig`].\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    /// Set the list of libs that exist in `/usr/lib*` to be include in the final AppImage.\n    /// The libs will be searched for using, the command\n    /// `find -L /usr/lib* -name <libname>`\n    pub fn libs<I, S>(mut self, libs: I) -> Self\n    where\n        I: IntoIterator<Item = S>,\n        S: Into<String>,\n    {\n        self.libs\n            .replace(libs.into_iter().map(Into::into).collect());\n        self\n    }\n\n    /// Set the list of binary paths to include in the final AppImage.\n    /// For example, if you want `xdg-open`, you'd specify `/usr/bin/xdg-open`\n    pub fn bins<I, S>(mut self, bins: I) -> Self\n    where\n        I: IntoIterator<Item = S>,\n        S: Into<String>,\n    {\n        self.bins\n            .replace(bins.into_iter().map(Into::into).collect());\n        self\n    }\n\n    /// Set the list of custom files to add to the appimage package.\n    /// Maps a dir/file to a dir/file inside the appimage package.\n    pub fn files<I, S, T>(mut self, files: I) -> Self\n    where\n        I: IntoIterator<Item = (S, T)>,\n        S: Into<String>,\n        T: Into<String>,\n    {\n        self.files.replace(\n            files\n                .into_iter()\n                .map(|(k, v)| (k.into(), v.into()))\n                .collect(),\n        );\n        self\n    }\n\n    /// Set the map of [`linuxdeploy`](https://github.com/linuxdeploy/linuxdeploy)\n    /// plugin name and its URL to be downloaded and executed while packaing the appimage.\n    /// For example, if you want to use the\n    /// [`gtk`](https://raw.githubusercontent.com/linuxdeploy/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh) plugin,\n    /// you'd specify `gtk` as the key and its url as the value.\n    pub fn linuxdeploy_plugins<I, S, T>(mut self, linuxdeploy_plugins: I) -> Self\n    where\n        I: IntoIterator<Item = (S, T)>,\n        S: Into<String>,\n        T: Into<String>,\n    {\n        self.linuxdeploy_plugins.replace(\n            linuxdeploy_plugins\n                .into_iter()\n                .map(|(k, v)| (k.into(), v.into()))\n                .collect(),\n        );\n        self\n    }\n}\n\n/// The Linux pacman configuration.\n#[derive(Clone, Debug, Default, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\n#[non_exhaustive]\npub struct PacmanConfig {\n    /// List of custom files to add to the pacman package.\n    /// Maps a dir/file to a dir/file inside the pacman package.\n    pub files: Option<HashMap<String, String>>,\n    /// List of softwares that must be installed for the app to build and run.\n    ///\n    /// See : <https://wiki.archlinux.org/title/PKGBUILD#depends>\n    pub depends: Option<Dependencies>,\n    /// Additional packages that are provided by this app.\n    ///\n    /// See : <https://wiki.archlinux.org/title/PKGBUILD#provides>\n    pub provides: Option<Vec<String>>,\n    /// Packages that conflict or cause problems with the app.\n    /// All these packages and packages providing this item will need to be removed\n    ///\n    /// See : <https://wiki.archlinux.org/title/PKGBUILD#conflicts>\n    pub conflicts: Option<Vec<String>>,\n    /// Only use if this app replaces some obsolete packages.\n    /// For example, if you rename any package.\n    ///\n    /// See : <https://wiki.archlinux.org/title/PKGBUILD#replaces>\n    pub replaces: Option<Vec<String>>,\n    /// Source of the package to be stored at PKGBUILD.\n    /// PKGBUILD is a bash script, so version can be referred as ${pkgver}\n    pub source: Option<Vec<String>>,\n}\n\nimpl PacmanConfig {\n    /// Creates a new [`PacmanConfig`].\n    pub fn new() -> Self {\n        Self::default()\n    }\n    /// Set the list of custom files to add to the pacman package.\n    /// Maps a dir/file to a dir/file inside the pacman package.\n    pub fn files<I, S, T>(mut self, files: I) -> Self\n    where\n        I: IntoIterator<Item = (S, T)>,\n        S: Into<String>,\n        T: Into<String>,\n    {\n        self.files.replace(\n            files\n                .into_iter()\n                .map(|(k, v)| (k.into(), v.into()))\n                .collect(),\n        );\n        self\n    }\n\n    /// Set the list of pacman dependencies directly using an iterator of strings.\n    pub fn depends<I, S>(mut self, depends: I) -> Self\n    where\n        I: IntoIterator<Item = S>,\n        S: Into<String>,\n    {\n        self.depends.replace(Dependencies::List(\n            depends.into_iter().map(Into::into).collect(),\n        ));\n        self\n    }\n\n    /// Set the list of pacman dependencies indirectly via a path to a file,\n    /// which must contain one dependency (a package name) per line.\n    pub fn depends_path<P>(mut self, path: P) -> Self\n    where\n        P: Into<PathBuf>,\n    {\n        self.depends.replace(Dependencies::Path(path.into()));\n        self\n    }\n\n    /// Set the list of additional packages that are provided by this app.\n    pub fn provides<I, S>(mut self, provides: I) -> Self\n    where\n        I: IntoIterator<Item = S>,\n        S: Into<String>,\n    {\n        self.provides\n            .replace(provides.into_iter().map(Into::into).collect());\n        self\n    }\n    /// Set the list of packages that conflict with the app.\n    pub fn conflicts<I, S>(mut self, conflicts: I) -> Self\n    where\n        I: IntoIterator<Item = S>,\n        S: Into<String>,\n    {\n        self.conflicts\n            .replace(conflicts.into_iter().map(Into::into).collect());\n        self\n    }\n    /// Set the list of obsolete packages that are replaced by this package.\n    pub fn replaces<I, S>(mut self, replaces: I) -> Self\n    where\n        I: IntoIterator<Item = S>,\n        S: Into<String>,\n    {\n        self.replaces\n            .replace(replaces.into_iter().map(Into::into).collect());\n        self\n    }\n    /// Set the list of sources where the package will be stored.\n    pub fn source<I, S>(mut self, source: I) -> Self\n    where\n        I: IntoIterator<Item = S>,\n        S: Into<String>,\n    {\n        self.source\n            .replace(source.into_iter().map(Into::into).collect());\n        self\n    }\n}\n\n/// Position coordinates struct.\n#[derive(Default, Copy, Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct Position {\n    /// X coordinate.\n    pub x: u32,\n    /// Y coordinate.\n    pub y: u32,\n}\n\n/// Size struct.\n#[derive(Default, Copy, Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct Size {\n    /// Width.\n    pub width: u32,\n    /// Height.\n    pub height: u32,\n}\n\n/// The Apple Disk Image (.dmg) configuration.\n#[derive(Clone, Debug, Default, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\n#[non_exhaustive]\npub struct DmgConfig {\n    /// Image to use as the background in dmg file. Accepted formats: `png`/`jpg`/`gif`.\n    pub background: Option<PathBuf>,\n    /// Position of volume window on screen.\n    pub window_position: Option<Position>,\n    /// Size of volume window.\n    #[serde(alias = \"window-size\", alias = \"window_size\")]\n    pub window_size: Option<Size>,\n    /// Position of application file on window.\n    #[serde(alias = \"app-position\", alias = \"app_position\")]\n    pub app_position: Option<Position>,\n    /// Position of application folder on window.\n    #[serde(\n        alias = \"application-folder-position\",\n        alias = \"application_folder_position\"\n    )]\n    pub app_folder_position: Option<Position>,\n}\n\nimpl DmgConfig {\n    /// Creates a new [`DmgConfig`].\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    /// Set an image to use as the background in dmg file. Accepted formats: `png`/`jpg`/`gif`.\n    pub fn background<P: Into<PathBuf>>(mut self, path: P) -> Self {\n        self.background.replace(path.into());\n        self\n    }\n\n    /// Set the poosition of volume window on screen.\n    pub fn window_position(mut self, position: Position) -> Self {\n        self.window_position.replace(position);\n        self\n    }\n\n    /// Set the size of volume window.\n    pub fn window_size(mut self, size: Size) -> Self {\n        self.window_size.replace(size);\n        self\n    }\n\n    /// Set the poosition of app file on window.\n    pub fn app_position(mut self, position: Position) -> Self {\n        self.app_position.replace(position);\n        self\n    }\n\n    /// Set the position of application folder on window.\n    pub fn app_folder_position(mut self, position: Position) -> Self {\n        self.app_folder_position.replace(position);\n        self\n    }\n}\n\n/// Notarization authentication credentials.\n#[derive(Clone, Debug)]\npub enum MacOsNotarizationCredentials {\n    /// Apple ID authentication.\n    AppleId {\n        /// Apple ID.\n        apple_id: OsString,\n        /// Password.\n        password: OsString,\n        /// Team ID.\n        team_id: OsString,\n    },\n    /// App Store Connect API key.\n    ApiKey {\n        /// API key issuer.\n        issuer: OsString,\n        /// API key ID.\n        key_id: OsString,\n        /// Path to the API key file.\n        key_path: PathBuf,\n    },\n    /// Keychain profile with a stored app-specific password for notarytool to use\n    /// Passwords can be generated at https://account.apple.com when signed in with your developer account.\n    /// The password must then be stored in your keychain for notarytool to access,\n    /// using the following, with the appopriate Apple and Team IDs:\n    /// `xcrun notarytool store-credentials --apple-id \"name@example.com\" --team-id \"ABCD123456\"`\n    /// This will prompt for a keychain profile name, and the password itself.\n    /// This setting can only be provided as an environment variable \"APPLE_KEYCHAIN_PROFILE\"\n    KeychainProfile {\n        /// The keychain profile name (as provided when the password was stored using notarytool)\n        keychain_profile: OsString,\n    },\n}\n\n/// The macOS configuration.\n#[derive(Clone, Debug, Default, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\n#[non_exhaustive]\npub struct MacOsConfig {\n    /// MacOS frameworks that need to be packaged with the app.\n    ///\n    /// Each string can either be the name of a framework (without the `.framework` extension, e.g. `\"SDL2\"`),\n    /// in which case we will search for that framework in the standard install locations (`~/Library/Frameworks/`, `/Library/Frameworks/`, and `/Network/Library/Frameworks/`),\n    /// or a path to a specific framework bundle (e.g. `./data/frameworks/SDL2.framework`).  Note that this setting just makes cargo-packager copy the specified frameworks into the OS X app bundle\n    /// (under `Foobar.app/Contents/Frameworks/`); you are still responsible for:\n    ///\n    /// - arranging for the compiled binary to link against those frameworks (e.g. by emitting lines like `cargo:rustc-link-lib=framework=SDL2` from your `build.rs` script)\n    ///\n    /// - embedding the correct rpath in your binary (e.g. by running `install_name_tool -add_rpath \"@executable_path/../Frameworks\" path/to/binary` after compiling)\n    pub frameworks: Option<Vec<String>>,\n    /// A version string indicating the minimum MacOS version that the packaged app supports (e.g. `\"10.11\"`).\n    /// If you are using this config field, you may also want have your `build.rs` script emit `cargo:rustc-env=MACOSX_DEPLOYMENT_TARGET=10.11`.\n    #[serde(alias = \"minimum-system-version\", alias = \"minimum_system_version\")]\n    pub minimum_system_version: Option<String>,\n    /// The exception domain to use on the macOS .app package.\n    ///\n    /// This allows communication to the outside world e.g. a web server you're shipping.\n    #[serde(alias = \"exception-domain\", alias = \"exception_domain\")]\n    pub exception_domain: Option<String>,\n    /// Code signing identity.\n    ///\n    /// This is typically of the form: `\"Developer ID Application: TEAM_NAME (TEAM_ID)\"`.\n    #[serde(alias = \"signing-identity\", alias = \"signing_identity\")]\n    pub signing_identity: Option<String>,\n    /// Codesign certificate (base64 encoded of the p12 file).\n    ///\n    /// Note: this field cannot be specified via a config file or Cargo package metadata.\n    #[serde(skip)]\n    pub signing_certificate: Option<OsString>,\n    /// Password of the codesign certificate.\n    ///\n    /// Note: this field cannot be specified via a config file or Cargo package metadata.\n    #[serde(skip)]\n    pub signing_certificate_password: Option<OsString>,\n    /// Notarization authentication credentials.\n    ///\n    /// Note: this field cannot be specified via a config file or Cargo package metadata.\n    #[serde(skip)]\n    pub notarization_credentials: Option<MacOsNotarizationCredentials>,\n    /// Provider short name for notarization.\n    #[serde(alias = \"provider-short-name\", alias = \"provider_short_name\")]\n    pub provider_short_name: Option<String>,\n    /// Path to the entitlements.plist file.\n    pub entitlements: Option<String>,\n    /// Path to the Info.plist file for the package.\n    #[serde(alias = \"info-plist-path\", alias = \"info_plist_path\")]\n    pub info_plist_path: Option<PathBuf>,\n    /// Path to the embedded.provisionprofile file for the package.\n    #[serde(\n        alias = \"embedded-provisionprofile-path\",\n        alias = \"embedded_provisionprofile_path\"\n    )]\n    pub embedded_provisionprofile_path: Option<PathBuf>,\n    /// Apps that need to be packaged within the app.\n    #[serde(alias = \"embedded-apps\", alias = \"embedded_apps\")]\n    pub embedded_apps: Option<Vec<String>>,\n    /// Whether this is a background application. If true, the app will not appear in the Dock.\n    ///\n    /// Sets the `LSUIElement` flag in the macOS plist file.\n    #[serde(default, alias = \"background_app\", alias = \"background-app\")]\n    pub background_app: bool,\n}\n\nimpl MacOsConfig {\n    /// Creates a new [`MacOsConfig`].\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    /// MacOS frameworks that need to be packaged with the app.\n    ///\n    /// Each string can either be the name of a framework (without the `.framework` extension, e.g. `\"SDL2\"`),\n    /// in which case we will search for that framework in the standard install locations (`~/Library/Frameworks/`, `/Library/Frameworks/`, and `/Network/Library/Frameworks/`),\n    /// or a path to a specific framework bundle (e.g. `./data/frameworks/SDL2.framework`).  Note that this setting just makes cargo-packager copy the specified frameworks into the OS X app bundle\n    /// (under `Foobar.app/Contents/Frameworks/`); you are still responsible for:\n    ///\n    /// - arranging for the compiled binary to link against those frameworks (e.g. by emitting lines like `cargo:rustc-link-lib=framework=SDL2` from your `build.rs` script)\n    ///\n    /// - embedding the correct rpath in your binary (e.g. by running `install_name_tool -add_rpath \"@executable_path/../Frameworks\" path/to/binary` after compiling)\n    pub fn frameworks<I, S>(mut self, frameworks: I) -> Self\n    where\n        I: IntoIterator<Item = S>,\n        S: Into<String>,\n    {\n        self.frameworks\n            .replace(frameworks.into_iter().map(Into::into).collect());\n        self\n    }\n\n    /// A version string indicating the minimum MacOS version that the packaged app supports (e.g. `\"10.11\"`).\n    /// If you are using this config field, you may also want have your `build.rs` script emit `cargo:rustc-env=MACOSX_DEPLOYMENT_TARGET=10.11`.\n    pub fn minimum_system_version<S: Into<String>>(mut self, minimum_system_version: S) -> Self {\n        self.minimum_system_version\n            .replace(minimum_system_version.into());\n        self\n    }\n\n    /// The exception domain to use on the macOS .app package.\n    ///\n    /// This allows communication to the outside world e.g. a web server you're shipping.\n    pub fn exception_domain<S: Into<String>>(mut self, exception_domain: S) -> Self {\n        self.exception_domain.replace(exception_domain.into());\n        self\n    }\n\n    /// Code signing identity.\n    pub fn signing_identity<S: Into<String>>(mut self, signing_identity: S) -> Self {\n        self.signing_identity.replace(signing_identity.into());\n        self\n    }\n\n    /// Provider short name for notarization.\n    pub fn provider_short_name<S: Into<String>>(mut self, provider_short_name: S) -> Self {\n        self.provider_short_name.replace(provider_short_name.into());\n        self\n    }\n\n    /// Path to the entitlements.plist file.\n    pub fn entitlements<S: Into<String>>(mut self, entitlements: S) -> Self {\n        self.entitlements.replace(entitlements.into());\n        self\n    }\n\n    /// Path to the Info.plist file for the package.\n    pub fn info_plist_path<S: Into<PathBuf>>(mut self, info_plist_path: S) -> Self {\n        self.info_plist_path.replace(info_plist_path.into());\n        self\n    }\n\n    /// Path to the embedded.provisionprofile file for the package.\n    pub fn embedded_provisionprofile_path<S: Into<PathBuf>>(\n        mut self,\n        embedded_provisionprofile_path: S,\n    ) -> Self {\n        self.embedded_provisionprofile_path\n            .replace(embedded_provisionprofile_path.into());\n        self\n    }\n\n    /// Apps that need to be packaged within the app.\n    pub fn embedded_apps<I, S>(mut self, embedded_apps: I) -> Self\n    where\n        I: IntoIterator<Item = S>,\n        S: Into<String>,\n    {\n        self.embedded_apps\n            .replace(embedded_apps.into_iter().map(Into::into).collect());\n        self\n    }\n}\n\n/// Linux configuration\n#[derive(Clone, Debug, Default, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\n#[non_exhaustive]\npub struct LinuxConfig {\n    /// Flag to indicate if desktop entry should be generated.\n    #[serde(\n        default = \"default_true\",\n        alias = \"generate-desktop-entry\",\n        alias = \"generate_desktop_entry\"\n    )]\n    pub generate_desktop_entry: bool,\n}\n\n/// A wix language.\n#[derive(Debug, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(untagged)]\n#[non_exhaustive]\npub enum WixLanguage {\n    /// Built-in wix language identifier.\n    Identifier(String),\n    /// Custom wix language.\n    Custom {\n        /// Idenitifier of this language, for example `en-US`\n        identifier: String,\n        /// The path to a locale (`.wxl`) file. See <https://wixtoolset.org/documentation/manual/v3/howtos/ui_and_localization/build_a_localized_version.html>.\n        path: Option<PathBuf>,\n    },\n}\n\nimpl Default for WixLanguage {\n    fn default() -> Self {\n        Self::Identifier(\"en-US\".into())\n    }\n}\n\n/// The wix format configuration\n#[derive(Clone, Debug, Default, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\n#[non_exhaustive]\npub struct WixConfig {\n    /// The app languages to build. See <https://docs.microsoft.com/en-us/windows/win32/msi/localizing-the-error-and-actiontext-tables>.\n    pub languages: Option<Vec<WixLanguage>>,\n    /// By default, the packager uses an internal template.\n    /// This option allows you to define your own wix file.\n    pub template: Option<PathBuf>,\n    /// List of merge modules to include in your installer.\n    /// For example, if you want to include [C++ Redis merge modules]\n    ///\n    /// [C++ Redis merge modules]: https://wixtoolset.org/docs/v3/howtos/redistributables_and_install_checks/install_vcredist/\n    #[serde(alias = \"merge-modules\", alias = \"merge_modules\")]\n    pub merge_modules: Option<Vec<PathBuf>>,\n    /// A list of paths to .wxs files with WiX fragments to use.\n    #[serde(alias = \"fragment-paths\", alias = \"fragment_paths\")]\n    pub fragment_paths: Option<Vec<PathBuf>>,\n    /// List of WiX fragments as strings. This is similar to `config.wix.fragments_paths` but\n    /// is a string so you can define it inline in your config.\n    ///\n    /// ```text\n    /// <?xml version=\"1.0\" encoding=\"utf-8\"?>\n    /// <Wix xmlns=\"http://schemas.microsoft.com/wix/2006/wi\">\n    /// <Fragment>\n    ///     <CustomAction Id=\"OpenNotepad\" Directory=\"INSTALLDIR\" Execute=\"immediate\" ExeCommand=\"cmd.exe /c notepad.exe\" Return=\"check\" />\n    ///     <InstallExecuteSequence>\n    ///         <Custom Action=\"OpenNotepad\" After=\"InstallInitialize\" />\n    ///     </InstallExecuteSequence>\n    /// </Fragment>\n    /// </Wix>\n    /// ```\n    pub fragments: Option<Vec<String>>,\n    /// The ComponentGroup element ids you want to reference from the fragments.\n    #[serde(alias = \"component-group-refs\", alias = \"component_group_refs\")]\n    pub component_group_refs: Option<Vec<String>>,\n    /// The Component element ids you want to reference from the fragments.\n    #[serde(alias = \"component-refs\", alias = \"component_refs\")]\n    pub component_refs: Option<Vec<String>>,\n    /// The CustomAction element ids you want to reference from the fragments.\n    #[serde(alias = \"custom-action-refs\", alias = \"custom_action_refs\")]\n    pub custom_action_refs: Option<Vec<String>>,\n    /// The FeatureGroup element ids you want to reference from the fragments.\n    #[serde(alias = \"feature-group-refs\", alias = \"feature_group_refs\")]\n    pub feature_group_refs: Option<Vec<String>>,\n    /// The Feature element ids you want to reference from the fragments.\n    #[serde(alias = \"feature-refs\", alias = \"feature_refs\")]\n    pub feature_refs: Option<Vec<String>>,\n    /// The Merge element ids you want to reference from the fragments.\n    #[serde(alias = \"merge-refs\", alias = \"merge_refs\")]\n    pub merge_refs: Option<Vec<String>>,\n    /// Path to a bitmap file to use as the installation user interface banner.\n    /// This bitmap will appear at the top of all but the first page of the installer.\n    ///\n    /// The required dimensions are 493px × 58px.\n    #[serde(alias = \"banner-path\", alias = \"banner_path\")]\n    pub banner_path: Option<PathBuf>,\n    /// Path to a bitmap file to use on the installation user interface dialogs.\n    /// It is used on the welcome and completion dialogs.\n    /// The required dimensions are 493px × 312px.\n    #[serde(alias = \"dialog-image-path\", alias = \"dialog_image_path\")]\n    pub dialog_image_path: Option<PathBuf>,\n    /// Enables FIPS compliant algorithms.\n    #[serde(default, alias = \"fips-compliant\", alias = \"fips_compliant\")]\n    pub fips_compliant: bool,\n}\n\nimpl WixConfig {\n    /// Creates a new [`WixConfig`].\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    /// Set the app languages to build. See <https://docs.microsoft.com/en-us/windows/win32/msi/localizing-the-error-and-actiontext-tables>.\n    pub fn languages<I: IntoIterator<Item = WixLanguage>>(mut self, languages: I) -> Self {\n        self.languages.replace(languages.into_iter().collect());\n        self\n    }\n\n    /// By default, the packager uses an internal template.\n    /// This option allows you to define your own wix file.\n    pub fn template<P: Into<PathBuf>>(mut self, template: P) -> Self {\n        self.template.replace(template.into());\n        self\n    }\n\n    /// Set a list of merge modules to include in your installer.\n    /// For example, if you want to include [C++ Redis merge modules]\n    ///\n    /// [C++ Redis merge modules]: https://wixtoolset.org/docs/v3/howtos/redistributables_and_install_checks/install_vcredist/\n    pub fn merge_modules<I, P>(mut self, merge_modules: I) -> Self\n    where\n        I: IntoIterator<Item = P>,\n        P: Into<PathBuf>,\n    {\n        self.merge_modules\n            .replace(merge_modules.into_iter().map(Into::into).collect());\n        self\n    }\n\n    /// Set a list of paths to .wxs files with WiX fragments to use.\n    pub fn fragment_paths<I, S>(mut self, fragment_paths: I) -> Self\n    where\n        I: IntoIterator<Item = S>,\n        S: Into<PathBuf>,\n    {\n        self.fragment_paths\n            .replace(fragment_paths.into_iter().map(Into::into).collect());\n        self\n    }\n\n    /// Set a list of WiX fragments as strings. This is similar to [`WixConfig::fragment_paths`] but\n    /// is a string so you can define it inline in your config.\n    ///\n    /// ```text\n    /// <?xml version=\"1.0\" encoding=\"utf-8\"?>\n    /// <Wix xmlns=\"http://schemas.microsoft.com/wix/2006/wi\">\n    /// <Fragment>\n    ///     <CustomAction Id=\"OpenNotepad\" Directory=\"INSTALLDIR\" Execute=\"immediate\" ExeCommand=\"cmd.exe /c notepad.exe\" Return=\"check\" />\n    ///     <InstallExecuteSequence>\n    ///         <Custom Action=\"OpenNotepad\" After=\"InstallInitialize\" />\n    ///     </InstallExecuteSequence>\n    /// </Fragment>\n    /// </Wix>\n    /// ```\n    pub fn fragments<I, S>(mut self, fragments: I) -> Self\n    where\n        I: IntoIterator<Item = S>,\n        S: Into<String>,\n    {\n        self.fragments\n            .replace(fragments.into_iter().map(Into::into).collect());\n        self\n    }\n\n    /// Set the ComponentGroup element ids you want to reference from the fragments.\n    pub fn component_group_refs<I, S>(mut self, component_group_refs: I) -> Self\n    where\n        I: IntoIterator<Item = S>,\n        S: Into<String>,\n    {\n        self.component_group_refs\n            .replace(component_group_refs.into_iter().map(Into::into).collect());\n        self\n    }\n\n    /// Set the Component element ids you want to reference from the fragments.\n    pub fn component_refs<I, S>(mut self, component_refs: I) -> Self\n    where\n        I: IntoIterator<Item = S>,\n        S: Into<String>,\n    {\n        self.component_refs\n            .replace(component_refs.into_iter().map(Into::into).collect());\n        self\n    }\n\n    /// Set the CustomAction element ids you want to reference from the fragments.\n    pub fn custom_action_refs<I, S>(mut self, custom_action_refs: I) -> Self\n    where\n        I: IntoIterator<Item = S>,\n        S: Into<String>,\n    {\n        self.custom_action_refs\n            .replace(custom_action_refs.into_iter().map(Into::into).collect());\n        self\n    }\n\n    /// Set he FeatureGroup element ids you want to reference from the fragments.\n    pub fn feature_group_refs<I, S>(mut self, feature_group_refs: I) -> Self\n    where\n        I: IntoIterator<Item = S>,\n        S: Into<String>,\n    {\n        self.feature_group_refs\n            .replace(feature_group_refs.into_iter().map(Into::into).collect());\n        self\n    }\n\n    /// Set the Feature element ids you want to reference from the fragments.\n    pub fn feature_refs<I, S>(mut self, feature_refs: I) -> Self\n    where\n        I: IntoIterator<Item = S>,\n        S: Into<String>,\n    {\n        self.feature_refs\n            .replace(feature_refs.into_iter().map(Into::into).collect());\n        self\n    }\n\n    /// Set he Merge element ids you want to reference from the fragments.\n    pub fn merge_refs<I, S>(mut self, merge_refs: I) -> Self\n    where\n        I: IntoIterator<Item = S>,\n        S: Into<String>,\n    {\n        self.merge_refs\n            .replace(merge_refs.into_iter().map(Into::into).collect());\n        self\n    }\n\n    /// Set the path to a bitmap file to use as the installation user interface banner.\n    /// This bitmap will appear at the top of all but the first page of the installer.\n    ///\n    /// The required dimensions are 493px × 58px.\n    pub fn banner_path<P: Into<PathBuf>>(mut self, path: P) -> Self {\n        self.banner_path.replace(path.into());\n        self\n    }\n\n    /// Set the path to a bitmap file to use on the installation user interface dialogs.\n    /// It is used on the welcome and completion dialogs.\n    /// The required dimensions are 493px × 312px.\n    pub fn dialog_image_path<P: Into<PathBuf>>(mut self, path: P) -> Self {\n        self.dialog_image_path.replace(path.into());\n        self\n    }\n\n    /// Set whether to enable or disable FIPS compliant algorithms.\n    pub fn fips_compliant(mut self, fips_compliant: bool) -> Self {\n        self.fips_compliant = fips_compliant;\n        self\n    }\n}\n\n/// Install Modes for the NSIS installer.\n#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\n#[non_exhaustive]\n#[derive(Default)]\npub enum NSISInstallerMode {\n    /// Default mode for the installer.\n    ///\n    /// Install the app by default in a directory that doesn't require Administrator access.\n    ///\n    /// Installer metadata will be saved under the `HKCU` registry path.\n    #[default]\n    CurrentUser,\n    /// Install the app by default in the `Program Files` folder directory requires Administrator\n    /// access for the installation.\n    ///\n    /// Installer metadata will be saved under the `HKLM` registry path.\n    PerMachine,\n    /// Combines both modes and allows the user to choose at install time\n    /// whether to install for the current user or per machine. Note that this mode\n    /// will require Administrator access even if the user wants to install it for the current user only.\n    ///\n    /// Installer metadata will be saved under the `HKLM` or `HKCU` registry path based on the user's choice.\n    Both,\n}\n\n/// Compression algorithms used in the NSIS installer.\n///\n/// See <https://nsis.sourceforge.io/Reference/SetCompressor>\n#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\")]\n#[non_exhaustive]\npub enum NsisCompression {\n    /// ZLIB uses the deflate algorithm, it is a quick and simple method. With the default compression level it uses about 300 KB of memory.\n    Zlib,\n    /// BZIP2 usually gives better compression ratios than ZLIB, but it is a bit slower and uses more memory. With the default compression level it uses about 4 MB of memory.\n    Bzip2,\n    /// LZMA (default) is a new compression method that gives very good compression ratios. The decompression speed is high (10-20 MB/s on a 2 GHz CPU), the compression speed is lower. The memory size that will be used for decompression is the dictionary size plus a few KBs, the default is 8 MB.\n    Lzma,\n    /// Disable compression.\n    Off,\n}\n\n/// The NSIS format configuration.\n#[derive(Clone, Debug, Default, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\n#[non_exhaustive]\npub struct NsisConfig {\n    /// Set the compression algorithm used to compress files in the installer.\n    ///\n    /// See <https://nsis.sourceforge.io/Reference/SetCompressor>\n    pub compression: Option<NsisCompression>,\n    /// A custom `.nsi` template to use.\n    ///\n    /// See the default template here\n    /// <https://github.com/crabnebula-dev/cargo-packager/blob/main/crates/packager/src/package/nsis/installer.nsi>\n    pub template: Option<PathBuf>,\n    /// Logic of an NSIS section that will be ran before the install section.\n    ///\n    /// See the available libraries, dlls and global variables here\n    /// <https://github.com/crabnebula-dev/cargo-packager/blob/main/crates/packager/src/package/nsis/installer.nsi>\n    ///\n    /// ### Example\n    /// ```toml\n    /// [package.metadata.packager.nsis]\n    /// preinstall-section = \"\"\"\n    ///     ; Setup custom messages\n    ///     LangString webview2AbortError ${LANG_ENGLISH} \"Failed to install WebView2! The app can't run without it. Try restarting the installer.\"\n    ///     LangString webview2DownloadError ${LANG_ARABIC} \"خطأ: فشل تنزيل WebView2 - $0\"\n    ///\n    ///     Section PreInstall\n    ///      ; <section logic here>\n    ///     SectionEnd\n    ///\n    ///     Section AnotherPreInstall\n    ///      ; <section logic here>\n    ///     SectionEnd\n    /// \"\"\"\n    /// ```\n    #[serde(alias = \"preinstall-section\", alias = \"preinstall_section\")]\n    pub preinstall_section: Option<String>,\n    /// The path to a bitmap file to display on the header of installers pages.\n    ///\n    /// The recommended dimensions are 150px x 57px.\n    #[serde(alias = \"header-image\", alias = \"header_image\")]\n    pub header_image: Option<PathBuf>,\n    /// The path to a bitmap file for the Welcome page and the Finish page.\n    ///\n    /// The recommended dimensions are 164px x 314px.\n    #[serde(alias = \"sidebar-image\", alias = \"sidebar_image\")]\n    pub sidebar_image: Option<PathBuf>,\n    /// The path to an icon file used as the installer icon.\n    #[serde(alias = \"installer-icon\", alias = \"installer_icon\")]\n    pub installer_icon: Option<PathBuf>,\n    /// Whether the installation will be for all users or just the current user.\n    #[serde(default, alias = \"installer-mode\", alias = \"installer_mode\")]\n    pub install_mode: NSISInstallerMode,\n    /// A list of installer languages.\n    /// By default the OS language is used. If the OS language is not in the list of languages, the first language will be used.\n    /// To allow the user to select the language, set `display_language_selector` to `true`.\n    ///\n    /// See <https://github.com/kichik/nsis/tree/9465c08046f00ccb6eda985abbdbf52c275c6c4d/Contrib/Language%20files> for the complete list of languages.\n    pub languages: Option<Vec<String>>,\n    /// An key-value pair where the key is the language and the\n    /// value is the path to a custom `.nsi` file that holds the translated text for cargo-packager's custom messages.\n    ///\n    /// See <https://github.com/crabnebula-dev/cargo-packager/blob/main/crates/packager/src/nsis/languages/English.nsh> for an example `.nsi` file.\n    ///\n    /// **Note**: the key must be a valid NSIS language and it must be added to [`NsisConfig`]languages array,\n    #[serde(alias = \"custom-language-file\", alias = \"custom_language_file\")]\n    pub custom_language_files: Option<HashMap<String, PathBuf>>,\n    /// Whether to display a language selector dialog before the installer and uninstaller windows are rendered or not.\n    /// By default the OS language is selected, with a fallback to the first language in the `languages` array.\n    #[serde(\n        default,\n        alias = \"display-language-selector\",\n        alias = \"display_language_selector\"\n    )]\n    pub display_language_selector: bool,\n    /// List of paths where your app stores data.\n    /// This options tells the uninstaller to provide the user with an option\n    /// (disabled by default) whether they want to rmeove your app data or keep it.\n    ///\n    /// The path should use a constant from <https://nsis.sourceforge.io/Docs/Chapter4.html#varconstant>\n    /// in addition to `$IDENTIFIER`, `$PUBLISHER` and `$PRODUCTNAME`, for example, if you store your\n    /// app data in `C:\\\\Users\\\\<user>\\\\AppData\\\\Local\\\\<your-company-name>\\\\<your-product-name>`\n    /// you'd need to specify\n    /// ```toml\n    /// [package.metadata.packager.nsis]\n    /// appdata-paths = [\"$LOCALAPPDATA/$PUBLISHER/$PRODUCTNAME\"]\n    /// ```\n    #[serde(default, alias = \"appdata-paths\", alias = \"appdata_paths\")]\n    pub appdata_paths: Option<Vec<String>>,\n}\n\nimpl NsisConfig {\n    /// Creates a new [`NsisConfig`].\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    /// Set the compression algorithm used to compress files in the installer.\n    ///\n    /// See <https://nsis.sourceforge.io/Reference/SetCompressor>\n    pub fn compression(mut self, compression: NsisCompression) -> Self {\n        self.compression.replace(compression);\n        self\n    }\n\n    /// Set a custom `.nsi` template to use.\n    ///\n    /// See the default template here\n    /// <https://github.com/crabnebula-dev/cargo-packager/blob/main/crates/packager/src/package/nsis/installer.nsi>\n    pub fn template<P: Into<PathBuf>>(mut self, template: P) -> Self {\n        self.template.replace(template.into());\n        self\n    }\n\n    /// Set the logic of an NSIS section that will be ran before the install section.\n    ///\n    /// See the available libraries, dlls and global variables here\n    /// <https://github.com/crabnebula-dev/cargo-packager/blob/main/crates/packager/src/package/nsis/installer.nsi>\n    ///\n    /// ### Example\n    /// ```toml\n    /// [package.metadata.packager.nsis]\n    /// preinstall-section = \"\"\"\n    ///     ; Setup custom messages\n    ///     LangString webview2AbortError ${LANG_ENGLISH} \"Failed to install WebView2! The app can't run without it. Try restarting the installer.\"\n    ///     LangString webview2DownloadError ${LANG_ARABIC} \"خطأ: فشل تنزيل WebView2 - $0\"\n    ///\n    ///     Section PreInstall\n    ///      ; <section logic here>\n    ///     SectionEnd\n    ///\n    ///     Section AnotherPreInstall\n    ///      ; <section logic here>\n    ///     SectionEnd\n    /// \"\"\"\n    /// ```\n    pub fn preinstall_section<S: Into<String>>(mut self, preinstall_section: S) -> Self {\n        self.preinstall_section.replace(preinstall_section.into());\n        self\n    }\n\n    /// Set the path to a bitmap file to display on the header of installers pages.\n    ///\n    /// The recommended dimensions are 150px x 57px.\n    pub fn header_image<P: Into<PathBuf>>(mut self, header_image: P) -> Self {\n        self.header_image.replace(header_image.into());\n        self\n    }\n\n    /// Set the path to a bitmap file for the Welcome page and the Finish page.\n    ///\n    /// The recommended dimensions are 164px x 314px.\n    pub fn sidebar_image<P: Into<PathBuf>>(mut self, sidebar_image: P) -> Self {\n        self.sidebar_image.replace(sidebar_image.into());\n        self\n    }\n\n    /// Set the path to an icon file used as the installer icon.\n    pub fn installer_icon<P: Into<PathBuf>>(mut self, installer_icon: P) -> Self {\n        self.installer_icon.replace(installer_icon.into());\n        self\n    }\n\n    /// Set whether the installation will be for all users or just the current user.\n    pub fn install_mode(mut self, install_mode: NSISInstallerMode) -> Self {\n        self.install_mode = install_mode;\n        self\n    }\n\n    /// Set a list of installer languages.\n    /// By default the OS language is used. If the OS language is not in the list of languages, the first language will be used.\n    /// To allow the user to select the language, set `display_language_selector` to `true`.\n    ///\n    /// See <https://github.com/kichik/nsis/tree/9465c08046f00ccb6eda985abbdbf52c275c6c4d/Contrib/Language%20files> for the complete list of languages.\n    pub fn languages<I, S>(mut self, languages: I) -> Self\n    where\n        I: IntoIterator<Item = S>,\n        S: Into<String>,\n    {\n        self.languages\n            .replace(languages.into_iter().map(Into::into).collect());\n        self\n    }\n\n    /// Set a map of key-value pair where the key is the language and the\n    /// value is the path to a custom `.nsi` file that holds the translated text for cargo-packager's custom messages.\n    ///\n    /// See <https://github.com/crabnebula-dev/cargo-packager/blob/main/crates/packager/src/nsis/languages/English.nsh> for an example `.nsi` file.\n    ///\n    /// **Note**: the key must be a valid NSIS language and it must be added to [`NsisConfig`]languages array,\n    pub fn custom_language_files<I, S, P>(mut self, custom_language_files: I) -> Self\n    where\n        I: IntoIterator<Item = (S, P)>,\n        S: Into<String>,\n        P: Into<PathBuf>,\n    {\n        self.custom_language_files.replace(\n            custom_language_files\n                .into_iter()\n                .map(|(k, v)| (k.into(), v.into()))\n                .collect(),\n        );\n        self\n    }\n\n    /// Set wether to display a language selector dialog before the installer and uninstaller windows are rendered or not.\n    /// By default the OS language is selected, with a fallback to the first language in the `languages` array.\n    pub fn display_language_selector(mut self, display: bool) -> Self {\n        self.display_language_selector = display;\n        self\n    }\n\n    /// Set a list of paths where your app stores data.\n    /// This options tells the uninstaller to provide the user with an option\n    /// (disabled by default) whether they want to rmeove your app data or keep it.\n    ///\n    /// The path should use a constant from <https://nsis.sourceforge.io/Docs/Chapter4.html#varconstant>\n    /// in addition to `$IDENTIFIER`, `$PUBLISHER` and `$PRODUCTNAME`, for example, if you store your\n    /// app data in `C:\\\\Users\\\\<user>\\\\AppData\\\\Local\\\\<your-company-name>\\\\<your-product-name>`\n    /// you'd need to specify\n    /// ```toml\n    /// [package.metadata.packager.nsis]\n    /// appdata-paths = [\"$LOCALAPPDATA/$PUBLISHER/$PRODUCTNAME\"]\n    /// ```\n    pub fn appdata_paths<I, S>(mut self, appdata_paths: I) -> Self\n    where\n        I: IntoIterator<Item = S>,\n        S: Into<String>,\n    {\n        self.appdata_paths\n            .replace(appdata_paths.into_iter().map(Into::into).collect());\n        self\n    }\n}\n\n/// The Windows configuration.\n#[derive(Clone, Debug, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\n#[non_exhaustive]\npub struct WindowsConfig {\n    /// The file digest algorithm to use for creating file signatures. Required for code signing. SHA-256 is recommended.\n    #[serde(alias = \"digest-algorithm\", alias = \"digest_algorithm\")]\n    pub digest_algorithm: Option<String>,\n    /// The SHA1 hash of the signing certificate.\n    #[serde(alias = \"certificate-thumbprint\", alias = \"certificate_thumbprint\")]\n    pub certificate_thumbprint: Option<String>,\n    /// Whether to use Time-Stamp Protocol (TSP, a.k.a. RFC 3161) for the timestamp server. Your code signing provider may\n    /// use a TSP timestamp server, like e.g. SSL.com does. If so, enable TSP by setting to true.\n    #[serde(default)]\n    pub tsp: bool,\n    /// Server to use during timestamping.\n    #[serde(alias = \"timestamp-url\", alias = \"timestamp_url\")]\n    pub timestamp_url: Option<String>,\n    /// Whether to validate a second app installation, blocking the user from installing an older version if set to `false`.\n    ///\n    /// For instance, if `1.2.1` is installed, the user won't be able to install app version `1.2.0` or `1.1.5`.\n    ///\n    /// The default value of this flag is `true`.\n    #[serde(\n        default = \"default_true\",\n        alias = \"allow-downgrades\",\n        alias = \"allow_downgrades\"\n    )]\n    pub allow_downgrades: bool,\n\n    /// Specify a custom command to sign the binaries.\n    /// This command needs to have a `%1` in it which is just a placeholder for the binary path,\n    /// which we will detect and replace before calling the command.\n    ///\n    /// By Default we use `signtool.exe` which can be found only on Windows so\n    /// if you are on another platform and want to cross-compile and sign you will\n    /// need to use another tool like `osslsigncode`.\n    #[serde(alias = \"sign-command\", alias = \"sign_command\")]\n    pub sign_command: Option<String>,\n}\n\nimpl Default for WindowsConfig {\n    fn default() -> Self {\n        Self {\n            digest_algorithm: None,\n            certificate_thumbprint: None,\n            timestamp_url: None,\n            tsp: false,\n            allow_downgrades: true,\n            sign_command: None,\n        }\n    }\n}\n\nimpl WindowsConfig {\n    /// Creates a new [`WindowsConfig`].\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    /// Set the file digest algorithm to use for creating file signatures. Required for code signing. SHA-256 is recommended.\n    pub fn digest_algorithm<S: Into<String>>(mut self, digest_algorithm: S) -> Self {\n        self.digest_algorithm.replace(digest_algorithm.into());\n        self\n    }\n\n    /// Set the SHA1 hash of the signing certificate.\n    pub fn certificate_thumbprint<S: Into<String>>(mut self, certificate_thumbprint: S) -> Self {\n        self.certificate_thumbprint\n            .replace(certificate_thumbprint.into());\n        self\n    }\n\n    /// Set whether to use Time-Stamp Protocol (TSP, a.k.a. RFC 3161) for the timestamp server. Your code signing provider may\n    /// use a TSP timestamp server, like e.g. SSL.com does. If so, enable TSP by setting to true.\n    pub fn tsp(mut self, tsp: bool) -> Self {\n        self.tsp = tsp;\n        self\n    }\n\n    /// Set server url to use during timestamping.\n    pub fn timestamp_url<S: Into<String>>(mut self, timestamp_url: S) -> Self {\n        self.timestamp_url.replace(timestamp_url.into());\n        self\n    }\n\n    /// Set whether to validate a second app installation, blocking the user from installing an older version if set to `false`.\n    ///\n    /// For instance, if `1.2.1` is installed, the user won't be able to install app version `1.2.0` or `1.1.5`.\n    ///\n    /// The default value of this flag is `true`.\n    pub fn allow_downgrades(mut self, allow: bool) -> Self {\n        self.allow_downgrades = allow;\n        self\n    }\n}\n\n/// An enum representing the available verbosity levels of the logger.\n#[derive(Deserialize, Serialize)]\n#[repr(usize)]\n#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\n#[derive(Default)]\npub enum LogLevel {\n    /// The \"error\" level.\n    ///\n    /// Designates very serious errors.\n    #[default]\n    Error = 1,\n    /// The \"warn\" level.\n    ///\n    /// Designates hazardous situations.\n    Warn,\n    /// The \"info\" level.\n    ///\n    /// Designates useful information.\n    Info,\n    /// The \"debug\" level.\n    ///\n    /// Designates lower priority information.\n    Debug,\n    /// The \"trace\" level.\n    ///\n    /// Designates very low priority, often extremely verbose, information.\n    Trace,\n}\n\n/// A binary to package within the final package.\n#[derive(Debug, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\n#[non_exhaustive]\npub struct Binary {\n    /// Path to the binary (without `.exe` on Windows).\n    /// If it's relative, it will be resolved from [`Config::out_dir`].\n    pub path: PathBuf,\n    /// Whether this is the main binary or not\n    #[serde(default)]\n    pub main: bool,\n}\n\nimpl Binary {\n    /// Creates a new [`Binary`] from a path to the binary (without `.exe` on Windows).\n    /// If it's relative, it will be resolved from [`Config::out_dir`].\n    pub fn new<P: Into<PathBuf>>(path: P) -> Self {\n        Self {\n            path: path.into(),\n            main: false,\n        }\n    }\n\n    /// Set the path of the binary.\n    pub fn path<P: Into<PathBuf>>(mut self, path: P) -> Self {\n        self.path = path.into();\n        self\n    }\n\n    /// Set the binary as main binary.\n    pub fn main(mut self, main: bool) -> Self {\n        self.main = main;\n        self\n    }\n}\n\n/// A path to a resource (with optional glob pattern)\n/// or an object of `src` and `target` paths.\n#[derive(Debug, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(untagged)]\n#[non_exhaustive]\npub enum Resource {\n    /// Supports glob patterns\n    Single(String),\n    /// An object descriping the src file or directory\n    /// and its target location in the final package.\n    Mapped {\n        /// The src file or directory, supports glob patterns.\n        src: String,\n        /// A relative path from the root of the final package.\n        ///\n        /// If `src` is a glob, this will always be treated as a directory\n        /// where all globbed files will be placed under.\n        target: PathBuf,\n    },\n}\n\n/// Describes a shell command to be executed when a CLI hook is triggered.\n#[derive(Debug, Clone, Deserialize, Serialize)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(untagged)]\n#[non_exhaustive]\npub enum HookCommand {\n    /// Run the given script with the default options.\n    Script(String),\n    /// Run the given script with custom options.\n    ScriptWithOptions {\n        /// The script to execute.\n        script: String,\n        /// The working directory.\n        dir: Option<String>,\n    },\n}\n\n/// The packaging config.\n#[derive(Deserialize, Serialize, Default, Debug, Clone)]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\npub struct Config {\n    /// The JSON schema for the config.\n    ///\n    /// Setting this field has no effect, this just exists so\n    /// we can parse the JSON correctly when it has `$schema` field set.\n    #[serde(rename = \"$schema\")]\n    schema: Option<String>,\n    /// The app name, this is just an identifier that could be used\n    /// to filter which app to package using `--packages` cli arg when there is multiple apps in the\n    /// workspace or in the same config.\n    ///\n    /// This field resembles, the `name` field in `Cargo.toml` or `package.json`\n    ///\n    /// If `unset`, the CLI will try to auto-detect it from `Cargo.toml` or\n    /// `package.json` otherwise, it will keep it unset.\n    pub(crate) name: Option<String>,\n    /// Whether this config is enabled or not. Defaults to `true`.\n    #[serde(default = \"default_true\")]\n    pub(crate) enabled: bool,\n    /// The package's product name, for example \"My Awesome App\".\n    #[serde(default, alias = \"product-name\", alias = \"product_name\")]\n    pub product_name: String,\n    /// The package's version.\n    #[serde(default)]\n    pub version: String,\n    /// The binaries to package.\n    #[serde(default)]\n    pub binaries: Vec<Binary>,\n    /// The application identifier in reverse domain name notation (e.g. `com.packager.example`).\n    /// This string must be unique across applications since it is used in some system configurations.\n    /// This string must contain only alphanumeric characters (A-Z, a-z, and 0-9), hyphens (-),\n    /// and periods (.).\n    #[cfg_attr(feature = \"schema\", schemars(regex(pattern = r\"^[a-zA-Z0-9-\\.]*$\")))]\n    pub identifier: Option<String>,\n    /// The command to run before starting to package an application.\n    ///\n    /// This runs only once.\n    #[serde(alias = \"before-packaging-command\", alias = \"before_packaging_command\")]\n    pub before_packaging_command: Option<HookCommand>,\n    /// The command to run before packaging each format for an application.\n    ///\n    /// This will run multiple times depending on the formats specifed.\n    #[serde(\n        alias = \"before-each-package-command\",\n        alias = \"before_each_package_command\"\n    )]\n    pub before_each_package_command: Option<HookCommand>,\n    /// The logging level.\n    #[serde(alias = \"log-level\", alias = \"log_level\")]\n    pub log_level: Option<LogLevel>,\n    /// The packaging formats to create, if not present, [`PackageFormat::platform_default`] is used.\n    pub formats: Option<Vec<PackageFormat>>,\n    /// The directory where the generated packages will be placed.\n    ///\n    /// If [`Config::binaries_dir`] is not set, this is also where the [`Config::binaries`] exist.\n    #[serde(default, alias = \"out-dir\", alias = \"out_dir\")]\n    pub out_dir: PathBuf,\n    /// The directory where the [`Config::binaries`] exist.\n    ///\n    /// Defaults to [`Config::out_dir`].\n    #[serde(default, alias = \"binaries-dir\", alias = \"binaries_dir\")]\n    pub binaries_dir: Option<PathBuf>,\n    /// The target triple we are packaging for.\n    ///\n    /// Defaults to the current OS target triple.\n    #[serde(alias = \"target-triple\", alias = \"target_triple\")]\n    pub target_triple: Option<String>,\n    /// The package's description.\n    pub description: Option<String>,\n    /// The app's long description.\n    #[serde(alias = \"long-description\", alias = \"long_description\")]\n    pub long_description: Option<String>,\n    /// The package's homepage.\n    pub homepage: Option<String>,\n    /// The package's authors.\n    #[serde(default)]\n    pub authors: Option<Vec<String>>,\n    /// The app's publisher. Defaults to the second element in [`Config::identifier`](Config::identifier) string.\n    /// Currently maps to the Manufacturer property of the Windows Installer.\n    pub publisher: Option<String>,\n    /// A path to the license file.\n    #[serde(alias = \"license-file\", alias = \"license_file\")]\n    pub license_file: Option<PathBuf>,\n    /// The app's copyright.\n    pub copyright: Option<String>,\n    /// The app's category.\n    pub category: Option<AppCategory>,\n    /// The app's icon list. Supports glob patterns.\n    pub icons: Option<Vec<String>>,\n    /// The file associations\n    #[serde(alias = \"file-associations\", alias = \"file_associations\")]\n    pub file_associations: Option<Vec<FileAssociation>>,\n    /// Deep-link protocols.\n    #[serde(alias = \"deep-link-protocols\", alias = \"deep_link_protocols\")]\n    pub deep_link_protocols: Option<Vec<DeepLinkProtocol>>,\n    /// The app's resources to package. This a list of either a glob pattern, path to a file, path to a directory\n    /// or an object of `src` and `target` paths. In the case of using an object,\n    /// the `src` could be either a glob pattern, path to a file, path to a directory,\n    /// and the `target` is a path inside the final resources folder in the installed package.\n    ///\n    /// ## Format-specific:\n    ///\n    /// - **[PackageFormat::Nsis] / [PackageFormat::Wix]**: The resources are placed next to the executable in the root of the packager.\n    /// - **[PackageFormat::Deb]**: The resources are placed in `usr/lib` of the package.\n    pub resources: Option<Vec<Resource>>,\n    /// Paths to external binaries to add to the package.\n    ///\n    /// The path specified should not include `-<target-triple><.exe>` suffix,\n    /// it will be auto-added when by the packager when reading these paths,\n    /// so the actual binary name should have the target platform's target triple appended,\n    /// as well as `.exe` for Windows.\n    ///\n    /// For example, if you're packaging an external binary called `sqlite3`, the packager expects\n    /// a binary named `sqlite3-x86_64-unknown-linux-gnu` on linux,\n    /// and `sqlite3-x86_64-pc-windows-gnu.exe` on windows.\n    ///\n    /// If you are building a universal binary for MacOS, the packager expects\n    /// your external binary to also be universal, and named after the target triple,\n    /// e.g. `sqlite3-universal-apple-darwin`. See\n    /// <https://developer.apple.com/documentation/apple-silicon/building-a-universal-macos-binary>\n    #[serde(alias = \"external-binaries\", alias = \"external_binaries\")]\n    pub external_binaries: Option<Vec<PathBuf>>,\n    /// Windows-specific configuration.\n    pub windows: Option<WindowsConfig>,\n    /// MacOS-specific configuration.\n    pub macos: Option<MacOsConfig>,\n    /// Linux-specific configuration\n    pub linux: Option<LinuxConfig>,\n    /// Debian-specific configuration.\n    pub deb: Option<DebianConfig>,\n    /// AppImage configuration.\n    pub appimage: Option<AppImageConfig>,\n    /// Pacman configuration.\n    pub pacman: Option<PacmanConfig>,\n    /// WiX configuration.\n    pub wix: Option<WixConfig>,\n    /// Nsis configuration.\n    pub nsis: Option<NsisConfig>,\n    /// Dmg configuration.\n    pub dmg: Option<DmgConfig>,\n}\n\nimpl Config {\n    /// Creates a new [`ConfigBuilder`].\n    pub fn builder() -> ConfigBuilder {\n        ConfigBuilder::default()\n    }\n\n    /// Returns the [windows](Config::windows) specific configuration.\n    pub fn windows(&self) -> Option<&WindowsConfig> {\n        self.windows.as_ref()\n    }\n\n    /// Returns the [macos](Config::macos) specific configuration.\n    pub fn macos(&self) -> Option<&MacOsConfig> {\n        self.macos.as_ref()\n    }\n\n    /// Returns the [linux](Config::linux) specific configuration.\n    pub fn linux(&self) -> Option<&LinuxConfig> {\n        self.linux.as_ref()\n    }\n\n    /// Returns the [nsis](Config::nsis) specific configuration.\n    pub fn nsis(&self) -> Option<&NsisConfig> {\n        self.nsis.as_ref()\n    }\n\n    /// Returns the [wix](Config::wix) specific configuration.\n    pub fn wix(&self) -> Option<&WixConfig> {\n        self.wix.as_ref()\n    }\n\n    /// Returns the [debian](Config::deb) specific configuration.\n    pub fn deb(&self) -> Option<&DebianConfig> {\n        self.deb.as_ref()\n    }\n\n    /// Returns the [appimage](Config::appimage) specific configuration.\n    pub fn appimage(&self) -> Option<&AppImageConfig> {\n        self.appimage.as_ref()\n    }\n\n    /// Returns the [pacman](Config::pacman) specific configuration.\n    pub fn pacman(&self) -> Option<&PacmanConfig> {\n        self.pacman.as_ref()\n    }\n\n    /// Returns the [dmg](Config::dmg) specific configuration.\n    pub fn dmg(&self) -> Option<&DmgConfig> {\n        self.dmg.as_ref()\n    }\n\n    /// Returns the target triple of this config, if not set, fallsback to the current OS target triple.\n    pub fn target_triple(&self) -> String {\n        self.target_triple.clone().unwrap_or_else(|| {\n            util::target_triple().expect(\"Failed to detect current target triple\")\n        })\n    }\n\n    /// Returns the architecture for the package to be built (e.g. \"arm\", \"x86\" or \"x86_64\").\n    pub fn target_arch(&self) -> crate::Result<&str> {\n        let target = self.target_triple();\n        Ok(if target.starts_with(\"x86_64\") {\n            \"x86_64\"\n        } else if target.starts_with('i') {\n            \"x86\"\n        } else if target.starts_with(\"arm\") {\n            \"arm\"\n        } else if target.starts_with(\"aarch64\") {\n            \"aarch64\"\n        } else if target.starts_with(\"universal\") {\n            \"universal\"\n        } else {\n            return Err(crate::Error::UnexpectedTargetTriple(target));\n        })\n    }\n\n    /// Returns the path to the specified binary.\n    pub fn binary_path(&self, binary: &Binary) -> PathBuf {\n        if binary.path.is_absolute() {\n            binary.path.clone()\n        } else {\n            self.binaries_dir().join(&binary.path)\n        }\n    }\n\n    /// Returns the package identifier. Defaults an empty string.\n    pub fn identifier(&self) -> &str {\n        self.identifier.as_deref().unwrap_or(\"\")\n    }\n\n    /// Returns the package publisher.\n    /// Defaults to the second element in [`Config::identifier`](Config::identifier()).\n    pub fn publisher(&self) -> String {\n        self.publisher.clone().unwrap_or_else(|| {\n            self.identifier()\n                .split('.')\n                .nth(1)\n                .unwrap_or(self.identifier())\n                .into()\n        })\n    }\n\n    /// Returns the out dir. Defaults to the current directory.\n    pub fn out_dir(&self) -> PathBuf {\n        if self.out_dir.as_os_str().is_empty() {\n            return std::env::current_dir().expect(\"failed to resolve cwd\");\n        }\n\n        if !self.out_dir.exists() {\n            fs::create_dir_all(&self.out_dir).expect(\"failed to create output directory\");\n        }\n        dunce::canonicalize(&self.out_dir).unwrap_or_else(|_| self.out_dir.clone())\n    }\n\n    /// Returns the binaries dir. Defaults to [`Self::out_dir`] if [`Self::binaries_dir`] is not set.\n    pub fn binaries_dir(&self) -> PathBuf {\n        if let Some(path) = &self.binaries_dir {\n            dunce::canonicalize(path).unwrap_or_else(|_| path.clone())\n        } else {\n            self.out_dir()\n        }\n    }\n\n    /// Returns the main binary.\n    pub fn main_binary(&self) -> crate::Result<&Binary> {\n        self.binaries\n            .iter()\n            .find(|bin| bin.main)\n            .ok_or_else(|| crate::Error::MainBinaryNotFound)\n    }\n\n    /// Returns a mutable reference to the main binary.\n    pub fn main_binary_mut(&mut self) -> crate::Result<&mut Binary> {\n        self.binaries\n            .iter_mut()\n            .find(|bin| bin.main)\n            .ok_or_else(|| crate::Error::MainBinaryNotFound)\n    }\n\n    /// Returns the main binary name.\n    pub fn main_binary_name(&self) -> crate::Result<String> {\n        self.binaries\n            .iter()\n            .find(|bin| bin.main)\n            .map(|b| b.path.file_stem().unwrap().to_string_lossy().into_owned())\n            .ok_or_else(|| crate::Error::MainBinaryNotFound)\n    }\n\n    /// Returns all icons path.\n    pub fn icons(&self) -> crate::Result<Option<Vec<PathBuf>>> {\n        let Some(patterns) = &self.icons else {\n            return Ok(None);\n        };\n        let mut paths = Vec::new();\n        for pattern in patterns {\n            for icon_path in glob::glob(pattern)? {\n                paths.push(icon_path?);\n            }\n        }\n        Ok(Some(paths))\n    }\n}\n\n#[derive(Debug, Clone)]\npub(crate) struct ResolvedResource {\n    pub src: PathBuf,\n    pub target: PathBuf,\n}\n\nimpl Config {\n    #[inline]\n    pub(crate) fn resources_from_dir(\n        src_dir: &Path,\n        target_dir: &Path,\n    ) -> crate::Result<Vec<ResolvedResource>> {\n        let mut out = Vec::new();\n        for entry in walkdir::WalkDir::new(src_dir) {\n            let entry = entry?;\n            let path = entry.path();\n            if path.is_file() {\n                let relative = path.relative_to(src_dir)?.to_path(\"\");\n                let src = dunce::canonicalize(path)\n                    .map_err(|e| Error::IoWithPath(path.to_path_buf(), e))?;\n                let resource = ResolvedResource {\n                    src,\n                    target: target_dir.join(relative),\n                };\n                out.push(resource);\n            }\n        }\n        Ok(out)\n    }\n\n    #[inline]\n    pub(crate) fn resources_from_glob(glob: &str) -> crate::Result<Vec<ResolvedResource>> {\n        let mut out = Vec::new();\n        for src in glob::glob(glob)? {\n            let src = src?;\n            let src = dunce::canonicalize(&src).map_err(|e| Error::IoWithPath(src, e))?;\n            let target = PathBuf::from(src.file_name().unwrap_or_default());\n            out.push(ResolvedResource { src, target })\n        }\n        Ok(out)\n    }\n\n    pub(crate) fn resources(&self) -> crate::Result<Vec<ResolvedResource>> {\n        if let Some(resources) = &self.resources {\n            let mut out = Vec::new();\n            for r in resources {\n                match r {\n                    Resource::Single(src) => {\n                        let src_dir = PathBuf::from(src);\n                        if src_dir.is_dir() {\n                            let target_dir = Path::new(src_dir.file_name().unwrap_or_default());\n                            out.extend(Self::resources_from_dir(&src_dir, target_dir)?);\n                        } else {\n                            out.extend(Self::resources_from_glob(src)?);\n                        }\n                    }\n                    Resource::Mapped { src, target } => {\n                        let src_path = PathBuf::from(src);\n                        let target_dir = sanitize_path(target);\n                        if src_path.is_dir() {\n                            out.extend(Self::resources_from_dir(&src_path, &target_dir)?);\n                        } else if src_path.is_file() {\n                            let src = dunce::canonicalize(&src_path)\n                                .map_err(|e| Error::IoWithPath(src_path, e))?;\n                            out.push(ResolvedResource {\n                                src,\n                                target: sanitize_path(target),\n                            });\n                        } else {\n                            let globbed_res = Self::resources_from_glob(src)?;\n                            let retargetd_res = globbed_res.into_iter().map(|mut r| {\n                                r.target = target_dir.join(r.target);\n                                r\n                            });\n                            out.extend(retargetd_res);\n                        }\n                    }\n                }\n            }\n\n            Ok(out)\n        } else {\n            Ok(vec![])\n        }\n    }\n\n    #[allow(unused)]\n    pub(crate) fn find_ico(&self) -> crate::Result<Option<PathBuf>> {\n        let icon = self\n            .icons()?\n            .as_ref()\n            .and_then(|icons| {\n                icons\n                    .iter()\n                    .find(|i| PathBuf::from(i).extension().and_then(|s| s.to_str()) == Some(\"ico\"))\n                    .or_else(|| {\n                        icons.iter().find(|i| {\n                            PathBuf::from(i).extension().and_then(|s| s.to_str()) == Some(\"png\")\n                        })\n                    })\n            })\n            .map(PathBuf::from);\n        Ok(icon)\n    }\n\n    #[allow(unused)]\n    pub(crate) fn copy_resources(&self, path: &Path) -> crate::Result<()> {\n        for resource in self.resources()? {\n            let dest = path.join(resource.target);\n            fs::create_dir_all(\n                dest.parent()\n                    .ok_or_else(|| crate::Error::ParentDirNotFound(dest.to_path_buf()))?,\n            )?;\n            fs::copy(&resource.src, &dest)\n                .map_err(|e| Error::CopyFile(resource.src.clone(), dest.clone(), e))?;\n        }\n        Ok(())\n    }\n\n    #[allow(unused)]\n    pub(crate) fn copy_external_binaries(&self, path: &Path) -> crate::Result<Vec<PathBuf>> {\n        let mut paths = Vec::new();\n        if let Some(external_binaries) = &self.external_binaries {\n            let cwd = std::env::current_dir()?;\n            let target_triple = self.target_triple();\n            for src in external_binaries {\n                let file_name = src\n                    .file_name()\n                    .ok_or_else(|| crate::Error::FailedToExtractFilename(src.clone()))?\n                    .to_string_lossy();\n                #[cfg(windows)]\n                let src = src.with_file_name(format!(\"{file_name}-{target_triple}.exe\"));\n                #[cfg(not(windows))]\n                let src = src.with_file_name(format!(\"{file_name}-{target_triple}\"));\n                #[cfg(windows)]\n                let dest = path.join(format!(\"{file_name}.exe\"));\n                #[cfg(not(windows))]\n                let dest = path.join(&*file_name);\n                fs::copy(&src, &dest).map_err(|e| Error::CopyFile(src.clone(), dest.clone(), e))?;\n                paths.push(dest);\n            }\n        }\n\n        Ok(paths)\n    }\n}\n\nfn sanitize_path<P: AsRef<Path>>(path: P) -> PathBuf {\n    let mut dest = PathBuf::new();\n    for c in path.as_ref().components() {\n        if let std::path::Component::Normal(s) = c {\n            dest.push(s)\n        }\n    }\n    dest\n}\n\nfn default_true() -> bool {\n    true\n}\n"
  },
  {
    "path": "crates/packager/src/error.rs",
    "content": "// Copyright 2019-2023 Tauri Programme within The Commons Conservancy\n// Copyright 2023-2023 CrabNebula Ltd.\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::path::PathBuf;\n\nuse thiserror::Error;\n\n#[non_exhaustive]\n#[derive(Error, Debug)]\n/// Errors returned by cargo-packager.\npub enum Error {\n    /// JSON parsing error.\n    #[error(transparent)]\n    Json(#[from] serde_json::Error),\n    /// Target triple architecture error\n    #[error(\"Unable to determine target-architecture\")]\n    Architecture,\n    /// Target triple OS error\n    #[error(\"Unable to determine target-os\")]\n    Os,\n    /// Target triple environment error\n    #[error(\"Unable to determine target-environment\")]\n    Environment,\n    /// I/O errors with path.\n    #[error(\"I/O Error ({0}): {1}\")]\n    IoWithPath(PathBuf, std::io::Error),\n    /// I/O copy file errors.\n    #[error(\"Failed to copy file from {0} to {1}: {2}\")]\n    CopyFile(PathBuf, PathBuf, std::io::Error),\n    /// I/O rename file errors.\n    #[error(\"Failed to rename file from {0} to {1}: {2}\")]\n    RenameFile(PathBuf, PathBuf, std::io::Error),\n    /// I/O symlink file errors.\n    #[error(\"Failed to symlink file from {0} to {1}: {2}\")]\n    Symlink(PathBuf, PathBuf, std::io::Error),\n    /// I/O errors.\n    #[error(transparent)]\n    Io(#[from] std::io::Error),\n    /// Hex de/encoding errors.\n    #[error(transparent)]\n    Hex(#[from] hex::FromHexError),\n    /// Failed to validate downloaded file hash.\n    #[error(\"Hash mismatch of downloaded file\")]\n    HashError,\n    /// Zip error.\n    #[error(transparent)]\n    ZipError(#[from] zip::result::ZipError),\n    /// Zip error.\n    #[error(transparent)]\n    DownloadError(#[from] Box<ureq::Error>),\n    /// Unsupported OS bitness.\n    #[error(\"Unsupported OS bitness\")]\n    UnsupportedBitness,\n    /// Windows SignTool not found.\n    #[error(\"SignTool not found\")]\n    SignToolNotFound,\n    /// Unexpected target triple.\n    #[error(\"Unexpected target triple: {0}\")]\n    UnexpectedTargetTriple(String),\n    /// Unsupported architecture.\n    #[error(\"Unsupported architecture for \\\"{0}\\\" target triple: {0}\")]\n    UnsupportedArch(String, String),\n    /// Could not find the main binary in list of provided binaries.\n    #[error(\"Could not find the main binary in list of provided binaries\")]\n    MainBinaryNotFound,\n    /// Semver parsing error\n    #[error(transparent)]\n    Semver(#[from] semver::Error),\n    /// Non-numeric build metadata in app version.\n    #[error(\"Optional build metadata in app version must be numeric-only {}\", .0.clone().unwrap_or_default())]\n    NonNumericBuildMetadata(Option<String>),\n    /// Invalid app version when building [crate::PackageFormat::Wix]\n    #[error(\"Invalid app version: {0}\")]\n    InvalidAppVersion(String),\n    /// Handlebars render error.\n    #[error(transparent)]\n    HandleBarsRenderError(#[from] handlebars::RenderError),\n    /// Handlebars template error.\n    #[error(transparent)]\n    HandleBarsTemplateError(#[from] Box<handlebars::TemplateError>),\n    /// Nsis error\n    #[error(\"Error running makensis.exe: {0}\")]\n    NsisFailed(std::io::Error),\n    /// Nsis error\n    #[error(\"Error running {0}: {0}\")]\n    WixFailed(String, std::io::Error),\n    /// create-dmg script error\n    #[error(\"Error running create-dmg script: {0}\")]\n    CreateDmgFailed(std::io::Error),\n    /// signtool.exe error\n    #[error(\"Error running signtool.exe: {0}\")]\n    SignToolFailed(std::io::Error),\n    /// Custom signing command error\n    #[error(\"Error running custom signing command: {0}\")]\n    CustomSignCommandFailed(std::io::Error),\n    /// bundle_appimage script error\n    #[error(\"Error running bundle_appimage.sh script: {0}\")]\n    AppImageScriptFailed(std::io::Error),\n    /// Failed to get parent directory of a path\n    #[error(\"Failed to get parent directory of {0}\")]\n    ParentDirNotFound(PathBuf),\n    /// A hook, for example `beforePackagaingCommand`, has failed.\n    #[error(\"{0} `{1}` failed: {2}\")]\n    HookCommandFailure(String, String, std::io::Error),\n    /// A hook, for example `beforePackagaingCommand`, has failed with an exit code.\n    #[error(\"{0} `{1}` failed with exit code {2}\")]\n    HookCommandFailureWithExitCode(String, String, i32),\n    /// Regex error.\n    #[cfg(windows)]\n    #[error(transparent)]\n    RegexError(#[from] regex::Error),\n    /// Glob pattern error.\n    #[error(transparent)]\n    GlobPatternError(#[from] glob::PatternError),\n    /// Glob error.\n    #[error(transparent)]\n    Glob(#[from] glob::GlobError),\n    /// Unsupported WiX language\n    #[cfg(windows)]\n    #[error(\"Wix language {0} not found. It must be one of {1}\")]\n    UnsupportedWixLanguage(String, String),\n    /// Image crate errors.\n    #[error(transparent)]\n    ImageError(#[from] image::ImageError),\n    /// walkdir crate errors.\n    #[error(transparent)]\n    WalkDirError(#[from] walkdir::Error),\n    /// Path prefix strip error.\n    #[error(transparent)]\n    StripPrefixError(#[from] std::path::StripPrefixError),\n    /// Relative paths errors\n    #[error(transparent)]\n    RelativeToError(#[from] relative_path::RelativeToError),\n    /// Time error.\n    #[error(\"`{0}`\")]\n    TimeError(#[from] time::error::Error),\n    /// Plist error.\n    #[error(transparent)]\n    Plist(#[from] plist::Error),\n    /// Framework not found.\n    #[error(\"Framework {0} not found\")]\n    FrameworkNotFound(String),\n    /// Invalid framework.\n    #[error(\"Invalid framework {framework}: {reason}\")]\n    InvalidFramework {\n        /// Framework name\n        framework: String,\n        /// Reason why this framework is invalid\n        reason: &'static str,\n    },\n    /// Invalid icons.\n    #[error(\"Could not find a valid icon\")]\n    InvalidIconList,\n    /// Failed to notarize.\n    #[error(\"Failed to notarize app\")]\n    FailedToNotarize,\n    /// Rejected on notarize.\n    #[error(\"Failed to notarize app: {0}\")]\n    NotarizeRejected(String),\n    /// Failed to parse notarytool output.\n    #[error(\"Failed to parse notarytool output as JSON: `{0}`\")]\n    FailedToParseNotarytoolOutput(String),\n    /// Failed to find API key file.\n    #[error(\"Could not find API key file. Please set the APPLE_API_KEY_PATH environment variables to the path to the {filename} file\")]\n    ApiKeyMissing {\n        /// Filename of the API key.\n        filename: String,\n    },\n    /// Missing notarize environment variables.\n    #[error(\"Could not find APPLE_ID & APPLE_PASSWORD & APPLE_TEAM_ID or APPLE_API_KEY & APPLE_API_ISSUER & APPLE_API_KEY_PATH environment variables found\")]\n    MissingNotarizeAuthVars,\n    /// Failed to list keychains\n    #[error(\"Failed to list keychains: {0}\")]\n    FailedToListKeyChain(std::io::Error),\n    /// Failed to decode certficate as base64\n    #[error(\"Failed to decode certficate as base64: {0}\")]\n    FailedToDecodeCert(std::io::Error),\n    /// Failed to create keychain.\n    #[error(\"Failed to create keychain: {0}\")]\n    FailedToCreateKeyChain(std::io::Error),\n    /// Failed to create keychain.\n    #[error(\"Failed to unlock keychain: {0}\")]\n    FailedToUnlockKeyChain(std::io::Error),\n    /// Failed to import certificate.\n    #[error(\"Failed to import certificate: {0}\")]\n    FailedToImportCert(std::io::Error),\n    /// Failed to set keychain settings.\n    #[error(\"Failed to set keychain settings: {0}\")]\n    FailedToSetKeychainSettings(std::io::Error),\n    /// Failed to set key partition list.\n    #[error(\"Failed to set key partition list: {0}\")]\n    FailedToSetKeyPartitionList(std::io::Error),\n    /// Failed to run codesign utility.\n    #[error(\"Failed to run codesign utility: {0}\")]\n    FailedToRunCodesign(std::io::Error),\n    /// Failed to run ditto utility.\n    #[error(\"Failed to run ditto utility: {0}\")]\n    FailedToRunDitto(std::io::Error),\n    /// Failed to run xcrun utility.\n    #[error(\"Failed to run xcrun utility: {0}\")]\n    FailedToRunXcrun(std::io::Error),\n    /// Path already exists.\n    #[error(\"{0} already exists\")]\n    AlreadyExists(PathBuf),\n    /// Path does not exist.\n    #[error(\"{0} does not exist\")]\n    DoesNotExist(PathBuf),\n    /// Path is not a directory.\n    #[error(\"{0} is not a directory\")]\n    IsNotDirectory(PathBuf),\n    /// Could not find a square icon to use as AppImage icon\n    #[error(\"Could not find a square icon to use as AppImage icon\")]\n    AppImageSquareIcon,\n    /// Base64 decoding error.\n    #[error(transparent)]\n    Base64DecodeError(#[from] base64::DecodeError),\n    /// Utf8 parsing error.\n    #[error(transparent)]\n    Utf8Error(#[from] std::str::Utf8Error),\n    /// minisign errors.\n    #[error(transparent)]\n    Minisign(#[from] minisign::PError),\n    /// System time errors.\n    #[error(transparent)]\n    SystemTimeError(#[from] std::time::SystemTimeError),\n    /// Signing keys generation error.\n    #[error(\"aborted key generation, {0} already exists and force overrwite wasnot desired.\")]\n    SigningKeyExists(PathBuf),\n    /// Failed to extract external binary filename\n    #[error(\"Failed to extract filename from {0}\")]\n    FailedToExtractFilename(PathBuf),\n    /// Failed to remove extended attributes from app bundle\n    #[error(\"Failed to remove extended attributes from app bundle: {0}\")]\n    #[cfg(target_os = \"macos\")]\n    FailedToRemoveExtendedAttributes(std::io::Error),\n    /// Could not find the embedded.provisionprofile file.\n    #[error(\"Embedded provision profile file {0} not found\")]\n    EmbeddedProvisionprofileFileNotFound(PathBuf),\n    /// Could not copy the embedded.provisionprofile file to the Contents directory.\n    #[error(\"Could not copy embedded provision profile file {0}: {1}\")]\n    FailedToCopyEmbeddedProvisionprofile(PathBuf, std::io::Error),\n    /// Failed to open Windows registry.\n    #[error(\"failed to open registry {0}\")]\n    OpenRegistry(String),\n    /// Failed to get registry value.\n    #[error(\"failed to get {0} value on registry\")]\n    GetRegistryValue(String),\n    /// Failed to enumerate registry keys.\n    #[error(\"failed to enumerate registry keys\")]\n    FailedToEnumerateRegKeys,\n}\n\n/// Convenient type alias of Result type for cargo-packager.\npub type Result<T> = std::result::Result<T, Error>;\n"
  },
  {
    "path": "crates/packager/src/lib.rs",
    "content": "// Copyright 2023-2023 CrabNebula Ltd.\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! [![cargo-packager splash](https://github.com/crabnebula-dev/cargo-packager/raw/main/.github/splash.png)](https://github.com/crabnebula-dev/cargo-packager)\n//!\n//! Executable packager, bundler and updater. A cli tool and library to generate installers or app bundles for your executables.\n//! It also comes with useful addons:\n//! - an [updater](https://docs.rs/cargo-packager-updater)\n//! - a [resource resolver](https://docs.rs/cargo-packager-resource-resolver)\n//!\n//! ### Supported packages\n//!\n//! - macOS\n//!   - DMG (.dmg)\n//!   - Bundle (.app)\n//! - Linux\n//!   - Debian package (.deb)\n//!   - AppImage (.AppImage)\n//!   - Pacman (.tar.gz and PKGBUILD)\n//! - Windows\n//!   - NSIS (.exe)\n//!   - MSI using WiX Toolset (.msi)\n//!\n//! ## CLI\n//!\n//! This crate is a cargo subcommand so you can install using:\n//!\n//! ```sh\n//! cargo install cargo-packager --locked\n//! ```\n//! You then need to configure your app so the cli can recognize it. Configuration can be done in `Packager.toml` or `packager.json` in your project or modify Cargo.toml and include this snippet:\n//!\n//! ```toml\n//! [package.metadata.packager]\n//! before-packaging-command = \"cargo build --release\"\n//! ```\n//!\n//! Once, you are done configuring your app, run:\n//!\n//! ```sh\n//! cargo packager --release\n//! ```\n//!\n//! ### Configuration\n//!\n//! By default, the packager reads its configuration from `Packager.toml` or `packager.json` if it exists, and from `package.metadata.packager` table in `Cargo.toml`.\n//! You can also specify a custom configuration using the `-c/--config` cli argument.\n//!\n//! For a full list of configuration options, see [Config].\n//!\n//! You could also use the [schema](./schema.json) file from GitHub to validate your configuration or have auto completions in your IDE.\n//!\n//! ### Building your application before packaging\n//!\n//! By default, the packager doesn't build your application, so if your app requires a compilation step, the packager has an option to specify a shell command to be executed before packaing your app, `beforePackagingCommand`.\n//!\n//! ### Cargo profiles\n//!\n//! By default, the packager looks for binaries built using the `debug` profile, if your `beforePackagingCommand` builds your app using `cargo build --release`, you will also need to\n//! run the packager in release mode `cargo packager --release`, otherwise, if you have a custom cargo profile, you will need to specify it using `--profile` cli arg `cargo packager --profile custom-release-profile`.\n//!\n//! ### Library\n//!\n//! This crate is also published to crates.io as a library that you can integrate into your tooling, just make sure to disable the default-feature flags.\n//!\n//! ```sh\n//! cargo add cargo-packager --no-default-features\n//! ```\n//!\n//! #### Feature flags\n//!\n//! - **`cli`**: Enables the cli specifc features and dependencies. Enabled by default.\n//! - **`tracing`**: Enables `tracing` crate integration.\n\n#![cfg_attr(doc_cfg, feature(doc_cfg))]\n#![deny(missing_docs)]\n\nuse std::{io::Write, path::PathBuf};\n\nmod codesign;\nmod error;\nmod package;\nmod shell;\nmod util;\n\n#[cfg(feature = \"cli\")]\n#[cfg_attr(doc_cfg, doc(cfg(feature = \"cli\")))]\npub mod cli;\npub mod config;\npub mod sign;\n\npub use config::{Config, PackageFormat};\npub use error::{Error, Result};\nuse flate2::{write::GzEncoder, Compression};\npub use sign::SigningConfig;\n\npub use package::{package, PackageOutput};\nuse util::PathExt;\n\n#[cfg(feature = \"cli\")]\nfn parse_log_level(verbose: u8) -> tracing::Level {\n    match verbose {\n        0 => tracing_subscriber::EnvFilter::builder()\n            .from_env_lossy()\n            .max_level_hint()\n            .and_then(|l| l.into_level())\n            .unwrap_or(tracing::Level::INFO),\n        1 => tracing::Level::DEBUG,\n        2.. => tracing::Level::TRACE,\n    }\n}\n\n/// Inits the tracing subscriber.\n#[cfg(feature = \"cli\")]\n#[cfg_attr(doc_cfg, doc(cfg(feature = \"cli\")))]\npub fn init_tracing_subscriber(verbosity: u8) {\n    let level = parse_log_level(verbosity);\n\n    let debug = level == tracing::Level::DEBUG;\n    let tracing = level == tracing::Level::TRACE;\n\n    let subscriber = tracing_subscriber::fmt()\n        .with_ansi(std::io::IsTerminal::is_terminal(&std::io::stderr()))\n        .with_target(debug)\n        .with_line_number(tracing)\n        .with_file(tracing)\n        .with_max_level(level);\n\n    let formatter = tracing_subscriber::fmt::format()\n        .compact()\n        .with_target(debug)\n        .with_line_number(tracing)\n        .with_file(tracing);\n\n    if tracing {\n        subscriber\n            .event_format(TracingFormatter::WithTime(formatter))\n            .init();\n    } else {\n        subscriber\n            .without_time()\n            .event_format(TracingFormatter::WithoutTime(formatter.without_time()))\n            .init();\n    }\n}\n\n#[cfg(feature = \"cli\")]\nenum TracingFormatter {\n    WithoutTime(\n        tracing_subscriber::fmt::format::Format<tracing_subscriber::fmt::format::Compact, ()>,\n    ),\n    WithTime(tracing_subscriber::fmt::format::Format<tracing_subscriber::fmt::format::Compact>),\n}\n\n#[cfg(feature = \"cli\")]\nstruct ShellFieldVisitor {\n    message: String,\n}\n\n#[cfg(feature = \"cli\")]\nimpl tracing::field::Visit for ShellFieldVisitor {\n    fn record_str(&mut self, field: &tracing::field::Field, value: &str) {\n        if field.name() == \"message\" {\n            self.message = value.to_string();\n        }\n    }\n\n    fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {\n        if field.name() == \"message\" {\n            self.message = format!(\"{value:?}\");\n        }\n    }\n}\n\n#[cfg(feature = \"cli\")]\nimpl<S, N> tracing_subscriber::fmt::FormatEvent<S, N> for TracingFormatter\nwhere\n    S: tracing::Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a>,\n    N: for<'a> tracing_subscriber::fmt::FormatFields<'a> + 'static,\n{\n    fn format_event(\n        &self,\n        ctx: &tracing_subscriber::fmt::FmtContext<'_, S, N>,\n        mut writer: tracing_subscriber::fmt::format::Writer<'_>,\n        event: &tracing::Event<'_>,\n    ) -> std::fmt::Result {\n        if event.fields().any(|f| f.name() == \"shell\") {\n            let mut visitor = ShellFieldVisitor { message: \"\".into() };\n            event.record(&mut visitor);\n            writeln!(writer, \"{}\", visitor.message)\n        } else {\n            match self {\n                TracingFormatter::WithoutTime(formatter) => {\n                    formatter.format_event(ctx, writer, event)\n                }\n                TracingFormatter::WithTime(formatter) => formatter.format_event(ctx, writer, event),\n            }\n        }\n    }\n}\n\n/// Sign the specified packages and return the signatures paths.\n///\n/// If `packages` contain a directory in the case of [`PackageFormat::App`]\n/// it will zip the directory before signing and appends it to `packages`.\n#[tracing::instrument(level = \"trace\")]\npub fn sign_outputs(\n    config: &SigningConfig,\n    packages: &mut Vec<PackageOutput>,\n) -> crate::Result<Vec<PathBuf>> {\n    let mut signatures = Vec::new();\n    for package in packages {\n        for path in &package.paths.clone() {\n            let path = if path.is_dir() {\n                let zip = path.with_additional_extension(\"tar.gz\");\n                let dest_file = util::create_file(&zip)?;\n                let gzip_encoder = GzEncoder::new(dest_file, Compression::default());\n                let writer = util::create_tar_from_dir(path, gzip_encoder)?;\n                let mut dest_file = writer.finish()?;\n                dest_file.flush()?;\n\n                package.paths.push(zip);\n                package.paths.last().unwrap()\n            } else {\n                path\n            };\n            signatures.push(sign::sign_file(config, path)?);\n        }\n    }\n\n    Ok(signatures)\n}\n\n/// Package an app using the specified config.\n/// Then signs the generated packages.\n///\n/// This is similar to calling `sign_outputs(signing_config, package(config)?)`\n///\n/// Returns a tuple of list of packages and list of signatures.\n#[tracing::instrument(level = \"trace\")]\npub fn package_and_sign(\n    config: &Config,\n    signing_config: &SigningConfig,\n) -> crate::Result<(Vec<PackageOutput>, Vec<PathBuf>)> {\n    let mut packages = package(config)?;\n    let signatures = sign_outputs(signing_config, &mut packages)?;\n    Ok((packages, signatures))\n}\n"
  },
  {
    "path": "crates/packager/src/package/app/mod.rs",
    "content": "// Copyright 2016-2019 Cargo-Bundle developers <https://github.com/burtonageo/cargo-bundle>\n// Copyright 2019-2023 Tauri Programme within The Commons Conservancy\n// Copyright 2023-2023 CrabNebula Ltd.\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::fs;\nuse std::path::{Path, PathBuf};\n\nuse super::Context;\nuse crate::Error;\nuse crate::{config::Config, util};\n\n#[cfg(target_os = \"macos\")]\nuse crate::{\n    codesign::macos::{self as codesign, SignTarget},\n    shell::CommandExt,\n};\n\n#[tracing::instrument(level = \"trace\", skip(ctx))]\npub(crate) fn package(ctx: &Context) -> crate::Result<Vec<PathBuf>> {\n    let Context { config, .. } = ctx;\n    // we should use the bundle name (App name) as a macOS standard.\n    // version or platform shouldn't be included in the App name.\n    let app_product_name = format!(\"{}.app\", config.product_name);\n    let app_bundle_path = config.out_dir().join(&app_product_name);\n\n    if app_bundle_path.exists() {\n        fs::remove_dir_all(&app_bundle_path)\n            .map_err(|e| Error::IoWithPath(app_bundle_path.clone(), e))?;\n    }\n\n    tracing::info!(\n        \"Packaging {} ({})\",\n        app_product_name,\n        app_bundle_path.display()\n    );\n\n    let contents_directory = app_bundle_path.join(\"Contents\");\n    fs::create_dir_all(&contents_directory)\n        .map_err(|e| Error::IoWithPath(contents_directory.clone(), e))?;\n\n    let resources_dir = contents_directory.join(\"Resources\");\n    let bin_dir = contents_directory.join(\"MacOS\");\n    fs::create_dir_all(&bin_dir).map_err(|e| Error::IoWithPath(bin_dir.clone(), e))?;\n\n    #[cfg(target_os = \"macos\")]\n    let mut sign_paths = std::collections::BinaryHeap::new();\n\n    let bundle_icon_file = util::create_icns_file(&resources_dir, config)?;\n\n    tracing::debug!(\"Creating Info.plist\");\n    create_info_plist(&contents_directory, bundle_icon_file, config)?;\n\n    tracing::debug!(\"Copying frameworks\");\n    let _framework_paths = copy_frameworks_to_bundle(&contents_directory, config)?;\n\n    #[cfg(target_os = \"macos\")]\n    sign_paths.extend(\n        _framework_paths\n            .into_iter()\n            .filter(|p| {\n                let ext = p.extension();\n                ext == Some(std::ffi::OsStr::new(\"framework\"))\n            })\n            .map(|path| SignTarget {\n                path,\n                is_native_binary: false,\n            }),\n    );\n\n    tracing::debug!(\"Copying resources\");\n    config.copy_resources(&resources_dir)?;\n\n    tracing::debug!(\"Copying embedded.provisionprofile\");\n    copy_embedded_provisionprofile_file(&contents_directory, config)?;\n\n    tracing::debug!(\"Copying embedded apps\");\n    let embedded_apps = copy_embedded_apps(&contents_directory, config)?;\n\n    tracing::debug!(\"Copying external binaries\");\n    config.copy_external_binaries(&bin_dir)?;\n    tracing::debug!(\"Copying binaries\");\n    for bin in &config.binaries {\n        let bin_path = config.binary_path(bin);\n        let dest_path = bin_dir.join(bin.path.file_name().unwrap());\n        fs::copy(&bin_path, &dest_path)\n            .map_err(|e| Error::CopyFile(bin_path.clone(), dest_path.clone(), e))?;\n    }\n\n    // All dylib files and native executables should be signed manually\n    // It is highly discouraged by Apple to use the --deep codesign parameter in larger projects.\n    // https://developer.apple.com/forums/thread/129980\n\n    // Find all files in the app bundle\n    let files = walkdir::WalkDir::new(&app_bundle_path)\n        .into_iter()\n        .flatten()\n        .map(|dir| dir.into_path())\n        .filter(|path| !embedded_apps.iter().any(|x| path.starts_with(x)));\n\n    // Filter all files for Mach-O headers. This will target all .dylib and native executable files\n    for file in files {\n        let metadata = match fs::metadata(&file) {\n            Ok(f) => f,\n            Err(err) => {\n                tracing::warn!(\"Failed to get metadata for {}: {err}, this file will not be scanned for Mach-O header!\", file.display());\n                continue;\n            }\n        };\n\n        // ignore folders and files that do not include at least the header size\n        if !metadata.is_file() || metadata.len() < 4 {\n            continue;\n        }\n\n        let mut open_file = match fs::File::open(&file) {\n            Ok(f) => f,\n            Err(err) => {\n                tracing::warn!(\"Failed to open {} for reading: {err}, this file will not be scanned for Mach-O header!\", file.display());\n                continue;\n            }\n        };\n\n        let mut buffer = [0; 4];\n        std::io::Read::read_exact(&mut open_file, &mut buffer)?;\n\n        const MACH_O_MAGIC_NUMBERS: [u32; 5] =\n            [0xfeedface, 0xfeedfacf, 0xcafebabe, 0xcefaedfe, 0xcffaedfe];\n\n        let magic = u32::from_be_bytes(buffer);\n\n        let is_mach = MACH_O_MAGIC_NUMBERS.contains(&magic);\n        if !is_mach {\n            continue;\n        }\n\n        #[cfg(target_os = \"macos\")]\n        sign_paths.push(SignTarget {\n            path: file,\n            is_native_binary: true,\n        });\n    }\n\n    #[cfg(target_os = \"macos\")]\n    if let Some(identity) = config\n        .macos()\n        .and_then(|macos| macos.signing_identity.as_ref())\n    {\n        tracing::debug!(\"Codesigning {}\", app_bundle_path.display());\n        // Sign frameworks and sidecar binaries first, per apple, signing must be done inside out\n        // https://developer.apple.com/forums/thread/701514\n        sign_paths.push(SignTarget {\n            path: app_bundle_path.clone(),\n            is_native_binary: true,\n        });\n\n        // Remove extra attributes, which could cause codesign to fail\n        // https://developer.apple.com/library/archive/qa/qa1940/_index.html\n        remove_extra_attr(&app_bundle_path)?;\n\n        // sign application\n        let sign_paths = sign_paths.into_sorted_vec();\n        codesign::try_sign(sign_paths, identity, config)?;\n\n        // notarization is required for distribution\n        match config\n            .macos()\n            .and_then(|m| m.notarization_credentials.clone())\n            .ok_or(crate::Error::MissingNotarizeAuthVars)\n            .or_else(|_| codesign::notarize_auth())\n        {\n            Ok(auth) => {\n                tracing::debug!(\"Notarizing {}\", app_bundle_path.display());\n                codesign::notarize(app_bundle_path.clone(), auth, config)?;\n            }\n            Err(e) => {\n                tracing::warn!(\"Skipping app notarization, {}\", e.to_string());\n            }\n        }\n    }\n\n    Ok(vec![app_bundle_path])\n}\n\n// Creates the Info.plist file.\n#[tracing::instrument(level = \"trace\", skip(config))]\nfn create_info_plist(\n    contents_directory: &Path,\n    bundle_icon_file: Option<PathBuf>,\n    config: &Config,\n) -> crate::Result<()> {\n    let format = time::format_description::parse(\"[year][month][day].[hour][minute][second]\")\n        .map_err(time::error::Error::from)?;\n    let build_number = time::OffsetDateTime::now_utc()\n        .format(&format)\n        .map_err(time::error::Error::from)?;\n\n    let mut plist = plist::Dictionary::new();\n    plist.insert(\"CFBundleDevelopmentRegion\".into(), \"English\".into());\n    plist.insert(\n        \"CFBundleDisplayName\".into(),\n        config.product_name.clone().into(),\n    );\n    plist.insert(\n        \"CFBundleExecutable\".into(),\n        config.main_binary_name()?.clone().into(),\n    );\n    if let Some(path) = bundle_icon_file {\n        plist.insert(\n            \"CFBundleIconFile\".into(),\n            path.file_name()\n                .ok_or_else(|| Error::FailedToExtractFilename(path.clone()))?\n                .to_string_lossy()\n                .into_owned()\n                .into(),\n        );\n    }\n    plist.insert(\"CFBundleIdentifier\".into(), config.identifier().into());\n    plist.insert(\"CFBundleInfoDictionaryVersion\".into(), \"6.0\".into());\n    plist.insert(\"CFBundleName\".into(), config.product_name.clone().into());\n    plist.insert(\"CFBundlePackageType\".into(), \"APPL\".into());\n    plist.insert(\n        \"CFBundleShortVersionString\".into(),\n        config.version.clone().into(),\n    );\n    plist.insert(\"CFBundleVersion\".into(), build_number.into());\n    plist.insert(\"CSResourcesFileMapped\".into(), true.into());\n    if let Some(category) = &config.category {\n        plist.insert(\n            \"LSApplicationCategoryType\".into(),\n            category.macos_application_category_type().into(),\n        );\n    }\n    if let Some(version) = config\n        .macos()\n        .and_then(|macos| macos.minimum_system_version.as_deref())\n    {\n        plist.insert(\"LSMinimumSystemVersion\".into(), version.into());\n    }\n\n    if let Some(associations) = &config.file_associations {\n        plist.insert(\n            \"CFBundleDocumentTypes\".into(),\n            plist::Value::Array(\n                associations\n                    .iter()\n                    .map(|association| {\n                        let mut dict = plist::Dictionary::new();\n                        dict.insert(\n                            \"CFBundleTypeExtensions\".into(),\n                            plist::Value::Array(\n                                association\n                                    .extensions\n                                    .iter()\n                                    .map(|ext| ext.to_string().into())\n                                    .collect(),\n                            ),\n                        );\n                        dict.insert(\n                            \"CFBundleTypeName\".into(),\n                            association\n                                .name\n                                .as_ref()\n                                .unwrap_or(&association.extensions[0])\n                                .to_string()\n                                .into(),\n                        );\n                        dict.insert(\n                            \"CFBundleTypeRole\".into(),\n                            association.role.to_string().into(),\n                        );\n                        plist::Value::Dictionary(dict)\n                    })\n                    .collect(),\n            ),\n        );\n    }\n\n    if let Some(protocols) = &config.deep_link_protocols {\n        plist.insert(\n            \"CFBundleURLTypes\".into(),\n            plist::Value::Array(\n                protocols\n                    .iter()\n                    .map(|protocol| {\n                        let mut dict = plist::Dictionary::new();\n                        dict.insert(\n                            \"CFBundleURLSchemes\".into(),\n                            plist::Value::Array(\n                                protocol\n                                    .schemes\n                                    .iter()\n                                    .map(|s| s.to_string().into())\n                                    .collect(),\n                            ),\n                        );\n                        dict.insert(\n                            \"CFBundleURLName\".into(),\n                            protocol\n                                .name\n                                .clone()\n                                .unwrap_or(format!(\n                                    \"{} {}\",\n                                    config.identifier(),\n                                    protocol.schemes[0]\n                                ))\n                                .into(),\n                        );\n                        dict.insert(\"CFBundleTypeRole\".into(), protocol.role.to_string().into());\n                        plist::Value::Dictionary(dict)\n                    })\n                    .collect(),\n            ),\n        );\n    }\n\n    plist.insert(\"LSRequiresCarbon\".into(), true.into());\n    plist.insert(\"NSHighResolutionCapable\".into(), true.into());\n\n    if let Some(macos_config) = config.macos() {\n        if macos_config.background_app {\n            plist.insert(\"LSUIElement\".into(), true.into());\n        }\n    }\n\n    if let Some(copyright) = &config.copyright {\n        plist.insert(\"NSHumanReadableCopyright\".into(), copyright.clone().into());\n    }\n\n    if let Some(exception_domain) = config\n        .macos()\n        .and_then(|macos| macos.exception_domain.clone())\n    {\n        let mut security = plist::Dictionary::new();\n        let mut domain = plist::Dictionary::new();\n        domain.insert(\"NSExceptionAllowsInsecureHTTPLoads\".into(), true.into());\n        domain.insert(\"NSIncludesSubdomains\".into(), true.into());\n\n        let mut exception_domains = plist::Dictionary::new();\n        exception_domains.insert(exception_domain, domain.into());\n        security.insert(\"NSExceptionDomains\".into(), exception_domains.into());\n        plist.insert(\"NSAppTransportSecurity\".into(), security.into());\n    }\n\n    if let Some(user_plist_path) = config\n        .macos()\n        .and_then(|macos| macos.info_plist_path.as_ref())\n    {\n        let user_plist = plist::Value::from_file(user_plist_path)?;\n        if let Some(dict) = user_plist.into_dictionary() {\n            for (key, value) in dict {\n                plist.insert(key, value);\n            }\n        }\n    }\n\n    plist::Value::Dictionary(plist).to_file_xml(contents_directory.join(\"Info.plist\"))?;\n\n    Ok(())\n}\n\n#[tracing::instrument(level = \"trace\")]\nfn copy_dir(from: &Path, to: &Path) -> crate::Result<()> {\n    if !from.exists() {\n        return Err(Error::DoesNotExist(from.to_path_buf()));\n    }\n    if !from.is_dir() {\n        return Err(Error::IsNotDirectory(from.to_path_buf()));\n    }\n    if to.exists() {\n        return Err(Error::AlreadyExists(to.to_path_buf()));\n    }\n\n    let parent = to\n        .parent()\n        .ok_or_else(|| Error::ParentDirNotFound(to.to_path_buf()))?;\n    fs::create_dir_all(parent).map_err(|e| Error::IoWithPath(parent.to_path_buf(), e))?;\n    for entry in walkdir::WalkDir::new(from) {\n        let entry = entry?;\n        let path = entry.path();\n        debug_assert!(path.starts_with(from));\n        let rel_path = path.strip_prefix(from)?;\n        let dest = to.join(rel_path);\n        if entry.file_type().is_symlink() {\n            let target =\n                fs::read_link(path).map_err(|e| Error::IoWithPath(path.to_path_buf(), e))?;\n\n            #[cfg(unix)]\n            std::os::unix::fs::symlink(&target, &dest)\n                .map_err(|e| Error::Symlink(target, dest, e))?;\n\n            #[cfg(windows)]\n            {\n                if entry.file_type().is_file() {\n                    std::os::windows::fs::symlink_file(&target, &dest)\n                        .map_err(|e| Error::Symlink(target, dest, e))?;\n                } else {\n                    std::os::windows::fs::symlink_dir(&target, &dest)\n                        .map_err(|e| Error::Symlink(target, dest, e))?;\n                }\n            }\n        } else if entry.file_type().is_dir() {\n            fs::create_dir(&dest).map_err(|e| Error::IoWithPath(dest, e))?\n        } else {\n            fs::copy(path, &dest).map_err(|e| Error::CopyFile(path.to_path_buf(), dest, e))?;\n        }\n    }\n    Ok(())\n}\n\n// Copies the framework under `{src_dir}/{framework}.framework` to `{dest_dir}/{framework}.framework`.\n#[tracing::instrument(level = \"trace\")]\nfn copy_framework_from(dest_dir: &Path, framework: &str, src_dir: &Path) -> crate::Result<bool> {\n    let src_name = format!(\"{framework}.framework\");\n    let src_path = src_dir.join(&src_name);\n    if src_path.exists() {\n        copy_dir(&src_path, &dest_dir.join(&src_name))?;\n        Ok(true)\n    } else {\n        Ok(false)\n    }\n}\n\n// Copies the macOS application bundle frameworks to the .app\n#[tracing::instrument(level = \"trace\", skip(config))]\nfn copy_frameworks_to_bundle(\n    contents_directory: &Path,\n    config: &Config,\n) -> crate::Result<Vec<PathBuf>> {\n    let mut paths = Vec::new();\n\n    if let Some(frameworks) = config.macos().and_then(|m| m.frameworks.as_ref()) {\n        let dest_dir = contents_directory.join(\"Frameworks\");\n        fs::create_dir_all(contents_directory)?;\n\n        for framework in frameworks {\n            if framework.ends_with(\".framework\") || framework.ends_with(\".app\") {\n                let src_path = PathBuf::from(framework);\n                let src_name = src_path\n                    .file_name()\n                    .ok_or_else(|| Error::FailedToExtractFilename(src_path.clone()))?;\n                let dest_path = dest_dir.join(src_name);\n                copy_dir(&src_path, &dest_path)?;\n                paths.push(dest_path);\n                continue;\n            } else if framework.ends_with(\".dylib\") {\n                let src_path = PathBuf::from(&framework);\n                if !src_path.exists() {\n                    return Err(Error::FrameworkNotFound(framework.to_string()));\n                }\n                let src_name = src_path\n                    .file_name()\n                    .ok_or_else(|| Error::FailedToExtractFilename(src_path.clone()))?;\n                fs::create_dir_all(&dest_dir)?;\n                let dest_path = dest_dir.join(src_name);\n                fs::copy(&src_path, &dest_path)\n                    .map_err(|e| Error::CopyFile(src_path.clone(), dest_path.clone(), e))?;\n                paths.push(dest_path);\n                continue;\n            } else if framework.contains('/') {\n                return Err(Error::InvalidFramework {\n                    framework: framework.to_string(),\n                    reason: \"framework extension should be either .framework, .dylib or .app\",\n                });\n            }\n            if let Some(home_dir) = dirs::home_dir() {\n                if copy_framework_from(&dest_dir, framework, &home_dir.join(\"Library/Frameworks/\"))?\n                {\n                    continue;\n                }\n            }\n            if copy_framework_from(&dest_dir, framework, &PathBuf::from(\"/Library/Frameworks/\"))?\n                || copy_framework_from(\n                    &dest_dir,\n                    framework,\n                    &PathBuf::from(\"/Network/Library/Frameworks/\"),\n                )?\n            {\n                continue;\n            }\n\n            return Err(Error::FrameworkNotFound(framework.to_string()));\n        }\n    }\n\n    Ok(paths)\n}\n\n#[cfg(target_os = \"macos\")]\nfn remove_extra_attr(app_bundle_path: &Path) -> crate::Result<()> {\n    std::process::Command::new(\"xattr\")\n        .arg(\"-cr\")\n        .arg(app_bundle_path)\n        .output_ok()\n        .map(|_| ())\n        .map_err(crate::Error::FailedToRemoveExtendedAttributes)\n}\n\n// Copies the embedded.provisionprofile file to the Contents directory, if needed.\nfn copy_embedded_provisionprofile_file(\n    contents_directory: &Path,\n    config: &Config,\n) -> crate::Result<()> {\n    if let Some(embedded_provisionprofile_file) = config\n        .macos()\n        .and_then(|m| m.embedded_provisionprofile_path.as_ref())\n    {\n        if !embedded_provisionprofile_file.exists() {\n            return Err(crate::Error::EmbeddedProvisionprofileFileNotFound(\n                embedded_provisionprofile_file.to_path_buf(),\n            ));\n        }\n\n        fs::copy(\n            embedded_provisionprofile_file,\n            contents_directory.join(\"embedded.provisionprofile\"),\n        )\n        .map_err(|e| {\n            crate::Error::FailedToCopyEmbeddedProvisionprofile(\n                embedded_provisionprofile_file.to_path_buf(),\n                e,\n            )\n        })?;\n    }\n    Ok(())\n}\n\n// Copies app structures that may need to be embedded inside this app.\n#[tracing::instrument(level = \"trace\", skip(config))]\nfn copy_embedded_apps(contents_directory: &Path, config: &Config) -> crate::Result<Vec<PathBuf>> {\n    let mut paths = Vec::new();\n\n    if let Some(embedded_apps) = config.macos().and_then(|m| m.embedded_apps.as_ref()) {\n        let dest_dir = contents_directory.join(\"MacOS\");\n\n        for embedded_app in embedded_apps {\n            let src_path = PathBuf::from(embedded_app);\n            let src_name = src_path\n                .file_name()\n                .ok_or_else(|| Error::FailedToExtractFilename(src_path.clone()))?;\n            let dest_path = dest_dir.join(src_name);\n            copy_dir(&src_path, &dest_path)?;\n\n            tracing::debug!(\"Copied embedded app: {:?}\", dest_path);\n            paths.push(dest_path);\n        }\n    }\n    Ok(paths)\n}\n"
  },
  {
    "path": "crates/packager/src/package/appimage/appimage",
    "content": "#!/usr/bin/env bash\n\n# Copyright 2019-2023 Tauri Programme within The Commons Conservancy\n# Copyright 2023-2023 CrabNebula Ltd.\n# SPDX-License-Identifier: Apache-2.0\n# SPDX-License-Identifier: MIT\n\nset -euxo pipefail\n\nexport ARCH={{arch}}\n\nmkdir -p \"{{app_name}}.AppDir\"\ncp -r ../appimage_deb/data/* \"{{app_name}}.AppDir\"\n\ncd \"{{app_name}}.AppDir\"\nmkdir -p \"usr/bin\"\nmkdir -p \"usr/lib\"\nmkdir -p \"usr/lib64\"\n\n# Copy libs. Follow symlinks in case `/usr/lib64` is a symlink to `/usr/lib`\n{{#each libs}}\nfind -L /usr/lib* -name {{this}} -exec mkdir -p \"$(dirname '{}')\" \\; -exec cp --parents '{}' \".\" \\; || true\n{{/each}}\n\n# Copy bins.\n{{#each bins}}\ncp {{this}} usr/bin\n{{/each}}\n\n# We need AppRun to be installed as {{app_name}}.AppDir/AppRun.\n# Otherwise the linuxdeploy scripts will default to symlinking our main bin instead and will crash on trying to launch.\ncp \"{{packager_tools_path}}/AppRun-${ARCH}\" AppRun\n\ncp \"{{icon_path}}\" .DirIcon\nln -sf \"{{icon_path}}\" \"{{app_name}}.png\"\n\nln -sf \"usr/share/applications/{{app_name}}.desktop\" \"{{app_name}}.desktop\"\n\ncd ..\n\n# modify the linux deploy appimage ELF header so that binfmt no longer identifies it as an appimage\n# and so appimagelauncher doesn't inject itself and the binary runs directly\ndd if=/dev/zero bs=1 count=3 seek=8 conv=notrunc of=\"{{packager_tools_path}}/linuxdeploy-{{linuxdeploy_arch}}.AppImage\"\n\nOUTPUT=\"{{appimage_path}}\" \"{{packager_tools_path}}/linuxdeploy-{{linuxdeploy_arch}}.AppImage\" --appimage-extract-and-run --appdir \"{{app_name}}.AppDir\" {{linuxdeploy_plugins}} {{excluded_libs}} --output appimage\n"
  },
  {
    "path": "crates/packager/src/package/appimage/mod.rs",
    "content": "// Copyright 2019-2023 Tauri Programme within The Commons Conservancy\n// Copyright 2023-2023 CrabNebula Ltd.\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{\n    collections::BTreeMap,\n    fs,\n    os::unix::fs::PermissionsExt,\n    path::{Path, PathBuf},\n    process::Command,\n};\n\nuse handlebars::{to_json, Handlebars};\n\nuse super::{deb, Context};\nuse crate::{shell::CommandExt, util, Error};\n\n#[tracing::instrument(level = \"trace\", skip(ctx))]\nfn donwload_dependencies(\n    ctx: &Context,\n    appimage_tools_path: &Path,\n    arch: &str,\n    linuxdeploy_arch: &str,\n) -> crate::Result<()> {\n    let internal_deps = vec![\n        (\n            format!(\"AppRun-{arch}\"),\n            format!(\"https://github.com/tauri-apps/binary-releases/releases/download/apprun-old/AppRun-{arch}\")\n        ),\n        (\n            format!(\"linuxdeploy-{linuxdeploy_arch}.AppImage\"),\n            format!(\"https://github.com/tauri-apps/binary-releases/releases/download/linuxdeploy/linuxdeploy-{linuxdeploy_arch}.AppImage\")\n        ),\n        // This path is incompatible with cross-platform compilation but linuxdeploy doens't support that anyway.\n        (\n            \"linuxdeploy-plugin-appimage.AppImage\".to_string(),\n            format!(\"https://github.com/linuxdeploy/linuxdeploy-plugin-appimage/releases/download/continuous/linuxdeploy-plugin-appimage-{arch}.AppImage\")\n        ),\n    ];\n\n    let user_deps = ctx\n        .config\n        .appimage()\n        .and_then(|a| a.linuxdeploy_plugins.clone())\n        .unwrap_or_default()\n        .into_iter()\n        .map(|mut p| {\n            p.0 = format!(\"linuxdeploy-plugin-{}.sh\", p.0);\n            p\n        })\n        .collect();\n\n    for (path, url) in [internal_deps, user_deps].concat() {\n        let path = appimage_tools_path.join(path);\n        if !path.exists() {\n            let data = util::download(&url)?;\n            tracing::debug!(\n                \"Writing {} and setting its permissions to 764\",\n                path.display()\n            );\n            fs::write(&path, data).map_err(|e| Error::IoWithPath(path.clone(), e))?;\n            fs::set_permissions(&path, fs::Permissions::from_mode(0o764))\n                .map_err(|e| Error::IoWithPath(path, e))?;\n        }\n    }\n\n    Ok(())\n}\n\n#[tracing::instrument(level = \"trace\", skip(ctx))]\npub(crate) fn package(ctx: &Context) -> crate::Result<Vec<PathBuf>> {\n    let Context {\n        config,\n        intermediates_path,\n        tools_path,\n        ..\n    } = ctx;\n\n    let mut config = config.clone();\n    let main_binary_name = config.main_binary_name()?;\n\n    // if binary file name contains spaces, we must change it to kebab-case\n    if main_binary_name.contains(' ') {\n        let main_binary = config.main_binary_mut()?;\n\n        let main_binary_name_kebab = heck::AsKebabCase(main_binary_name).to_string();\n        let new_path = intermediates_path.join(&main_binary_name_kebab);\n        fs::copy(&main_binary.path, &new_path)?;\n\n        main_binary.path = new_path;\n    }\n\n    // generate the deb binary name\n    let (arch, linuxdeploy_arch) = match config.target_arch()? {\n        \"x86\" => (\"i686\", \"i386\"),\n        \"arm\" => (\"armhf\", \"arm\"),\n        other => (other, other),\n    };\n\n    let appimage_tools_path = tools_path.join(\"AppImage\");\n    fs::create_dir_all(&appimage_tools_path)\n        .map_err(|e| Error::IoWithPath(appimage_tools_path.clone(), e))?;\n\n    donwload_dependencies(ctx, &appimage_tools_path, arch, linuxdeploy_arch)?;\n\n    let appimage_deb_data_dir = intermediates_path.join(\"appimage_deb\").join(\"data\");\n    let intermediates_path = intermediates_path.join(\"appimage\");\n\n    // generate deb_folder structure\n    tracing::debug!(\"Generating data\");\n    let icons = deb::generate_data(&config, &appimage_deb_data_dir)?;\n    tracing::debug!(\"Copying files specified in `appimage.files`\");\n    if let Some(files) = config.appimage().and_then(|d| d.files.as_ref()) {\n        deb::copy_custom_files(files, &appimage_deb_data_dir)?;\n    }\n    let icons: Vec<deb::DebIcon> = icons.into_iter().collect();\n\n    let main_binary_name = config.main_binary_name()?;\n    let upcase_app_name = main_binary_name.to_uppercase();\n    let app_dir_path = intermediates_path.join(format!(\"{}.AppDir\", &main_binary_name));\n    let appimage_filename = format!(\"{}_{}_{}.AppImage\", main_binary_name, config.version, arch);\n    let appimage_path = config.out_dir().join(&appimage_filename);\n\n    fs::create_dir_all(&app_dir_path).map_err(|e| Error::IoWithPath(app_dir_path.clone(), e))?;\n\n    // setup data to insert into shell script\n    let mut sh_map = BTreeMap::new();\n    sh_map.insert(\"arch\", to_json(arch));\n    sh_map.insert(\"linuxdeploy_arch\", to_json(linuxdeploy_arch));\n    sh_map.insert(\"app_name\", to_json(main_binary_name));\n    sh_map.insert(\"app_name_uppercase\", to_json(upcase_app_name));\n    sh_map.insert(\"appimage_path\", to_json(&appimage_path));\n    sh_map.insert(\n        \"packager_tools_path\",\n        to_json(appimage_tools_path.display().to_string()),\n    );\n\n    let libs = config\n        .appimage()\n        .and_then(|c| c.libs.clone())\n        .unwrap_or_default();\n    sh_map.insert(\"libs\", to_json(libs));\n\n    let bins = config\n        .appimage()\n        .and_then(|c| c.bins.clone())\n        .unwrap_or_default();\n    sh_map.insert(\"bins\", to_json(bins));\n\n    let linuxdeploy_plugins = config\n        .appimage()\n        .and_then(|a| a.linuxdeploy_plugins.clone())\n        .unwrap_or_default()\n        .into_keys()\n        .map(|name| format!(\"--plugin {name}\"))\n        .collect::<Vec<_>>()\n        .join(\" \");\n    sh_map.insert(\"linuxdeploy_plugins\", to_json(linuxdeploy_plugins));\n\n    let excluded_libraries = config\n        .appimage()\n        .and_then(|a| a.excluded_libs.clone())\n        .unwrap_or_default()\n        .into_iter()\n        .map(|library| format!(\"--exclude-library {library}\"))\n        .collect::<Vec<_>>()\n        .join(\" \");\n    sh_map.insert(\"excluded_libs\", to_json(excluded_libraries));\n\n    let larger_icon = icons\n        .iter()\n        .filter(|i| i.width == i.height)\n        .max_by_key(|i| i.width)\n        .ok_or(crate::Error::AppImageSquareIcon)?;\n    let larger_icon_path = larger_icon\n        .path\n        .strip_prefix(appimage_deb_data_dir)\n        .unwrap()\n        .to_string_lossy()\n        .to_string();\n    sh_map.insert(\"icon_path\", to_json(larger_icon_path));\n\n    // initialize shell script template.\n    let mut handlebars = Handlebars::new();\n    handlebars.register_escape_fn(handlebars::no_escape);\n    handlebars\n        .register_template_string(\"appimage\", include_str!(\"appimage\"))\n        .map_err(Box::new)?;\n    let template = handlebars.render(\"appimage\", &sh_map)?;\n\n    let sh_file = intermediates_path.join(\"build_appimage.sh\");\n    tracing::debug!(\n        \"Writing {} and setting its permissions to 764\",\n        sh_file.display()\n    );\n    fs::write(&sh_file, template).map_err(|e| Error::IoWithPath(sh_file.clone(), e))?;\n    fs::set_permissions(&sh_file, fs::Permissions::from_mode(0o764))\n        .map_err(|e| Error::IoWithPath(sh_file.clone(), e))?;\n\n    tracing::info!(\n        \"Packaging {} ({})\",\n        appimage_filename,\n        appimage_path.display()\n    );\n\n    // execute the shell script to build the appimage.\n    Command::new(&sh_file)\n        .current_dir(intermediates_path)\n        .output_ok()\n        .map_err(crate::Error::AppImageScriptFailed)?;\n\n    Ok(vec![appimage_path])\n}\n"
  },
  {
    "path": "crates/packager/src/package/context.rs",
    "content": "// Copyright 2023-2023 CrabNebula Ltd.\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{fs, path::PathBuf};\n\nuse crate::{util, Config};\n\n/// The packaging context info\n#[derive(Debug)]\npub struct Context {\n    /// The config for the app we are packaging\n    pub config: Config,\n    /// The intermediates path, which is `<out-dir>/.cargo-packager`\n    pub intermediates_path: PathBuf,\n    /// The global path which we store tools used by cargo-packager and usually is\n    /// `<cache-dir>/.cargo-packager`\n    pub tools_path: PathBuf,\n}\n\nimpl Context {\n    pub fn new(config: &Config) -> crate::Result<Self> {\n        let tools_path = dirs::cache_dir()\n            .unwrap_or_else(|| config.out_dir())\n            .join(\".cargo-packager\");\n        if !tools_path.exists() {\n            fs::create_dir_all(&tools_path)?;\n        }\n\n        let intermediates_path = config.out_dir().join(\".cargo-packager\");\n        util::create_clean_dir(&intermediates_path)?;\n\n        Ok(Self {\n            config: config.clone(),\n            tools_path,\n            intermediates_path,\n        })\n    }\n}\n"
  },
  {
    "path": "crates/packager/src/package/deb/main.desktop",
    "content": "[Desktop Entry]\nCategories={{categories}}\n{{#if comment}}\nComment={{comment}}\n{{/if}}\nExec={{exec}} {{exec_arg}}\nIcon={{icon}}\nName={{name}}\nTerminal=false\nType=Application\n{{#if mime_type}}\nMimeType={{mime_type}}\n{{/if}}\n"
  },
  {
    "path": "crates/packager/src/package/deb/mod.rs",
    "content": "// Copyright 2016-2019 Cargo-Bundle developers <https://github.com/burtonageo/cargo-bundle>\n// Copyright 2019-2023 Tauri Programme within The Commons Conservancy\n// Copyright 2023-2023 CrabNebula Ltd.\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{\n    collections::{BTreeSet, HashMap},\n    ffi::OsStr,\n    fs::{self, File},\n    io::{BufReader, Write},\n    path::{Path, PathBuf},\n};\n\nuse flate2::{write::GzEncoder, Compression};\nuse handlebars::Handlebars;\nuse heck::AsKebabCase;\nuse image::{codecs::png::PngDecoder, ImageDecoder};\nuse relative_path::PathExt;\nuse serde::Serialize;\nuse tar::HeaderMode;\nuse walkdir::WalkDir;\n\nuse super::Context;\nuse crate::{\n    config::Config,\n    util::{self, PathExt as UtilPathExt},\n    Error,\n};\n\n#[derive(PartialEq, Eq, PartialOrd, Ord)]\npub struct DebIcon {\n    pub width: u32,\n    pub height: u32,\n    pub is_high_density: bool,\n    pub path: PathBuf,\n}\n\n/// Generate the icon files and store them under the `data_dir`.\n#[tracing::instrument(level = \"trace\", skip(config))]\nfn generate_icon_files(config: &Config, data_dir: &Path) -> crate::Result<BTreeSet<DebIcon>> {\n    let hicolor_dir = data_dir.join(\"usr/share/icons/hicolor\");\n    let main_binary_name = config.main_binary_name()?;\n    let get_dest_path = |width: u32, height: u32, is_high_density: bool| {\n        hicolor_dir.join(format!(\n            \"{}x{}{}/apps/{}.png\",\n            width,\n            height,\n            if is_high_density { \"@2\" } else { \"\" },\n            main_binary_name\n        ))\n    };\n    let mut icons_set = BTreeSet::new();\n    if let Some(icons) = config.icons()? {\n        for icon_path in icons {\n            if icon_path.extension() != Some(OsStr::new(\"png\")) {\n                continue;\n            }\n            // Put file in scope so that it's closed when copying it\n            let deb_icon = {\n                let file =\n                    File::open(&icon_path).map_err(|e| Error::IoWithPath(icon_path.clone(), e))?;\n                let file = BufReader::new(file);\n                let decoder = PngDecoder::new(file)?;\n                let width = decoder.dimensions().0;\n                let height = decoder.dimensions().1;\n                let is_high_density = util::is_retina(&icon_path);\n                let dest_path = get_dest_path(width, height, is_high_density);\n                DebIcon {\n                    width,\n                    height,\n                    is_high_density,\n                    path: dest_path,\n                }\n            };\n            if !icons_set.contains(&deb_icon) {\n                let parent = deb_icon\n                    .path\n                    .parent()\n                    .ok_or_else(|| crate::Error::ParentDirNotFound(deb_icon.path.clone()))?;\n                fs::create_dir_all(parent)\n                    .map_err(|e| Error::IoWithPath(parent.to_path_buf(), e))?;\n                fs::copy(&icon_path, &deb_icon.path)\n                    .map_err(|e| Error::CopyFile(icon_path.clone(), deb_icon.path.clone(), e))?;\n                icons_set.insert(deb_icon);\n            }\n        }\n    }\n    Ok(icons_set)\n}\n\n/// Generate the application desktop file and store it under the `data_dir`.\n#[tracing::instrument(level = \"trace\", skip(config))]\nfn generate_desktop_file(config: &Config, data_dir: &Path) -> crate::Result<()> {\n    let bin_name = config.main_binary_name()?;\n    let desktop_file_name = format!(\"{bin_name}.desktop\");\n    let desktop_file_path = data_dir\n        .join(\"usr/share/applications\")\n        .join(desktop_file_name);\n\n    // For more information about the format of this file, see:\n    // <https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html>\n    let file = &mut util::create_file(&desktop_file_path)?;\n\n    let mut handlebars = Handlebars::new();\n    handlebars.register_escape_fn(handlebars::no_escape);\n    if let Some(template) = config.deb().and_then(|d| d.desktop_template.as_ref()) {\n        handlebars\n            .register_template_string(\"main.desktop\", fs::read_to_string(template)?)\n            .map_err(Box::new)?;\n    } else {\n        handlebars\n            .register_template_string(\"main.desktop\", include_str!(\"./main.desktop\"))\n            .map_err(Box::new)?;\n    }\n\n    #[derive(Serialize)]\n    struct DesktopTemplateParams<'a> {\n        categories: &'a str,\n        comment: Option<&'a str>,\n        exec: &'a str,\n        exec_arg: Option<&'a str>,\n        icon: &'a str,\n        name: &'a str,\n        mime_type: Option<String>,\n    }\n\n    // Set the argument code at the end of the `Exec` key.\n    // See the docs for `DebianConfig::desktop_template` for more details.\n    let mut exec_arg = None;\n\n    let mut mime_type: Vec<String> = Vec::new();\n\n    if let Some(associations) = &config.file_associations {\n        if !associations.is_empty() {\n            exec_arg = Some(\"%F\");\n        }\n        mime_type.extend(\n            associations\n                .iter()\n                .filter_map(|association| association.mime_type.clone()),\n        );\n    }\n\n    if let Some(protocols) = &config.deep_link_protocols {\n        if !protocols.is_empty() {\n            // Use \"%U\" even if file associations were already provided,\n            // as it can also accommodate file names in addition to URLs.\n            exec_arg = Some(\"%U\");\n        }\n        mime_type.extend(\n            protocols\n                .iter()\n                .flat_map(|protocol| &protocol.schemes)\n                .map(|s| format!(\"x-scheme-handler/{s}\")),\n        );\n    }\n\n    let mime_type = (!mime_type.is_empty()).then(|| mime_type.join(\";\"));\n\n    let bin_name_exec = if bin_name.contains(' ') {\n        format!(\"\\\"{bin_name}\\\"\")\n    } else {\n        bin_name.to_string()\n    };\n\n    handlebars.render_to_write(\n        \"main.desktop\",\n        &DesktopTemplateParams {\n            categories: config\n                .category\n                .map(|category| category.gnome_desktop_categories())\n                .unwrap_or(\"\"),\n            comment: config.description.as_deref(),\n            exec: &bin_name_exec,\n            exec_arg,\n            icon: &bin_name,\n            name: config.product_name.as_str(),\n            mime_type,\n        },\n        file,\n    )?;\n\n    Ok(())\n}\n\n#[tracing::instrument(level = \"trace\", skip(config))]\npub fn generate_data(config: &Config, data_dir: &Path) -> crate::Result<BTreeSet<DebIcon>> {\n    let bin_dir = data_dir.join(\"usr/bin\");\n\n    tracing::debug!(\"Copying binaries\");\n    fs::create_dir_all(&bin_dir).map_err(|e| Error::IoWithPath(bin_dir.clone(), e))?;\n\n    for bin in config.binaries.iter() {\n        let bin_path = config.binary_path(bin);\n        let bin_out_path = bin_dir.join(bin.path.file_name().unwrap());\n        fs::copy(&bin_path, &bin_out_path)\n            .map_err(|e| Error::CopyFile(bin_path.clone(), bin_out_path.clone(), e))?;\n    }\n\n    tracing::debug!(\"Copying resources\");\n    let resource_dir = data_dir.join(\"usr/lib\").join(config.main_binary_name()?);\n    config.copy_resources(&resource_dir)?;\n\n    tracing::debug!(\"Copying external binaries\");\n    config.copy_external_binaries(&bin_dir)?;\n\n    tracing::debug!(\"Generating icons\");\n    let icons = generate_icon_files(config, data_dir)?;\n\n    let generate_desktop_entry = config\n        .linux()\n        .is_none_or(|linux| linux.generate_desktop_entry);\n\n    if generate_desktop_entry {\n        tracing::debug!(\"Generating desktop file\");\n        generate_desktop_file(config, data_dir)?;\n    }\n\n    Ok(icons)\n}\n\npub fn get_size<P: AsRef<Path>>(path: P) -> crate::Result<u64> {\n    let mut result = 0;\n    let path = path.as_ref();\n\n    if path.is_dir() {\n        for entry in fs::read_dir(path).map_err(|e| Error::IoWithPath(path.to_path_buf(), e))? {\n            let path = entry?.path();\n            if path.is_file() {\n                let metadata = path.metadata().map_err(|e| Error::IoWithPath(path, e))?;\n                result += metadata.len();\n            } else {\n                result += get_size(path)?;\n            }\n        }\n    } else {\n        let metadata = path\n            .metadata()\n            .map_err(|e| Error::IoWithPath(path.to_path_buf(), e))?;\n        result += metadata.len();\n    }\n\n    Ok(result)\n}\n\n/// Copies user-defined files to the deb package.\n#[tracing::instrument(level = \"trace\")]\npub fn copy_custom_files(files: &HashMap<String, String>, data_dir: &Path) -> crate::Result<()> {\n    for (src, target) in files.iter() {\n        let src = Path::new(src);\n        let src = src\n            .canonicalize()\n            .map_err(|e| Error::IoWithPath(src.to_path_buf(), e))?;\n        let target = Path::new(target);\n        let target = if target.is_absolute() {\n            target.strip_prefix(\"/\").unwrap()\n        } else {\n            target\n        };\n\n        if src.is_file() {\n            let dest = data_dir.join(target);\n            let parent = dest\n                .parent()\n                .ok_or_else(|| crate::Error::ParentDirNotFound(dest.clone()))?;\n            fs::create_dir_all(parent).map_err(|e| Error::IoWithPath(parent.to_path_buf(), e))?;\n            fs::copy(&src, &dest).map_err(|e| Error::CopyFile(src, dest, e))?;\n        } else if src.is_dir() {\n            let dest_dir = data_dir.join(target);\n\n            for entry in walkdir::WalkDir::new(&src) {\n                let entry = entry?;\n                let path = entry.path();\n                if path.is_file() {\n                    let relative = path.relative_to(&src)?.to_path(\"\");\n                    let dest = dest_dir.join(relative);\n                    let parent = dest\n                        .parent()\n                        .ok_or_else(|| crate::Error::ParentDirNotFound(dest.clone()))?;\n                    fs::create_dir_all(parent)\n                        .map_err(|e| Error::IoWithPath(parent.to_path_buf(), e))?;\n                    fs::copy(path, &dest)\n                        .map_err(|e| Error::CopyFile(src.clone(), dest.clone(), e))?;\n                }\n            }\n        }\n    }\n\n    Ok(())\n}\n\n/// Generates the debian control file and stores it under the `control_dir`.\n#[tracing::instrument(level = \"trace\", skip(config))]\nfn generate_control_file(\n    config: &Config,\n    arch: &str,\n    control_dir: &Path,\n    data_dir: &Path,\n) -> crate::Result<()> {\n    // For more information about the format of this file, see\n    // https://www.debian.org/doc/debian-policy/ch-controlfields.html\n    let dest_path = control_dir.join(\"control\");\n    let mut file = util::create_file(&dest_path)?;\n\n    let pkg_name = config\n        .deb()\n        .and_then(|deb| deb.package_name.clone())\n        .unwrap_or_else(|| AsKebabCase(&config.product_name).to_string());\n\n    writeln!(file, \"Package: {pkg_name}\")?;\n    writeln!(file, \"Version: {}\", &config.version)?;\n    writeln!(file, \"Architecture: {arch}\")?;\n    // Installed-Size must be divided by 1024, see https://www.debian.org/doc/debian-policy/ch-controlfields.html#installed-size\n    writeln!(file, \"Installed-Size: {}\", get_size(data_dir)? / 1024)?;\n    if let Some(authors) = &config.authors {\n        writeln!(file, \"Maintainer: {}\", authors.join(\", \"))?;\n    }\n    if let Some(section) = config.deb().and_then(|d| d.section.as_ref()) {\n        writeln!(file, \"Section: {section}\")?;\n    }\n\n    if let Some(priority) = config.deb().and_then(|d| d.priority.as_ref()) {\n        writeln!(file, \"Priority: {priority}\")?;\n    } else {\n        writeln!(file, \"Priority: optional\")?;\n    }\n\n    if let Some(homepage) = &config.homepage {\n        writeln!(file, \"Homepage: {homepage}\")?;\n    }\n    if let Some(depends) = config.deb().and_then(|d| d.depends.as_ref()) {\n        let dependencies = depends.to_list()?;\n        if !dependencies.is_empty() {\n            writeln!(file, \"Depends: {}\", dependencies.join(\", \"))?;\n        }\n    }\n\n    writeln!(\n        file,\n        \"Description: {}\",\n        config.description.as_deref().unwrap_or(\"(none)\")\n    )?;\n    for line in config\n        .long_description\n        .as_deref()\n        .unwrap_or(\"(none)\")\n        .lines()\n    {\n        let line = line.trim();\n        if line.is_empty() {\n            writeln!(file, \" .\")?;\n        } else {\n            writeln!(file, \" {line}\")?;\n        }\n    }\n    file.flush()?;\n    Ok(())\n}\n\n/// Creates an `md5sums` file in the `control_dir` containing the MD5 checksums\n/// for each file within the `data_dir`.\n#[tracing::instrument(level = \"trace\")]\nfn generate_md5sums(control_dir: &Path, data_dir: &Path) -> crate::Result<()> {\n    let md5sums_path = control_dir.join(\"md5sums\");\n    let mut md5sums_file = util::create_file(&md5sums_path)?;\n    for entry in WalkDir::new(data_dir) {\n        let entry = entry?;\n        let path = entry.path();\n        if path.is_dir() {\n            continue;\n        }\n        let mut file = File::open(path).map_err(|e| Error::IoWithPath(path.to_path_buf(), e))?;\n        let mut hash = md5::Context::new();\n        std::io::copy(&mut file, &mut hash)?;\n        for byte in hash.finalize().iter() {\n            write!(md5sums_file, \"{byte:02x}\")?;\n        }\n        let rel_path = path.strip_prefix(data_dir)?;\n        let path_str = rel_path.to_str().ok_or_else(|| {\n            let msg = format!(\"Non-UTF-8 path: {rel_path:?}\");\n            std::io::Error::new(std::io::ErrorKind::InvalidData, msg)\n        })?;\n        writeln!(md5sums_file, \"  {path_str}\")?;\n    }\n    Ok(())\n}\n\nfn create_tar_from_dir<P: AsRef<Path>, W: Write>(src_dir: P, dest_file: W) -> crate::Result<W> {\n    use std::os::unix::fs::MetadataExt;\n\n    let src_dir = src_dir.as_ref();\n    let mut tar_builder = tar::Builder::new(dest_file);\n    for entry in walkdir::WalkDir::new(src_dir) {\n        let entry = entry?;\n        let src_path = entry.path();\n        if src_path == src_dir {\n            continue;\n        }\n        let dest_path = src_path.strip_prefix(src_dir)?;\n        let stat =\n            fs::metadata(src_path).map_err(|e| Error::IoWithPath(src_path.to_path_buf(), e))?;\n        let mut header = tar::Header::new_gnu();\n        header.set_metadata_in_mode(&stat, HeaderMode::Deterministic);\n        header.set_mtime(stat.mtime() as u64);\n        if entry.file_type().is_dir() {\n            tar_builder.append_data(&mut header, dest_path, &mut std::io::empty())?;\n        } else {\n            let mut src_file =\n                File::open(src_path).map_err(|e| Error::IoWithPath(src_path.to_path_buf(), e))?;\n            tar_builder.append_data(&mut header, dest_path, &mut src_file)?;\n        }\n    }\n    tar_builder.into_inner().map_err(Into::into)\n}\n\n/// Creates a `.tar.gz` file from the given directory (placing the new file\n/// within the given directory's parent directory), then deletes the original\n/// directory and returns the path to the new file.\npub fn tar_and_gzip_dir<P: AsRef<Path>>(src_dir: P) -> crate::Result<PathBuf> {\n    let src_dir = src_dir.as_ref();\n    let dest_path = src_dir.with_additional_extension(\"tar.gz\");\n    let dest_file = util::create_file(&dest_path)?;\n    let gzip_encoder = GzEncoder::new(dest_file, Compression::default());\n    let gzip_encoder = create_tar_from_dir(src_dir, gzip_encoder)?;\n    let mut dest_file = gzip_encoder.finish()?;\n    dest_file.flush()?;\n    Ok(dest_path)\n}\n\n/// Creates an `ar` archive from the given source files and writes it to the\n/// given destination path.\nfn create_archive(srcs: Vec<PathBuf>, dest: &Path) -> crate::Result<()> {\n    let mut builder = ar::Builder::new(util::create_file(dest)?);\n    for path in &srcs {\n        builder.append_path(path)?;\n    }\n    builder.into_inner()?.flush()?;\n    Ok(())\n}\n\n#[tracing::instrument(level = \"trace\", skip(ctx))]\npub(crate) fn package(ctx: &Context) -> crate::Result<Vec<PathBuf>> {\n    let Context {\n        config,\n        intermediates_path,\n        ..\n    } = ctx;\n\n    let arch = match config.target_arch()? {\n        \"x86\" => \"i386\",\n        \"x86_64\" => \"amd64\",\n        \"arm\" => \"armhf\",\n        \"aarch64\" => \"arm64\",\n        other => other,\n    };\n\n    let intermediates_path = intermediates_path.join(\"deb\");\n    util::create_clean_dir(&intermediates_path)?;\n\n    let deb_base_name = format!(\"{}_{}_{}\", config.main_binary_name()?, config.version, arch);\n    let deb_name = format!(\"{deb_base_name}.deb\");\n\n    let deb_dir = intermediates_path.join(&deb_base_name);\n    let deb_path = config.out_dir().join(&deb_name);\n\n    tracing::info!(\"Packaging {} ({})\", deb_name, deb_path.display());\n\n    tracing::debug!(\"Generating data\");\n    let data_dir = deb_dir.join(\"data\");\n    let _ = generate_data(config, &data_dir)?;\n\n    tracing::debug!(\"Copying files specified in `deb.files`\");\n    if let Some(files) = config.deb().and_then(|d| d.files.as_ref()) {\n        copy_custom_files(files, &data_dir)?;\n    }\n\n    let control_dir = deb_dir.join(\"control\");\n    tracing::debug!(\"Generating control file\");\n    generate_control_file(config, arch, &control_dir, &data_dir)?;\n\n    tracing::debug!(\"Generating md5sums\");\n    generate_md5sums(&control_dir, &data_dir)?;\n\n    // Generate `debian-binary` file; see\n    // http://www.tldp.org/HOWTO/Debian-Binary-Package-Building-HOWTO/x60.html#AEN66\n    tracing::debug!(\"Creating debian-binary file\");\n    let debian_binary_path = deb_dir.join(\"debian-binary\");\n    let mut file = util::create_file(&debian_binary_path)?;\n    file.write_all(b\"2.0\\n\")?;\n    file.flush()?;\n\n    // Apply tar/gzip/ar to create the final package file.\n    tracing::debug!(\"Zipping control dir using tar and gzip\");\n    let control_tar_gz_path = tar_and_gzip_dir(control_dir)?;\n\n    tracing::debug!(\"Zipping data dir using tar and gzip\");\n    let data_tar_gz_path = tar_and_gzip_dir(data_dir)?;\n\n    tracing::debug!(\"Creating final archive: {}\", deb_path.display());\n    create_archive(\n        vec![debian_binary_path, control_tar_gz_path, data_tar_gz_path],\n        &deb_path,\n    )?;\n    Ok(vec![deb_path])\n}\n"
  },
  {
    "path": "crates/packager/src/package/dmg/eula-resources-template.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>LPic</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>Attributes</key>\n\t\t\t<string>0x0000</string>\n\t\t\t<key>Data</key>\n\t\t\t<data>\n\t\t\tAAAAAgAAAAAAAAAAAAQAAA==\n\t\t\t</data>\n\t\t\t<key>ID</key>\n\t\t\t<string>5000</string>\n\t\t\t<key>Name</key>\n\t\t\t<string></string>\n\t\t</dict>\n\t</array>\n\t<key>STR#</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>Attributes</key>\n\t\t\t<string>0x0000</string>\n\t\t\t<key>Data</key>\n\t\t\t<data>\n\t\t\tAAYNRW5nbGlzaCB0ZXN0MQVBZ3JlZQhEaXNhZ3JlZQVQcmludAdT\n\t\t\tYXZlLi4ueklmIHlvdSBhZ3JlZSB3aXRoIHRoZSB0ZXJtcyBvZiB0\n\t\t\taGlzIGxpY2Vuc2UsIGNsaWNrICJBZ3JlZSIgdG8gYWNjZXNzIHRo\n\t\t\tZSBzb2Z0d2FyZS4gIElmIHlvdSBkbyBub3QgYWdyZWUsIHByZXNz\n\t\t\tICJEaXNhZ3JlZS4i\n\t\t\t</data>\n\t\t\t<key>ID</key>\n\t\t\t<string>5000</string>\n\t\t\t<key>Name</key>\n\t\t\t<string>English buttons</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>Attributes</key>\n\t\t\t<string>0x0000</string>\n\t\t\t<key>Data</key>\n\t\t\t<data>\n\t\t\tAAYHRW5nbGlzaAVBZ3JlZQhEaXNhZ3JlZQVQcmludAdTYXZlLi4u\n\t\t\te0lmIHlvdSBhZ3JlZSB3aXRoIHRoZSB0ZXJtcyBvZiB0aGlzIGxp\n\t\t\tY2Vuc2UsIHByZXNzICJBZ3JlZSIgdG8gaW5zdGFsbCB0aGUgc29m\n\t\t\tdHdhcmUuICBJZiB5b3UgZG8gbm90IGFncmVlLCBwcmVzcyAiRGlz\n\t\t\tYWdyZWUiLg==\n\t\t\t</data>\n\t\t\t<key>ID</key>\n\t\t\t<string>5002</string>\n\t\t\t<key>Name</key>\n\t\t\t<string>English</string>\n\t\t</dict>\n\t</array>\n\t<key>${EULA_FORMAT}</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>Attributes</key>\n\t\t\t<string>0x0000</string>\n\t\t\t<key>Data</key>\n\t\t\t<data>\n${EULA_DATA}\n\t\t\t</data>\n\t\t\t<key>ID</key>\n\t\t\t<string>5000</string>\n\t\t\t<key>Name</key>\n\t\t\t<string>English</string>\n\t\t</dict>\n\t</array>\n\t<key>TMPL</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>Attributes</key>\n\t\t\t<string>0x0000</string>\n\t\t\t<key>Data</key>\n\t\t\t<data>\n\t\t\tE0RlZmF1bHQgTGFuZ3VhZ2UgSUREV1JEBUNvdW50T0NOVAQqKioq\n\t\t\tTFNUQwtzeXMgbGFuZyBJRERXUkQebG9jYWwgcmVzIElEIChvZmZz\n\t\t\tZXQgZnJvbSA1MDAwRFdSRBAyLWJ5dGUgbGFuZ3VhZ2U/RFdSRAQq\n\t\t\tKioqTFNURQ==\n\t\t\t</data>\n\t\t\t<key>ID</key>\n\t\t\t<string>128</string>\n\t\t\t<key>Name</key>\n\t\t\t<string>LPic</string>\n\t\t</dict>\n\t</array>\n\t<key>styl</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>Attributes</key>\n\t\t\t<string>0x0000</string>\n\t\t\t<key>Data</key>\n\t\t\t<data>\n\t\t\tAAMAAAAAAAwACQAUAAAAAAAAAAAAAAAAACcADAAJABQBAAAAAAAA\n\t\t\tAAAAAAAAKgAMAAkAFAAAAAAAAAAAAAA=\n\t\t\t</data>\n\t\t\t<key>ID</key>\n\t\t\t<string>5000</string>\n\t\t\t<key>Name</key>\n\t\t\t<string>English</string>\n\t\t</dict>\n\t</array>\n</dict>\n</plist>\n"
  },
  {
    "path": "crates/packager/src/package/dmg/mod.rs",
    "content": "// Copyright 2019-2023 Tauri Programme within The Commons Conservancy\n// Copyright 2023-2023 CrabNebula Ltd.\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{fs, os::unix::fs::PermissionsExt, path::PathBuf, process::Command};\n\nuse super::Context;\nuse crate::{\n    codesign::macos as codesign,\n    shell::CommandExt,\n    util::{self, download},\n    Error,\n};\n\nconst CREATE_DMG_URL: &str =\n    \"https://raw.githubusercontent.com/create-dmg/create-dmg/28867ba3563ddef62f55dcf130677103b4296c42/create-dmg\";\n\n#[tracing::instrument(level = \"trace\", skip(ctx))]\npub(crate) fn package(ctx: &Context) -> crate::Result<Vec<PathBuf>> {\n    let Context {\n        config,\n        tools_path,\n        intermediates_path,\n        ..\n    } = ctx;\n\n    let out_dir = config.out_dir();\n    let intermediates_path = intermediates_path.join(\"dmg\");\n    util::create_clean_dir(&intermediates_path)?;\n\n    let package_base_name = format!(\n        \"{}_{}_{}\",\n        config.product_name,\n        config.version,\n        match config.target_arch()? {\n            \"x86_64\" => \"x64\",\n            other => other,\n        }\n    );\n    let app_bundle_file_name = format!(\"{}.app\", config.product_name);\n    let dmg_name = format!(\"{}.dmg\", &package_base_name);\n    let dmg_path = out_dir.join(&dmg_name);\n\n    tracing::info!(\"Packaging {} ({})\", dmg_name, dmg_path.display());\n\n    if dmg_path.exists() {\n        fs::remove_file(&dmg_path).map_err(|e| Error::IoWithPath(dmg_path.clone(), e))?;\n    }\n\n    let dmg_tools_path = tools_path.join(\"DMG\");\n\n    let script_dir = dmg_tools_path.join(\"script\");\n    fs::create_dir_all(&script_dir).map_err(|e| Error::IoWithPath(script_dir.clone(), e))?;\n\n    let create_dmg_script_path = script_dir.join(\"create-dmg\");\n\n    let support_directory_path = dmg_tools_path.join(\"share/create-dmg/support\");\n    fs::create_dir_all(&support_directory_path)\n        .map_err(|e| Error::IoWithPath(support_directory_path.clone(), e))?;\n\n    if !dmg_tools_path.exists() {\n        fs::create_dir_all(&dmg_tools_path)\n            .map_err(|e| Error::IoWithPath(dmg_tools_path.clone(), e))?;\n    }\n    if !create_dmg_script_path.exists() {\n        tracing::debug!(\"Downloading create-dmg script\");\n        let data = download(CREATE_DMG_URL)?;\n        tracing::debug!(\n            \"Writing {} and setting its permissions to 764\",\n            create_dmg_script_path.display()\n        );\n        fs::write(&create_dmg_script_path, data)\n            .map_err(|e| Error::IoWithPath(create_dmg_script_path.clone(), e))?;\n        fs::set_permissions(&create_dmg_script_path, fs::Permissions::from_mode(0o764))\n            .map_err(|e| Error::IoWithPath(create_dmg_script_path.clone(), e))?;\n    }\n\n    tracing::debug!(\"Writing template.applescript\");\n    let template_applescript = support_directory_path.join(\"template.applescript\");\n    fs::write(&template_applescript, include_str!(\"template.applescript\"))\n        .map_err(|e| Error::IoWithPath(template_applescript, e))?;\n\n    tracing::debug!(\"Writing eula-resources-template.xml\");\n    let eula_template = support_directory_path.join(\"eula-resources-template.xml\");\n    fs::write(&eula_template, include_str!(\"eula-resources-template.xml\"))\n        .map_err(|e| Error::IoWithPath(eula_template, e))?;\n\n    let dmg = config.dmg();\n\n    let mut bundle_dmg_cmd = Command::new(&create_dmg_script_path);\n\n    let app_x = dmg\n        .and_then(|d| d.app_position)\n        .map(|p| p.x)\n        .unwrap_or(180)\n        .to_string();\n    let app_y = dmg\n        .and_then(|d| d.app_position)\n        .map(|p| p.y)\n        .unwrap_or(170)\n        .to_string();\n    let app_folder_x = dmg\n        .and_then(|d| d.app_folder_position)\n        .map(|p| p.x)\n        .unwrap_or(480)\n        .to_string();\n    let app_folder_y = dmg\n        .and_then(|d| d.app_folder_position)\n        .map(|p| p.y)\n        .unwrap_or(170)\n        .to_string();\n    let window_width = dmg\n        .and_then(|d| d.window_size)\n        .map(|s| s.width)\n        .unwrap_or(600)\n        .to_string();\n    let window_height = dmg\n        .and_then(|d| d.window_size)\n        .map(|s| s.height)\n        .unwrap_or(400)\n        .to_string();\n\n    bundle_dmg_cmd.args([\n        \"--volname\",\n        &config.product_name,\n        \"--icon\",\n        &app_bundle_file_name,\n        &app_x,\n        &app_y,\n        \"--app-drop-link\",\n        &app_folder_x,\n        &app_folder_y,\n        \"--window-size\",\n        &window_width,\n        &window_height,\n        \"--hide-extension\",\n        &app_bundle_file_name,\n    ]);\n\n    let window_position = dmg\n        .and_then(|d| d.window_position)\n        .map(|p| (p.x.to_string(), p.y.to_string()));\n    if let Some((x, y)) = window_position {\n        bundle_dmg_cmd.arg(\"--window-pos\");\n        bundle_dmg_cmd.arg(&x);\n        bundle_dmg_cmd.arg(&y);\n    }\n\n    let background_path = match &dmg.and_then(|d| d.background.as_ref()) {\n        Some(p) => Some(std::env::current_dir()?.join(p)),\n        None => None,\n    };\n\n    if let Some(background_path) = &background_path {\n        bundle_dmg_cmd.arg(\"--background\");\n        bundle_dmg_cmd.arg(background_path);\n    }\n\n    tracing::debug!(\"Creating icns file\");\n    let icns_icon_path = util::create_icns_file(&intermediates_path, config)?;\n    if let Some(icon) = &icns_icon_path {\n        bundle_dmg_cmd.arg(\"--volicon\");\n        bundle_dmg_cmd.arg(icon);\n    }\n\n    let license_file = match config.license_file.as_ref() {\n        Some(l) => Some(std::env::current_dir()?.join(l)),\n        None => None,\n    };\n    if let Some(license_path) = &license_file {\n        bundle_dmg_cmd.arg(\"--eula\");\n        bundle_dmg_cmd.arg(license_path);\n    }\n\n    // Issue #592 - Building MacOS dmg files on CI\n    // https://github.com/tauri-apps/tauri/issues/592\n    if let Some(value) = std::env::var_os(\"CI\") {\n        if value == \"true\" {\n            bundle_dmg_cmd.arg(\"--skip-jenkins\");\n        }\n    }\n\n    tracing::debug!(\"Running create-dmg\");\n\n    // execute the bundle script\n    bundle_dmg_cmd\n        .current_dir(&out_dir)\n        .args(vec![dmg_name.as_str(), app_bundle_file_name.as_str()])\n        .output_ok()\n        .map_err(crate::Error::CreateDmgFailed)?;\n\n    // Sign DMG if needed\n    if let Some(identity) = &config\n        .macos()\n        .and_then(|macos| macos.signing_identity.as_ref())\n    {\n        tracing::debug!(\"Codesigning {}\", dmg_path.display());\n        codesign::try_sign(\n            vec![codesign::SignTarget {\n                path: dmg_path.clone(),\n                is_native_binary: false,\n            }],\n            identity,\n            config,\n        )?;\n    }\n\n    Ok(vec![dmg_path])\n}\n"
  },
  {
    "path": "crates/packager/src/package/dmg/template.applescript",
    "content": "on run (volumeName)\n\ttell application \"Finder\"\n\t\ttell disk (volumeName as string)\n\t\t\topen\n\n\t\t\tset theXOrigin to WINX\n\t\t\tset theYOrigin to WINY\n\t\t\tset theWidth to WINW\n\t\t\tset theHeight to WINH\n\n\t\t\tset theBottomRightX to (theXOrigin + theWidth)\n\t\t\tset theBottomRightY to (theYOrigin + theHeight)\n\t\t\tset dsStore to \"\\\"\" & \"/Volumes/\" & volumeName & \"/\" & \".DS_STORE\\\"\"\n\n\t\t\ttell container window\n\t\t\t\tset current view to icon view\n\t\t\t\tset toolbar visible to false\n\t\t\t\tset statusbar visible to false\n\t\t\t\tset the bounds to {theXOrigin, theYOrigin, theBottomRightX, theBottomRightY}\n\t\t\t\tset statusbar visible to false\n\t\t\t\tREPOSITION_HIDDEN_FILES_CLAUSE\n\t\t\tend tell\n\n\t\t\tset opts to the icon view options of container window\n\t\t\ttell opts\n\t\t\t\tset icon size to ICON_SIZE\n\t\t\t\tset text size to TEXT_SIZE\n\t\t\t\tset arrangement to not arranged\n\t\t\tend tell\n\t\t\tBACKGROUND_CLAUSE\n\n\t\t\t-- Positioning\n\t\t\tPOSITION_CLAUSE\n\n\t\t\t-- Hiding\n\t\t\tHIDING_CLAUSE\n\n\t\t\t-- Application and QL Link Clauses\n\t\t\tAPPLICATION_CLAUSE\n\t\t\tQL_CLAUSE\n\t\t\tclose\n\t\t\topen\n\t\t\t-- Force saving of the size\n\t\t\tdelay 1\n\n\t\t\ttell container window\n\t\t\t\tset statusbar visible to false\n\t\t\t\tset the bounds to {theXOrigin, theYOrigin, theBottomRightX - 10, theBottomRightY - 10}\n\t\t\tend tell\n\t\tend tell\n\n\t\tdelay 1\n\n\t\ttell disk (volumeName as string)\n\t\t\ttell container window\n\t\t\t\tset statusbar visible to false\n\t\t\t\tset the bounds to {theXOrigin, theYOrigin, theBottomRightX, theBottomRightY}\n\t\t\tend tell\n\t\tend tell\n\n\t\t--give the finder some time to write the .DS_Store file\n\t\tdelay 3\n\n\t\tset waitTime to 0\n\t\tset ejectMe to false\n\t\trepeat while ejectMe is false\n\t\t\tdelay 1\n\t\t\tset waitTime to waitTime + 1\n\t\t\t\n\t\t\tif (do shell script \"[ -f \" & dsStore & \" ]; echo $?\") = \"0\" then set ejectMe to true\n\t\tend repeat\n\t\tlog \"waited \" & waitTime & \" seconds for .DS_STORE to be created.\"\n\tend tell\nend run\n"
  },
  {
    "path": "crates/packager/src/package/mod.rs",
    "content": "// Copyright 2023-2023 CrabNebula Ltd.\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::path::PathBuf;\n\nuse crate::{config, shell::CommandExt, util, Config, PackageFormat};\n\nuse self::context::Context;\n\nmod app;\n#[cfg(any(\n    target_os = \"linux\",\n    target_os = \"dragonfly\",\n    target_os = \"freebsd\",\n    target_os = \"netbsd\",\n    target_os = \"openbsd\"\n))]\nmod appimage;\n#[cfg(any(\n    target_os = \"linux\",\n    target_os = \"dragonfly\",\n    target_os = \"freebsd\",\n    target_os = \"netbsd\",\n    target_os = \"openbsd\"\n))]\nmod deb;\n#[cfg(target_os = \"macos\")]\nmod dmg;\nmod nsis;\n#[cfg(any(\n    target_os = \"linux\",\n    target_os = \"dragonfly\",\n    target_os = \"freebsd\",\n    target_os = \"netbsd\",\n    target_os = \"openbsd\"\n))]\nmod pacman;\n#[cfg(windows)]\nmod wix;\n\nmod context;\n\n/// Generated Package metadata.\n#[derive(Debug, Clone)]\n#[non_exhaustive]\npub struct PackageOutput {\n    /// The package type.\n    pub format: PackageFormat,\n    /// All paths for this package.\n    pub paths: Vec<PathBuf>,\n}\n\nimpl PackageOutput {\n    /// Creates a new package output.\n    ///\n    /// This is only useful if you need to sign the packages in a different process,\n    /// after packaging the app and storing its paths.\n    pub fn new(format: PackageFormat, paths: Vec<PathBuf>) -> Self {\n        Self { format, paths }\n    }\n}\n\n/// Package an app using the specified config.\n#[tracing::instrument(level = \"trace\", skip(config))]\npub fn package(config: &Config) -> crate::Result<Vec<PackageOutput>> {\n    let mut formats = config\n        .formats\n        .clone()\n        .unwrap_or_else(|| PackageFormat::platform_default().to_vec());\n\n    if formats.is_empty() {\n        return Ok(Vec::new());\n    }\n\n    if formats.contains(&PackageFormat::Default) {\n        formats = PackageFormat::platform_default().to_vec();\n    }\n\n    if formats.contains(&PackageFormat::All) {\n        formats = PackageFormat::platform_all().to_vec();\n    }\n\n    formats.sort_by_key(|f| f.priority());\n\n    let formats_comma_separated = formats\n        .iter()\n        .map(|f| f.short_name())\n        .collect::<Vec<_>>()\n        .join(\",\");\n\n    run_before_packaging_command_hook(config, &formats_comma_separated)?;\n\n    let ctx = Context::new(config)?;\n    tracing::trace!(ctx = ?ctx);\n\n    let mut packages = Vec::new();\n    for format in &formats {\n        run_before_each_packaging_command_hook(\n            config,\n            &formats_comma_separated,\n            format.short_name(),\n        )?;\n\n        let paths = match format {\n            PackageFormat::App => app::package(&ctx),\n            #[cfg(target_os = \"macos\")]\n            PackageFormat::Dmg => {\n                // PackageFormat::App is required for the DMG bundle\n                if !packages\n                    .iter()\n                    .any(|b: &PackageOutput| b.format == PackageFormat::App)\n                {\n                    let paths = app::package(&ctx)?;\n                    packages.push(PackageOutput {\n                        format: PackageFormat::App,\n                        paths,\n                    });\n                }\n                dmg::package(&ctx)\n            }\n            #[cfg(target_os = \"windows\")]\n            PackageFormat::Wix => wix::package(&ctx),\n            PackageFormat::Nsis => nsis::package(&ctx),\n            #[cfg(any(\n                target_os = \"linux\",\n                target_os = \"dragonfly\",\n                target_os = \"freebsd\",\n                target_os = \"netbsd\",\n                target_os = \"openbsd\"\n            ))]\n            PackageFormat::Deb => deb::package(&ctx),\n            #[cfg(any(\n                target_os = \"linux\",\n                target_os = \"dragonfly\",\n                target_os = \"freebsd\",\n                target_os = \"netbsd\",\n                target_os = \"openbsd\"\n            ))]\n            PackageFormat::AppImage => appimage::package(&ctx),\n            #[cfg(any(\n                target_os = \"linux\",\n                target_os = \"dragonfly\",\n                target_os = \"freebsd\",\n                target_os = \"netbsd\",\n                target_os = \"openbsd\"\n            ))]\n            PackageFormat::Pacman => pacman::package(&ctx),\n\n            _ => {\n                tracing::warn!(\"ignoring {}\", format.short_name());\n                continue;\n            }\n        }?;\n\n        packages.push(PackageOutput {\n            format: *format,\n            paths,\n        });\n    }\n\n    #[cfg(target_os = \"macos\")]\n    {\n        // Clean up .app if only building dmg\n        if !formats.contains(&PackageFormat::App) {\n            if let Some(app_bundle_paths) = packages\n                .iter()\n                .position(|b| b.format == PackageFormat::App)\n                .map(|i| packages.remove(i))\n                .map(|b| b.paths)\n            {\n                for p in &app_bundle_paths {\n                    use crate::Error;\n                    use std::fs;\n\n                    tracing::debug!(\"Cleaning {}\", p.display());\n                    match p.is_dir() {\n                        true => {\n                            fs::remove_dir_all(p).map_err(|e| Error::IoWithPath(p.clone(), e))?\n                        }\n                        false => fs::remove_file(p).map_err(|e| Error::IoWithPath(p.clone(), e))?,\n                    };\n                }\n            }\n        }\n    }\n\n    Ok(packages)\n}\n\nfn run_before_each_packaging_command_hook(\n    config: &Config,\n    formats_comma_separated: &str,\n    format: &str,\n) -> crate::Result<()> {\n    if let Some(hook) = &config.before_each_package_command {\n        let (mut cmd, script) = match hook {\n            config::HookCommand::Script(script) => {\n                let cmd = util::cross_command(script);\n                (cmd, script)\n            }\n            config::HookCommand::ScriptWithOptions { script, dir } => {\n                let mut cmd = util::cross_command(script);\n                if let Some(dir) = dir {\n                    cmd.current_dir(dir);\n                }\n                (cmd, script)\n            }\n        };\n\n        tracing::info!(\"Running beforeEachPackageCommand [{format}] `{script}`\");\n        let output = cmd\n            .env(\"CARGO_PACKAGER_FORMATS\", formats_comma_separated)\n            .env(\"CARGO_PACKAGER_FORMAT\", format)\n            .output_ok_info()\n            .map_err(|e| {\n                crate::Error::HookCommandFailure(\n                    \"beforeEachPackageCommand\".into(),\n                    script.into(),\n                    e,\n                )\n            })?;\n\n        if !output.status.success() {\n            return Err(crate::Error::HookCommandFailureWithExitCode(\n                \"beforeEachPackageCommand\".into(),\n                script.into(),\n                output.status.code().unwrap_or_default(),\n            ));\n        }\n    }\n\n    Ok(())\n}\n\nfn run_before_packaging_command_hook(\n    config: &Config,\n    formats_comma_separated: &str,\n) -> crate::Result<()> {\n    if let Some(hook) = &config.before_packaging_command {\n        let (mut cmd, script) = match hook {\n            config::HookCommand::Script(script) => {\n                let cmd = util::cross_command(script);\n                (cmd, script)\n            }\n            config::HookCommand::ScriptWithOptions { script, dir } => {\n                let mut cmd = util::cross_command(script);\n                if let Some(dir) = dir {\n                    cmd.current_dir(dir);\n                }\n                (cmd, script)\n            }\n        };\n\n        tracing::info!(\"Running beforePackageCommand `{script}`\");\n        let output = cmd\n            .env(\"CARGO_PACKAGER_FORMATS\", formats_comma_separated)\n            .output_ok_info()\n            .map_err(|e| {\n                crate::Error::HookCommandFailure(\"beforePackagingCommand\".into(), script.into(), e)\n            })?;\n\n        if !output.status.success() {\n            return Err(crate::Error::HookCommandFailureWithExitCode(\n                \"beforePackagingCommand\".into(),\n                script.into(),\n                output.status.code().unwrap_or_default(),\n            ));\n        }\n    }\n\n    Ok(())\n}\n"
  },
  {
    "path": "crates/packager/src/package/nsis/FileAssociation.nsh",
    "content": "; from https://gist.github.com/nikku/281d0ef126dbc215dd58bfd5b3a5cd5b\n; fileassoc.nsh\n; File association helper macros\n; Written by Saivert\n;\n; Improved by Nikku<https://github.com/nikku>.\n;\n; Features automatic backup system and UPDATEFILEASSOC macro for\n; shell change notification.\n;\n; |> How to use <|\n; To associate a file with an application so you can double-click it in explorer, use\n; the APP_ASSOCIATE macro like this:\n;\n;   Example:\n;   !insertmacro APP_ASSOCIATE \"txt\" \"myapp.textfile\" \"Description of txt files\" \\\n;     \"$INSTDIR\\myapp.exe,0\" \"Open with myapp\" \"$INSTDIR\\myapp.exe $\\\"%1$\\\"\"\n;\n; Never insert the APP_ASSOCIATE macro multiple times, it is only ment\n; to associate an application with a single file and using the\n; the \"open\" verb as default. To add more verbs (actions) to a file\n; use the APP_ASSOCIATE_ADDVERB macro.\n;\n;   Example:\n;   !insertmacro APP_ASSOCIATE_ADDVERB \"myapp.textfile\" \"edit\" \"Edit with myapp\" \\\n;     \"$INSTDIR\\myapp.exe /edit $\\\"%1$\\\"\"\n;\n; To have access to more options when registering the file association use the\n; APP_ASSOCIATE_EX macro. Here you can specify the verb and what verb is to be the\n; standard action (default verb).\n;\n; Note, that this script takes into account user versus global installs.\n; To properly work you must initialize the SHELL_CONTEXT variable via SetShellVarContext.\n;\n; And finally: To remove the association from the registry use the APP_UNASSOCIATE\n; macro. Here is another example just to wrap it up:\n;   !insertmacro APP_UNASSOCIATE \"txt\" \"myapp.textfile\"\n;\n; |> Note <|\n; When defining your file class string always use the short form of your application title\n; then a period (dot) and the type of file. This keeps the file class sort of unique.\n;   Examples:\n;   Winamp.Playlist\n;   NSIS.Script\n;   Photoshop.JPEGFile\n;\n; |> Tech info <|\n; The registry key layout for a global file association is:\n;\n; HKEY_LOCAL_MACHINE\\Software\\Classes\n;     <\".ext\"> = <applicationID>\n;     <applicationID> = <\"description\">\n;         shell\n;             <verb> = <\"menu-item text\">\n;                 command = <\"command string\">\n;\n;\n; The registry key layout for a per-user file association is:\n;\n; HKEY_CURRENT_USER\\Software\\Classes\n;     <\".ext\"> = <applicationID>\n;     <applicationID> = <\"description\">\n;         shell\n;             <verb> = <\"menu-item text\">\n;                 command = <\"command string\">\n;\n\n!macro APP_ASSOCIATE EXT FILECLASS DESCRIPTION ICON COMMANDTEXT COMMAND\n  ; Backup the previously associated file class\n  ReadRegStr $R0 SHELL_CONTEXT \"Software\\Classes\\.${EXT}\" \"\"\n  WriteRegStr SHELL_CONTEXT \"Software\\Classes\\.${EXT}\" \"${FILECLASS}_backup\" \"$R0\"\n\n  WriteRegStr SHELL_CONTEXT \"Software\\Classes\\.${EXT}\" \"\" \"${FILECLASS}\"\n\n  WriteRegStr SHELL_CONTEXT \"Software\\Classes\\${FILECLASS}\" \"\" `${DESCRIPTION}`\n  WriteRegStr SHELL_CONTEXT \"Software\\Classes\\${FILECLASS}\\DefaultIcon\" \"\" `${ICON}`\n  WriteRegStr SHELL_CONTEXT \"Software\\Classes\\${FILECLASS}\\shell\" \"\" \"open\"\n  WriteRegStr SHELL_CONTEXT \"Software\\Classes\\${FILECLASS}\\shell\\open\" \"\" `${COMMANDTEXT}`\n  WriteRegStr SHELL_CONTEXT \"Software\\Classes\\${FILECLASS}\\shell\\open\\command\" \"\" `${COMMAND}`\n!macroend\n\n!macro APP_ASSOCIATE_EX EXT FILECLASS DESCRIPTION ICON VERB DEFAULTVERB SHELLNEW COMMANDTEXT COMMAND\n  ; Backup the previously associated file class\n  ReadRegStr $R0 SHELL_CONTEXT \"Software\\Classes\\.${EXT}\" \"\"\n  WriteRegStr SHELL_CONTEXT \"Software\\Classes\\.${EXT}\" \"${FILECLASS}_backup\" \"$R0\"\n\n  WriteRegStr SHELL_CONTEXT \"Software\\Classes\\.${EXT}\" \"\" \"${FILECLASS}\"\n  StrCmp \"${SHELLNEW}\" \"0\" +2\n  WriteRegStr SHELL_CONTEXT \"Software\\Classes\\.${EXT}\\ShellNew\" \"NullFile\" \"\"\n\n  WriteRegStr SHELL_CONTEXT \"Software\\Classes\\${FILECLASS}\" \"\" `${DESCRIPTION}`\n  WriteRegStr SHELL_CONTEXT \"Software\\Classes\\${FILECLASS}\\DefaultIcon\" \"\" `${ICON}`\n  WriteRegStr SHELL_CONTEXT \"Software\\Classes\\${FILECLASS}\\shell\" \"\" `${DEFAULTVERB}`\n  WriteRegStr SHELL_CONTEXT \"Software\\Classes\\${FILECLASS}\\shell\\${VERB}\" \"\" `${COMMANDTEXT}`\n  WriteRegStr SHELL_CONTEXT \"Software\\Classes\\${FILECLASS}\\shell\\${VERB}\\command\" \"\" `${COMMAND}`\n!macroend\n\n!macro APP_ASSOCIATE_ADDVERB FILECLASS VERB COMMANDTEXT COMMAND\n  WriteRegStr SHELL_CONTEXT \"Software\\Classes\\${FILECLASS}\\shell\\${VERB}\" \"\" `${COMMANDTEXT}`\n  WriteRegStr SHELL_CONTEXT \"Software\\Classes\\${FILECLASS}\\shell\\${VERB}\\command\" \"\" `${COMMAND}`\n!macroend\n\n!macro APP_ASSOCIATE_REMOVEVERB FILECLASS VERB\n  DeleteRegKey SHELL_CONTEXT `Software\\Classes\\${FILECLASS}\\shell\\${VERB}`\n!macroend\n\n\n!macro APP_UNASSOCIATE EXT FILECLASS\n  ; Backup the previously associated file class\n  ReadRegStr $R0 SHELL_CONTEXT \"Software\\Classes\\.${EXT}\" `${FILECLASS}_backup`\n  WriteRegStr SHELL_CONTEXT \"Software\\Classes\\.${EXT}\" \"\" \"$R0\"\n\n  DeleteRegKey SHELL_CONTEXT `Software\\Classes\\${FILECLASS}`\n!macroend\n\n!macro APP_ASSOCIATE_GETFILECLASS OUTPUT EXT\n  ReadRegStr ${OUTPUT} SHELL_CONTEXT \"Software\\Classes\\.${EXT}\" \"\"\n!macroend\n\n\n; !defines for use with SHChangeNotify\n!ifdef SHCNE_ASSOCCHANGED\n!undef SHCNE_ASSOCCHANGED\n!endif\n!define SHCNE_ASSOCCHANGED 0x08000000\n!ifdef SHCNF_FLUSH\n!undef SHCNF_FLUSH\n!endif\n!define SHCNF_FLUSH        0x1000\n\n!macro UPDATEFILEASSOC\n; Using the system.dll plugin to call the SHChangeNotify Win32 API function so we\n; can update the shell.\n  System::Call \"shell32::SHChangeNotify(i,i,i,i) (${SHCNE_ASSOCCHANGED}, ${SHCNF_FLUSH}, 0, 0)\"\n!macroend\n"
  },
  {
    "path": "crates/packager/src/package/nsis/installer.nsi",
    "content": "; Set the compression algorithm.\n!if \"{{compression}}\" == \"\"\n  SetCompressor /SOLID lzma\n!else\n  SetCompressor /SOLID \"{{compression}}\"\n!endif\n\nUnicode true\n\n!include MUI2.nsh\n!include FileFunc.nsh\n!include x64.nsh\n!include WordFunc.nsh\n!include \"FileAssociation.nsh\"\n!include \"StrFunc.nsh\"\n!include \"StrFunc.nsh\"\n${StrCase}\n${StrLoc}\n\n!define MANUFACTURER \"{{manufacturer}}\"\n!define PRODUCTNAME \"{{product_name}}\"\n!define VERSION \"{{version}}\"\n!define VERSIONWITHBUILD \"{{version_with_build}}\"\n!define SHORTDESCRIPTION \"{{short_description}}\"\n!define INSTALLMODE \"{{install_mode}}\"\n!define LICENSE \"{{license}}\"\n!define INSTALLERICON \"{{installer_icon}}\"\n!define SIDEBARIMAGE \"{{sidebar_image}}\"\n!define HEADERIMAGE \"{{header_image}}\"\n!define MAINBINARYNAME \"{{main_binary_name}}\"\n!define MAINBINARYSRCPATH \"{{main_binary_path}}\"\n!define IDENTIFIER \"{{identifier}}\"\n!define COPYRIGHT \"{{copyright}}\"\n!define OUTFILE \"{{out_file}}\"\n!define ARCH \"{{arch}}\"\n!define PLUGINSPATH \"{{additional_plugins_path}}\"\n!define ALLOWDOWNGRADES \"{{allow_downgrades}}\"\n!define DISPLAYLANGUAGESELECTOR \"{{display_language_selector}}\"\n!define UNINSTKEY \"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${PRODUCTNAME}\"\n!define MANUPRODUCTKEY \"Software\\${MANUFACTURER}\\${PRODUCTNAME}\"\n!define UNINSTALLERSIGNCOMMAND \"{{uninstaller_sign_cmd}}\"\n!define ESTIMATEDSIZE \"{{estimated_size}}\"\n\nName \"${PRODUCTNAME}\"\nBrandingText \"${COPYRIGHT}\"\nOutFile \"${OUTFILE}\"\n\nVIProductVersion \"${VERSIONWITHBUILD}\"\nVIAddVersionKey \"ProductName\" \"${PRODUCTNAME}\"\nVIAddVersionKey \"FileDescription\" \"${SHORTDESCRIPTION}\"\nVIAddVersionKey \"LegalCopyright\" \"${COPYRIGHT}\"\nVIAddVersionKey \"FileVersion\" \"${VERSION}\"\nVIAddVersionKey \"ProductVersion\" \"${VERSION}\"\n\n; Plugins path, currently exists for linux only\n!if \"${PLUGINSPATH}\" != \"\"\n    !addplugindir \"${PLUGINSPATH}\"\n!endif\n\n!if \"${UNINSTALLERSIGNCOMMAND}\" != \"\"\n  !uninstfinalize '${UNINSTALLERSIGNCOMMAND}'\n!endif\n\n; Handle install mode, `perUser`, `perMachine` or `both`\n!if \"${INSTALLMODE}\" == \"perMachine\"\n  RequestExecutionLevel highest\n!endif\n\n!if \"${INSTALLMODE}\" == \"currentUser\"\n  RequestExecutionLevel user\n!endif\n\n!if \"${INSTALLMODE}\" == \"both\"\n  !define MULTIUSER_MUI\n  !define MULTIUSER_INSTALLMODE_INSTDIR \"${PRODUCTNAME}\"\n  !define MULTIUSER_INSTALLMODE_COMMANDLINE\n  !if \"${ARCH}\" == \"x64\"\n    !define MULTIUSER_USE_PROGRAMFILES64\n  !else if \"${ARCH}\" == \"arm64\"\n    !define MULTIUSER_USE_PROGRAMFILES64\n  !endif\n  !define MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_KEY \"${UNINSTKEY}\"\n  !define MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_VALUENAME \"CurrentUser\"\n  !define MULTIUSER_INSTALLMODEPAGE_SHOWUSERNAME\n  !define MULTIUSER_INSTALLMODE_FUNCTION RestorePreviousInstallLocation\n  !define MULTIUSER_EXECUTIONLEVEL Highest\n  !include MultiUser.nsh\n!endif\n\n; installer icon\n!if \"${INSTALLERICON}\" != \"\"\n  !define MUI_ICON \"${INSTALLERICON}\"\n!endif\n\n; installer sidebar image\n!if \"${SIDEBARIMAGE}\" != \"\"\n  !define MUI_WELCOMEFINISHPAGE_BITMAP \"${SIDEBARIMAGE}\"\n!endif\n\n; installer header image\n!if \"${HEADERIMAGE}\" != \"\"\n  !define MUI_HEADERIMAGE\n  !define MUI_HEADERIMAGE_BITMAP  \"${HEADERIMAGE}\"\n!endif\n\n; Define registry key to store installer language\n!define MUI_LANGDLL_REGISTRY_ROOT \"HKCU\"\n!define MUI_LANGDLL_REGISTRY_KEY \"${MANUPRODUCTKEY}\"\n!define MUI_LANGDLL_REGISTRY_VALUENAME \"Installer Language\"\n\n; Installer pages, must be ordered as they appear\n; 1. Welcome Page\n!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive\n!insertmacro MUI_PAGE_WELCOME\n\n; 2. License Page (if defined)\n!if \"${LICENSE}\" != \"\"\n  !define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive\n  !insertmacro MUI_PAGE_LICENSE \"${LICENSE}\"\n!endif\n\n; 3. Install mode (if it is set to `both`)\n!if \"${INSTALLMODE}\" == \"both\"\n  !define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive\n  !insertmacro MULTIUSER_PAGE_INSTALLMODE\n!endif\n\n\n; 4. Custom page to ask user if he wants to reinstall/uninstall\n;    only if a previous installtion was detected\nVar ReinstallPageCheck\nPage custom PageReinstall PageLeaveReinstall\nFunction PageReinstall\n  ; Uninstall previous WiX installation if exists.\n  ;\n  ; A WiX installer stores the isntallation info in registry\n  ; using a UUID and so we have to loop through all keys under\n  ; `HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall`\n  ; and check if `DisplayName` and `Publisher` keys match ${PRODUCTNAME} and ${MANUFACTURER}\n  ;\n  ; This has a potentional issue that there maybe another installation that matches\n  ; our ${PRODUCTNAME} and ${MANUFACTURER} but wasn't installed by our WiX installer,\n  ; however, this should be fine since the user will have to confirm the uninstallation\n  ; and they can chose to abort it if doesn't make sense.\n  StrCpy $0 0\n  wix_loop:\n    EnumRegKey $1 HKLM \"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\" $0\n    StrCmp $1 \"\" wix_done ; Exit loop if there is no more keys to loop on\n    IntOp $0 $0 + 1\n    ReadRegStr $R0 HKLM \"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\$1\" \"DisplayName\"\n    ReadRegStr $R1 HKLM \"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\$1\" \"Publisher\"\n    StrCmp \"$R0$R1\" \"${PRODUCTNAME}${MANUFACTURER}\" 0 wix_loop\n    ReadRegStr $R0 HKLM \"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\$1\" \"UninstallString\"\n    ${StrCase} $R1 $R0 \"L\"\n    ${StrLoc} $R0 $R1 \"msiexec\" \">\"\n    StrCmp $R0 0 0 wix_done\n    StrCpy $R7 \"wix\"\n    StrCpy $R6 \"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\$1\"\n    Goto compare_version\n  wix_done:\n\n  ; Check if there is an existing installation, if not, abort the reinstall page\n  ReadRegStr $R0 SHCTX \"${UNINSTKEY}\" \"\"\n  ReadRegStr $R1 SHCTX \"${UNINSTKEY}\" \"UninstallString\"\n  ${IfThen} \"$R0$R1\" == \"\" ${|} Abort ${|}\n\n  ; Compare this installar version with the existing installation\n  ; and modify the messages presented to the user accordingly\n  compare_version:\n  StrCpy $R4 \"$(older)\"\n  ${If} $R7 == \"wix\"\n    ReadRegStr $R0 HKLM \"$R6\" \"DisplayVersion\"\n  ${Else}\n    ReadRegStr $R0 SHCTX \"${UNINSTKEY}\" \"DisplayVersion\"\n  ${EndIf}\n  ${IfThen} $R0 == \"\" ${|} StrCpy $R4 \"$(unknown)\" ${|}\n\n  nsis_tauri_utils::SemverCompare \"${VERSION}\" $R0\n  Pop $R0\n  ; Reinstalling the same version\n  ${If} $R0 == 0\n    StrCpy $R1 \"$(alreadyInstalledLong)\"\n    StrCpy $R2 \"$(addOrReinstall)\"\n    StrCpy $R3 \"$(uninstallApp)\"\n    !insertmacro MUI_HEADER_TEXT \"$(alreadyInstalled)\" \"$(chooseMaintenanceOption)\"\n    StrCpy $R5 \"2\"\n  ; Upgrading\n  ${ElseIf} $R0 == 1\n    StrCpy $R1 \"$(olderOrUnknownVersionInstalled)\"\n    StrCpy $R2 \"$(uninstallBeforeInstalling)\"\n    StrCpy $R3 \"$(dontUninstall)\"\n    !insertmacro MUI_HEADER_TEXT \"$(alreadyInstalled)\" \"$(choowHowToInstall)\"\n    StrCpy $R5 \"1\"\n  ; Downgrading\n  ${ElseIf} $R0 == -1\n    StrCpy $R1 \"$(newerVersionInstalled)\"\n    StrCpy $R2 \"$(uninstallBeforeInstalling)\"\n    !if \"${ALLOWDOWNGRADES}\" == \"true\"\n      StrCpy $R3 \"$(dontUninstall)\"\n    !else\n      StrCpy $R3 \"$(dontUninstallDowngrade)\"\n    !endif\n    !insertmacro MUI_HEADER_TEXT \"$(alreadyInstalled)\" \"$(choowHowToInstall)\"\n    StrCpy $R5 \"1\"\n  ${Else}\n    Abort\n  ${EndIf}\n\n  Call SkipIfPassive\n\n  nsDialogs::Create 1018\n  Pop $R4\n  ${IfThen} $(^RTL) == 1 ${|} nsDialogs::SetRTL $(^RTL) ${|}\n\n  ${NSD_CreateLabel} 0 0 100% 24u $R1\n  Pop $R1\n\n  ${NSD_CreateRadioButton} 30u 50u -30u 8u $R2\n  Pop $R2\n  ${NSD_OnClick} $R2 PageReinstallUpdateSelection\n\n  ${NSD_CreateRadioButton} 30u 70u -30u 8u $R3\n  Pop $R3\n  ; disable this radio button if downgrading and downgrades are disabled\n  !if \"${ALLOWDOWNGRADES}\" == \"false\"\n    ${IfThen} $R0 == -1 ${|} EnableWindow $R3 0 ${|}\n  !endif\n  ${NSD_OnClick} $R3 PageReinstallUpdateSelection\n\n  ; Check the first radio button if this the first time\n  ; we enter this page or if the second button wasn't\n  ; selected the last time we were on this page\n  ${If} $ReinstallPageCheck != 2\n    SendMessage $R2 ${BM_SETCHECK} ${BST_CHECKED} 0\n  ${Else}\n    SendMessage $R3 ${BM_SETCHECK} ${BST_CHECKED} 0\n  ${EndIf}\n\n  ${NSD_SetFocus} $R2\n  nsDialogs::Show\nFunctionEnd\nFunction PageReinstallUpdateSelection\n  ${NSD_GetState} $R2 $R1\n  ${If} $R1 == ${BST_CHECKED}\n    StrCpy $ReinstallPageCheck 1\n  ${Else}\n    StrCpy $ReinstallPageCheck 2\n  ${EndIf}\nFunctionEnd\nFunction PageLeaveReinstall\n  ${NSD_GetState} $R2 $R1\n\n  ; $R5 holds whether we are reinstalling the same version or not\n  ; $R5 == \"1\" -> different versions\n  ; $R5 == \"2\" -> same version\n  ;\n  ; $R1 holds the radio buttons state. its meaning is dependant on the context\n  StrCmp $R5 \"1\" 0 +2 ; Existing install is not the same version?\n    StrCmp $R1 \"1\" reinst_uninstall reinst_done ; $R1 == \"1\", then user chose to uninstall existing version, otherwise skip uninstalling\n  StrCmp $R1 \"1\" reinst_done ; Same version? skip uninstalling\n\n  reinst_uninstall:\n    HideWindow\n    ClearErrors\n\n    ${If} $R7 == \"wix\"\n      ReadRegStr $R1 HKLM \"$R6\" \"UninstallString\"\n      ExecWait '$R1' $0\n    ${Else}\n      ReadRegStr $4 SHCTX \"${MANUPRODUCTKEY}\" \"\"\n      ReadRegStr $R1 SHCTX \"${UNINSTKEY}\" \"UninstallString\"\n      ExecWait '$R1 /P _?=$4' $0\n    ${EndIf}\n\n    BringToFront\n\n    ${IfThen} ${Errors} ${|} StrCpy $0 2 ${|} ; ExecWait failed, set fake exit code\n\n    ${If} $0 <> 0\n    ${OrIf} ${FileExists} \"$INSTDIR\\${MAINBINARYNAME}.exe\"\n      ${If} $0 = 1 ; User aborted uninstaller?\n        StrCmp $R5 \"2\" 0 +2 ; Is the existing install the same version?\n          Quit ; ...yes, already installed, we are done\n        Abort\n      ${EndIf}\n      MessageBox MB_ICONEXCLAMATION \"$(unableToUninstall)\"\n      Abort\n    ${Else}\n      StrCpy $0 $R1 1\n      ${IfThen} $0 == '\"' ${|} StrCpy $R1 $R1 -1 1 ${|} ; Strip quotes from UninstallString\n      Delete $R1\n      RMDir $INSTDIR\n    ${EndIf}\n  reinst_done:\nFunctionEnd\n\n; 5. Choose install directoy page\n!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive\n!insertmacro MUI_PAGE_DIRECTORY\n\n; 6. Start menu shortcut page\n!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive\nVar AppStartMenuFolder\n!insertmacro MUI_PAGE_STARTMENU Application $AppStartMenuFolder\n\n; 7. Installation page\n!insertmacro MUI_PAGE_INSTFILES\n\n; 8. Finish page\n;\n; Don't auto jump to finish page after installation page,\n; because the installation page has useful info that can be used debug any issues with the installer.\n!define MUI_FINISHPAGE_NOAUTOCLOSE\n; Use show readme button in the finish page as a button create a desktop shortcut\n!define MUI_FINISHPAGE_SHOWREADME\n!define MUI_FINISHPAGE_SHOWREADME_TEXT \"$(createDesktop)\"\n!define MUI_FINISHPAGE_SHOWREADME_FUNCTION CreateDesktopShortcut\n; Show run app after installation.\n!define MUI_FINISHPAGE_RUN \"$INSTDIR\\${MAINBINARYNAME}.exe\"\n!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive\n!insertmacro MUI_PAGE_FINISH\n\n; Uninstaller Pages\n; 1. Confirm uninstall page\n{{#if appdata_paths}}\nVar DeleteAppDataCheckbox\nVar DeleteAppDataCheckboxState\n!define /ifndef WS_EX_LAYOUTRTL         0x00400000\n!define MUI_PAGE_CUSTOMFUNCTION_SHOW un.ConfirmShow\nFunction un.ConfirmShow\n    FindWindow $1 \"#32770\" \"\" $HWNDPARENT ; Find inner dialog\n    ${If} $(^RTL) == 1\n      System::Call 'USER32::CreateWindowEx(i${__NSD_CheckBox_EXSTYLE}|${WS_EX_LAYOUTRTL},t\"${__NSD_CheckBox_CLASS}\",t \"$(deleteAppData)\",i${__NSD_CheckBox_STYLE},i 50,i 100,i 400, i 25,i$1,i0,i0,i0)i.s'\n    ${Else}\n      System::Call 'USER32::CreateWindowEx(i${__NSD_CheckBox_EXSTYLE},t\"${__NSD_CheckBox_CLASS}\",t \"$(deleteAppData)\",i${__NSD_CheckBox_STYLE},i 0,i 100,i 400, i 25,i$1,i0,i0,i0)i.s'\n    ${EndIf}\n    Pop $DeleteAppDataCheckbox\n    SendMessage $HWNDPARENT ${WM_GETFONT} 0 0 $1\n    SendMessage $DeleteAppDataCheckbox ${WM_SETFONT} $1 1\nFunctionEnd\n!define MUI_PAGE_CUSTOMFUNCTION_LEAVE un.ConfirmLeave\nFunction un.ConfirmLeave\n    SendMessage $DeleteAppDataCheckbox ${BM_GETCHECK} 0 0 $DeleteAppDataCheckboxState\nFunctionEnd\n{{/if}}\n!insertmacro MUI_UNPAGE_CONFIRM\n\n; 2. Uninstalling Page\n!insertmacro MUI_UNPAGE_INSTFILES\n\n;Languages\n{{#each languages}}\n!insertmacro MUI_LANGUAGE \"{{this}}\"\n{{/each}}\n!insertmacro MUI_RESERVEFILE_LANGDLL\n{{#each language_files}}\n  !include \"{{this}}\"\n{{/each}}\n\n!macro SetContext\n  !if \"${INSTALLMODE}\" == \"currentUser\"\n    SetShellVarContext current\n  !else if \"${INSTALLMODE}\" == \"perMachine\"\n    SetShellVarContext all\n  !endif\n\n  ${If} ${RunningX64}\n    !if \"${ARCH}\" == \"x64\"\n      SetRegView 64\n    !else if \"${ARCH}\" == \"arm64\"\n      SetRegView 64\n    !else\n      SetRegView 32\n    !endif\n  ${EndIf}\n!macroend\n\nVar PassiveMode\nFunction .onInit\n  ${GetOptions} $CMDLINE \"/P\" $PassiveMode\n  IfErrors +2 0\n    StrCpy $PassiveMode 1\n\n  !if \"${DISPLAYLANGUAGESELECTOR}\" == \"true\"\n    !insertmacro MUI_LANGDLL_DISPLAY\n  !endif\n\n  !insertmacro SetContext\n\n  ${If} $INSTDIR == \"\"\n    ; Set default install location\n    !if \"${INSTALLMODE}\" == \"perMachine\"\n      ${If} ${RunningX64}\n        !if \"${ARCH}\" == \"x64\"\n          StrCpy $INSTDIR \"$PROGRAMFILES64\\${PRODUCTNAME}\"\n        !else if \"${ARCH}\" == \"arm64\"\n          StrCpy $INSTDIR \"$PROGRAMFILES64\\${PRODUCTNAME}\"\n        !else\n          StrCpy $INSTDIR \"$PROGRAMFILES\\${PRODUCTNAME}\"\n        !endif\n      ${Else}\n        StrCpy $INSTDIR \"$PROGRAMFILES\\${PRODUCTNAME}\"\n      ${EndIf}\n    !else if \"${INSTALLMODE}\" == \"currentUser\"\n      StrCpy $INSTDIR \"$LOCALAPPDATA\\${PRODUCTNAME}\"\n    !endif\n\n    Call RestorePreviousInstallLocation\n  ${EndIf}\n\n\n  !if \"${INSTALLMODE}\" == \"both\"\n    !insertmacro MULTIUSER_INIT\n  !endif\nFunctionEnd\n\n\nSection EarlyChecks\n  ; Abort silent installer if downgrades is disabled\n  !if \"${ALLOWDOWNGRADES}\" == \"false\"\n  IfSilent 0 silent_downgrades_done\n    ; If downgrading\n    ${If} $R0 == -1\n      System::Call 'kernel32::AttachConsole(i -1)i.r0'\n      ${If} $0 != 0\n        System::Call 'kernel32::GetStdHandle(i -11)i.r0'\n        System::call 'kernel32::SetConsoleTextAttribute(i r0, i 0x0004)' ; set red color\n        FileWrite $0 \"$(silentDowngrades)\"\n      ${EndIf}\n      Abort\n    ${EndIf}\n  silent_downgrades_done:\n  !endif\n\nSectionEnd\n\n{{#if preinstall_section}}\n{{unescape_newlines preinstall_section}}\n{{/if}}\n\n!macro CheckIfAppIsRunning\n  nsis_tauri_utils::FindProcess \"${MAINBINARYNAME}.exe\"\n  Pop $R0\n  ${If} $R0 = 0\n      IfSilent kill 0\n      ${IfThen} $PassiveMode != 1 ${|} MessageBox MB_OKCANCEL \"$(appRunningOkKill)\" IDOK kill IDCANCEL cancel ${|}\n      kill:\n        nsis_tauri_utils::KillProcess \"${MAINBINARYNAME}.exe\"\n        Pop $R0\n        Sleep 500\n        ${If} $R0 = 0\n          Goto app_check_done\n        ${Else}\n          IfSilent silent ui\n          silent:\n            System::Call 'kernel32::AttachConsole(i -1)i.r0'\n            ${If} $0 != 0\n              System::Call 'kernel32::GetStdHandle(i -11)i.r0'\n              System::call 'kernel32::SetConsoleTextAttribute(i r0, i 0x0004)' ; set red color\n              FileWrite $0 \"$(appRunning)$\\n\"\n            ${EndIf}\n            Abort\n          ui:\n            Abort \"$(failedToKillApp)\"\n        ${EndIf}\n      cancel:\n        Abort \"$(appRunning)\"\n  ${EndIf}\n  app_check_done:\n!macroend\n\nSection Install\n  SetOutPath $INSTDIR\n\n  !insertmacro CheckIfAppIsRunning\n\n  ; Copy main executable\n  File \"${MAINBINARYSRCPATH}\"\n\n  ; Create resources directory structure\n  {{#each resources_dirs}}\n    CreateDirectory \"$INSTDIR\\\\{{this}}\"\n  {{/each}}\n\n  ; Copy resources\n  {{#each resources}}\n    File /a \"/oname={{this}}\" \"{{@key}}\"\n  {{/each}}\n\n  ; Copy external binaries\n  {{#each binaries}}\n    File /a \"/oname={{this}}\" \"{{@key}}\"\n  {{/each}}\n\n  ; Create file associations\n  {{#each file_associations as |association| ~}}\n    {{#each association.extensions as |ext| ~}}\n       !insertmacro APP_ASSOCIATE \"{{ext}}\" \"{{or association.name ext}}\" \"{{association-description association.description ext}}\" \"$INSTDIR\\${MAINBINARYNAME}.exe,0\" \"Open with ${PRODUCTNAME}\" \"$INSTDIR\\${MAINBINARYNAME}.exe $\\\"%1$\\\"\"\n    {{/each}}\n  {{/each}}\n\n  ; Register deep links\n  {{#each deep_link_protocols as |protocol| ~}}\n    WriteRegStr SHCTX \"Software\\Classes\\\\{{protocol}}\" \"URL Protocol\" \"\"\n    WriteRegStr SHCTX \"Software\\Classes\\\\{{protocol}}\" \"\" \"URL:${BUNDLEID} protocol\"\n    WriteRegStr SHCTX \"Software\\Classes\\\\{{protocol}}\\DefaultIcon\" \"\" \"$\\\"$INSTDIR\\${MAINBINARYNAME}.exe$\\\",0\"\n    WriteRegStr SHCTX \"Software\\Classes\\\\{{protocol}}\\shell\\open\\command\" \"\" \"$\\\"$INSTDIR\\${MAINBINARYNAME}.exe$\\\" $\\\"%1$\\\"\"\n  {{/each}}\n\n  ; Create uninstaller\n  WriteUninstaller \"$INSTDIR\\uninstall.exe\"\n\n  ; Save $INSTDIR in registry for future installations\n  WriteRegStr SHCTX \"${MANUPRODUCTKEY}\" \"\" $INSTDIR\n\n  !if \"${INSTALLMODE}\" == \"both\"\n    ; Save install mode to be selected by default for the next installation such as updating\n    ; or when uninstalling\n    WriteRegStr SHCTX \"${UNINSTKEY}\" $MultiUser.InstallMode 1\n  !endif\n\n  ; Registry information for add/remove programs\n  WriteRegStr SHCTX \"${UNINSTKEY}\" \"DisplayName\" \"${PRODUCTNAME}\"\n  WriteRegStr SHCTX \"${UNINSTKEY}\" \"DisplayIcon\" \"$\\\"$INSTDIR\\${MAINBINARYNAME}.exe$\\\"\"\n  WriteRegStr SHCTX \"${UNINSTKEY}\" \"DisplayVersion\" \"${VERSION}\"\n  WriteRegStr SHCTX \"${UNINSTKEY}\" \"Publisher\" \"${MANUFACTURER}\"\n  WriteRegStr SHCTX \"${UNINSTKEY}\" \"InstallLocation\" \"$\\\"$INSTDIR$\\\"\"\n  WriteRegStr SHCTX \"${UNINSTKEY}\" \"UninstallString\" \"$\\\"$INSTDIR\\uninstall.exe$\\\"\"\n  WriteRegDWORD SHCTX \"${UNINSTKEY}\" \"NoModify\" \"1\"\n  WriteRegDWORD SHCTX \"${UNINSTKEY}\" \"NoRepair\" \"1\"\n  WriteRegDWORD SHCTX \"${UNINSTKEY}\" \"EstimatedSize\" \"${ESTIMATEDSIZE}\"\n\n  ; Create start menu shortcut (GUI)\n  !insertmacro MUI_STARTMENU_WRITE_BEGIN Application\n    Call CreateStartMenuShortcut\n  !insertmacro MUI_STARTMENU_WRITE_END\n\n  ; Create shortcuts for silent and passive installers, which\n  ; can be disabled by passing `/NS` flag\n  ; GUI installer has buttons for users to control creating them\n  IfSilent check_ns_flag 0\n  ${IfThen} $PassiveMode == 1 ${|} Goto check_ns_flag ${|}\n  Goto shortcuts_done\n  check_ns_flag:\n    ${GetOptions} $CMDLINE \"/NS\" $R0\n    IfErrors 0 shortcuts_done\n      Call CreateDesktopShortcut\n      Call CreateStartMenuShortcut\n  shortcuts_done:\n\n  ; Auto close this page for passive mode\n  ${IfThen} $PassiveMode == 1 ${|} SetAutoClose true ${|}\nSectionEnd\n\nFunction .onInstSuccess\n  ; Check for `/R` flag only in silent and passive installers because\n  ; GUI installer has a toggle for the user to (re)start the app\n  IfSilent check_r_flag 0\n  ${IfThen} $PassiveMode == 1 ${|} Goto check_r_flag ${|}\n  Goto run_done\n  check_r_flag:\n    ${GetOptions} $CMDLINE \"/R\" $R0\n    IfErrors run_done 0\n      Exec '\"$INSTDIR\\${MAINBINARYNAME}.exe\"'\n  run_done:\nFunctionEnd\n\nFunction un.onInit\n  !insertmacro SetContext\n\n  !if \"${INSTALLMODE}\" == \"both\"\n    !insertmacro MULTIUSER_UNINIT\n  !endif\n\n  !insertmacro MUI_UNGETLANGUAGE\nFunctionEnd\n\nSection Uninstall\n  !insertmacro CheckIfAppIsRunning\n\n  ; Delete the app directory and its content from disk\n  ; Copy main executable\n  Delete \"$INSTDIR\\${MAINBINARYNAME}.exe\"\n\n  ; Delete resources\n  {{#each resources}}\n    Delete \"$INSTDIR\\\\{{this}}\"\n  {{/each}}\n\n  ; Delete external binaries\n  {{#each binaries}}\n    Delete \"$INSTDIR\\\\{{this}}\"\n  {{/each}}\n\n  ; Delete app associations\n  {{#each file_associations as |association| ~}}\n    {{#each association.ext as |ext| ~}}\n      !insertmacro APP_UNASSOCIATE \"{{ext}}\" \"{{or association.name ext}}\"\n    {{/each}}\n  {{/each}}\n\n  ; Delete deep links\n  {{#each deep_link_protocols as |protocol| ~}}\n    ReadRegStr $R7 SHCTX \"Software\\Classes\\\\{{protocol}}\\shell\\open\\command\" \"\"\n    !if $R7 == \"$\\\"$INSTDIR\\${MAINBINARYNAME}.exe$\\\" $\\\"%1$\\\"\"\n      DeleteRegKey SHCTX \"Software\\Classes\\\\{{protocol}}\"\n    !endif\n  {{/each}}\n\n  ; Delete uninstaller\n  Delete \"$INSTDIR\\uninstall.exe\"\n\n  {{#each resources_dirs}}\n  RMDir /REBOOTOK \"$INSTDIR\\\\{{this}}\"\n  {{/each}}\n  RMDir \"$INSTDIR\"\n\n  ; Remove start menu shortcut\n  !insertmacro MUI_STARTMENU_GETFOLDER Application $AppStartMenuFolder\n  Delete \"$SMPROGRAMS\\$AppStartMenuFolder\\${PRODUCTNAME}.lnk\"\n  RMDir \"$SMPROGRAMS\\$AppStartMenuFolder\"\n\n  ; Remove desktop shortcuts\n  Delete \"$DESKTOP\\${PRODUCTNAME}.lnk\"\n\n  ; Remove registry information for add/remove programs\n  !if \"${INSTALLMODE}\" == \"both\"\n    DeleteRegKey SHCTX \"${UNINSTKEY}\"\n  !else if \"${INSTALLMODE}\" == \"perMachine\"\n    DeleteRegKey HKLM \"${UNINSTKEY}\"\n  !else\n    DeleteRegKey HKCU \"${UNINSTKEY}\"\n  !endif\n\n  DeleteRegValue HKCU \"${MANUPRODUCTKEY}\" \"Installer Language\"\n\n  ; Delete app data\n  {{#if appdata_paths}}\n  ${If} $DeleteAppDataCheckboxState == 1\n      SetShellVarContext current\n      {{#each appdata_paths}}\n      RmDir /r \"{{unescape_dollar_sign this}}\"\n      {{/each}}\n  ${EndIf}\n  {{/if}}\n\n  ${GetOptions} $CMDLINE \"/P\" $R0\n  IfErrors +2 0\n    SetAutoClose true\nSectionEnd\n\nFunction RestorePreviousInstallLocation\n  ReadRegStr $4 SHCTX \"${MANUPRODUCTKEY}\" \"\"\n  StrCmp $4 \"\" +2 0\n    StrCpy $INSTDIR $4\nFunctionEnd\n\nFunction SkipIfPassive\n  ${IfThen} $PassiveMode == 1  ${|} Abort ${|}\nFunctionEnd\n\nFunction CreateDesktopShortcut\n  CreateShortcut \"$DESKTOP\\${PRODUCTNAME}.lnk\" \"$INSTDIR\\${MAINBINARYNAME}.exe\"\n  ApplicationID::Set \"$DESKTOP\\${PRODUCTNAME}.lnk\" \"${IDENTIFIER}\"\nFunctionEnd\n\nFunction CreateStartMenuShortcut\n  CreateDirectory \"$SMPROGRAMS\\$AppStartMenuFolder\"\n  CreateShortcut \"$SMPROGRAMS\\$AppStartMenuFolder\\${PRODUCTNAME}.lnk\" \"$INSTDIR\\${MAINBINARYNAME}.exe\"\n  ApplicationID::Set \"$SMPROGRAMS\\$AppStartMenuFolder\\${PRODUCTNAME}.lnk\" \"${IDENTIFIER}\"\nFunctionEnd\n"
  },
  {
    "path": "crates/packager/src/package/nsis/languages/Arabic.nsh",
    "content": "LangString addOrReinstall ${LANG_ARABIC} \"إضافة أو إزالة المكونات\"\nLangString alreadyInstalled ${LANG_ARABIC} \"التطبيق مثبت بالفعل\"\nLangString alreadyInstalledLong ${LANG_ARABIC} \"${PRODUCTNAME} ${VERSION} مثبت بالفعل. قم باختيار العملية التى تريدها ثم اضغط على التالى.\"\nLangString appRunning ${LANG_ARABIC} \"${PRODUCTNAME} مازال يعمل! من فضلك، قم بإغلاق التطبيق أولاً ثم حاول مرة أخرى.\"\nLangString appRunningOkKill ${LANG_ARABIC} \"${PRODUCTNAME} مازال يعمل!$\\nاضغط OK لإغلاقه\"\nLangString chooseMaintenanceOption ${LANG_ARABIC} \"قم باختيار نوع الصيانة التى تريدها.\"\nLangString choowHowToInstall ${LANG_ARABIC} \"قم باختيار طريقة تنصيب ${PRODUCTNAME}.\"\nLangString createDesktop ${LANG_ARABIC} \"اضف اختصار على سطح المكتب\"\nLangString dontUninstall ${LANG_ARABIC} \"عدم إزالة\"\nLangString dontUninstallDowngrade ${LANG_ARABIC} \"عدم إزالة (التخفيض بدون إزالة غير مسموح لهذا المثبت)\"\nLangString failedToKillApp ${LANG_ARABIC} \"فشل فى غلف ${PRODUCTNAME}. من فضلك، قم بإغلاق التطبيق أولاً ثم حاول مرة أخرى.\"\nLangString installingWebview2 ${LANG_ARABIC} \"تنصيب WebView2...\"\nLangString newerVersionInstalled ${LANG_ARABIC} \"يوجد نسخة جديدة من ${PRODUCTNAME} مثبتة بالغعل! لا ينصح بتنصيب نسخة اقدم من النسخة الحالية. اذا مازلت ترغب فى تنصيب النسخة الأقدم، فينصح بإزالة النسخة الحالية أولاً. قم باختيار العملية التى تريدها ثم اضغط على التالى للاستمرار.\"\nLangString older ${LANG_ARABIC} \"أقدم\"\nLangString olderOrUnknownVersionInstalled ${LANG_ARABIC} \"نسخة $R4 من ${PRODUCTNAME} مثبتة بالفعل على نظامك. ينصح بإزالة النسخة الحالية قبل التنصيب. قم باختيار العملية التى تريدها ثم اضغط على التالى للاستمرار.\"\nLangString silentDowngrades ${LANG_ARABIC} \"التخفيض غير مسموح لهذا المثبت ولا يمكن الاستمرار فى الوضع الصامت. من فضلك، قم باستخدام الواجهة الرسومية.\"\nLangString unableToUninstall ${LANG_ARABIC} \"غير قادر على الإزالة!\"\nLangString uninstallApp ${LANG_ARABIC} \"إزالة ${PRODUCTNAME}\"\nLangString uninstallBeforeInstalling ${LANG_ARABIC} \"قم بإزالة التطبيق قبل التثبيت\"\nLangString unknown ${LANG_ARABIC} \"غير معروفة\"\nLangString deleteAppData ${LANG_ARABIC} \"مسح بيانات التطبيق\"\n"
  },
  {
    "path": "crates/packager/src/package/nsis/languages/Bulgarian.nsh",
    "content": "LangString addOrReinstall ${LANG_BULGARIAN} \"Добавяне/Преинсталиране на компоненти\"\nLangString alreadyInstalled ${LANG_BULGARIAN} \"Вече инсталиран\"\nLangString alreadyInstalledLong ${LANG_BULGARIAN} \"${PRODUCTNAME} ${VERSION} е вече е инсталиран. Изберете операцията, която искате да извършите и натиснете Напред, за да продължите.\"\nLangString appRunning ${LANG_BULGARIAN} \"${PRODUCTNAME} е отворен! Моля, затворете го първо и опитайте отново.\"\nLangString appRunningOkKill ${LANG_BULGARIAN} \"${PRODUCTNAME} е отворен!$\\nНатиснете ОК, за да го затворите.\"\nLangString chooseMaintenanceOption ${LANG_BULGARIAN} \"Изберете опция за поддръжка.\"\nLangString choowHowToInstall ${LANG_BULGARIAN} \"Изберете как искате да инсталирате ${PRODUCTNAME}.\"\nLangString createDesktop ${LANG_BULGARIAN} \"Създайте пряк път на работния плот\"\nLangString dontUninstall ${LANG_BULGARIAN} \"Не деинсталирайте\"\nLangString dontUninstallDowngrade ${LANG_BULGARIAN} \"Не деинсталирайте (Понижаването без деинсталация е забранено за този инсталатор)\"\nLangString failedToKillApp ${LANG_BULGARIAN} \"Неуспешно прекратяване на ${PRODUCTNAME}. Моля, затворете го първо и опитайте отново.\"\nLangString installingWebview2 ${LANG_BULGARIAN} \"Инсталиране на WebView2...\"\nLangString newerVersionInstalled ${LANG_BULGARIAN} \"Вече е инсталирана по-нова версия на ${PRODUCTNAME}! Не се препоръчва да инсталирате по-стара версия. Ако наистина желаете да инсталирате тази по-стара версия, по-добре е да деинсталирате текущата версия първо. Изберете операцията, която искате да извършите и натиснете Напред, за да продължите.\"\nLangString older ${LANG_BULGARIAN} \"по-стара\"\nLangString olderOrUnknownVersionInstalled ${LANG_BULGARIAN} \"На вашата система е инсталирана $R4 версия на ${PRODUCTNAME}. Препоръчително е да деинсталирате текущата версия преди да инсталирате нова. Изберете операцията, която искате да извършите и натиснете Напред, за да продължите.\"\nLangString silentDowngrades ${LANG_BULGARIAN} \"Понижаванията не са позволени за този инсталатор. Не е възможно да продължите с безшумен инсталатор. Моля, използвайте графичния интерфейсен инсталатор.\"\nLangString unableToUninstall ${LANG_BULGARIAN} \"Неуспешна деинсталация!\"\nLangString uninstallApp ${LANG_BULGARIAN} \"Деинсталиране на ${PRODUCTNAME}\"\nLangString uninstallBeforeInstalling ${LANG_BULGARIAN} \"Деинсталирай преди инсталиране\"\nLangString unknown ${LANG_BULGARIAN} \"неизвестно\"\nLangString deleteAppData ${LANG_BULGARIAN} \"Изтриване на данните на приложението\"\n"
  },
  {
    "path": "crates/packager/src/package/nsis/languages/Dutch.nsh",
    "content": "LangString addOrReinstall ${LANG_DUTCH} \"(Her)installeer componenten\"\nLangString alreadyInstalled ${LANG_DUTCH} \"Al geïnstalleerd\"\nLangString alreadyInstalledLong ${LANG_DUTCH} \"${PRODUCTNAME} ${VERSION} is al geïnstalleerd. Kies een van de volgende opties en klik op Volgende om door te gaan.\"\nLangString appRunning ${LANG_DUTCH} \"${PRODUCTNAME} is geopend! Sluit het programma eerst en probeer het dan opnieuw.\"\nLangString appRunningOkKill ${LANG_DUTCH} \"${PRODUCTNAME} is geopend!$\\nKlik op OK om het te stoppen.\"\nLangString chooseMaintenanceOption ${LANG_DUTCH} \"Kies de onderhoudsoptie die u wilt uitvoeren.\"\nLangString choowHowToInstall ${LANG_DUTCH} \"Kies hoe u ${PRODUCTNAME} wilt installeren.\"\nLangString createDesktop ${LANG_DUTCH} \"Maak een snelkoppeling aan op het bureaublad\"\nLangString dontUninstall ${LANG_DUTCH} \"Deïnstalleer niet\"\nLangString dontUninstallDowngrade ${LANG_DUTCH} \"Deïnstalleer niet (Downgraden zonder deïnstalleren is uitgeschakeld voor deze installer)\"\nLangString failedToKillApp ${LANG_DUTCH} \"Het is niet gelukt ${PRODUCTNAME} te stoppen. Sluit het eerst zelf en probeer het dan nog een keer\"\nLangString installingWebview2 ${LANG_DUTCH} \"WebView2 wordt geïnstalleerd...\"\nLangString newerVersionInstalled ${LANG_DUTCH} \"Een nieuwere versie van ${PRODUCTNAME} is al geïnstalleerd! Het word niet aangeraden om een oudere versie te installeren. Als u echt deze oudere versie wilt installeren, kunt u beter de huidige versie eerst deïnstalleren. Kies een van de volgende opties en klik op Volgende om door te gaan.\"\nLangString older ${LANG_DUTCH} \"oudere\"\nLangString olderOrUnknownVersionInstalled ${LANG_DUTCH} \"Een $R4 versie van ${PRODUCTNAME} is al geïnstalleerd. Het word aangeraden om de huidige versie eerst te deïnstalleren. Kies een van de volgende opties en klik op Volgende om door te gaan.\"\nLangString silentDowngrades ${LANG_DUTCH} \"Downgrades zijn uitgeschakeld voor deze installer, de stille installatie kan niet worden voltooid, gebruik a.u.b. de grafische installatie methode.$\\n\"\nLangString unableToUninstall ${LANG_DUTCH} \"De installatie kan niet ongedaan worden gemaakt.\"\nLangString uninstallApp ${LANG_DUTCH} \"Deïnstalleer ${PRODUCTNAME}\"\nLangString uninstallBeforeInstalling ${LANG_DUTCH} \"Deïnstalleer voor installatie\"\nLangString unknown ${LANG_DUTCH} \"onbekende\"\nLangString deleteAppData ${LANG_DUTCH} \"Verwijder de data van de applicatie\"\n"
  },
  {
    "path": "crates/packager/src/package/nsis/languages/English.nsh",
    "content": "LangString addOrReinstall ${LANG_ENGLISH} \"Add/Reinstall components\"\nLangString alreadyInstalled ${LANG_ENGLISH} \"Already Installed\"\nLangString alreadyInstalledLong ${LANG_ENGLISH} \"${PRODUCTNAME} ${VERSION} is already installed. Select the operation you want to perform and click Next to continue.\"\nLangString appRunning ${LANG_ENGLISH} \"${PRODUCTNAME} is running! Please close it first then try again.\"\nLangString appRunningOkKill ${LANG_ENGLISH} \"${PRODUCTNAME} is running!$\\nClick OK to kill it\"\nLangString chooseMaintenanceOption ${LANG_ENGLISH} \"Choose the maintenance option to perform.\"\nLangString choowHowToInstall ${LANG_ENGLISH} \"Choose how you want to install ${PRODUCTNAME}.\"\nLangString createDesktop ${LANG_ENGLISH} \"Create desktop shortcut\"\nLangString dontUninstall ${LANG_ENGLISH} \"Do not uninstall\"\nLangString dontUninstallDowngrade ${LANG_ENGLISH} \"Do not uninstall (Downgrading without uninstall is disabled for this installer)\"\nLangString failedToKillApp ${LANG_ENGLISH} \"Failed to kill ${PRODUCTNAME}. Please close it first then try again\"\nLangString installingWebview2 ${LANG_ENGLISH} \"Installing WebView2...\"\nLangString newerVersionInstalled ${LANG_ENGLISH} \"A newer version of ${PRODUCTNAME} is already installed! It is not recommended that you install an older version. If you really want to install this older version, it's better to uninstall the current version first. Select the operation you want to perform and click Next to continue.\"\nLangString older ${LANG_ENGLISH} \"older\"\nLangString olderOrUnknownVersionInstalled ${LANG_ENGLISH} \"An $R4 version of ${PRODUCTNAME} is installed on your system. It's recommended that you uninstall the current version before installing. Select the operation you want to perform and click Next to continue.\"\nLangString silentDowngrades ${LANG_ENGLISH} \"Downgrades are disabled for this installer, can't proceed with the silent installer, please use the graphical interface installer instead.$\\n\"\nLangString unableToUninstall ${LANG_ENGLISH} \"Unable to uninstall!\"\nLangString uninstallApp ${LANG_ENGLISH} \"Uninstall ${PRODUCTNAME}\"\nLangString uninstallBeforeInstalling ${LANG_ENGLISH} \"Uninstall before installing\"\nLangString unknown ${LANG_ENGLISH} \"unknown\"\nLangString deleteAppData ${LANG_ENGLISH} \"Delete the application data\"\n"
  },
  {
    "path": "crates/packager/src/package/nsis/languages/French.nsh",
    "content": "LangString addOrReinstall ${LANG_FRENCH} \"Ajouter/Réinstaller un composant.\"\nLangString alreadyInstalled ${LANG_FRENCH} \"Déja installé.\"\nLangString alreadyInstalledLong ${LANG_FRENCH} \"${PRODUCTNAME} ${VERSION} est déja installé. Sélectionnez l'opération que vous souhaitez effectuer, puis cliquez sur Suivant pour continuer.\"\nLangString appRunning ${LANG_FRENCH} \"${PRODUCTNAME} est en cours d'exécution. Veuillez fermer l'application avant de réessayer.\"\nLangString appRunningOkKill ${LANG_FRENCH} \"${PRODUCTNAME} est en cours d'exécution.$\\nCliquez sur OK pour fermer l'application.\"\nLangString chooseMaintenanceOption ${LANG_FRENCH} \"Veuillez choisir l'option de maintenance à effectuer.\"\nLangString choowHowToInstall ${LANG_FRENCH} \"Veuillez choisir l'emplacement d'installation de ${PRODUCTNAME}.\"\nLangString createDesktop ${LANG_FRENCH} \"Créer un raccourci sur le bureau.\"\nLangString dontUninstall ${LANG_FRENCH} \"Ne pas désinstaller\"\nLangString dontUninstallDowngrade ${LANG_FRENCH} \"Ne pas désinstaller (revenir à une ancienne version sans désinstallation est désactivé pour cet installateur)\"\nLangString failedToKillApp ${LANG_FRENCH} \"La fermeture de ${PRODUCTNAME} a échoué. Veuillez fermer l'application et réessayer.\"\nLangString installingWebview2 ${LANG_FRENCH} \"Installation de WebView2...\"\nLangString newerVersionInstalled ${LANG_FRENCH} \"Une version plus récente de ${PRODUCTNAME} est déja installée. Il n'est pas recommandé d'installer une ancienne version. Si vous souhaitez installer cette ancienne version, il est conseillé de désinstaller la version courante en premier. Veuillez sélectionner l'opération que vous souhaitez effectuer, puis cliquez sur Suivant pour continer.\"\nLangString older ${LANG_FRENCH} \"ancien\"\nLangString olderOrUnknownVersionInstalled ${LANG_FRENCH} \"La version $R4 de ${PRODUCTNAME} est installée sur le système. Il est recommandé de désinstaller la version actuelle avant d'installer celle-ci. Sélectionnez l'opération que vous souhaitez effectuer, puis cliquez sur Suivant pour continuer.\"\nLangString silentDowngrades ${LANG_FRENCH} \"Revenir à une version antérieure est désactivé pour cet installateur. Impossible de continuer avec l'installation silencieuse, veuillez utiliser l'interface graphique à la place.$\\n\"\nLangString unableToUninstall ${LANG_FRENCH} \"Impossible de désinstaller le programme !\"\nLangString uninstallApp ${LANG_FRENCH} \"Désinstaller ${PRODUCTNAME}\"\nLangString uninstallBeforeInstalling ${LANG_FRENCH} \"Désinstaller avant d'installer\"\nLangString unknown ${LANG_FRENCH} \"inconnu\"\nLangString deleteAppData ${LANG_FRENCH} \"Supprimer les données de l'application\"\n"
  },
  {
    "path": "crates/packager/src/package/nsis/languages/Japanese.nsh",
    "content": "LangString addOrReinstall ${LANG_JAPANESE} \"コンポーネントの追加・再インストール\"\nLangString alreadyInstalled ${LANG_JAPANESE} \"既にインストールされています\"\nLangString alreadyInstalledLong ${LANG_JAPANESE} \"${PRODUCTNAME} ${VERSION} は既にインストールされています。実行したい操作を選択し、「次へ」をクリックして続行します。\"\nLangString appRunning ${LANG_JAPANESE} \"${PRODUCTNAME} は動作中です。動作中のプログラムを終了し、もう一度やり直してください。\"\nLangString appRunningOkKill ${LANG_JAPANESE} \"${PRODUCTNAME} は動作中です。$\\n「OK」を押すと動作中のプログラムを終了します。\"\nLangString chooseMaintenanceOption ${LANG_JAPANESE} \"メンテナンスオプションを選択して実行します。\"\nLangString choowHowToInstall ${LANG_JAPANESE} \"${PRODUCTNAME} のインストール方法を選択してください。\"\nLangString createDesktop ${LANG_JAPANESE} \"デスクトップショートカットを作成する\"\nLangString dontUninstall ${LANG_JAPANESE} \"アンインストールしない\"\nLangString dontUninstallDowngrade ${LANG_JAPANESE} \"アンインストールしない (このインストーラーでは、アンインストールをせずにダウングレードすることはできません)\"\nLangString failedToKillApp ${LANG_JAPANESE} \"${PRODUCTNAME} の終了に失敗しました。動作中のプログラムを終了し、もう一度やり直してください。\"\nLangString installingWebview2 ${LANG_JAPANESE} \"WebView2 をインストール中です...\"\nLangString newerVersionInstalled ${LANG_JAPANESE} \"既に新しいバージョンの ${PRODUCTNAME} がインストールされています。古いバージョンをインストールすることは推奨されません。どうしてもこの旧バージョンをインストールしたい場合は、先に現行バージョンをアンインストールしておく方がよいでしょう。実行したい操作を選択し、「次へ」をクリックして続行します。\"\nLangString older ${LANG_JAPANESE} \"旧\"\nLangString olderOrUnknownVersionInstalled ${LANG_JAPANESE} \"お使いのシステムには、 ${PRODUCTNAME} のバージョン $R4 がインストールされています。インストールする前に、現在のバージョンをアンインストールすることをお勧めします。実行したい操作を選択し、「次へ」をクリックして続行します。\"\nLangString silentDowngrades ${LANG_JAPANESE} \"このインストーラーではダウングレードはできません。サイレントインストーラーを続行できないので、代わりにグラフィカルインターフェースインストーラーを使用してください。$\\n\"\nLangString unableToUninstall ${LANG_JAPANESE} \"アンインストールできません。\"\nLangString uninstallApp ${LANG_JAPANESE} \"${PRODUCTNAME} をアンインストールする\"\nLangString uninstallBeforeInstalling ${LANG_JAPANESE} \"インストールする前にアンインストールする\"\nLangString unknown ${LANG_JAPANESE} \"不明\"\nLangString deleteAppData ${LANG_JAPANESE} \"アプリケーションデータを削除する\"\n"
  },
  {
    "path": "crates/packager/src/package/nsis/languages/Korean.nsh",
    "content": "LangString addOrReinstall ${LANG_KOREAN} \"컴포넌트 추가 및 재설치\"\nLangString alreadyInstalled ${LANG_KOREAN} \"이미 설치되어 있습니다\"\nLangString alreadyInstalledLong ${LANG_KOREAN} \"${PRODUCTNAME} ${VERSION}이(가) 이미 설치되어 있습니다. 수행하고자 하는 작업을 선택하고 '다음'을 클릭하여 계속합니다.\"\nLangString appRunning ${LANG_KOREAN} \"${PRODUCTNAME}이(가) 실행 중입니다! 먼저 닫은 후 다시 시도하세요.\"\nLangString appRunningOkKill ${LANG_KOREAN} \"${PRODUCTNAME}이(가) 실행 중입니다!$\\n'OK'를 누르면 실행 중인 프로그램을 종료합니다.\"\nLangString chooseMaintenanceOption ${LANG_KOREAN} \"수행하려는 관리 옵션을 선택합니다.\"\nLangString choowHowToInstall ${LANG_KOREAN} \"${PRODUCTNAME}의 설치 방법을 선택하세요..\"\nLangString createDesktop ${LANG_KOREAN} \"바탕화면 바로가기 만들기\"\nLangString dontUninstall ${LANG_KOREAN} \"제거하지 않기\"\nLangString dontUninstallDowngrade ${LANG_KOREAN} \"제거하지 않기 (이 설치 프로그램에서는 제거하지 않고 다운그레이드할 수 없습니다.)\"\nLangString failedToKillApp ${LANG_KOREAN} \"${PRODUCTNAME}을(를) 종료하지 못했습니다. 먼저 닫은 후 다시 시도하세요.\"\nLangString installingWebview2 ${LANG_KOREAN} \"WebView2를 설치하는 중입니다...\"\nLangString newerVersionInstalled ${LANG_KOREAN} \"${PRODUCTNAME}의 최신 버전이 이미 설치되어 있습니다! 이전 버전을 설치하지 않는 것이 좋습니다. 이 이전 버전을 꼭 설치하려면 먼저 현재 버전을 제거하는 것이 좋습니다. 수행하려는 작업을 선택하고 '다음'을 클릭하여 계속합니다.\"\nLangString older ${LANG_KOREAN} \"구\"\nLangString olderOrUnknownVersionInstalled ${LANG_KOREAN} \"시스템에 ${PRODUCTNAME}의 $R4 버전이 설치되어 있습니다. 설치하기 전에 현재 버전을 제거하는 것이 좋습니다. 수행하려는 작업을 선택하고 다음을 클릭하여 계속합니다.\"\nLangString silentDowngrades ${LANG_KOREAN} \"이 설치 프로그램에서는 다운그레이드가 비활성화되어 자동 설치 프로그램을 진행할 수 없습니다. 대신 그래픽 인터페이스 설치 프로그램을 사용하세요.$\\n\"\nLangString unableToUninstall ${LANG_KOREAN} \"제거할 수 없습니다!\"\nLangString uninstallApp ${LANG_KOREAN} \"${PRODUCTNAME} 제거하기\"\nLangString uninstallBeforeInstalling ${LANG_KOREAN} \"설치하기 전에 제거하기\"\nLangString unknown ${LANG_KOREAN} \"알 수 없음\"\nLangString deleteAppData ${LANG_KOREAN} \"애플리케이션 데이터 삭제하기\"\n"
  },
  {
    "path": "crates/packager/src/package/nsis/languages/Persian.nsh",
    "content": "LangString addOrReinstall ${LANG_PERSIAN} \"اضافه کردن/نصب مجدد کامپونتت\"\nLangString alreadyInstalled ${LANG_PERSIAN} \"قبلا نصب شده است\"\nLangString alreadyInstalledLong ${LANG_PERSIAN} \"${PRODUCTNAME} ${VERSION} قبلا نصب شده است. عملیات مدنظر را انتخاب کنید و بروی بعدی کلیک کنید.\"\nLangString appRunning ${LANG_PERSIAN} \"${PRODUCTNAME} در حال اجر می باشد ! لطفا اول الان را ببندید و دوباره تلاش کنید\"\nLangString appRunningOkKill ${LANG_PERSIAN} \"${PRODUCTNAME} در حال اجرا می باشد!$\\nبرای از بین بردن اوکی را انتخاب کنید\"\nLangString chooseMaintenanceOption ${LANG_PERSIAN} \"عملیات نگهداری مدنظر را برای اجرا انتخاب کنید\"\nLangString choowHowToInstall ${LANG_PERSIAN} \"نحوه نصب ${PRODUCTNAME} را انتخاب کنید\"\nLangString createDesktop ${LANG_PERSIAN} \"ایجاد میانبر دسکتاپ\"\nLangString dontUninstall ${LANG_PERSIAN} \"حذف نکنید\"\nLangString dontUninstallDowngrade ${LANG_PERSIAN} \"حذف نکنید (تنزل ورژن بدون حذف برای نصب کننده غیرفعال است)\"\nLangString failedToKillApp ${LANG_PERSIAN} \"${PRODUCTNAME} قابل کشته شدن نیست. اول آن را ببندید و دوباره تلاش کنید\"\nLangString installingWebview2 ${LANG_PERSIAN} \"در حال نصب WebView2 ...\"\nLangString newerVersionInstalled ${LANG_PERSIAN} \"ورژن جدید ${PRODUCTNAME} قبلا نصب شده است! نصب ورژن قدیمی تر به هیچ عنوان پیشنهاد نمی شود. اگر از این بابت اطمینان دارید , بهتر است ورژن فعلی را حذف کنید. عملیات مدنظر را انتخاب کنید و بروی بعدی کلیک کنید.\"\nLangString older ${LANG_PERSIAN} \"قدیمی تر\"\nLangString olderOrUnknownVersionInstalled ${LANG_PERSIAN} \"ورژن $R4 ${PRODUCTNAME} قبلا بروی سیستم شما نصب شده است. ر. عملیات مدنظر را انتخاب کنید و بروی بعدی کلیک کنید.\"\nLangString silentDowngrades ${LANG_PERSIAN} \"تنزل ورژن بدون حذف غیرفعال می باشد, عملیات نصب خاموش غیرقابل انجام است , از رابط گرافیکی برای نصب استفاده کنید.$\\n\"\nLangString unableToUninstall ${LANG_PERSIAN} \"قابل نصب نیست!\"\nLangString uninstallApp ${LANG_PERSIAN} \"حذف ${PRODUCTNAME}\"\nLangString uninstallBeforeInstalling ${LANG_PERSIAN} \"قبل از نصب , حذف کنید\"\nLangString unknown ${LANG_PERSIAN} \"ناشناس\"\nLangString deleteAppData ${LANG_PERSIAN} \"حذف دیتا های اپلیکیشن\"\n"
  },
  {
    "path": "crates/packager/src/package/nsis/languages/PortugueseBR.nsh",
    "content": "LangString addOrReinstall ${LANG_PORTUGUESEBR} \"Adicionar/Reinstalar componentes\"\nLangString alreadyInstalled ${LANG_PORTUGUESEBR} \"Já instalado\"\nLangString alreadyInstalledLong ${LANG_PORTUGUESEBR} \"${PRODUCTNAME} ${VERSION} já está instalado. Selecione a operação que deseja realizar e clique Próximo para continuar.\"\nLangString appRunning ${LANG_PORTUGUESEBR} \"${PRODUCTNAME} está aberto! Por favor feche a janela dele e tente novamente.\"\nLangString appRunningOkKill ${LANG_PORTUGUESEBR} \"${PRODUCTNAME} está aberto!$\\nClique OK para fechar ele.\"\nLangString chooseMaintenanceOption ${LANG_PORTUGUESEBR} \"Escolha a opção de manutenção a realizar.\"\nLangString choowHowToInstall ${LANG_PORTUGUESEBR} \"Escolha como deseja instalar ${PRODUCTNAME}.\"\nLangString createDesktop ${LANG_PORTUGUESEBR} \"Criar atalho na área de trabalho\"\nLangString dontUninstall ${LANG_PORTUGUESEBR} \"Não desinstalar\"\nLangString dontUninstallDowngrade ${LANG_PORTUGUESEBR} \"Não desinstalar (Instalar versão anterior sem desinstalar está desabilitado nesse instalador)\"\nLangString failedToKillApp ${LANG_PORTUGUESEBR} \"Falha ao fechar ${PRODUCTNAME}. Por favor feche a janela dele primeiro e tente novamente\"\nLangString installingWebview2 ${LANG_PORTUGUESEBR} \"Instalando WebView2...\"\nLangString newerVersionInstalled ${LANG_PORTUGUESEBR} \"Uma nova versão do ${PRODUCTNAME} já está instalado! Não é recomendado instalar uma versão anterior. Se realmente deseja instalar essa versão antiga, é recomendado desinstalar a versão atual primeirl. Selecione a operação que deseja executare clique Próximo para continuar.\"\nLangString older ${LANG_PORTUGUESEBR} \"mais antiga\"\nLangString olderOrUnknownVersionInstalled ${LANG_PORTUGUESEBR} \"Uma versão $R4 do ${PRODUCTNAME} está instalada no seu sistema. É recomendado desinstalar a versão atual antes de prosseguir com a instalação. Selecione a operação que deseja executare clique Próximo para continuar.\"\nLangString silentDowngrades ${LANG_PORTUGUESEBR} \"Instalar versão anterior está desabilitado nesse instalador, não foi possível proceder com a instalação silenciosa, por favor use a interface gráfica.$\\n\"\nLangString unableToUninstall ${LANG_PORTUGUESEBR} \"Não foi possível instalar!\"\nLangString uninstallApp ${LANG_PORTUGUESEBR} \"Desinstalar ${PRODUCTNAME}\"\nLangString uninstallBeforeInstalling ${LANG_PORTUGUESEBR} \"Desinstalar antes de instalar\"\nLangString unknown ${LANG_PORTUGUESEBR} \"desconhecida\"\nLangString deleteAppData ${LANG_PORTUGUESEBR} \"Remover dados do programa\"\n"
  },
  {
    "path": "crates/packager/src/package/nsis/languages/SimpChinese.nsh",
    "content": "LangString addOrReinstall ${LANG_SIMPCHINESE} \"添加/重新安装组件\"\nLangString alreadyInstalled ${LANG_SIMPCHINESE} \"已安装\"\nLangString alreadyInstalledLong ${LANG_SIMPCHINESE} \"${PRODUCTNAME} ${VERSION} 已经安装了。选择你想要执行的操作后点击下一步以继续。\"\nLangString appRunning ${LANG_SIMPCHINESE} \"${PRODUCTNAME} 正在运行！请关闭后再试。\"\nLangString appRunningOkKill ${LANG_SIMPCHINESE} \"${PRODUCTNAME} 正在运行！$\\n点击确定以终止运行。\"\nLangString chooseMaintenanceOption ${LANG_SIMPCHINESE} \"选择要执行的操作。\"\nLangString choowHowToInstall ${LANG_SIMPCHINESE} \"选择你想要安装 ${PRODUCTNAME} 的方式。\"\nLangString createDesktop ${LANG_SIMPCHINESE} \"创建桌面快捷方式\"\nLangString dontUninstall ${LANG_SIMPCHINESE} \"不要卸载\"\nLangString dontUninstallDowngrade ${LANG_SIMPCHINESE} \"不要卸载（无需卸载的降级在此安装程序上已禁用）\"\nLangString failedToKillApp ${LANG_SIMPCHINESE} \"无法终止 ${PRODUCTNAME}。请关闭后再试。\"\nLangString installingWebview2 ${LANG_SIMPCHINESE} \"正在安装 WebView2...\"\nLangString newerVersionInstalled ${LANG_SIMPCHINESE} \"有一个更新的 ${PRODUCTNAME} 已经安装了！不推荐你安装旧的版本。如果你真的想要安装这个旧的版本，如果你真的想要安装这个版本，推荐先卸载当前版本。选择你想要执行的操作后点击下一步以继续。\"\nLangString older ${LANG_SIMPCHINESE} \"旧的\"\nLangString olderOrUnknownVersionInstalled ${LANG_SIMPCHINESE} \"系统中已存在版本为 $R4 的 ${PRODUCTNAME}。推荐先卸载当前版本后再进行安装。选择你想要执行的操作后点击下一步以继续。\"\nLangString silentDowngrades ${LANG_SIMPCHINESE} \"降级操作在此安装程序上已禁用，无法进行安静安装，请使用图形操作界面。$\\n\"\nLangString unableToUninstall ${LANG_SIMPCHINESE} \"无法卸载！\"\nLangString uninstallApp ${LANG_SIMPCHINESE} \"卸载 ${PRODUCTNAME}\"\nLangString uninstallBeforeInstalling ${LANG_SIMPCHINESE} \"安装前卸载\"\nLangString unknown ${LANG_SIMPCHINESE} \"未知\"\nLangString deleteAppData ${LANG_SIMPCHINESE} \"删除应用程序数据\"\n"
  },
  {
    "path": "crates/packager/src/package/nsis/languages/Spanish.nsh",
    "content": "LangString addOrReinstall ${LANG_SPANISH} \"Añadir o reinstalar componentes\"\nLangString alreadyInstalled ${LANG_SPANISH} \"Ya está instalado\"\nLangString alreadyInstalledLong ${LANG_SPANISH} \"${PRODUCTNAME} ${VERSION} ya está instalado. Seleccione la operación que desee realizar y pulse Siguiente para continuar.\"\nLangString appRunning ${LANG_SPANISH} \"¡${PRODUCTNAME} está abierto! Por favor ciérrelo e intente de nuevo.\"\nLangString appRunningOkKill ${LANG_SPANISH} \"¡${PRODUCTNAME} está abierto!$\\nPulse Aceptar para cerrarlo.\"\nLangString chooseMaintenanceOption ${LANG_SPANISH} \"Elija la operación de mantenimiento que desee realizar.\"\nLangString choowHowToInstall ${LANG_SPANISH} \"Elija cómo desea instalar ${PRODUCTNAME}.\"\nLangString createDesktop ${LANG_SPANISH} \"Crear acceso directo en el escritorio\"\nLangString dontUninstall ${LANG_SPANISH} \"No desinstalar\"\nLangString dontUninstallDowngrade ${LANG_SPANISH} \"No desinstalar (Disminuir la versión sin desinstalar está deshabilitado para este instalador)\"\nLangString failedToKillApp ${LANG_SPANISH} \"No se ha podido cerrar ${PRODUCTNAME}. Por favor ciérrelo e intente de nuevo.\"\nLangString installingWebview2 ${LANG_SPANISH} \"Instalando WebView2...\"\nLangString newerVersionInstalled ${LANG_SPANISH} \"Ya está instalada una versión más reciente de ${PRODUCTNAME}. No se recomienda que instale una versión anterior. Si realmente desea instalar esta versión anterior, es recomendable desinstalar la versión actual antes de continuar. Seleccione la operación que desee realizar y pulse Siguiente para continuar.\"\nLangString older ${LANG_SPANISH} \"anterior\"\nLangString olderOrUnknownVersionInstalled ${LANG_SPANISH} \"Una versión $R4 de ${PRODUCTNAME} está instalada en su sistema. Es recomendable desinstalar la versión actual antes de continuar. Seleccione la operación que desee realizar y pulse Siguiente para continuar.\"\nLangString silentDowngrades ${LANG_SPANISH} \"Disminuir la versión está deshabilitado para este instalador. No se puede continuar con el instalador silencioso, por favor use el instalador de interfaz gráfica.$\\n\"\nLangString unableToUninstall ${LANG_SPANISH} \"No se ha podido desinstalar.\"\nLangString uninstallApp ${LANG_SPANISH} \"Desinstalar ${PRODUCTNAME}\"\nLangString uninstallBeforeInstalling ${LANG_SPANISH} \"Desinstalar antes de instalar\"\nLangString unknown ${LANG_SPANISH} \"desconocida\"\nLangString deleteAppData ${LANG_SPANISH} \"Eliminar los datos de aplicación\"\n"
  },
  {
    "path": "crates/packager/src/package/nsis/languages/SpanishInternational.nsh",
    "content": "LangString addOrReinstall ${LANG_SPANISH} \"Añadir o reinstalar componentes\"\nLangString alreadyInstalled ${LANG_SPANISH} \"Ya está instalado\"\nLangString alreadyInstalledLong ${LANG_SPANISH} \"${PRODUCTNAME} ${VERSION} ya está instalado. Seleccione la operación que desee realizar y pulse Siguiente para continuar.\"\nLangString appRunning ${LANG_SPANISH} \"¡${PRODUCTNAME} está abierto! Por favor ciérrelo e intente de nuevo.\"\nLangString appRunningOkKill ${LANG_SPANISH} \"¡${PRODUCTNAME} está abierto!$\\nPulse Aceptar para cerrarlo.\"\nLangString chooseMaintenanceOption ${LANG_SPANISH} \"Elija la operación de mantenimiento que desee realizar.\"\nLangString choowHowToInstall ${LANG_SPANISH} \"Elija cómo desea instalar ${PRODUCTNAME}.\"\nLangString createDesktop ${LANG_SPANISH} \"Crear acceso directo en el escritorio\"\nLangString dontUninstall ${LANG_SPANISH} \"No desinstalar\"\nLangString dontUninstallDowngrade ${LANG_SPANISH} \"No desinstalar (Disminuir la versión sin desinstalar está deshabilitado para este instalador)\"\nLangString failedToKillApp ${LANG_SPANISH} \"No se ha podido cerrar ${PRODUCTNAME}. Por favor ciérrelo e intente de nuevo.\"\nLangString installingWebview2 ${LANG_SPANISH} \"Instalando WebView2...\"\nLangString newerVersionInstalled ${LANG_SPANISH} \"Ya está instalada una versión más reciente de ${PRODUCTNAME}. No se recomienda que instale una versión anterior. Si realmente desea instalar esta versión anterior, es recomendable desinstalar la versión actual antes de continuar. Seleccione la operación que desee realizar y pulse Siguiente para continuar.\"\nLangString older ${LANG_SPANISH} \"anterior\"\nLangString olderOrUnknownVersionInstalled ${LANG_SPANISH} \"Una versión $R4 de ${PRODUCTNAME} está instalada en su sistema. Es recomendable desinstalar la versión actual antes de continuar. Seleccione la operación que desee realizar y pulse Siguiente para continuar.\"\nLangString silentDowngrades ${LANG_SPANISH} \"Disminuir la versión está deshabilitado para este instalador. No se puede continuar con el instalador silencioso, por favor use el instalador de interfaz gráfica.$\\n\"\nLangString unableToUninstall ${LANG_SPANISH} \"No se ha podido desinstalar.\"\nLangString uninstallApp ${LANG_SPANISH} \"Desinstalar ${PRODUCTNAME}\"\nLangString uninstallBeforeInstalling ${LANG_SPANISH} \"Desinstalar antes de instalar\"\nLangString unknown ${LANG_SPANISH} \"desconocida\"\nLangString deleteAppData ${LANG_SPANISH} \"Eliminar los datos de aplicación\"\n"
  },
  {
    "path": "crates/packager/src/package/nsis/languages/Swedish.nsh",
    "content": "LangString addOrReinstall ${LANG_SWEDISH} \"Lägg till/Installera om komponenter\"\nLangString alreadyInstalled ${LANG_SWEDISH}} \"Redan installerad\"\nLangString alreadyInstalledLong ${LANG_SWEDISH}} \"${PRODUCTNAME} ${VERSION} är redan installerad. Välj åtgärd och klicka på Nästa för att fortsätta.\"\nLangString appRunning ${LANG_SWEDISH} \"${PRODUCTNAME} körs! Stäng det först och försök igen.\"\nLangString appRunningOkKill ${LANG_SWEDISH} \"${PRODUCTNAME} körs!$\\nKlicka på OK för att avsluta det.\"\nLangString chooseMaintenanceOption ${LANG_SWEDISH} \"Välj underhållsåtgärd.\"\nLangString choowHowToInstall ${LANG_SWEDISH} \"Välj hur du vill installera ${PRODUCTNAME}.\"\nLangString createDesktop ${LANG_SWEDISH} \"Skapa genväg på skrivbordet\"\nLangString dontUninstall ${LANG_SWEDISH} \"Avinstallera inte\"\nLangString dontUninstallDowngrade ${LANG_SWEDISH} \"Avinstallera inte (nedgradering utan avinstallation är inaktiverad för den här installationsprogrammet)\"\nLangString failedToKillApp ${LANG_SWEDISH} \"Kunde inte avsluta ${PRODUCTNAME}. Stäng det först och försök igen.\"\nLangString installingWebview2 ${LANG_SWEDISH} \"Installerar WebView2...\"\nLangString newerVersionInstalled ${LANG_SWEDISH} \"En nyare version av ${PRODUCTNAME} är redan installerad! Det rekommenderas inte att installera en äldre version. Om du verkligen vill installera denna äldre version är det bättre att avinstallera den nuvarande versionen först. Välj åtgärd och klicka på Nästa för att fortsätta.\"\nLangString older ${LANG_SWEDISH} \"äldre\"\nLangString olderOrUnknownVersionInstalled ${LANG_SWEDISH} \"En $R4-version av ${PRODUCTNAME} är installerad på ditt system. Det rekommenderas att du avinstallerar den nuvarande versionen innan du installerar. Välj åtgärd och klicka på Nästa för att fortsätta.\"\nLangString silentDowngrades ${LANG_SWEDISH} \"Nedgraderingar är inaktiverade för den här installationsprogrammet. Kan inte fortsätta med installationsprogrammet. Använd det grafiska installationsprogrammet istället.$\\n\"\nLangString unableToUninstall ${LANG_SWEDISH} \"Kan inte avinstallera!\"\nLangString uninstallApp ${LANG_SWEDISH} \"Avinstallera ${PRODUCTNAME}\"\nLangString uninstallBeforeInstalling ${LANG_SWEDISH} \"Avinstallera innan installation\"\nLangString unknown ${LANG_SWEDISH} \"okänd\"\nLangString deleteAppData ${LANG_SWEDISH} \"Ta bort applikationsdata\"\n"
  },
  {
    "path": "crates/packager/src/package/nsis/languages/TradChinese.nsh",
    "content": "LangString addOrReinstall ${LANG_TRADCHINESE} \"增加或重新安裝元件\"\nLangString alreadyInstalled ${LANG_TRADCHINESE} \"已安裝\"\nLangString alreadyInstalledLong ${LANG_TRADCHINESE} \"${PRODUCTNAME} ${VERSION} 已經安裝了。選擇你想要進行的操作並且點選下一步。\"\nLangString appRunning ${LANG_TRADCHINESE} \"${PRODUCTNAME} 正在執行中！請先關閉再進行嘗試。\"\nLangString appRunningOkKill ${LANG_TRADCHINESE} \"${PRODUCTNAME} 正在執行中！點選確定後終止。\"\nLangString chooseMaintenanceOption ${LANG_TRADCHINESE} \"請選擇你要進行的維護選項。\"\nLangString choowHowToInstall ${LANG_TRADCHINESE} \"選擇你要如何安裝 ${PRODUCTNAME}。\"\nLangString createDesktop ${LANG_TRADCHINESE} \"建立桌面捷徑\"\nLangString dontUninstall ${LANG_TRADCHINESE} \"請勿解除安裝\"\nLangString dontUninstallDowngrade ${LANG_TRADCHINESE} \"請勿解除安裝（本安裝程式不允許未解除安裝就進行版本降低的操作）\"\nLangString failedToKillApp ${LANG_TRADCHINESE} \"無法終止 ${PRODUCTNAME}。請先關閉再進行嘗試。\"\nLangString installingWebview2 ${LANG_TRADCHINESE} \"WebView2 安裝中...\"\nLangString newerVersionInstalled ${LANG_TRADCHINESE} \"已安裝更新版本的 ${PRODUCTNAME}！不建議安裝舊版。如果真的想要安裝舊版的話，最好先解除安裝現在的版本。選擇你想要進行的操作後再進行下一步。\"\nLangString older ${LANG_TRADCHINESE} \"舊版\"\nLangString olderOrUnknownVersionInstalled ${LANG_TRADCHINESE} \"你的系統已經安裝 ${PRODUCTNAME} 的 $R4 版本。建議你先解除安裝現在的版本後再進行安裝。選擇你想要進行的操作後再進行下一步。\"\nLangString silentDowngrades ${LANG_TRADCHINESE} \"本安裝程式不允許未解除安裝就進行版本降低的操作，無法繼續無聲安裝，請改用圖形化介面安裝。$\\n\"\nLangString unableToUninstall ${LANG_TRADCHINESE} \"無法解除安裝！\"\nLangString uninstallApp ${LANG_TRADCHINESE} \"解除安裝 ${PRODUCTNAME}\"\nLangString uninstallBeforeInstalling ${LANG_TRADCHINESE} \"安裝前先解除安裝\"\nLangString unknown ${LANG_TRADCHINESE} \"未知\"\nLangString deleteAppData ${LANG_TRADCHINESE} \"刪除應用程式數據\"\n"
  },
  {
    "path": "crates/packager/src/package/nsis/languages/Turkish.nsh",
    "content": "LangString addOrReinstall ${LANG_TURKISH} \"Bileşen Ekle/Yeniden Yükle\"\nLangString alreadyInstalled ${LANG_TURKISH} \"Daha Önceden Yüklenmiş\"\nLangString alreadyInstalledLong ${LANG_TURKISH} \"${PRODUCTNAME} ${VERSION} daha önceden yüklenmiş. Gerçekleştirmek istediğiniz işlemi seçin ve devam etmek için İleri'ye tıklayın.\"\nLangString appRunning ${LANG_TURKISH} \"${PRODUCTNAME} çalışır durumda! Lütfen önce uygulamayı kapatın ve sonra tekrar deneyin.\"\nLangString appRunningOkKill ${LANG_TURKISH} \"${PRODUCTNAME} çalışır durumda!$\\nUygulamayı sonlandırmak için Tamam'a tıklayın.\"\nLangString chooseMaintenanceOption ${LANG_TURKISH} \"Gerçekleştirmek istediğiniz bakım seçeneğini belirleyin.\"\nLangString choowHowToInstall ${LANG_TURKISH} \"${PRODUCTNAME} uygulamasını nasıl yüklemek istediğinizi seçin.\"\nLangString createDesktop ${LANG_TURKISH} \"Masaüstü kısayolu oluştur\"\nLangString dontUninstall ${LANG_TURKISH} \"Kaldırma işlemini gerçekleştirme\"\nLangString dontUninstallDowngrade ${LANG_TURKISH} \"Kaldırma işlemini gerçekleştirme (Kaldırma işlemi yapmadan sürüm düşürme bu yükleyici için devre dışı bırakılmıştır)\"\nLangString failedToKillApp ${LANG_TURKISH} \"${PRODUCTNAME} sonlandırılamadı. Lütfen önce kapatın sonra tekrar deneyin.\"\nLangString installingWebview2 ${LANG_TURKISH} \"WebView2 yükleniyor...\"\nLangString newerVersionInstalled ${LANG_TURKISH} \"${PRODUCTNAME} uygulamasının daha yeni bir sürümü zaten yüklü! Daha eski bir sürümü yüklemeniz önerilmez. Bu eski sürümü gerçekten yüklemek istiyorsanız, önce mevcut sürümü kaldırmanız daha uygundur. Gerçekleştirmek istediğiniz işlemi seçin ve devam etmek için İleri'ye tıklayın.\"\nLangString older ${LANG_TURKISH} \"daha eski\"\nLangString olderOrUnknownVersionInstalled ${LANG_TURKISH} \"Sisteminizde ${PRODUCTNAME} uygulamasının $R4 sürümü yüklü. Yükleme işleminden önce mevcut sürümü kaldırmanız önerilir. Gerçekleştirmek istediğiniz işlemi seçin ve devam etmek için İleri'ye tıklayın.\"\nLangString silentDowngrades ${LANG_TURKISH} \"Bu yükleyici için sürüm düşürme işlemleri devre dışı bırakıldı, sessiz yükleyici ile devam edilemiyor, lütfen bunun yerine grafik arayüz yükleyicisini kullanın.$\\n\"\nLangString unableToUninstall ${LANG_TURKISH} \"Kaldırma işlemi gerçekleştirilemiyor!\"\nLangString uninstallApp ${LANG_TURKISH} \"${PRODUCTNAME}'i kaldır\"\nLangString uninstallBeforeInstalling ${LANG_TURKISH} \"Yükleme yapmadan önce kaldırın\"\nLangString unknown ${LANG_TURKISH} \"bilinmeyen\"\nLangString deleteAppData ${LANG_TURKISH} \"Uygulama verilerini sil\"\n"
  },
  {
    "path": "crates/packager/src/package/nsis/mod.rs",
    "content": "// Copyright 2019-2023 Tauri Programme within The Commons Conservancy\n// Copyright 2023-2023 CrabNebula Ltd.\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{\n    collections::{BTreeMap, BTreeSet, HashMap},\n    fmt::Debug,\n    fs,\n    path::{Path, PathBuf},\n    process::Command,\n};\n\nuse handlebars::{to_json, Handlebars};\n\nuse super::Context;\nuse crate::{\n    codesign::windows::{self as codesign},\n    util::verify_file_hash,\n    Error,\n};\nuse crate::{\n    config::{Config, LogLevel, NSISInstallerMode, NsisCompression},\n    shell::CommandExt,\n    util::{self, download, download_and_verify, extract_zip, HashAlgorithm},\n};\n\n// URLS for the NSIS toolchain.\n#[cfg(target_os = \"windows\")]\nconst NSIS_URL: &str =\n    \"https://github.com/tauri-apps/binary-releases/releases/download/nsis-3.9/nsis-3.09.zip\";\n#[cfg(target_os = \"windows\")]\nconst NSIS_SHA1: &str = \"586855a743a6e0ade203d8758af303a48ee0716b\";\nconst NSIS_APPLICATIONID_URL: &str = \"https://github.com/tauri-apps/binary-releases/releases/download/nsis-plugins-v0/NSIS-ApplicationID.zip\";\nconst NSIS_TAURI_UTILS_URL: &str =\n  \"https://github.com/tauri-apps/nsis-tauri-utils/releases/download/nsis_tauri_utils-v0.2.1/nsis_tauri_utils.dll\";\nconst NSIS_TAURI_UTILS_SHA1: &str = \"53A7CFAEB6A4A9653D6D5FBFF02A3C3B8720130A\";\n\n#[cfg(target_os = \"windows\")]\nconst NSIS_REQUIRED_FILES: &[&str] = &[\n    \"makensis.exe\",\n    \"Bin/makensis.exe\",\n    \"Stubs/lzma-x86-unicode\",\n    \"Stubs/lzma_solid-x86-unicode\",\n    \"Plugins/x86-unicode/ApplicationID.dll\",\n    \"Plugins/x86-unicode/nsis_tauri_utils.dll\",\n    \"Include/MUI2.nsh\",\n    \"Include/FileFunc.nsh\",\n    \"Include/x64.nsh\",\n    \"Include/nsDialogs.nsh\",\n    \"Include/WinMessages.nsh\",\n];\n#[cfg(not(target_os = \"windows\"))]\nconst NSIS_REQUIRED_FILES: &[&str] = &[\n    \"Plugins/x86-unicode/ApplicationID.dll\",\n    \"Plugins/x86-unicode/nsis_tauri_utils.dll\",\n];\n\nconst NSIS_REQUIRED_FILES_HASH: &[(&str, &str, &str, HashAlgorithm)] = &[(\n    \"Plugins/x86-unicode/nsis_tauri_utils.dll\",\n    NSIS_TAURI_UTILS_URL,\n    NSIS_TAURI_UTILS_SHA1,\n    HashAlgorithm::Sha1,\n)];\n\ntype DirectoriesSet = BTreeSet<PathBuf>;\ntype ResourcesMap = BTreeMap<PathBuf, PathBuf>;\n\n#[cfg(windows)]\nfn normalize_resource_path<P: AsRef<Path>>(path: P) -> PathBuf {\n    path.as_ref().to_owned()\n}\n\n// We need to convert / to \\ for nsis to move the files into the correct dirs\n#[cfg(not(windows))]\nfn normalize_resource_path<P: AsRef<Path>>(path: P) -> PathBuf {\n    path.as_ref()\n        .display()\n        .to_string()\n        .replace('/', \"\\\\\")\n        .into()\n}\n\n#[tracing::instrument(level = \"trace\", skip(config))]\nfn generate_resource_data(config: &Config) -> crate::Result<(DirectoriesSet, ResourcesMap)> {\n    let mut directories = BTreeSet::new();\n    let mut resources_map = BTreeMap::new();\n    for r in config.resources()? {\n        // only add if resource has a parent e.g. `files/a.txt`\n        // and is not empty. this is to ensure that we don't\n        // generate `CreateDirectory \"$INSTDIR\\\"` which is useless\n        // since `INSTDIR` is already created.\n        if let Some(parent) = r.target.parent() {\n            if parent.as_os_str() != \"\" {\n                directories.insert(normalize_resource_path(parent));\n            }\n        }\n\n        resources_map.insert(r.src, normalize_resource_path(r.target));\n    }\n    Ok((directories, resources_map))\n}\n\n/// BTreeMap<OriginalPath, TargetFileName>\ntype BinariesMap = BTreeMap<PathBuf, String>;\n#[tracing::instrument(level = \"trace\", skip(config))]\nfn generate_binaries_data(config: &Config) -> crate::Result<BinariesMap> {\n    let mut binaries = BinariesMap::new();\n\n    if let Some(external_binaries) = &config.external_binaries {\n        let cwd = std::env::current_dir()?;\n        let target_triple = config.target_triple();\n        for src in external_binaries {\n            let file_name = src\n                .file_name()\n                .ok_or_else(|| Error::FailedToExtractFilename(src.clone()))?\n                .to_string_lossy();\n            let src = src.with_file_name(format!(\"{file_name}-{target_triple}.exe\"));\n            let bin_path = cwd.join(src);\n            let bin_path =\n                dunce::canonicalize(&bin_path).map_err(|e| Error::IoWithPath(bin_path, e))?;\n            let dest_file_name = format!(\"{file_name}.exe\");\n            binaries.insert(bin_path, dest_file_name);\n        }\n    }\n\n    for bin in &config.binaries {\n        if !bin.main {\n            let bin_path = config.binary_path(bin).with_extension(\"exe\");\n            let dest_filename = bin_path\n                .file_name()\n                .ok_or_else(|| Error::FailedToExtractFilename(bin_path.clone()))?\n                .to_string_lossy()\n                .to_string();\n            binaries.insert(bin_path, dest_filename);\n        }\n    }\n\n    Ok(binaries)\n}\n\n#[tracing::instrument(level = \"trace\")]\nfn get_lang_data(\n    lang: &str,\n    custom_lang_files: Option<&HashMap<String, PathBuf>>,\n) -> crate::Result<Option<(PathBuf, Option<&'static str>)>> {\n    if let Some(path) = custom_lang_files.and_then(|h| h.get(lang)) {\n        let canonicalized =\n            dunce::canonicalize(path).map_err(|e| Error::IoWithPath(path.clone(), e))?;\n        return Ok(Some((canonicalized, None)));\n    }\n\n    let lang_path = PathBuf::from(format!(\"{lang}.nsh\"));\n    let lang_content = match lang.to_lowercase().as_str() {\n        \"arabic\" => Some(include_str!(\"./languages/Arabic.nsh\")),\n        \"bulgarian\" => Some(include_str!(\"./languages/Bulgarian.nsh\")),\n        \"dutch\" => Some(include_str!(\"./languages/Dutch.nsh\")),\n        \"english\" => Some(include_str!(\"./languages/English.nsh\")),\n        \"japanese\" => Some(include_str!(\"./languages/Japanese.nsh\")),\n        \"korean\" => Some(include_str!(\"./languages/Korean.nsh\")),\n        \"portuguesebr\" => Some(include_str!(\"./languages/PortugueseBR.nsh\")),\n        \"tradchinese\" => Some(include_str!(\"./languages/TradChinese.nsh\")),\n        \"simpchinese\" => Some(include_str!(\"./languages/SimpChinese.nsh\")),\n        \"french\" => Some(include_str!(\"./languages/French.nsh\")),\n        \"spanish\" => Some(include_str!(\"./languages/Spanish.nsh\")),\n        \"spanishinternational\" => Some(include_str!(\"./languages/SpanishInternational.nsh\")),\n        \"persian\" => Some(include_str!(\"./languages/Persian.nsh\")),\n        \"turkish\" => Some(include_str!(\"./languages/Turkish.nsh\")),\n        \"swedish\" => Some(include_str!(\"./languages/Swedish.nsh\")),\n        _ => return Ok(None),\n    };\n\n    Ok(Some((lang_path, lang_content)))\n}\n\n#[tracing::instrument(level = \"trace\")]\nfn write_ut16_le_with_bom<P: AsRef<Path> + Debug>(path: P, content: &str) -> crate::Result<()> {\n    tracing::debug!(\"Writing {path:?} in UTF-16 LE encoding\");\n\n    use std::fs::File;\n    use std::io::{BufWriter, Write};\n\n    let path = path.as_ref();\n    let file = File::create(path).map_err(|e| Error::IoWithPath(path.to_path_buf(), e))?;\n    let mut output = BufWriter::new(file);\n    output.write_all(&[0xFF, 0xFE])?; // the BOM part\n    for utf16 in content.encode_utf16() {\n        output.write_all(&utf16.to_le_bytes())?;\n    }\n    Ok(())\n}\n\nfn handlebars_or(\n    h: &handlebars::Helper<'_>,\n    _: &Handlebars<'_>,\n    _: &handlebars::Context,\n    _: &mut handlebars::RenderContext<'_, '_>,\n    out: &mut dyn handlebars::Output,\n) -> handlebars::HelperResult {\n    let param1 = h.param(0).unwrap().render();\n    let param2 = h.param(1).unwrap();\n\n    out.write(&if param1.is_empty() {\n        param2.render()\n    } else {\n        param1\n    })?;\n    Ok(())\n}\n\nfn association_description(\n    h: &handlebars::Helper<'_>,\n    _: &Handlebars<'_>,\n    _: &handlebars::Context,\n    _: &mut handlebars::RenderContext<'_, '_>,\n    out: &mut dyn handlebars::Output,\n) -> handlebars::HelperResult {\n    let description = h.param(0).unwrap().render();\n    let ext = h.param(1).unwrap();\n\n    out.write(&if description.is_empty() {\n        format!(\"{} File\", ext.render().to_uppercase())\n    } else {\n        description\n    })?;\n    Ok(())\n}\n\nfn unescape_newlines(\n    h: &handlebars::Helper<'_>,\n    _: &Handlebars<'_>,\n    _: &handlebars::Context,\n    _: &mut handlebars::RenderContext<'_, '_>,\n    out: &mut dyn handlebars::Output,\n) -> handlebars::HelperResult {\n    let s = h.param(0).unwrap().render();\n    out.write(&s.replace(\"$\\\\n\", \"\\n\"))?;\n    Ok(())\n}\n\nfn unescape_dollar_sign(\n    h: &handlebars::Helper<'_>,\n    _: &Handlebars<'_>,\n    _: &handlebars::Context,\n    _: &mut handlebars::RenderContext<'_, '_>,\n    out: &mut dyn handlebars::Output,\n) -> handlebars::HelperResult {\n    let s = h.param(0).unwrap().render();\n    out.write(&s.replace(\"$$\", \"$\"))?;\n    Ok(())\n}\n\nfn add_build_number_if_needed(version_str: &str) -> crate::Result<String> {\n    let version = semver::Version::parse(version_str)?;\n    if !version.build.is_empty() {\n        let build = version.build.parse::<u64>();\n        if build.is_ok() {\n            return Ok(format!(\n                \"{}.{}.{}.{}\",\n                version.major, version.minor, version.patch, version.build\n            ));\n        } else {\n            return Err(Error::NonNumericBuildMetadata(None));\n        }\n    }\n\n    Ok(format!(\n        \"{}.{}.{}.0\",\n        version.major, version.minor, version.patch,\n    ))\n}\n\nfn file_len<P: AsRef<Path>>(p: P) -> crate::Result<u64> {\n    fs::metadata(&p)\n        .map(|m| m.len())\n        .map_err(|e| Error::IoWithPath(p.as_ref().to_path_buf(), e))\n}\n\nfn generate_estimated_size<I, P, P2>(main: P, other_files: I) -> crate::Result<String>\nwhere\n    I: IntoIterator<Item = P2>,\n    P: AsRef<Path>,\n    P2: AsRef<Path>,\n{\n    let mut size = file_len(main)?;\n\n    for k in other_files {\n        size += file_len(k)?;\n    }\n\n    size /= 1000;\n\n    Ok(format!(\"{size:#08x}\"))\n}\n\n#[tracing::instrument(level = \"trace\", skip(ctx))]\nfn get_and_extract_nsis(\n    #[allow(unused)] ctx: &Context,\n    nsis_toolset_path: &Path,\n) -> crate::Result<()> {\n    #[cfg(target_os = \"windows\")]\n    {\n        let data = download_and_verify(\"nsis-3.09.zip\", NSIS_URL, NSIS_SHA1, HashAlgorithm::Sha1)?;\n        tracing::debug!(\"Extracting nsis-3.09.zip\");\n        extract_zip(&data, &ctx.tools_path)?;\n        let downloaded_nsis = ctx.tools_path.join(\"nsis-3.09\");\n        fs::rename(&downloaded_nsis, nsis_toolset_path)\n            .map_err(|e| Error::RenameFile(downloaded_nsis, nsis_toolset_path.to_path_buf(), e))?;\n    }\n\n    let nsis_plugins = nsis_toolset_path.join(\"Plugins\");\n\n    let unicode_plugins = nsis_plugins.join(\"x86-unicode\");\n    fs::create_dir_all(&unicode_plugins)\n        .map_err(|e| Error::IoWithPath(unicode_plugins.clone(), e))?;\n\n    let data = download(NSIS_APPLICATIONID_URL)?;\n    tracing::debug!(\"Extracting NSIS ApplicationID plugin\");\n    extract_zip(&data, &nsis_plugins)?;\n\n    let src = nsis_plugins.join(\"ReleaseUnicode/ApplicationID.dll\");\n    let dest = unicode_plugins.join(\"ApplicationID.dll\");\n    fs::copy(&src, &dest).map_err(|e| Error::CopyFile(src, dest, e))?;\n\n    let data = download_and_verify(\n        \"nsis_tauri_utils.dll\",\n        NSIS_TAURI_UTILS_URL,\n        NSIS_TAURI_UTILS_SHA1,\n        HashAlgorithm::Sha1,\n    )?;\n    let path = unicode_plugins.join(\"nsis_tauri_utils.dll\");\n    fs::write(&path, data).map_err(|e| Error::IoWithPath(path, e))?;\n\n    Ok(())\n}\n\n#[tracing::instrument(level = \"trace\", skip(ctx))]\nfn build_nsis_app_installer(ctx: &Context, nsis_path: &Path) -> crate::Result<Vec<PathBuf>> {\n    let Context {\n        config,\n        intermediates_path,\n        ..\n    } = ctx;\n\n    let arch = match config.target_arch()? {\n        \"x86_64\" => \"x64\",\n        \"x86\" => \"x86\",\n        \"aarch64\" => \"arm64\",\n        target => return Err(Error::UnsupportedArch(\"nsis\".into(), target.into())),\n    };\n\n    let main_binary = config.main_binary()?;\n    let main_binary_name = config.main_binary_name()?;\n    let main_binary_path = config.binary_path(main_binary).with_extension(\"exe\");\n\n    if config.can_sign() {\n        tracing::debug!(\"Codesigning {}\", main_binary_path.display());\n        codesign::try_sign(&main_binary_path, config)?;\n    } else {\n        #[cfg(not(target_os = \"windows\"))]\n        tracing::warn!(\"Codesigning is by default is only supported on Windows hosts, but you can specify a custom signing command in `config.windows.sign_command`, for now, skipping signing the main binary...\");\n    }\n\n    let intermediates_path = intermediates_path.join(\"nsis\").join(arch);\n    util::create_clean_dir(&intermediates_path)?;\n\n    let mut data = BTreeMap::new();\n\n    #[cfg(not(target_os = \"windows\"))]\n    {\n        let dir = nsis_path.join(\"Plugins/x86-unicode\");\n        data.insert(\"additional_plugins_path\", to_json(dir));\n    }\n\n    let identifier = config.identifier();\n    let manufacturer = config.publisher();\n\n    data.insert(\"arch\", to_json(arch));\n    data.insert(\"identifier\", to_json(identifier));\n    data.insert(\"manufacturer\", to_json(&manufacturer));\n    data.insert(\"product_name\", to_json(&config.product_name));\n    data.insert(\"short_description\", to_json(&config.description));\n    data.insert(\"copyright\", to_json(&config.copyright));\n    data.insert(\"version\", to_json(&config.version));\n    data.insert(\n        \"version_with_build\",\n        to_json(add_build_number_if_needed(&config.version)?),\n    );\n    data.insert(\n        \"allow_downgrades\",\n        to_json(config.windows().map(|w| w.allow_downgrades)),\n    );\n\n    if config.can_sign() {\n        let sign_cmd = format!(\"{:?}\", codesign::sign_command(\"%1\", &config.sign_params())?);\n        data.insert(\"uninstaller_sign_cmd\", to_json(sign_cmd));\n    }\n\n    if let Some(license) = &config.license_file {\n        let canonicalized =\n            dunce::canonicalize(license).map_err(|e| Error::IoWithPath(license.clone(), e))?;\n        data.insert(\"license\", to_json(canonicalized));\n    }\n\n    let mut install_mode = NSISInstallerMode::CurrentUser;\n    let mut languages = vec![\"English\".into()];\n    let mut custom_template_path = None;\n    let mut custom_language_files = None;\n    if let Some(nsis) = config.nsis() {\n        custom_template_path.clone_from(&nsis.template);\n        custom_language_files.clone_from(&nsis.custom_language_files);\n        install_mode = nsis.install_mode;\n        if let Some(langs) = &nsis.languages {\n            languages.clear();\n            languages.extend_from_slice(langs);\n        }\n        data.insert(\n            \"display_language_selector\",\n            to_json(nsis.display_language_selector && languages.len() > 1),\n        );\n        if let Some(installer_icon) = &nsis.installer_icon {\n            let canonicalized = dunce::canonicalize(installer_icon)\n                .map_err(|e| Error::IoWithPath(installer_icon.clone(), e))?;\n            data.insert(\"installer_icon\", to_json(canonicalized));\n        }\n        if let Some(header_image) = &nsis.header_image {\n            let canonicalized = dunce::canonicalize(header_image)\n                .map_err(|e| Error::IoWithPath(header_image.clone(), e))?;\n            data.insert(\"header_image\", to_json(canonicalized));\n        }\n        if let Some(sidebar_image) = &nsis.sidebar_image {\n            let canonicalized = dunce::canonicalize(sidebar_image)\n                .map_err(|e| Error::IoWithPath(sidebar_image.clone(), e))?;\n            data.insert(\"sidebar_image\", to_json(canonicalized));\n        }\n        if let Some(preinstall_section) = &nsis.preinstall_section {\n            data.insert(\"preinstall_section\", to_json(preinstall_section));\n        }\n        if let Some(compression) = &nsis.compression {\n            data.insert(\n                \"compression\",\n                to_json(match &compression {\n                    NsisCompression::Zlib => \"zlib\",\n                    NsisCompression::Bzip2 => \"bzip2\",\n                    NsisCompression::Lzma => \"lzma\",\n                    NsisCompression::Off => \"off\",\n                }),\n            );\n        }\n        if let Some(appdata_paths) = &nsis.appdata_paths {\n            let appdata_paths = appdata_paths\n                .iter()\n                .map(|p| {\n                    p.replace(\"$PUBLISHER\", &manufacturer)\n                        .replace(\"$PRODUCTNAME\", &config.product_name)\n                        .replace(\"$IDENTIFIER\", config.identifier())\n                })\n                .collect::<Vec<_>>();\n            data.insert(\"appdata_paths\", to_json(appdata_paths));\n        }\n    }\n\n    data.insert(\"install_mode\", to_json(install_mode));\n\n    let mut languages_data = Vec::new();\n    for lang in &languages {\n        if let Some(data) = get_lang_data(lang, custom_language_files.as_ref())? {\n            languages_data.push(data);\n        } else {\n            tracing::warn!(\"Custom cargo-packager messages for {lang} are not translated.\\nIf it is a valid language listed on <https://github.com/kichik/nsis/tree/9465c08046f00ccb6eda985abbdbf52c275c6c4d/Contrib/Language%20files>, please open a cargo-packager feature request\\n or you can provide a custom language file for it in ` nsis.custom_language_files`\");\n        }\n    }\n    data.insert(\"languages\", to_json(languages.clone()));\n    data.insert(\n        \"language_files\",\n        to_json(\n            languages_data\n                .iter()\n                .map(|d| d.0.clone())\n                .collect::<Vec<_>>(),\n        ),\n    );\n\n    data.insert(\"main_binary_name\", to_json(&main_binary_name));\n    data.insert(\"main_binary_path\", to_json(&main_binary_path));\n\n    if let Some(file_associations) = &config.file_associations {\n        data.insert(\"file_associations\", to_json(file_associations));\n    }\n\n    if let Some(protocols) = &config.deep_link_protocols {\n        let schemes = protocols\n            .iter()\n            .flat_map(|p| &p.schemes)\n            .collect::<Vec<_>>();\n        data.insert(\"deep_link_protocols\", to_json(schemes));\n    }\n\n    let out_file = \"nsis-output.exe\";\n    data.insert(\"out_file\", to_json(out_file));\n\n    let (resources_dirs, resources) = generate_resource_data(config)?;\n    data.insert(\"resources_dirs\", to_json(resources_dirs));\n    data.insert(\"resources\", to_json(&resources));\n\n    let binaries = generate_binaries_data(config)?;\n    data.insert(\"binaries\", to_json(&binaries));\n\n    let estimated_size =\n        generate_estimated_size(main_binary_path, resources.keys().chain(binaries.keys()))?;\n    data.insert(\"estimated_size\", to_json(estimated_size));\n\n    let mut handlebars = Handlebars::new();\n    handlebars.register_helper(\"or\", Box::new(handlebars_or));\n    handlebars.register_helper(\"association-description\", Box::new(association_description));\n    handlebars.register_helper(\"unescape_newlines\", Box::new(unescape_newlines));\n    handlebars.register_helper(\"unescape_dollar_sign\", Box::new(unescape_dollar_sign));\n    handlebars.register_escape_fn(|s| {\n        let mut output = String::new();\n        for c in s.chars() {\n            match c {\n                '\\\"' => output.push_str(\"$\\\\\\\"\"),\n                '$' => output.push_str(\"$$\"),\n                '`' => output.push_str(\"$\\\\`\"),\n                '\\n' => output.push_str(\"$\\\\n\"),\n                '\\t' => output.push_str(\"$\\\\t\"),\n                '\\r' => output.push_str(\"$\\\\r\"),\n                _ => output.push(c),\n            }\n        }\n        output\n    });\n    if let Some(path) = custom_template_path {\n        handlebars\n            .register_template_string(\"installer.nsi\", fs::read_to_string(path)?)\n            .map_err(Box::new)?;\n    } else {\n        handlebars\n            .register_template_string(\"installer.nsi\", include_str!(\"./installer.nsi\"))\n            .map_err(Box::new)?;\n    }\n\n    write_ut16_le_with_bom(\n        intermediates_path.join(\"FileAssociation.nsh\"),\n        include_str!(\"./FileAssociation.nsh\"),\n    )?;\n\n    let installer_nsi_path = intermediates_path.join(\"installer.nsi\");\n    write_ut16_le_with_bom(\n        &installer_nsi_path,\n        handlebars.render(\"installer.nsi\", &data)?.as_str(),\n    )?;\n\n    for (lang, data) in languages_data.iter() {\n        if let Some(content) = data {\n            write_ut16_le_with_bom(intermediates_path.join(lang).with_extension(\"nsh\"), content)?;\n        }\n    }\n\n    let nsis_output_path = intermediates_path.join(out_file);\n\n    let installer_path = config.out_dir().join(format!(\n        \"{}_{}_{}-setup.exe\",\n        main_binary_name, config.version, arch\n    ));\n\n    let installer_path_parent = installer_path\n        .parent()\n        .ok_or_else(|| Error::ParentDirNotFound(installer_path.clone()))?;\n    fs::create_dir_all(installer_path_parent)\n        .map_err(|e| Error::IoWithPath(installer_path_parent.to_path_buf(), e))?;\n\n    tracing::info!(\n        \"Running makensis.exe to produce {}\",\n        util::display_path(&installer_path)\n    );\n    #[cfg(target_os = \"windows\")]\n    let mut nsis_cmd = Command::new(nsis_path.join(\"makensis.exe\"));\n    #[cfg(not(target_os = \"windows\"))]\n    let mut nsis_cmd = Command::new(\"makensis\");\n\n    if let Some(level) = config.log_level {\n        nsis_cmd.arg(match level {\n            LogLevel::Error => \"-V1\",\n            LogLevel::Warn | LogLevel::Info => \"-V2\",\n            LogLevel::Debug => \"-V3\",\n            _ => \"-V4\",\n        });\n    }\n\n    nsis_cmd\n        .arg(installer_nsi_path)\n        .current_dir(intermediates_path)\n        .output_ok()\n        .map_err(Error::NsisFailed)?;\n\n    fs::rename(&nsis_output_path, &installer_path)\n        .map_err(|e| Error::RenameFile(nsis_output_path, installer_path.clone(), e))?;\n\n    if config.can_sign() {\n        tracing::debug!(\"Codesigning {}\", installer_path.display());\n        codesign::try_sign(&installer_path, config)?;\n    } else {\n        #[cfg(not(target_os = \"windows\"))]\n        tracing::warn!(\"Codesigning is by default is only supported on Windows hosts, but you can specify a custom signing command in `config.windows.sign_command`, for now, skipping signing the installer...\");\n    }\n\n    Ok(vec![installer_path])\n}\n\n#[tracing::instrument(level = \"trace\", skip(ctx))]\npub(crate) fn package(ctx: &Context) -> crate::Result<Vec<PathBuf>> {\n    let nsis_toolset_path = ctx.tools_path.join(\"NSIS\");\n\n    if !nsis_toolset_path.exists() {\n        get_and_extract_nsis(ctx, &nsis_toolset_path)?;\n    } else if NSIS_REQUIRED_FILES\n        .iter()\n        .any(|p| !nsis_toolset_path.join(p).exists())\n    {\n        tracing::warn!(\"NSIS directory is missing some files. Recreating it...\");\n        fs::remove_dir_all(&nsis_toolset_path)\n            .map_err(|e| Error::IoWithPath(nsis_toolset_path.clone(), e))?;\n        get_and_extract_nsis(ctx, &nsis_toolset_path)?;\n    } else {\n        let mismatched = NSIS_REQUIRED_FILES_HASH\n            .iter()\n            .filter(|(p, _, hash, hash_algorithm)| {\n                verify_file_hash(nsis_toolset_path.join(p), hash, *hash_algorithm).is_err()\n            })\n            .collect::<Vec<_>>();\n\n        if !mismatched.is_empty() {\n            tracing::warn!(\"NSIS directory contains mis-hashed files. Redownloading them.\");\n            for (path, url, hash, hash_algorithim) in mismatched {\n                let path = nsis_toolset_path.join(path);\n                let data = download_and_verify(&path, url, hash, *hash_algorithim)?;\n                fs::write(&path, data).map_err(|e| Error::IoWithPath(path, e))?;\n            }\n        }\n    }\n\n    build_nsis_app_installer(ctx, &nsis_toolset_path)\n}\n"
  },
  {
    "path": "crates/packager/src/package/pacman/mod.rs",
    "content": "// Copyright 2024-2024 CrabNebula Ltd.\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse super::deb;\nuse crate::{config::Config, package::Context, util, Error};\nuse heck::AsKebabCase;\nuse sha2::{Digest, Sha512};\nuse std::{\n    fs::{self, File},\n    io::{self, Write},\n    path::{Path, PathBuf},\n};\n\n#[tracing::instrument(level = \"trace\", skip(ctx))]\npub(crate) fn package(ctx: &Context) -> crate::Result<Vec<PathBuf>> {\n    let Context {\n        config,\n        intermediates_path,\n        ..\n    } = ctx;\n\n    let arch = match config.target_arch()? {\n        \"x86\" => \"i386\",\n        \"arm\" => \"armhf\",\n        other => other,\n    };\n\n    let intermediates_path = intermediates_path.join(\"pacman\");\n    util::create_clean_dir(&intermediates_path)?;\n\n    let package_base_name = format!(\"{}_{}_{}\", config.main_binary_name()?, config.version, arch);\n    let package_name = format!(\"{package_base_name}.tar.gz\");\n\n    let pkg_dir = intermediates_path.join(&package_base_name);\n    let pkg_path = config.out_dir().join(&package_name);\n    let pkgbuild_path = pkg_path.with_file_name(\"PKGBUILD\");\n\n    tracing::info!(\"Packaging {} ({})\", package_name, pkg_path.display());\n\n    tracing::debug!(\"Generating data\");\n    let _ = deb::generate_data(config, &pkg_dir)?;\n\n    tracing::debug!(\"Copying files specified in `pacman.files`\");\n    if let Some(files) = config.pacman().and_then(|d| d.files.as_ref()) {\n        deb::copy_custom_files(files, &pkg_dir)?;\n    }\n\n    // Apply tar/gzip to create the final package file.\n    tracing::debug!(\"Creating package archive using tar and gzip\");\n    let data_tar_gz_path = deb::tar_and_gzip_dir(pkg_dir)?;\n    fs::copy(&data_tar_gz_path, &pkg_path)\n        .map_err(|e| Error::CopyFile(data_tar_gz_path, pkg_path.clone(), e))?;\n\n    tracing::info!(\"Generating PKGBUILD: {}\", pkgbuild_path.display());\n    generate_pkgbuild_file(config, arch, pkgbuild_path.as_path(), pkg_path.as_path())?;\n\n    Ok(vec![pkg_path])\n}\n\n/// Generates the pacman PKGBUILD file.\n/// For more information about the format of this file, see\n/// <https://wiki.archlinux.org/title/PKGBUILD>\nfn generate_pkgbuild_file(\n    config: &Config,\n    arch: &str,\n    dest_dir: &Path,\n    package_path: &Path,\n) -> crate::Result<()> {\n    let pkgbuild_path = dest_dir.with_file_name(\"PKGBUILD\");\n    let mut file = util::create_file(&pkgbuild_path)?;\n\n    if let Some(authors) = &config.authors {\n        writeln!(file, \"# Maintainer: {}\", authors.join(\", \"))?;\n    }\n    writeln!(file, \"pkgname={}\", AsKebabCase(&config.product_name))?;\n    writeln!(file, \"pkgver={}\", config.version)?;\n    writeln!(file, \"pkgrel=1\")?;\n    writeln!(file, \"epoch=\")?;\n    writeln!(\n        file,\n        \"pkgdesc=\\\"{}\\\"\",\n        config.description.as_deref().unwrap_or(\"\")\n    )?;\n    writeln!(file, \"arch=('{arch}')\")?;\n\n    if let Some(homepage) = &config.homepage {\n        writeln!(file, \"url=\\\"{homepage}\\\"\")?;\n    }\n\n    let dependencies = config\n        .pacman()\n        .and_then(|d| d.depends.as_ref())\n        .map_or_else(|| Ok(Vec::new()), |d| d.to_list())?;\n    writeln!(file, \"depends=({})\", dependencies.join(\" \\n\"))?;\n\n    let provides = config\n        .pacman()\n        .and_then(|d| d.provides.clone())\n        .unwrap_or_default();\n    writeln!(file, \"provides=({})\", provides.join(\" \\n\"))?;\n\n    let conflicts = config\n        .pacman()\n        .and_then(|d| d.conflicts.clone())\n        .unwrap_or_default();\n    writeln!(file, \"conflicts=({})\", conflicts.join(\" \\n\"))?;\n\n    let replaces = config\n        .pacman()\n        .and_then(|d| d.replaces.clone())\n        .unwrap_or_default();\n    writeln!(file, \"replaces=({})\", replaces.join(\" \\n\"))?;\n\n    writeln!(file, \"options=(!lto)\")?;\n    let source = config\n        .pacman()\n        .and_then(|d| d.source.clone())\n        .unwrap_or_default();\n\n    if source.is_empty() {\n        writeln!(file, \"source=({:?})\", package_path.file_name().unwrap())?;\n    } else {\n        writeln!(file, \"source=({})\", source.join(\" \\n\"))?;\n    }\n\n    // Generate SHA512 sum of the package\n    let mut sha_file =\n        File::open(package_path).map_err(|e| Error::IoWithPath(package_path.to_path_buf(), e))?;\n    let mut sha512 = Sha512::new();\n    io::copy(&mut sha_file, &mut sha512)?;\n    let sha_hash = sha512.finalize();\n\n    writeln!(file, \"sha512sums=(\\\"{sha_hash:x}\\\")\")?;\n    writeln!(\n        file,\n        \"package() {{\\n\\tcp -r \\\"${{srcdir}}\\\"/* \\\"${{pkgdir}}\\\"/\\n}}\"\n    )?;\n\n    file.flush()?;\n    Ok(())\n}\n"
  },
  {
    "path": "crates/packager/src/package/wix/default-locale-strings.xml",
    "content": "<String Id=\"CargoPackagerLanguage\">__language__</String>\n<String Id=\"CargoPackagerCodepage\">__codepage__</String>\n<String Id=\"LaunchApp\">Launch __productName__</String>\n<String Id=\"DowngradeErrorMessage\">A newer version of __productName__ is already installed.</String>\n<String Id=\"PathEnvVarFeature\">Add the install location of the __productName__ executable to the PATH system environment variable. This allows the __productName__ executable to be called from any location.</String>\n<String Id=\"InstallAppFeature\">Installs __productName__.</String>"
  },
  {
    "path": "crates/packager/src/package/wix/languages.json",
    "content": "{\n  \"ar-SA\": {\n    \"langId\": 1025,\n    \"asciiCode\": 1256\n  },\n  \"ca-ES\": {\n    \"langId\": 1027,\n    \"asciiCode\": 1252\n  },\n  \"zh-TW\": {\n    \"langId\": 1028,\n    \"asciiCode\": 950\n  },\n  \"zh-CN\": {\n    \"langId\": 2052,\n    \"asciiCode\": 936\n  },\n  \"cs-CZ\": {\n    \"langId\": 1029,\n    \"asciiCode\": 1250\n  },\n  \"da-DK\": {\n    \"langId\": 1030,\n    \"asciiCode\": 1252\n  },\n  \"de-DE\": {\n    \"langId\": 1031,\n    \"asciiCode\": 1252\n  },\n  \"el-GR\": {\n    \"langId\": 1032,\n    \"asciiCode\": 1253\n  },\n  \"en-US\": {\n    \"langId\": 1033,\n    \"asciiCode\": 1252\n  },\n  \"es-ES\": {\n    \"langId\": 3082,\n    \"asciiCode\": 1252\n  },\n  \"et-EE\": {\n    \"langId\": 1061,\n    \"asciiCode\": 1257\n  },\n  \"fi-FI\": {\n    \"langId\": 1035,\n    \"asciiCode\": 1252\n  },\n  \"fr-FR\": {\n    \"langId\": 1036,\n    \"asciiCode\": 1252\n  },\n  \"he-IL\": {\n    \"langId\": 1037,\n    \"asciiCode\": 1255\n  },\n  \"hu-HU\": {\n    \"langId\": 1038,\n    \"asciiCode\": 1250\n  },\n  \"it-IT\": {\n    \"langId\": 1040,\n    \"asciiCode\": 1252\n  },\n  \"ja-JP\": {\n    \"langId\": 1041,\n    \"asciiCode\": 932\n  },\n  \"ko-KR\": {\n    \"langId\": 1042,\n    \"asciiCode\": 949\n  },\n  \"lt-LT\": {\n    \"langId\": 1063,\n    \"asciiCode\": 1257\n  },\n  \"lv-LV\": {\n    \"langId\": 1062,\n    \"asciiCode\": 1257\n  },\n  \"nl-NL\": {\n    \"langId\": 1043,\n    \"asciiCode\": 1252\n  },\n  \"nb-NO\": {\n    \"langId\": 1044,\n    \"asciiCode\": 1252\n  },\n  \"pl-PL\": {\n    \"langId\": 1045,\n    \"asciiCode\": 1250\n  },\n  \"pt-BR\": {\n    \"langId\": 1046,\n    \"asciiCode\": 1252\n  },\n  \"pt-PT\": {\n    \"langId\": 2070,\n    \"asciiCode\": 1252\n  },\n  \"ro-RO\": {\n    \"langId\": 1048,\n    \"asciiCode\": 1250\n  },\n  \"ru-RU\": {\n    \"langId\": 1049,\n    \"asciiCode\": 1251\n  },\n  \"hr-HR\": {\n    \"langId\": 1050,\n    \"asciiCode\": 1250\n  },\n  \"sk-SK\": {\n    \"langId\": 1051,\n    \"asciiCode\": 1250\n  },\n  \"sv-SE\": {\n    \"langId\": 1053,\n    \"asciiCode\": 1252\n  },\n  \"th-TH\": {\n    \"langId\": 1054,\n    \"asciiCode\": 874\n  },\n  \"tr-TR\": {\n    \"langId\": 1055,\n    \"asciiCode\": 1254\n  },\n  \"sl-SI\": {\n    \"langId\": 1060,\n    \"asciiCode\": 1250\n  },\n  \"vi-VN\": {\n    \"langId\": 1066,\n    \"asciiCode\": 1258\n  },\n  \"eu-ES\": {\n    \"langId\": 1069,\n    \"asciiCode\": 1252\n  },\n  \"bg-BG\": {\n    \"langId\": 1026,\n    \"asciiCode\": 1251\n  },\n  \"uk-UA\": {\n    \"langId\": 1058,\n    \"asciiCode\": 1251\n  },\n  \"sr-Latn-CS\": {\n    \"langId\": 2074,\n    \"asciiCode\": 1250\n  }\n}\n"
  },
  {
    "path": "crates/packager/src/package/wix/main.wxs",
    "content": "<?if $(sys.BUILDARCH)=\"x86\"?>\n    <?define Win64 = \"no\" ?>\n    <?define PlatformProgramFilesFolder = \"ProgramFilesFolder\" ?>\n<?elseif $(sys.BUILDARCH)=\"x64\"?>\n    <?define Win64 = \"yes\" ?>\n    <?define PlatformProgramFilesFolder = \"ProgramFiles64Folder\" ?>\n<?else?>\n    <?error Unsupported value of sys.BUILDARCH=$(sys.BUILDARCH)?>\n<?endif?>\n\n<Wix xmlns=\"http://schemas.microsoft.com/wix/2006/wi\">\n    <Product\n            Id=\"*\"\n            Name=\"{{product_name}}\"\n            UpgradeCode=\"{{upgrade_code}}\"\n            Language=\"!(loc.CargoPackagerLanguage)\"\n            Manufacturer=\"{{manufacturer}}\"\n            Version=\"{{version}}\">\n\n        <Package Id=\"*\"\n                 Keywords=\"Installer\"\n                 InstallerVersion=\"450\"\n                 Languages=\"0\"\n                 Compressed=\"yes\"\n                 InstallScope=\"perMachine\"\n                 SummaryCodepage=\"!(loc.CargoPackagerCodepage)\"/>\n\n        <!-- https://docs.microsoft.com/en-us/windows/win32/msi/reinstallmode -->\n        <!-- reinstall all files; rewrite all registry entries; reinstall all shortcuts -->\n        <Property Id=\"REINSTALLMODE\" Value=\"amus\" />\n\n        {{#if allow_downgrades}}\n            <MajorUpgrade Schedule=\"afterInstallInitialize\" AllowDowngrades=\"yes\" />\n        {{else}}\n            <MajorUpgrade Schedule=\"afterInstallInitialize\" DowngradeErrorMessage=\"!(loc.DowngradeErrorMessage)\" AllowSameVersionUpgrades=\"yes\" />\n        {{/if}}\n\n        <InstallExecuteSequence>\n            <RemoveShortcuts>Installed AND NOT UPGRADINGPRODUCTCODE</RemoveShortcuts>\n        </InstallExecuteSequence>\n\n        <Media Id=\"1\" Cabinet=\"app.cab\" EmbedCab=\"yes\" />\n\n        {{#if banner_path}}\n        <WixVariable Id=\"WixUIBannerBmp\" Value=\"{{banner_path}}\" />\n        {{/if}}\n        {{#if dialog_image_path}}\n        <WixVariable Id=\"WixUIDialogBmp\" Value=\"{{dialog_image_path}}\" />\n        {{/if}}\n        {{#if license}}\n        <WixVariable Id=\"WixUILicenseRtf\" Value=\"{{license}}\" />\n        {{/if}}\n\n        {{#if icon_path}}\n        <Icon Id=\"ProductIcon\" SourceFile=\"{{icon_path}}\"/>\n        <Property Id=\"ARPPRODUCTICON\" Value=\"ProductIcon\" />\n        {{/if}}\n        <Property Id=\"ARPNOREPAIR\" Value=\"yes\" Secure=\"yes\" />      <!-- Remove repair -->\n        <SetProperty Id=\"ARPNOMODIFY\" Value=\"1\" After=\"InstallValidate\" Sequence=\"execute\"/>\n\n        <!-- initialize with previous InstallDir -->\n        <Property Id=\"INSTALLDIR\">\n            <RegistrySearch Id=\"PrevInstallDirReg\" Root=\"HKCU\" Key=\"Software\\\\{{manufacturer}}\\\\{{product_name}}\" Name=\"InstallDir\" Type=\"raw\"/>\n        </Property>\n\n        <!-- launch app checkbox -->\n        <Property Id=\"WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT\" Value=\"!(loc.LaunchApp)\" />\n        <Property Id=\"WIXUI_EXITDIALOGOPTIONALCHECKBOX\" Value=\"1\"/>\n        <Property Id=\"WixShellExecTarget\" Value=\"[!Path]\" />\n        <CustomAction Id=\"LaunchApplication\" BinaryKey=\"WixCA\" DllEntry=\"WixShellExec\" Impersonate=\"yes\" />\n\n        <UI>\n            <!-- launch app checkbox -->\n            <Publish Dialog=\"ExitDialog\" Control=\"Finish\" Event=\"DoAction\" Value=\"LaunchApplication\">WIXUI_EXITDIALOGOPTIONALCHECKBOX = 1 and NOT Installed</Publish>\n\n            <Property Id=\"WIXUI_INSTALLDIR\" Value=\"INSTALLDIR\" />\n\n            {{#unless license}}\n            <!-- Skip license dialog -->\n            <Publish Dialog=\"WelcomeDlg\"\n                     Control=\"Next\"\n                     Event=\"NewDialog\"\n                     Value=\"InstallDirDlg\"\n                     Order=\"2\">1</Publish>\n            <Publish Dialog=\"InstallDirDlg\"\n                     Control=\"Back\"\n                     Event=\"NewDialog\"\n                     Value=\"WelcomeDlg\"\n                     Order=\"2\">1</Publish>\n            {{/unless}}\n        </UI>\n\n        <UIRef Id=\"WixUI_InstallDir\" />\n\n        <Directory Id=\"TARGETDIR\" Name=\"SourceDir\">\n            <Directory Id=\"DesktopFolder\" Name=\"Desktop\">\n                <Component Id=\"ApplicationShortcutDesktop\" Guid=\"*\">\n                    <Shortcut Id=\"ApplicationDesktopShortcut\" Name=\"{{product_name}}\" Description=\"Runs {{product_name}}\" Target=\"[!Path]\" WorkingDirectory=\"INSTALLDIR\" />\n                    <RemoveFolder Id=\"DesktopFolder\" On=\"uninstall\" />\n                    <RegistryValue Root=\"HKCU\" Key=\"Software\\\\{{manufacturer}}\\\\{{product_name}}\" Name=\"Desktop Shortcut\" Type=\"integer\" Value=\"1\" KeyPath=\"yes\" />\n                </Component>\n            </Directory>\n            <Directory Id=\"$(var.PlatformProgramFilesFolder)\" Name=\"PFiles\">\n                <Directory Id=\"INSTALLDIR\" Name=\"{{product_name}}\"/>\n            </Directory>\n            <Directory Id=\"ProgramMenuFolder\">\n                <Directory Id=\"ApplicationProgramsFolder\" Name=\"{{product_name}}\"/>\n            </Directory>\n        </Directory>\n\n        <DirectoryRef Id=\"INSTALLDIR\">\n            <Component Id=\"RegistryEntries\" Guid=\"*\">\n                <RegistryKey Root=\"HKCU\" Key=\"Software\\\\{{manufacturer}}\\\\{{product_name}}\">\n                    <RegistryValue Name=\"InstallDir\" Type=\"string\" Value=\"[INSTALLDIR]\" KeyPath=\"yes\" />\n                </RegistryKey>\n                <!-- Change the Root to HKCU for perUser installations -->\n                {{#each deep_link_protocols as |protocol| ~}}\n                <RegistryKey Root=\"HKLM\" Key=\"Software\\Classes\\\\{{protocol}}\">\n                    <RegistryValue Type=\"string\" Name=\"URL Protocol\" Value=\"\"/>\n                    <RegistryValue Type=\"string\" Value=\"URL:{{bundle_id}} protocol\"/>\n                    <RegistryKey Key=\"DefaultIcon\">\n                        <RegistryValue Type=\"string\" Value=\"&quot;[!Path]&quot;,0\" />\n                    </RegistryKey>\n                    <RegistryKey Key=\"shell\\open\\command\">\n                        <RegistryValue Type=\"string\" Value=\"&quot;[!Path]&quot; &quot;%1&quot;\" />\n                    </RegistryKey>\n                </RegistryKey>\n                {{/each~}}\n            </Component>\n            <Component Id=\"Path\" Guid=\"{{path_component_guid}}\" Win64=\"$(var.Win64)\">\n                <File Id=\"Path\" Source=\"{{app_exe_source}}\" KeyPath=\"yes\" Checksum=\"yes\"/>\n                {{#each file_associations as |association| ~}}\n                {{#each association.ext as |ext| ~}}\n                <ProgId Id=\"{{../../product_name}}.{{ext}}\" Advertise=\"yes\" Description=\"{{association.description}}\">\n                    <Extension Id=\"{{ext}}\" Advertise=\"yes\">\n                        <Verb Id=\"open\" Command=\"Open with {{../../product_name}}\" Argument=\"&quot;%1&quot;\" />\n                    </Extension>\n                </ProgId>\n                {{/each~}}\n                {{/each~}}\n            </Component>\n            {{#each binaries as |bin| ~}}\n            <Component Id=\"{{ bin.id }}\" Guid=\"{{bin.guid}}\" Win64=\"$(var.Win64)\">\n                <File Id=\"Bin_{{ bin.id }}\" Source=\"{{bin.path}}\" KeyPath=\"yes\"/>\n            </Component>\n            {{/each~}}\n            {{resources}}\n            <Component Id=\"CMP_UninstallShortcut\" Guid=\"*\">\n\n                <Shortcut Id=\"UninstallShortcut\"\n\t\t\t\t\t\t  Name=\"Uninstall {{product_name}}\"\n\t\t\t\t\t\t  Description=\"Uninstalls {{product_name}}\"\n\t\t\t\t\t\t  Target=\"[System64Folder]msiexec.exe\"\n\t\t\t\t\t\t  Arguments=\"/x [ProductCode]\" />\n\n\t\t\t\t<RemoveFolder Id=\"INSTALLDIR\"\n\t\t\t\t\t\t\t  On=\"uninstall\" />\n\n\t\t\t\t<RegistryValue Root=\"HKCU\"\n\t\t\t\t\t\t\t   Key=\"Software\\\\{{manufacturer}}\\\\{{product_name}}\"\n\t\t\t\t\t\t\t   Name=\"Uninstaller Shortcut\"\n\t\t\t\t\t\t\t   Type=\"integer\"\n\t\t\t\t\t\t\t   Value=\"1\"\n\t\t\t\t\t\t\t   KeyPath=\"yes\" />\n            </Component>\n        </DirectoryRef>\n\n        <DirectoryRef Id=\"ApplicationProgramsFolder\">\n            <Component Id=\"ApplicationShortcut\" Guid=\"*\">\n                <Shortcut Id=\"ApplicationStartMenuShortcut\"\n                    Name=\"{{product_name}}\"\n                    Description=\"Runs {{product_name}}\"\n                    Target=\"[!Path]\"\n                    {{#if icon_path}}\n                    Icon=\"ProductIcon\"\n                    {{/if}}\n                    WorkingDirectory=\"INSTALLDIR\">\n                    <ShortcutProperty Key=\"System.AppUserModel.ID\" Value=\"{{identifier}}\"/>\n                </Shortcut>\n                <RemoveFolder Id=\"ApplicationProgramsFolder\" On=\"uninstall\"/>\n                <RegistryValue Root=\"HKCU\" Key=\"Software\\\\{{manufacturer}}\\\\{{product_name}}\" Name=\"Start Menu Shortcut\" Type=\"integer\" Value=\"1\" KeyPath=\"yes\"/>\n           </Component>\n        </DirectoryRef>\n\n        {{#each merge_modules as |msm| ~}}\n        <DirectoryRef Id=\"TARGETDIR\">\n            <Merge Id=\"{{ msm.name }}\" SourceFile=\"{{ msm.path }}\" DiskId=\"1\" Language=\"!(loc.CargoPackagerLanguage)\" />\n        </DirectoryRef>\n\n        <Feature Id=\"{{ msm.name }}\" Title=\"{{ msm.name }}\" AllowAdvertise=\"no\" Display=\"hidden\" Level=\"1\">\n            <MergeRef Id=\"{{ msm.name }}\"/>\n        </Feature>\n        {{/each~}}\n\n        <Feature\n                Id=\"MainProgram\"\n                Title=\"Application\"\n                Description=\"!(loc.InstallAppFeature)\"\n                Level=\"1\"\n                ConfigurableDirectory=\"INSTALLDIR\"\n                AllowAdvertise=\"no\"\n                Display=\"expand\"\n                Absent=\"disallow\">\n\n            <ComponentRef Id=\"RegistryEntries\"/>\n\n            {{#each resource_file_ids as |resource_file_id| ~}}\n                <ComponentRef Id=\"{{ resource_file_id }}\"/>\n            {{/each~}}\n\n            <Feature Id=\"ShortcutsFeature\"\n                Title=\"Shortcuts\"\n                Level=\"1\">\n                <ComponentRef Id=\"Path\"/>\n                <ComponentRef Id=\"CMP_UninstallShortcut\" />\n                <ComponentRef Id=\"ApplicationShortcut\" />\n                <ComponentRef Id=\"ApplicationShortcutDesktop\" />\n            </Feature>\n\n            <Feature\n                Id=\"Environment\"\n                Title=\"PATH Environment Variable\"\n                Description=\"!(loc.PathEnvVarFeature)\"\n                Level=\"1\"\n                Absent=\"allow\">\n            <ComponentRef Id=\"Path\"/>\n            {{#each binaries as |bin| ~}}\n            <ComponentRef Id=\"{{ bin.id }}\"/>\n            {{/each~}}\n            </Feature>\n        </Feature>\n\n        <Feature Id=\"External\" AllowAdvertise=\"no\" Absent=\"disallow\">\n            {{#each component_group_refs as |id| ~}}\n            <ComponentGroupRef Id=\"{{ id }}\"/>\n            {{/each~}}\n            {{#each component_refs as |id| ~}}\n            <ComponentRef Id=\"{{ id }}\"/>\n            {{/each~}}\n            {{#each feature_group_refs as |id| ~}}\n            <FeatureGroupRef Id=\"{{ id }}\"/>\n            {{/each~}}\n            {{#each feature_refs as |id| ~}}\n            <FeatureRef Id=\"{{ id }}\"/>\n            {{/each~}}\n            {{#each merge_refs as |id| ~}}\n            <MergeRef Id=\"{{ id }}\"/>\n            {{/each~}}\n        </Feature>\n\n        {{#each custom_action_refs as |id| ~}}\n            <CustomActionRef Id=\"{{ id }}\"/>\n        {{/each~}}\n\n        {{#if install_webview}}\n        <!-- WebView2 -->\n        <Property Id=\"WVRTINSTALLED\">\n            <RegistrySearch Id=\"WVRTInstalledSystem\" Root=\"HKLM\" Key=\"SOFTWARE\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}\" Name=\"pv\" Type=\"raw\" Win64=\"no\" />\n            <RegistrySearch Id=\"WVRTInstalledUser\" Root=\"HKCU\" Key=\"SOFTWARE\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}\" Name=\"pv\" Type=\"raw\"/>\n        </Property>\n\n        {{#if download_bootstrapper}}\n        <CustomAction Id='DownloadAndInvokeBootstrapper' Directory=\"INSTALLDIR\" Execute=\"deferred\" ExeCommand='powershell.exe -NoProfile -windowstyle hidden try [\\{] [\\[]Net.ServicePointManager[\\]]::SecurityProtocol = [\\[]Net.SecurityProtocolType[\\]]::Tls12 [\\}] catch [\\{][\\}]; Invoke-WebRequest -Uri \"https://go.microsoft.com/fwlink/p/?LinkId=2124703\" -OutFile \"$env:TEMP\\MicrosoftEdgeWebview2Setup.exe\" ; Start-Process -FilePath \"$env:TEMP\\MicrosoftEdgeWebview2Setup.exe\" -ArgumentList ({{webview_installer_args}} &apos;/install&apos;) -Wait' Return='check'/>\n        <InstallExecuteSequence>\n            <Custom Action='DownloadAndInvokeBootstrapper' Before='InstallFinalize'>\n                <![CDATA[NOT(REMOVE OR WVRTINSTALLED)]]>\n            </Custom>\n        </InstallExecuteSequence>\n        {{/if}}\n\n        <!-- Embedded webview bootstrapper mode -->\n        {{#if webview2_bootstrapper_path}}\n        <Binary Id=\"MicrosoftEdgeWebview2Setup.exe\" SourceFile=\"{{webview2_bootstrapper_path}}\"/>\n        <CustomAction Id='InvokeBootstrapper' BinaryKey='MicrosoftEdgeWebview2Setup.exe' Execute=\"deferred\" ExeCommand='{{webview_installer_args}} /install' Return='check' />\n        <InstallExecuteSequence>\n            <Custom Action='InvokeBootstrapper' Before='InstallFinalize'>\n                <![CDATA[NOT(REMOVE OR WVRTINSTALLED)]]>\n            </Custom>\n        </InstallExecuteSequence>\n        {{/if}}\n\n        <!-- Embedded offline installer -->\n        {{#if webview2_installer_path}}\n        <Binary Id=\"MicrosoftEdgeWebView2RuntimeInstaller.exe\" SourceFile=\"{{webview2_installer_path}}\"/>\n        <CustomAction Id='InvokeStandalone' BinaryKey='MicrosoftEdgeWebView2RuntimeInstaller.exe' Execute=\"deferred\" ExeCommand='{{webview_installer_args}} /install' Return='check' />\n        <InstallExecuteSequence>\n            <Custom Action='InvokeStandalone' Before='InstallFinalize'>\n                <![CDATA[NOT(REMOVE OR WVRTINSTALLED)]]>\n            </Custom>\n        </InstallExecuteSequence>\n        {{/if}}\n\n        {{/if}}\n\n        <SetProperty Id=\"ARPINSTALLLOCATION\" Value=\"[INSTALLDIR]\" After=\"CostFinalize\"/>\n    </Product>\n</Wix>\n"
  },
  {
    "path": "crates/packager/src/package/wix/mod.rs",
    "content": "// Copyright 2019-2023 Tauri Programme within The Commons Conservancy\n// Copyright 2023-2023 CrabNebula Ltd.\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{\n    collections::{BTreeMap, HashMap, HashSet},\n    fs::{self, File},\n    io::Write,\n    path::{Path, PathBuf},\n    process::Command,\n};\n\nuse handlebars::{to_json, Handlebars};\nuse regex::Regex;\nuse serde::{Deserialize, Serialize};\nuse uuid::Uuid;\n\nuse super::Context;\nuse crate::{\n    codesign::windows as codesign,\n    config::{Config, LogLevel, WixLanguage},\n    shell::CommandExt,\n    util::{self, download_and_verify, extract_zip, HashAlgorithm},\n    Error,\n};\n\npub const WIX_URL: &str =\n    \"https://github.com/wixtoolset/wix3/releases/download/wix3112rtm/wix311-binaries.zip\";\npub const WIX_SHA256: &str = \"2c1888d5d1dba377fc7fa14444cf556963747ff9a0a289a3599cf09da03b9e2e\";\n\nconst WIX_REQUIRED_FILES: &[&str] = &[\n    \"candle.exe\",\n    \"candle.exe.config\",\n    \"darice.cub\",\n    \"light.exe\",\n    \"light.exe.config\",\n    \"wconsole.dll\",\n    \"winterop.dll\",\n    \"wix.dll\",\n    \"WixUIExtension.dll\",\n    \"WixUtilExtension.dll\",\n];\n\n// A v4 UUID that was generated specifically for cargo-packager, to be used as a\n// namespace for generating v5 UUIDs from bundle identifier strings.\nconst UUID_NAMESPACE: [u8; 16] = [\n    0xfd, 0x85, 0x95, 0xa8, 0x17, 0xa3, 0x47, 0x4e, 0xa6, 0x16, 0x76, 0x14, 0x8d, 0xfa, 0x0c, 0x7b,\n];\n\n#[derive(Debug, Deserialize)]\nstruct LanguageMetadata {\n    #[serde(rename = \"asciiCode\")]\n    ascii_code: usize,\n    #[serde(rename = \"langId\")]\n    lang_id: usize,\n}\n\n/// Generates a GUID.\nfn generate_guid(key: &[u8]) -> Uuid {\n    let namespace = Uuid::from_bytes(UUID_NAMESPACE);\n    Uuid::new_v5(&namespace, key)\n}\n\n/// Generates the UUID for the Wix template.\nfn generate_package_guid(config: &Config) -> Uuid {\n    generate_guid(config.identifier().as_bytes())\n}\n\n// WiX requires versions to be numeric only in a `major.minor.patch.build` format\npub fn convert_version(version_str: &str) -> crate::Result<String> {\n    let version = semver::Version::parse(version_str)?;\n    if version.major > 255 {\n        return Err(Error::InvalidAppVersion(\n            \"major number cannot be greater than 255\".into(),\n        ));\n    }\n    if version.minor > 255 {\n        return Err(Error::InvalidAppVersion(\n            \"minor number cannot be greater than 255\".into(),\n        ));\n    }\n    if version.patch > 65535 {\n        return Err(Error::InvalidAppVersion(\n            \"patch number cannot be greater than 65535\".into(),\n        ));\n    }\n\n    if !version.build.is_empty() {\n        let build = version.build.parse::<u64>();\n        if build.map(|b| b <= 65535).unwrap_or_default() {\n            return Ok(format!(\n                \"{}.{}.{}.{}\",\n                version.major, version.minor, version.patch, version.build\n            ));\n        } else {\n            return Err(Error::NonNumericBuildMetadata(Some(\n                \"and cannot be greater than 65535 for msi target\".into(),\n            )));\n        }\n    }\n\n    if !version.pre.is_empty() {\n        let pre = version.pre.parse::<u64>();\n        if pre.is_ok() && pre.unwrap() <= 65535 {\n            return Ok(format!(\n                \"{}.{}.{}.{}\",\n                version.major, version.minor, version.patch, version.pre\n            ));\n        } else {\n            return Err(Error::NonNumericBuildMetadata(Some(\n                \"and cannot be greater than 65535 for msi target\".into(),\n            )));\n        }\n    }\n\n    Ok(version_str.to_string())\n}\n\n/// A binary to bundle with WIX.\n/// External binaries or additional project binaries are represented with this data structure.\n/// This data structure is needed because WIX requires each path to have its own `id` and `guid`.\n#[derive(Serialize)]\nstruct Binary {\n    /// the GUID to use on the WIX XML.\n    guid: String,\n    /// the id to use on the WIX XML.\n    id: String,\n    /// the binary path.\n    path: String,\n}\n\n/// Generates the data required for the external binaries.\n#[tracing::instrument(level = \"trace\", skip(config))]\nfn generate_binaries_data(config: &Config) -> crate::Result<Vec<Binary>> {\n    let mut binaries = Vec::new();\n    let tmp_dir = std::env::temp_dir();\n    let regex = Regex::new(r\"[^\\w\\d\\.]\")?;\n\n    if let Some(external_binaries) = &config.external_binaries {\n        let cwd = std::env::current_dir()?;\n        let target_triple = config.target_triple();\n        for src in external_binaries {\n            let file_name = src\n                .file_name()\n                .ok_or_else(|| Error::FailedToExtractFilename(src.clone()))?\n                .to_string_lossy();\n            let src = src.with_file_name(format!(\"{file_name}-{target_triple}.exe\"));\n            let path = cwd.join(src);\n            let bin_path = dunce::canonicalize(&path).map_err(|e| Error::IoWithPath(path, e))?;\n            let dest_file_name = format!(\"{file_name}.exe\");\n            let dest = tmp_dir.join(&*dest_file_name);\n\n            fs::copy(&bin_path, &dest).map_err(|e| Error::CopyFile(bin_path, dest.clone(), e))?;\n\n            binaries.push(Binary {\n                guid: Uuid::new_v4().to_string(),\n                path: dest.into_os_string().into_string().unwrap_or_default(),\n                id: regex\n                    .replace_all(&dest_file_name.replace('-', \"_\"), \"\")\n                    .to_string(),\n            });\n        }\n    }\n\n    for bin in &config.binaries {\n        if !bin.main {\n            binaries.push(Binary {\n                guid: Uuid::new_v4().to_string(),\n                path: config\n                    .binary_path(bin)\n                    .with_extension(\"exe\")\n                    .into_os_string()\n                    .to_string_lossy()\n                    .to_string(),\n                id: regex\n                    .replace_all(\n                        &bin.path\n                            .file_stem()\n                            .unwrap()\n                            .to_string_lossy()\n                            .replace('-', \"_\"),\n                        \"\",\n                    )\n                    .to_string(),\n            })\n        }\n    }\n\n    Ok(binaries)\n}\n\n/// A Resource file to bundle with WIX.\n/// This data structure is needed because WIX requires each path to have its own `id` and `guid`.\n#[derive(Serialize, Clone)]\nstruct ResourceFile {\n    /// the GUID to use on the WIX XML.\n    guid: String,\n    /// the id to use on the WIX XML.\n    id: String,\n    /// the file path.\n    path: PathBuf,\n}\n\n/// A resource directory to bundle with WIX.\n/// This data structure is needed because WIX requires each path to have its own `id` and `guid`.\n#[derive(Serialize)]\nstruct ResourceDirectory {\n    /// the directory path.\n    path: String,\n    /// the directory name of the described resource.\n    name: String,\n    /// the files of the described resource directory.\n    files: Vec<ResourceFile>,\n    /// the directories that are children of the described resource directory.\n    directories: Vec<ResourceDirectory>,\n}\n\nimpl ResourceDirectory {\n    /// Adds a file to this directory descriptor.\n    fn add_file(&mut self, file: ResourceFile) {\n        self.files.push(file);\n    }\n\n    /// Generates the wix XML string to bundle this directory resources recursively\n    fn get_wix_data(self) -> (String, Vec<String>) {\n        let mut files = String::from(\"\");\n        let mut file_ids = Vec::new();\n        for file in self.files {\n            file_ids.push(file.id.clone());\n            files.push_str(\n          format!(\n            r#\"<Component Id=\"{id}\" Guid=\"{guid}\" Win64=\"$(var.Win64)\" KeyPath=\"yes\"><File Id=\"PathFile_{id}\" Source=\"{path}\" /></Component>\"#,\n            id = file.id,\n            guid = file.guid,\n            path = file.path.display()\n          ).as_str()\n        );\n        }\n        let mut directories = String::from(\"\");\n        for directory in self.directories {\n            let (wix_string, ids) = directory.get_wix_data();\n            for id in ids {\n                file_ids.push(id)\n            }\n            directories.push_str(wix_string.as_str());\n        }\n        let wix_string = if self.name.is_empty() {\n            format!(\"{files}{directories}\")\n        } else {\n            format!(\n                r#\"<Directory Id=\"I{id}\" Name=\"{name}\">{files}{directories}</Directory>\"#,\n                id = Uuid::new_v4().as_simple(),\n                name = self.name,\n                files = files,\n                directories = directories,\n            )\n        };\n\n        (wix_string, file_ids)\n    }\n}\n\n/// Mapper between a resource directory name and its ResourceDirectory descriptor.\ntype ResourceMap = BTreeMap<String, ResourceDirectory>;\n\n/// Generates the data required for the resource on wix\n#[tracing::instrument(level = \"trace\", skip(config))]\nfn generate_resource_data(config: &Config) -> crate::Result<ResourceMap> {\n    let mut resources_map = ResourceMap::new();\n    for resource in config.resources()? {\n        let resource_entry = ResourceFile {\n            id: format!(\"I{}\", Uuid::new_v4().as_simple()),\n            guid: Uuid::new_v4().to_string(),\n            path: resource.src,\n        };\n\n        // split the resource path directories\n        let components_count = resource.target.components().count();\n        let directories = resource\n            .target\n            .components()\n            .take(components_count - 1) // the last component is the file\n            .collect::<Vec<_>>();\n\n        // transform the directory structure to a chained vec structure\n        let first_directory = directories\n            .first()\n            .map(|d| d.as_os_str().to_string_lossy().into_owned())\n            .unwrap_or_else(String::new);\n\n        if !resources_map.contains_key(&first_directory) {\n            resources_map.insert(\n                first_directory.clone(),\n                ResourceDirectory {\n                    path: first_directory.clone(),\n                    name: first_directory.clone(),\n                    directories: vec![],\n                    files: vec![],\n                },\n            );\n        }\n\n        let mut directory_entry = resources_map.get_mut(&first_directory).unwrap();\n\n        let mut path = String::new();\n        // the first component is already parsed on `first_directory` so we skip(1)\n        for directory in directories.into_iter().skip(1) {\n            let directory_name = directory\n                .as_os_str()\n                .to_os_string()\n                .into_string()\n                .unwrap_or_default();\n            path.push_str(directory_name.as_str());\n            path.push(std::path::MAIN_SEPARATOR);\n\n            let index = directory_entry\n                .directories\n                .iter()\n                .position(|f| f.path == path);\n            match index {\n                Some(i) => directory_entry = directory_entry.directories.get_mut(i).unwrap(),\n                None => {\n                    directory_entry.directories.push(ResourceDirectory {\n                        path: path.clone(),\n                        name: directory_name,\n                        directories: vec![],\n                        files: vec![],\n                    });\n                    directory_entry = directory_entry.directories.iter_mut().last().unwrap();\n                }\n            }\n        }\n        directory_entry.add_file(resource_entry);\n    }\n\n    Ok(resources_map)\n}\n\n#[derive(Serialize)]\nstruct MergeModule<'a> {\n    name: &'a str,\n    path: &'a PathBuf,\n}\n\nfn clear_env_for_wix(cmd: &mut Command) {\n    cmd.env_clear();\n    let required_vars: Vec<std::ffi::OsString> =\n        vec![\"SYSTEMROOT\".into(), \"TMP\".into(), \"TEMP\".into()];\n    for (k, v) in std::env::vars_os() {\n        let k = k.to_ascii_uppercase();\n        if required_vars.contains(&k) || k.to_string_lossy().starts_with(\"CARGO_PACKAGER\") {\n            cmd.env(k, v);\n        }\n    }\n}\n\n/// Runs the Candle.exe executable for Wix. Candle parses the wxs file and generates the code for building the installer.\nfn run_candle(\n    config: &Config,\n    wix_path: &Path,\n    intermediates_path: &Path,\n    arch: &str,\n    wxs_file_path: PathBuf,\n    extensions: Vec<PathBuf>,\n) -> crate::Result<()> {\n    let main_binary = config.main_binary()?;\n    let mut args = vec![\n        \"-arch\".to_string(),\n        arch.to_string(),\n        wxs_file_path.to_string_lossy().to_string(),\n        format!(\n            \"-dSourceDir={}\",\n            util::display_path(config.binary_path(main_binary))\n        ),\n    ];\n\n    if config.wix().map(|w| w.fips_compliant).unwrap_or_default() {\n        args.push(\"-fips\".into());\n    }\n\n    let candle_exe = wix_path.join(\"candle.exe\");\n\n    tracing::info!(\"Running candle for {:?}\", wxs_file_path);\n    let mut cmd = Command::new(candle_exe);\n    for ext in extensions {\n        cmd.arg(\"-ext\");\n        cmd.arg(ext);\n    }\n\n    clear_env_for_wix(&mut cmd);\n\n    if let Some(level) = config.log_level {\n        if level >= LogLevel::Debug {\n            cmd.arg(\"-v\");\n        }\n    }\n\n    cmd.args(&args)\n        .current_dir(intermediates_path)\n        .output_ok()\n        .map_err(|e| Error::WixFailed(\"candle.exe\".into(), e))?;\n\n    Ok(())\n}\n\n/// Runs the Light.exe file. Light takes the generated code from Candle and produces an MSI Installer.\nfn run_light(\n    config: &Config,\n    wix_path: &Path,\n    intermediates_path: &Path,\n    arguments: Vec<String>,\n    extensions: &Vec<PathBuf>,\n    output_path: &Path,\n) -> crate::Result<()> {\n    let light_exe = wix_path.join(\"light.exe\");\n\n    let mut args: Vec<String> = vec![\"-o\".to_string(), util::display_path(output_path)];\n\n    args.extend(arguments);\n\n    let mut cmd = Command::new(light_exe);\n    for ext in extensions {\n        cmd.arg(\"-ext\");\n        cmd.arg(ext);\n    }\n\n    clear_env_for_wix(&mut cmd);\n\n    if let Some(level) = config.log_level {\n        if level >= LogLevel::Debug {\n            cmd.arg(\"-v\");\n        }\n    }\n\n    cmd.args(&args)\n        .current_dir(intermediates_path)\n        .output_ok()\n        .map_err(|e| Error::WixFailed(\"light.exe\".into(), e))?;\n\n    Ok(())\n}\n\n#[tracing::instrument(level = \"trace\")]\nfn get_and_extract_wix(path: &Path) -> crate::Result<()> {\n    let data = download_and_verify(\n        \"wix311-binaries.zip\",\n        WIX_URL,\n        WIX_SHA256,\n        HashAlgorithm::Sha256,\n    )?;\n    tracing::debug!(\"extracting WIX\");\n    extract_zip(&data, path)\n}\n\n#[tracing::instrument(level = \"trace\", skip(ctx))]\nfn build_wix_app_installer(ctx: &Context, wix_path: &Path) -> crate::Result<Vec<PathBuf>> {\n    let Context {\n        config,\n        intermediates_path,\n        ..\n    } = ctx;\n\n    let arch = match config.target_arch()? {\n        \"x86_64\" => \"x64\",\n        \"x86\" => \"x86\",\n        \"aarch64\" => \"arm64\",\n        target => return Err(Error::UnsupportedArch(\"wix\".into(), target.into())),\n    };\n\n    let main_binary = config.main_binary()?;\n    let main_binary_name = config.main_binary_name()?;\n    let main_binary_path = config.binary_path(main_binary).with_extension(\"exe\");\n\n    tracing::debug!(\"Codesigning {}\", main_binary_path.display());\n    codesign::try_sign(&main_binary_path, config)?;\n\n    let intermediates_path = intermediates_path.join(\"wix\").join(arch);\n    util::create_clean_dir(&intermediates_path)?;\n\n    let mut data = BTreeMap::new();\n\n    data.insert(\"product_name\", to_json(&config.product_name));\n    data.insert(\"version\", to_json(convert_version(&config.version)?));\n    let identifier = config.identifier();\n    let manufacturer = config.publisher();\n    data.insert(\"identifier\", to_json(identifier));\n    data.insert(\"manufacturer\", to_json(manufacturer));\n    let upgrade_code = Uuid::new_v5(\n        &Uuid::NAMESPACE_DNS,\n        format!(\"{main_binary_name}.app.x64\").as_bytes(),\n    )\n    .to_string();\n\n    data.insert(\"upgrade_code\", to_json(upgrade_code.as_str()));\n    data.insert(\n        \"allow_downgrades\",\n        to_json(config.windows().map(|w| w.allow_downgrades).unwrap_or(true)),\n    );\n\n    let path_guid = generate_package_guid(config).to_string();\n    data.insert(\"path_component_guid\", to_json(path_guid.as_str()));\n\n    let shortcut_guid = generate_package_guid(config).to_string();\n    data.insert(\"shortcut_guid\", to_json(shortcut_guid.as_str()));\n\n    let binaries = generate_binaries_data(config)?;\n    data.insert(\"binaries\", to_json(binaries));\n\n    let resources = generate_resource_data(config)?;\n    let mut resources_wix_string = String::from(\"\");\n    let mut files_ids = Vec::new();\n    for (_, dir) in resources {\n        let (wix_string, ids) = dir.get_wix_data();\n        resources_wix_string.push_str(wix_string.as_str());\n        for id in ids {\n            files_ids.push(id);\n        }\n    }\n    data.insert(\"resources\", to_json(resources_wix_string));\n    data.insert(\"resource_file_ids\", to_json(files_ids));\n\n    data.insert(\"app_exe_source\", to_json(&main_binary_path));\n\n    // copy icon from configured `icons` to resource folder near msi\n    if let Some(icon) = config.find_ico()? {\n        let icon_path = dunce::canonicalize(&icon).map_err(|e| Error::IoWithPath(icon, e))?;\n        data.insert(\"icon_path\", to_json(icon_path));\n    }\n\n    if let Some(license) = &config.license_file {\n        if license.ends_with(\".rtf\") {\n            data.insert(\"license\", to_json(license));\n        } else {\n            let license_contents =\n                fs::read_to_string(license).map_err(|e| Error::IoWithPath(license.clone(), e))?;\n            let license_rtf = format!(\n                r#\"{{\\rtf1\\ansi\\ansicpg1252\\deff0\\nouicompat\\deflang1033{{\\fonttbl{{\\f0\\fnil\\fcharset0 Calibri;}}}}\n{{\\*\\generator Riched20 10.0.18362}}\\viewkind4\\uc1\n\\pard\\sa200\\sl276\\slmult1\\f0\\fs22\\lang9 {}\\par\n}}\n \"#,\n                license_contents.replace('\\n', \"\\\\par \")\n            );\n            let rtf_output_path = intermediates_path.join(\"LICENSE.rtf\");\n            tracing::debug!(\"Writing {}\", util::display_path(&rtf_output_path));\n            fs::write(&rtf_output_path, license_rtf)\n                .map_err(|e| Error::IoWithPath(rtf_output_path.clone(), e))?;\n            data.insert(\"license\", to_json(rtf_output_path));\n        }\n    }\n\n    let mut fragment_paths = Vec::new();\n    let mut handlebars = Handlebars::new();\n    handlebars.register_escape_fn(handlebars::no_escape);\n    let mut custom_template_path = None;\n    if let Some(wix) = config.wix() {\n        data.insert(\"custom_action_refs\", to_json(&wix.custom_action_refs));\n        data.insert(\"component_group_refs\", to_json(&wix.component_group_refs));\n        data.insert(\"component_refs\", to_json(&wix.component_refs));\n        data.insert(\"feature_group_refs\", to_json(&wix.feature_group_refs));\n        data.insert(\"feature_refs\", to_json(&wix.feature_refs));\n        data.insert(\"merge_refs\", to_json(&wix.merge_refs));\n        custom_template_path.clone_from(&wix.template);\n\n        fragment_paths = wix.fragment_paths.clone().unwrap_or_default();\n        if let Some(ref inline_fragments) = wix.fragments {\n            tracing::debug!(\n                \"Writing inline fragments to {}\",\n                util::display_path(&intermediates_path)\n            );\n            for (idx, fragment) in inline_fragments.iter().enumerate() {\n                let path = intermediates_path.join(format!(\"inline_fragment{idx}.wxs\"));\n                fs::write(&path, fragment).map_err(|e| Error::IoWithPath(path.clone(), e))?;\n                fragment_paths.push(path);\n            }\n        }\n\n        if let Some(banner_path) = &wix.banner_path {\n            let canonicalized = dunce::canonicalize(banner_path)\n                .map_err(|e| Error::IoWithPath(banner_path.clone(), e))?;\n            data.insert(\"banner_path\", to_json(canonicalized));\n        }\n\n        if let Some(dialog_image_path) = &wix.dialog_image_path {\n            let canonicalized = dunce::canonicalize(dialog_image_path)\n                .map_err(|e| Error::IoWithPath(dialog_image_path.clone(), e))?;\n            data.insert(\"dialog_image_path\", to_json(canonicalized));\n        }\n\n        if let Some(merge_modules) = &wix.merge_modules {\n            let merge_modules = merge_modules\n                .iter()\n                .map(|path| MergeModule {\n                    name: path\n                        .file_name()\n                        .and_then(|f| f.to_str())\n                        .unwrap_or_default(),\n                    path,\n                })\n                .collect::<Vec<_>>();\n            data.insert(\"merge_modules\", to_json(merge_modules));\n        }\n    }\n\n    if let Some(file_associations) = &config.file_associations {\n        data.insert(\"file_associations\", to_json(file_associations));\n    }\n\n    if let Some(protocols) = &config.deep_link_protocols {\n        let schemes = protocols\n            .iter()\n            .flat_map(|p| &p.schemes)\n            .collect::<Vec<_>>();\n        data.insert(\"deep_link_protocols\", to_json(schemes));\n    }\n\n    if let Some(path) = custom_template_path {\n        handlebars\n            .register_template_string(\"main.wxs\", fs::read_to_string(path)?)\n            .map_err(Box::new)?;\n    } else {\n        handlebars\n            .register_template_string(\"main.wxs\", include_str!(\"./main.wxs\"))\n            .map_err(Box::new)?;\n    }\n\n    let main_wxs_path = intermediates_path.join(\"main.wxs\");\n    tracing::debug!(\"Writing {}\", util::display_path(&main_wxs_path));\n    fs::write(&main_wxs_path, handlebars.render(\"main.wxs\", &data)?)\n        .map_err(|e| Error::IoWithPath(main_wxs_path.clone(), e))?;\n\n    let mut candle_inputs = vec![(main_wxs_path, Vec::new())];\n\n    let current_dir = std::env::current_dir()?;\n    let extension_regex = Regex::new(\"\\\"http://schemas.microsoft.com/wix/(\\\\w+)\\\"\")?;\n    for fragment_path in fragment_paths {\n        let fragment_path = current_dir.join(fragment_path);\n        let fragment = fs::read_to_string(&fragment_path)\n            .map_err(|e| Error::IoWithPath(fragment_path.clone(), e))?;\n        let mut extensions = Vec::new();\n        for cap in extension_regex.captures_iter(&fragment) {\n            extensions.push(wix_path.join(format!(\"Wix{}.dll\", &cap[1])));\n        }\n        candle_inputs.push((fragment_path, extensions));\n    }\n\n    let mut fragment_extensions = HashSet::new();\n    //Default extensions\n    fragment_extensions.insert(wix_path.join(\"WixUIExtension.dll\"));\n    fragment_extensions.insert(wix_path.join(\"WixUtilExtension.dll\"));\n\n    for (path, extensions) in candle_inputs {\n        for ext in &extensions {\n            fragment_extensions.insert(ext.clone());\n        }\n        run_candle(\n            config,\n            wix_path,\n            &intermediates_path,\n            arch,\n            path,\n            extensions,\n        )?;\n    }\n\n    let mut output_paths = Vec::new();\n\n    let language_map: HashMap<String, LanguageMetadata> =\n        serde_json::from_str(include_str!(\"./languages.json\"))?;\n    let configured_languages = config\n        .wix()\n        .and_then(|w| w.languages.clone())\n        .unwrap_or_else(|| vec![WixLanguage::default()]);\n    for language in configured_languages {\n        let (language, locale_path) = match language {\n            WixLanguage::Identifier(identifier) => (identifier, None),\n            WixLanguage::Custom { identifier, path } => (identifier, path),\n        };\n\n        let language_metadata = language_map.get(&language).ok_or_else(|| {\n            Error::UnsupportedWixLanguage(\n                language.clone(),\n                language_map\n                    .keys()\n                    .cloned()\n                    .collect::<Vec<String>>()\n                    .join(\", \"),\n            )\n        })?;\n\n        let locale_contents = match locale_path {\n            Some(p) => fs::read_to_string(&p).map_err(|e| Error::IoWithPath(p, e))?,\n            None => format!(\n                r#\"<WixLocalization Culture=\"{}\" xmlns=\"http://schemas.microsoft.com/wix/2006/localization\"></WixLocalization>\"#,\n                language.to_lowercase(),\n            ),\n        };\n\n        let locale_strings = include_str!(\"./default-locale-strings.xml\")\n            .replace(\"__language__\", &language_metadata.lang_id.to_string())\n            .replace(\"__codepage__\", &language_metadata.ascii_code.to_string())\n            .replace(\"__productName__\", &config.product_name);\n\n        let mut unset_locale_strings = String::new();\n        let prefix_len = \"<String \".len();\n        for locale_string in locale_strings.split('\\n').filter(|s| !s.is_empty()) {\n            // strip `<String ` prefix and `>{value}</String` suffix.\n            let id = locale_string\n                .chars()\n                .skip(prefix_len)\n                .take(locale_string.find('>').unwrap() - prefix_len)\n                .collect::<String>();\n            if !locale_contents.contains(&id) {\n                unset_locale_strings.push_str(locale_string);\n            }\n        }\n\n        let locale_contents = locale_contents.replace(\n            \"</WixLocalization>\",\n            &format!(\"{unset_locale_strings}</WixLocalization>\"),\n        );\n        let locale_path = intermediates_path.join(\"locale.wxl\");\n        {\n            tracing::debug!(\"Writing {}\", util::display_path(&locale_path));\n            let mut fileout = File::create(&locale_path)\n                .map_err(|e| Error::IoWithPath(locale_path.clone(), e))?;\n            fileout.write_all(locale_contents.as_bytes())?;\n        }\n\n        let arguments = vec![\n            format!(\n                \"-cultures:{}\",\n                if language == \"en-US\" {\n                    language.to_lowercase()\n                } else {\n                    format!(\"{};en-US\", language.to_lowercase())\n                }\n            ),\n            \"-loc\".into(),\n            util::display_path(&locale_path),\n            \"*.wixobj\".into(),\n        ];\n        let msi_output_path = intermediates_path.join(\"output.msi\");\n        let msi_path = config.out_dir().join(format!(\n            \"{}_{}_{}_{}.msi\",\n            main_binary_name, config.version, arch, language\n        ));\n        let msi_path_parent = msi_path\n            .parent()\n            .ok_or_else(|| Error::ParentDirNotFound(msi_path.clone()))?;\n        fs::create_dir_all(msi_path_parent)\n            .map_err(|e| Error::IoWithPath(msi_path_parent.to_path_buf(), e))?;\n\n        tracing::info!(\n            \"Running light.exe to produce {}\",\n            util::display_path(&msi_path)\n        );\n\n        run_light(\n            config,\n            wix_path,\n            &intermediates_path,\n            arguments,\n            &(fragment_extensions.clone().into_iter().collect()),\n            &msi_output_path,\n        )?;\n        fs::rename(&msi_output_path, &msi_path)\n            .map_err(|e| Error::RenameFile(msi_output_path, msi_path.clone(), e))?;\n        tracing::debug!(\"Codesigning {}\", msi_path.display());\n        codesign::try_sign(&msi_path, config)?;\n        output_paths.push(msi_path);\n    }\n\n    Ok(output_paths)\n}\n\n#[tracing::instrument(level = \"trace\", skip(ctx))]\npub(crate) fn package(ctx: &Context) -> crate::Result<Vec<PathBuf>> {\n    let wix_path = ctx.tools_path.join(\"WixTools\");\n    if !wix_path.exists() {\n        get_and_extract_wix(&wix_path)?;\n    } else if WIX_REQUIRED_FILES\n        .iter()\n        .any(|p| !wix_path.join(p).exists())\n    {\n        tracing::warn!(\"WixTools directory is missing some files. Recreating it.\");\n        fs::remove_dir_all(&wix_path).map_err(|e| Error::IoWithPath(wix_path.clone(), e))?;\n        get_and_extract_wix(&wix_path)?;\n    }\n\n    build_wix_app_installer(ctx, &wix_path)\n}\n"
  },
  {
    "path": "crates/packager/src/shell.rs",
    "content": "// Copyright 2019-2023 Tauri Programme within The Commons Conservancy\n// Copyright 2023-2023 CrabNebula Ltd.\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{\n    borrow::Cow,\n    io::{BufRead, BufReader},\n    process::{Command, Output, Stdio},\n    sync::{Arc, Mutex},\n};\n\npub trait CommandExt {\n    fn output_ok(&mut self) -> std::io::Result<Output>;\n    fn output_ok_info(&mut self) -> std::io::Result<Output>;\n    fn output_ok_inner(&mut self, level: tracing::Level) -> std::io::Result<Output>;\n}\n\nimpl CommandExt for Command {\n    fn output_ok(&mut self) -> std::io::Result<Output> {\n        self.output_ok_inner(tracing::Level::DEBUG)\n    }\n\n    fn output_ok_info(&mut self) -> std::io::Result<Output> {\n        self.output_ok_inner(tracing::Level::INFO)\n    }\n\n    fn output_ok_inner(&mut self, level: tracing::Level) -> std::io::Result<Output> {\n        tracing::debug!(\"Running Command `{self:?}`\");\n\n        self.stdout(Stdio::piped());\n        self.stderr(Stdio::piped());\n\n        let mut child = self.spawn()?;\n\n        let mut stdout = child.stdout.take().map(BufReader::new).unwrap();\n        let stdout_lines = Arc::new(Mutex::new(Vec::new()));\n        let stdout_lines_ = stdout_lines.clone();\n        std::thread::spawn(move || {\n            let mut buf = Vec::new();\n            let mut lines = stdout_lines_.lock().unwrap();\n            loop {\n                buf.clear();\n                if let Ok(0) = stdout.read_until(b'\\n', &mut buf) {\n                    break;\n                }\n                log(\n                    level,\n                    \"stdout\",\n                    String::from_utf8_lossy(&buf[..buf.len() - 1]),\n                );\n                lines.extend(&buf);\n            }\n        });\n\n        let mut stderr = child.stderr.take().map(BufReader::new).unwrap();\n        let stderr_lines = Arc::new(Mutex::new(Vec::new()));\n        let stderr_lines_ = stderr_lines.clone();\n        std::thread::spawn(move || {\n            let mut buf = Vec::new();\n            let mut lines = stderr_lines_.lock().unwrap();\n            loop {\n                buf.clear();\n                if let Ok(0) = stderr.read_until(b'\\n', &mut buf) {\n                    break;\n                }\n                log(\n                    level,\n                    \"stderr\",\n                    String::from_utf8_lossy(&buf[..buf.len() - 1]),\n                );\n                lines.extend(&buf);\n            }\n        });\n\n        let status = child.wait()?;\n        let output = Output {\n            status,\n            stdout: std::mem::take(&mut *stdout_lines.lock().unwrap()),\n            stderr: std::mem::take(&mut *stderr_lines.lock().unwrap()),\n        };\n\n        if output.status.success() {\n            Ok(output)\n        } else {\n            Err(std::io::Error::other(format!(\n                \"failed to run command: {self:?}\\nstdout: {}\\nstderr: {}\",\n                String::from_utf8_lossy(&output.stdout),\n                String::from_utf8_lossy(&output.stderr)\n            )))\n        }\n    }\n}\n\n#[inline]\nfn log(level: tracing::Level, shell: &str, msg: Cow<'_, str>) {\n    match level {\n        tracing::Level::INFO => tracing::info!(shell = shell, \"{msg}\"),\n        _ => tracing::debug!(shell = shell, \"{msg}\"),\n    }\n}\n"
  },
  {
    "path": "crates/packager/src/sign.rs",
    "content": "// Copyright 2019-2023 Tauri Programme within The Commons Conservancy\n// Copyright 2023-2023 CrabNebula Ltd.\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! File singing and signing keys creation and decoding.\n\nuse std::{\n    fmt::Debug,\n    fs::{self, OpenOptions},\n    io::{BufReader, Write},\n    path::{Path, PathBuf},\n    str,\n    time::{SystemTime, UNIX_EPOCH},\n};\n\nuse base64::{engine::general_purpose::STANDARD, Engine};\nuse serde::{Deserialize, Serialize};\n\nuse crate::{\n    util::{self, PathExt},\n    Error,\n};\n\n/// A public and secret key pair.\n#[derive(Clone, Debug)]\npub struct KeyPair {\n    /// Publick key\n    pub pk: String,\n    /// Secret key\n    pub sk: String,\n}\n\n/// Generates a new signing key. If `password` is `None`, it will prompt\n/// the user for a password, so if you want to skip the prompt, specify and\n/// empty string as the password.\n#[tracing::instrument(level = \"trace\")]\npub fn generate_key(password: Option<String>) -> crate::Result<KeyPair> {\n    let minisign::KeyPair { pk, sk } = minisign::KeyPair::generate_encrypted_keypair(password)?;\n\n    let pk_box_str = pk.to_box()?.to_string();\n    let sk_box_str = sk.to_box(None)?.to_string();\n\n    let encoded_pk = base64::engine::general_purpose::STANDARD.encode(pk_box_str);\n    let encoded_sk = base64::engine::general_purpose::STANDARD.encode(sk_box_str);\n\n    Ok(KeyPair {\n        pk: encoded_pk,\n        sk: encoded_sk,\n    })\n}\n\nfn decode_base64(base64_key: &str) -> crate::Result<String> {\n    let decoded_str = &base64::engine::general_purpose::STANDARD.decode(base64_key)?[..];\n    Ok(String::from(str::from_utf8(decoded_str)?))\n}\n\n/// Decodes a private key using the specified password.\n#[tracing::instrument(level = \"trace\")]\npub fn decode_private_key(\n    private_key: &str,\n    password: Option<&str>,\n) -> crate::Result<minisign::SecretKey> {\n    let decoded_secret = decode_base64(private_key)?;\n    let sk_box = minisign::SecretKeyBox::from_string(&decoded_secret)?;\n    let sk = sk_box.into_secret_key(password.map(Into::into))?;\n    Ok(sk)\n}\n\n/// Saves a [`KeyPair`] to disk.\n#[tracing::instrument(level = \"trace\")]\npub fn save_keypair<P: AsRef<Path> + Debug>(\n    keypair: &KeyPair,\n    path: P,\n    force: bool,\n) -> crate::Result<(PathBuf, PathBuf)> {\n    let path = path.as_ref();\n\n    let pubkey_path = format!(\"{}.pub\", path.display());\n    let pk_path = Path::new(&pubkey_path);\n\n    if path.exists() {\n        if !force {\n            return Err(Error::SigningKeyExists(path.to_path_buf()));\n        } else {\n            fs::remove_file(path).map_err(|e| Error::IoWithPath(path.to_path_buf(), e))?;\n        }\n    }\n\n    if pk_path.exists() {\n        fs::remove_file(pk_path).map_err(|e| Error::IoWithPath(pk_path.to_path_buf(), e))?;\n    }\n\n    let mut sk_writer = util::create_file(path)?;\n    write!(sk_writer, \"{}\", keypair.sk)?;\n    sk_writer.flush()?;\n\n    let mut pk_writer = util::create_file(pk_path)?;\n    write!(pk_writer, \"{}\", keypair.pk)?;\n    pk_writer.flush()?;\n\n    Ok((\n        dunce::canonicalize(path).map_err(|e| Error::IoWithPath(path.to_path_buf(), e))?,\n        dunce::canonicalize(pk_path).map_err(|e| Error::IoWithPath(pk_path.to_path_buf(), e))?,\n    ))\n}\n\n/// Signing configuration.\n#[derive(Debug, Clone, Serialize, Deserialize, Default)]\n#[serde(rename_all = \"camelCase\", deny_unknown_fields)]\n#[non_exhaustive]\npub struct SigningConfig {\n    /// The private key to use for signing.\n    pub private_key: String,\n    /// The private key password.\n    ///\n    /// If `None`, user will be prompted to write a password.\n    /// You can skip the prompt by specifying an empty string.\n    pub password: Option<String>,\n}\n\nimpl SigningConfig {\n    /// Creates a new [`SigningConfig`].\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    /// Set the private key to use for signing.\n    pub fn private_key<S: Into<String>>(mut self, private_key: S) -> Self {\n        self.private_key = private_key.into();\n        self\n    }\n\n    /// Set the private key password.\n    pub fn password<S: Into<String>>(mut self, password: S) -> Self {\n        self.password.replace(password.into());\n\n        self\n    }\n}\n\n/// Signs a specified file using the specified signing configuration.\n#[tracing::instrument(level = \"trace\")]\npub fn sign_file<P: AsRef<Path> + Debug>(\n    config: &SigningConfig,\n    path: P,\n) -> crate::Result<PathBuf> {\n    let secret_key = decode_private_key(&config.private_key, config.password.as_deref())?;\n    sign_file_with_secret_key(&secret_key, path)\n}\n\n/// Signs a specified file using an already decoded secret key.\n#[tracing::instrument(level = \"trace\")]\npub fn sign_file_with_secret_key<P: AsRef<Path> + Debug>(\n    secret_key: &minisign::SecretKey,\n    path: P,\n) -> crate::Result<PathBuf> {\n    let path = path.as_ref();\n    let signature_path = path.with_additional_extension(\"sig\");\n    let signature_path = dunce::simplified(&signature_path);\n\n    let mut signature_box_writer = util::create_file(signature_path)?;\n    let start = SystemTime::now();\n    let since_epoch = start.duration_since(UNIX_EPOCH)?.as_secs();\n    let trusted_comment = format!(\n        \"timestamp:{}\\tfile:{}\",\n        since_epoch,\n        path.file_name()\n            .ok_or_else(|| crate::Error::FailedToExtractFilename(path.to_path_buf()))?\n            .to_string_lossy()\n    );\n\n    let file = OpenOptions::new()\n        .read(true)\n        .open(path)\n        .map_err(|e| Error::IoWithPath(path.to_path_buf(), e))?;\n    let file_reader = BufReader::new(file);\n\n    let signature_box = minisign::sign(\n        None,\n        secret_key,\n        file_reader,\n        Some(trusted_comment.as_str()),\n        Some(\"signature from cargo-packager secret key\"),\n    )?;\n\n    let encoded_signature = STANDARD.encode(signature_box.to_string());\n    signature_box_writer.write_all(encoded_signature.as_bytes())?;\n    signature_box_writer.flush()?;\n\n    dunce::canonicalize(signature_path).map_err(|e| crate::Error::IoWithPath(path.to_path_buf(), e))\n}\n"
  },
  {
    "path": "crates/packager/src/util.rs",
    "content": "// Copyright 2016-2019 Cargo-Bundle developers <https://github.com/burtonageo/cargo-bundle>\n// Copyright 2019-2023 Tauri Programme within The Commons Conservancy\n// Copyright 2023-2023 CrabNebula Ltd.\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse sha2::Digest;\nuse std::{\n    ffi::OsStr,\n    fs::{self, File},\n    io::{Cursor, Read, Write},\n    path::{Path, PathBuf},\n    process::Command,\n};\n\nuse zip::ZipArchive;\n\nuse crate::{shell::CommandExt, Error};\n\n#[inline]\npub(crate) fn cross_command(script: &str) -> Command {\n    #[cfg(windows)]\n    let mut cmd = Command::new(\"cmd\");\n    #[cfg(windows)]\n    cmd.arg(\"/S\").arg(\"/C\").arg(script);\n    #[cfg(not(windows))]\n    let mut cmd = Command::new(\"sh\");\n    cmd.current_dir(dunce::canonicalize(std::env::current_dir().unwrap()).unwrap());\n    #[cfg(not(windows))]\n    cmd.arg(\"-c\").arg(script);\n    cmd\n}\n\n#[inline]\npub fn display_path<P: AsRef<Path>>(p: P) -> String {\n    dunce::simplified(&p.as_ref().components().collect::<PathBuf>())\n        .display()\n        .to_string()\n}\n\n/// Recursively create a directory and all of its parent components if they\n/// are missing after Deleting the existing directory (if it exists).\n#[inline]\npub fn create_clean_dir<P: AsRef<Path>>(path: P) -> crate::Result<()> {\n    let path = path.as_ref();\n    if path.exists() {\n        fs::remove_dir_all(path).map_err(|e| Error::IoWithPath(path.to_path_buf(), e))?;\n    }\n    fs::create_dir_all(path).map_err(|e| Error::IoWithPath(path.to_path_buf(), e))\n}\n\n/// Creates a new file at the given path, creating any parent directories as needed.\n#[inline]\npub(crate) fn create_file(path: &Path) -> crate::Result<std::io::BufWriter<File>> {\n    if let Some(parent) = path.parent() {\n        fs::create_dir_all(parent).map_err(|e| Error::IoWithPath(path.to_path_buf(), e))?;\n    }\n    let file = File::create(path).map_err(|e| Error::IoWithPath(path.to_path_buf(), e))?;\n    Ok(std::io::BufWriter::new(file))\n}\n\n#[derive(Debug, PartialEq, Eq)]\nstruct RustCfg {\n    target_arch: Option<String>,\n}\n\nfn parse_rust_cfg(cfg: String) -> RustCfg {\n    let target_line = \"target_arch=\\\"\";\n    let mut target_arch = None;\n    for line in cfg.split('\\n') {\n        if line.starts_with(target_line) {\n            let len = target_line.len();\n            let arch = line.chars().skip(len).take(line.len() - len - 1).collect();\n            target_arch.replace(arch);\n        }\n    }\n    RustCfg { target_arch }\n}\n\n/// Try to determine the current target triple.\n///\n/// Returns a target triple (e.g. `x86_64-unknown-linux-gnu` or `i686-pc-windows-msvc`) or an\n/// error if the current config cannot be determined or is not some combination of the\n/// following values:\n/// `linux, mac, windows` -- `i686, x86, armv7` -- `gnu, musl, msvc`\npub fn target_triple() -> crate::Result<String> {\n    let arch_res = Command::new(\"rustc\").args([\"--print\", \"cfg\"]).output_ok();\n\n    let arch = match arch_res {\n        Ok(output) => parse_rust_cfg(String::from_utf8_lossy(&output.stdout).into())\n            .target_arch\n            .expect(\"could not find `target_arch` when running `rustc --print cfg`.\"),\n        Err(err) => {\n            tracing:: debug!(\n                \"Failed to determine target arch using rustc, error: `{err}`. Falling back to the architecture of the machine that compiled this crate.\",\n            );\n            if cfg!(target_arch = \"x86\") {\n                \"i686\".into()\n            } else if cfg!(target_arch = \"x86_64\") {\n                \"x86_64\".into()\n            } else if cfg!(target_arch = \"arm\") {\n                \"armv7\".into()\n            } else if cfg!(target_arch = \"aarch64\") {\n                \"aarch64\".into()\n            } else {\n                return Err(crate::Error::Architecture);\n            }\n        }\n    };\n\n    let os = if cfg!(target_os = \"linux\") {\n        \"unknown-linux\"\n    } else if cfg!(target_os = \"macos\") {\n        \"apple-darwin\"\n    } else if cfg!(target_os = \"windows\") {\n        \"pc-windows\"\n    } else if cfg!(target_os = \"freebsd\") {\n        \"unknown-freebsd\"\n    } else {\n        return Err(crate::Error::Os);\n    };\n\n    let os = if cfg!(target_os = \"macos\") || cfg!(target_os = \"freebsd\") {\n        String::from(os)\n    } else {\n        let env = if cfg!(target_env = \"gnu\") {\n            \"gnu\"\n        } else if cfg!(target_env = \"musl\") {\n            \"musl\"\n        } else if cfg!(target_env = \"msvc\") {\n            \"msvc\"\n        } else {\n            return Err(crate::Error::Environment);\n        };\n\n        format!(\"{os}-{env}\")\n    };\n\n    Ok(format!(\"{arch}-{os}\"))\n}\n\npub(crate) fn download(url: &str) -> crate::Result<Vec<u8>> {\n    tracing::debug!(\"Downloading {}\", url);\n\n    // This is required because ureq does not bind native-tls as the default TLS implementation when rustls is not available.\n    // See <https://github.com/crabnebula-dev/cargo-packager/issues/127>\n    #[cfg(feature = \"native-tls\")]\n    let agent = ureq::AgentBuilder::new()\n        .tls_connector(std::sync::Arc::new(\n            native_tls::TlsConnector::new().unwrap(),\n        ))\n        .try_proxy_from_env(true)\n        .build();\n    #[cfg(not(feature = \"native-tls\"))]\n    let agent = ureq::AgentBuilder::new().try_proxy_from_env(true).build();\n\n    let response = agent.get(url).call().map_err(Box::new)?;\n    let mut bytes = Vec::new();\n    response.into_reader().read_to_end(&mut bytes)?;\n    Ok(bytes)\n}\n\n#[derive(Clone, Copy)]\npub(crate) enum HashAlgorithm {\n    #[cfg(target_os = \"windows\")]\n    Sha256,\n    Sha1,\n}\n\n/// Function used to download a file and checks SHA256 to verify the download.\npub(crate) fn download_and_verify<P: AsRef<Path>>(\n    path: P,\n    url: &str,\n    hash: &str,\n    hash_algorithm: HashAlgorithm,\n) -> crate::Result<Vec<u8>> {\n    let data = download(url)?;\n    tracing::debug!(\"Validating {} hash\", path.as_ref().display());\n    verify_hash(&data, hash, hash_algorithm)?;\n    Ok(data)\n}\n\npub(crate) fn verify_hash(\n    data: &[u8],\n    hash: &str,\n    hash_algorithm: HashAlgorithm,\n) -> crate::Result<()> {\n    match hash_algorithm {\n        #[cfg(target_os = \"windows\")]\n        HashAlgorithm::Sha256 => {\n            let hasher = sha2::Sha256::new();\n            verify_data_with_hasher(data, hash, hasher)\n        }\n        HashAlgorithm::Sha1 => {\n            let hasher = sha1::Sha1::new();\n            verify_data_with_hasher(data, hash, hasher)\n        }\n    }\n}\n\nfn verify_data_with_hasher(data: &[u8], hash: &str, mut hasher: impl Digest) -> crate::Result<()> {\n    hasher.update(data);\n\n    let url_hash = hasher.finalize().to_vec();\n    let expected_hash = hex::decode(hash)?;\n    if expected_hash == url_hash {\n        Ok(())\n    } else {\n        Err(crate::Error::HashError)\n    }\n}\n\npub(crate) fn verify_file_hash<P: AsRef<Path>>(\n    path: P,\n    hash: &str,\n    hash_algorithm: HashAlgorithm,\n) -> crate::Result<()> {\n    let data = fs::read(&path).map_err(|e| Error::IoWithPath(path.as_ref().to_path_buf(), e))?;\n    verify_hash(&data, hash, hash_algorithm)\n}\n\n/// Extracts the zips from memory into a useable path.\npub(crate) fn extract_zip(data: &[u8], path: &Path) -> crate::Result<()> {\n    let cursor = Cursor::new(data);\n\n    let mut zipa = ZipArchive::new(cursor)?;\n\n    for i in 0..zipa.len() {\n        let mut file = zipa.by_index(i)?;\n\n        if let Some(name) = file.enclosed_name() {\n            let dest_path = path.join(name);\n            if file.is_dir() {\n                fs::create_dir_all(&dest_path)\n                    .map_err(|e| Error::IoWithPath(dest_path.clone(), e))?;\n                continue;\n            }\n\n            let parent = dest_path\n                .parent()\n                .ok_or_else(|| crate::Error::ParentDirNotFound(dest_path.clone()))?;\n\n            if !parent.exists() {\n                fs::create_dir_all(parent)\n                    .map_err(|e| Error::IoWithPath(parent.to_path_buf(), e))?\n            }\n\n            let mut buff: Vec<u8> = Vec::new();\n            file.read_to_end(&mut buff)?;\n\n            let mut fileout = File::create(dest_path)?;\n            fileout.write_all(&buff)?;\n        }\n    }\n\n    Ok(())\n}\n\n#[cfg(windows)]\npub(crate) enum Bitness {\n    X86_32,\n    X86_64,\n    Unknown,\n}\n\n#[cfg(windows)]\npub(crate) fn os_bitness() -> crate::Result<Bitness> {\n    use windows_sys::Win32::System::{\n        SystemInformation::{GetNativeSystemInfo, SYSTEM_INFO},\n        SystemInformation::{PROCESSOR_ARCHITECTURE_AMD64, PROCESSOR_ARCHITECTURE_INTEL},\n    };\n\n    let mut system_info: SYSTEM_INFO = unsafe { std::mem::zeroed() };\n    unsafe { GetNativeSystemInfo(&mut system_info) };\n\n    Ok(\n        match unsafe { system_info.Anonymous.Anonymous.wProcessorArchitecture } {\n            PROCESSOR_ARCHITECTURE_INTEL => Bitness::X86_32,\n            PROCESSOR_ARCHITECTURE_AMD64 => Bitness::X86_64,\n            _ => Bitness::Unknown,\n        },\n    )\n}\n\n/// Returns true if the path has a filename indicating that it is a high-density\n/// \"retina\" icon.  Specifically, returns true the file stem ends with\n/// \"@2x\" (a convention specified by the [Apple developer docs](\n/// https://developer.apple.com/library/mac/documentation/GraphicsAnimation/Conceptual/HighResolutionOSX/Optimizing/Optimizing.html)).xw\npub(crate) fn is_retina<P: AsRef<Path>>(path: P) -> bool {\n    path.as_ref()\n        .file_stem()\n        .and_then(std::ffi::OsStr::to_str)\n        .map(|stem| stem.ends_with(\"@2x\"))\n        .unwrap_or(false)\n}\n\n// Given a list of icon files, try to produce an ICNS file in the out_dir\n// and return the path to it.  Returns `Ok(None)` if no usable icons\n// were provided.\npub fn create_icns_file(out_dir: &Path, config: &crate::Config) -> crate::Result<Option<PathBuf>> {\n    use image::GenericImageView;\n\n    let icons = config.icons()?;\n    if icons.as_ref().map(|i| i.len()).unwrap_or_default() == 0 {\n        return Ok(None);\n    }\n\n    // If one of the icon files is already an ICNS file, just use that.\n    if let Some(icons) = icons {\n        fs::create_dir_all(out_dir).map_err(|e| Error::IoWithPath(out_dir.to_path_buf(), e))?;\n        for icon_path in icons {\n            if icon_path.extension() == Some(std::ffi::OsStr::new(\"icns\")) {\n                let dest_path = out_dir.join(\n                    icon_path\n                        .file_name()\n                        .ok_or_else(|| crate::Error::FailedToExtractFilename(icon_path.clone()))?,\n                );\n                fs::copy(&icon_path, &dest_path)\n                    .map_err(|e| Error::CopyFile(icon_path.clone(), dest_path.clone(), e))?;\n\n                return Ok(Some(dest_path));\n            }\n        }\n    }\n\n    // Otherwise, read available images and pack them into a new ICNS file.\n    let mut family = icns::IconFamily::new();\n\n    #[inline]\n    fn add_icon_to_family(\n        icon: image::DynamicImage,\n        density: u32,\n        family: &mut icns::IconFamily,\n    ) -> std::io::Result<()> {\n        // Try to add this image to the icon family.  Ignore images whose sizes\n        // don't map to any ICNS icon type; print warnings and skip images that\n        // fail to encode.\n        match icns::IconType::from_pixel_size_and_density(icon.width(), icon.height(), density) {\n            Some(icon_type) => {\n                if !family.has_icon_with_type(icon_type) {\n                    let icon = make_icns_image(icon)?;\n                    family.add_icon_with_type(&icon, icon_type)?;\n                }\n                Ok(())\n            }\n            None => Err(std::io::Error::new(\n                std::io::ErrorKind::InvalidData,\n                \"No matching IconType\",\n            )),\n        }\n    }\n\n    let mut images_to_resize: Vec<(image::DynamicImage, u32, u32)> = vec![];\n    if let Some(icons) = config.icons()? {\n        for icon_path in &icons {\n            let icon = image::open(icon_path)?;\n            let density = if is_retina(icon_path) { 2 } else { 1 };\n            let (w, h) = icon.dimensions();\n            let orig_size = std::cmp::min(w, h);\n            let next_size_down = 2f32.powf((orig_size as f32).log2().floor()) as u32;\n            if orig_size > next_size_down {\n                images_to_resize.push((icon, next_size_down, density));\n            } else {\n                add_icon_to_family(icon, density, &mut family)?;\n            }\n        }\n    }\n\n    for (icon, next_size_down, density) in images_to_resize {\n        let icon = icon.resize_exact(\n            next_size_down,\n            next_size_down,\n            image::imageops::FilterType::Lanczos3,\n        );\n        add_icon_to_family(icon, density, &mut family)?;\n    }\n\n    if !family.is_empty() {\n        fs::create_dir_all(out_dir).map_err(|e| Error::IoWithPath(out_dir.to_path_buf(), e))?;\n        let mut dest_path = out_dir.to_path_buf();\n        dest_path.push(config.product_name.clone());\n        dest_path.set_extension(\"icns\");\n        let file =\n            File::create(&dest_path).map_err(|e| Error::IoWithPath(out_dir.to_path_buf(), e))?;\n        let icns_file = std::io::BufWriter::new(file);\n        family.write(icns_file)?;\n        Ok(Some(dest_path))\n    } else {\n        Err(crate::Error::InvalidIconList)\n    }\n}\n\n// Converts an image::DynamicImage into an icns::Image.\nfn make_icns_image(img: image::DynamicImage) -> std::io::Result<icns::Image> {\n    let pixel_format = match img.color() {\n        image::ColorType::Rgba8 => icns::PixelFormat::RGBA,\n        image::ColorType::Rgb8 => icns::PixelFormat::RGB,\n        image::ColorType::La8 => icns::PixelFormat::GrayAlpha,\n        image::ColorType::L8 => icns::PixelFormat::Gray,\n        _ => {\n            let msg = format!(\"unsupported ColorType: {:?}\", img.color());\n            return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, msg));\n        }\n    };\n    icns::Image::from_data(pixel_format, img.width(), img.height(), img.into_bytes())\n}\n\n/// Writes a tar file to the given writer containing the given directory.\n///\n/// The generated tar contains the `src_dir` as a whole and not just its files,\n/// so if we are creating a tar for:\n/// ```text\n/// dir/\n///   |_ file1\n///   |_ file2\n///   |_ file3\n/// ```\n/// the generated tar will contain the following entries:\n/// ```text\n/// - dir\n/// - dir/file1\n/// - dir/file2\n/// - dir/file3\n/// ```\npub fn create_tar_from_dir<P: AsRef<Path>, W: Write>(src_dir: P, dest_file: W) -> crate::Result<W> {\n    let src_dir = src_dir.as_ref();\n    let filename = src_dir\n        .file_name()\n        .ok_or_else(|| crate::Error::FailedToExtractFilename(src_dir.to_path_buf()))?;\n    let mut builder = tar::Builder::new(dest_file);\n    builder.follow_symlinks(false);\n    builder.append_dir_all(filename, src_dir)?;\n    builder.into_inner().map_err(Into::into)\n}\n\npub trait PathExt {\n    fn with_additional_extension(&self, extension: impl AsRef<OsStr>) -> PathBuf;\n}\n\nimpl PathExt for Path {\n    fn with_additional_extension(&self, extension: impl AsRef<OsStr>) -> PathBuf {\n        match self.extension() {\n            Some(ext) => {\n                let mut e = ext.to_os_string();\n                e.push(\".\");\n                e.push(extension);\n                self.with_extension(e)\n            }\n            None => self.with_extension(extension),\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn it_appends_ext() {\n        // Something that has an extention getting another suffix.\n        assert_eq!(\n            PathBuf::from(\"./asset.zip\").with_additional_extension(\"sig\"),\n            PathBuf::from(\"./asset.zip.sig\")\n        );\n\n        // Something that doesn't have an extention, setting its extension.\n        assert_eq!(\n            PathBuf::from(\"./executable\").with_additional_extension(\"sig\"),\n            PathBuf::from(\"./executable.sig\")\n        )\n    }\n}\n"
  },
  {
    "path": "crates/resource-resolver/CHANGELOG.md",
    "content": "# Changelog\n\n## \\[0.1.2]\n\n- [`2b6dd55`](https://www.github.com/crabnebula-dev/cargo-packager/commit/2b6dd55eac6733715a4f717af54ff167e1fdcdf8) ([#266](https://www.github.com/crabnebula-dev/cargo-packager/pull/266)) Fix `process-relaunch-dangerous-allow-symlink-macos` feature usage.\n\n### Dependencies\n\n- Upgraded to `cargo-packager-utils@0.1.1`\n\n## \\[0.1.1]\n\n- [`053b50b`](https://www.github.com/crabnebula-dev/cargo-packager/commit/053b50b4b92c769d00a5e8d27b0de5951c034b65)([#192](https://www.github.com/crabnebula-dev/cargo-packager/pull/192)) Added support for Pacman Packages in the Resource Resolver.\n\n## \\[0.1.0]\n\n- [`cd0242b`](https://www.github.com/crabnebula-dev/cargo-packager/commit/cd0242b8a41b2f7ecb78dfbae04b3a2e1c72c931) Initial Release.\n\n### Dependencies\n\n- Upgraded to `cargo-packager-utils@0.1.0`\n"
  },
  {
    "path": "crates/resource-resolver/Cargo.toml",
    "content": "[package]\nname = \"cargo-packager-resource-resolver\"\ndescription = \"Cargo packager resource resolver\"\nversion = \"0.1.2\"\nauthors = { workspace = true }\nedition = { workspace = true }\nlicense = { workspace = true }\nrepository = { workspace = true }\n\n[package.metadata.docs.rs]\nfeatures = [ \"auto-detect-format\" ]\n\n[dependencies]\nthiserror = { workspace = true }\ncargo-packager-utils = { version = \"0.1.1\", path = \"../utils\", default-features = false }\nlog = \"0.4\"\nheck = \"0.5\"\n\n[features]\nprocess-relaunch-dangerous-allow-symlink-macos = [ \"cargo-packager-utils/process-relaunch-dangerous-allow-symlink-macos\" ]\nauto-detect-format = [ ]\n"
  },
  {
    "path": "crates/resource-resolver/README.md",
    "content": "# cargo-packager-resource-resolver\n\nResource resolver for apps that was packaged by [`cargo-packager`](https://docs.rs/cargo-packager).\n\nIt resolves the root path which contains resources, which was set using the `resources` field of [cargo packager configuration](https://docs.rs/cargo-packager/latest/cargo_packager/config/struct.Config.html).\n\n## Get the resource path\n\n```rs\nuse cargo_packager_resource_resolver::{resources_dir, PackageFormat};\n\nlet resource_path = resources_dir(PackageFormat::Nsis).unwrap();\n```\n\n## Automatically detect formats\n\n:warning: This feature is only available for Rust apps that were built with cargo packager.\n\n1. Make sure to use the `before_each_package_command` field of [cargo packager configuration](https://docs.rs/cargo-packager/latest/cargo_packager/config/struct.Config.html) to build your app (this will not work with the `before_packaging_command` field).\n2. Activete the feature `auto-detect-format` for this crate in your Cargo.toml.\n\n```rs\nuse cargo_packager_resource_resolver::{resources_dir, current_format};\n\nlet resource_path = resources_dir(current_format().unwrap()).unwrap();\n```\n\n## Licenses\n\nMIT or MIT/Apache 2.0 where applicable.\n"
  },
  {
    "path": "crates/resource-resolver/src/error.rs",
    "content": "// Copyright 2023-2023 CrabNebula Ltd.\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::path::PathBuf;\n\n/// The result type of `resource-resolver`.\npub type Result<T> = std::result::Result<T, Error>;\n\n/// The error type of `resource-resolver`.\n#[derive(Debug, thiserror::Error)]\n#[non_exhaustive]\npub enum Error {\n    #[error(transparent)]\n    Io(#[from] std::io::Error),\n    /// Unkown package format.\n    #[error(\"Unkown package format\")]\n    UnkownPackageFormat,\n    /// Unsupported package format.\n    #[error(\"Unsupported package format\")]\n    UnsupportedPackageFormat,\n    /// Couldn't find `APPDIR` environment variable.\n    #[error(\"Couldn't find `APPDIR` environment variable\")]\n    AppDirNotFound,\n    /// `APPDIR` or `APPIMAGE` environment variable found but this application was not detected as an AppImage; this might be a security issue.\n    #[error(\"`APPDIR` or `APPIMAGE` environment variable found but this application was not detected as an AppImage; this might be a security issue.\")]\n    InvalidAppImage,\n    /// Couldn't find parent of path.\n    #[error(\"Couldn't find parent of {0}\")]\n    ParentNotFound(PathBuf),\n}\n"
  },
  {
    "path": "crates/resource-resolver/src/lib.rs",
    "content": "// Copyright 2023-2023 CrabNebula Ltd.\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! # cargo-packager-resource-resolver\n//!\n//! Resource resolver for apps that were packaged by [`cargo-packager`](https://docs.rs/cargo-packager).\n//!\n//! It resolves the root path which contains resources, which was set using the `resources`\n//! field of [cargo packager configuration](https://docs.rs/cargo-packager/latest/cargo_packager/config/struct.Config.html).\n//!\n//! ## Get the resource path\n//!\n//! ```\n//! use cargo_packager_resource_resolver::{resources_dir, PackageFormat};\n//!\n//! let resource_path = resources_dir(PackageFormat::Nsis).unwrap();\n//! ```\n//! ## Automatically detect formats\n//!\n//! <div class=\"warning\">\n//!\n//! This feature is only available for apps that were built with cargo packager. So the node js binding will not work.\n//!\n//! </div>\n//!\n//! 1. Make sure to use the `before_each_package_command` field of [cargo packager configuration](https://docs.rs/cargo-packager/latest/cargo_packager/config/struct.Config.html) to build your app (this will not work with the `before_packaging_command` field).\n//! 2. Active the feature `auto-detect-format`.\n//!\n//! ```rs\n//! use cargo_packager_resource_resolver::{resources_dir, current_format};\n//!\n//! let resource_path = resources_dir(current_format()).unwrap();\n//! ```\n//!\nuse std::path::PathBuf;\n\nuse cargo_packager_utils::current_exe::current_exe;\npub use cargo_packager_utils::PackageFormat;\nuse error::Result;\n\nmod error;\n\npub use error::Error;\n\n/// Get the current package format.\n/// Can only be used if the app was build with cargo-packager\n/// and when the `before-each-package-command` Cargo feature is enabled.\n#[cfg(feature = \"auto-detect-format\")]\npub fn current_format() -> crate::Result<PackageFormat> {\n    // sync with PackageFormat::short_name function of packager crate\n    // maybe having a special crate for the Config struct,\n    // that both packager and resource-resolver could be a\n    // better alternative\n    match std::option_env!(\"CARGO_PACKAGER_FORMAT\") {\n        Some(\"app\") => Ok(PackageFormat::App),\n        Some(\"dmg\") => Ok(PackageFormat::Dmg),\n        Some(\"wix\") => Ok(PackageFormat::Wix),\n        Some(\"nsis\") => Ok(PackageFormat::Nsis),\n        Some(\"deb\") => Ok(PackageFormat::Deb),\n        Some(\"appimage\") => Ok(PackageFormat::AppImage),\n        Some(\"pacman\") => Ok(PackageFormat::Pacman),\n        _ => Err(Error::UnkownPackageFormat),\n    }\n}\n\n/// Retrieve the resource path of your app, packaged with cargo packager.\n///\n/// ## Example\n///\n/// ```\n/// use cargo_packager_resource_resolver::{resources_dir, PackageFormat};\n///\n/// let resource_path = resources_dir(PackageFormat::Nsis).unwrap();\n/// ```\npub fn resources_dir(package_format: PackageFormat) -> Result<PathBuf> {\n    match package_format {\n        PackageFormat::App | PackageFormat::Dmg => {\n            let exe = current_exe()?;\n            let exe_dir = exe\n                .parent()\n                .ok_or_else(|| Error::ParentNotFound(exe.clone()))?;\n            Ok(exe_dir.join(\"../Resources\"))\n        }\n        PackageFormat::Wix | PackageFormat::Nsis => {\n            let exe = current_exe()?;\n            let exe_dir = exe\n                .parent()\n                .ok_or_else(|| Error::ParentNotFound(exe.clone()))?;\n            Ok(exe_dir.to_path_buf())\n        }\n        PackageFormat::Deb | PackageFormat::Pacman => {\n            let exe = current_exe()?;\n            let exe_name = exe.file_name().unwrap().to_string_lossy();\n\n            let path = format!(\"/usr/lib/{exe_name}/\");\n            Ok(PathBuf::from(path))\n        }\n\n        PackageFormat::AppImage => {\n            let appdir = std::env::var_os(\"APPDIR\").ok_or(Error::AppDirNotFound)?;\n\n            // validate that we're actually running on an AppImage\n            // an AppImage is mounted to `/$TEMPDIR/.mount_${appPrefix}${hash}`\n            // see https://github.com/AppImage/AppImageKit/blob/1681fd84dbe09c7d9b22e13cdb16ea601aa0ec47/src/runtime.c#L501\n            // note that it is safe to use `std::env::current_exe` here since we just loaded an AppImage.\n            let is_temp = std::env::current_exe()\n                .map(|p| {\n                    p.display()\n                        .to_string()\n                        .starts_with(&format!(\"{}/.mount_\", std::env::temp_dir().display()))\n                })\n                .unwrap_or(true);\n\n            if !is_temp {\n                return Err(Error::InvalidAppImage);\n            }\n\n            let appdir: &std::path::Path = appdir.as_ref();\n\n            let exe = current_exe()?;\n            let exe_name = exe.file_name().unwrap().to_string_lossy();\n\n            Ok(PathBuf::from(format!(\n                \"{}/usr/lib/{}\",\n                appdir.display(),\n                exe_name\n            )))\n        }\n        _ => Err(Error::UnsupportedPackageFormat),\n    }\n}\n"
  },
  {
    "path": "crates/updater/CHANGELOG.md",
    "content": "# Changelog\n\n## \\[0.2.3]\n\n- [`d861f1a`](https://www.github.com/crabnebula-dev/cargo-packager/commit/d861f1a6b1dfe585014e04234b33d49b1a895219) ([#356](https://www.github.com/crabnebula-dev/cargo-packager/pull/356)) Pull enhancements from tauri-plugin-updater.\n\n## \\[0.2.2]\n\n- [`af990f8`](https://www.github.com/crabnebula-dev/cargo-packager/commit/af990f848b78fa07fe2aa8f4cc32599557af9bf7) ([#281](https://www.github.com/crabnebula-dev/cargo-packager/pull/281)) Relax `url` dependency version requirement from `2.5` to `2`.\n\n## \\[0.2.1]\n\n- [`2b6dd55`](https://www.github.com/crabnebula-dev/cargo-packager/commit/2b6dd55eac6733715a4f717af54ff167e1fdcdf8) ([#266](https://www.github.com/crabnebula-dev/cargo-packager/pull/266)) Fix `process-relaunch-dangerous-allow-symlink-macos` feature usage.\n\n### Dependencies\n\n- Upgraded to `cargo-packager-utils@0.1.1`\n\n## \\[0.2.0]\n\n- [`c16d17a`](https://www.github.com/crabnebula-dev/cargo-packager/commit/c16d17ae190f49be3f9e78c5441bee16c0f8fc69) Enable `rustls-tls` feature flag by default.\n\n## \\[0.1.4]\n\n- [`3ee2290`](https://www.github.com/crabnebula-dev/cargo-packager/commit/3ee2290df518103056b295dae426b38a65293048)([#147](https://www.github.com/crabnebula-dev/cargo-packager/pull/147)) Prevent powershell window from opening when the msi and nsis installer are executed.\n\n## \\[0.1.3]\n\n- [`0e00ca2`](https://www.github.com/crabnebula-dev/cargo-packager/commit/0e00ca25fc0e71cad4bb7085edda067a184e5ec7)([#146](https://www.github.com/crabnebula-dev/cargo-packager/pull/146)) Enable native certificates via `rustls-native-certs`.\n\n## \\[0.1.2]\n\n### Dependencies\n\n- Upgraded to `cargo-packager-utils@0.1.0`\n\n## \\[0.1.1]\n\n- [`feb53a2`](https://www.github.com/crabnebula-dev/cargo-packager/commit/feb53a2f16ef2c8d93ff2d73a4eb318490f33471)([#102](https://www.github.com/crabnebula-dev/cargo-packager/pull/102)) Fix NSIS updater failing to launch when using `basicUi` mode.\n- [`e58c7e2`](https://www.github.com/crabnebula-dev/cargo-packager/commit/e58c7e2af586927848965aace34139fbe2b7abc4)([#113](https://www.github.com/crabnebula-dev/cargo-packager/pull/113)) Add `process-relaunch-dangerous-allow-symlink-macos` feature flag to control whether to allow relaunching if executable path contains a symlink or not.\n\n## \\[0.1.0]\n\n- [`c4fa8fd`](https://www.github.com/crabnebula-dev/cargo-packager/commit/c4fa8fd6334b7fd0c32710ea2df0b54aa6bde713) Initial release.\n"
  },
  {
    "path": "crates/updater/Cargo.toml",
    "content": "[package]\nname = \"cargo-packager-updater\"\nversion = \"0.2.3\"\ndescription = \"Rust executable updater.\"\nauthors = [\"CrabNebula Ltd.\", \"Tauri Programme within The Commons Conservancy\"]\nedition = { workspace = true }\nlicense = { workspace = true }\nrepository = { workspace = true }\n\n[features]\ndefault = [\"rustls-tls\"]\nprocess-relaunch-dangerous-allow-symlink-macos = [\n  \"cargo-packager-utils/process-relaunch-dangerous-allow-symlink-macos\",\n]\nnative-tls = [\"reqwest/native-tls\"]\nnative-tls-vendored = [\"reqwest/native-tls-vendored\"]\nrustls-tls = [\"reqwest/rustls-tls-native-roots\"]\n\n[dependencies]\ncargo-packager-utils = { version = \"0.1.1\", path = \"../utils\" }\nreqwest = { version = \"0.12\", default-features = false, features = [\n  \"json\",\n  \"stream\",\n  \"blocking\",\n] }\nthiserror = { workspace = true }\nserde = { workspace = true }\nserde_json = { workspace = true }\ndunce = { workspace = true }\ndirs = { workspace = true }\nsemver = { workspace = true }\nbase64 = { workspace = true }\ntime = { workspace = true, features = [\"parsing\", \"formatting\"] }\nhttp = \"1\"\nurl = { version = \"2\", features = [\"serde\"] }\nminisign-verify = \"0.2\"\nctor = \"0.2\"\ntempfile = \"3.12\"\nlog = \"0.4\"\npercent-encoding = \"2\"\n\n[target.\"cfg(target_os = \\\"macos\\\")\".dependencies]\ntar = { workspace = true }\nflate2 = \"1.0\"\n\n[dev-dependencies]\ntiny_http = \"0.12\"\n"
  },
  {
    "path": "crates/updater/README.md",
    "content": "# cargo-packager-updater\n\nUpdater for apps that was packaged by [`cargo-packager`](https://docs.rs/cargo-packager).\n\n## Checking for an update\n\nyou can check for an update using [`check_update`](https://docs.rs/cargo-packager-updater/latest/cargo_packager_updater/fn.check_update.html) function or construct a new [`Updater`](https://docs.rs/cargo-packager-updater/latest/cargo_packager_updater/struct.Updater.html)\nusing [`UpdaterBuilder`](https://docs.rs/cargo-packager-updater/latest/cargo_packager_updater/struct.UpdaterBuilder.html), both methods require the current version of the app and\na [`Config`](https://docs.rs/cargo-packager-updater/latest/cargo_packager_updater/struct.Config.html) that specifies the endpoints to request updates from and the public key of the update signature.\n\n```rs\nuse cargo_packager_updater::{check_update, Config};\n\nlet config = Config {\n  endpoints: vec![\"http://myserver.com/updates\".parse().unwrap()],\n  pubkey: \"<pubkey here>\".into(),\n  ..Default::default()\n};\nif let Some(update) = check_update(\"0.1.0\".parse().unwrap(), config).expect(\"failed while checking for update\") {\n  update.download_and_install().expect(\"failed to download and install update\");\n} else {\n  // there is no updates\n}\n\n```\n\n## Endpoints\n\nEach endpoint optionally could have `{{arch}}`, `{{target}}` or `{{current_version}}`\nwhich will be detected and replaced with the appropriate value before making a request to the endpoint.\n\n- `{{current_version}}`: The version of the app that is requesting the update.\n- `{{target}}`: The operating system name (one of `linux`, `windows` or `macos`).\n- `{{arch}}`: The architecture of the machine (one of `x86_64`, `i686`, `aarch64` or `armv7`).\n\nfor example:\n\n```\n \"https://releases.myapp.com/{{target}}/{{arch}}/{{current_version}}\"\n```\n\nwill turn into\n\n```\n \"https://releases.myapp.com/windows/x86_64/0.1.0\"\n```\n\nif you need more data, you can set additional request headers [`UpdaterBuilder::header`](https://docs.rs/cargo-packager-updater/latest/cargo_packager_updater/struct.UpdaterBuilder.html#method.header) to your liking.\n\n## Endpoint Response\n\nThe updater expects the endpoint to respond with 2 possible reponses:\n\n1.  [`204 No Content`](https://datatracker.ietf.org/doc/html/rfc2616#section-10.2.5) in case there is no updates available.\n2.  [`200 OK`](https://datatracker.ietf.org/doc/html/rfc2616#section-10.2.1) and a JSON response that could be either a JSON representing all available platform updates\n    or if using endpoints variables (see above) or a header to attach the current updater target,\n    then it can just return information for the requested target.\n\nThe JSON response is expected to have these fields set:\n\n- `version`: must be a valid semver, with or without a leading `v``, meaning that both `1.0.0`and`v1.0.0`are valid.\n- `url`or`platforms.[target].url`: must be a valid url to the update bundle.\n- `signature`or`platforms.[target].signature`: must be the content of the generated `.sig`file. The signature may change each time you run build your app so make sure to always update it.\n- `format`or`platforms.[target].format`: must be one of `app`, `appimage`, `nsis`or`wix`.\n\n> [!NOTE]\n> if using `platforms` object, each key is in the `OS-ARCH` format, where `OS` is one of `linux`, `macos` or `windows`, and `ARCH` is one of `x86_64`, `aarch64`, `i686` or `armv7`, see the example below.\n\nIt can also contain these optional fields:\n\n- `notes`: Here you can add notes about the update, like release notes.\n- `pub_date`: must be formatted according to [RFC 3339](https://datatracker.ietf.org/doc/html/rfc3339#section-5.8) if present.\n\nHere is an example of the two expected JSON formats:\n\n- **JSON for all platforms**\n\n  ```json\n  {\n    \"version\": \"v1.0.0\",\n    \"notes\": \"Test version\",\n    \"pub_date\": \"2020-06-22T19:25:57Z\",\n    \"platforms\": {\n      \"macos-x86_64\": {\n        \"signature\": \"Content of app.tar.gz.sig\",\n        \"url\": \"https://github.com/username/reponame/releases/download/v1.0.0/app-x86_64.app.tar.gz\",\n        \"format\": \"app\"\n      },\n      \"macos-aarch64\": {\n        \"signature\": \"Content of app.tar.gz.sig\",\n        \"url\": \"https://github.com/username/reponame/releases/download/v1.0.0/app-aarch64.app.tar.gz\",\n        \"format\": \"app\"\n      },\n      \"linux-x86_64\": {\n        \"signature\": \"Content of app.AppImage.sig\",\n        \"url\": \"https://github.com/username/reponame/releases/download/v1.0.0/app-amd64.AppImage.tar.gz\",\n        \"format\": \"appimage\"\n      },\n      \"windows-x86_64\": {\n        \"signature\": \"Content of app-setup.exe.sig or app.msi.sig, depending on the chosen format\",\n        \"url\": \"https://github.com/username/reponame/releases/download/v1.0.0/app-x64-setup.nsis.zip\",\n        \"format\": \"nsis or wix depending on the chosen format\"\n      }\n    }\n  }\n  ```\n\n- **JSON for one platform**\n\n  ```json\n  {\n    \"version\": \"0.2.0\",\n    \"pub_date\": \"2020-09-18T12:29:53+01:00\",\n    \"url\": \"https://mycompany.example.com/myapp/releases/myrelease.tar.gz\",\n    \"signature\": \"Content of the relevant .sig file\",\n    \"format\": \"app or nsis or wix or appimage depending on the release target and the chosen format\",\n    \"notes\": \"These are some release notes\"\n  }\n  ```\n\n## Update install mode on Windows\n\nYou can specify which install mode to use on Windows using [`WindowsConfig::install_mode`](https://docs.rs/cargo-packager-updater/latest/cargo_packager_updater/struct.WindowsConfig.html#structfield.install_mode) which can be on of:\n\n- `\"Passive\"`: There will be a small window with a progress bar. The update will be installed without requiring any user interaction. Generally recommended and the default mode.\n- `\"BasicUi\"`: There will be a basic user interface shown which requires user interaction to finish the installation.\n- `\"Quiet\"`: There will be no progress feedback to the user. With this mode the installer cannot request admin privileges by itself so it only works in user-wide installations or when your app itself already runs with admin privileges. Generally not recommended.\n\n## Licenses\n\nMIT or MIT/Apache 2.0 where applicable.\n"
  },
  {
    "path": "crates/updater/src/custom_serialization.rs",
    "content": "// Copyright 2019-2023 Tauri Programme within The Commons Conservancy\n// Copyright 2023-2023 CrabNebula Ltd.\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse std::{collections::HashMap, str::FromStr};\n\nuse semver::Version;\nuse serde::{de::Error, Deserialize, Deserializer};\nuse time::OffsetDateTime;\nuse url::Url;\n\nuse crate::{ReleaseManifestPlatform, RemoteRelease, RemoteReleaseData, UpdateFormat};\n\nfn parse_version<'de, D>(deserializer: D) -> std::result::Result<Version, D::Error>\nwhere\n    D: serde::Deserializer<'de>,\n{\n    let str = String::deserialize(deserializer)?;\n    Version::from_str(str.trim_start_matches('v')).map_err(Error::custom)\n}\n\nimpl<'de> Deserialize<'de> for UpdateFormat {\n    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>\n    where\n        D: Deserializer<'de>,\n    {\n        let lower = String::deserialize(deserializer)?.to_lowercase();\n        let variant = match lower.as_str() {\n            \"nsis\" => UpdateFormat::Nsis,\n            \"wix\" => UpdateFormat::Wix,\n            \"app\" => UpdateFormat::App,\n            \"appimage\" => UpdateFormat::AppImage,\n            _ => {\n                return Err(serde::de::Error::custom(\n                    \"Unkown updater format, expected one of 'nsis', 'wix', 'app' or 'appimage'\",\n                ))\n            }\n        };\n\n        Ok(variant)\n    }\n}\n\nimpl<'de> Deserialize<'de> for RemoteRelease {\n    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>\n    where\n        D: Deserializer<'de>,\n    {\n        #[derive(Deserialize)]\n        struct InnerRemoteRelease {\n            #[serde(alias = \"name\", deserialize_with = \"parse_version\")]\n            version: Version,\n            notes: Option<String>,\n            pub_date: Option<String>,\n            platforms: Option<HashMap<String, ReleaseManifestPlatform>>,\n            // dynamic platform response\n            url: Option<Url>,\n            signature: Option<String>,\n            format: Option<UpdateFormat>,\n        }\n\n        let release = InnerRemoteRelease::deserialize(deserializer)?;\n\n        let pub_date = if let Some(date) = release.pub_date {\n            Some(\n                OffsetDateTime::parse(&date, &time::format_description::well_known::Rfc3339)\n                    .map_err(|e| {\n                        serde::de::Error::custom(format!(\"invalid value for `pub_date`: {e}\"))\n                    })?,\n            )\n        } else {\n            None\n        };\n\n        Ok(RemoteRelease {\n            version: release.version,\n            notes: release.notes,\n            pub_date,\n            data: if let Some(platforms) = release.platforms {\n                RemoteReleaseData::Static { platforms }\n            } else {\n                RemoteReleaseData::Dynamic(ReleaseManifestPlatform {\n                    url: release.url.ok_or_else(|| {\n                        Error::custom(\"the `url` field was not set on the updater response\")\n                    })?,\n                    signature: release.signature.ok_or_else(|| {\n                        Error::custom(\"the `signature` field was not set on the updater response\")\n                    })?,\n                    format: release.format.ok_or_else(|| {\n                        Error::custom(\"the `format` field was not set on the updater response\")\n                    })?,\n                })\n            },\n        })\n    }\n}\n"
  },
  {
    "path": "crates/updater/src/error.rs",
    "content": "// Copyright 2019-2023 Tauri Programme within The Commons Conservancy\n// Copyright 2023-2023 CrabNebula Ltd.\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nuse thiserror::Error;\n\n/// All errors that can occur while running the updater.\n#[derive(Debug, Error)]\n#[non_exhaustive]\npub enum Error {\n    /// Endpoints are not sent.\n    #[error(\"Updater does not have any endpoints set.\")]\n    EmptyEndpoints,\n    /// IO errors.\n    #[error(transparent)]\n    Io(#[from] std::io::Error),\n    /// Semver errors.\n    #[error(transparent)]\n    Semver(#[from] semver::Error),\n    /// Serialization errors.\n    #[error(transparent)]\n    Serialization(#[from] serde_json::Error),\n    /// Could not fetch a valid response from the server.\n    #[error(\"Could not fetch a valid release JSON from the remote\")]\n    ReleaseNotFound,\n    /// Unsupported app architecture.\n    #[error(\"Unsupported application architecture, expected one of `x86`, `x86_64`, `arm` or `aarch64`.\")]\n    UnsupportedArch,\n    /// Unsupported update fromat.\n    #[error(\"Unsupported update format for the current target\")]\n    UnsupportedUpdateFormat,\n    /// Operating system is not supported.\n    #[error(\"Unsupported OS, expected one of `linux`, `macos` or `windows`.\")]\n    UnsupportedOs,\n    /// Failed to determine updater package extract path\n    #[error(\"Failed to determine updater package extract path.\")]\n    FailedToDetermineExtractPath,\n    /// Url parsing errors.\n    #[error(transparent)]\n    UrlParse(#[from] url::ParseError),\n    /// The platform was not found on the updater JSON response.\n    #[error(\"the platform `{0}` was not found on the response `platforms` object\")]\n    TargetNotFound(String),\n    /// Download failed\n    #[error(\"`{0}`\")]\n    Network(String),\n    /// `minisign_verify` errors.\n    #[error(transparent)]\n    Minisign(#[from] minisign_verify::Error),\n    /// `base64` errors.\n    #[error(transparent)]\n    Base64(#[from] base64::DecodeError),\n    /// UTF8 Errors in signature.\n    #[error(\"The signature {0} could not be decoded, please check if it is a valid base64 string. The signature must be the contents of the `.sig` file generated by the Tauri bundler, as a string.\")]\n    SignatureUtf8(String),\n    /// Temp dir is not on same mount mount. This prevents our updater to rename the AppImage to a temp file.\n    #[error(\"temp directory is not on the same mount point as the AppImage\")]\n    TempDirNotOnSameMountPoint,\n    /// The `reqwest` crate errors.\n    #[error(transparent)]\n    Reqwest(#[from] reqwest::Error),\n    /// The `http` crate errors.\n    #[error(transparent)]\n    Http(#[from] http::Error),\n    /// Error returned when persisting a temporary file fails.\n    #[error(transparent)]\n    PersistError(#[from] tempfile::PersistError),\n}\n\n/// Convenience alias for `cargo-packager-updater` crate Result type.\npub type Result<T> = std::result::Result<T, Error>;\n"
  },
  {
    "path": "crates/updater/src/lib.rs",
    "content": "// Copyright 2019-2023 Tauri Programme within The Commons Conservancy\n// Copyright 2023-2023 CrabNebula Ltd.\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! # cargo-packager-updater\n//!\n//! Updater for apps that was packaged by [`cargo-packager`](https://docs.rs/cargo-packager).\n//!\n//! ## Checking for an update\n//!\n//! you can check for an update using [`check_update`] function or construct a new [`Updater`]\n//! using [`UpdaterBuilder`], both methods require the current version of the app and\n//! a [`Config`] that specifies the endpoints to request updates from and the public key of the update signature.\n//!\n//! ```no_run\n//! use cargo_packager_updater::{check_update, Config};\n//!\n//! let config = Config {\n//!   endpoints: vec![\"http://myserver.com/updates\".parse().unwrap()],\n//!   pubkey: \"<pubkey here>\".into(),\n//!   ..Default::default()\n//! };\n//! if let Some(update) = check_update(\"0.1.0\".parse().unwrap(), config).expect(\"failed while checking for update\") {\n//!     update.download_and_install().expect(\"failed to download and install update\");\n//! } else {\n//!     // there is no updates\n//! }\n//!\n//! ```\n//!\n//! ## Endpoints\n//!\n//! Each endpoint optionally could have `{{arch}}`, `{{target}}` or `{{current_version}}`\n//! which will be detected and replaced with the appropriate value before making a request to the endpoint.\n//!\n//! - `{{current_version}}`: The version of the app that is requesting the update.\n//! - `{{target}}`: The operating system name (one of `linux`, `windows` or `macos`).\n//! - `{{arch}}`: The architecture of the machine (one of `x86_64`, `i686`, `aarch64` or `armv7`).\n//!\n//! for example:\n//! ```text\n//! \"https://releases.myapp.com/{{target}}/{{arch}}/{{current_version}}\"\n//! ```\n//! will turn into\n//! ```text\n//! \"https://releases.myapp.com/windows/x86_64/0.1.0\"\n//! ```\n//!\n//! if you need more data, you can set additional request headers [`UpdaterBuilder::header`] to your liking.\n//!\n//! ## Endpoint Response\n//!\n//! The updater expects the endpoint to respond with 2 possible responses:\n//!\n//! 1. [`204 No Content`](https://datatracker.ietf.org/doc/html/rfc2616#section-10.2.5) in case there is no updates available.\n//! 2. [`200 OK`](https://datatracker.ietf.org/doc/html/rfc2616#section-10.2.1) and a JSON response that could be either a JSON representing all available platform updates\n//!    or if using endpoints variables (see above) or a header to attach the current updater target,\n//!    then it can just return information for the requested target.\n//!\n//! The JSON response is expected to have these fields set:\n//!\n//! - `version`: must be a valid semver, with or without a leading `v``, meaning that both `1.0.0` and `v1.0.0` are valid.\n//! - `url` or `platforms.[target].url`: must be a valid url to the update bundle\n//! - `signature` or `platforms.[target].signature`: must be the content of the generated `.sig` file. The signature may change each time you run build your app so make sure to always update it.\n//! - `format` or `platforms.[target].format`: must be one of `app`, `appimage`, `nsis` or `wix`.\n//!\n//! <div style=\"border-left: 2px solid rgba(47,129,247);padding-left:0.75em;\">\n//!   <p style=\"display:flex;align-items:center;gap:3px;color:rgb(47,129,247)\">\n//!     <svg viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill=\"rgb(47,129,247)\" d=\"M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z\"></path></svg>\n//!     Note\n//!   </p>\n//!   if using <code>platforms</code> object, each key is in the <code>OS-ARCH</code> format, where <code>OS</code> is one of <code>linux</code>, <code>macos</code> or <code>windows</code>, and <code>ARCH</code> is one of <code>x86_64</code>, <code>aarch64</code>, <code>i686</code> or <code>armv7</code>, see the example below.\n//! </div>\n//! <br>\n//!\n//! It can also contain these optional fields:\n//! - `notes`: Here you can add notes about the update, like release notes.\n//! - `pub_date`: must be formatted according to [RFC 3339](https://datatracker.ietf.org/doc/html/rfc3339#section-5.8) if present.\n//!\n//! Here is an example of the two expected JSON formats:\n//!\n//!  - **JSON for all platforms**\n//!\n//!    ```json\n//!    {\n//!      \"version\": \"v1.0.0\",\n//!      \"notes\": \"Test version\",\n//!      \"pub_date\": \"2020-06-22T19:25:57Z\",\n//!      \"platforms\": {\n//!        \"macos-x86_64\": {\n//!          \"signature\": \"Content of app.tar.gz.sig\",\n//!          \"url\": \"https://github.com/username/reponame/releases/download/v1.0.0/app-x86_64.app.tar.gz\",\n//!          \"format\": \"app\"\n//!        },\n//!        \"macos-aarch64\": {\n//!          \"signature\": \"Content of app.tar.gz.sig\",\n//!          \"url\": \"https://github.com/username/reponame/releases/download/v1.0.0/app-aarch64.app.tar.gz\",\n//!          \"format\": \"app\"\n//!        },\n//!        \"linux-x86_64\": {\n//!          \"signature\": \"Content of app.AppImage.sig\",\n//!          \"url\": \"https://github.com/username/reponame/releases/download/v1.0.0/app-amd64.AppImage.tar.gz\",\n//!          \"format\": \"appimage\"\n//!        },\n//!        \"windows-x86_64\": {\n//!          \"signature\": \"Content of app-setup.exe.sig or app.msi.sig, depending on the chosen format\",\n//!          \"url\": \"https://github.com/username/reponame/releases/download/v1.0.0/app-x64-setup.nsis.zip\",\n//!          \"format\": \"nsis or wix depending on the chosen format\"\n//!        }\n//!      }\n//!    }\n//!    ```\n//!\n//!  - **JSON for one platform**\n//!\n//!    ```json\n//!    {\n//!      \"version\": \"0.2.0\",\n//!      \"pub_date\": \"2020-09-18T12:29:53+01:00\",\n//!      \"url\": \"https://mycompany.example.com/myapp/releases/myrelease.tar.gz\",\n//!      \"signature\": \"Content of the relevant .sig file\",\n//!      \"format\": \"app or nsis or wix or appimage depending on the release target and the chosen format\",\n//!      \"notes\": \"These are some release notes\"\n//!    }\n//!    ```\n//!\n//!\n//! ## Update install mode on Windows\n//!\n//! You can specify which install mode to use on Windows using [`WindowsConfig::install_mode`] which can be one of:\n//!\n//! - [`\"Passive\"`](WindowsUpdateInstallMode::Passive): There will be a small window with a progress bar. The update will be installed without requiring any user interaction. Generally recommended and the default mode.\n//! - [`\"BasicUi\"`](WindowsUpdateInstallMode::BasicUi): There will be a basic user interface shown which requires user interaction to finish the installation.\n//! - [`\"Quiet\"`](WindowsUpdateInstallMode::Quiet): There will be no progress feedback to the user. With this mode the installer cannot request admin privileges by itself so it only works in user-wide installations or when your app itself already runs with admin privileges. Generally not recommended.\n\n#![deny(missing_docs)]\n\nuse base64::Engine;\nuse cargo_packager_utils::current_exe::current_exe;\nuse http::{\n    header::{ACCEPT, USER_AGENT},\n    HeaderName,\n};\nuse minisign_verify::{PublicKey, Signature};\nuse percent_encoding::{AsciiSet, CONTROLS};\nuse reqwest::{\n    blocking::Client,\n    header::{HeaderMap, HeaderValue},\n    StatusCode,\n};\nuse semver::Version;\nuse serde::{Deserialize, Serialize};\nuse std::{\n    collections::HashMap,\n    io::{Cursor, Read},\n    path::{Path, PathBuf},\n    time::Duration,\n};\nuse time::OffsetDateTime;\nuse url::Url;\n\nmod custom_serialization;\nmod error;\n\npub use crate::error::*;\npub use http;\npub use reqwest;\npub use semver;\npub use url;\n\n/// Install modes for the Windows update.\n#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]\npub enum WindowsUpdateInstallMode {\n    /// Specifies there's a basic UI during the installation process, including a final dialog box at the end.\n    BasicUi,\n    /// The quiet mode means there's no user interaction required.\n    /// Requires admin privileges if the installer does.\n    Quiet,\n    /// Specifies unattended mode, which means the installation only shows a progress bar.\n    #[default]\n    Passive,\n}\n\nimpl WindowsUpdateInstallMode {\n    /// Returns the associated `msiexec.exe` arguments.\n    pub fn msiexec_args(&self) -> &'static [&'static str] {\n        match self {\n            Self::BasicUi => &[\"/qb+\"],\n            Self::Quiet => &[\"/quiet\"],\n            Self::Passive => &[\"/passive\"],\n        }\n    }\n\n    /// Returns the associated nsis arguments.\n    pub fn nsis_args(&self) -> &'static [&'static str] {\n        match self {\n            Self::Passive => &[\"/P\", \"/R\"],\n            Self::Quiet => &[\"/S\", \"/R\"],\n            _ => &[],\n        }\n    }\n}\n\n/// The updater configuration for Windows.\n#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct WindowsConfig {\n    /// Additional arguments given to the NSIS or WiX installer.\n    pub installer_args: Option<Vec<String>>,\n    /// The installation mode for the update on Windows. Defaults to `passive`.\n    pub install_mode: Option<WindowsUpdateInstallMode>,\n}\n\n/// Updater configuration.\n#[derive(Debug, Clone, Default, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct Config {\n    /// The updater endpoints.\n    ///\n    /// Each endpoint optionally could have `{{arch}}`, `{{target}}` or `{{current_version}}`\n    /// which will be detected and replaced with the appropriate value before making a request to the endpoint.\n    ///\n    /// - `{{current_version}}`: The version of the app that is requesting the update.\n    /// - `{{target}}`: The operating system name (one of `linux`, `windows` or `macos`).\n    /// - `{{arch}}`: The architecture of the machine (one of `x86_64`, `i686`, `aarch64` or `armv7`).\n    pub endpoints: Vec<Url>,\n    /// Signature public key.\n    pub pubkey: String,\n    /// The Windows configuration for the updater.\n    pub windows: Option<WindowsConfig>,\n}\n\n/// Supported update format\n#[derive(Debug, Serialize, Copy, Clone)]\npub enum UpdateFormat {\n    /// The NSIS installer (.exe).\n    Nsis,\n    /// The Microsoft Software Installer (.msi) through WiX Toolset.\n    Wix,\n    /// The Linux AppImage package (.AppImage).\n    AppImage,\n    /// The macOS application bundle (.app).\n    App,\n}\n\nimpl std::fmt::Display for UpdateFormat {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(\n            f,\n            \"{}\",\n            match self {\n                UpdateFormat::Nsis => \"nsis\",\n                UpdateFormat::Wix => \"wix\",\n                UpdateFormat::AppImage => \"appimage\",\n                UpdateFormat::App => \"app\",\n            }\n        )\n    }\n}\n\n/// Information about a release\n#[derive(Debug, Deserialize, Serialize, Clone)]\npub struct ReleaseManifestPlatform {\n    /// Download URL for the platform\n    pub url: Url,\n    /// Signature for the platform\n    pub signature: String,\n    /// Update format\n    pub format: UpdateFormat,\n}\n\n/// Information about a release data.\n#[derive(Debug, Deserialize, Serialize, Clone)]\n#[serde(untagged)]\npub enum RemoteReleaseData {\n    /// Dynamic release data based on the platform the update has been requested from.\n    Dynamic(ReleaseManifestPlatform),\n    /// A map of release data for each platform, where the key is `<platform>-<arch>`.\n    Static {\n        /// A map of release data for each platform, where the key is `<platform>-<arch>`.\n        platforms: HashMap<String, ReleaseManifestPlatform>,\n    },\n}\n\n/// Information about a release returned by the remote update server.\n///\n/// This type can have one of two shapes: Server Format (Dynamic Format) and Static Format.\n#[derive(Debug, Clone)]\npub struct RemoteRelease {\n    /// Version to install.\n    pub version: Version,\n    /// Release notes.\n    pub notes: Option<String>,\n    /// Release date.\n    pub pub_date: Option<OffsetDateTime>,\n    /// Release data.\n    pub data: RemoteReleaseData,\n}\n\nimpl RemoteRelease {\n    /// The release's download URL for the given target.\n    pub fn download_url(&self, target: &str) -> Result<&Url> {\n        match self.data {\n            RemoteReleaseData::Dynamic(ref platform) => Ok(&platform.url),\n            RemoteReleaseData::Static { ref platforms } => platforms\n                .get(target)\n                .map_or(Err(Error::TargetNotFound(target.to_string())), |p| {\n                    Ok(&p.url)\n                }),\n        }\n    }\n\n    /// The release's signature for the given target.\n    pub fn signature(&self, target: &str) -> Result<&String> {\n        match self.data {\n            RemoteReleaseData::Dynamic(ref platform) => Ok(&platform.signature),\n            RemoteReleaseData::Static { ref platforms } => platforms\n                .get(target)\n                .map_or(Err(Error::TargetNotFound(target.to_string())), |platform| {\n                    Ok(&platform.signature)\n                }),\n        }\n    }\n\n    /// The release's update format for the given target.\n    pub fn format(&self, target: &str) -> Result<UpdateFormat> {\n        match self.data {\n            RemoteReleaseData::Dynamic(ref platform) => Ok(platform.format),\n            RemoteReleaseData::Static { ref platforms } => platforms\n                .get(target)\n                .map_or(Err(Error::TargetNotFound(target.to_string())), |platform| {\n                    Ok(platform.format)\n                }),\n        }\n    }\n}\n\n/// An [`Updater`] builder.\npub struct UpdaterBuilder {\n    current_version: Version,\n    config: Config,\n    version_comparator: Option<Box<dyn Fn(Version, RemoteRelease) -> bool + Send + Sync>>,\n    executable_path: Option<PathBuf>,\n    target: Option<String>,\n    headers: HeaderMap,\n    timeout: Option<Duration>,\n}\n\nimpl UpdaterBuilder {\n    /// Create a new updater builder request.\n    pub fn new(current_version: Version, config: crate::Config) -> Self {\n        Self {\n            current_version,\n            config,\n            version_comparator: None,\n            executable_path: None,\n            target: None,\n            headers: Default::default(),\n            timeout: None,\n        }\n    }\n\n    /// A custom function to compare whether a new version exists or not.\n    pub fn version_comparator<F: Fn(Version, RemoteRelease) -> bool + Send + Sync + 'static>(\n        mut self,\n        f: F,\n    ) -> Self {\n        self.version_comparator = Some(Box::new(f));\n        self\n    }\n\n    /// Specify a public key to use when checking if the update is valid.\n    pub fn pub_key(mut self, pub_key: impl Into<String>) -> Self {\n        self.config.pubkey = pub_key.into();\n        self\n    }\n\n    /// Specify the target to request an update for.\n    pub fn target(mut self, target: impl Into<String>) -> Self {\n        self.target.replace(target.into());\n        self\n    }\n\n    /// Specify the endpoints where an update will be requested from.\n    pub fn endpoints(mut self, endpoints: Vec<Url>) -> Self {\n        self.config.endpoints = endpoints;\n        self\n    }\n\n    /// Specify the path to the current executable where the updater will try to update in the same directory.\n    pub fn executable_path<P: AsRef<Path>>(mut self, p: P) -> Self {\n        self.executable_path.replace(p.as_ref().into());\n        self\n    }\n\n    /// Add a header to the updater request.\n    pub fn header<K, V>(mut self, key: K, value: V) -> Result<Self>\n    where\n        HeaderName: TryFrom<K>,\n        <HeaderName as TryFrom<K>>::Error: Into<http::Error>,\n        HeaderValue: TryFrom<V>,\n        <HeaderValue as TryFrom<V>>::Error: Into<http::Error>,\n    {\n        let key: std::result::Result<HeaderName, http::Error> = key.try_into().map_err(Into::into);\n        let value: std::result::Result<HeaderValue, http::Error> =\n            value.try_into().map_err(Into::into);\n        self.headers.insert(key?, value?);\n\n        Ok(self)\n    }\n\n    /// Specify a timeout for the updater request.\n    pub fn timeout(mut self, timeout: Duration) -> Self {\n        self.timeout = Some(timeout);\n        self\n    }\n\n    /// Specify custom installer args on Windows.\n    pub fn installer_args<I, S>(mut self, args: I) -> Self\n    where\n        I: IntoIterator<Item = S>,\n        S: Into<String>,\n    {\n        if self.config.windows.is_none() {\n            self.config.windows.replace(Default::default());\n        }\n        self.config\n            .windows\n            .as_mut()\n            .unwrap()\n            .installer_args\n            .replace(args.into_iter().map(Into::into).collect());\n        self\n    }\n\n    /// Build the updater.\n    pub fn build(self) -> Result<Updater> {\n        if self.config.endpoints.is_empty() {\n            return Err(Error::EmptyEndpoints);\n        };\n\n        let arch = get_updater_arch().ok_or(Error::UnsupportedArch)?;\n        let (target, json_target) = if let Some(target) = self.target {\n            (target.clone(), target)\n        } else {\n            let target = get_updater_target().ok_or(Error::UnsupportedOs)?;\n            (target.to_string(), format!(\"{target}-{arch}\"))\n        };\n\n        let executable_path = match self.executable_path {\n            Some(p) => p,\n            #[cfg(not(any(windows, target_os = \"macos\")))]\n            None => {\n                if let Some(appimage) = std::env::var_os(\"APPIMAGE\").map(PathBuf::from) {\n                    appimage\n                } else {\n                    current_exe()?\n                }\n            }\n            #[cfg(any(windows, target_os = \"macos\"))]\n            _ => current_exe()?,\n        };\n\n        // Get the extract_path from the provided executable_path\n        #[cfg(any(windows, target_os = \"macos\"))]\n        let extract_path = extract_path_from_executable(&executable_path)?;\n        #[cfg(not(any(windows, target_os = \"macos\")))]\n        let extract_path = executable_path;\n\n        Ok(Updater {\n            config: self.config,\n            current_version: self.current_version,\n            version_comparator: self.version_comparator,\n            timeout: self.timeout,\n            arch,\n            target,\n            json_target,\n            headers: self.headers,\n            extract_path,\n        })\n    }\n}\n\n/// A type that can check for updates and created by [`UpdaterBuilder`].\npub struct Updater {\n    config: Config,\n    current_version: Version,\n    version_comparator: Option<Box<dyn Fn(Version, RemoteRelease) -> bool + Send + Sync>>,\n    timeout: Option<Duration>,\n    arch: &'static str,\n    // The `{{target}}` variable we replace in the endpoint\n    target: String,\n    // The value we search if the updater server returns a JSON with the `platforms` object\n    json_target: String,\n    headers: HeaderMap,\n    extract_path: PathBuf,\n}\n\nimpl Updater {\n    /// Check for an update. Returns `None` if an update was not found, otherwise it will be `Some`.\n    pub fn check(&self) -> Result<Option<Update>> {\n        // we want JSON only\n        let mut headers = self.headers.clone();\n        if !headers.contains_key(ACCEPT) {\n            headers.insert(ACCEPT, HeaderValue::from_str(\"application/json\").unwrap());\n        }\n\n        // Set SSL certs for linux if they aren't available.\n        #[cfg(target_os = \"linux\")]\n        {\n            if std::env::var_os(\"SSL_CERT_FILE\").is_none() {\n                std::env::set_var(\"SSL_CERT_FILE\", \"/etc/ssl/certs/ca-certificates.crt\");\n            }\n            if std::env::var_os(\"SSL_CERT_DIR\").is_none() {\n                std::env::set_var(\"SSL_CERT_DIR\", \"/etc/ssl/certs\");\n            }\n        }\n\n        let mut remote_release: Option<RemoteRelease> = None;\n        let mut last_error: Option<Error> = None;\n\n        let version = self.current_version.to_string();\n        let version = version.as_bytes();\n        const CONTROLS_ADD: &AsciiSet = &CONTROLS.add(b'+');\n        let encoded_version = percent_encoding::percent_encode(version, CONTROLS_ADD);\n        let encoded_version = encoded_version.to_string();\n\n        for url in &self.config.endpoints {\n            // replace {{current_version}}, {{target}} and {{arch}} in the provided URL\n            // this is useful if we need to query example\n            // https://releases.myapp.com/update/{{target}}/{{arch}}/{{current_version}}\n            // will be translated into ->\n            // https://releases.myapp.com/update/macos/aarch64/1.0.0\n            // The main objective is if the update URL is defined via the Cargo.toml\n            // the URL will be generated dynamically\n            let url: Url = url\n                .to_string()\n                // url::Url automatically url-encodes the path components\n                .replace(\"%7B%7Bcurrent_version%7D%7D\", &encoded_version)\n                .replace(\"%7B%7Btarget%7D%7D\", &self.target)\n                .replace(\"%7B%7Barch%7D%7D\", self.arch)\n                // but not query parameters\n                .replace(\"{{current_version}}\", &encoded_version)\n                .replace(\"{{target}}\", &self.target)\n                .replace(\"{{arch}}\", self.arch)\n                .parse()?;\n\n            log::debug!(\"checking for updates {url}\");\n\n            let mut request = Client::new().get(url).headers(headers.clone());\n            if let Some(timeout) = self.timeout {\n                request = request.timeout(timeout);\n            }\n            let response = request.send();\n\n            match response {\n                Ok(res) => {\n                    if res.status().is_success() {\n                        // no updates found!\n                        if StatusCode::NO_CONTENT == res.status() {\n                            log::debug!(\"update endpoint returned 204 No Content\");\n                            return Ok(None);\n                        };\n\n                        let update_response: serde_json::Value = res.json()?;\n                        log::debug!(\"update response: {update_response:?}\");\n\n                        match serde_json::from_value::<RemoteRelease>(update_response)\n                            .map_err(Into::into)\n                        {\n                            Ok(release) => {\n                                log::debug!(\"parsed release response {release:?}\");\n                                last_error = None;\n                                remote_release = Some(release);\n                                // we found a relase, break the loop\n                                break;\n                            }\n                            Err(err) => {\n                                log::error!(\"failed to deserialize update response: {err}\");\n                                last_error = Some(err)\n                            }\n                        }\n                    } else {\n                        log::error!(\n                            \"update endpoint did not respond with a successful status code\"\n                        );\n                    }\n                }\n                Err(err) => {\n                    log::error!(\"failed to check for updates: {err}\");\n                    last_error = Some(err.into());\n                }\n            }\n        }\n\n        // Last error is cleaned on success.\n        // Shouldn't be triggered if we had a successfull call\n        if let Some(error) = last_error {\n            return Err(error);\n        }\n\n        // Extracted remote metadata\n        let release = remote_release.ok_or(Error::ReleaseNotFound)?;\n\n        let should_update = match self.version_comparator.as_ref() {\n            Some(comparator) => comparator(self.current_version.clone(), release.clone()),\n            None => release.version > self.current_version,\n        };\n\n        let update = if should_update {\n            Some(Update {\n                current_version: self.current_version.to_string(),\n                config: self.config.clone(),\n                target: self.target.clone(),\n                extract_path: self.extract_path.clone(),\n                version: release.version.to_string(),\n                date: release.pub_date,\n                download_url: release.download_url(&self.json_target)?.to_owned(),\n                body: release.notes.clone(),\n                signature: release.signature(&self.json_target)?.to_owned(),\n                timeout: self.timeout,\n                headers: self.headers.clone(),\n                format: release.format(&self.json_target)?,\n            })\n        } else {\n            None\n        };\n\n        Ok(update)\n    }\n}\n\n/// Information about an update and associted methods to perform the update.\n#[derive(Debug, Clone)]\npub struct Update {\n    /// Config used to check for this update.\n    pub config: Config,\n    /// Update description\n    pub body: Option<String>,\n    /// Version used to check for update\n    pub current_version: String,\n    /// Version announced\n    pub version: String,\n    /// Update publish date\n    pub date: Option<OffsetDateTime>,\n    /// Target\n    pub target: String,\n    /// Extract path\n    pub extract_path: PathBuf,\n    /// Download URL announced\n    pub download_url: Url,\n    /// Signature announced\n    pub signature: String,\n    /// Request timeout\n    pub timeout: Option<Duration>,\n    /// Request headers\n    pub headers: HeaderMap,\n    /// Update format\n    pub format: UpdateFormat,\n}\n\nimpl Update {\n    /// Downloads the updater package, verifies it then return it as bytes.\n    ///\n    /// Use [`Update::install`] to install it\n    pub fn download(&self) -> Result<Vec<u8>> {\n        self.download_extended_inner(\n            None::<Box<dyn Fn(usize, Option<u64>)>>,\n            None::<Box<dyn FnOnce()>>,\n        )\n    }\n\n    /// Downloads the updater package, verifies it then return it as bytes.\n    ///\n    /// Takes two callbacks, the first will be excuted when receiveing each chunk\n    /// while the second will be called only once when the download finishes.\n    ///\n    /// Use [`Update::install`] to install it\n    pub fn download_extended<C: Fn(usize, Option<u64>), D: FnOnce()>(\n        &self,\n        on_chunk: C,\n        on_download_finish: D,\n    ) -> Result<Vec<u8>> {\n        self.download_extended_inner(Some(on_chunk), Some(on_download_finish))\n    }\n\n    fn download_extended_inner<C: Fn(usize, Option<u64>), D: FnOnce()>(\n        &self,\n        on_chunk: Option<C>,\n        on_download_finish: Option<D>,\n    ) -> Result<Vec<u8>> {\n        // set our headers\n        let mut headers = self.headers.clone();\n        if !headers.contains_key(ACCEPT) {\n            headers.insert(\n                ACCEPT,\n                HeaderValue::from_str(\"application/octet-stream\").unwrap(),\n            );\n        }\n        if !headers.contains_key(USER_AGENT) {\n            headers.insert(\n                USER_AGENT,\n                HeaderValue::from_str(\"cargo-packager-updater\").unwrap(),\n            );\n        }\n\n        let mut request = Client::new()\n            .get(self.download_url.clone())\n            .headers(headers);\n        if let Some(timeout) = self.timeout {\n            request = request.timeout(timeout);\n        }\n\n        struct DownloadProgress<R, C: Fn(usize, Option<u64>)> {\n            content_length: Option<u64>,\n            inner: R,\n            on_chunk: Option<C>,\n        }\n\n        impl<R: Read, C: Fn(usize, Option<u64>)> Read for DownloadProgress<R, C> {\n            fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {\n                self.inner.read(buf).inspect(|&n| {\n                    if let Some(on_chunk) = &self.on_chunk {\n                        (on_chunk)(n, self.content_length);\n                    }\n                })\n            }\n        }\n\n        let response = request.send()?;\n\n        if !response.status().is_success() {\n            return Err(Error::Network(format!(\n                \"Download request failed with status: {}\",\n                response.status()\n            )));\n        }\n\n        let mut source = DownloadProgress {\n            content_length: response\n                .headers()\n                .get(\"Content-Length\")\n                .and_then(|value| value.to_str().ok())\n                .and_then(|value| value.parse().ok()),\n            inner: response,\n            on_chunk,\n        };\n\n        let mut buffer = Vec::new();\n\n        let _ = std::io::copy(&mut source, &mut buffer)?;\n        if let Some(on_download_finish) = on_download_finish {\n            on_download_finish();\n        }\n\n        let mut update_buffer = Cursor::new(&buffer);\n\n        verify_signature(&mut update_buffer, &self.signature, &self.config.pubkey)?;\n\n        Ok(buffer)\n    }\n\n    /// Installs the updater package downloaded by [`Update::download`]\n    pub fn install(&self, bytes: Vec<u8>) -> Result<()> {\n        self.install_inner(bytes)\n    }\n\n    /// Downloads and installs the updater package\n    pub fn download_and_install(&self) -> Result<()> {\n        let bytes = self.download()?;\n        self.install(bytes)\n    }\n\n    /// Downloads and installs the updater package\n    ///\n    /// Takes two callbacks, the first will be excuted when receiveing each chunk\n    /// while the second will be called only once when the download finishes.\n    pub fn download_and_install_extended<C: Fn(usize, Option<u64>), D: FnOnce()>(\n        &self,\n        on_chunk: C,\n        on_download_finish: D,\n    ) -> Result<()> {\n        let bytes = self.download_extended(on_chunk, on_download_finish)?;\n        self.install(bytes)\n    }\n\n    // Windows\n    //\n    // ### Expected installers:\n    // │── [AppName]_[version]_x64.msi           # Application MSI\n    // │── [AppName]_[version]_x64-setup.exe           # NSIS installer\n    // └── ...\n    #[cfg(windows)]\n    fn install_inner(&self, bytes: Vec<u8>) -> Result<()> {\n        use std::{io::Write, os::windows::process::CommandExt, process::Command};\n\n        let extension = match self.format {\n            UpdateFormat::Nsis => \".exe\",\n            UpdateFormat::Wix => \".msi\",\n            _ => return Err(crate::Error::UnsupportedUpdateFormat),\n        };\n\n        let mut temp_file = tempfile::Builder::new().suffix(extension).tempfile()?;\n        temp_file.write_all(&bytes)?;\n        let (f, path) = temp_file.keep()?;\n        drop(f);\n\n        let system_root = std::env::var(\"SYSTEMROOT\");\n        let powershell_path = system_root.as_ref().map_or_else(\n            |_| \"powershell.exe\".to_string(),\n            |p| format!(\"{p}\\\\System32\\\\WindowsPowerShell\\\\v1.0\\\\powershell.exe\"),\n        );\n\n        const CREATE_NO_WINDOW: u32 = 0x08000000;\n\n        // we support 2 type of files exe & msi for now\n        // If it's an `exe` we expect an installer not a runtime.\n        match self.format {\n            UpdateFormat::Nsis => {\n                // we need to wrap the installer path in quotes for Start-Process\n                let mut installer_path = std::ffi::OsString::new();\n                installer_path.push(\"\\\"\");\n                installer_path.push(&path);\n                installer_path.push(\"\\\"\");\n\n                let installer_args = self\n                    .config\n                    .windows\n                    .as_ref()\n                    .and_then(|w| w.installer_args.clone())\n                    .unwrap_or_default();\n                let installer_args = [\n                    self.config\n                        .windows\n                        .as_ref()\n                        .and_then(|w| w.install_mode.clone())\n                        .unwrap_or_default()\n                        .nsis_args(),\n                    installer_args\n                        .iter()\n                        .map(AsRef::as_ref)\n                        .collect::<Vec<_>>()\n                        .as_slice(),\n                ]\n                .concat();\n\n                // Run the installer\n                let mut cmd = Command::new(powershell_path);\n                cmd.creation_flags(CREATE_NO_WINDOW)\n                    .args([\"-NoProfile\", \"-WindowStyle\", \"Hidden\"])\n                    .args([\"Start-Process\"])\n                    .arg(installer_path);\n                if !installer_args.is_empty() {\n                    cmd.arg(\"-ArgumentList\").arg(installer_args.join(\", \"));\n                }\n                cmd.spawn().expect(\"installer failed to start\");\n\n                std::process::exit(0);\n            }\n            UpdateFormat::Wix => {\n                {\n                    // we need to wrap the current exe path in quotes for Start-Process\n                    let mut current_exe_arg = std::ffi::OsString::new();\n                    current_exe_arg.push(\"\\\"\");\n                    current_exe_arg.push(current_exe()?);\n                    current_exe_arg.push(\"\\\"\");\n\n                    let mut mis_path = std::ffi::OsString::new();\n                    mis_path.push(\"\\\"\\\"\\\"\");\n                    mis_path.push(&path);\n                    mis_path.push(\"\\\"\\\"\\\"\");\n\n                    let installer_args = self\n                        .config\n                        .windows\n                        .as_ref()\n                        .and_then(|w| w.installer_args.clone())\n                        .unwrap_or_default();\n                    let installer_args = [\n                        self.config\n                            .windows\n                            .as_ref()\n                            .and_then(|w| w.install_mode.clone())\n                            .unwrap_or_default()\n                            .msiexec_args(),\n                        installer_args\n                            .iter()\n                            .map(AsRef::as_ref)\n                            .collect::<Vec<_>>()\n                            .as_slice(),\n                    ]\n                    .concat();\n\n                    // run the installer and relaunch the application\n                    let powershell_install_res = Command::new(powershell_path)\n                        .creation_flags(CREATE_NO_WINDOW)\n                        .args([\"-NoProfile\", \"-WindowStyle\", \"Hidden\"])\n                        .args([\n                            \"Start-Process\",\n                            \"-Wait\",\n                            \"-FilePath\",\n                            \"$env:SYSTEMROOT\\\\System32\\\\msiexec.exe\",\n                            \"-ArgumentList\",\n                        ])\n                        .arg(\"/i,\")\n                        .arg(&mis_path)\n                        .arg(format!(\", {}, /promptrestart;\", installer_args.join(\", \")))\n                        .arg(\"Start-Process\")\n                        .arg(current_exe_arg)\n                        .spawn();\n                    if powershell_install_res.is_err() {\n                        // fallback to running msiexec directly - relaunch won't be available\n                        // we use this here in case powershell fails in an older machine somehow\n                        let msiexec_path = system_root.as_ref().map_or_else(\n                            |_| \"msiexec.exe\".to_string(),\n                            |p| format!(\"{p}\\\\System32\\\\msiexec.exe\"),\n                        );\n                        let _ = Command::new(msiexec_path)\n                            .arg(\"/i\")\n                            .arg(mis_path)\n                            .args(installer_args)\n                            .arg(\"/promptrestart\")\n                            .spawn();\n                    }\n\n                    std::process::exit(0);\n                }\n            }\n            _ => unreachable!(),\n        }\n    }\n\n    // Linux (AppImage)\n    //\n    // ### Expected structure:\n    // ├── [AppName]_[version]_amd64.AppImage.tar.gz    # GZ generated by cargo-packager\n    // │   └──[AppName]_[version]_amd64.AppImage        # Application AppImage\n    // └── ...\n    //\n    // We should have an AppImage already installed to be able to copy and install\n    // the extract_path is the current AppImage path\n    // tmp_dir is where our new AppImage is found\n    #[cfg(any(\n        target_os = \"linux\",\n        target_os = \"dragonfly\",\n        target_os = \"freebsd\",\n        target_os = \"netbsd\",\n        target_os = \"openbsd\"\n    ))]\n    fn install_inner(&self, bytes: Vec<u8>) -> Result<()> {\n        use std::fs;\n\n        match self.format {\n            UpdateFormat::AppImage => {}\n            _ => return Err(crate::Error::UnsupportedUpdateFormat),\n        };\n\n        let extract_path_metadata = self.extract_path.metadata()?;\n        let tmp_dir_locations = vec![\n            Box::new(|| Some(std::env::temp_dir())) as Box<dyn FnOnce() -> Option<PathBuf>>,\n            Box::new(dirs::cache_dir),\n            Box::new(|| Some(self.extract_path.parent().unwrap().to_path_buf())),\n        ];\n\n        for tmp_dir_location in tmp_dir_locations {\n            if let Some(tmp_dir_root) = tmp_dir_location() {\n                use std::os::unix::fs::{MetadataExt, PermissionsExt};\n\n                let tmp_dir = tempfile::Builder::new()\n                    .prefix(\"current_app\")\n                    .tempdir_in(tmp_dir_root)?;\n                let tmp_dir_metadata = tmp_dir.path().metadata()?;\n\n                if extract_path_metadata.dev() == tmp_dir_metadata.dev() {\n                    let mut perms = tmp_dir_metadata.permissions();\n                    perms.set_mode(0o700);\n                    fs::set_permissions(&tmp_dir, perms)?;\n\n                    let tmp_app_image = tmp_dir.path().join(\"current_app.AppImage\");\n\n                    // get metadata to restore later\n                    let metadata = self.extract_path.metadata()?;\n\n                    // create a backup of our current app image\n                    fs::rename(&self.extract_path, &tmp_app_image)?;\n\n                    // if something went wrong during the extraction, we should restore previous app\n                    if let Err(err) = fs::write(&self.extract_path, bytes).and_then(|_| {\n                        fs::set_permissions(&self.extract_path, metadata.permissions())\n                    }) {\n                        fs::rename(tmp_app_image, &self.extract_path)?;\n                        return Err(err.into());\n                    }\n\n                    // early finish we have everything we need here\n                    return Ok(());\n                }\n            }\n        }\n\n        Err(Error::TempDirNotOnSameMountPoint)\n    }\n\n    // MacOS\n    //\n    // ### Expected structure:\n    // ├── [AppName]_[version]_x64.app.tar.gz       # GZ generated by cargo-packager\n    // │   └──[AppName].app                         # Main application\n    // │      └── Contents                          # Application contents...\n    // │          └── ...\n    // └── ...\n    #[cfg(target_os = \"macos\")]\n    fn install_inner(&self, bytes: Vec<u8>) -> Result<()> {\n        use flate2::read::GzDecoder;\n\n        let cursor = Cursor::new(bytes);\n        let mut extracted_files: Vec<PathBuf> = Vec::new();\n\n        // Create temp directories for backup and extraction\n        let tmp_backup_dir = tempfile::Builder::new()\n            .prefix(\"packager_current_app\")\n            .tempdir()?;\n\n        let tmp_extract_dir = tempfile::Builder::new()\n            .prefix(\"packager_updated_app\")\n            .tempdir()?;\n\n        let decoder = GzDecoder::new(cursor);\n        let mut archive = tar::Archive::new(decoder);\n\n        // Extract files to temporary directory\n        for entry in archive.entries()? {\n            let mut entry = entry?;\n            let collected_path: PathBuf = entry.path()?.iter().skip(1).collect();\n            let extraction_path = tmp_extract_dir.path().join(&collected_path);\n\n            // Ensure parent directories exist\n            if let Some(parent) = extraction_path.parent() {\n                std::fs::create_dir_all(parent)?;\n            }\n\n            if let Err(err) = entry.unpack(&extraction_path) {\n                // Cleanup on error\n                std::fs::remove_dir_all(tmp_extract_dir.path()).ok();\n                return Err(err.into());\n            }\n            extracted_files.push(extraction_path);\n        }\n\n        // Try to move the current app to backup\n        let move_result = std::fs::rename(\n            &self.extract_path,\n            tmp_backup_dir.path().join(\"current_app\"),\n        );\n        let need_authorization = if let Err(err) = move_result {\n            if err.kind() == std::io::ErrorKind::PermissionDenied {\n                true\n            } else {\n                std::fs::remove_dir_all(tmp_extract_dir.path()).ok();\n                return Err(err.into());\n            }\n        } else {\n            false\n        };\n\n        if need_authorization {\n            log::debug!(\"app installation needs admin privileges\");\n            // Use AppleScript to perform moves with admin privileges\n            let apple_script = format!(\n                \"do shell script \\\"rm -rf '{src}' && mv -f '{new}' '{src}'\\\" with administrator privileges\",\n                src = self.extract_path.display(),\n                new = tmp_extract_dir.path().display()\n            );\n\n            let res = std::process::Command::new(\"osascript\")\n                .arg(\"-e\")\n                .arg(apple_script)\n                .status();\n\n            if res.is_err() || !res.unwrap().success() {\n                log::error!(\"failed to install update using AppleScript\");\n                std::fs::remove_dir_all(tmp_extract_dir.path()).ok();\n                return Err(Error::Io(std::io::Error::new(\n                    std::io::ErrorKind::PermissionDenied,\n                    \"Failed to move the new app into place\",\n                )));\n            }\n        } else {\n            // Remove existing directory if it exists\n            if self.extract_path.exists() {\n                std::fs::remove_dir_all(&self.extract_path)?;\n            }\n            // Move the new app to the target path\n            std::fs::rename(tmp_extract_dir.path(), &self.extract_path)?;\n        }\n\n        let _ = std::process::Command::new(\"touch\")\n            .arg(&self.extract_path)\n            .status();\n\n        Ok(())\n    }\n}\n\n/// Check for an update using the provided\npub fn check_update(current_version: Version, config: crate::Config) -> Result<Option<Update>> {\n    UpdaterBuilder::new(current_version, config)\n        .build()?\n        .check()\n}\n\n/// Get the updater target for the current platform.\n#[doc(hidden)]\npub fn target() -> Option<String> {\n    if let (Some(target), Some(arch)) = (get_updater_target(), get_updater_arch()) {\n        Some(format!(\"{target}-{arch}\"))\n    } else {\n        None\n    }\n}\n\npub(crate) fn get_updater_target() -> Option<&'static str> {\n    if cfg!(target_os = \"linux\") {\n        Some(\"linux\")\n    } else if cfg!(target_os = \"macos\") {\n        Some(\"macos\")\n    } else if cfg!(target_os = \"windows\") {\n        Some(\"windows\")\n    } else {\n        None\n    }\n}\n\npub(crate) fn get_updater_arch() -> Option<&'static str> {\n    if cfg!(target_arch = \"x86\") {\n        Some(\"i686\")\n    } else if cfg!(target_arch = \"x86_64\") {\n        Some(\"x86_64\")\n    } else if cfg!(target_arch = \"arm\") {\n        Some(\"armv7\")\n    } else if cfg!(target_arch = \"aarch64\") {\n        Some(\"aarch64\")\n    } else if cfg!(target_arch = \"riscv64\") {\n        Some(\"riscv64\")\n    } else {\n        None\n    }\n}\n\n#[cfg(any(windows, target_os = \"macos\"))]\nfn extract_path_from_executable(executable_path: &Path) -> Result<PathBuf> {\n    // Return the path of the current executable by default\n    // Example C:\\Program Files\\My App\\\n    let extract_path = executable_path\n        .parent()\n        .map(PathBuf::from)\n        .ok_or(Error::FailedToDetermineExtractPath)?;\n\n    // MacOS example binary is in /Applications/TestApp.app/Contents/MacOS/myApp\n    // We need to get /Applications/<app>.app\n    // TODO(lemarier): Need a better way here\n    // Maybe we could search for <*.app> to get the right path\n    #[cfg(target_os = \"macos\")]\n    if extract_path\n        .display()\n        .to_string()\n        .contains(\"Contents/MacOS\")\n    {\n        return extract_path\n            .parent()\n            .map(PathBuf::from)\n            .ok_or(Error::FailedToDetermineExtractPath)?\n            .parent()\n            .map(PathBuf::from)\n            .ok_or(Error::FailedToDetermineExtractPath);\n    }\n\n    Ok(extract_path)\n}\n\n// Validate signature\n// need to be public because its been used\n// by our tests in the bundler\n//\n// NOTE: The buffer position is not reset.\nfn verify_signature<R>(\n    archive_reader: &mut R,\n    release_signature: &str,\n    pub_key: &str,\n) -> Result<bool>\nwhere\n    R: Read,\n{\n    // we need to convert the pub key\n    let pub_key_decoded = base64_to_string(pub_key)?;\n    let public_key = PublicKey::decode(&pub_key_decoded)?;\n    let signature_base64_decoded = base64_to_string(release_signature)?;\n    let signature = Signature::decode(&signature_base64_decoded)?;\n\n    // read all bytes until EOF in the buffer\n    let mut data = Vec::new();\n    archive_reader.read_to_end(&mut data)?;\n\n    // Validate signature or bail out\n    public_key.verify(&data, &signature, true)?;\n    Ok(true)\n}\n\nfn base64_to_string(base64_string: &str) -> Result<String> {\n    let decoded_string = &base64::engine::general_purpose::STANDARD.decode(base64_string)?;\n    let result = std::str::from_utf8(decoded_string)\n        .map_err(|_| Error::SignatureUtf8(base64_string.into()))?\n        .to_string();\n    Ok(result)\n}\n"
  },
  {
    "path": "crates/updater/tests/app/Cargo.toml",
    "content": "[package]\nname = \"cargo-packager-updater-app-test\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n[dependencies]\ncargo-packager-updater = { path = \"../..\" }\n"
  },
  {
    "path": "crates/updater/tests/app/src/main.rs",
    "content": "// Copyright 2019-2023 Tauri Programme within The Commons Conservancy\n// Copyright 2023-2023 CrabNebula Ltd.\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nfn main() {\n    #[allow(clippy::option_env_unwrap)]\n    let version = option_env!(\"APP_VERSION\").unwrap();\n    let mut builder = cargo_packager_updater::UpdaterBuilder::new(\n        version.parse().unwrap(),\n        cargo_packager_updater::Config {\n            pubkey: include_str!(\"../../dummy.pub.key\").into(),\n            endpoints: vec![\"http://localhost:3007\".parse().unwrap()],\n            ..Default::default()\n        },\n    );\n    let format = std::env::var(\"UPDATER_FORMAT\").unwrap_or_default();\n\n    match format.as_str() {\n        \"nsis\" | \"wix\" => {\n            // NOTE: we only need this because this is an integration test and we don't want to install the app in the programs folder\n            builder = builder.installer_args(vec![format!(\n                \"{}={}\",\n                if format == \"nsis\" { \"/D\" } else { \"INSTALLDIR\" },\n                std::env::current_exe().unwrap().parent().unwrap().display()\n            )]);\n        }\n        #[cfg(any(\n            target_os = \"linux\",\n            target_os = \"dragonfly\",\n            target_os = \"freebsd\",\n            target_os = \"netbsd\",\n            target_os = \"openbsd\"\n        ))]\n        \"appimage\" => {\n            if let Some(p) = std::env::var_os(\"APPIMAGE\") {\n                builder = builder.executable_path(p);\n            }\n        }\n        _ => {}\n    }\n\n    let updater = builder.build().unwrap();\n\n    println!(\"{version}\");\n\n    match updater.check() {\n        Ok(Some(update)) => {\n            if let Err(e) = update.download_and_install() {\n                println!(\"{e}\");\n                std::process::exit(1);\n            }\n\n            std::process::exit(0);\n        }\n        Ok(None) => {\n            std::process::exit(0);\n        }\n        Err(e) => {\n            println!(\"{e}\");\n            std::process::exit(1);\n        }\n    }\n}\n"
  },
  {
    "path": "crates/updater/tests/dummy.key",
    "content": "dW50cnVzdGVkIGNvbW1lbnQ6IHJzaWduIGVuY3J5cHRlZCBzZWNyZXQga2V5ClJXUlRZMEl5VU1qSHBMT0E4R0JCVGZzbUMzb3ZXeGpGY1NSdm9OaUxaVTFuajd0T2ZKZ0FBQkFBQUFBQUFBQUFBQUlBQUFBQWlhRnNPUmxKWjBiWnJ6M29Cd0RwOUpqTW1yOFFQK3JTOGdKSi9CajlHZktHajI2ZnprbEM0VUl2MHhGdFdkZWpHc1BpTlJWK2hOTWo0UVZDemMvaFlYVUM4U2twRW9WV1JHenNzUkRKT2RXQ1FCeXlkYUwxelhacmtxOGZJOG1Nb1R6b0VEcWFLVUk9Cg=="
  },
  {
    "path": "crates/updater/tests/dummy.pub.key",
    "content": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDQ2Njc0OTE5Mzk2Q0ExODkKUldTSm9XdzVHVWxuUmtJdjB4RnRXZGVqR3NQaU5SVitoTk1qNFFWQ3pjL2hZWFVDOFNrcEVvVlcK"
  },
  {
    "path": "crates/updater/tests/update.rs",
    "content": "// Copyright 2019-2023 Tauri Programme within The Commons Conservancy\n// Copyright 2023-2023 CrabNebula Ltd.\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n#![allow(dead_code, unused_imports)]\n\nuse std::{\n    collections::HashMap,\n    fs::{self, File},\n    path::{Path, PathBuf},\n    process::Command,\n};\n\nuse serde::Serialize;\n\nconst UPDATER_PRIVATE_KEY: &str = include_str!(\"./dummy.key\");\n\n#[derive(Serialize)]\nstruct PlatformUpdate {\n    signature: String,\n    url: &'static str,\n    format: &'static str,\n}\n\n#[derive(Serialize)]\nstruct Update {\n    version: &'static str,\n    date: String,\n    platforms: HashMap<String, PlatformUpdate>,\n}\n\nfn build_app(cwd: &Path, root_dir: &Path, version: &str, target: &[UpdaterFormat]) {\n    let mut command = Command::new(\"cargo\");\n    command\n        .args([\n            \"run\",\n            \"--package\",\n            \"cargo-packager\",\n            \"--\",\n            \"-vvv\",\n            \"-f\",\n           &target.iter().map(|t|t.name()).collect::<Vec<_>>().join(\",\"),\n            \"-c\",\n        ])\n        .arg(format!(r#\"{{\"outDir\":\"{}\",\"beforePackagingCommand\": \"cargo build\", \"identifier\": \"com.updater-app.test\", \"productName\": \"CargoPackagerAppUpdaterTest\", \"version\": \"{version}\", \"icons\": [\"32x32.png\"], \"binaries\": [{{\"path\": \"cargo-packager-updater-app-test\", \"main\": true}}]}}\"#, dunce::simplified(&root_dir.join(\"target/debug\")).to_string_lossy().replace('\\\\', \"\\\\\\\\\")))\n        .env(\"CARGO_PACKAGER_SIGN_PRIVATE_KEY\", UPDATER_PRIVATE_KEY)\n        .env(\"CARGO_PACKAGER_SIGN_PRIVATE_KEY_PASSWORD\", \"\")\n        // This is read by the updater app test\n        .env(\"APP_VERSION\", version)\n        .current_dir(cwd.join(\"tests/app\"));\n\n    let status = command\n        .status()\n        .expect(\"failed to run cargo-packager to package app\");\n\n    if !status.code().map(|c| c == 0).unwrap_or(true) {\n        panic!(\"failed to package app with exit code: {:?}\", status.code());\n    }\n}\n\n#[derive(Copy, Clone)]\nenum UpdaterFormat {\n    AppImage,\n\n    App,\n\n    Wix,\n    Nsis,\n}\n\nimpl UpdaterFormat {\n    fn name(self) -> &'static str {\n        match self {\n            Self::AppImage => \"appimage\",\n            Self::App => \"app\",\n            Self::Wix => \"wix\",\n            Self::Nsis => \"nsis\",\n        }\n    }\n\n    fn default() -> &'static [Self] {\n        #[cfg(any(target_os = \"macos\", target_os = \"ios\"))]\n        return &[Self::App];\n        #[cfg(target_os = \"linux\")]\n        return &[Self::AppImage];\n        #[cfg(windows)]\n        return &[Self::Nsis, Self::Wix];\n    }\n}\n\n#[test]\n#[ignore]\nfn update_app() {\n    let target =\n        cargo_packager_updater::target().expect(\"running updater test in an unsupported platform\");\n\n    let manifest_dir = PathBuf::from(env!(\"CARGO_MANIFEST_DIR\"));\n    let root_dir = manifest_dir.join(\"../..\").canonicalize().unwrap();\n\n    // bundle app update\n    build_app(&manifest_dir, &root_dir, \"1.0.0\", UpdaterFormat::default());\n\n    #[cfg(target_os = \"linux\")]\n    let generated_packages = vec![(\n        UpdaterFormat::AppImage,\n        root_dir.join(\"target/debug/cargo-packager-updater-app-test_1.0.0_x86_64.AppImage\"),\n    )];\n    #[cfg(target_os = \"macos\")]\n    let generated_packages: Vec<_> = vec![(\n        UpdaterFormat::App,\n        root_dir.join(\"target/debug/CargoPackagerAppUpdaterTest.app.tar.gz\"),\n    )];\n    #[cfg(windows)]\n    let generated_packages: Vec<_> = vec![\n        (\n            UpdaterFormat::Nsis,\n            root_dir.join(\"target/debug/cargo-packager-updater-app-test_1.0.0_x64-setup.exe\"),\n        ),\n        (\n            UpdaterFormat::Wix,\n            root_dir.join(\"target/debug/cargo-packager-updater-app-test_1.0.0_x64_en-US.msi\"),\n        ),\n    ];\n\n    for (format, update_package_path) in generated_packages {\n        let ext = update_package_path.extension().unwrap().to_str().unwrap();\n        let signature_path = update_package_path.with_extension(format!(\"{ext}.sig\",));\n        let signature = fs::read_to_string(&signature_path).unwrap_or_else(|_| {\n            panic!(\"failed to read signature file {}\", signature_path.display())\n        });\n\n        // on macOS, generated bundle doesn't have the version in its name\n        // so we need to move it otherwise it'll be overwritten when we build the next app\n        #[cfg(target_os = \"macos\")]\n        let update_package_path = {\n            let filename = update_package_path.file_name().unwrap().to_str().unwrap();\n            let new_path = update_package_path.with_file_name(format!(\"update-{filename}\",));\n            fs::rename(&update_package_path, &new_path).expect(\"failed to rename bundle\");\n            new_path\n        };\n\n        let update_package = update_package_path.clone();\n        let target = target.clone();\n        std::thread::spawn(move || {\n            // start the updater server\n            let server =\n                tiny_http::Server::http(\"localhost:3007\").expect(\"failed to start updater server\");\n\n            loop {\n                if let Ok(request) = server.recv() {\n                    match request.url() {\n                        \"/\" => {\n                            let mut platforms = HashMap::new();\n\n                            platforms.insert(\n                                target.clone(),\n                                PlatformUpdate {\n                                    signature: signature.clone(),\n                                    url: \"http://localhost:3007/download\",\n                                    format: format.name(),\n                                },\n                            );\n                            let body = serde_json::to_vec(&Update {\n                                version: \"1.0.0\",\n                                date: time::OffsetDateTime::now_utc()\n                                    .format(&time::format_description::well_known::Rfc3339)\n                                    .unwrap(),\n                                platforms,\n                            })\n                            .unwrap();\n                            let len = body.len();\n                            let response = tiny_http::Response::new(\n                                tiny_http::StatusCode(200),\n                                Vec::new(),\n                                std::io::Cursor::new(body),\n                                Some(len),\n                                None,\n                            );\n                            let _ = request.respond(response);\n                        }\n                        \"/download\" => {\n                            let _ = request.respond(tiny_http::Response::from_file(\n                                File::open(&update_package).unwrap_or_else(|_| {\n                                    panic!(\"failed to open package {}\", update_package.display())\n                                }),\n                            ));\n                            // close server\n                            return;\n                        }\n                        _ => (),\n                    }\n                }\n            }\n        });\n\n        // bundle initial app version\n        build_app(&manifest_dir, &root_dir, \"0.1.0\", &[format]);\n\n        // install the inital app on Windows to `installdir`\n        #[cfg(windows)]\n        {\n            let install_dir = dunce::simplified(&root_dir.join(\"target/debug/installdir\"))\n                .display()\n                .to_string();\n\n            let installer_path = root_dir.join(match format {\n                UpdaterFormat::Nsis => {\n                    \"target/debug/cargo-packager-updater-app-test_0.1.0_x64-setup.exe\"\n                }\n                UpdaterFormat::Wix => {\n                    \"target/debug/cargo-packager-updater-app-test_0.1.0_x64_en-US.msi\"\n                }\n                _ => unreachable!(),\n            });\n            let installer_path = dunce::simplified(&installer_path);\n\n            let mut installer_arg = std::ffi::OsString::new();\n            installer_arg.push(\"\\\"\");\n            installer_arg.push(installer_path.display().to_string());\n            installer_arg.push(\"\\\"\");\n\n            let status = Command::new(\"powershell.exe\")\n                .args([\"-NoProfile\", \"-WindowStyle\", \"Hidden\"])\n                .args([\"Start-Process\"])\n                .arg(installer_arg)\n                .arg(\"-Wait\")\n                .arg(\"-ArgumentList\")\n                .arg(\n                    [\n                        match format {\n                            UpdaterFormat::Wix => \"/passive\",\n                            UpdaterFormat::Nsis => \"/P\",\n                            _ => unreachable!(),\n                        },\n                        &format!(\n                            \"{}={}\",\n                            match format {\n                                UpdaterFormat::Wix => \"INSTALLDIR\",\n                                UpdaterFormat::Nsis => \"/D\",\n                                _ => unreachable!(),\n                            },\n                            install_dir\n                        ),\n                    ]\n                    .join(\", \"),\n                )\n                .status()\n                .expect(\"failed to run installer\");\n\n            if !status.success() {\n                panic!(\"failed to run installer\");\n            }\n        }\n\n        #[cfg(windows)]\n        let app = root_dir.join(\"target/debug/installdir/cargo-packager-updater-app-test.exe\");\n        #[cfg(target_os = \"linux\")]\n        let app =\n            root_dir.join(\"target/debug/cargo-packager-updater-app-test_0.1.0_x86_64.AppImage\");\n        #[cfg(target_os = \"macos\")]\n        let app = root_dir.join(\"target/debug/CargoPackagerAppUpdaterTest.app/Contents/MacOS/cargo-packager-updater-app-test\");\n\n        // save the current creation time\n        let ctime1 = fs::metadata(&app)\n            .expect(\"failed to read app metadata\")\n            .created()\n            .unwrap();\n\n        // run initial app\n        Command::new(&app)\n            // This is read by the updater app test\n            .env(\"UPDATER_FORMAT\", format.name())\n            .status()\n            .expect(\"failed to start initial app\");\n\n        // wait until the update is finished and the new version has been installed\n        // before starting another updater test, this is because we use the same starting binary\n        // and we can't use it while the updater is installing it\n        let mut counter = 0;\n        loop {\n            // check if the main binary creation time has changed since `ctime1`\n            if app.exists() {\n                let ctime2 = fs::metadata(&app)\n                    .expect(\"failed to read app metadata\")\n                    .created()\n                    .unwrap();\n                if ctime1 != ctime2 {\n                    match Command::new(&app).output() {\n                        Ok(o) => {\n                            let output = String::from_utf8_lossy(&o.stdout).to_string();\n                            let version = output.split_once('\\n').unwrap().0;\n                            if version == \"1.0.0\" {\n                                println!(\"app is updated, new version: {version}\");\n                                break;\n                            }\n                            println!(\"unexpected output (stdout): {output}\");\n                            eprintln!(\"stderr: {}\", String::from_utf8_lossy(&o.stderr));\n                        }\n                        Err(e) => {\n                            eprintln!(\"failed to check if app was updated: {e}\");\n                        }\n                    }\n                }\n            }\n\n            counter += 1;\n            if counter == 10 {\n                panic!(\"updater test timedout and couldn't verify the update has happened\")\n            }\n            std::thread::sleep(std::time::Duration::from_secs(5));\n        }\n\n        // force a new build of the updater app test\n        // so `APP_VERSION` env arg would be embedded correctly\n        // for the next format test\n        let _ = Command::new(\"cargo\")\n            .args([\"clean\", \"--package\", \"cargo-packager-updater-app-test\"])\n            .current_dir(&manifest_dir)\n            .output();\n    }\n}\n"
  },
  {
    "path": "crates/utils/CHANGELOG.md",
    "content": "# Changelog\n\n## \\[0.1.1]\n\n- [`2b6dd55`](https://www.github.com/crabnebula-dev/cargo-packager/commit/2b6dd55eac6733715a4f717af54ff167e1fdcdf8) ([#266](https://www.github.com/crabnebula-dev/cargo-packager/pull/266)) Fix `process-relaunch-dangerous-allow-symlink-macos` feature usage.\n\n## \\[0.1.0]\n\n- [`cd0242b`](https://www.github.com/crabnebula-dev/cargo-packager/commit/cd0242b8a41b2f7ecb78dfbae04b3a2e1c72c931) Initial Release.\n"
  },
  {
    "path": "crates/utils/Cargo.toml",
    "content": "[package]\nname = \"cargo-packager-utils\"\ndescription = \"Utilities for cargo-packager crates\"\nversion = \"0.1.1\"\nauthors = { workspace = true }\nedition = { workspace = true }\nlicense = { workspace = true }\nrepository = { workspace = true }\n\n[dependencies]\nctor = \"0.2\"\nschemars = { workspace = true, optional = true }\nclap = { workspace = true, optional = true }\nserde = { workspace = true, optional = true }\n\n[features]\ndefault = [ \"cli\" ]\ncli = [ ]\nschema = [ \"schemars\" ]\nclap = [ \"dep:clap\" ]\nserde = [ \"dep:serde\" ]\nprocess-relaunch-dangerous-allow-symlink-macos = []\n"
  },
  {
    "path": "crates/utils/README.md",
    "content": "# cargo-packager-utils\n\nUtilities for cargo-packager crate and related crates.\n"
  },
  {
    "path": "crates/utils/src/current_exe.rs",
    "content": "// Copyright 2019-2023 Tauri Programme within The Commons Conservancy\n// Copyright 2023-2023 CrabNebula Ltd.\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n/// Retrieves the currently running binary's path, taking into account security considerations.\n///\n/// The path is cached as soon as possible (before even `main` runs) and that value is returned\n/// repeatedly instead of fetching the path every time. It is possible for the path to not be found,\n/// or explicitly disabled (see following macOS specific behavior).\n///\n/// # Platform-specific behavior\n///\n/// On `macOS`, this function will return an error if the original path contained any symlinks\n/// due to less protection on macOS regarding symlinks. This behavior can be disabled by setting the\n/// `process-relaunch-dangerous-allow-symlink-macos` feature, although it is *highly discouraged*.\n///\n/// # Security\n///\n/// If the above platform-specific behavior does **not** take place, this function uses the\n/// following resolution.\n///\n/// We canonicalize the path we received from [`std::env::current_exe`] to resolve any soft links.\n/// This avoids the usual issue of needing the file to exist at the passed path because a valid\n/// current executable result for our purpose should always exist. Notably,\n/// [`std::env::current_exe`] also has a security section that goes over a theoretical attack using\n/// hard links. Let's cover some specific topics that relate to different ways an attacker might\n/// try to trick this function into returning the wrong binary path.\n///\n/// ## Symlinks (\"Soft Links\")\n///\n/// [`std::path::Path::canonicalize`] is used to resolve symbolic links to the original path,\n/// including nested symbolic links (`link2 -> link1 -> bin`). On macOS, any results that include\n/// a symlink are rejected by default due to lesser symlink protections. This can be disabled,\n/// **although discouraged**, with the `process-relaunch-dangerous-allow-symlink-macos` feature.\n///\n/// ## Hard Links\n///\n/// A [Hard Link] is a named entry that points to a file in the file system.\n/// On most systems, this is what you would think of as a \"file\". The term is\n/// used on filesystems that allow multiple entries to point to the same file.\n/// The linked [Hard Link] Wikipedia page provides a decent overview.\n///\n/// In short, unless the attacker was able to create the link with elevated\n/// permissions, it should generally not be possible for them to hard link\n/// to a file they do not have permissions to - with exception to possible\n/// operating system exploits.\n///\n/// There are also some platform-specific information about this below.\n///\n/// ### Windows\n///\n/// Windows requires a permission to be set for the user to create a symlink\n/// or a hard link, regardless of ownership status of the target. Elevated\n/// permissions users have the ability to create them.\n///\n/// ### macOS\n///\n/// macOS allows for the creation of symlinks and hard links to any file.\n/// Accessing through those links will fail if the user who owns the links\n/// does not have the proper permissions on the original file.\n///\n/// ### Linux\n///\n/// Linux allows for the creation of symlinks to any file. Accessing the\n/// symlink will fail if the user who owns the symlink does not have the\n/// proper permissions on the original file.\n///\n/// Linux additionally provides a kernel hardening feature since version\n/// 3.6 (30 September 2012). Most distributions since then have enabled\n/// the protection (setting `fs.protected_hardlinks = 1`) by default, which\n/// means that a vast majority of desktop Linux users should have it enabled.\n/// **The feature prevents the creation of hardlinks that the user does not own\n/// or have read/write access to.** [See the patch that enabled this].\n///\n/// [Hard Link]: https://en.wikipedia.org/wiki/Hard_link\n/// [See the patch that enabled this]: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=800179c9b8a1e796e441674776d11cd4c05d61d7\npub fn current_exe() -> std::io::Result<PathBuf> {\n    STARTING_BINARY.cloned()\n}\n\nuse ctor::ctor;\nuse std::{\n    io::{Error, ErrorKind, Result},\n    path::{Path, PathBuf},\n};\n\n/// A cached version of the current binary using [`ctor`] to cache it before even `main` runs.\n#[ctor]\n#[used]\nstatic STARTING_BINARY: StartingBinary = StartingBinary::new();\n\n/// Represents a binary path that was cached when the program was loaded.\nstruct StartingBinary(std::io::Result<PathBuf>);\n\nimpl StartingBinary {\n    /// Find the starting executable as safely as possible.\n    fn new() -> Self {\n        // see notes on current_exe() for security implications\n        let dangerous_path = match std::env::current_exe() {\n            Ok(dangerous_path) => dangerous_path,\n            error @ Err(_) => return Self(error),\n        };\n\n        // note: this only checks symlinks on problematic platforms, see implementation below\n        if let Some(symlink) = Self::has_symlink(&dangerous_path) {\n            return Self(Err(Error::new(\n        ErrorKind::InvalidData,\n        format!(\"StartingBinary found current_exe() that contains a symlink on a non-allowed platform: {}\", symlink.display()),\n      )));\n        }\n\n        // we canonicalize the path to resolve any symlinks to the real exe path\n        Self(dangerous_path.canonicalize())\n    }\n\n    /// A clone of the [`PathBuf`] found to be the starting path.\n    ///\n    /// Because [`Error`] is not clone-able, it is recreated instead.\n    pub(super) fn cloned(&self) -> Result<PathBuf> {\n        // false positive\n        #[allow(clippy::useless_asref)]\n        self.0\n            .as_ref()\n            .map(Clone::clone)\n            .map_err(|e| Error::new(e.kind(), e.to_string()))\n    }\n\n    /// We only care about checking this on macOS currently, as it has the least symlink protections.\n    #[cfg(any(\n        not(target_os = \"macos\"),\n        feature = \"process-relaunch-dangerous-allow-symlink-macos\"\n    ))]\n    fn has_symlink(_: &Path) -> Option<&Path> {\n        None\n    }\n\n    /// We only care about checking this on macOS currently, as it has the least symlink protections.\n    #[cfg(all(\n        target_os = \"macos\",\n        not(feature = \"process-relaunch-dangerous-allow-symlink-macos\")\n    ))]\n    fn has_symlink(path: &Path) -> Option<&Path> {\n        use std::fs;\n\n        path.ancestors().find(|ancestor| {\n            matches!(\n                ancestor\n                    .symlink_metadata()\n                    .as_ref()\n                    .map(fs::Metadata::file_type)\n                    .as_ref()\n                    .map(fs::FileType::is_symlink),\n                Ok(true)\n            )\n        })\n    }\n}\n"
  },
  {
    "path": "crates/utils/src/lib.rs",
    "content": "// Copyright 2023-2023 CrabNebula Ltd.\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\n//! # cargo-packager-utils\n//!\n//! Contain reusable components of the cargo-packager ecosystem.\n\nuse std::fmt::Display;\n\npub mod current_exe;\n\n// NOTE: When making changes to this enum,\n// make sure to also update in updater and resource-resolver bindings if needed\n/// Types of supported packages by [`cargo-packager`](https://docs.rs/cargo-packager).\n#[derive(Clone, Copy, Debug, Eq, PartialEq)]\n#[cfg_attr(feature = \"serde\", derive(serde::Deserialize, serde::Serialize))]\n#[cfg_attr(feature = \"serde\", serde(rename_all = \"lowercase\"))]\n#[cfg_attr(feature = \"schema\", derive(schemars::JsonSchema))]\n#[cfg_attr(feature = \"clap\", derive(clap::ValueEnum))]\n#[cfg_attr(feature = \"clap\", value(rename_all = \"lowercase\"))]\n#[non_exhaustive]\npub enum PackageFormat {\n    /// All available package formats for the current platform.\n    ///\n    /// See [`PackageFormat::platform_all`]\n    #[cfg(feature = \"cli\")]\n    All,\n    /// The default list of package formats for the current platform.\n    ///\n    /// See [`PackageFormat::platform_default`]\n    #[cfg(feature = \"cli\")]\n    Default,\n    /// The macOS application bundle (.app).\n    App,\n    /// The macOS DMG package (.dmg).\n    Dmg,\n    /// The Microsoft Software Installer (.msi) through WiX Toolset.\n    Wix,\n    /// The NSIS installer (.exe).\n    Nsis,\n    /// The Linux Debian package (.deb).\n    Deb,\n    /// The Linux AppImage package (.AppImage).\n    AppImage,\n    /// The Linux Pacman package (.tar.gz and PKGBUILD)\n    Pacman,\n}\n\nimpl Display for PackageFormat {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"{}\", self.short_name())\n    }\n}\n\nimpl PackageFormat {\n    /// Maps a short name to a [PackageFormat].\n    /// Possible values are \"deb\", \"pacman\", \"appimage\", \"dmg\", \"app\", \"wix\", \"nsis\".\n    pub fn from_short_name(name: &str) -> Option<PackageFormat> {\n        // Other types we may eventually want to support: apk.\n        match name {\n            \"app\" => Some(PackageFormat::App),\n            \"dmg\" => Some(PackageFormat::Dmg),\n            \"wix\" => Some(PackageFormat::Wix),\n            \"nsis\" => Some(PackageFormat::Nsis),\n            \"deb\" => Some(PackageFormat::Deb),\n            \"appimage\" => Some(PackageFormat::AppImage),\n            _ => None,\n        }\n    }\n\n    /// Gets the short name of this [PackageFormat].\n    pub fn short_name(&self) -> &'static str {\n        match *self {\n            #[cfg(feature = \"cli\")]\n            PackageFormat::All => \"all\",\n            #[cfg(feature = \"cli\")]\n            PackageFormat::Default => \"default\",\n            PackageFormat::App => \"app\",\n            PackageFormat::Dmg => \"dmg\",\n            PackageFormat::Wix => \"wix\",\n            PackageFormat::Nsis => \"nsis\",\n            PackageFormat::Deb => \"deb\",\n            PackageFormat::AppImage => \"appimage\",\n            PackageFormat::Pacman => \"pacman\",\n        }\n    }\n\n    /// Gets the list of the possible package types on the current OS.\n    ///\n    /// - **macOS**: App, Dmg\n    /// - **Windows**: Nsis, Wix\n    /// - **Linux**: Deb, AppImage, Pacman\n    pub fn platform_all() -> &'static [PackageFormat] {\n        &[\n            #[cfg(target_os = \"macos\")]\n            PackageFormat::App,\n            #[cfg(target_os = \"macos\")]\n            PackageFormat::Dmg,\n            #[cfg(target_os = \"windows\")]\n            PackageFormat::Wix,\n            #[cfg(target_os = \"windows\")]\n            PackageFormat::Nsis,\n            #[cfg(any(\n                target_os = \"linux\",\n                target_os = \"dragonfly\",\n                target_os = \"freebsd\",\n                target_os = \"netbsd\",\n                target_os = \"openbsd\"\n            ))]\n            PackageFormat::Deb,\n            #[cfg(any(\n                target_os = \"linux\",\n                target_os = \"dragonfly\",\n                target_os = \"freebsd\",\n                target_os = \"netbsd\",\n                target_os = \"openbsd\"\n            ))]\n            PackageFormat::AppImage,\n            #[cfg(any(\n                target_os = \"linux\",\n                target_os = \"dragonfly\",\n                target_os = \"freebsd\",\n                target_os = \"netbsd\",\n                target_os = \"openbsd\"\n            ))]\n            PackageFormat::Pacman,\n        ]\n    }\n\n    /// Returns the default list of targets this platform\n    ///\n    /// - **macOS**: App, Dmg\n    /// - **Windows**: Nsis\n    /// - **Linux**: Deb, AppImage, Pacman\n    pub fn platform_default() -> &'static [PackageFormat] {\n        &[\n            #[cfg(target_os = \"macos\")]\n            PackageFormat::App,\n            #[cfg(target_os = \"macos\")]\n            PackageFormat::Dmg,\n            #[cfg(target_os = \"windows\")]\n            PackageFormat::Nsis,\n            #[cfg(any(\n                target_os = \"linux\",\n                target_os = \"dragonfly\",\n                target_os = \"freebsd\",\n                target_os = \"netbsd\",\n                target_os = \"openbsd\"\n            ))]\n            PackageFormat::Deb,\n            #[cfg(any(\n                target_os = \"linux\",\n                target_os = \"dragonfly\",\n                target_os = \"freebsd\",\n                target_os = \"netbsd\",\n                target_os = \"openbsd\"\n            ))]\n            PackageFormat::AppImage,\n            #[cfg(any(\n                target_os = \"linux\",\n                target_os = \"dragonfly\",\n                target_os = \"freebsd\",\n                target_os = \"netbsd\",\n                target_os = \"openbsd\"\n            ))]\n            PackageFormat::Pacman,\n        ]\n    }\n\n    /// Gets a number representing priority which used to sort package types\n    /// in an order that guarantees that if a certain package type\n    /// depends on another (like Dmg depending on MacOsBundle), the dependency\n    /// will be built first\n    ///\n    /// The lower the number, the higher the priority\n    pub fn priority(&self) -> u32 {\n        match self {\n            #[cfg(feature = \"cli\")]\n            PackageFormat::All => 0,\n            #[cfg(feature = \"cli\")]\n            PackageFormat::Default => 0,\n            PackageFormat::App => 0,\n            PackageFormat::Wix => 0,\n            PackageFormat::Nsis => 0,\n            PackageFormat::Deb => 0,\n            PackageFormat::AppImage => 0,\n            PackageFormat::Pacman => 0,\n            PackageFormat::Dmg => 1,\n        }\n    }\n}\n"
  },
  {
    "path": "deny.toml",
    "content": "# Target triples to include when checking. This is essentially our supported target list.\n[graph]\ntargets = [\n    { triple = \"x86_64-unknown-linux-gnu\" },\n    { triple = \"aarch64-unknown-linux-gnu\" },\n    { triple = \"x86_64-pc-windows-msvc\" },\n    { triple = \"i686-pc-windows-msvc\" },\n    { triple = \"x86_64-apple-darwin\" },\n    { triple = \"aarch64-apple-darwin\" },\n]\n\n# exclude examples and their dependecies\nexclude = [\n    \"dioxus-example\",\n    \"egui-example\",\n    \"slint-example\",\n    \"tauri-example\",\n    \"wry-example\",\n    \"cargo-packager-updater-app-test\",\n]\n\n[licenses]\n# List of explicitly allowed licenses\n# See https://spdx.org/licenses/ for list of possible licenses\n# [possible values: any SPDX 3.11 short identifier (+ optional exception)].\nallow = [\n    \"MIT\",\n    \"Apache-2.0\",\n    \"ISC\",\n    # Apparently for us it's equivalent to BSD-3 which is considered compatible with MIT and Apache-2.0\n    \"Unicode-DFS-2016\",\n    \"Unicode-3.0\",\n    # Used by webpki-roots and option-ext which we are using without modifications in a larger work, therefore okay.\n    \"CDLA-Permissive-2.0\",\n    \"MPL-2.0\",\n    \"BSD-3-Clause\",\n    \"BSD-2-Clause\",\n    \"OpenSSL\",\n    \"Zlib\",\n]\n\n# Sigh\n[[licenses.clarify]]\nname = \"ring\"\n# SPDX considers OpenSSL to encompass both the OpenSSL and SSLeay licenses\n# https://spdx.org/licenses/OpenSSL.html\n# ISC - Both BoringSSL and ring use this for their new files\n# MIT - \"Files in third_party/ have their own licenses, as described therein. The MIT\n# license, for third_party/fiat, which, unlike other third_party directories, is\n# compiled into non-test libraries, is included below.\"\n# OpenSSL - Obviously\nexpression = \"ISC AND MIT AND OpenSSL\"\nlicense-files = [{ path = \"LICENSE\", hash = 0xbd0eed23 }]\n\n[licenses.private]\n# If true, ignores workspace crates that aren't published, or are only\n# published to private registries.\n# To see how to mark a crate as unpublished (to the official registry),\n# visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field.\nignore = true\n"
  },
  {
    "path": "examples/deno/.gitignore",
    "content": "/dist"
  },
  {
    "path": "examples/deno/README.md",
    "content": "## Deno example\n\n1. install `deno` first https://deno.land/manual/getting_started/installation\n2. package the app\n   ```sh\n    cargo r -p cargo-packager -- -p deno-example --release\n   ```\n"
  },
  {
    "path": "examples/deno/deno-example.js",
    "content": "console.log(\"Hello World!\");\n"
  },
  {
    "path": "examples/deno/packager.json",
    "content": "{\n  \"$schema\": \"../../crates/packager/schema.json\",\n  \"name\": \"deno-example\",\n  \"outDir\": \"./dist\",\n  \"beforePackagingCommand\": \"deno compile --output dist/deno-example deno-example.js\",\n  \"productName\": \"Deno example\",\n  \"version\": \"0.0.0\",\n  \"identifier\": \"com.deno.example\",\n  \"resources\": [\"deno-example.js\", \"README.md\"],\n  \"binaries\": [{ \"path\": \"deno-example\", \"main\": true }],\n  \"icons\": [\"32x32.png\"]\n}\n"
  },
  {
    "path": "examples/dioxus/.gitignore",
    "content": "/target\n/dist"
  },
  {
    "path": "examples/dioxus/Cargo.toml",
    "content": "[package]\nname = \"dioxus-example\"\nversion = \"0.0.0\"\nedition = \"2021\"\npublish = false\n\n[dependencies]\ndioxus = { version = \"0.7\", features = [\"desktop\"] }\n\n[package.metadata.packager]\n# TODO: Needs https://github.com/crabnebula-dev/cargo-packager/issues/360\n# before-packaging-command = \"dx build --platform desktop --release\"\nbefore-packaging-command = \"cargo build --release\"\nout-dir = \"./dist\"\nproduct-name = \"Dioxus example\"\nidentifier = \"com.dioxus.example\"\nresources = [\"src/**/*\", \"Cargo.toml\", \"README.md\"]\nicons = [\"32x32.png\"]\n\n[package.metadata.packager.deb]\ndepends = [\"libgtk-3-0\", \"libwebkit2gtk-4.1-0\", \"libayatana-appindicator3-1\"]\n\n[package.metadata.packager.appimage]\nlibs = [\n  \"WebKitNetworkProcess\",\n  \"WebKitWebProcess\",\n  \"libwebkit2gtkinjectedbundle.so\",\n  \"libayatana-appindicator3.so.1\",\n]\n\n[package.metadata.packager.appimage.linuxdeploy-plugins]\n\"gtk\" = \"https://raw.githubusercontent.com/tauri-apps/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh\"\n\n[package.metadata.packager.nsis]\npreinstall-section = \"\"\"\n; Setup messages\n; English\nLangString webview2AbortError ${LANG_ENGLISH} \"Failed to install WebView2! The app can't run without it. Try restarting the installer.\"\nLangString webview2DownloadError ${LANG_ENGLISH} \"Error: Downloading WebView2 Failed - $0\"\nLangString webview2DownloadSuccess ${LANG_ENGLISH} \"WebView2 bootstrapper downloaded successfully\"\nLangString webview2Downloading ${LANG_ENGLISH} \"Downloading WebView2 bootstrapper...\"\nLangString webview2InstallError ${LANG_ENGLISH} \"Error: Installing WebView2 failed with exit code $1\"\nLangString webview2InstallSuccess ${LANG_ENGLISH} \"WebView2 installed successfully\"\n\nSection PreInstall\n  ; Check if Webview2 is already installed and skip this section\n  ${If} ${RunningX64}\n    ReadRegStr $4 HKLM \"SOFTWARE\\\\WOW6432Node\\\\Microsoft\\\\EdgeUpdate\\\\Clients\\\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}\" \"pv\"\n  ${Else}\n    ReadRegStr $4 HKLM \"SOFTWARE\\\\Microsoft\\\\EdgeUpdate\\\\Clients\\\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}\" \"pv\"\n  ${EndIf}\n  ReadRegStr $5 HKCU \"SOFTWARE\\\\Microsoft\\\\EdgeUpdate\\\\Clients\\\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}\" \"pv\"\n\n  StrCmp $4 \"\" 0 webview2_done\n  StrCmp $5 \"\" 0 webview2_done\n\n  Delete \"$TEMP\\\\MicrosoftEdgeWebview2Setup.exe\"\n  DetailPrint \"$(webview2Downloading)\"\n  nsis_tauri_utils::download \"https://go.microsoft.com/fwlink/p/?LinkId=2124703\" \"$TEMP\\\\MicrosoftEdgeWebview2Setup.exe\"\n  Pop $0\n  ${If} $0 == 0\n      DetailPrint \"$(webview2DownloadSuccess)\"\n  ${Else}\n      DetailPrint \"$(webview2DownloadError)\"\n      Abort \"$(webview2AbortError)\"\n  ${EndIf}\n  StrCpy $6 \"$TEMP\\\\MicrosoftEdgeWebview2Setup.exe\"\n\n  DetailPrint \"$(installingWebview2)\"\n  ; $6 holds the path to the webview2 installer\n  ExecWait \"$6 /install\" $1\n  ${If} $1 == 0\n    DetailPrint \"$(webview2InstallSuccess)\"\n  ${Else}\n    DetailPrint \"$(webview2InstallError)\"\n    Abort \"$(webview2AbortError)\"\n  ${EndIf}\n  webview2_done:\nSectionEnd\n\"\"\"\n"
  },
  {
    "path": "examples/dioxus/README.md",
    "content": "## Dioxus example\n\n1. install `dioxus-cli` first\n\n   ```sh\n   cargo install dioxus-cli --locked\n   ```\n\n2. package the app\n\n   ```sh\n   cargo r -p cargo-packager -- -p dioxus-example --release\n   ```\n"
  },
  {
    "path": "examples/dioxus/src/main.rs",
    "content": "// Prevents additional console window on Windows in release, DO NOT REMOVE!!\n#![cfg_attr(not(debug_assertions), windows_subsystem = \"windows\")]\n#![allow(non_snake_case)]\n\n// import the prelude to get access to the `rsx!` macro and the `Scope` and `Element` types\nuse dioxus::prelude::*;\n\nfn main() {\n    // launch the dioxus app in a webview\n    launch(App);\n}\n\n// define a component that renders a div with the text \"Hello, world!\"\nfn App() -> Element {\n    rsx! {\n        div {\n            \"Hello, world!\"\n        }\n    }\n}\n"
  },
  {
    "path": "examples/egui/.gitignore",
    "content": "/target"
  },
  {
    "path": "examples/egui/Cargo.toml",
    "content": "[package]\nname = \"egui-example\"\nversion = \"0.0.0\"\nedition = \"2021\"\npublish = false\n\n[dependencies]\neframe = \"0.33\"\n\n[package.metadata.packager]\nbefore-packaging-command = \"cargo build --release\"\nproduct-name = \"Egui example\"\nidentifier = \"com.egui.example\"\nresources = [\"Cargo.toml\", \"src\", \"32x32.png\"]\nicons = [\"32x32.png\"]\n"
  },
  {
    "path": "examples/egui/README.md",
    "content": "## EGUI example\n\n1. package the app\n\n   ```sh\n   cargo r -p cargo-packager -- -p egui-example --release\n   ```\n"
  },
  {
    "path": "examples/egui/src/main.rs",
    "content": "#![cfg_attr(not(debug_assertions), windows_subsystem = \"windows\")] // hide console window on Windows in release\n\nuse eframe::egui;\n\nfn main() -> Result<(), eframe::Error> {\n    eframe::run_native(\n        \"My egui App\",\n        eframe::NativeOptions::default(),\n        Box::new(|_cc| Ok(Box::<MyApp>::default())),\n    )\n}\n\nstruct MyApp {\n    name: String,\n    age: u32,\n}\n\nimpl Default for MyApp {\n    fn default() -> Self {\n        Self {\n            name: \"Arthur\".to_owned(),\n            age: 42,\n        }\n    }\n}\n\nimpl eframe::App for MyApp {\n    fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {\n        egui::CentralPanel::default().show(ctx, |ui| {\n            ui.heading(\"My egui Application\");\n            ui.horizontal(|ui| {\n                let name_label = ui.label(\"Your name: \");\n                ui.text_edit_singleline(&mut self.name)\n                    .labelled_by(name_label.id);\n            });\n            ui.add(egui::Slider::new(&mut self.age, 0..=120).text(\"age\"));\n            if ui.button(\"Click each year\").clicked() {\n                self.age += 1;\n            }\n            ui.label(format!(\"Hello '{}', age {}\", self.name, self.age));\n        });\n    }\n}\n"
  },
  {
    "path": "examples/electron/.gitignore",
    "content": "node_modules/\ndist/"
  },
  {
    "path": "examples/electron/index.html",
    "content": "<!doctype html>\n<html>\n  <head>\n    <meta charset=\"UTF-8\" />\n    <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->\n    <meta\n      http-equiv=\"Content-Security-Policy\"\n      content=\"default-src 'self'; script-src 'self'\"\n    />\n    <title>Hello World!</title>\n  </head>\n\n  <body>\n    <h1>Hello World!</h1>\n    We are using Node.js <span id=\"node-version\"></span>, Chromium\n    <span id=\"chrome-version\"></span>, and Electron\n    <span id=\"electron-version\"></span>.\n  </body>\n</html>\n"
  },
  {
    "path": "examples/electron/main.js",
    "content": "const { app, BrowserWindow } = require(\"electron\");\nconst path = require(\"node:path\");\n\nconst createWindow = () => {\n  const win = new BrowserWindow({\n    width: 800,\n    height: 600,\n    webPreferences: {\n      preload: path.join(__dirname, \"preload.js\"),\n    },\n  });\n\n  win.loadFile(\"index.html\");\n};\n\napp.whenReady().then(() => {\n  createWindow();\n\n  app.on(\"activate\", () => {\n    if (BrowserWindow.getAllWindows().length === 0) createWindow();\n  });\n});\n\napp.on(\"window-all-closed\", () => {\n  if (process.platform !== \"darwin\") app.quit();\n});\n"
  },
  {
    "path": "examples/electron/package.json",
    "content": "{\n  \"name\": \"electron-app\",\n  \"productName\": \"ElectronApp\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Hello World!\",\n  \"main\": \"main.js\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/crabnebula-dev/cargo-packager.git\"\n  },\n  \"author\": \"CrabNebula Ltd.\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/crabnebula-dev/cargo-packager/issues\"\n  },\n  \"homepage\": \"https://github.com/crabnebula-dev/cargo-packager#readme\",\n  \"devDependencies\": {\n    \"electron\": \"^27.0.3\"\n  },\n  \"packager\": {\n    \"outDir\": \"./dist\",\n    \"identifier\": \"com.electron.example\",\n    \"icons\": [\n      \"electron.png\"\n    ]\n  }\n}\n"
  },
  {
    "path": "examples/electron/preload.js",
    "content": "window.addEventListener(\"DOMContentLoaded\", () => {\n  const replaceText = (selector, text) => {\n    const element = document.getElementById(selector);\n    if (element) element.innerText = text;\n  };\n\n  for (const dependency of [\"chrome\", \"node\", \"electron\"]) {\n    replaceText(`${dependency}-version`, process.versions[dependency]);\n  }\n});\n"
  },
  {
    "path": "examples/slint/.gitignore",
    "content": "/target"
  },
  {
    "path": "examples/slint/Cargo.toml",
    "content": "[package]\nname = \"slint-example\"\nversion = \"0.0.0\"\nedition = \"2021\"\npublish = false\n\n[dependencies]\nslint = \"1.14\"\n\n[build-dependencies]\nslint-build = \"1.14\"\n\n[package.metadata.packager]\nbefore-packaging-command = \"cargo build --release\"\nproduct-name = \"Slint example\"\nidentifier = \"com.slint.example\"\nresources = [\"Cargo.toml\", \"src\", \"32x32.png\"]\nicons = [\"32x32.png\"]\n"
  },
  {
    "path": "examples/slint/README.md",
    "content": "## Slint example\n\n1. package the app\n\n   ```sh\n   cargo r -p cargo-packager -- -p slint-example --release\n   ```\n"
  },
  {
    "path": "examples/slint/build.rs",
    "content": "fn main() {\n    slint_build::compile(\"ui/appwindow.slint\").unwrap();\n}\n"
  },
  {
    "path": "examples/slint/src/main.rs",
    "content": "#![cfg_attr(not(debug_assertions), windows_subsystem = \"windows\")] // hide console window on Windows in release\n#![allow(dead_code)]\n\nslint::include_modules!();\n\nfn main() -> Result<(), slint::PlatformError> {\n    let ui = AppWindow::new()?;\n\n    let ui_handle = ui.as_weak();\n    ui.on_request_increase_value(move || {\n        let ui = ui_handle.unwrap();\n        ui.set_counter(ui.get_counter() + 1);\n    });\n\n    ui.run()\n}\n"
  },
  {
    "path": "examples/slint/ui/appwindow.slint",
    "content": "import { Button, VerticalBox } from \"std-widgets.slint\";\n\nexport component AppWindow inherits Window {\n    in-out property<int> counter: 42;\n    callback request-increase-value();\n    VerticalBox {\n        Text {\n            text: \"Counter: \\{root.counter}\";\n        }\n        Button {\n            text: \"Increase value\";\n            clicked => {\n                root.request-increase-value();\n            }\n        }\n    }\n}"
  },
  {
    "path": "examples/tauri/.gitignore",
    "content": "/target\n/gen"
  },
  {
    "path": "examples/tauri/Cargo.toml",
    "content": "[package]\nname = \"tauri-example\"\nversion = \"0.0.0\"\ndescription = \"Tauri Example\"\nedition = \"2021\"\npublish = false\n\n[build-dependencies]\ntauri-build = { version = \"2.5\", features = [] }\n\n[dependencies]\ntauri = { version = \"2.9\", features = [\"devtools\"] }\nserde.workspace = true\nserde_json.workspace = true\ncargo-packager-updater = { path = \"../../crates/updater\" }\n\n[features]\n# this feature is used for production builds or when `devPath` points to the filesystem\n# DO NOT REMOVE!!\ncustom-protocol = [\"tauri/custom-protocol\"]\n\n[package.metadata.packager]\nbefore-packaging-command = \"cargo tauri build\"\nproduct-name = \"Tauri example\"\nidentifier = \"com.tauri.example\"\nresources = [\n  \"Cargo.toml\",\n  \"../../README.md\",\n  \"icons/*\",\n  \"src\",\n  { src = \"src-ui/assets/tauri.svg\", target = \"path/to/tauri.svg\" },\n  { src = \"src-ui\", target = \"path/to/src-ui\" },\n  { src = \"src-ui/assets/*\", target = \"public\" },\n]\nicons = [\n  \"icons/32x32.png\",\n  \"icons/128x128.png\",\n  \"icons/128x128@2x.png\",\n  \"icons/icon.icns\",\n  \"icons/icon.ico\",\n]\n\n[package.metadata.packager.deb]\ndepends = [\"libgtk-3-0\", \"libwebkit2gtk-4.1-0\", \"libayatana-appindicator3-1\"]\nsection = \"rust\"\n\n[package.metadata.packager.appimage]\nbins = [\"/usr/bin/xdg-open\"]\nlibs = [\n  \"WebKitNetworkProcess\",\n  \"WebKitWebProcess\",\n  \"libwebkit2gtkinjectedbundle.so\",\n  \"libayatana-appindicator3.so.1\",\n]\n\n[package.metadata.packager.appimage.linuxdeploy-plugins]\n\"gtk\" = \"https://raw.githubusercontent.com/tauri-apps/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh\"\n\n[package.metadata.packager.nsis]\nappdata-paths = [\"$LOCALAPPDATA/$IDENTIFIER\"]\npreinstall-section = \"\"\"\n; Setup messages\n; English\nLangString webview2AbortError ${LANG_ENGLISH} \"Failed to install WebView2! The app can't run without it. Try restarting the installer.\"\nLangString webview2DownloadError ${LANG_ENGLISH} \"Error: Downloading WebView2 Failed - $0\"\nLangString webview2DownloadSuccess ${LANG_ENGLISH} \"WebView2 bootstrapper downloaded successfully\"\nLangString webview2Downloading ${LANG_ENGLISH} \"Downloading WebView2 bootstrapper...\"\nLangString webview2InstallError ${LANG_ENGLISH} \"Error: Installing WebView2 failed with exit code $1\"\nLangString webview2InstallSuccess ${LANG_ENGLISH} \"WebView2 installed successfully\"\n\nSection PreInstall\n  ; Check if Webview2 is already installed and skip this section\n  ${If} ${RunningX64}\n    ReadRegStr $4 HKLM \"SOFTWARE\\\\WOW6432Node\\\\Microsoft\\\\EdgeUpdate\\\\Clients\\\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}\" \"pv\"\n  ${Else}\n    ReadRegStr $4 HKLM \"SOFTWARE\\\\Microsoft\\\\EdgeUpdate\\\\Clients\\\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}\" \"pv\"\n  ${EndIf}\n  ReadRegStr $5 HKCU \"SOFTWARE\\\\Microsoft\\\\EdgeUpdate\\\\Clients\\\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}\" \"pv\"\n\n  StrCmp $4 \"\" 0 webview2_done\n  StrCmp $5 \"\" 0 webview2_done\n\n  Delete \"$TEMP\\\\MicrosoftEdgeWebview2Setup.exe\"\n  DetailPrint \"$(webview2Downloading)\"\n  nsis_tauri_utils::download \"https://go.microsoft.com/fwlink/p/?LinkId=2124703\" \"$TEMP\\\\MicrosoftEdgeWebview2Setup.exe\"\n  Pop $0\n  ${If} $0 == 0\n      DetailPrint \"$(webview2DownloadSuccess)\"\n  ${Else}\n      DetailPrint \"$(webview2DownloadError)\"\n      Abort \"$(webview2AbortError)\"\n  ${EndIf}\n  StrCpy $6 \"$TEMP\\\\MicrosoftEdgeWebview2Setup.exe\"\n\n  DetailPrint \"$(installingWebview2)\"\n  ; $6 holds the path to the webview2 installer\n  ExecWait \"$6 /install\" $1\n  ${If} $1 == 0\n    DetailPrint \"$(webview2InstallSuccess)\"\n  ${Else}\n    DetailPrint \"$(webview2InstallError)\"\n    Abort \"$(webview2AbortError)\"\n  ${EndIf}\n  webview2_done:\nSectionEnd\n\"\"\"\n"
  },
  {
    "path": "examples/tauri/README.md",
    "content": "## Tauri example\n\n1. install `tauri-cli` first\n\n   ```sh\n   cargo install tauri-cli --version \"2.0.0-rc.10\" --locked\n   ```\n\n2. Change `UPDATER_ENDPOINT` value in `src/main.rs` to point to your updater server or static update file.\n3. package the app\n\n   ```sh\n   cargo r -p cargo-packager -- -p tauri-example  --release --private-key dummy.key --password \"\"\n   ```\n\n4. increase the version in `Cargo.toml`\n5. do step 3 again\n6. upload the resulting package from step 5 to your endpoint\n7. run the app generated from step 3\n"
  },
  {
    "path": "examples/tauri/build.rs",
    "content": "fn main() {\n    tauri_build::build()\n}\n"
  },
  {
    "path": "examples/tauri/capabilities/base.json",
    "content": "{\n  \"identifier\": \"base\",\n  \"description\": \"app base capabilities\",\n  \"permissions\": [\"core:default\"]\n}\n"
  },
  {
    "path": "examples/tauri/dummy.key",
    "content": "dW50cnVzdGVkIGNvbW1lbnQ6IHJzaWduIGVuY3J5cHRlZCBzZWNyZXQga2V5ClJXUlRZMEl5VU1qSHBMT0E4R0JCVGZzbUMzb3ZXeGpGY1NSdm9OaUxaVTFuajd0T2ZKZ0FBQkFBQUFBQUFBQUFBQUlBQUFBQWlhRnNPUmxKWjBiWnJ6M29Cd0RwOUpqTW1yOFFQK3JTOGdKSi9CajlHZktHajI2ZnprbEM0VUl2MHhGdFdkZWpHc1BpTlJWK2hOTWo0UVZDemMvaFlYVUM4U2twRW9WV1JHenNzUkRKT2RXQ1FCeXlkYUwxelhacmtxOGZJOG1Nb1R6b0VEcWFLVUk9Cg=="
  },
  {
    "path": "examples/tauri/dummy.pub.key",
    "content": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDQ2Njc0OTE5Mzk2Q0ExODkKUldTSm9XdzVHVWxuUmtJdjB4RnRXZGVqR3NQaU5SVitoTk1qNFFWQ3pjL2hZWFVDOFNrcEVvVlcK"
  },
  {
    "path": "examples/tauri/src/main.rs",
    "content": "// Prevents additional console window on Windows in release, DO NOT REMOVE!!\n#![cfg_attr(not(debug_assertions), windows_subsystem = \"windows\")]\n\nuse cargo_packager_updater::{Config, Update, UpdaterBuilder};\nuse tauri::{AppHandle, Emitter, Manager, Runtime};\n\n// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command\n#[tauri::command]\nfn greet(name: &str) -> String {\n    format!(\"Hello, {name}! You've been greeted from Rust!\")\n}\n\n#[tauri::command]\nfn version() -> &'static str {\n    env!(\"CARGO_PKG_VERSION\")\n}\n\nconst UPDATER_PUB_KEY: &str = include_str!(\"../dummy.pub.key\");\nconst UPDATER_ENDPOINT: &str = \"http://localhost:2342\";\n\n#[tauri::command]\nfn check_update<R: Runtime>(app: AppHandle<R>) -> Result<(bool, Option<String>), ()> {\n    let config = Config {\n        pubkey: UPDATER_PUB_KEY.into(),\n        endpoints: vec![UPDATER_ENDPOINT.parse().unwrap()],\n        ..Default::default()\n    };\n\n    let updater = {\n        #[allow(unused_mut)]\n        let mut updater_builder = UpdaterBuilder::new(app.package_info().version.clone(), config);\n        #[cfg(any(\n            target_os = \"linux\",\n            target_os = \"dragonfly\",\n            target_os = \"freebsd\",\n            target_os = \"netbsd\",\n            target_os = \"openbsd\"\n        ))]\n        {\n            if let Some(appimage) = app.env().appimage {\n                updater_builder = updater_builder.executable_path(appimage)\n            }\n        }\n        updater_builder.build().unwrap()\n    };\n\n    let update = updater.check().unwrap();\n    let has_update = update.is_some();\n    let version = update.as_ref().map(|u| u.version.clone());\n    if let Some(update) = update {\n        app.manage(update);\n    }\n\n    Ok((has_update, version))\n}\n\nstruct UpdateBytes(Vec<u8>);\n\n#[derive(serde::Serialize, Clone)]\nstruct ProgressPayload {\n    chunk_len: usize,\n    content_len: Option<u64>,\n}\n#[tauri::command]\nfn download_update<R: Runtime>(app: AppHandle<R>) -> Result<(), ()> {\n    let app_1 = app.clone();\n    std::thread::spawn(move || {\n        let update = app.state::<Update>();\n        let update_bytes = update\n            .download_extended(\n                move |chunk_len, content_len| {\n                    app_1\n                        .emit(\n                            \"update_progress\",\n                            ProgressPayload {\n                                chunk_len,\n                                content_len,\n                            },\n                        )\n                        .unwrap();\n                },\n                move || {},\n            )\n            .unwrap();\n        app.manage(UpdateBytes(update_bytes));\n    });\n    Ok(())\n}\n#[tauri::command]\nfn install_update<R: Runtime>(app: AppHandle<R>) -> Result<(), ()> {\n    let update = app.state::<Update>();\n    let update_bytes = app.state::<UpdateBytes>();\n    update.install(update_bytes.0.clone()).unwrap();\n    Ok(())\n}\n\nfn main() {\n    tauri::Builder::default()\n        .invoke_handler(tauri::generate_handler![\n            greet,\n            version,\n            check_update,\n            download_update,\n            install_update\n        ])\n        .run(tauri::generate_context!())\n        .expect(\"error while running tauri application\");\n}\n"
  },
  {
    "path": "examples/tauri/src-ui/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"stylesheet\" href=\"styles.css\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>Tauri App</title>\n    <script type=\"module\" src=\"/main.js\" defer></script>\n  </head>\n  <body>\n    <div id=\"app\"></div>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/tauri/src-ui/main.js",
    "content": "import {\n  createApp,\n  ref,\n  onMounted,\n  onUnmounted,\n  computed,\n} from \"https://unpkg.com/vue@3/dist/vue.esm-browser.prod.js\";\nconst { invoke } = window.__TAURI__;\nconst { listen } = window.__TAURI__.event;\n\ncreateApp({\n  setup() {\n    const greetInput = ref(\"\");\n    const greetMsg = ref(\"\");\n    async function greet() {\n      // Learn more about Tauri commands at https://tauri.app/v1/guides/features/command\n      greetMsg.value = await invoke(\"greet\", {\n        name: greetInput.value,\n      });\n    }\n\n    const version = ref(\"\");\n    const updateStatus = ref(\"unchecked\"); // variants: \"unchecked\" | \"has-update\" | \"no-updates\" | \"downloading\" | \"ready-for-install\" | \"installing\"\n    const updateContentLen = ref(0);\n    const updateDownloadedDataLen = ref(0);\n    const updateProgress = ref(0);\n    const updateVersion = ref(\"\");\n    const checkBtnDisabled = computed(\n      () =>\n        updateStatus.value === \"has-update\" ||\n        updateStatus.value == \"downloading\" ||\n        updateStatus.value === \"ready-for-install\",\n    );\n\n    async function checkUpdate() {\n      const [hasUpdate, version] = await invoke(\"check_update\");\n      if (version) updateVersion.value = version;\n      updateStatus.value = hasUpdate ? \"has-update\" : \"no-updates\";\n    }\n\n    async function downloadUpdate() {\n      updateStatus.value = \"downloading\";\n      await invoke(\"download_update\");\n      updateStatus.value = \"ready-for-install\";\n    }\n\n    async function installUpdate() {\n      await invoke(\"install_update\");\n    }\n\n    let removeProgressListener;\n\n    onMounted(async () => {\n      version.value = await invoke(\"version\");\n\n      removeProgressListener = await listen(\"update_progress\", (event) => {\n        const { chunk_len, content_len } = event.payload;\n        if (content_len) {\n          updateContentLen.value = content_len;\n        }\n\n        updateDownloadedDataLen.value =\n          updateDownloadedDataLen.value + chunk_len;\n\n        updateProgress.value =\n          (updateDownloadedDataLen.value / updateContentLen.value) * 100;\n      });\n    });\n\n    onUnmounted(() => removeProgressListener());\n\n    return {\n      greetInput,\n      greetMsg,\n      greet,\n      version,\n      updateStatus,\n      updateVersion,\n      updateContentLen,\n      updateDownloadedDataLen,\n      updateProgress,\n      checkBtnDisabled,\n      checkUpdate,\n      downloadUpdate,\n      installUpdate,\n    };\n  },\n  template: `\n    <div class=\"container\">\n      <h1>Welcome to Tauri!</h1>\n\n      <div class=\"row\">\n        <a href=\"https://tauri.app\" target=\"_blank\">\n          <img src=\"/assets/tauri.svg\" class=\"logo tauri\" alt=\"Tauri logo\" />\n        </a>\n        <a\n          href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript\"\n          target=\"_blank\"\n        >\n          <img\n            src=\"/assets/javascript.svg\"\n            class=\"logo vanilla\"\n            alt=\"JavaScript logo\"\n          />\n        </a>\n      </div>\n\n      <p>Click on the Tauri logo to learn more about the framework</p>\n\n      <form class=\"row\" @submit.prevent=\"greet\">\n        <input id=\"greet-input\" v-model=\"greetInput\" placeholder=\"Enter a name...\" />\n        <button type=\"submit\">Greet</button>\n      </form>\n\n      <p>{{greetMsg}}</p>\n\n      <p>Current Verion: {{version}}</p>\n      <button @click=\"checkUpdate\" :disabled=\"checkBtnDisabled\">Check Update</button>\n\n      <template v-if=\"updateStatus !== 'unchecked'\">\n        <p v-if=\"updateStatus === 'no-updates'\">There is no updates available!</p>\n        <p v-else>There is a new version available! <span v-if=\"!!updateVersion\">({{updateVersion}})</span></p>\n\n        <button\n          v-if=\"updateStatus === 'has-update' || updateStatus === 'downloading' || updateStatus === 'ready-for-install'\"\n          :disabled=\"updateStatus === 'downloading' || updateStatus === 'ready-for-install'\"\n          @click=\"downloadUpdate\"\n        >\n          Download\n        </button>\n        <p v-if=\"updateStatus === 'downloading' || updateStatus === 'ready-for-install'\">{{updateProgress.toFixed(1)}}</p>\n\n        <button v-if=\"updateStatus === 'ready-for-install'\" @click=\"installUpdate\">Install</button>\n      </template>\n    </div>\n  `,\n}).mount(\"#app\");\n"
  },
  {
    "path": "examples/tauri/src-ui/styles.css",
    "content": ":root {\n  font-family: Inter, Avenir, Helvetica, Arial, sans-serif;\n  font-size: 16px;\n  line-height: 24px;\n  font-weight: 400;\n\n  color: #0f0f0f;\n  background-color: #f6f6f6;\n\n  font-synthesis: none;\n  text-rendering: optimizeLegibility;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n  -webkit-text-size-adjust: 100%;\n}\n\n.container {\n  margin: 0;\n  padding-top: 10vh;\n  display: flex;\n  flex-direction: column;\n  justify-content: center;\n  text-align: center;\n}\n\n.logo {\n  height: 6em;\n  padding: 1.5em;\n  will-change: filter;\n  transition: 0.75s;\n}\n\n.logo.tauri:hover {\n  filter: drop-shadow(0 0 2em #24c8db);\n}\n\n.row {\n  display: flex;\n  justify-content: center;\n}\n\na {\n  font-weight: 500;\n  color: #646cff;\n  text-decoration: inherit;\n}\n\na:hover {\n  color: #535bf2;\n}\n\nh1 {\n  text-align: center;\n}\n\ninput,\nbutton {\n  border-radius: 8px;\n  border: 1px solid transparent;\n  padding: 0.6em 1.2em;\n  font-size: 1em;\n  font-weight: 500;\n  font-family: inherit;\n  color: #0f0f0f;\n  background-color: #ffffff;\n  transition: border-color 0.25s;\n  box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);\n}\n\nbutton {\n  cursor: pointer;\n}\n\nbutton:hover {\n  border-color: #396cd8;\n}\nbutton:active {\n  border-color: #396cd8;\n  background-color: #e8e8e8;\n}\n\ninput,\nbutton {\n  outline: none;\n}\n\n#greet-input {\n  margin-right: 5px;\n}\n\n@media (prefers-color-scheme: dark) {\n  :root {\n    color: #f6f6f6;\n    background-color: #2f2f2f;\n  }\n\n  a:hover {\n    color: #24c8db;\n  }\n\n  input,\n  button {\n    color: #ffffff;\n    background-color: #0f0f0f98;\n  }\n  button:active {\n    background-color: #0f0f0f69;\n  }\n}\n"
  },
  {
    "path": "examples/tauri/tauri.conf.json",
    "content": "{\n  \"productName\": \"tauri-example\",\n  \"identifier\": \"com.tauri.example\",\n  \"version\": \"0.0.1\",\n  \"build\": {\n    \"beforeDevCommand\": \"\",\n    \"beforeBuildCommand\": \"\",\n    \"frontendDist\": \"./src-ui\"\n  },\n  \"app\": {\n    \"withGlobalTauri\": true,\n    \"windows\": [\n      {\n        \"fullscreen\": false,\n        \"resizable\": true,\n        \"title\": \"tauri\",\n        \"width\": 800,\n        \"height\": 600\n      }\n    ]\n  },\n  \"bundle\": {\n    \"active\": false,\n    \"targets\": \"all\",\n    \"icon\": [\n      \"icons/32x32.png\",\n      \"icons/128x128.png\",\n      \"icons/128x128@2x.png\",\n      \"icons/icon.icns\",\n      \"icons/icon.ico\"\n    ]\n  }\n}\n"
  },
  {
    "path": "examples/wails/.gitignore",
    "content": "build/bin\nnode_modules\nfrontend/dist\n"
  },
  {
    "path": "examples/wails/Packager.toml",
    "content": "name = \"wails-example\"\nbefore-packaging-command = \"wails build -nopackage\"\nout-dir = \"./build/bin\"\nversion = \"0.0.0\"\nproduct-name = \"Wails example\"\nidentifier = \"com.wails.example\"\nresources = [\"Cargo.toml\", \"src\", \"32x32.png\"]\nicons = [\"32x32.png\"]\nbinaries = [{ path = \"wails_example\", main = true }]\n\n[deb]\ndepends = [\"libgtk-3-0\", \"libwebkit2gtk-4.1-0\"]\n\n[appimage]\nlibs = [\n  \"WebKitNetworkProcess\",\n  \"WebKitWebProcess\",\n  \"libwebkit2gtkinjectedbundle.so\",\n]\n\n[appimage.linuxdeploy-plugins]\n\"gtk\" = \"https://raw.githubusercontent.com/tauri-apps/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh\"\n\n[nsis]\nappdata-paths = [\"$LOCALAPPDATA/$IDENTIFIER\"]\npreinstall-section = \"\"\"\n; Setup messages\n; English\nLangString webview2AbortError ${LANG_ENGLISH} \"Failed to install WebView2! The app can't run without it. Try restarting the installer.\"\nLangString webview2DownloadError ${LANG_ENGLISH} \"Error: Downloading WebView2 Failed - $0\"\nLangString webview2DownloadSuccess ${LANG_ENGLISH} \"WebView2 bootstrapper downloaded successfully\"\nLangString webview2Downloading ${LANG_ENGLISH} \"Downloading WebView2 bootstrapper...\"\nLangString webview2InstallError ${LANG_ENGLISH} \"Error: Installing WebView2 failed with exit code $1\"\nLangString webview2InstallSuccess ${LANG_ENGLISH} \"WebView2 installed successfully\"\n\nSection PreInstall\n  ; Check if Webview2 is already installed and skip this section\n  ${If} ${RunningX64}\n    ReadRegStr $4 HKLM \"SOFTWARE\\\\WOW6432Node\\\\Microsoft\\\\EdgeUpdate\\\\Clients\\\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}\" \"pv\"\n  ${Else}\n    ReadRegStr $4 HKLM \"SOFTWARE\\\\Microsoft\\\\EdgeUpdate\\\\Clients\\\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}\" \"pv\"\n  ${EndIf}\n  ReadRegStr $5 HKCU \"SOFTWARE\\\\Microsoft\\\\EdgeUpdate\\\\Clients\\\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}\" \"pv\"\n\n  StrCmp $4 \"\" 0 webview2_done\n  StrCmp $5 \"\" 0 webview2_done\n\n  Delete \"$TEMP\\\\MicrosoftEdgeWebview2Setup.exe\"\n  DetailPrint \"$(webview2Downloading)\"\n  nsis_tauri_utils::download \"https://go.microsoft.com/fwlink/p/?LinkId=2124703\" \"$TEMP\\\\MicrosoftEdgeWebview2Setup.exe\"\n  Pop $0\n  ${If} $0 == 0\n      DetailPrint \"$(webview2DownloadSuccess)\"\n  ${Else}\n      DetailPrint \"$(webview2DownloadError)\"\n      Abort \"$(webview2AbortError)\"\n  ${EndIf}\n  StrCpy $6 \"$TEMP\\\\MicrosoftEdgeWebview2Setup.exe\"\n\n  DetailPrint \"$(installingWebview2)\"\n  ; $6 holds the path to the webview2 installer\n  ExecWait \"$6 /install\" $1\n  ${If} $1 == 0\n    DetailPrint \"$(webview2InstallSuccess)\"\n  ${Else}\n    DetailPrint \"$(webview2InstallError)\"\n    Abort \"$(webview2AbortError)\"\n  ${EndIf}\n  webview2_done:\nSectionEnd\n\"\"\"\n"
  },
  {
    "path": "examples/wails/README.md",
    "content": "## Wails example\n\n1. install The Go programming language: https://go.dev/dl/\n2. install `wails` CLI first\n\n   ```sh\n   go install github.com/wailsapp/wails/v2/cmd/wails@latest\n   ```\n\n3. package the app\n\n   ```sh\n   cargo r -p cargo-packager -- -p wails-example --release\n   ```\n"
  },
  {
    "path": "examples/wails/app.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n)\n\n// App struct\ntype App struct {\n\tctx context.Context\n}\n\n// NewApp creates a new App application struct\nfunc NewApp() *App {\n\treturn &App{}\n}\n\n// startup is called when the app starts. The context is saved\n// so we can call the runtime methods\nfunc (a *App) startup(ctx context.Context) {\n\ta.ctx = ctx\n}\n\n// Greet returns a greeting for the given name\nfunc (a *App) Greet(name string) string {\n\treturn fmt.Sprintf(\"Hello %s, It's show time!\", name)\n}\n"
  },
  {
    "path": "examples/wails/build.sh",
    "content": "#!/usr/bin/env -S pkgx +npm +go +gum +jq zsh\n# ^^ curl https://pkgx.sh | sh\n# ^^ pkgx makes all those tools (including bash!) available to the script\n#    no packages are installed; your system remains pristine\n\ngo install github.com/wailsapp/wails/v2/cmd/wails@latest\n\n# works on mac\nexport PATH=\"$HOME/go/bin:$PATH\"\n\nif [ -d wails_example ]; then\n  cd wails_example\nelif [ ! -d .git ] && gum confirm 'Create new wails app?'; then\n  wails init -n wails_example -t vanilla\n  cd wails_example\nfi\n\n# probably not resilient if wails changes\nwails build | grep \"Built\" | cut -d \" \" -f 2 | read buildpath\n\necho \"Your binary is available at ${buildpath}\"\nmkdir -p ./dist\n\n# I am stupid why doesn't this work\n# cp ${buildpath} ./dist/\ncp ./build/bin/wails_example.app/Contents/MacOS/wails_example ./dist\n\n# also doesn't work here, sadly\n# cargo r -p cargo-packager -- -p wails-example --release -c packager.json\n"
  },
  {
    "path": "examples/wails/frontend/app.css",
    "content": "#logo {\n  display: block;\n  width: 50%;\n  height: 50%;\n  margin: auto;\n  padding: 10% 0 0;\n  background-position: center;\n  background-repeat: no-repeat;\n  background-size: 100% 100%;\n  background-origin: content-box;\n}\n\n.result {\n  height: 20px;\n  line-height: 20px;\n  margin: 1.5rem auto;\n}\n\n.input-box .btn {\n  width: 60px;\n  height: 30px;\n  line-height: 30px;\n  border-radius: 3px;\n  border: none;\n  margin: 0 0 0 20px;\n  padding: 0 8px;\n  cursor: pointer;\n}\n\n.input-box .btn:hover {\n  background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%);\n  color: #333333;\n}\n\n.input-box .input {\n  border: none;\n  border-radius: 3px;\n  outline: none;\n  height: 30px;\n  line-height: 30px;\n  padding: 0 10px;\n  background-color: rgba(240, 240, 240, 1);\n  -webkit-font-smoothing: antialiased;\n}\n\n.input-box .input:hover {\n  border: none;\n  background-color: rgba(255, 255, 255, 1);\n}\n\n.input-box .input:focus {\n  border: none;\n  background-color: rgba(255, 255, 255, 1);\n}\n"
  },
  {
    "path": "examples/wails/frontend/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta content=\"width=device-width, initial-scale=1.0\" name=\"viewport\" />\n    <title>wails-example</title>\n    <link rel=\"stylesheet\" href=\"/app.css\" />\n    <link rel=\"stylesheet\" href=\"/style.css\" />\n  </head>\n  <body>\n    <div id=\"app\">\n      <img id=\"logo\" class=\"logo\" src=\"/assets/logo-universal.png\" />\n      <div class=\"result\" id=\"result\">Please enter your name below 👇</div>\n      <div class=\"input-box\" id=\"input\">\n        <input class=\"input\" id=\"name\" type=\"text\" autocomplete=\"off\" />\n        <button class=\"btn\" onclick=\"greet()\">Greet</button>\n      </div>\n    </div>\n    <script src=\"/main.js\" type=\"module\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/wails/frontend/main.js",
    "content": "import { Greet } from \"/wailsjs/go/main/App.js\";\n\nlet nameElement = document.getElementById(\"name\");\nnameElement.focus();\nlet resultElement = document.getElementById(\"result\");\n\n// Setup the greet function\nwindow.greet = () => {\n  // Get name\n  let name = nameElement.value;\n\n  // Check if the input is empty\n  if (name === \"\") return;\n\n  // Call App.Greet(name)\n  try {\n    Greet(name)\n      .then((result) => {\n        // Update result with data back from App.Greet()\n        resultElement.innerText = result;\n      })\n      .catch((err) => {\n        console.error(err);\n      });\n  } catch (err) {\n    console.error(err);\n  }\n};\n"
  },
  {
    "path": "examples/wails/frontend/style.css",
    "content": "html {\n  background-color: rgba(27, 38, 54, 1);\n  text-align: center;\n  color: white;\n}\n\nbody {\n  margin: 0;\n  color: white;\n  font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", \"Roboto\", \"Oxygen\",\n    \"Ubuntu\", \"Cantarell\", \"Fira Sans\", \"Droid Sans\", \"Helvetica Neue\",\n    sans-serif;\n}\n\n#app {\n  height: 100vh;\n  text-align: center;\n}\n"
  },
  {
    "path": "examples/wails/frontend/wailsjs/go/main/App.d.ts",
    "content": "// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL\n// This file is automatically generated. DO NOT EDIT\n\nexport function Greet(arg1: string): Promise<string>;\n"
  },
  {
    "path": "examples/wails/frontend/wailsjs/go/main/App.js",
    "content": "// @ts-check\n// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL\n// This file is automatically generated. DO NOT EDIT\n\nexport function Greet(arg1) {\n  return window[\"go\"][\"main\"][\"App\"][\"Greet\"](arg1);\n}\n"
  },
  {
    "path": "examples/wails/frontend/wailsjs/runtime/package.json",
    "content": "{\n  \"name\": \"@wailsapp/runtime\",\n  \"version\": \"2.0.0\",\n  \"description\": \"Wails Javascript runtime library\",\n  \"main\": \"runtime.js\",\n  \"types\": \"runtime.d.ts\",\n  \"scripts\": {},\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/wailsapp/wails.git\"\n  },\n  \"keywords\": [\n    \"Wails\",\n    \"Javascript\",\n    \"Go\"\n  ],\n  \"author\": \"Lea Anthony <lea.anthony@gmail.com>\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/wailsapp/wails/issues\"\n  },\n  \"homepage\": \"https://github.com/wailsapp/wails#readme\"\n}\n"
  },
  {
    "path": "examples/wails/frontend/wailsjs/runtime/runtime.d.ts",
    "content": "/*\n _       __      _ __\n| |     / /___ _(_) /____\n| | /| / / __ `/ / / ___/\n| |/ |/ / /_/ / / (__  )\n|__/|__/\\__,_/_/_/____/\nThe electron alternative for Go\n(c) Lea Anthony 2019-present\n*/\n\nexport interface Position {\n  x: number;\n  y: number;\n}\n\nexport interface Size {\n  w: number;\n  h: number;\n}\n\nexport interface Screen {\n  isCurrent: boolean;\n  isPrimary: boolean;\n  width: number;\n  height: number;\n}\n\n// Environment information such as platform, buildtype, ...\nexport interface EnvironmentInfo {\n  buildType: string;\n  platform: string;\n  arch: string;\n}\n\n// [EventsEmit](https://wails.io/docs/reference/runtime/events#eventsemit)\n// emits the given event. Optional data may be passed with the event.\n// This will trigger any event listeners.\nexport function EventsEmit(eventName: string, ...data: any): void;\n\n// [EventsOn](https://wails.io/docs/reference/runtime/events#eventson) sets up a listener for the given event name.\nexport function EventsOn(\n  eventName: string,\n  callback: (...data: any) => void,\n): () => void;\n\n// [EventsOnMultiple](https://wails.io/docs/reference/runtime/events#eventsonmultiple)\n// sets up a listener for the given event name, but will only trigger a given number times.\nexport function EventsOnMultiple(\n  eventName: string,\n  callback: (...data: any) => void,\n  maxCallbacks: number,\n): () => void;\n\n// [EventsOnce](https://wails.io/docs/reference/runtime/events#eventsonce)\n// sets up a listener for the given event name, but will only trigger once.\nexport function EventsOnce(\n  eventName: string,\n  callback: (...data: any) => void,\n): () => void;\n\n// [EventsOff](https://wails.io/docs/reference/runtime/events#eventsoff)\n// unregisters the listener for the given event name.\nexport function EventsOff(\n  eventName: string,\n  ...additionalEventNames: string[]\n): void;\n\n// [EventsOffAll](https://wails.io/docs/reference/runtime/events#eventsoffall)\n// unregisters all listeners.\nexport function EventsOffAll(): void;\n\n// [LogPrint](https://wails.io/docs/reference/runtime/log#logprint)\n// logs the given message as a raw message\nexport function LogPrint(message: string): void;\n\n// [LogTrace](https://wails.io/docs/reference/runtime/log#logtrace)\n// logs the given message at the `trace` log level.\nexport function LogTrace(message: string): void;\n\n// [LogDebug](https://wails.io/docs/reference/runtime/log#logdebug)\n// logs the given message at the `debug` log level.\nexport function LogDebug(message: string): void;\n\n// [LogError](https://wails.io/docs/reference/runtime/log#logerror)\n// logs the given message at the `error` log level.\nexport function LogError(message: string): void;\n\n// [LogFatal](https://wails.io/docs/reference/runtime/log#logfatal)\n// logs the given message at the `fatal` log level.\n// The application will quit after calling this method.\nexport function LogFatal(message: string): void;\n\n// [LogInfo](https://wails.io/docs/reference/runtime/log#loginfo)\n// logs the given message at the `info` log level.\nexport function LogInfo(message: string): void;\n\n// [LogWarning](https://wails.io/docs/reference/runtime/log#logwarning)\n// logs the given message at the `warning` log level.\nexport function LogWarning(message: string): void;\n\n// [WindowReload](https://wails.io/docs/reference/runtime/window#windowreload)\n// Forces a reload by the main application as well as connected browsers.\nexport function WindowReload(): void;\n\n// [WindowReloadApp](https://wails.io/docs/reference/runtime/window#windowreloadapp)\n// Reloads the application frontend.\nexport function WindowReloadApp(): void;\n\n// [WindowSetAlwaysOnTop](https://wails.io/docs/reference/runtime/window#windowsetalwaysontop)\n// Sets the window AlwaysOnTop or not on top.\nexport function WindowSetAlwaysOnTop(b: boolean): void;\n\n// [WindowSetSystemDefaultTheme](https://wails.io/docs/next/reference/runtime/window#windowsetsystemdefaulttheme)\n// *Windows only*\n// Sets window theme to system default (dark/light).\nexport function WindowSetSystemDefaultTheme(): void;\n\n// [WindowSetLightTheme](https://wails.io/docs/next/reference/runtime/window#windowsetlighttheme)\n// *Windows only*\n// Sets window to light theme.\nexport function WindowSetLightTheme(): void;\n\n// [WindowSetDarkTheme](https://wails.io/docs/next/reference/runtime/window#windowsetdarktheme)\n// *Windows only*\n// Sets window to dark theme.\nexport function WindowSetDarkTheme(): void;\n\n// [WindowCenter](https://wails.io/docs/reference/runtime/window#windowcenter)\n// Centers the window on the monitor the window is currently on.\nexport function WindowCenter(): void;\n\n// [WindowSetTitle](https://wails.io/docs/reference/runtime/window#windowsettitle)\n// Sets the text in the window title bar.\nexport function WindowSetTitle(title: string): void;\n\n// [WindowFullscreen](https://wails.io/docs/reference/runtime/window#windowfullscreen)\n// Makes the window full screen.\nexport function WindowFullscreen(): void;\n\n// [WindowUnfullscreen](https://wails.io/docs/reference/runtime/window#windowunfullscreen)\n// Restores the previous window dimensions and position prior to full screen.\nexport function WindowUnfullscreen(): void;\n\n// [WindowIsFullscreen](https://wails.io/docs/reference/runtime/window#windowisfullscreen)\n// Returns the state of the window, i.e. whether the window is in full screen mode or not.\nexport function WindowIsFullscreen(): Promise<boolean>;\n\n// [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize)\n// Sets the width and height of the window.\nexport function WindowSetSize(width: number, height: number): Promise<Size>;\n\n// [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize)\n// Gets the width and height of the window.\nexport function WindowGetSize(): Promise<Size>;\n\n// [WindowSetMaxSize](https://wails.io/docs/reference/runtime/window#windowsetmaxsize)\n// Sets the maximum window size. Will resize the window if the window is currently larger than the given dimensions.\n// Setting a size of 0,0 will disable this constraint.\nexport function WindowSetMaxSize(width: number, height: number): void;\n\n// [WindowSetMinSize](https://wails.io/docs/reference/runtime/window#windowsetminsize)\n// Sets the minimum window size. Will resize the window if the window is currently smaller than the given dimensions.\n// Setting a size of 0,0 will disable this constraint.\nexport function WindowSetMinSize(width: number, height: number): void;\n\n// [WindowSetPosition](https://wails.io/docs/reference/runtime/window#windowsetposition)\n// Sets the window position relative to the monitor the window is currently on.\nexport function WindowSetPosition(x: number, y: number): void;\n\n// [WindowGetPosition](https://wails.io/docs/reference/runtime/window#windowgetposition)\n// Gets the window position relative to the monitor the window is currently on.\nexport function WindowGetPosition(): Promise<Position>;\n\n// [WindowHide](https://wails.io/docs/reference/runtime/window#windowhide)\n// Hides the window.\nexport function WindowHide(): void;\n\n// [WindowShow](https://wails.io/docs/reference/runtime/window#windowshow)\n// Shows the window, if it is currently hidden.\nexport function WindowShow(): void;\n\n// [WindowMaximise](https://wails.io/docs/reference/runtime/window#windowmaximise)\n// Maximises the window to fill the screen.\nexport function WindowMaximise(): void;\n\n// [WindowToggleMaximise](https://wails.io/docs/reference/runtime/window#windowtogglemaximise)\n// Toggles between Maximised and UnMaximised.\nexport function WindowToggleMaximise(): void;\n\n// [WindowUnmaximise](https://wails.io/docs/reference/runtime/window#windowunmaximise)\n// Restores the window to the dimensions and position prior to maximising.\nexport function WindowUnmaximise(): void;\n\n// [WindowIsMaximised](https://wails.io/docs/reference/runtime/window#windowismaximised)\n// Returns the state of the window, i.e. whether the window is maximised or not.\nexport function WindowIsMaximised(): Promise<boolean>;\n\n// [WindowMinimise](https://wails.io/docs/reference/runtime/window#windowminimise)\n// Minimises the window.\nexport function WindowMinimise(): void;\n\n// [WindowUnminimise](https://wails.io/docs/reference/runtime/window#windowunminimise)\n// Restores the window to the dimensions and position prior to minimising.\nexport function WindowUnminimise(): void;\n\n// [WindowIsMinimised](https://wails.io/docs/reference/runtime/window#windowisminimised)\n// Returns the state of the window, i.e. whether the window is minimised or not.\nexport function WindowIsMinimised(): Promise<boolean>;\n\n// [WindowIsNormal](https://wails.io/docs/reference/runtime/window#windowisnormal)\n// Returns the state of the window, i.e. whether the window is normal or not.\nexport function WindowIsNormal(): Promise<boolean>;\n\n// [WindowSetBackgroundColour](https://wails.io/docs/reference/runtime/window#windowsetbackgroundcolour)\n// Sets the background colour of the window to the given RGBA colour definition. This colour will show through for all transparent pixels.\nexport function WindowSetBackgroundColour(\n  R: number,\n  G: number,\n  B: number,\n  A: number,\n): void;\n\n// [ScreenGetAll](https://wails.io/docs/reference/runtime/window#screengetall)\n// Gets the all screens. Call this anew each time you want to refresh data from the underlying windowing system.\nexport function ScreenGetAll(): Promise<Screen[]>;\n\n// [BrowserOpenURL](https://wails.io/docs/reference/runtime/browser#browseropenurl)\n// Opens the given URL in the system browser.\nexport function BrowserOpenURL(url: string): void;\n\n// [Environment](https://wails.io/docs/reference/runtime/intro#environment)\n// Returns information about the environment\nexport function Environment(): Promise<EnvironmentInfo>;\n\n// [Quit](https://wails.io/docs/reference/runtime/intro#quit)\n// Quits the application.\nexport function Quit(): void;\n\n// [Hide](https://wails.io/docs/reference/runtime/intro#hide)\n// Hides the application.\nexport function Hide(): void;\n\n// [Show](https://wails.io/docs/reference/runtime/intro#show)\n// Shows the application.\nexport function Show(): void;\n\n// [ClipboardGetText](https://wails.io/docs/reference/runtime/clipboard#clipboardgettext)\n// Returns the current text stored on clipboard\nexport function ClipboardGetText(): Promise<string>;\n\n// [ClipboardSetText](https://wails.io/docs/reference/runtime/clipboard#clipboardsettext)\n// Sets a text on the clipboard\nexport function ClipboardSetText(text: string): Promise<boolean>;\n"
  },
  {
    "path": "examples/wails/frontend/wailsjs/runtime/runtime.js",
    "content": "/*\n _       __      _ __\n| |     / /___ _(_) /____\n| | /| / / __ `/ / / ___/\n| |/ |/ / /_/ / / (__  )\n|__/|__/\\__,_/_/_/____/\nThe electron alternative for Go\n(c) Lea Anthony 2019-present\n*/\n\nexport function LogPrint(message) {\n  window.runtime.LogPrint(message);\n}\n\nexport function LogTrace(message) {\n  window.runtime.LogTrace(message);\n}\n\nexport function LogDebug(message) {\n  window.runtime.LogDebug(message);\n}\n\nexport function LogInfo(message) {\n  window.runtime.LogInfo(message);\n}\n\nexport function LogWarning(message) {\n  window.runtime.LogWarning(message);\n}\n\nexport function LogError(message) {\n  window.runtime.LogError(message);\n}\n\nexport function LogFatal(message) {\n  window.runtime.LogFatal(message);\n}\n\nexport function EventsOnMultiple(eventName, callback, maxCallbacks) {\n  return window.runtime.EventsOnMultiple(eventName, callback, maxCallbacks);\n}\n\nexport function EventsOn(eventName, callback) {\n  return EventsOnMultiple(eventName, callback, -1);\n}\n\nexport function EventsOff(eventName, ...additionalEventNames) {\n  return window.runtime.EventsOff(eventName, ...additionalEventNames);\n}\n\nexport function EventsOnce(eventName, callback) {\n  return EventsOnMultiple(eventName, callback, 1);\n}\n\nexport function EventsEmit(eventName) {\n  let args = [eventName].slice.call(arguments);\n  return window.runtime.EventsEmit.apply(null, args);\n}\n\nexport function WindowReload() {\n  window.runtime.WindowReload();\n}\n\nexport function WindowReloadApp() {\n  window.runtime.WindowReloadApp();\n}\n\nexport function WindowSetAlwaysOnTop(b) {\n  window.runtime.WindowSetAlwaysOnTop(b);\n}\n\nexport function WindowSetSystemDefaultTheme() {\n  window.runtime.WindowSetSystemDefaultTheme();\n}\n\nexport function WindowSetLightTheme() {\n  window.runtime.WindowSetLightTheme();\n}\n\nexport function WindowSetDarkTheme() {\n  window.runtime.WindowSetDarkTheme();\n}\n\nexport function WindowCenter() {\n  window.runtime.WindowCenter();\n}\n\nexport function WindowSetTitle(title) {\n  window.runtime.WindowSetTitle(title);\n}\n\nexport function WindowFullscreen() {\n  window.runtime.WindowFullscreen();\n}\n\nexport function WindowUnfullscreen() {\n  window.runtime.WindowUnfullscreen();\n}\n\nexport function WindowIsFullscreen() {\n  return window.runtime.WindowIsFullscreen();\n}\n\nexport function WindowGetSize() {\n  return window.runtime.WindowGetSize();\n}\n\nexport function WindowSetSize(width, height) {\n  window.runtime.WindowSetSize(width, height);\n}\n\nexport function WindowSetMaxSize(width, height) {\n  window.runtime.WindowSetMaxSize(width, height);\n}\n\nexport function WindowSetMinSize(width, height) {\n  window.runtime.WindowSetMinSize(width, height);\n}\n\nexport function WindowSetPosition(x, y) {\n  window.runtime.WindowSetPosition(x, y);\n}\n\nexport function WindowGetPosition() {\n  return window.runtime.WindowGetPosition();\n}\n\nexport function WindowHide() {\n  window.runtime.WindowHide();\n}\n\nexport function WindowShow() {\n  window.runtime.WindowShow();\n}\n\nexport function WindowMaximise() {\n  window.runtime.WindowMaximise();\n}\n\nexport function WindowToggleMaximise() {\n  window.runtime.WindowToggleMaximise();\n}\n\nexport function WindowUnmaximise() {\n  window.runtime.WindowUnmaximise();\n}\n\nexport function WindowIsMaximised() {\n  return window.runtime.WindowIsMaximised();\n}\n\nexport function WindowMinimise() {\n  window.runtime.WindowMinimise();\n}\n\nexport function WindowUnminimise() {\n  window.runtime.WindowUnminimise();\n}\n\nexport function WindowSetBackgroundColour(R, G, B, A) {\n  window.runtime.WindowSetBackgroundColour(R, G, B, A);\n}\n\nexport function ScreenGetAll() {\n  return window.runtime.ScreenGetAll();\n}\n\nexport function WindowIsMinimised() {\n  return window.runtime.WindowIsMinimised();\n}\n\nexport function WindowIsNormal() {\n  return window.runtime.WindowIsNormal();\n}\n\nexport function BrowserOpenURL(url) {\n  window.runtime.BrowserOpenURL(url);\n}\n\nexport function Environment() {\n  return window.runtime.Environment();\n}\n\nexport function Quit() {\n  window.runtime.Quit();\n}\n\nexport function Hide() {\n  window.runtime.Hide();\n}\n\nexport function Show() {\n  window.runtime.Show();\n}\n\nexport function ClipboardGetText() {\n  return window.runtime.ClipboardGetText();\n}\n\nexport function ClipboardSetText(text) {\n  return window.runtime.ClipboardSetText(text);\n}\n"
  },
  {
    "path": "examples/wails/go.mod",
    "content": "module changeme\n\ngo 1.18\n\nrequire github.com/wailsapp/wails/v2 v2.6.0\n\nrequire (\n\tgithub.com/bep/debounce v1.2.1 // indirect\n\tgithub.com/go-ole/go-ole v1.2.6 // indirect\n\tgithub.com/google/uuid v1.3.0 // indirect\n\tgithub.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect\n\tgithub.com/labstack/echo/v4 v4.10.2 // indirect\n\tgithub.com/labstack/gommon v0.4.0 // indirect\n\tgithub.com/leaanthony/go-ansi-parser v1.6.0 // indirect\n\tgithub.com/leaanthony/gosod v1.0.3 // indirect\n\tgithub.com/leaanthony/slicer v1.6.0 // indirect\n\tgithub.com/mattn/go-colorable v0.1.13 // indirect\n\tgithub.com/mattn/go-isatty v0.0.19 // indirect\n\tgithub.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect\n\tgithub.com/pkg/errors v0.9.1 // indirect\n\tgithub.com/rivo/uniseg v0.4.4 // indirect\n\tgithub.com/samber/lo v1.38.1 // indirect\n\tgithub.com/tkrajina/go-reflector v0.5.6 // indirect\n\tgithub.com/valyala/bytebufferpool v1.0.0 // indirect\n\tgithub.com/valyala/fasttemplate v1.2.2 // indirect\n\tgithub.com/wailsapp/go-webview2 v1.0.1 // indirect\n\tgithub.com/wailsapp/mimetype v1.4.1 // indirect\n\tgolang.org/x/crypto v0.36.0 // indirect\n\tgolang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect\n\tgolang.org/x/net v0.38.0 // indirect\n\tgolang.org/x/sys v0.31.0 // indirect\n\tgolang.org/x/text v0.23.0 // indirect\n)\n\n// replace github.com/wailsapp/wails/v2 v2.6.0 => C:\\Users\\amr\\go\\pkg\\mod\n"
  },
  {
    "path": "examples/wails/go.sum",
    "content": "github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=\ngithub.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=\ngithub.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=\ngithub.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=\ngithub.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck=\ngithub.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=\ngithub.com/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M=\ngithub.com/labstack/echo/v4 v4.10.2/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k=\ngithub.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=\ngithub.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=\ngithub.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oOc=\ngithub.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA=\ngithub.com/leaanthony/go-ansi-parser v1.6.0 h1:T8TuMhFB6TUMIUm0oRrSbgJudTFw9csT3ZK09w0t4Pg=\ngithub.com/leaanthony/go-ansi-parser v1.6.0/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU=\ngithub.com/leaanthony/gosod v1.0.3 h1:Fnt+/B6NjQOVuCWOKYRREZnjGyvg+mEhd1nkkA04aTQ=\ngithub.com/leaanthony/gosod v1.0.3/go.mod h1:BJ2J+oHsQIyIQpnLPjnqFGTMnOZXDbvWtRCSG7jGxs4=\ngithub.com/leaanthony/slicer v1.5.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY=\ngithub.com/leaanthony/slicer v1.6.0 h1:1RFP5uiPJvT93TAHi+ipd3NACobkW53yUiBqZheE/Js=\ngithub.com/leaanthony/slicer v1.6.0/go.mod h1:o/Iz29g7LN0GqH3aMjWAe90381nyZlDNquK+mtH2Fj8=\ngithub.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=\ngithub.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=\ngithub.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=\ngithub.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=\ngithub.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=\ngithub.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=\ngithub.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=\ngithub.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=\ngithub.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\ngithub.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=\ngithub.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=\ngithub.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=\ngithub.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=\ngithub.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=\ngithub.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=\ngithub.com/tkrajina/go-reflector v0.5.6 h1:hKQ0gyocG7vgMD2M3dRlYN6WBBOmdoOzJ6njQSepKdE=\ngithub.com/tkrajina/go-reflector v0.5.6/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4=\ngithub.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=\ngithub.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=\ngithub.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=\ngithub.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=\ngithub.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=\ngithub.com/wailsapp/go-webview2 v1.0.1 h1:dEJIeEApW/MhO2tTMISZBFZPuW7kwrFA1NtgFB1z1II=\ngithub.com/wailsapp/go-webview2 v1.0.1/go.mod h1:Uk2BePfCRzttBBjFrBmqKGJd41P6QIHeV9kTgIeOZNo=\ngithub.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=\ngithub.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=\ngithub.com/wailsapp/wails/v2 v2.6.0 h1:EyH0zR/EO6dDiqNy8qU5spaXDfkluiq77xrkabPYD4c=\ngithub.com/wailsapp/wails/v2 v2.6.0/go.mod h1:WBG9KKWuw0FKfoepBrr/vRlyTmHaMibWesK3yz6nNiM=\ngolang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=\ngolang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=\ngolang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=\ngolang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=\ngolang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=\ngolang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=\ngolang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=\ngolang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=\ngolang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\n"
  },
  {
    "path": "examples/wails/main.go",
    "content": "package main\n\nimport (\n\t\"embed\"\n\n\t\"github.com/wailsapp/wails/v2\"\n\t\"github.com/wailsapp/wails/v2/pkg/options\"\n\t\"github.com/wailsapp/wails/v2/pkg/options/assetserver\"\n)\n\n//go:embed all:frontend\nvar assets embed.FS\n\nfunc main() {\n\t// Create an instance of the app structure\n\tapp := NewApp()\n\n\t// Create application with options\n\terr := wails.Run(&options.App{\n\t\tTitle:  \"wails-example\",\n\t\tWidth:  1024,\n\t\tHeight: 768,\n\t\tAssetServer: &assetserver.Options{\n\t\t\tAssets: assets,\n\t\t},\n\t\tBackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},\n\t\tOnStartup:        app.startup,\n\t\tBind: []interface{}{\n\t\t\tapp,\n\t\t},\n\t})\n\n\tif err != nil {\n\t\tprintln(\"Error:\", err.Error())\n\t}\n}\n"
  },
  {
    "path": "examples/wails/wails.json",
    "content": "{\n  \"$schema\": \"https://wails.io/schemas/config.v2.json\",\n  \"name\": \"wails-example\",\n  \"outputfilename\": \"wails_example\"\n}\n"
  },
  {
    "path": "examples/wry/.gitignore",
    "content": "/target"
  },
  {
    "path": "examples/wry/Cargo.toml",
    "content": "[package]\nname = \"wry-example\"\nversion = \"0.0.0\"\nedition = \"2021\"\npublish = false\n\n[dependencies]\nwry = \"0.53\"\ntao = \"0.34\"\n\n[package.metadata.packager]\nbefore-packaging-command = \"cargo build --release\"\nproduct-name = \"WRY example\"\nidentifier = \"com.wry.example\"\nresources = [\"Cargo.toml\", \"src\", \"32x32.png\"]\nicons = [\"32x32.png\"]\n\n[package.metadata.packager.deb]\ndepends = [\"libgtk-3-0\", \"libwebkit2gtk-4.1-0\"]\n\n[package.metadata.packager.appimage]\nlibs = [\n  \"WebKitNetworkProcess\",\n  \"WebKitWebProcess\",\n  \"libwebkit2gtkinjectedbundle.so\",\n]\n\n[package.metadata.packager.appimage.linuxdeploy-plugins]\n\"gtk\" = \"https://raw.githubusercontent.com/tauri-apps/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh\"\n\n[package.metadata.packager.nsis]\nappdata-paths = [\"$LOCALAPPDATA/$IDENTIFIER\"]\npreinstall-section = \"\"\"\n; Setup messages\n; English\nLangString webview2AbortError ${LANG_ENGLISH} \"Failed to install WebView2! The app can't run without it. Try restarting the installer.\"\nLangString webview2DownloadError ${LANG_ENGLISH} \"Error: Downloading WebView2 Failed - $0\"\nLangString webview2DownloadSuccess ${LANG_ENGLISH} \"WebView2 bootstrapper downloaded successfully\"\nLangString webview2Downloading ${LANG_ENGLISH} \"Downloading WebView2 bootstrapper...\"\nLangString webview2InstallError ${LANG_ENGLISH} \"Error: Installing WebView2 failed with exit code $1\"\nLangString webview2InstallSuccess ${LANG_ENGLISH} \"WebView2 installed successfully\"\n\nSection PreInstall\n  ; Check if Webview2 is already installed and skip this section\n  ${If} ${RunningX64}\n    ReadRegStr $4 HKLM \"SOFTWARE\\\\WOW6432Node\\\\Microsoft\\\\EdgeUpdate\\\\Clients\\\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}\" \"pv\"\n  ${Else}\n    ReadRegStr $4 HKLM \"SOFTWARE\\\\Microsoft\\\\EdgeUpdate\\\\Clients\\\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}\" \"pv\"\n  ${EndIf}\n  ReadRegStr $5 HKCU \"SOFTWARE\\\\Microsoft\\\\EdgeUpdate\\\\Clients\\\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}\" \"pv\"\n\n  StrCmp $4 \"\" 0 webview2_done\n  StrCmp $5 \"\" 0 webview2_done\n\n  Delete \"$TEMP\\\\MicrosoftEdgeWebview2Setup.exe\"\n  DetailPrint \"$(webview2Downloading)\"\n  nsis_tauri_utils::download \"https://go.microsoft.com/fwlink/p/?LinkId=2124703\" \"$TEMP\\\\MicrosoftEdgeWebview2Setup.exe\"\n  Pop $0\n  ${If} $0 == 0\n      DetailPrint \"$(webview2DownloadSuccess)\"\n  ${Else}\n      DetailPrint \"$(webview2DownloadError)\"\n      Abort \"$(webview2AbortError)\"\n  ${EndIf}\n  StrCpy $6 \"$TEMP\\\\MicrosoftEdgeWebview2Setup.exe\"\n\n  DetailPrint \"$(installingWebview2)\"\n  ; $6 holds the path to the webview2 installer\n  ExecWait \"$6 /install\" $1\n  ${If} $1 == 0\n    DetailPrint \"$(webview2InstallSuccess)\"\n  ${Else}\n    DetailPrint \"$(webview2InstallError)\"\n    Abort \"$(webview2AbortError)\"\n  ${EndIf}\n  webview2_done:\nSectionEnd\n\"\"\"\n"
  },
  {
    "path": "examples/wry/README.md",
    "content": "## WRY example\n\n1. package the app\n\n   ```sh\n   cargo r -p cargo-packager -- -p wry-example --release\n   ```\n"
  },
  {
    "path": "examples/wry/src/main.rs",
    "content": "#![cfg_attr(not(debug_assertions), windows_subsystem = \"windows\")] // hide console window on Windows in release\n\nfn main() -> Result<(), Box<dyn std::error::Error>> {\n    use tao::{\n        event::{Event, StartCause, WindowEvent},\n        event_loop::{ControlFlow, EventLoop},\n        window::WindowBuilder,\n    };\n    use wry::WebViewBuilder;\n\n    let event_loop = EventLoop::new();\n    let window = WindowBuilder::new()\n        .with_title(\"html5test\")\n        .build(&event_loop)?;\n    let _webview = WebViewBuilder::new()\n        .with_url(\"https://html5test.com/\")\n        .build(&window)?;\n\n    event_loop.run(move |event, _, control_flow| {\n        *control_flow = ControlFlow::Wait;\n\n        match event {\n            Event::NewEvents(StartCause::Init) => println!(\"Wry has started!\"),\n            Event::WindowEvent {\n                event: WindowEvent::CloseRequested,\n                ..\n            } => *control_flow = ControlFlow::Exit,\n            _ => (),\n        }\n    });\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"private\": true,\n  \"scripts\": {\n    \"build\": \"pnpm run -r --parallel build\",\n    \"build:debug\": \"pnpm run -r --parallel build:debug\",\n    \"format\": \"prettier --write .\",\n    \"format:check\": \"prettier --check .\"\n  },\n  \"devDependencies\": {\n    \"prettier\": \"^3.1.0\"\n  }\n}\n"
  },
  {
    "path": "pnpm-workspace.yaml",
    "content": "packages:\n  - bindings/*/nodejs\n"
  },
  {
    "path": "renovate.json",
    "content": "{\n  \"extends\": [\"config:recommended\"],\n  \"rangeStrategy\": \"replace\",\n  \"packageRules\": [\n    {\n      \"semanticCommitType\": \"chore\",\n      \"matchPackageNames\": [\"*\"]\n    }\n  ]\n}\n"
  }
]