mirror of
https://github.com/tauri-apps/smoke-tests.git
synced 2026-01-31 00:35:18 +01:00
fix(structure): remove inception
This commit is contained in:
181
README.md
181
README.md
@@ -1,183 +1,10 @@
|
||||
# smoke-tests [WIP]
|
||||
# Examples
|
||||
A collection of frameworks used as a suite of smoke-tests for tauri
|
||||
|
||||
## Basic Tests
|
||||
The `smoke/todomvc` folder is composed of baseline apps from the [todomvc](https://github.com/tastejs/todomvc) projects. At the moment we're building:
|
||||
- angular2
|
||||
- polymer
|
||||
- react
|
||||
- vanillajs
|
||||
|
||||
They have not been modified in any way.
|
||||
|
||||
### smoke/todomvc/vanillajs/monolith
|
||||
This is a hand-made monolithic html file. It should be good to just drop in. Soon we'll have a webpack and babel approach to do this as well.
|
||||
|
||||
### smoke/todomvc/vanillajs/duolith
|
||||
This splits the monolith into an `index.html` and `js/app.js` files
|
||||
|
||||
### smoke/quasar/compiled-web
|
||||
This is a Quasar app that has been transpiled and un-chunked to make a single monolithic file. The toolchain to do this is not yet available.
|
||||
|
||||
## test-drive
|
||||
Assuming you have cargo and rust installed. If not, see below/
|
||||
|
||||
```bash
|
||||
$ git clone https://github.com/tauri-apps/smoke-tests
|
||||
$ cd test
|
||||
$ yarn
|
||||
$ cargo install --path node_modules/@tauri-apps/tauri/tools/rust/cargo-tauri-bundle --force
|
||||
$ yarn tauri build
|
||||
```
|
||||
After tauri has compiled its rust resources, look in the `src-tauri/target/release/bundle`.
|
||||
|
||||
Currently available in `test/binaries`:
|
||||
- [x] MacOS app
|
||||
- [ ] Windows exe
|
||||
- [ ] Linux deb
|
||||
|
||||
## Add Rust and Build Toolchain
|
||||
|
||||
### Windows 64 or 32 bit
|
||||
You will need to have Visual Studio and windows-build-tools installed.
|
||||
|
||||
First visit the [Microsoft docs](https://docs.microsoft.com/en-us/visualstudio/install/install-visual-studio?view=vs-2019) and install Visual Studio.
|
||||
```bash
|
||||
$ npm install --global windows-build-tools
|
||||
```
|
||||
|
||||
If you are running Windows 64-bit, download and run [rustup‑init.exe](https://win.rustup.rs/x86_64) and then follow the onscreen instructions.
|
||||
|
||||
If you are running Windows 32-bit, download and run [rustup‑init.exe](https://win.rustup.rs/i686) and then follow the onscreen instructions.
|
||||
|
||||
### Arch
|
||||
According to the Arch manual, this is something you were born knowing. But seriously, if you want to help out explaining how newbies to Arch can do this, please feel free to make a PR to this doc.
|
||||
|
||||
### BSD
|
||||
Similar to Arch, you already have everything installed because you compile kernels. However:
|
||||
- Execution on OpenBSD requires wxallowed mount(8) option.
|
||||
- FreeBSD is also supported, to install webkit2 run pkg install webkit2-gtk3.
|
||||
|
||||
### Ubuntu
|
||||
First install Ubuntu then:
|
||||
```bash
|
||||
$ sudo apt update && sudo apt install libwebkit2gtk-4.0-dev build-essential
|
||||
```
|
||||
|
||||
### MacOS
|
||||
```bash
|
||||
$ brew install gcc
|
||||
```
|
||||
|
||||
### Everybody except Windows
|
||||
```
|
||||
$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
```
|
||||
|
||||
> We have audited this bash script, and it does what it says it is supposed to do. Nevertheless, before blindly curl-bashing a script, it is always wise to look at it first. Here is file as a mere [download link](https://sh.rustup.rs)
|
||||
|
||||
Make sure that `rustc` and `cargo` are in your $PATH. Run
|
||||
```bash
|
||||
$ rustc --version
|
||||
latest update on 2019-07-04, rust version 1.37.0
|
||||
```
|
||||
and make sure you are on latest update on 2019-07-04, rust version 1.37.0 - otherwise be sure to update.
|
||||
|
||||
```
|
||||
$ rustup update stable
|
||||
$ rustup override set 1.37.0
|
||||
```
|
||||
|
||||
|
||||
## About `rustup` (from their [website](https://rustup.rs))
|
||||
`rustup` installs rustc, cargo, rustup and other standard tools to Cargo's bin directory. On Unix it is located at `$HOME/.cargo/bin` and on Windows at `%USERPROFILE%\.cargo\bin`. This is the same directory that cargo install will install Rust programs and Cargo plugins.
|
||||
|
||||
This directory will be in your `$PATH` environment variable, which means you can run them from the shell without further configuration. **Open a new shell** and type the following:
|
||||
|
||||
```bash
|
||||
$ rustc --version
|
||||
```
|
||||
or run:
|
||||
|
||||
```bash
|
||||
source $HOME/.cargo/env
|
||||
|
||||
# and then
|
||||
|
||||
$ rustc --version
|
||||
```
|
||||
|
||||
If you don't see 1.37.0 or later, then you'll need to upgrade your rust.
|
||||
|
||||
```bash
|
||||
$ rustup update stable
|
||||
$ rustup override set 1.37.0
|
||||
```
|
||||
|
||||
### bundler
|
||||
After you have installed Rust and the build toolchain, it is wise to open a new shell before continuing.
|
||||
|
||||
Setup the bundler:
|
||||
```bash
|
||||
$ cargo install --path node_modules/@tauri-apps/tauri/tools/rust/cargo-tauri-bundle --force
|
||||
```
|
||||
|
||||
Want to debug?
|
||||
#### *nix
|
||||
|
||||
```bash
|
||||
$ cd src-tauri
|
||||
$ RUST_DEBUG=1 cargo build
|
||||
```
|
||||
|
||||
#### Windows
|
||||
|
||||
```bash
|
||||
$ cd src-tauri
|
||||
$ set RUST_DEBUG=1
|
||||
$ cargo build
|
||||
```
|
||||
|
||||
|
||||
## experimental anti-bloat features
|
||||
|
||||
- https://github.com/RazrFalcon/cargo-bloat
|
||||
- https://lifthrasiir.github.io/rustlog/why-is-a-rust-executable-large.html
|
||||
- https://doc.rust-lang.org/cargo/reference/manifest.html#the-profile-sections
|
||||
|
||||
add this to your `/src-tauri/Cargo.toml` (currently being used in the /test project)
|
||||
```
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
codegen-units = 1
|
||||
lto = true
|
||||
incremental = false
|
||||
opt-level = "z"
|
||||
```
|
||||
|
||||
### upx
|
||||
UPX, **Ultimate Packer for eXecutables**, is a dinosaur amongst the binary packers. This 23-year old, well-maintained piece of kit is GPL-v2 licensed with a pretty liberal usage declaration. Our understanding of the licensing is that you can use it for any purposes (commercial or otherwise) without needing to change your license unless you modify the source code of UPX.
|
||||
|
||||
Basically it compresses the binary and decompresses it at runtime. It should work for pretty much any binary type out there. Read more: https://github.com/upx/upx
|
||||
|
||||
> You should know that this technique might flag your binary as a virus on Windows and MacOS - so use at your own discretion, and as always validate with Frida and do real distribution testing!
|
||||
|
||||
#### Usage on MacOS
|
||||
```bash
|
||||
$ brew install upx
|
||||
$ yarn tauri build
|
||||
$ upx --ultra-brute src-tauri/target/release/bundle/osx/app.app/Contents/MacOS/app
|
||||
Ultimate Packer for eXecutables
|
||||
Copyright (C) 1996 - 2018
|
||||
UPX 3.95 Markus Oberhumer, Laszlo Molnar & John Reiser Aug 26th 2018
|
||||
|
||||
File size Ratio Format Name
|
||||
-------------------- ------ ----------- -----------
|
||||
963140 -> 274448 28.50% macho/amd64 app
|
||||
```
|
||||
These examples are a great way to see how Tauri works with a variety of frameworks.
|
||||
|
||||
## error reporting
|
||||
Please report all library errors at https://github.com/tauri-apps/tauri
|
||||
Please report all library errors at https://github.com/tauri-apps/tauri
|
||||
|
||||
## License
|
||||
Everything in this repo is MIT License unless otherwise specified. The TodoMVC projects are also Copyright (c) Addy Osmani, Sindre Sorhus, Pascal Hartig, Stephen Sawchuk and the respective authors.
|
||||
Everything in this repo is MIT License unless otherwise specified.
|
||||
@@ -1,24 +0,0 @@
|
||||
# Current WG Code Sub Teams:
|
||||
# @tauri-apps/admins
|
||||
# @tauri-apps/bundler
|
||||
# @tauri-apps/core
|
||||
# @tauri-apps/js-cli
|
||||
# @tauri-apps/testing
|
||||
|
||||
# admins default to review
|
||||
# Order is important; the last matching pattern takes the most precedence.
|
||||
* @tauri-apps/admins
|
||||
|
||||
.github @tauri-apps/admins @tauri-apps/testing
|
||||
|
||||
/examples/ @tauri-apps/testing
|
||||
|
||||
/cli/tauri-bundler/ @tauri-apps/bundler
|
||||
|
||||
/cli/tauri.js/ @tauri-apps/js-cli
|
||||
|
||||
/tauri-update/ @tauri-apps/core
|
||||
|
||||
/tauri-api/ @tauri-apps/core
|
||||
|
||||
/tauri/ @tauri-apps/core
|
||||
@@ -1,13 +0,0 @@
|
||||
# Contributor Code of Conduct
|
||||
|
||||
As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
|
||||
|
||||
We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion.
|
||||
|
||||
Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
|
||||
@@ -1,54 +0,0 @@
|
||||
# Tauri Contributing Guide
|
||||
|
||||
Hi! We, the maintainers, are really excited that you are interested in contributing to Tauri. Before submitting your contribution though, please make sure to take a moment and read through the following guidelines.
|
||||
|
||||
- [Code of Conduct](CODE_OF_CONDUCT.md)
|
||||
- [Issue Reporting Guidelines](#issue-reporting-guidelines)
|
||||
- [Pull Request Guidelines](#pull-request-guidelines)
|
||||
- [Development Setup](#development-setup)
|
||||
- [Project Structure](#project-structure)
|
||||
- [Financial Contribution](#financial-contribution)
|
||||
|
||||
## Issue Reporting Guidelines
|
||||
|
||||
- The issue list of this repo is **exclusively** for bug reports and feature requests. Non-conforming issues will be closed immediately.
|
||||
|
||||
- For simple beginner questions, you can get quick answers from the [Tauri Discord chat](https://discord.gg/SpmNs4S).
|
||||
|
||||
- Try to search for your issue, it may have already been answered or even fixed in the development branch (`dev`).
|
||||
|
||||
- Check if the issue is reproducible with the latest stable version of Tauri. If you are using a pre-release, please indicate the specific version you are using.
|
||||
|
||||
- It is **required** that you clearly describe the steps necessary to reproduce the issue you are running into. Although we would love to help our users as much as possible, diagnosing issues without clear reproduction steps is extremely time-consuming and simply not sustainable.
|
||||
|
||||
- Use only the minimum amount of code necessary to reproduce the unexpected behavior. A good bug report should isolate specific methods that exhibit unexpected behavior and precisely define how expectations were violated. What did you expect the method or methods to do, and how did the observed behavior differ? The more precisely you isolate the issue, the faster we can investigate.
|
||||
|
||||
- Issues with no clear repro steps will not be triaged. If an issue labeled "need repro" receives no further input from the issue author for more than 5 days, it will be closed.
|
||||
|
||||
- If your issue is resolved but still open, don’t hesitate to close it. In case you found a solution by yourself, it could be helpful to explain how you fixed it.
|
||||
|
||||
- Most importantly, we beg your patience: the team must balance your request against many other responsibilities — fixing other bugs, answering other questions, new features, new documentation, etc. The issue list is not paid support and we cannot make guarantees about how fast your issue can be resolved.
|
||||
|
||||
## Pull Request Guidelines
|
||||
|
||||
- The `master` branch is basically just a snapshot of the latest stable release. All development should be done in dedicated branches. **Do not submit PRs against the `master` branch.**
|
||||
|
||||
- Checkout a topic branch from the relevant branch, e.g. `dev`, and merge back against that branch.
|
||||
|
||||
- **DO NOT** checkin `dist` in the commits.
|
||||
|
||||
- It's OK to have multiple small commits as you work on the PR - we will let GitHub automatically squash it before merging.
|
||||
|
||||
- If adding new feature:
|
||||
- Provide convincing reason to add this feature. Ideally you should open a suggestion issue first and have it greenlighted before working on it.
|
||||
|
||||
- If fixing a bug:
|
||||
- If you are resolving a special issue, add `(fix: #xxxx[,#xxx])` (#xxxx is the issue id) in your PR title for a better release log, e.g. `fix: update entities encoding/decoding (fix #3899)`.
|
||||
- Provide detailed description of the bug in the PR. Live demo preferred.
|
||||
|
||||
## Development Setup
|
||||
TO BE UPDATED.
|
||||
|
||||
## Financial Contribution
|
||||
|
||||
Tauri is an MIT-licensed open source project. Its ongoing development can be supported via open-collective.
|
||||
@@ -1,8 +0,0 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: nothingismagick
|
||||
patreon: #
|
||||
open_collective: tauri
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
custom: # Replace with a single custom sponsorship URL
|
||||
@@ -1,38 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve Tauri
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Platform and Versions (please complete the following information):**
|
||||
<!-- You can use the `tauri info` command to get this information -->
|
||||
OS:
|
||||
Node:
|
||||
NPM:
|
||||
Yarn:
|
||||
Rustc:
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
|
||||
**Stack Trace**
|
||||
<!-- add if applicable -->
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
name: Docs report
|
||||
about: Create a report to help us improve Tauri docs
|
||||
title: "[docs]"
|
||||
labels: docs
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for Tauri
|
||||
title: ''
|
||||
labels: feature request
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
@@ -1,39 +0,0 @@
|
||||
<!--
|
||||
Please make sure to read the Pull Request Guidelines:
|
||||
https://github.com/tauri-apps/tauri/blob/dev/.github/CONTRIBUTING.md#pull-request-guidelines
|
||||
-->
|
||||
|
||||
<!-- PULL REQUEST TEMPLATE -->
|
||||
<!-- (Update "[ ]" to "[x]" to check a box) -->
|
||||
|
||||
**What kind of change does this PR introduce?** (check at least one)
|
||||
<!--
|
||||
If you are introducing a new binding, you must reference an issue where this binding has been proposed, discussed and approved by the maintainers.
|
||||
-->
|
||||
|
||||
- [ ] Bugfix
|
||||
- [ ] Feature
|
||||
- [ ] New Binding Issue #___
|
||||
- [ ] Code style update
|
||||
- [ ] Refactor
|
||||
- [ ] Build-related changes
|
||||
- [ ] Other, please describe:
|
||||
|
||||
**Does this PR introduce a breaking change?** (check one)
|
||||
<!--
|
||||
If yes, please describe the impact and migration path for existing applications in an attached issue. Filing a PR with breaking changes that has not been discussed and approved by the maintainers in an issue will be immediately closed.
|
||||
-->
|
||||
|
||||
- [ ] Yes. Issue #___
|
||||
- [ ] No
|
||||
|
||||
|
||||
**The PR fulfills these requirements:**
|
||||
|
||||
- [ ] It's submitted to the `dev` branch and _not_ the `master` branch
|
||||
- [ ] When resolving a specific issue, it's referenced in the PR's title (e.g. `fix: #xxx[,#xxx]`, where "xxx" is the issue number)
|
||||
|
||||
If adding a **new feature**, the PR's description includes:
|
||||
- [ ] A convincing reason for adding this feature (to avoid wasting your time, it's best to open a suggestion issue first and wait for approval before working on it)
|
||||
|
||||
**Other information:**
|
||||
@@ -1,24 +0,0 @@
|
||||
name: clippy check
|
||||
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- master
|
||||
- dev
|
||||
|
||||
jobs:
|
||||
clippy_check:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TAURI_DIST_DIR: tauri/test/fixture/dist
|
||||
TAURI_DIR: ../test/fixture/src-tauri
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: install webkit2gtk
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y webkit2gtk-4.0
|
||||
- run: rustup component add clippy
|
||||
- uses: actions-rs/clippy-check@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -1,13 +0,0 @@
|
||||
name: Greetings
|
||||
|
||||
on: [pull_request, issues]
|
||||
|
||||
jobs:
|
||||
greeting:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/first-interaction@v1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue-message: 'Thanks so much for your interest in Tauri!'
|
||||
pr-message: 'Thanks so much for your contribution! :heart: We will get to reviewing and merging your PR asap. In the meantime, please make sure you have signed all your commits. Signed commits mean that we can have improved level of trust that the commit was actually pushed by you.'
|
||||
@@ -1,32 +0,0 @@
|
||||
name: pr-to-master
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
|
||||
jobs:
|
||||
pr-to-master:
|
||||
runs-on: ubuntu-latest
|
||||
if: contains(github.event.head_commit.message, 'version updates')
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
ref: master
|
||||
- name: git config
|
||||
run: |
|
||||
git config --global user.name "${{ github.event.pusher.name }}"
|
||||
git config --global user.email "${{ github.event.pusher.email }}"
|
||||
- run: git fetch origin dev
|
||||
- run: git read-tree -u --reset ${{ github.sha }}
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v2
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
commit-message: ${{ github.event.head_commit.message }}
|
||||
branch: release/master
|
||||
branch-suffix: short-commit-hash
|
||||
title: Publish
|
||||
body: |
|
||||
Merging this PR will squash and push all changes to the master branch.
|
||||
It will kick off the process to release and publish any packages with an incremented version number.
|
||||
@@ -1,105 +0,0 @@
|
||||
name: release-cargo
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- '**/Cargo.toml'
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
package:
|
||||
- name: tauri-bundler
|
||||
registryName: tauri-bundler
|
||||
path: cli/tauri-bundler
|
||||
publishPath: /target/package
|
||||
- name: tauri-core
|
||||
registryName: tauri
|
||||
path: tauri
|
||||
publishPath: /target/package
|
||||
- name: tauri-api
|
||||
registryName: tauri-api
|
||||
path: tauri-api
|
||||
- name: tauri-updater
|
||||
registryName: tauri-updater
|
||||
path: tauri-updater
|
||||
- name: tauri-utils
|
||||
registryName: tauri-utils
|
||||
path: tauri-utils
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
ref: master
|
||||
toolchain: stable
|
||||
- name: install webkit2gtk
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y webkit2gtk-4.0
|
||||
- name: get version
|
||||
working-directory: ${{ matrix.package.path }}
|
||||
run: echo ::set-env name=PACKAGE_VERSION::$(sed -nE 's/^\s*version = "(.*?)"/\1/p' Cargo.toml)
|
||||
- name: check published version
|
||||
run: echo ::set-env name=PUBLISHED_VERSION::$(cargo search ${{ matrix.package.registryName }} --limit 1 | sed -nE 's/^[^"]*"//; s/".*//1p' -)
|
||||
- name: cargo package
|
||||
if: env.PACKAGE_VERSION != env.PUBLISHED_VERSION
|
||||
working-directory: ${{ matrix.package.path }}
|
||||
run: |
|
||||
echo "package dir:"
|
||||
ls
|
||||
cargo package
|
||||
echo "We will publish:" $PACKAGE_VERSION
|
||||
echo "This is current latest:" $PUBLISHED_VERSION
|
||||
echo "post package dir:"
|
||||
cd ${{ matrix.publishPath }}
|
||||
ls
|
||||
- name: cargo audit
|
||||
if: env.PACKAGE_VERSION != env.PUBLISHED_VERSION
|
||||
working-directory: ${{ matrix.package.path }}
|
||||
run: |
|
||||
cargo install cargo-audit
|
||||
echo "# Cargo Audit" | tee -a ${{runner.workspace }}/notes.md
|
||||
cargo audit 2>&1 | tee -a ${{runner.workspace }}/notes.md
|
||||
- name: Publish ${{ matrix.package.name }}
|
||||
if: env.PACKAGE_VERSION != env.PUBLISHED_VERSION
|
||||
working-directory: ${{ matrix.package.path }}
|
||||
run: |
|
||||
echo "# Cargo Publish" | tee -a ${{runner.workspace }}/notes.md
|
||||
cargo publish 2>&1 | tee -a ${{runner.workspace }}/notes.md
|
||||
- name: Create Release
|
||||
id: create_crate_release
|
||||
if: env.PACKAGE_VERSION != env.PUBLISHED_VERSION
|
||||
uses: jbolda/create-release@v1.1.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ matrix.package.name }}-v${{ env.PACKAGE_VERSION }}
|
||||
release_name: "Release ${{ matrix.package.name }} v${{ env.PACKAGE_VERSION }} [crates.io]"
|
||||
bodyFromFile: ./../notes.md
|
||||
draft: false
|
||||
prerelease: false
|
||||
- name: Upload Release Asset
|
||||
id: upload-release-asset
|
||||
if: env.PACKAGE_VERSION != env.PUBLISHED_VERSION
|
||||
uses: actions/upload-release-asset@v1.0.1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_crate_release.outputs.upload_url }}
|
||||
asset_path: ./${{ matrix.package.publishPath }}/${{ matrix.package.registryName }}-${{ env.PACKAGE_VERSION }}.crate
|
||||
asset_name: ${{ matrix.package.registryName }}-${{ env.PACKAGE_VERSION }}.crate
|
||||
asset_content_type: application/x-gtar
|
||||
- name: Tangle Release
|
||||
id: tangle_release
|
||||
if: env.PACKAGE_VERSION != env.PUBLISHED_VERSION
|
||||
uses: iotaledger/gh-tangle-release@v0.5.2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
IOTA_SEED: ${{ secrets.IOTA_SEED }}
|
||||
with:
|
||||
tag_name: ${{ matrix.package.name }}-v${{ env.PACKAGE_VERSION }}
|
||||
comment: "[Test] Release ${{ matrix.package.name }} v${{ env.PACKAGE_VERSION }} [crates.io]"
|
||||
@@ -1,89 +0,0 @@
|
||||
name: release-npm
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- '**/package.json'
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
package:
|
||||
- name: tauri.js
|
||||
registryName: tauri
|
||||
path: cli/tauri.js
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
ref: master
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
- name: get version
|
||||
working-directory: ${{ matrix.package.path }}
|
||||
run: echo ::set-env name=PACKAGE_VERSION::$(node -p "require('./package.json').version")
|
||||
- name: check published version
|
||||
run: echo ::set-env name=PUBLISHED_VERSION::$(npm view ${{ matrix.package.registryName }} version)
|
||||
- name: npm pack
|
||||
if: env.PACKAGE_VERSION != env.PUBLISHED_VERSION
|
||||
working-directory: ${{ matrix.package.path }}
|
||||
run: |
|
||||
echo "pack dir:"
|
||||
ls
|
||||
npm pack
|
||||
echo "We will publish:" $PACKAGE_VERSION
|
||||
echo "This is current latest:" $PUBLISHED_VERSION
|
||||
echo "post pack dir:"
|
||||
ls
|
||||
- name: npm audit
|
||||
working-directory: ${{ matrix.package.path }}
|
||||
run: |
|
||||
echo "# NPM Audit Results" | tee -a ${{runner.workspace }}/notes.md
|
||||
npm audit 2>&1 | tee -a ${{runner.workspace }}/notes.md
|
||||
- name: Publish ${{ matrix.package.name }}
|
||||
if: env.PACKAGE_VERSION != env.PUBLISHED_VERSION
|
||||
working-directory: ${{ matrix.package.path }}
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: |
|
||||
echo "# NPM Package Publish" | tee -a ${{runner.workspace }}/notes.md
|
||||
npm publish 2>&1 | tee -a ${{runner.workspace }}/notes.md
|
||||
- name: Create Release
|
||||
id: create_npm_release
|
||||
if: env.PACKAGE_VERSION != env.PUBLISHED_VERSION
|
||||
uses: jbolda/create-release@v1.1.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ matrix.package.name }}-v${{ env.PACKAGE_VERSION }}
|
||||
release_name: "Release ${{ matrix.package.name }} v${{ env.PACKAGE_VERSION }} [npmjs.com]"
|
||||
bodyFromFile: ./../notes.md
|
||||
draft: false
|
||||
prerelease: false
|
||||
- name: Upload Release Asset
|
||||
id: upload-release-asset
|
||||
if: env.PACKAGE_VERSION != env.PUBLISHED_VERSION
|
||||
uses: actions/upload-release-asset@v1.0.1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_npm_release.outputs.upload_url }}
|
||||
asset_path: ./${{ matrix.package.path }}/${{ matrix.package.registryName }}-${{ env.PACKAGE_VERSION }}.tgz
|
||||
asset_name: ${{ matrix.package.registryName }}-${{ env.PACKAGE_VERSION }}.tgz
|
||||
asset_content_type: application/x-gtar
|
||||
- name: Tangle Release
|
||||
id: tangle_release
|
||||
if: env.PACKAGE_VERSION != env.PUBLISHED_VERSION
|
||||
uses: iotaledger/gh-tangle-release@v0.5.2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
IOTA_SEED: ${{ secrets.IOTA_SEED }}
|
||||
with:
|
||||
tag_name: ${{ matrix.package.name }}-v${{ env.PACKAGE_VERSION }}
|
||||
comment: "[Test] Release ${{ matrix.package.name }} v${{ env.PACKAGE_VERSION }} [npmjs.com]"
|
||||
@@ -1,75 +0,0 @@
|
||||
name: build smoke tests with prod
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
via-prod:
|
||||
runs-on: ${{ matrix.platform }}
|
||||
timeout-minutes: 40
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [ubuntu-latest, macos-latest, windows-latest]
|
||||
example: [react/gatsby-themed-site, react/create-react-app, react/next.js, vanilla/monolith, vue/quasar-app, svelte/svelte-app]
|
||||
include:
|
||||
- platform: ubuntu-latest
|
||||
CARGO_HOME: ~/.cargo
|
||||
- platform: macos-latest
|
||||
CARGO_HOME: ~/.cargo
|
||||
- platform: windows-latest
|
||||
CARGO_HOME: ~/.cargo
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: install rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
- name: install webkit2gtk (ubuntu only)
|
||||
if: matrix.platform == 'ubuntu-latest'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y webkit2gtk-4.0
|
||||
- name: cache rust bin
|
||||
id: cache_rust_bin
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{ format('{0}/bin/', matrix.CARGO_HOME) }}
|
||||
key: ${{ runner.OS }}-build-bin-${{ hashFiles('**/Cargo.toml') }}-
|
||||
- name: cache rust registry/index
|
||||
id: cache_rust_reg_index
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{ format('{0}/registry/index/', matrix.CARGO_HOME) }}
|
||||
key: ${{ runner.OS }}-build-reg-index-${{ hashFiles('**/Cargo.toml') }}-
|
||||
- name: cache rust registry/cache
|
||||
id: cache_rust_reg_cache
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{ format('{0}/registry/cache/', matrix.CARGO_HOME) }}
|
||||
key: ${{ runner.OS }}-build-reg-cache-${{ hashFiles('**/Cargo.toml') }}-
|
||||
- run: cargo install tauri-bundler --force
|
||||
# if we pull from cache, this will be skipped, but force just in case cache has an issue but it does exist
|
||||
if: steps.cache_rust_bin.outputs.cache-hit != 'true' || steps.cache_rust_reg_index.outputs.cache-hit != 'true' || steps.cache_rust_reg_cache.outputs.cache-hit != 'true'
|
||||
- name: cache node modules
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{ format('examples/{0}/node_modules', matrix.example) }}
|
||||
key: ${{ runner.OS }}-build-${{ hashFiles(format('examples/{0}/yarn.lock', matrix.example)) }}
|
||||
restore-keys: ${{ runner.OS }}-build-${{ env.cache-name }}-
|
||||
- name: install via yarn
|
||||
run: |
|
||||
cd ./examples/${{ matrix.example }}
|
||||
yarn
|
||||
- name: build example
|
||||
run: |
|
||||
cd ./examples/${{ matrix.example }}
|
||||
yarn build
|
||||
- name: yarn tauri build
|
||||
run: |
|
||||
cd ./examples/${{ matrix.example }}
|
||||
yarn tauri:prod:build
|
||||
@@ -1,106 +0,0 @@
|
||||
name: build smoke tests with source
|
||||
|
||||
on: pull_request
|
||||
|
||||
jobs:
|
||||
via-source:
|
||||
runs-on: ${{ matrix.platform }}
|
||||
timeout-minutes: 40
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [ubuntu-latest, macos-latest, windows-latest]
|
||||
example:
|
||||
- name: GatsbyThemedSite
|
||||
folder: react/gatsby-themed-site
|
||||
executable: gatsby-themed-site-app
|
||||
- name: CRAApp
|
||||
folder: react/create-react-app
|
||||
executable: app
|
||||
- name: NextjsApp
|
||||
folder: react/next.js
|
||||
executable: app
|
||||
- name: VanillajsMonolithApp
|
||||
folder: vanillajs/monolith
|
||||
executable: app
|
||||
- name: quasar-app
|
||||
folder: vue/quasar-app
|
||||
executable: app
|
||||
- name: svelte-app
|
||||
folder: svelte/svelte-app
|
||||
executable: app
|
||||
include:
|
||||
- platform: ubuntu-latest
|
||||
CARGO_HOME: ~/.cargo
|
||||
releaseFolder: target/release/bundle/deb
|
||||
ext: _0.1.0_amd64.deb
|
||||
- platform: macos-latest
|
||||
CARGO_HOME: ~/.cargo
|
||||
releaseFolder: target/release/bundle/osx
|
||||
ext: .app
|
||||
- platform: windows-latest
|
||||
CARGO_HOME: ~/.cargo
|
||||
releaseFolder: target/release
|
||||
ext: .x86.msi
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: install rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
- name: install webkit2gtk (ubuntu only)
|
||||
if: matrix.platform == 'ubuntu-latest'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y webkit2gtk-4.0
|
||||
- name: cache rust bin
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{ format('{0}/bin/', matrix.CARGO_HOME) }}
|
||||
key: ${{ runner.OS }}-build-bin-${{ hashFiles('**/Cargo.toml') }}-
|
||||
- name: cache rust registry/index
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{ format('{0}/registry/index/', matrix.CARGO_HOME) }}
|
||||
key: ${{ runner.OS }}-build-reg-index-${{ hashFiles('**/Cargo.toml') }}-
|
||||
- name: cache rust registry/cache
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{ format('{0}/registry/cache/', matrix.CARGO_HOME) }}
|
||||
key: ${{ runner.OS }}-build-reg-cache-${{ hashFiles('**/Cargo.toml') }}-
|
||||
- name: build rust
|
||||
run: cargo build
|
||||
env:
|
||||
TAURI_DIST_DIR: ../../test/fixture/dist
|
||||
TAURI_DIR: ../test/fixture/src-tauri
|
||||
- run: cargo install --path ./cli/tauri-bundler --force
|
||||
- name: install cli deps via yarn
|
||||
run: |
|
||||
cd ./cli/tauri.js
|
||||
yarn
|
||||
- name: cache node modules
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{ format('examples/{0}/node_modules', matrix.example.folder) }}
|
||||
key: ${{ runner.OS }}-build-${{ hashFiles(format('examples/{0}/yarn.lock', matrix.example.folder)) }}
|
||||
restore-keys: ${{ runner.OS }}-build-${{ env.cache-name }}-
|
||||
- name: install via yarn
|
||||
run: |
|
||||
cd ./examples/${{ matrix.example.folder }}
|
||||
yarn
|
||||
- name: build example
|
||||
run: |
|
||||
cd ./examples/${{ matrix.example.folder }}
|
||||
yarn build
|
||||
- name: yarn tauri build
|
||||
run: |
|
||||
cd ./examples/${{ matrix.example.folder }}
|
||||
yarn tauri:source:build
|
||||
- uses: actions/upload-artifact@v1
|
||||
if: success()
|
||||
with:
|
||||
name: ${{ matrix.example.name }}(${{ matrix.platform }})
|
||||
path: ${{ format('./examples/{0}/src-tauri/{1}/{2}{3}', matrix.example.folder, matrix.releaseFolder, matrix.example.executable, matrix.ext ) }}
|
||||
@@ -1,73 +0,0 @@
|
||||
name: test library
|
||||
|
||||
on: pull_request
|
||||
|
||||
jobs:
|
||||
build-tauri-core:
|
||||
runs-on: ${{ matrix.platform }}
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [ubuntu-latest, macos-latest, windows-latest]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: install stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
- name: install webkit2gtk (ubuntu only)
|
||||
if: matrix.platform == 'ubuntu-latest'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y webkit2gtk-4.0
|
||||
- name: build
|
||||
run: |
|
||||
cd ./tauri
|
||||
cargo build
|
||||
env:
|
||||
TAURI_DIST_DIR: ../../test/fixture/dist
|
||||
TAURI_DIR: ../test/fixture/src-tauri
|
||||
- name: test
|
||||
run: |
|
||||
cargo test
|
||||
env:
|
||||
TAURI_DIST_DIR: ../../test/fixture/dist
|
||||
TAURI_DIR: ../test/fixture/src-tauri
|
||||
|
||||
build-tauri-bundler:
|
||||
runs-on: ${{ matrix.platform }}
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [ubuntu-latest, macos-latest, windows-latest]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: install stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
- name: build
|
||||
run: |
|
||||
cd ./cli/tauri-bundler
|
||||
cargo build
|
||||
|
||||
test-tauri-js-cli:
|
||||
runs-on: ${{ matrix.platform }}
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [ubuntu-latest, macos-latest, windows-latest]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: test
|
||||
timeout-minutes: 15
|
||||
run: |
|
||||
cd ./cli/tauri.js
|
||||
yarn
|
||||
yarn test
|
||||
81
examples/react/create-react-app/.gitignore
vendored
81
examples/react/create-react-app/.gitignore
vendored
@@ -1,81 +0,0 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Typescript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
/.vs
|
||||
.DS_Store
|
||||
.Thumbs.db
|
||||
*.sublime*
|
||||
.idea
|
||||
debug.log
|
||||
package-lock.json
|
||||
.vscode/settings.json
|
||||
*/.vscode/
|
||||
proptest-regressions/
|
||||
TODO.md
|
||||
|
||||
# Tauri output
|
||||
/bundle.json
|
||||
/config.json
|
||||
/src-tauri
|
||||
|
||||
# rust compiled folders
|
||||
target
|
||||
|
||||
# Cargo lock for libs
|
||||
Cargo.lock
|
||||
|
||||
/cli/tauri.js/test/jest/tmp
|
||||
|
||||
# doing this because of how our tests currently (naively) drop the tauri.conf.js in that folder
|
||||
# todo: needs a proper fic
|
||||
/cli/tauri.js/tauri.conf.js
|
||||
@@ -1,28 +0,0 @@
|
||||
@echo OFF
|
||||
echo "Setting up enviromental Variables"
|
||||
|
||||
rem check script execution directory vs script directory.
|
||||
|
||||
IF "%cd%\"=="%~dp0" (
|
||||
GOTO exitnodir
|
||||
)
|
||||
|
||||
rem setup relative paths from root folder
|
||||
set "TAURI_DIST_DIR=%~1tauri\test\fixture\dist"
|
||||
set "TAURI_DIR=%~1tauri\test\fixture\src-tauri"
|
||||
rem convert relative path to absolute path and re-set it into the enviroment var
|
||||
for /F "delims=" %%F IN ("%TAURI_DIST_DIR%") DO SET "TAURI_DIST_DIR=%%~fF"
|
||||
for /F "delims=" %%F IN ("%TAURI_DIR%") DO SET "TAURI_DIR=%%~fF"
|
||||
|
||||
if NOT EXIST %TAURI_DIR% GOTO exitnodir
|
||||
if NOT EXIST %TAURI_DIST_DIR% GOTO exitnodir
|
||||
|
||||
GOTO exitfine
|
||||
|
||||
:exitnodir
|
||||
echo "Variables are not setup properly. Please run from Tauri Root directory"
|
||||
@EXIT /B 1
|
||||
|
||||
:exitfine
|
||||
echo "Variables set, ready to work!"
|
||||
@EXIT /B 0
|
||||
@@ -1,17 +0,0 @@
|
||||
Write-Output "Setting up enviromental Variables"
|
||||
# setup relative paths
|
||||
$dist_path = "tauri\test\fixture\dist"
|
||||
$src_path = "tauri\test\fixture\src-tauri"
|
||||
|
||||
# check to see if path variables are directories
|
||||
if ((Test-Path $dist_path -PathType Any) -Or (Test-Path $src_path -PathType Any)) {
|
||||
# convert relative paths to absolute paths.
|
||||
# put these absolute paths in enviromental variables
|
||||
$env:TAURI_DIST_DIR = Resolve-Path $dist_path
|
||||
$env:TAURI_DIR = Resolve-Path $src_path
|
||||
Write-Output "Variables set, ready to work!"
|
||||
|
||||
}
|
||||
else {
|
||||
Write-Output "Variables are not setup properly. Please run from Tauri Root directory"
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
# Note: Script must be run like this `. .init_env.sh` to setup variables for your current shell
|
||||
# define relative paths
|
||||
|
||||
DistPath='tauri/test/fixture/dist'
|
||||
SrcPath='tauri/test/fixture/src-tauri'
|
||||
|
||||
echo "Setting up enviroment Variables"
|
||||
|
||||
# check if relative paths exist
|
||||
if [ -d "${DistPath}" ]||[ -d "${SrcPath}" ]
|
||||
then
|
||||
# Convert to absolute paths
|
||||
DistPath="$(cd "${DistPath}" && pwd -P)"
|
||||
SrcPath="$(cd "${SrcPath}" && pwd -P)"
|
||||
|
||||
# export enviromental variables
|
||||
export TAURI_DIST_DIR=${DistPath}
|
||||
export TAURI_DIR=${SrcPath}
|
||||
echo "Variables set, ready to work!"
|
||||
|
||||
else
|
||||
# if directories don't exist then exit script and tell user run script in root dir.
|
||||
echo "Error: Variables are not setup properly. Please run from Tauri Root directory '. .scripts/init_env.sh'"
|
||||
fi
|
||||
@@ -1,7 +0,0 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"tauri",
|
||||
"tauri-api",
|
||||
"tauri-updater",
|
||||
"tauri-utils"
|
||||
]
|
||||
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 - Present Tauri Apps Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,164 +0,0 @@
|
||||
# tauri
|
||||
<img align="right" src="app-icon.png" height="120" width="120">
|
||||
|
||||
## A fresh take on creating cross-platform apps.
|
||||
[](https://github.com/tauri-apps/tauri/tree/dev)
|
||||
[](https://discord.gg/SpmNs4S)
|
||||
[](https://dev.to/tauri)
|
||||
|
||||

|
||||
[](https://github.com/tauri-apps/tauri/wiki)
|
||||
|
||||
[](https://good-labs.github.io/greater-good-affirmation)
|
||||
[](https://opencollective.com/tauri)
|
||||
|
||||
|
||||
Tauri is a tool for building tiny, blazing fast binaries for all major desktop platforms. You can use any front-end framework that compiles to HTML,JS and CSS for building your interface.
|
||||
|
||||
| Component | Version | Lin | Win | Mac |
|
||||
|-----------|---------|-----|-----|-----|
|
||||
| tauri.js CLI |  |✅|✅|✅|
|
||||
| tauri core |  |✅|✅|✅|
|
||||
| tauri bundler |  |✅|✅|✅ |
|
||||
|
||||
## Who Tauri is For
|
||||
Because of the way Tauri has been built and can be extended, developers
|
||||
are able to interface not only with the entire Rust ecosystem, but also
|
||||
with many other programming languages. Being freed of the heaviest thing
|
||||
in the universe and the many shortcomings of server-side Javascript
|
||||
suddenly opens up whole new avenues for high-performance, security-focused
|
||||
applications that need the purebred power, agility and community
|
||||
acceptance of a low-level language.
|
||||
|
||||
We expect to witness an entire new class of applications being built with
|
||||
Tauri. From a simple calender to locally crunching massive realtime
|
||||
feeds at particle colliders or even mesh-network based distributed message-
|
||||
passing ecosystems - the bar has been raised and gauntlet thrown.
|
||||
|
||||
What will you make?
|
||||
|
||||
## 4 Reasons to consider Tauri
|
||||
- **BUNDLE SIZE** of a vanilla Tauri app is less than 3 MB - about 140 MB smaller than what you get with Electron.
|
||||
- **MEMORY FOOTPRINT** is less than half of the size of an Electron app built from the same codebase.
|
||||
- **SECURITY** is Tauri's biggest priority and we are constantly innovating.
|
||||
- **FLOSS** licensing is regretfully impossible with downstream Chromium consumers, like Electron. Sources: [0](https://lists.gnu.org/archive/html/libreplanet-discuss/2017-01/msg00056.html) [1](https://lists.gnu.org/archive/html/directory-discuss/2017-12/msg00008.html) [2](https://lists.gnu.org/archive/html/libreplanet-discuss/2019-02/msg00001.html)
|
||||
|
||||
## Technical Details
|
||||
Tauri has five major components:
|
||||
- [Node.js CLI](https://github.com/tauri-apps/tauri/tree/dev/cli/tauri.js) for creating, developing and building apps
|
||||
- [Rust Core](https://github.com/tauri-apps/tauri/tree/dev/tauri) for binding to the low level WEBVIEW and providing a tree-shakeable API
|
||||
- [Rust Bundler](https://github.com/tauri-apps/tauri/tree/dev/cli/tauri-bundler) for manufacturing the final binaries
|
||||
- [Rust Bindings](https://github.com/Boscop/web-view) for Webviews
|
||||
- [Webview](https://github.com/Boscop/web-view/tree/master/webview-sys)
|
||||
Low level library for creating and interfacing with OS "native" webviews
|
||||
|
||||
The user interface in Tauri apps currently leverages Cocoa/WebKit on macOS,
|
||||
gtk-webkit2 on Linux and MSHTML (IE10/11) or Webkit via Edge on Windows.
|
||||
**Tauri** is based on the MIT licensed prior work known as
|
||||
[webview](https://github.com/zserge/webview).
|
||||
|
||||
The default binding to the underlying webview library currently uses Rust,
|
||||
but other languages like Golang or Python (and many others) are possible
|
||||
(and only a PR away).
|
||||
|
||||
The combination of power, safety and usability are why we chose Rust to be
|
||||
the default binding for Tauri. It is our intention to provide the most safe
|
||||
and performant native app experience (for devs and app consumers), out of
|
||||
the box.
|
||||
|
||||
#### App Bundles
|
||||
- [x] App Icons
|
||||
- [x] Build on MacOS (.app, .dmg coming soon)
|
||||
- [x] Build on Linux (.deb, AppImage coming soon)
|
||||
- [x] Build on Windows (.exe, .msi coming soon)
|
||||
- [ ] App Signing
|
||||
- [ ] Self Updater (WIP)
|
||||
- [ ] Frameless Mode
|
||||
- [ ] Transparent Mode
|
||||
- [ ] Multiwindow Mode
|
||||
- [ ] Tray (coming soon)
|
||||
- [x] Copy Buffer
|
||||
|
||||
#### API
|
||||
- [x] bridge - enable fast bridge
|
||||
- [x] event - enable binding to message
|
||||
- [x] execute - STDOUT Passthrough with Command Invocation
|
||||
- [x] listFiles - list files in a directory
|
||||
- [x] open - open link in a browser
|
||||
- [x] readBinaryFile - read binary file from local filesystem
|
||||
- [x] readTextFile - read text file from local filesystem
|
||||
- [x] setTitle - set the window title
|
||||
- [x] writeFile - write file to local filesystem
|
||||
- [x] API Spec
|
||||
- [x] Documentation (WIP)
|
||||
|
||||
### Security Features
|
||||
- [x] localhost-free mode (:fire:)
|
||||
- [x] Dynamic ahead of Time Compilation (dAoT) with functional tree-shaking
|
||||
- [x] functional Address Space Layout Randomization
|
||||
- [x] OTP salting of function names and messages at runtime
|
||||
- [x] CSP Injection
|
||||
- [ ] Frida-based harness for Post-Binary Analysis
|
||||
|
||||
### Comparison between Tauri and Electron
|
||||
|
||||
| | Tauri | Electron |
|
||||
|--|--------|----------|
|
||||
| Binary Size MacOS | 0.6 MB | 47.7 MB |
|
||||
| Memory Consumption MacOS | 13 MB | 34.1 MB |
|
||||
| Interface Service Provider | Varies | Chromium |
|
||||
| Backend Binding | Rust | Node.js (ECMAScript) |
|
||||
| Underlying Engine | C/C++ | V8 (C/C++) |
|
||||
| FLOSS | Yes | No |
|
||||
| Multithreading | Yes | No |
|
||||
| Bytecode Delivery | Yes | No |
|
||||
| Can Render PDF | Yes | No |
|
||||
| Multiple Windows | Soon | Yes |
|
||||
| GPU Access | Yes | Yes |
|
||||
| Auto Updater | Soon | Yes (1) |
|
||||
| Cross Platform | Yes | Yes |
|
||||
| Custom App Icon | Yes | Yes |
|
||||
| Windows Binary | Yes | Yes |
|
||||
| MacOS Binary | Yes | Yes |
|
||||
| Linux Binary | Yes | Yes |
|
||||
| iOS Binary | Soon | No |
|
||||
| Android Binary | Soon | No |
|
||||
| Localhost Server | Yes | Yes |
|
||||
| No localhost option | Yes | No |
|
||||
| Desktop Tray | Soon | No |
|
||||
|
||||
#### Notes
|
||||
1) Electron has no native auto updater on Linux, but is offered by electron-packager
|
||||
|
||||
## Organization
|
||||
Tauri aims to be a sustainable collective based on principles that guide [sustainable
|
||||
free and open software communities](https://sfosc.org). You can get involved in many ways.
|
||||
|
||||
This has been done with our best attempt at due diligence and in
|
||||
respect of the original authors. Thankyou - this project would never have
|
||||
been possible without your amazing contribution to open-source and we are
|
||||
honoured to carry the torch further. Of special note:
|
||||
- [zserge](https://github.com/zserge) for the original webview approach and
|
||||
go bindings
|
||||
- [Boscop](https://github.com/Boscop) for the Rust Bindings
|
||||
- [Burtonago](https://github.com/burtonageo) for the Cargo Bundle prototype
|
||||
|
||||
## Contributing
|
||||
Please make sure to read the [Contributing Guide](./.github/CONTRIBUTING.md)
|
||||
before making a pull request.
|
||||
|
||||
Thank you to all the people who already contributed to Tauri!
|
||||
|
||||
Special thanks to the development team at Volentix Labs for the encouragement and support in the early phases of Tauri, notably Rhys Parry and Gregory Luneau. Also a warm thanks to the incubation period at the Quasar Framework and specifically Razvan Stoenescu for believing in Tauri from the beginning.
|
||||
|
||||
## Semver
|
||||
**tauri** is following [Semantic Versioning 2.0](https://semver.org/).
|
||||
|
||||
## Licenses
|
||||
Code: (c) 2015 - present - Daniel Thompson-Yvetot, Lucas Nogueira, Tensor, Boscop, Serge Zaitsev, George Burton and all the other amazing contributors.
|
||||
|
||||
MIT or MIT/Apache where applicable.
|
||||
|
||||
Logo: CC-BY-NC-ND
|
||||
- Original Tauri Logo Design by [Daniel Thompson-Yvetot](https://github.com/nothingismagick)
|
||||
@@ -1,19 +0,0 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| > 1.0 | :white_check_mark: |
|
||||
| < 1.0 | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you have found a potential security threat, vulnerability or exploit in Tauri
|
||||
or one of its upstream dependencies, please DON’T create a pull-request, DON’T
|
||||
file an issue on GitHub, DON’T mention it on Discord and DON’T create a forum thread.
|
||||
|
||||
We will be adding contact information to this page very soon.
|
||||
|
||||
At the current time we do not have the financial ability to reward bounties,
|
||||
but in extreme cases will at our discretion consider a reward.
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 208 KiB |
@@ -1,4 +0,0 @@
|
||||
target
|
||||
.DS_Store
|
||||
*.rs.bk
|
||||
*~
|
||||
@@ -1,58 +0,0 @@
|
||||
workspace = {}
|
||||
|
||||
[package]
|
||||
name = "tauri-bundler"
|
||||
version = "0.4.0"
|
||||
authors = ["George Burton <burtonageo@gmail.com>", "Lucas Fernandes Gonçalves Nogueira <lucas@quasar.dev>", "Daniel Thompson-Yvetot <denjell@sfosc.org>", "Tensor Programming <tensordeveloper@gmail.com>"]
|
||||
license = "MIT/Apache-2.0"
|
||||
keywords = ["bundle", "cargo", "tauri"]
|
||||
repository = "https://github.com/tauri-apps/tauri"
|
||||
description = "Wrap rust executables in OS-specific app bundles for Tauri"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
ar = "0.8.0"
|
||||
chrono = "0.4"
|
||||
clap = "^2"
|
||||
dirs = "2.0.2"
|
||||
error-chain = "0.12"
|
||||
glob = "0.3.0"
|
||||
icns = "0.3"
|
||||
image = "0.22.5"
|
||||
libflate = "0.1"
|
||||
md5 = "0.7.0"
|
||||
msi = "0.2"
|
||||
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
strsim = "0.10.0"
|
||||
tar = "0.4"
|
||||
target_build_utils = "0.3"
|
||||
term = "0.6.1"
|
||||
toml = "0.5.6"
|
||||
uuid = { version = "0.8", features = ["v5"] }
|
||||
walkdir = "2"
|
||||
|
||||
tauri-utils = {version = "0.4", path = "../../tauri-utils"}
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
attohttpc = { version = "0.11.1" }
|
||||
regex = { version = "1" }
|
||||
|
||||
[target.'cfg(not(target_os = "linux"))'.dependencies]
|
||||
handlebars = { version = "3.0" }
|
||||
lazy_static = { version = "1.4" }
|
||||
zip = { version = "0.5" }
|
||||
sha2 = { version = "0.8" }
|
||||
hex = { version = "0.4" }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3"
|
||||
|
||||
[[bin]]
|
||||
name = "cargo-tauri-bundler"
|
||||
path = "src/main.rs"
|
||||
|
||||
[features]
|
||||
appimage = []
|
||||
ios = []
|
||||
dmg = []
|
||||
@@ -1,30 +0,0 @@
|
||||
Copyright 2017 Cargo-Bundle developers
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
-------
|
||||
|
||||
Copyright 2019 Tauri Apps Organization
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
Copyright 2017 Cargo-Bundle developers
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
----
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Tauri-Apps Organization
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,140 +0,0 @@
|
||||
# Cargo Tauri Bundle
|
||||
|
||||
Wrap Rust executables in OS-specific app bundles
|
||||
|
||||
## About
|
||||
|
||||
`cargo tauri-bundle` is a tool used to generate installers or app bundles for GUI
|
||||
executables built with `cargo`. It can create `.app` bundles for Mac OS X and
|
||||
iOS, `.deb` packages for Linux, and `.msi` installers for Windows (note however
|
||||
that iOS and Windows support is still experimental). Support for creating
|
||||
`.rpm` packages (for Linux) and `.apk` packages (for Android) is still pending.
|
||||
|
||||
To install `cargo tauri-bundle`, run `cargo install cargo-tauri-bundle`. This will add the most recent version of `cargo-bundle`
|
||||
published to [crates.io](https://crates.io/crates/cargo-bundle) as a subcommand to your default `cargo` installation.
|
||||
|
||||
To start using `cargo tauri-bundle`, add a `[package.metadata.bundle]` section to your project's `Cargo.toml` file. This
|
||||
section describes various attributes of the generated bundle, such as its name, icon, description, copyright, as well
|
||||
as any packaging scripts you need to generate extra data. The full manifest format is described below.
|
||||
|
||||
To build a bundle for the OS you're on, simply run `cargo tauri-bundle` in your
|
||||
project's directory (where the `Cargo.toml` is placed). If you would like to
|
||||
bundle a release build, you must add the `--release` flag to your call. To
|
||||
cross-compile and bundle an application for another OS, add an appropriate
|
||||
`--target` flag, just as you would for `cargo build`.
|
||||
|
||||
## Bundle manifest format
|
||||
|
||||
There are several fields in the `[package.metadata.bundle]` section.
|
||||
|
||||
### General settings
|
||||
|
||||
These settings apply to bundles for all (or most) OSes.
|
||||
|
||||
* `name`: The name of the built application. If this is not present, then it will use the `name` value from
|
||||
your `Cargo.toml` file.
|
||||
* `identifier`: [REQUIRED] A string that uniquely identifies your application,
|
||||
in reverse-DNS form (for example, `"com.example.appname"` or
|
||||
`"io.github.username.project"`). For OS X and iOS, this is used as the
|
||||
bundle's `CFBundleIdentifier` value; for Windows, this is hashed to create
|
||||
an application GUID.
|
||||
* `icon`: [OPTIONAL] The icons used for your application. This should be an array of file paths or globs (with images
|
||||
in various sizes/formats); `cargo-bundle` will automatically convert between image formats as necessary for
|
||||
different platforms. Supported formats include ICNS, ICO, PNG, and anything else that can be decoded by the
|
||||
[`image`](https://crates.io/crates/image) crate. Icons intended for high-resolution (e.g. Retina) displays
|
||||
should have a filename with `@2x` just before the extension (see example below).
|
||||
* `version`: [OPTIONAL] The version of the application. If this is not present, then it will use the `version`
|
||||
value from your `Cargo.toml` file.
|
||||
* `resources`: [OPTIONAL] List of files or directories which will be copied to the resources section of the
|
||||
bundle. Globs are supported.
|
||||
* `script`: [OPTIONAL] This is a reserved field; at the moment it is not used for anything, but may be used to
|
||||
run scripts while packaging the bundle (e.g. download files, compress and encrypt, etc.).
|
||||
* `copyright`: [OPTIONAL] This contains a copyright string associated with your application.
|
||||
* `category`: [OPTIONAL] What kind of application this is. This can
|
||||
be a human-readable string (e.g. `"Puzzle game"`), or a Mac OS X
|
||||
LSApplicationCategoryType value
|
||||
(e.g. `"public.app-category.puzzle-games"`), or a GNOME desktop
|
||||
file category name (e.g. `"LogicGame"`), and `cargo-bundle` will
|
||||
automatically convert as needed for different platforms.
|
||||
* `short_description`: [OPTIONAL] A short, one-line description of the application. If this is not present, then it
|
||||
will use the `description` value from your `Cargo.toml` file.
|
||||
* `long_description`: [OPTIONAL] A longer, multi-line description of the application.
|
||||
|
||||
### Debian-specific settings
|
||||
|
||||
These settings are used only when bundling `deb` packages.
|
||||
|
||||
* `deb_depends`: A list of strings indicating other packages (e.g. shared
|
||||
libraries) that this package depends on to be installed. If present, this
|
||||
forms the `Depends:` field of the `deb` package control file.
|
||||
|
||||
### Mac OS X-specific settings
|
||||
|
||||
These settings are used only when bundling `osx` packages.
|
||||
|
||||
* `osx_frameworks`: A list of strings indicating any Mac OS X frameworks that
|
||||
need to be bundled with the app. Each string can either be the name of a
|
||||
framework (without the `.framework` extension, e.g. `"SDL2"`), in which case
|
||||
`cargo-bundle` 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-bundle` copy the specified frameworks into the OS X app bundle (under
|
||||
`Foobar.app/Contents/Frameworks/`); you are still responsible for (1)
|
||||
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), and (2) embedding the correct rpath in your binary
|
||||
(e.g. by running `install_name_tool -add_rpath
|
||||
"@executable_path/../Frameworks" path/to/binary` after compiling).
|
||||
* `osx_minimum_system_version`: A version string indicating the minimum Mac OS
|
||||
X version that the bundled 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` (or whatever version number
|
||||
you want) to ensure that the compiled binary has the same minimum version.
|
||||
|
||||
### Example `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[package]
|
||||
name = "example"
|
||||
# ...other fields...
|
||||
|
||||
[package.metadata.bundle]
|
||||
name = "ExampleApplication"
|
||||
identifier = "com.doe.exampleapplication"
|
||||
icon = ["32x32.png", "128x128.png", "128x128@2x.png"]
|
||||
version = "1.0.0"
|
||||
resources = ["assets", "images/**/*.png", "secrets/public_key.txt"]
|
||||
copyright = "Copyright (c) Jane Doe 2016. All rights reserved."
|
||||
category = "Developer Tool"
|
||||
short_description = "An example application."
|
||||
long_description = """
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
|
||||
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
|
||||
enim ad minim veniam, quis nostrud exercitation ullamco laboris
|
||||
nisi ut aliquip ex ea commodo consequat.
|
||||
"""
|
||||
deb_depends = ["libgl1-mesa-glx", "libsdl2-2.0-0 (>= 2.0.5)"]
|
||||
osx_frameworks = ["SDL2"]
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
`cargo-tauri-bundle` has ambitions to be inclusive project and welcome contributions from anyone. Please abide by the Rust
|
||||
code of conduct.
|
||||
|
||||
## Status
|
||||
|
||||
Very early alpha. Expect the format of the `[package.metadata.bundle]` section to change, and there is no guarantee of
|
||||
stability.
|
||||
|
||||
## License
|
||||
(c) 2017 - present, George Burton, Lucas Fernandes Gonçalves Nogueira, Daniel Thompson-Yvetot, Razvan Stoenescu
|
||||
|
||||
This program is licensed either under the terms of the
|
||||
[Apache Software License](http://www.apache.org/licenses/LICENSE-2.0), or the
|
||||
[MIT License](https://opensource.org/licenses/MIT).
|
||||
|
||||
-> note, for bundle_dmg we have included a BSD 3 licenced binary `seticon`.
|
||||
https://github.com/sveinbjornt/osxiconutils/blob/master/seticon.m
|
||||
`tools/rust/cargo-tauri-bundle/src/bundle/templates/seticon`
|
||||
@@ -1,63 +0,0 @@
|
||||
#[cfg(feature = "appimage")]
|
||||
mod appimage_bundle;
|
||||
mod category;
|
||||
mod common;
|
||||
mod deb_bundle;
|
||||
#[cfg(feature = "dmg")]
|
||||
mod dmg_bundle;
|
||||
#[cfg(feature = "ios")]
|
||||
mod ios_bundle;
|
||||
#[cfg(target_os = "windows")]
|
||||
mod msi_bundle;
|
||||
mod osx_bundle;
|
||||
mod path_utils;
|
||||
mod rpm_bundle;
|
||||
mod settings;
|
||||
#[cfg(target_os = "windows")]
|
||||
mod wix;
|
||||
|
||||
pub use self::common::{print_error, print_finished};
|
||||
pub use self::settings::{BuildArtifact, PackageType, Settings};
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub fn bundle_project(settings: Settings) -> crate::Result<Vec<PathBuf>> {
|
||||
let mut paths = Vec::new();
|
||||
for package_type in settings.package_types()? {
|
||||
paths.append(&mut match package_type {
|
||||
PackageType::OsxBundle => osx_bundle::bundle_project(&settings)?,
|
||||
#[cfg(feature = "ios")]
|
||||
PackageType::IosBundle => ios_bundle::bundle_project(&settings)?,
|
||||
// use dmg bundler
|
||||
// PackageType::OsxBundle => dmg_bundle::bundle_project(&settings)?,
|
||||
#[cfg(target_os = "windows")]
|
||||
PackageType::WindowsMsi => msi_bundle::bundle_project(&settings)?,
|
||||
// force appimage on linux
|
||||
// PackageType::Deb => appimage_bundle::bundle_project(&settings)?,
|
||||
PackageType::Deb => deb_bundle::bundle_project(&settings)?,
|
||||
PackageType::Rpm => rpm_bundle::bundle_project(&settings)?,
|
||||
#[cfg(feature = "appimage")]
|
||||
PackageType::AppImage => appimage_bundle::bundle_project(&settings)?,
|
||||
#[cfg(feature = "dmg")]
|
||||
PackageType::Dmg => dmg_bundle::bundle_project(&settings)?,
|
||||
});
|
||||
}
|
||||
|
||||
settings.copy_resources(settings.project_out_directory())?;
|
||||
settings.copy_binaries(settings.project_out_directory())?;
|
||||
|
||||
Ok(paths)
|
||||
}
|
||||
|
||||
// Check to see if there are icons in the settings struct
|
||||
pub fn check_icons(settings: &Settings) -> crate::Result<bool> {
|
||||
// make a peekable iterator of the icon_files
|
||||
let mut iter = settings.icon_files().peekable();
|
||||
|
||||
// if iter's first value is a None then there are no Icon files in the settings struct
|
||||
if iter.peek().is_none() {
|
||||
Ok(false)
|
||||
} else {
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
use super::common;
|
||||
use super::deb_bundle;
|
||||
use super::path_utils;
|
||||
use crate::ResultExt;
|
||||
use crate::Settings;
|
||||
|
||||
use handlebars::Handlebars;
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::fs::{remove_dir_all, write};
|
||||
use std::path::PathBuf;
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
// Create handlebars template for shell script
|
||||
lazy_static! {
|
||||
static ref HANDLEBARS: Handlebars<'static> = {
|
||||
let mut handlebars = Handlebars::new();
|
||||
|
||||
handlebars
|
||||
.register_template_string("appimage", include_str!("templates/appimage"))
|
||||
.expect("Failed to register template for handlebars");
|
||||
handlebars
|
||||
};
|
||||
}
|
||||
|
||||
// bundle the project.
|
||||
pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
|
||||
// generate the deb binary name
|
||||
let arch = match settings.binary_arch() {
|
||||
"x86" => "i386",
|
||||
"x86_64" => "amd64",
|
||||
other => other,
|
||||
};
|
||||
let package_base_name = format!(
|
||||
"{}_{}_{}",
|
||||
settings.binary_name(),
|
||||
settings.version_string(),
|
||||
arch
|
||||
);
|
||||
let base_dir = settings.project_out_directory().join("bundle/deb");
|
||||
let package_dir = base_dir.join(&package_base_name);
|
||||
if package_dir.exists() {
|
||||
remove_dir_all(&package_dir)
|
||||
.chain_err(|| format!("Failed to remove old {}", package_base_name))?;
|
||||
}
|
||||
|
||||
// generate deb_folder structure
|
||||
deb_bundle::generate_folders(settings, &package_dir)?;
|
||||
|
||||
let _app_dir_path = path_utils::create(
|
||||
settings
|
||||
.project_out_directory()
|
||||
.join(format!("{}.AppDir", settings.binary_name())),
|
||||
true,
|
||||
);
|
||||
|
||||
let upcase = settings.binary_name().to_uppercase();
|
||||
|
||||
// setup data to insert into shell script
|
||||
let mut sh_map = BTreeMap::new();
|
||||
sh_map.insert("app_name", settings.binary_name());
|
||||
sh_map.insert("bundle_name", package_base_name.as_str());
|
||||
sh_map.insert("app_name_uppercase", upcase.as_str());
|
||||
|
||||
// initialize shell script template.
|
||||
let temp = HANDLEBARS
|
||||
.render("appimage", &sh_map)
|
||||
.or_else(|e| Err(e.to_string()))?;
|
||||
let output_path = settings.project_out_directory();
|
||||
|
||||
// create the shell script file in the target/ folder.
|
||||
let sh_file = output_path.join("build_appimage");
|
||||
common::print_bundling(
|
||||
format!(
|
||||
"{:?}",
|
||||
&output_path.join(format!("{}.AppImage", settings.binary_name()))
|
||||
)
|
||||
.as_str(),
|
||||
)?;
|
||||
write(&sh_file, temp).or_else(|e| Err(e.to_string()))?;
|
||||
|
||||
// chmod script for execution
|
||||
Command::new("chmod")
|
||||
.arg("777")
|
||||
.arg(&sh_file)
|
||||
.current_dir(output_path)
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.expect("Failed to chmod script");
|
||||
|
||||
// execute the shell script to build the appimage.
|
||||
Command::new(&sh_file)
|
||||
.current_dir(output_path)
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.expect("Failed to execute shell script");
|
||||
|
||||
Ok(vec![sh_file])
|
||||
}
|
||||
@@ -1,456 +0,0 @@
|
||||
use std::fmt;
|
||||
|
||||
use serde;
|
||||
use strsim;
|
||||
|
||||
const CONFIDENCE_THRESHOLD: f64 = 0.8;
|
||||
|
||||
const OSX_APP_CATEGORY_PREFIX: &str = "public.app-category.";
|
||||
|
||||
// TODO: RIght now, these categories correspond to LSApplicationCategoryType
|
||||
// values for OS X. There are also some additional GNOME registered categories
|
||||
// that don't fit these; we should add those here too.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum AppCategory {
|
||||
Business,
|
||||
DeveloperTool,
|
||||
Education,
|
||||
Entertainment,
|
||||
Finance,
|
||||
Game,
|
||||
ActionGame,
|
||||
AdventureGame,
|
||||
ArcadeGame,
|
||||
BoardGame,
|
||||
CardGame,
|
||||
CasinoGame,
|
||||
DiceGame,
|
||||
EducationalGame,
|
||||
FamilyGame,
|
||||
KidsGame,
|
||||
MusicGame,
|
||||
PuzzleGame,
|
||||
RacingGame,
|
||||
RolePlayingGame,
|
||||
SimulationGame,
|
||||
SportsGame,
|
||||
StrategyGame,
|
||||
TriviaGame,
|
||||
WordGame,
|
||||
GraphicsAndDesign,
|
||||
HealthcareAndFitness,
|
||||
Lifestyle,
|
||||
Medical,
|
||||
Music,
|
||||
News,
|
||||
Photography,
|
||||
Productivity,
|
||||
Reference,
|
||||
SocialNetworking,
|
||||
Sports,
|
||||
Travel,
|
||||
Utility,
|
||||
Video,
|
||||
Weather,
|
||||
}
|
||||
|
||||
impl AppCategory {
|
||||
/// Given a string, returns the `AppCategory` it refers to, or the closest
|
||||
/// string that the user might have intended (if any).
|
||||
pub fn from_str(input: &str) -> Result<AppCategory, Option<&'static str>> {
|
||||
// Canonicalize input:
|
||||
let mut input = input.to_ascii_lowercase();
|
||||
if input.starts_with(OSX_APP_CATEGORY_PREFIX) {
|
||||
input = input.split_at(OSX_APP_CATEGORY_PREFIX.len()).1.to_string();
|
||||
}
|
||||
input = input.replace(" ", "");
|
||||
input = input.replace("-", "");
|
||||
|
||||
// Find best match:
|
||||
let mut best_confidence = 0.0;
|
||||
let mut best_category: Option<AppCategory> = None;
|
||||
for &(string, category) in CATEGORY_STRINGS.iter() {
|
||||
if input == string {
|
||||
return Ok(category);
|
||||
}
|
||||
let confidence = strsim::jaro_winkler(&input, string);
|
||||
if confidence >= CONFIDENCE_THRESHOLD && confidence > best_confidence {
|
||||
best_confidence = confidence;
|
||||
best_category = Some(category);
|
||||
}
|
||||
}
|
||||
Err(best_category.map(AppCategory::canonical))
|
||||
}
|
||||
|
||||
/// Map an AppCategory to the string we recommend to use in Cargo.toml if
|
||||
/// the users misspells the category name.
|
||||
fn canonical(self) -> &'static str {
|
||||
match self {
|
||||
AppCategory::Business => "Business",
|
||||
AppCategory::DeveloperTool => "Developer Tool",
|
||||
AppCategory::Education => "Education",
|
||||
AppCategory::Entertainment => "Entertainment",
|
||||
AppCategory::Finance => "Finance",
|
||||
AppCategory::Game => "Game",
|
||||
AppCategory::ActionGame => "Action Game",
|
||||
AppCategory::AdventureGame => "Adventure Game",
|
||||
AppCategory::ArcadeGame => "Arcade Game",
|
||||
AppCategory::BoardGame => "Board Game",
|
||||
AppCategory::CardGame => "Card Game",
|
||||
AppCategory::CasinoGame => "Casino Game",
|
||||
AppCategory::DiceGame => "Dice Game",
|
||||
AppCategory::EducationalGame => "Educational Game",
|
||||
AppCategory::FamilyGame => "Family Game",
|
||||
AppCategory::KidsGame => "Kids Game",
|
||||
AppCategory::MusicGame => "Music Game",
|
||||
AppCategory::PuzzleGame => "Puzzle Game",
|
||||
AppCategory::RacingGame => "Racing Game",
|
||||
AppCategory::RolePlayingGame => "Role-Playing Game",
|
||||
AppCategory::SimulationGame => "Simulation Game",
|
||||
AppCategory::SportsGame => "Sports Game",
|
||||
AppCategory::StrategyGame => "Strategy Game",
|
||||
AppCategory::TriviaGame => "Trivia Game",
|
||||
AppCategory::WordGame => "Word Game",
|
||||
AppCategory::GraphicsAndDesign => "Graphics and Design",
|
||||
AppCategory::HealthcareAndFitness => "Healthcare and Fitness",
|
||||
AppCategory::Lifestyle => "Lifestyle",
|
||||
AppCategory::Medical => "Medical",
|
||||
AppCategory::Music => "Music",
|
||||
AppCategory::News => "News",
|
||||
AppCategory::Photography => "Photography",
|
||||
AppCategory::Productivity => "Productivity",
|
||||
AppCategory::Reference => "Reference",
|
||||
AppCategory::SocialNetworking => "Social Networking",
|
||||
AppCategory::Sports => "Sports",
|
||||
AppCategory::Travel => "Travel",
|
||||
AppCategory::Utility => "Utility",
|
||||
AppCategory::Video => "Video",
|
||||
AppCategory::Weather => "Weather",
|
||||
}
|
||||
}
|
||||
|
||||
/// Map an AppCategory to the closest set of GNOME desktop registered
|
||||
/// categories that matches that category.
|
||||
pub fn gnome_desktop_categories(&self) -> &'static str {
|
||||
match &self {
|
||||
AppCategory::Business => "Office;",
|
||||
AppCategory::DeveloperTool => "Development;",
|
||||
AppCategory::Education => "Education;",
|
||||
AppCategory::Entertainment => "Network;",
|
||||
AppCategory::Finance => "Office;Finance;",
|
||||
AppCategory::Game => "Game;",
|
||||
AppCategory::ActionGame => "Game;ActionGame;",
|
||||
AppCategory::AdventureGame => "Game;AdventureGame;",
|
||||
AppCategory::ArcadeGame => "Game;ArcadeGame;",
|
||||
AppCategory::BoardGame => "Game;BoardGame;",
|
||||
AppCategory::CardGame => "Game;CardGame;",
|
||||
AppCategory::CasinoGame => "Game;",
|
||||
AppCategory::DiceGame => "Game;",
|
||||
AppCategory::EducationalGame => "Game;Education;",
|
||||
AppCategory::FamilyGame => "Game;",
|
||||
AppCategory::KidsGame => "Game;KidsGame;",
|
||||
AppCategory::MusicGame => "Game;",
|
||||
AppCategory::PuzzleGame => "Game;LogicGame;",
|
||||
AppCategory::RacingGame => "Game;",
|
||||
AppCategory::RolePlayingGame => "Game;RolePlaying;",
|
||||
AppCategory::SimulationGame => "Game;Simulation;",
|
||||
AppCategory::SportsGame => "Game;SportsGame;",
|
||||
AppCategory::StrategyGame => "Game;StrategyGame;",
|
||||
AppCategory::TriviaGame => "Game;",
|
||||
AppCategory::WordGame => "Game;",
|
||||
AppCategory::GraphicsAndDesign => "Graphics;",
|
||||
AppCategory::HealthcareAndFitness => "Science;",
|
||||
AppCategory::Lifestyle => "Education;",
|
||||
AppCategory::Medical => "Science;MedicalSoftware;",
|
||||
AppCategory::Music => "AudioVideo;Audio;Music;",
|
||||
AppCategory::News => "Network;News;",
|
||||
AppCategory::Photography => "Graphics;Photography;",
|
||||
AppCategory::Productivity => "Office;",
|
||||
AppCategory::Reference => "Education;",
|
||||
AppCategory::SocialNetworking => "Network;",
|
||||
AppCategory::Sports => "Education;Sports;",
|
||||
AppCategory::Travel => "Education;",
|
||||
AppCategory::Utility => "Utility;",
|
||||
AppCategory::Video => "AudioVideo;Video;",
|
||||
AppCategory::Weather => "Science;",
|
||||
}
|
||||
}
|
||||
|
||||
/// Map an AppCategory to the closest LSApplicationCategoryType value that
|
||||
/// matches that category.
|
||||
pub fn osx_application_category_type(&self) -> &'static str {
|
||||
match &self {
|
||||
AppCategory::Business => "public.app-category.business",
|
||||
AppCategory::DeveloperTool => "public.app-category.developer-tools",
|
||||
AppCategory::Education => "public.app-category.education",
|
||||
AppCategory::Entertainment => "public.app-category.entertainment",
|
||||
AppCategory::Finance => "public.app-category.finance",
|
||||
AppCategory::Game => "public.app-category.games",
|
||||
AppCategory::ActionGame => "public.app-category.action-games",
|
||||
AppCategory::AdventureGame => "public.app-category.adventure-games",
|
||||
AppCategory::ArcadeGame => "public.app-category.arcade-games",
|
||||
AppCategory::BoardGame => "public.app-category.board-games",
|
||||
AppCategory::CardGame => "public.app-category.card-games",
|
||||
AppCategory::CasinoGame => "public.app-category.casino-games",
|
||||
AppCategory::DiceGame => "public.app-category.dice-games",
|
||||
AppCategory::EducationalGame => "public.app-category.educational-games",
|
||||
AppCategory::FamilyGame => "public.app-category.family-games",
|
||||
AppCategory::KidsGame => "public.app-category.kids-games",
|
||||
AppCategory::MusicGame => "public.app-category.music-games",
|
||||
AppCategory::PuzzleGame => "public.app-category.puzzle-games",
|
||||
AppCategory::RacingGame => "public.app-category.racing-games",
|
||||
AppCategory::RolePlayingGame => "public.app-category.role-playing-games",
|
||||
AppCategory::SimulationGame => "public.app-category.simulation-games",
|
||||
AppCategory::SportsGame => "public.app-category.sports-games",
|
||||
AppCategory::StrategyGame => "public.app-category.strategy-games",
|
||||
AppCategory::TriviaGame => "public.app-category.trivia-games",
|
||||
AppCategory::WordGame => "public.app-category.word-games",
|
||||
AppCategory::GraphicsAndDesign => "public.app-category.graphics-design",
|
||||
AppCategory::HealthcareAndFitness => "public.app-category.healthcare-fitness",
|
||||
AppCategory::Lifestyle => "public.app-category.lifestyle",
|
||||
AppCategory::Medical => "public.app-category.medical",
|
||||
AppCategory::Music => "public.app-category.music",
|
||||
AppCategory::News => "public.app-category.news",
|
||||
AppCategory::Photography => "public.app-category.photography",
|
||||
AppCategory::Productivity => "public.app-category.productivity",
|
||||
AppCategory::Reference => "public.app-category.reference",
|
||||
AppCategory::SocialNetworking => "public.app-category.social-networking",
|
||||
AppCategory::Sports => "public.app-category.sports",
|
||||
AppCategory::Travel => "public.app-category.travel",
|
||||
AppCategory::Utility => "public.app-category.utilities",
|
||||
AppCategory::Video => "public.app-category.video",
|
||||
AppCategory::Weather => "public.app-category.weather",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d> serde::Deserialize<'d> for AppCategory {
|
||||
fn deserialize<D: serde::Deserializer<'d>>(deserializer: D) -> Result<AppCategory, D::Error> {
|
||||
deserializer.deserialize_str(AppCategoryVisitor { did_you_mean: None })
|
||||
}
|
||||
}
|
||||
|
||||
struct AppCategoryVisitor {
|
||||
did_you_mean: Option<&'static str>,
|
||||
}
|
||||
|
||||
impl<'d> serde::de::Visitor<'d> for AppCategoryVisitor {
|
||||
type Value = AppCategory;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.did_you_mean {
|
||||
Some(string) => write!(
|
||||
formatter,
|
||||
"a valid app category string (did you mean \"{}\"?)",
|
||||
string
|
||||
),
|
||||
None => write!(formatter, "a valid app category string"),
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_str<E: serde::de::Error>(mut self, value: &str) -> Result<AppCategory, E> {
|
||||
match AppCategory::from_str(value) {
|
||||
Ok(category) => Ok(category),
|
||||
Err(did_you_mean) => {
|
||||
self.did_you_mean = did_you_mean;
|
||||
let unexp = serde::de::Unexpected::Str(value);
|
||||
Err(serde::de::Error::invalid_value(unexp, &self))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const CATEGORY_STRINGS: &[(&str, AppCategory)] = &[
|
||||
("actiongame", AppCategory::ActionGame),
|
||||
("actiongames", AppCategory::ActionGame),
|
||||
("adventuregame", AppCategory::AdventureGame),
|
||||
("adventuregames", AppCategory::AdventureGame),
|
||||
("arcadegame", AppCategory::ArcadeGame),
|
||||
("arcadegames", AppCategory::ArcadeGame),
|
||||
("boardgame", AppCategory::BoardGame),
|
||||
("boardgames", AppCategory::BoardGame),
|
||||
("business", AppCategory::Business),
|
||||
("cardgame", AppCategory::CardGame),
|
||||
("cardgames", AppCategory::CardGame),
|
||||
("casinogame", AppCategory::CasinoGame),
|
||||
("casinogames", AppCategory::CasinoGame),
|
||||
("developer", AppCategory::DeveloperTool),
|
||||
("developertool", AppCategory::DeveloperTool),
|
||||
("developertools", AppCategory::DeveloperTool),
|
||||
("development", AppCategory::DeveloperTool),
|
||||
("dicegame", AppCategory::DiceGame),
|
||||
("dicegames", AppCategory::DiceGame),
|
||||
("education", AppCategory::Education),
|
||||
("educationalgame", AppCategory::EducationalGame),
|
||||
("educationalgames", AppCategory::EducationalGame),
|
||||
("entertainment", AppCategory::Entertainment),
|
||||
("familygame", AppCategory::FamilyGame),
|
||||
("familygames", AppCategory::FamilyGame),
|
||||
("finance", AppCategory::Finance),
|
||||
("fitness", AppCategory::HealthcareAndFitness),
|
||||
("game", AppCategory::Game),
|
||||
("games", AppCategory::Game),
|
||||
("graphicdesign", AppCategory::GraphicsAndDesign),
|
||||
("graphicsanddesign", AppCategory::GraphicsAndDesign),
|
||||
("graphicsdesign", AppCategory::GraphicsAndDesign),
|
||||
("healthcareandfitness", AppCategory::HealthcareAndFitness),
|
||||
("healthcarefitness", AppCategory::HealthcareAndFitness),
|
||||
("kidsgame", AppCategory::KidsGame),
|
||||
("kidsgames", AppCategory::KidsGame),
|
||||
("lifestyle", AppCategory::Lifestyle),
|
||||
("logicgame", AppCategory::PuzzleGame),
|
||||
("medical", AppCategory::Medical),
|
||||
("medicalsoftware", AppCategory::Medical),
|
||||
("music", AppCategory::Music),
|
||||
("musicgame", AppCategory::MusicGame),
|
||||
("musicgames", AppCategory::MusicGame),
|
||||
("news", AppCategory::News),
|
||||
("photography", AppCategory::Photography),
|
||||
("productivity", AppCategory::Productivity),
|
||||
("puzzlegame", AppCategory::PuzzleGame),
|
||||
("puzzlegames", AppCategory::PuzzleGame),
|
||||
("racinggame", AppCategory::RacingGame),
|
||||
("racinggames", AppCategory::RacingGame),
|
||||
("reference", AppCategory::Reference),
|
||||
("roleplaying", AppCategory::RolePlayingGame),
|
||||
("roleplayinggame", AppCategory::RolePlayingGame),
|
||||
("roleplayinggames", AppCategory::RolePlayingGame),
|
||||
("rpg", AppCategory::RolePlayingGame),
|
||||
("simulationgame", AppCategory::SimulationGame),
|
||||
("simulationgames", AppCategory::SimulationGame),
|
||||
("socialnetwork", AppCategory::SocialNetworking),
|
||||
("socialnetworking", AppCategory::SocialNetworking),
|
||||
("sports", AppCategory::Sports),
|
||||
("sportsgame", AppCategory::SportsGame),
|
||||
("sportsgames", AppCategory::SportsGame),
|
||||
("strategygame", AppCategory::StrategyGame),
|
||||
("strategygames", AppCategory::StrategyGame),
|
||||
("travel", AppCategory::Travel),
|
||||
("triviagame", AppCategory::TriviaGame),
|
||||
("triviagames", AppCategory::TriviaGame),
|
||||
("utilities", AppCategory::Utility),
|
||||
("utility", AppCategory::Utility),
|
||||
("video", AppCategory::Video),
|
||||
("weather", AppCategory::Weather),
|
||||
("wordgame", AppCategory::WordGame),
|
||||
("wordgames", AppCategory::WordGame),
|
||||
];
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::AppCategory;
|
||||
|
||||
#[test]
|
||||
fn category_from_string_ok() {
|
||||
// Canonical name of category works:
|
||||
assert_eq!(
|
||||
AppCategory::from_str("Education"),
|
||||
Ok(AppCategory::Education)
|
||||
);
|
||||
assert_eq!(
|
||||
AppCategory::from_str("Developer Tool"),
|
||||
Ok(AppCategory::DeveloperTool)
|
||||
);
|
||||
// Lowercase, spaces, and hypens are fine:
|
||||
assert_eq!(
|
||||
AppCategory::from_str(" puzzle game "),
|
||||
Ok(AppCategory::PuzzleGame)
|
||||
);
|
||||
assert_eq!(
|
||||
AppCategory::from_str("Role-playing game"),
|
||||
Ok(AppCategory::RolePlayingGame)
|
||||
);
|
||||
// Using macOS LSApplicationCategoryType value is fine:
|
||||
assert_eq!(
|
||||
AppCategory::from_str("public.app-category.developer-tools"),
|
||||
Ok(AppCategory::DeveloperTool)
|
||||
);
|
||||
assert_eq!(
|
||||
AppCategory::from_str("public.app-category.role-playing-games"),
|
||||
Ok(AppCategory::RolePlayingGame)
|
||||
);
|
||||
// Using GNOME category name is fine:
|
||||
assert_eq!(
|
||||
AppCategory::from_str("Development"),
|
||||
Ok(AppCategory::DeveloperTool)
|
||||
);
|
||||
assert_eq!(
|
||||
AppCategory::from_str("LogicGame"),
|
||||
Ok(AppCategory::PuzzleGame)
|
||||
);
|
||||
// Using common abbreviations is fine:
|
||||
assert_eq!(
|
||||
AppCategory::from_str("RPG"),
|
||||
Ok(AppCategory::RolePlayingGame)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn category_from_string_did_you_mean() {
|
||||
assert_eq!(AppCategory::from_str("gaming"), Err(Some("Game")));
|
||||
assert_eq!(AppCategory::from_str("photos"), Err(Some("Photography")));
|
||||
assert_eq!(
|
||||
AppCategory::from_str("strategery"),
|
||||
Err(Some("Strategy Game"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn category_from_string_totally_wrong() {
|
||||
assert_eq!(AppCategory::from_str("fhqwhgads"), Err(None));
|
||||
assert_eq!(AppCategory::from_str("WHARRGARBL"), Err(None));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ls_application_category_type_round_trip() {
|
||||
let values = &[
|
||||
"public.app-category.business",
|
||||
"public.app-category.developer-tools",
|
||||
"public.app-category.education",
|
||||
"public.app-category.entertainment",
|
||||
"public.app-category.finance",
|
||||
"public.app-category.games",
|
||||
"public.app-category.action-games",
|
||||
"public.app-category.adventure-games",
|
||||
"public.app-category.arcade-games",
|
||||
"public.app-category.board-games",
|
||||
"public.app-category.card-games",
|
||||
"public.app-category.casino-games",
|
||||
"public.app-category.dice-games",
|
||||
"public.app-category.educational-games",
|
||||
"public.app-category.family-games",
|
||||
"public.app-category.kids-games",
|
||||
"public.app-category.music-games",
|
||||
"public.app-category.puzzle-games",
|
||||
"public.app-category.racing-games",
|
||||
"public.app-category.role-playing-games",
|
||||
"public.app-category.simulation-games",
|
||||
"public.app-category.sports-games",
|
||||
"public.app-category.strategy-games",
|
||||
"public.app-category.trivia-games",
|
||||
"public.app-category.word-games",
|
||||
"public.app-category.graphics-design",
|
||||
"public.app-category.healthcare-fitness",
|
||||
"public.app-category.lifestyle",
|
||||
"public.app-category.medical",
|
||||
"public.app-category.music",
|
||||
"public.app-category.news",
|
||||
"public.app-category.photography",
|
||||
"public.app-category.productivity",
|
||||
"public.app-category.reference",
|
||||
"public.app-category.social-networking",
|
||||
"public.app-category.sports",
|
||||
"public.app-category.travel",
|
||||
"public.app-category.utilities",
|
||||
"public.app-category.video",
|
||||
"public.app-category.weather",
|
||||
];
|
||||
// Test that if the user uses an LSApplicationCategoryType string as
|
||||
// the category string, they will get back that same string for the
|
||||
// macOS app bundle LSApplicationCategoryType.
|
||||
for &value in values.iter() {
|
||||
let category = AppCategory::from_str(value).expect(value);
|
||||
assert_eq!(category.osx_application_category_type(), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,339 +0,0 @@
|
||||
use crate::ResultExt;
|
||||
|
||||
use std;
|
||||
use std::ffi::OsStr;
|
||||
use std::fs::{self, File};
|
||||
use std::io::{self, BufWriter, Write};
|
||||
use std::path::{Component, Path, PathBuf};
|
||||
|
||||
use error_chain::bail;
|
||||
use term;
|
||||
use walkdir;
|
||||
|
||||
/// Returns true if the path has a filename indicating that it is a high-desity
|
||||
/// "retina" icon. Specifically, returns true the the file stem ends with
|
||||
/// "@2x" (a convention specified by the [Apple developer docs](
|
||||
/// https://developer.apple.com/library/mac/documentation/GraphicsAnimation/Conceptual/HighResolutionOSX/Optimizing/Optimizing.html)).
|
||||
pub fn is_retina<P: AsRef<Path>>(path: P) -> bool {
|
||||
path
|
||||
.as_ref()
|
||||
.file_stem()
|
||||
.and_then(OsStr::to_str)
|
||||
.map(|stem| stem.ends_with("@2x"))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Creates a new file at the given path, creating any parent directories as
|
||||
/// needed.
|
||||
pub fn create_file(path: &Path) -> crate::Result<BufWriter<File>> {
|
||||
if let Some(parent) = path.parent() {
|
||||
fs::create_dir_all(&parent).chain_err(|| format!("Failed to create directory {:?}", parent))?;
|
||||
}
|
||||
let file = File::create(path).chain_err(|| format!("Failed to create file {:?}", path))?;
|
||||
Ok(BufWriter::new(file))
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn symlink_dir(src: &Path, dst: &Path) -> io::Result<()> {
|
||||
std::os::unix::fs::symlink(src, dst)
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn symlink_dir(src: &Path, dst: &Path) -> io::Result<()> {
|
||||
std::os::windows::fs::symlink_dir(src, dst)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn symlink_file(src: &Path, dst: &Path) -> io::Result<()> {
|
||||
std::os::unix::fs::symlink(src, dst)
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn symlink_file(src: &Path, dst: &Path) -> io::Result<()> {
|
||||
std::os::windows::fs::symlink_file(src, dst)
|
||||
}
|
||||
|
||||
/// Copies a regular file from one path to another, creating any parent
|
||||
/// directories of the destination path as necessary. Fails if the source path
|
||||
/// is a directory or doesn't exist.
|
||||
pub fn copy_file(from: &Path, to: &Path) -> crate::Result<()> {
|
||||
if !from.exists() {
|
||||
bail!("{:?} does not exist", from);
|
||||
}
|
||||
if !from.is_file() {
|
||||
bail!("{:?} is not a file", from);
|
||||
}
|
||||
let dest_dir = to.parent().expect("No data in parent");
|
||||
fs::create_dir_all(dest_dir).chain_err(|| format!("Failed to create {:?}", dest_dir))?;
|
||||
fs::copy(from, to).chain_err(|| format!("Failed to copy {:?} to {:?}", from, to))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Recursively copies a directory file from one path to another, creating any
|
||||
/// parent directories of the destination path as necessary. Fails if the
|
||||
/// source path is not a directory or doesn't exist, or if the destination path
|
||||
/// already exists.
|
||||
pub fn copy_dir(from: &Path, to: &Path) -> crate::Result<()> {
|
||||
if !from.exists() {
|
||||
bail!("{:?} does not exist", from);
|
||||
}
|
||||
if !from.is_dir() {
|
||||
bail!("{:?} is not a directory", from);
|
||||
}
|
||||
if to.exists() {
|
||||
bail!("{:?} already exists", to);
|
||||
}
|
||||
let parent = to.parent().expect("No data in parent");
|
||||
fs::create_dir_all(parent).chain_err(|| format!("Failed to create {:?}", parent))?;
|
||||
for entry in walkdir::WalkDir::new(from) {
|
||||
let entry = entry?;
|
||||
debug_assert!(entry.path().starts_with(from));
|
||||
let rel_path = entry.path().strip_prefix(from)?;
|
||||
let dest_path = to.join(rel_path);
|
||||
if entry.file_type().is_symlink() {
|
||||
let target = fs::read_link(entry.path())?;
|
||||
if entry.path().is_dir() {
|
||||
symlink_dir(&target, &dest_path)?;
|
||||
} else {
|
||||
symlink_file(&target, &dest_path)?;
|
||||
}
|
||||
} else if entry.file_type().is_dir() {
|
||||
fs::create_dir(dest_path)?;
|
||||
} else {
|
||||
fs::copy(entry.path(), dest_path)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Given a path (absolute or relative) to a resource file, returns the
|
||||
/// relative path from the bundle resources directory where that resource
|
||||
/// should be stored.
|
||||
pub fn resource_relpath(path: &Path) -> PathBuf {
|
||||
let mut dest = PathBuf::new();
|
||||
for component in path.components() {
|
||||
match component {
|
||||
Component::Prefix(_) => {}
|
||||
Component::RootDir => dest.push("_root_"),
|
||||
Component::CurDir => {}
|
||||
Component::ParentDir => dest.push("_up_"),
|
||||
Component::Normal(string) => dest.push(string),
|
||||
}
|
||||
}
|
||||
dest
|
||||
}
|
||||
|
||||
/// Prints a message to stderr, in the same format that `cargo` uses,
|
||||
/// indicating that we are creating a bundle with the given filename.
|
||||
pub fn print_bundling(filename: &str) -> crate::Result<()> {
|
||||
print_progress("Bundling", filename)
|
||||
}
|
||||
|
||||
/// Prints a message to stderr, in the same format that `cargo` uses,
|
||||
/// indicating that we have finished the the given bundles.
|
||||
pub fn print_finished(output_paths: &Vec<PathBuf>) -> crate::Result<()> {
|
||||
let pluralised = if output_paths.len() == 1 {
|
||||
"bundle"
|
||||
} else {
|
||||
"bundles"
|
||||
};
|
||||
let msg = format!("{} {} at:", output_paths.len(), pluralised);
|
||||
print_progress("Finished", &msg)?;
|
||||
for path in output_paths {
|
||||
println!(" {}", path.display());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn safe_term_attr<T: term::Terminal + ?Sized>(
|
||||
output: &mut Box<T>,
|
||||
attr: term::Attr,
|
||||
) -> term::Result<()> {
|
||||
match output.supports_attr(attr) {
|
||||
true => output.attr(attr),
|
||||
false => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn print_progress(step: &str, msg: &str) -> crate::Result<()> {
|
||||
if let Some(mut output) = term::stderr() {
|
||||
safe_term_attr(&mut output, term::Attr::Bold)?;
|
||||
output.fg(term::color::GREEN)?;
|
||||
write!(output, " {}", step)?;
|
||||
output.reset()?;
|
||||
write!(output, " {}\n", msg)?;
|
||||
output.flush()?;
|
||||
Ok(())
|
||||
} else {
|
||||
let mut output = io::stderr();
|
||||
write!(output, " {}", step)?;
|
||||
write!(output, " {}\n", msg)?;
|
||||
output.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Prints a warning message to stderr, in the same format that `cargo` uses.
|
||||
pub fn print_warning(message: &str) -> crate::Result<()> {
|
||||
if let Some(mut output) = term::stderr() {
|
||||
safe_term_attr(&mut output, term::Attr::Bold)?;
|
||||
output.fg(term::color::YELLOW)?;
|
||||
write!(output, "warning:")?;
|
||||
output.reset()?;
|
||||
write!(output, " {}\n", message)?;
|
||||
output.flush()?;
|
||||
Ok(())
|
||||
} else {
|
||||
let mut output = io::stderr();
|
||||
write!(output, "warning:")?;
|
||||
write!(output, " {}\n", message)?;
|
||||
output.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Prints a Info message to stderr.
|
||||
pub fn print_info(message: &str) -> crate::Result<()> {
|
||||
if let Some(mut output) = term::stderr() {
|
||||
safe_term_attr(&mut output, term::Attr::Bold)?;
|
||||
output.fg(term::color::GREEN)?;
|
||||
write!(output, "info:")?;
|
||||
output.reset()?;
|
||||
write!(output, " {}\n", message)?;
|
||||
output.flush()?;
|
||||
Ok(())
|
||||
} else {
|
||||
let mut output = io::stderr();
|
||||
write!(output, "info:")?;
|
||||
write!(output, " {}\n", message)?;
|
||||
output.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Prints an error to stderr, in the same format that `cargo` uses.
|
||||
pub fn print_error(error: &crate::Error) -> crate::Result<()> {
|
||||
if let Some(mut output) = term::stderr() {
|
||||
safe_term_attr(&mut output, term::Attr::Bold)?;
|
||||
output.fg(term::color::RED)?;
|
||||
write!(output, "error:")?;
|
||||
output.reset()?;
|
||||
safe_term_attr(&mut output, term::Attr::Bold)?;
|
||||
writeln!(output, " {}", error)?;
|
||||
output.reset()?;
|
||||
for cause in error.iter().skip(1) {
|
||||
writeln!(output, " Caused by: {}", cause)?;
|
||||
}
|
||||
if let Some(backtrace) = error.backtrace() {
|
||||
writeln!(output, "{:?}", backtrace)?;
|
||||
}
|
||||
output.flush()?;
|
||||
std::process::exit(1)
|
||||
} else {
|
||||
let mut output = io::stderr();
|
||||
write!(output, "error:")?;
|
||||
writeln!(output, " {}", error)?;
|
||||
for cause in error.iter().skip(1) {
|
||||
writeln!(output, " Caused by: {}", cause)?;
|
||||
}
|
||||
if let Some(backtrace) = error.backtrace() {
|
||||
writeln!(output, "{:?}", backtrace)?;
|
||||
}
|
||||
output.flush()?;
|
||||
std::process::exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{copy_dir, create_file, is_retina, resource_relpath, symlink_file};
|
||||
use std;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use tempfile;
|
||||
|
||||
#[test]
|
||||
fn create_file_with_parent_dirs() {
|
||||
let tmp = tempfile::tempdir().expect("Unable to create temp dir");
|
||||
assert!(!tmp.path().join("parent").exists());
|
||||
{
|
||||
let mut file =
|
||||
create_file(&tmp.path().join("parent/file.txt")).expect("Failed to create file");
|
||||
write!(file, "Hello, world!\n").expect("unable to write file");
|
||||
}
|
||||
assert!(tmp.path().join("parent").is_dir());
|
||||
assert!(tmp.path().join("parent/file.txt").is_file());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn copy_dir_with_symlinks() {
|
||||
// Create a directory structure that looks like this:
|
||||
// ${TMP}/orig/
|
||||
// sub/
|
||||
// file.txt
|
||||
// link -> sub/file.txt
|
||||
let tmp = tempfile::tempdir().expect("unable to create tempdir");
|
||||
{
|
||||
let mut file =
|
||||
create_file(&tmp.path().join("orig/sub/file.txt")).expect("Unable to create file");
|
||||
write!(file, "Hello, world!\n").expect("Unable to write to file");
|
||||
}
|
||||
symlink_file(
|
||||
&PathBuf::from("sub/file.txt"),
|
||||
&tmp.path().join("orig/link"),
|
||||
)
|
||||
.expect("Failed to create symlink");
|
||||
assert_eq!(
|
||||
std::fs::read(tmp.path().join("orig/link"))
|
||||
.expect("Failed to read file")
|
||||
.as_slice(),
|
||||
b"Hello, world!\n"
|
||||
);
|
||||
// Copy ${TMP}/orig to ${TMP}/parent/copy, and make sure that the
|
||||
// directory structure, file, and symlink got copied correctly.
|
||||
copy_dir(&tmp.path().join("orig"), &tmp.path().join("parent/copy"))
|
||||
.expect("Failed to copy dir");
|
||||
assert!(tmp.path().join("parent/copy").is_dir());
|
||||
assert!(tmp.path().join("parent/copy/sub").is_dir());
|
||||
assert!(tmp.path().join("parent/copy/sub/file.txt").is_file());
|
||||
assert_eq!(
|
||||
std::fs::read(tmp.path().join("parent/copy/sub/file.txt"))
|
||||
.expect("Failed to read file")
|
||||
.as_slice(),
|
||||
b"Hello, world!\n"
|
||||
);
|
||||
assert!(tmp.path().join("parent/copy/link").exists());
|
||||
assert_eq!(
|
||||
std::fs::read_link(tmp.path().join("parent/copy/link")).expect("Failed to read from symlink"),
|
||||
PathBuf::from("sub/file.txt")
|
||||
);
|
||||
assert_eq!(
|
||||
std::fs::read(tmp.path().join("parent/copy/link"))
|
||||
.expect("Failed to read from file")
|
||||
.as_slice(),
|
||||
b"Hello, world!\n"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retina_icon_paths() {
|
||||
assert!(!is_retina("data/icons/512x512.png"));
|
||||
assert!(is_retina("data/icons/512x512@2x.png"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resource_relative_paths() {
|
||||
assert_eq!(
|
||||
resource_relpath(&PathBuf::from("./data/images/button.png")),
|
||||
PathBuf::from("data/images/button.png")
|
||||
);
|
||||
assert_eq!(
|
||||
resource_relpath(&PathBuf::from("../../images/wheel.png")),
|
||||
PathBuf::from("_up_/_up_/images/wheel.png")
|
||||
);
|
||||
assert_eq!(
|
||||
resource_relpath(&PathBuf::from("/home/ferris/crab.png")),
|
||||
PathBuf::from("_root_/home/ferris/crab.png")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,344 +0,0 @@
|
||||
// The structure of a Debian package looks something like this:
|
||||
//
|
||||
// foobar_1.2.3_i386.deb # Actually an ar archive
|
||||
// debian-binary # Specifies deb format version (2.0 in our case)
|
||||
// control.tar.gz # Contains files controlling the installation:
|
||||
// control # Basic package metadata
|
||||
// md5sums # Checksums for files in data.tar.gz below
|
||||
// postinst # Post-installation script (optional)
|
||||
// prerm # Pre-uninstallation script (optional)
|
||||
// data.tar.gz # Contains files to be installed:
|
||||
// usr/bin/foobar # Binary executable file
|
||||
// usr/share/applications/foobar.desktop # Desktop file (for apps)
|
||||
// usr/share/icons/hicolor/... # Icon files (for apps)
|
||||
// usr/lib/foobar/... # Other resource files
|
||||
//
|
||||
// For cargo-bundle, we put bundle resource files under /usr/lib/package_name/,
|
||||
// and then generate the desktop file and control file from the bundle
|
||||
// metadata, as well as generating the md5sums file. Currently we do not
|
||||
// generate postinst or prerm files.
|
||||
|
||||
use super::common;
|
||||
use crate::{ResultExt, Settings};
|
||||
|
||||
use ar;
|
||||
use icns;
|
||||
use image::png::{PNGDecoder, PNGEncoder};
|
||||
use image::{self, GenericImageView, ImageDecoder};
|
||||
use libflate::gzip;
|
||||
use md5;
|
||||
use tar;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
use std::convert::TryInto;
|
||||
use std::ffi::OsStr;
|
||||
use std::fs::{self, File};
|
||||
use std::io::{self, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
|
||||
let arch = match settings.binary_arch() {
|
||||
"x86" => "i386",
|
||||
"x86_64" => "amd64",
|
||||
other => other,
|
||||
};
|
||||
let package_base_name = format!(
|
||||
"{}_{}_{}",
|
||||
settings.binary_name(),
|
||||
settings.version_string(),
|
||||
arch
|
||||
);
|
||||
let package_name = format!("{}.deb", package_base_name);
|
||||
common::print_bundling(&package_name)?;
|
||||
let base_dir = settings.project_out_directory().join("bundle/deb");
|
||||
let package_dir = base_dir.join(&package_base_name);
|
||||
if package_dir.exists() {
|
||||
fs::remove_dir_all(&package_dir)
|
||||
.chain_err(|| format!("Failed to remove old {}", package_base_name))?;
|
||||
}
|
||||
let package_path = base_dir.join(package_name);
|
||||
|
||||
let data_dir =
|
||||
generate_folders(settings, &package_dir).chain_err(|| "Failed to build folders")?;
|
||||
// Generate control files.
|
||||
let control_dir = package_dir.join("control");
|
||||
generate_control_file(settings, arch, &control_dir, &data_dir)
|
||||
.chain_err(|| "Failed to create control file")?;
|
||||
generate_md5sums(&control_dir, &data_dir).chain_err(|| "Failed to create md5sums file")?;
|
||||
|
||||
// Generate `debian-binary` file; see
|
||||
// http://www.tldp.org/HOWTO/Debian-Binary-Package-Building-HOWTO/x60.html#AEN66
|
||||
let debian_binary_path = package_dir.join("debian-binary");
|
||||
create_file_with_data(&debian_binary_path, "2.0\n")
|
||||
.chain_err(|| "Failed to create debian-binary file")?;
|
||||
|
||||
// Apply tar/gzip/ar to create the final package file.
|
||||
let control_tar_gz_path =
|
||||
tar_and_gzip_dir(control_dir).chain_err(|| "Failed to tar/gzip control directory")?;
|
||||
let data_tar_gz_path =
|
||||
tar_and_gzip_dir(data_dir).chain_err(|| "Failed to tar/gzip data directory")?;
|
||||
create_archive(
|
||||
vec![debian_binary_path, control_tar_gz_path, data_tar_gz_path],
|
||||
&package_path,
|
||||
)
|
||||
.chain_err(|| "Failed to create package archive")?;
|
||||
Ok(vec![package_path])
|
||||
}
|
||||
|
||||
pub fn generate_folders(settings: &Settings, package_dir: &Path) -> crate::Result<PathBuf> {
|
||||
// Generate data files.
|
||||
let data_dir = package_dir.join("data");
|
||||
let binary_dest = data_dir.join("usr/bin").join(settings.binary_name());
|
||||
let bin_dir = data_dir.join("usr/bin");
|
||||
|
||||
common::copy_file(settings.binary_path(), &binary_dest)
|
||||
.chain_err(|| "Failed to copy binary file")?;
|
||||
transfer_resource_files(settings, &data_dir).chain_err(|| "Failed to copy resource files")?;
|
||||
|
||||
settings
|
||||
.copy_binaries(&bin_dir)
|
||||
.chain_err(|| "Failed to copy external binaries")?;
|
||||
|
||||
generate_icon_files(settings, &data_dir).chain_err(|| "Failed to create icon files")?;
|
||||
generate_desktop_file(settings, &data_dir).chain_err(|| "Failed to create desktop file")?;
|
||||
|
||||
Ok(data_dir)
|
||||
}
|
||||
|
||||
/// Generate the application desktop file and store it under the `data_dir`.
|
||||
fn generate_desktop_file(settings: &Settings, data_dir: &Path) -> crate::Result<()> {
|
||||
let bin_name = settings.binary_name();
|
||||
let desktop_file_name = format!("{}.desktop", bin_name);
|
||||
let desktop_file_path = data_dir
|
||||
.join("usr/share/applications")
|
||||
.join(desktop_file_name);
|
||||
let file = &mut common::create_file(&desktop_file_path)?;
|
||||
// For more information about the format of this file, see
|
||||
// https://developer.gnome.org/integration-guide/stable/desktop-files.html.en
|
||||
write!(file, "[Desktop Entry]\n")?;
|
||||
write!(file, "Encoding=UTF-8\n")?;
|
||||
if let Some(category) = settings.app_category() {
|
||||
write!(file, "Categories={}\n", category.gnome_desktop_categories())?;
|
||||
}
|
||||
if !settings.short_description().is_empty() {
|
||||
write!(file, "Comment={}\n", settings.short_description())?;
|
||||
}
|
||||
write!(file, "Exec={}\n", bin_name)?;
|
||||
write!(file, "Icon={}\n", bin_name)?;
|
||||
write!(file, "Name={}\n", settings.bundle_name())?;
|
||||
write!(file, "Terminal=false\n")?;
|
||||
write!(file, "Type=Application\n")?;
|
||||
write!(file, "Version={}\n", settings.version_string())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn generate_control_file(
|
||||
settings: &Settings,
|
||||
arch: &str,
|
||||
control_dir: &Path,
|
||||
data_dir: &Path,
|
||||
) -> crate::Result<()> {
|
||||
// For more information about the format of this file, see
|
||||
// https://www.debian.org/doc/debian-policy/ch-controlfields.html
|
||||
let dest_path = control_dir.join("control");
|
||||
let mut file = common::create_file(&dest_path)?;
|
||||
writeln!(
|
||||
&mut file,
|
||||
"Package: {}",
|
||||
str::replace(settings.bundle_name(), " ", "-").to_ascii_lowercase()
|
||||
)?;
|
||||
writeln!(&mut file, "Version: {}", settings.version_string())?;
|
||||
writeln!(&mut file, "Architecture: {}", arch)?;
|
||||
writeln!(&mut file, "Installed-Size: {}", total_dir_size(data_dir)?)?;
|
||||
let authors = settings.authors_comma_separated().unwrap_or(String::new());
|
||||
writeln!(&mut file, "Maintainer: {}", authors)?;
|
||||
if !settings.homepage_url().is_empty() {
|
||||
writeln!(&mut file, "Homepage: {}", settings.homepage_url())?;
|
||||
}
|
||||
let dependencies = settings.debian_dependencies();
|
||||
if !dependencies.is_empty() {
|
||||
writeln!(&mut file, "Depends: {}", dependencies.join(", "))?;
|
||||
}
|
||||
let mut short_description = settings.short_description().trim();
|
||||
if short_description.is_empty() {
|
||||
short_description = "(none)";
|
||||
}
|
||||
let mut long_description = settings.long_description().unwrap_or("").trim();
|
||||
if long_description.is_empty() {
|
||||
long_description = "(none)";
|
||||
}
|
||||
writeln!(&mut file, "Description: {}", short_description)?;
|
||||
for line in long_description.lines() {
|
||||
let line = line.trim();
|
||||
if line.is_empty() {
|
||||
writeln!(&mut file, " .")?;
|
||||
} else {
|
||||
writeln!(&mut file, " {}", line)?;
|
||||
}
|
||||
}
|
||||
file.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create an `md5sums` file in the `control_dir` containing the MD5 checksums
|
||||
/// for each file within the `data_dir`.
|
||||
fn generate_md5sums(control_dir: &Path, data_dir: &Path) -> crate::Result<()> {
|
||||
let md5sums_path = control_dir.join("md5sums");
|
||||
let mut md5sums_file = common::create_file(&md5sums_path)?;
|
||||
for entry in WalkDir::new(data_dir) {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
continue;
|
||||
}
|
||||
let mut file = File::open(path)?;
|
||||
let mut hash = md5::Context::new();
|
||||
io::copy(&mut file, &mut hash)?;
|
||||
for byte in hash.compute().iter() {
|
||||
write!(md5sums_file, "{:02x}", byte)?;
|
||||
}
|
||||
let rel_path = path.strip_prefix(data_dir)?;
|
||||
let path_str = rel_path.to_str().ok_or_else(|| {
|
||||
let msg = format!("Non-UTF-8 path: {:?}", rel_path);
|
||||
io::Error::new(io::ErrorKind::InvalidData, msg)
|
||||
})?;
|
||||
write!(md5sums_file, " {}\n", path_str)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Copy the bundle's resource files into an appropriate directory under the
|
||||
/// `data_dir`.
|
||||
fn transfer_resource_files(settings: &Settings, data_dir: &Path) -> crate::Result<()> {
|
||||
let resource_dir = data_dir.join("usr/lib").join(settings.binary_name());
|
||||
settings.copy_resources(&resource_dir)
|
||||
}
|
||||
|
||||
/// Generate the icon files and store them under the `data_dir`.
|
||||
fn generate_icon_files(settings: &Settings, data_dir: &PathBuf) -> crate::Result<()> {
|
||||
let base_dir = data_dir.join("usr/share/icons/hicolor");
|
||||
let get_dest_path = |width: u32, height: u32, is_high_density: bool| {
|
||||
base_dir.join(format!(
|
||||
"{}x{}{}/apps/{}.png",
|
||||
width,
|
||||
height,
|
||||
if is_high_density { "@2x" } else { "" },
|
||||
settings.binary_name()
|
||||
))
|
||||
};
|
||||
let mut sizes = BTreeSet::new();
|
||||
// Prefer PNG files.
|
||||
for icon_path in settings.icon_files() {
|
||||
let icon_path = icon_path?;
|
||||
if icon_path.extension() != Some(OsStr::new("png")) {
|
||||
continue;
|
||||
}
|
||||
let decoder = PNGDecoder::new(File::open(&icon_path)?)?;
|
||||
let width = decoder.dimensions().0.try_into()?;
|
||||
let height = decoder.dimensions().1.try_into()?;
|
||||
let is_high_density = common::is_retina(&icon_path);
|
||||
if !sizes.contains(&(width, height, is_high_density)) {
|
||||
sizes.insert((width, height, is_high_density));
|
||||
let dest_path = get_dest_path(width, height, is_high_density);
|
||||
common::copy_file(&icon_path, &dest_path)?;
|
||||
}
|
||||
}
|
||||
// Fall back to non-PNG files for any missing sizes.
|
||||
for icon_path in settings.icon_files() {
|
||||
let icon_path = icon_path?;
|
||||
if icon_path.extension() == Some(OsStr::new("png")) {
|
||||
continue;
|
||||
} else if icon_path.extension() == Some(OsStr::new("icns")) {
|
||||
let icon_family = icns::IconFamily::read(File::open(&icon_path)?)?;
|
||||
for icon_type in icon_family.available_icons() {
|
||||
let width = icon_type.screen_width();
|
||||
let height = icon_type.screen_height();
|
||||
let is_high_density = icon_type.pixel_density() > 1;
|
||||
if !sizes.contains(&(width, height, is_high_density)) {
|
||||
sizes.insert((width, height, is_high_density));
|
||||
let dest_path = get_dest_path(width, height, is_high_density);
|
||||
let icon = icon_family.get_icon_with_type(icon_type)?;
|
||||
icon.write_png(common::create_file(&dest_path)?)?;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let icon = image::open(&icon_path)?;
|
||||
let (width, height) = icon.dimensions();
|
||||
let is_high_density = common::is_retina(&icon_path);
|
||||
if !sizes.contains(&(width, height, is_high_density)) {
|
||||
sizes.insert((width, height, is_high_density));
|
||||
let dest_path = get_dest_path(width, height, is_high_density);
|
||||
let encoder = PNGEncoder::new(common::create_file(&dest_path)?);
|
||||
encoder.encode(&icon.raw_pixels(), width, height, icon.color())?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create an empty file at the given path, creating any parent directories as
|
||||
/// needed, then write `data` into the file.
|
||||
fn create_file_with_data<P: AsRef<Path>>(path: P, data: &str) -> crate::Result<()> {
|
||||
let mut file = common::create_file(path.as_ref())?;
|
||||
file.write_all(data.as_bytes())?;
|
||||
file.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Computes the total size, in bytes, of the given directory and all of its
|
||||
/// contents.
|
||||
fn total_dir_size(dir: &Path) -> crate::Result<u64> {
|
||||
let mut total: u64 = 0;
|
||||
for entry in WalkDir::new(&dir) {
|
||||
total += entry?.metadata()?.len();
|
||||
}
|
||||
Ok(total)
|
||||
}
|
||||
|
||||
/// Writes a tar file to the given writer containing the given directory.
|
||||
fn create_tar_from_dir<P: AsRef<Path>, W: Write>(src_dir: P, dest_file: W) -> crate::Result<W> {
|
||||
let src_dir = src_dir.as_ref();
|
||||
let mut tar_builder = tar::Builder::new(dest_file);
|
||||
for entry in WalkDir::new(&src_dir) {
|
||||
let entry = entry?;
|
||||
let src_path = entry.path();
|
||||
if src_path == src_dir {
|
||||
continue;
|
||||
}
|
||||
let dest_path = src_path.strip_prefix(&src_dir)?;
|
||||
if entry.file_type().is_dir() {
|
||||
tar_builder.append_dir(dest_path, src_path)?;
|
||||
} else {
|
||||
let mut src_file = fs::File::open(src_path)?;
|
||||
tar_builder.append_file(dest_path, &mut src_file)?;
|
||||
}
|
||||
}
|
||||
let dest_file = tar_builder.into_inner()?;
|
||||
Ok(dest_file)
|
||||
}
|
||||
|
||||
/// Creates a `.tar.gz` file from the given directory (placing the new file
|
||||
/// within the given directory's parent directory), then deletes the original
|
||||
/// directory and returns the path to the new file.
|
||||
fn tar_and_gzip_dir<P: AsRef<Path>>(src_dir: P) -> crate::Result<PathBuf> {
|
||||
let src_dir = src_dir.as_ref();
|
||||
let dest_path = src_dir.with_extension("tar.gz");
|
||||
let dest_file = common::create_file(&dest_path)?;
|
||||
let gzip_encoder = gzip::Encoder::new(dest_file)?;
|
||||
let gzip_encoder = create_tar_from_dir(src_dir, gzip_encoder)?;
|
||||
let mut dest_file = gzip_encoder.finish().into_result()?;
|
||||
dest_file.flush()?;
|
||||
Ok(dest_path)
|
||||
}
|
||||
|
||||
/// Creates an `ar` archive from the given source files and writes it to the
|
||||
/// given destination path.
|
||||
fn create_archive(srcs: Vec<PathBuf>, dest: &Path) -> crate::Result<()> {
|
||||
let mut builder = ar::Builder::new(common::create_file(&dest)?);
|
||||
for path in &srcs {
|
||||
builder.append_path(path)?;
|
||||
}
|
||||
builder.into_inner()?.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
use super::common;
|
||||
use super::osx_bundle;
|
||||
use crate::Settings;
|
||||
|
||||
use handlebars::Handlebars;
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::fs::{write, File};
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
// Create handlebars template for shell scripts
|
||||
lazy_static! {
|
||||
static ref HANDLEBARS: Handlebars<'static> = {
|
||||
let mut handlebars = Handlebars::new();
|
||||
|
||||
handlebars
|
||||
.register_template_string("bundle_dmg", include_str!("templates/bundle_dmg"))
|
||||
.expect("Failed to setup handlebars template");
|
||||
handlebars
|
||||
};
|
||||
}
|
||||
|
||||
// create script files to bundle project and execute bundle_script.
|
||||
pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
|
||||
// generate the app.app folder
|
||||
osx_bundle::bundle_project(settings)?;
|
||||
|
||||
// get uppercase string of app name
|
||||
let upcase = settings.binary_name().to_uppercase();
|
||||
|
||||
// generate BTreeMap for templates
|
||||
let mut sh_map = BTreeMap::new();
|
||||
sh_map.insert("app_name", settings.binary_name());
|
||||
sh_map.insert("app_name_upcase", &upcase);
|
||||
|
||||
let bundle_temp = HANDLEBARS
|
||||
.render("bundle_dmg", &sh_map)
|
||||
.or_else(|e| Err(e.to_string()))?;
|
||||
|
||||
// get the target path
|
||||
let output_path = settings.project_out_directory();
|
||||
|
||||
// create paths for script
|
||||
let bundle_sh = output_path.join("bundle_dmg.sh");
|
||||
|
||||
common::print_bundling(format!("{:?}", &output_path.join(format!("{}.dmg", &upcase))).as_str())?;
|
||||
|
||||
// write the scripts
|
||||
write(&bundle_sh, bundle_temp).or_else(|e| Err(e.to_string()))?;
|
||||
|
||||
// copy seticon binary
|
||||
let seticon = include_bytes!("templates/seticon");
|
||||
let seticon_out = &output_path.join("seticon");
|
||||
let mut seticon_buffer = File::create(seticon_out).or_else(|e| Err(e.to_string()))?;
|
||||
seticon_buffer
|
||||
.write_all(seticon)
|
||||
.or_else(|e| Err(e.to_string()))?;
|
||||
|
||||
// chmod script for execution
|
||||
|
||||
Command::new("chmod")
|
||||
.arg("777")
|
||||
.arg(&bundle_sh)
|
||||
.arg(&seticon_out)
|
||||
.current_dir(output_path)
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.expect("Failed to chmod script");
|
||||
|
||||
// execute the bundle script
|
||||
Command::new(&bundle_sh)
|
||||
.current_dir(output_path)
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.output()
|
||||
.expect("Failed to execute shell script");
|
||||
|
||||
Ok(vec![bundle_sh])
|
||||
}
|
||||
@@ -1,175 +0,0 @@
|
||||
// An iOS package is laid out like:
|
||||
//
|
||||
// Foobar.app # Actually a directory
|
||||
// Foobar # The main binary executable of the app
|
||||
// Info.plist # An XML file containing the app's metadata
|
||||
// ... # Icons and other resource files
|
||||
//
|
||||
// See https://developer.apple.com/go/?id=bundle-structure for a full
|
||||
// explanation.
|
||||
|
||||
use super::common;
|
||||
use crate::{ResultExt, Settings};
|
||||
|
||||
use icns;
|
||||
use image::png::{PNGDecoder, PNGEncoder};
|
||||
use image::{self, GenericImageView, ImageDecoder};
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
use std::convert::TryInto;
|
||||
use std::ffi::OsStr;
|
||||
use std::fs::{self, File};
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
|
||||
common::print_warning("iOS bundle support is still experimental.")?;
|
||||
|
||||
let app_bundle_name = format!("{}.app", settings.bundle_name());
|
||||
common::print_bundling(&app_bundle_name)?;
|
||||
let bundle_dir = settings
|
||||
.project_out_directory()
|
||||
.join("bundle/ios")
|
||||
.join(&app_bundle_name);
|
||||
if bundle_dir.exists() {
|
||||
fs::remove_dir_all(&bundle_dir)
|
||||
.chain_err(|| format!("Failed to remove old {}", app_bundle_name))?;
|
||||
}
|
||||
fs::create_dir_all(&bundle_dir)
|
||||
.chain_err(|| format!("Failed to create bundle directory at {:?}", bundle_dir))?;
|
||||
|
||||
settings.copy_resources(bundle_dir);
|
||||
|
||||
let icon_filenames =
|
||||
generate_icon_files(&bundle_dir, settings).chain_err(|| "Failed to create app icons")?;
|
||||
generate_info_plist(&bundle_dir, settings, &icon_filenames)
|
||||
.chain_err(|| "Failed to create Info.plist")?;
|
||||
let bin_path = bundle_dir.join(&settings.bundle_name());
|
||||
common::copy_file(settings.binary_path(), &bin_path)
|
||||
.chain_err(|| format!("Failed to copy binary from {:?}", settings.binary_path()))?;
|
||||
Ok(vec![bundle_dir])
|
||||
}
|
||||
|
||||
/// Generate the icon files and store them under the `bundle_dir`.
|
||||
fn generate_icon_files(bundle_dir: &Path, settings: &Settings) -> crate::Result<Vec<String>> {
|
||||
let mut filenames = Vec::new();
|
||||
{
|
||||
let mut get_dest_path = |width: u32, height: u32, is_retina: bool| {
|
||||
let filename = format!(
|
||||
"icon_{}x{}{}.png",
|
||||
width,
|
||||
height,
|
||||
if is_retina { "@2x" } else { "" }
|
||||
);
|
||||
let path = bundle_dir.join(&filename);
|
||||
filenames.push(filename);
|
||||
path
|
||||
};
|
||||
let mut sizes = BTreeSet::new();
|
||||
// Prefer PNG files.
|
||||
for icon_path in settings.icon_files() {
|
||||
let icon_path = icon_path?;
|
||||
if icon_path.extension() != Some(OsStr::new("png")) {
|
||||
continue;
|
||||
}
|
||||
let decoder = PNGDecoder::new(File::open(&icon_path)?)?;
|
||||
let width = decoder.dimensions().0.try_into()?;
|
||||
let height = decoder.dimensions().1.try_into()?;
|
||||
let is_retina = common::is_retina(&icon_path);
|
||||
if !sizes.contains(&(width, height, is_retina)) {
|
||||
sizes.insert((width, height, is_retina));
|
||||
let dest_path = get_dest_path(width, height, is_retina);
|
||||
common::copy_file(&icon_path, &dest_path)?;
|
||||
}
|
||||
}
|
||||
// Fall back to non-PNG files for any missing sizes.
|
||||
for icon_path in settings.icon_files() {
|
||||
let icon_path = icon_path?;
|
||||
if icon_path.extension() == Some(OsStr::new("png")) {
|
||||
continue;
|
||||
} else if icon_path.extension() == Some(OsStr::new("icns")) {
|
||||
let icon_family = icns::IconFamily::read(File::open(&icon_path)?)?;
|
||||
for icon_type in icon_family.available_icons() {
|
||||
let width = icon_type.screen_width();
|
||||
let height = icon_type.screen_height();
|
||||
let is_retina = icon_type.pixel_density() > 1;
|
||||
if !sizes.contains(&(width, height, is_retina)) {
|
||||
sizes.insert((width, height, is_retina));
|
||||
let dest_path = get_dest_path(width, height, is_retina);
|
||||
let icon = icon_family.get_icon_with_type(icon_type)?;
|
||||
icon.write_png(File::create(dest_path)?)?;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let icon = image::open(&icon_path)?;
|
||||
let (width, height) = icon.dimensions();
|
||||
let is_retina = common::is_retina(&icon_path);
|
||||
if !sizes.contains(&(width, height, is_retina)) {
|
||||
sizes.insert((width, height, is_retina));
|
||||
let dest_path = get_dest_path(width, height, is_retina);
|
||||
let encoder = PNGEncoder::new(common::create_file(&dest_path)?);
|
||||
encoder.encode(&icon.raw_pixels(), width, height, icon.color())?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(filenames)
|
||||
}
|
||||
|
||||
fn generate_info_plist(
|
||||
bundle_dir: &Path,
|
||||
settings: &Settings,
|
||||
icon_filenames: &Vec<String>,
|
||||
) -> crate::Result<()> {
|
||||
let file = &mut common::create_file(&bundle_dir.join("Info.plist"))?;
|
||||
write!(
|
||||
file,
|
||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
|
||||
<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \
|
||||
\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n\
|
||||
<plist version=\"1.0\">\n\
|
||||
<dict>\n"
|
||||
)?;
|
||||
|
||||
write!(
|
||||
file,
|
||||
" <key>CFBundleIdentifier</key>\n <string>{}</string>\n",
|
||||
settings.bundle_identifier()
|
||||
)?;
|
||||
write!(
|
||||
file,
|
||||
" <key>CFBundleDisplayName</key>\n <string>{}</string>\n",
|
||||
settings.bundle_name()
|
||||
)?;
|
||||
write!(
|
||||
file,
|
||||
" <key>CFBundleName</key>\n <string>{}</string>\n",
|
||||
settings.bundle_name()
|
||||
)?;
|
||||
write!(
|
||||
file,
|
||||
" <key>CFBundleExecutable</key>\n <string>{}</string>\n",
|
||||
settings.binary_name()
|
||||
)?;
|
||||
write!(
|
||||
file,
|
||||
" <key>CFBundleVersion</key>\n <string>{}</string>\n",
|
||||
settings.version_string()
|
||||
)?;
|
||||
write!(
|
||||
file,
|
||||
" <key>CFBundleDevelopmentRegion</key>\n <string>en_US</string>\n"
|
||||
)?;
|
||||
|
||||
if !icon_filenames.is_empty() {
|
||||
write!(file, " <key>CFBundleIconFiles</key>\n <array>\n")?;
|
||||
for filename in icon_filenames {
|
||||
write!(file, " <string>{}</string>\n", filename)?;
|
||||
}
|
||||
write!(file, " </array>\n")?;
|
||||
}
|
||||
write!(file, " <key>LSRequiresIPhoneOS</key>\n <true/>\n")?;
|
||||
write!(file, "</dict>\n</plist>\n")?;
|
||||
file.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
use super::common;
|
||||
use super::settings::Settings;
|
||||
use super::wix;
|
||||
|
||||
use std;
|
||||
use std::path::PathBuf;
|
||||
|
||||
// Runs all of the commands to build the MSI installer.
|
||||
// Returns a vector of PathBuf that shows where the MSI was created.
|
||||
pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
|
||||
common::print_warning("MSI bundle support is still experimental.")?;
|
||||
|
||||
let wix_path = PathBuf::from("./WixTools");
|
||||
|
||||
if !wix_path.exists() {
|
||||
wix::get_and_extract_wix(&wix_path)?;
|
||||
}
|
||||
|
||||
let msi_path = wix::build_wix_app_installer(&settings, &wix_path)?;
|
||||
|
||||
Ok(vec![msi_path])
|
||||
}
|
||||
@@ -1,368 +0,0 @@
|
||||
// An OSX package is laid out like:
|
||||
//
|
||||
// foobar.app # Actually a directory
|
||||
// Contents # A further subdirectory
|
||||
// Info.plist # An xml file containing the app's metadata
|
||||
// MacOS # A directory to hold executable binary files
|
||||
// foobar # The main binary executable of the app
|
||||
// foobar_helper # A helper application, possibly provitidng a CLI
|
||||
// Resources # Data files such as images, sounds, translations and nib files
|
||||
// en.lproj # Folder containing english translation strings/data
|
||||
// Frameworks # A directory containing private frameworks (shared libraries)
|
||||
// ... # Any other optional files the developer wants to place here
|
||||
//
|
||||
// See https://developer.apple.com/go/?id=bundle-structure for a full
|
||||
// explanation.
|
||||
//
|
||||
// Currently, cargo-bundle does not support Frameworks, nor does it support placing arbitrary
|
||||
// files into the `Contents` directory of the bundle.
|
||||
|
||||
use super::common;
|
||||
use crate::{ResultExt, Settings};
|
||||
|
||||
use chrono;
|
||||
use dirs;
|
||||
use error_chain::bail;
|
||||
use icns;
|
||||
use image::{self, GenericImageView};
|
||||
|
||||
use std::cmp::min;
|
||||
use std::ffi::OsStr;
|
||||
use std::fs::{self, File};
|
||||
use std::io::prelude::*;
|
||||
use std::io::{self, BufWriter};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
|
||||
let app_bundle_name = format!("{}.app", settings.bundle_name());
|
||||
common::print_bundling(&app_bundle_name)?;
|
||||
let app_bundle_path = settings
|
||||
.project_out_directory()
|
||||
.join("bundle/osx")
|
||||
.join(&app_bundle_name);
|
||||
if app_bundle_path.exists() {
|
||||
fs::remove_dir_all(&app_bundle_path)
|
||||
.chain_err(|| format!("Failed to remove old {}", app_bundle_name))?;
|
||||
}
|
||||
let bundle_directory = app_bundle_path.join("Contents");
|
||||
fs::create_dir_all(&bundle_directory).chain_err(|| {
|
||||
format!(
|
||||
"Failed to create bundle directory at {:?}",
|
||||
bundle_directory
|
||||
)
|
||||
})?;
|
||||
|
||||
let resources_dir = bundle_directory.join("Resources");
|
||||
let bin_dir = bundle_directory.join("MacOS");
|
||||
|
||||
let bundle_icon_file: Option<PathBuf> =
|
||||
{ create_icns_file(&resources_dir, settings).chain_err(|| "Failed to create app icon")? };
|
||||
|
||||
create_info_plist(&bundle_directory, bundle_icon_file, settings)
|
||||
.chain_err(|| "Failed to create Info.plist")?;
|
||||
|
||||
copy_frameworks_to_bundle(&bundle_directory, settings)
|
||||
.chain_err(|| "Failed to bundle frameworks")?;
|
||||
|
||||
settings.copy_resources(&resources_dir)?;
|
||||
|
||||
settings
|
||||
.copy_binaries(&bin_dir)
|
||||
.chain_err(|| "Failed to copy external binaries")?;
|
||||
|
||||
copy_binary_to_bundle(&bundle_directory, settings)
|
||||
.chain_err(|| format!("Failed to copy binary from {:?}", settings.binary_path()))?;
|
||||
|
||||
create_path_hook(&bundle_directory, settings).chain_err(|| "Failed to create _boot wrapper")?;
|
||||
|
||||
Ok(vec![app_bundle_path])
|
||||
}
|
||||
|
||||
fn copy_binary_to_bundle(bundle_directory: &Path, settings: &Settings) -> crate::Result<()> {
|
||||
let dest_dir = bundle_directory.join("MacOS");
|
||||
common::copy_file(
|
||||
settings.binary_path(),
|
||||
&dest_dir.join(settings.binary_name()),
|
||||
)
|
||||
}
|
||||
|
||||
fn create_path_hook(bundle_dir: &Path, settings: &Settings) -> crate::Result<()> {
|
||||
let file = &mut common::create_file(&bundle_dir.join("MacOS/__bootstrapper"))?;
|
||||
// Create a shell script to bootstrap the $PATH for Tauri, so environments like node are available.
|
||||
write!(
|
||||
file,
|
||||
"#!/usr/bin/env sh
|
||||
# This bootstraps the $PATH for Tauri, so environments are available.
|
||||
|
||||
. ~/.bash_profile
|
||||
|
||||
if pidof -x \"__bootstrapper\" >/dev/null; then
|
||||
exit 0
|
||||
else
|
||||
exec \"`dirname \\\"$0\\\"`/{}\" $@ & disown
|
||||
fi
|
||||
exit 0",
|
||||
settings.bundle_name()
|
||||
)?;
|
||||
file.flush()?;
|
||||
|
||||
// We have to make the __bootstrapper executable, or the bundle will not work
|
||||
Command::new("chmod")
|
||||
.arg("+x")
|
||||
.arg("__bootstrapper")
|
||||
.current_dir(&bundle_dir.join("MacOS/"))
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.expect("Failed to chmod script");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_info_plist(
|
||||
bundle_dir: &Path,
|
||||
bundle_icon_file: Option<PathBuf>,
|
||||
settings: &Settings,
|
||||
) -> crate::Result<()> {
|
||||
let build_number = chrono::Utc::now().format("%Y%m%d.%H%M%S");
|
||||
let file = &mut common::create_file(&bundle_dir.join("Info.plist"))?;
|
||||
write!(
|
||||
file,
|
||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
|
||||
<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \
|
||||
\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n\
|
||||
<plist version=\"1.0\">\n\
|
||||
<dict>\n"
|
||||
)?;
|
||||
write!(
|
||||
file,
|
||||
" <key>CFBundleDevelopmentRegion</key>\n \
|
||||
<string>English</string>\n"
|
||||
)?;
|
||||
write!(
|
||||
file,
|
||||
" <key>CFBundleDisplayName</key>\n <string>{}</string>\n",
|
||||
settings.bundle_name()
|
||||
)?;
|
||||
write!(
|
||||
file,
|
||||
// Here we should only use this technique if they have specified
|
||||
// that they want to use the resources
|
||||
" <key>CFBundleExecutable</key>\n <string>__bootstrapper</string>\n"
|
||||
)?;
|
||||
if let Some(path) = bundle_icon_file {
|
||||
write!(
|
||||
file,
|
||||
" <key>CFBundleIconFile</key>\n <string>{}</string>\n",
|
||||
path.file_name().expect("No file name").to_string_lossy()
|
||||
)?;
|
||||
}
|
||||
write!(
|
||||
file,
|
||||
" <key>CFBundleIdentifier</key>\n <string>{}</string>\n",
|
||||
settings.bundle_identifier()
|
||||
)?;
|
||||
write!(
|
||||
file,
|
||||
" <key>CFBundleInfoDictionaryVersion</key>\n \
|
||||
<string>6.0</string>\n"
|
||||
)?;
|
||||
write!(
|
||||
file,
|
||||
" <key>CFBundleName</key>\n <string>{}</string>\n",
|
||||
settings.bundle_name()
|
||||
)?;
|
||||
write!(
|
||||
file,
|
||||
" <key>CFBundlePackageType</key>\n <string>APPL</string>\n"
|
||||
)?;
|
||||
write!(
|
||||
file,
|
||||
" <key>CFBundleShortVersionString</key>\n <string>{}</string>\n",
|
||||
settings.version_string()
|
||||
)?;
|
||||
write!(
|
||||
file,
|
||||
" <key>CFBundleVersion</key>\n <string>{}</string>\n",
|
||||
build_number
|
||||
)?;
|
||||
write!(file, " <key>CSResourcesFileMapped</key>\n <true/>\n")?;
|
||||
if let Some(category) = settings.app_category() {
|
||||
write!(
|
||||
file,
|
||||
" <key>LSApplicationCategoryType</key>\n \
|
||||
<string>{}</string>\n",
|
||||
category.osx_application_category_type()
|
||||
)?;
|
||||
}
|
||||
if let Some(version) = settings.osx_minimum_system_version() {
|
||||
write!(
|
||||
file,
|
||||
" <key>LSMinimumSystemVersion</key>\n \
|
||||
<string>{}</string>\n",
|
||||
version
|
||||
)?;
|
||||
}
|
||||
write!(file, " <key>LSRequiresCarbon</key>\n <true/>\n")?;
|
||||
write!(file, " <key>NSHighResolutionCapable</key>\n <true/>\n")?;
|
||||
if let Some(copyright) = settings.copyright_string() {
|
||||
write!(
|
||||
file,
|
||||
" <key>NSHumanReadableCopyright</key>\n \
|
||||
<string>{}</string>\n",
|
||||
copyright
|
||||
)?;
|
||||
}
|
||||
write!(file, "</dict>\n</plist>\n")?;
|
||||
file.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn copy_framework_from(dest_dir: &Path, framework: &str, src_dir: &Path) -> crate::Result<bool> {
|
||||
let src_name = format!("{}.framework", framework);
|
||||
let src_path = src_dir.join(&src_name);
|
||||
if src_path.exists() {
|
||||
common::copy_dir(&src_path, &dest_dir.join(&src_name))?;
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn copy_frameworks_to_bundle(bundle_directory: &Path, settings: &Settings) -> crate::Result<()> {
|
||||
let frameworks = settings.osx_frameworks();
|
||||
if frameworks.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
let dest_dir = bundle_directory.join("Frameworks");
|
||||
fs::create_dir_all(&bundle_directory)
|
||||
.chain_err(|| format!("Failed to create Frameworks directory at {:?}", dest_dir))?;
|
||||
for framework in frameworks.iter() {
|
||||
if framework.ends_with(".framework") {
|
||||
let src_path = PathBuf::from(framework);
|
||||
let src_name = src_path
|
||||
.file_name()
|
||||
.expect("Couldn't get framework filename");
|
||||
common::copy_dir(&src_path, &dest_dir.join(&src_name))?;
|
||||
continue;
|
||||
} else if framework.contains("/") {
|
||||
bail!(
|
||||
"Framework path should have .framework extension: {}",
|
||||
framework
|
||||
);
|
||||
}
|
||||
if let Some(home_dir) = dirs::home_dir() {
|
||||
if copy_framework_from(&dest_dir, framework, &home_dir.join("Library/Frameworks/"))? {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if copy_framework_from(&dest_dir, framework, &PathBuf::from("/Library/Frameworks/"))?
|
||||
|| copy_framework_from(
|
||||
&dest_dir,
|
||||
framework,
|
||||
&PathBuf::from("/Network/Library/Frameworks/"),
|
||||
)?
|
||||
{
|
||||
continue;
|
||||
}
|
||||
bail!("Could not locate {}.framework", framework);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Given a list of icon files, try to produce an ICNS file in the resources
|
||||
/// directory and return the path to it. Returns `Ok(None)` if no usable icons
|
||||
/// were provided.
|
||||
fn create_icns_file(
|
||||
resources_dir: &PathBuf,
|
||||
settings: &Settings,
|
||||
) -> crate::Result<Option<PathBuf>> {
|
||||
if settings.icon_files().count() == 0 {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// If one of the icon files is already an ICNS file, just use that.
|
||||
for icon_path in settings.icon_files() {
|
||||
let icon_path = icon_path?;
|
||||
if icon_path.extension() == Some(OsStr::new("icns")) {
|
||||
let mut dest_path = resources_dir.to_path_buf();
|
||||
dest_path.push(icon_path.file_name().expect("Could not get icon filename"));
|
||||
common::copy_file(&icon_path, &dest_path)?;
|
||||
return Ok(Some(dest_path));
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, read available images and pack them into a new ICNS file.
|
||||
let mut family = icns::IconFamily::new();
|
||||
|
||||
fn add_icon_to_family(
|
||||
icon: image::DynamicImage,
|
||||
density: u32,
|
||||
family: &mut icns::IconFamily,
|
||||
) -> io::Result<()> {
|
||||
// Try to add this image to the icon family. Ignore images whose sizes
|
||||
// don't map to any ICNS icon type; print warnings and skip images that
|
||||
// fail to encode.
|
||||
match icns::IconType::from_pixel_size_and_density(icon.width(), icon.height(), density) {
|
||||
Some(icon_type) => {
|
||||
if !family.has_icon_with_type(icon_type) {
|
||||
let icon = make_icns_image(icon)?;
|
||||
family.add_icon_with_type(&icon, icon_type)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
None => Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"No matching IconType",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
let mut images_to_resize: Vec<(image::DynamicImage, u32, u32)> = vec![];
|
||||
for icon_path in settings.icon_files() {
|
||||
let icon_path = icon_path?;
|
||||
let icon = image::open(&icon_path)?;
|
||||
let density = if common::is_retina(&icon_path) { 2 } else { 1 };
|
||||
let (w, h) = icon.dimensions();
|
||||
let orig_size = min(w, h);
|
||||
let next_size_down = 2f32.powf((orig_size as f32).log2().floor()) as u32;
|
||||
if orig_size > next_size_down {
|
||||
images_to_resize.push((icon, next_size_down, density));
|
||||
} else {
|
||||
add_icon_to_family(icon, density, &mut family)?;
|
||||
}
|
||||
}
|
||||
|
||||
for (icon, next_size_down, density) in images_to_resize {
|
||||
let icon = icon.resize_exact(next_size_down, next_size_down, image::Lanczos3);
|
||||
add_icon_to_family(icon, density, &mut family)?;
|
||||
}
|
||||
|
||||
if !family.is_empty() {
|
||||
fs::create_dir_all(resources_dir)?;
|
||||
let mut dest_path = resources_dir.clone();
|
||||
dest_path.push(settings.bundle_name());
|
||||
dest_path.set_extension("icns");
|
||||
let icns_file = BufWriter::new(File::create(&dest_path)?);
|
||||
family.write(icns_file)?;
|
||||
return Ok(Some(dest_path));
|
||||
}
|
||||
|
||||
bail!("No usable icon files found.");
|
||||
}
|
||||
|
||||
/// Converts an image::DynamicImage into an icns::Image.
|
||||
fn make_icns_image(img: image::DynamicImage) -> io::Result<icns::Image> {
|
||||
let pixel_format = match img.color() {
|
||||
image::ColorType::RGBA(8) => icns::PixelFormat::RGBA,
|
||||
image::ColorType::RGB(8) => icns::PixelFormat::RGB,
|
||||
image::ColorType::GrayA(8) => icns::PixelFormat::GrayAlpha,
|
||||
image::ColorType::Gray(8) => icns::PixelFormat::Gray,
|
||||
_ => {
|
||||
let msg = format!("unsupported ColorType: {:?}", img.color());
|
||||
return Err(io::Error::new(io::ErrorKind::InvalidData, msg));
|
||||
}
|
||||
};
|
||||
icns::Image::from_data(pixel_format, img.width(), img.height(), img.raw_pixels())
|
||||
}
|
||||
@@ -1,259 +0,0 @@
|
||||
use std::fs::{create_dir, create_dir_all, read_dir, remove_dir_all};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DirOpts {
|
||||
pub depth: u64,
|
||||
}
|
||||
|
||||
pub struct FileOpts {
|
||||
pub overwrite: bool,
|
||||
pub skip: bool,
|
||||
pub buffer_size: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Options {
|
||||
pub overwrite: bool,
|
||||
pub skip: bool,
|
||||
pub buffer_size: usize,
|
||||
pub copy_files: bool,
|
||||
pub content_only: bool,
|
||||
pub depth: u64,
|
||||
}
|
||||
|
||||
pub struct DirInfo {
|
||||
pub size: u64,
|
||||
pub files: Vec<String>,
|
||||
pub directories: Vec<String>,
|
||||
}
|
||||
|
||||
impl Default for Options {
|
||||
fn default() -> Options {
|
||||
Options {
|
||||
overwrite: false,
|
||||
skip: false,
|
||||
buffer_size: 64000,
|
||||
copy_files: false,
|
||||
content_only: false,
|
||||
depth: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for DirOpts {
|
||||
fn default() -> DirOpts {
|
||||
DirOpts { depth: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FileOpts {
|
||||
fn default() -> FileOpts {
|
||||
FileOpts {
|
||||
overwrite: false,
|
||||
skip: false,
|
||||
buffer_size: 64000,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create<P>(path: P, erase: bool) -> crate::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
if erase && path.as_ref().exists() {
|
||||
remove(&path)?;
|
||||
}
|
||||
Ok(create_dir(&path)?)
|
||||
}
|
||||
|
||||
pub fn create_all<P>(path: P, erase: bool) -> crate::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
if erase && path.as_ref().exists() {
|
||||
remove(&path)?;
|
||||
}
|
||||
Ok(create_dir_all(&path)?)
|
||||
}
|
||||
|
||||
pub fn remove<P: AsRef<Path>>(path: P) -> crate::Result<()> {
|
||||
if path.as_ref().exists() {
|
||||
Ok(remove_dir_all(path)?)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn copy_file<P, Q>(from: P, to: Q, options: &FileOpts) -> crate::Result<u64>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>,
|
||||
{
|
||||
let from = from.as_ref();
|
||||
if !from.exists() {
|
||||
if let Some(msg) = from.to_str() {
|
||||
let msg = format!("Path \"{}\" does not exist or you don't have access", msg);
|
||||
return Err(msg.into());
|
||||
}
|
||||
return Err("Path does not exist Or you don't have access!".into());
|
||||
}
|
||||
|
||||
if !from.is_file() {
|
||||
if let Some(msg) = from.to_str() {
|
||||
let msg = format!("Path \"{}\" is not a file!", msg);
|
||||
return Err(msg.into());
|
||||
}
|
||||
return Err("Path is not a file!".into());
|
||||
}
|
||||
if !options.overwrite && to.as_ref().exists() {
|
||||
if options.skip {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
if let Some(msg) = to.as_ref().to_str() {
|
||||
let msg = format!("Path \"{}\" is exist", msg);
|
||||
return Err(msg.into());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(std::fs::copy(from, to)?)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn copy<P, Q>(from: P, to: Q, options: &Options) -> crate::Result<u64>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>,
|
||||
{
|
||||
let from = from.as_ref();
|
||||
if !from.exists() {
|
||||
if let Some(msg) = from.to_str() {
|
||||
let msg = format!("Path \"{}\" does not exist or you don't have access!", msg);
|
||||
return Err(msg.into());
|
||||
}
|
||||
return Err("Path does not exist Or you don't have access!".into());
|
||||
}
|
||||
if !from.is_dir() {
|
||||
if let Some(msg) = from.to_str() {
|
||||
let msg = format!("Path \"{}\" is not a directory!", msg);
|
||||
return Err(msg.into());
|
||||
}
|
||||
return Err("Path is not a directory!".into());
|
||||
}
|
||||
let dir_name;
|
||||
if let Some(val) = from.components().last() {
|
||||
dir_name = val.as_os_str();
|
||||
} else {
|
||||
return Err("Invalid folder from".into());
|
||||
}
|
||||
let mut to: PathBuf = to.as_ref().to_path_buf();
|
||||
if !options.content_only && (!options.copy_files || to.exists()) {
|
||||
to.push(dir_name);
|
||||
}
|
||||
|
||||
let mut read_options = DirOpts::default();
|
||||
if options.depth > 0 {
|
||||
read_options.depth = options.depth;
|
||||
}
|
||||
|
||||
let dir_content = get_dir_info(from, &read_options)?;
|
||||
for directory in dir_content.directories {
|
||||
let tmp_to = Path::new(&directory).strip_prefix(from)?;
|
||||
let dir = to.join(&tmp_to);
|
||||
if !dir.exists() {
|
||||
if options.copy_files {
|
||||
create_all(dir, false)?;
|
||||
} else {
|
||||
create(dir, false)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut result: u64 = 0;
|
||||
for file in dir_content.files {
|
||||
let to = to.to_path_buf();
|
||||
let tp = Path::new(&file).strip_prefix(from)?;
|
||||
let path = to.join(&tp);
|
||||
|
||||
let file_options = FileOpts {
|
||||
overwrite: options.overwrite,
|
||||
skip: options.skip,
|
||||
buffer_size: options.buffer_size,
|
||||
};
|
||||
let mut result_copy: crate::Result<u64>;
|
||||
let mut work = true;
|
||||
|
||||
while work {
|
||||
result_copy = copy_file(&file, &path, &file_options);
|
||||
match result_copy {
|
||||
Ok(val) => {
|
||||
result += val;
|
||||
work = false;
|
||||
}
|
||||
Err(err) => {
|
||||
let err_msg = err.to_string();
|
||||
return Err(err_msg.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn get_dir_info<P>(path: P, options: &DirOpts) -> crate::Result<DirInfo>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let depth = if options.depth == 0 {
|
||||
0
|
||||
} else {
|
||||
options.depth + 1
|
||||
};
|
||||
|
||||
_get_dir_info(path, depth)
|
||||
}
|
||||
|
||||
fn _get_dir_info<P>(path: P, mut depth: u64) -> crate::Result<DirInfo>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let mut directories = Vec::new();
|
||||
let mut files = Vec::new();
|
||||
let mut size = 0;
|
||||
let item = path.as_ref().to_str();
|
||||
if item.is_none() {
|
||||
return Err("Invalid path".into());
|
||||
}
|
||||
let item = item.expect("Item had no data").to_string();
|
||||
|
||||
if path.as_ref().is_dir() {
|
||||
directories.push(item);
|
||||
if depth == 0 || depth > 1 {
|
||||
if depth > 1 {
|
||||
depth -= 1;
|
||||
}
|
||||
for entry in read_dir(&path)? {
|
||||
let _path = entry?.path();
|
||||
|
||||
match _get_dir_info(_path, depth) {
|
||||
Ok(items) => {
|
||||
let mut _files = items.files;
|
||||
let mut _directories = items.directories;
|
||||
size += items.size;
|
||||
files.append(&mut _files);
|
||||
directories.append(&mut _directories);
|
||||
}
|
||||
Err(err) => return Err(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
size = path.as_ref().metadata()?.len();
|
||||
files.push(item);
|
||||
}
|
||||
Ok(DirInfo {
|
||||
size,
|
||||
files,
|
||||
directories,
|
||||
})
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
use crate::Settings;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub fn bundle_project(_settings: &Settings) -> crate::Result<Vec<PathBuf>> {
|
||||
unimplemented!();
|
||||
}
|
||||
@@ -1,768 +0,0 @@
|
||||
use super::category::AppCategory;
|
||||
use crate::bundle::common;
|
||||
|
||||
use clap::ArgMatches;
|
||||
use error_chain::bail;
|
||||
use glob;
|
||||
use serde::Deserialize;
|
||||
use target_build_utils::TargetInfo;
|
||||
use tauri_utils::platform::target_triple;
|
||||
use toml;
|
||||
use walkdir;
|
||||
|
||||
use std;
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum PackageType {
|
||||
OsxBundle,
|
||||
#[cfg(feature = "ios")]
|
||||
IosBundle,
|
||||
#[cfg(target_os = "windows")]
|
||||
WindowsMsi,
|
||||
Deb,
|
||||
Rpm,
|
||||
#[cfg(feature = "appimage")]
|
||||
AppImage,
|
||||
#[cfg(feature = "dmg")]
|
||||
Dmg,
|
||||
}
|
||||
|
||||
impl PackageType {
|
||||
pub fn from_short_name(name: &str) -> Option<PackageType> {
|
||||
// Other types we may eventually want to support: apk
|
||||
match name {
|
||||
"deb" => Some(PackageType::Deb),
|
||||
#[cfg(feature = "ios")]
|
||||
"ios" => Some(PackageType::IosBundle),
|
||||
#[cfg(target_os = "windows")]
|
||||
"msi" => Some(PackageType::WindowsMsi),
|
||||
"osx" => Some(PackageType::OsxBundle),
|
||||
"rpm" => Some(PackageType::Rpm),
|
||||
#[cfg(feature = "appimage")]
|
||||
"appimage" => Some(PackageType::AppImage),
|
||||
#[cfg(feature = "dmg")]
|
||||
"dmg" => Some(PackageType::Dmg),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn short_name(&self) -> &'static str {
|
||||
match *self {
|
||||
PackageType::Deb => "deb",
|
||||
#[cfg(feature = "ios")]
|
||||
PackageType::IosBundle => "ios",
|
||||
#[cfg(target_os = "windows")]
|
||||
PackageType::WindowsMsi => "msi",
|
||||
PackageType::OsxBundle => "osx",
|
||||
PackageType::Rpm => "rpm",
|
||||
#[cfg(feature = "appimage")]
|
||||
PackageType::AppImage => "appimage",
|
||||
#[cfg(feature = "dmg")]
|
||||
PackageType::Dmg => "dmg",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn all() -> &'static [PackageType] {
|
||||
ALL_PACKAGE_TYPES
|
||||
}
|
||||
}
|
||||
|
||||
const ALL_PACKAGE_TYPES: &[PackageType] = &[
|
||||
PackageType::Deb,
|
||||
#[cfg(feature = "ios")]
|
||||
PackageType::IosBundle,
|
||||
#[cfg(target_os = "windows")]
|
||||
PackageType::WindowsMsi,
|
||||
PackageType::OsxBundle,
|
||||
PackageType::Rpm,
|
||||
#[cfg(feature = "dmg")]
|
||||
PackageType::Dmg,
|
||||
];
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum BuildArtifact {
|
||||
Main,
|
||||
Bin(String),
|
||||
Example(String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
struct BundleSettings {
|
||||
// General settings:
|
||||
name: Option<String>,
|
||||
identifier: Option<String>,
|
||||
icon: Option<Vec<String>>,
|
||||
version: Option<String>,
|
||||
resources: Option<Vec<String>>,
|
||||
copyright: Option<String>,
|
||||
category: Option<AppCategory>,
|
||||
short_description: Option<String>,
|
||||
long_description: Option<String>,
|
||||
script: Option<PathBuf>,
|
||||
// OS-specific settings:
|
||||
deb_depends: Option<Vec<String>>,
|
||||
osx_frameworks: Option<Vec<String>>,
|
||||
osx_minimum_system_version: Option<String>,
|
||||
// Bundles for other binaries/examples:
|
||||
bin: Option<HashMap<String, BundleSettings>>,
|
||||
example: Option<HashMap<String, BundleSettings>>,
|
||||
external_bin: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
struct MetadataSettings {
|
||||
bundle: Option<BundleSettings>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
struct PackageSettings {
|
||||
name: String,
|
||||
version: String,
|
||||
description: String,
|
||||
homepage: Option<String>,
|
||||
authors: Option<Vec<String>>,
|
||||
metadata: Option<MetadataSettings>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
struct WorkspaceSettings {
|
||||
members: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
struct CargoSettings {
|
||||
package: Option<PackageSettings>, // "Ancestor" workspace Cargo.toml files may not have package info
|
||||
workspace: Option<WorkspaceSettings>, // "Ancestor" workspace Cargo.toml files may declare workspaces
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Settings {
|
||||
package: PackageSettings,
|
||||
package_type: Option<PackageType>, // If `None`, use the default package type for this os
|
||||
target: Option<(String, TargetInfo)>,
|
||||
features: Option<Vec<String>>,
|
||||
project_out_directory: PathBuf,
|
||||
build_artifact: BuildArtifact,
|
||||
is_release: bool,
|
||||
binary_path: PathBuf,
|
||||
binary_name: String,
|
||||
bundle_settings: BundleSettings,
|
||||
}
|
||||
|
||||
impl CargoSettings {
|
||||
/*
|
||||
Try to load a set of CargoSettings from a "Cargo.toml" file in the specified directory
|
||||
*/
|
||||
fn load(dir: &PathBuf) -> crate::Result<Self> {
|
||||
let toml_path = dir.join("Cargo.toml");
|
||||
let mut toml_str = String::new();
|
||||
let mut toml_file = File::open(toml_path)?;
|
||||
toml_file.read_to_string(&mut toml_str)?;
|
||||
toml::from_str(&toml_str).map_err(|e| e.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
pub fn new(current_dir: PathBuf, matches: &ArgMatches<'_>) -> crate::Result<Self> {
|
||||
let package_type = match matches.value_of("format") {
|
||||
Some(name) => match PackageType::from_short_name(name) {
|
||||
Some(package_type) => Some(package_type),
|
||||
None => bail!("Unsupported bundle format: {}", name),
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
let build_artifact = if let Some(bin) = matches.value_of("bin") {
|
||||
BuildArtifact::Bin(bin.to_string())
|
||||
} else if let Some(example) = matches.value_of("example") {
|
||||
BuildArtifact::Example(example.to_string())
|
||||
} else {
|
||||
BuildArtifact::Main
|
||||
};
|
||||
let is_release = matches.is_present("release");
|
||||
let target = match matches.value_of("target") {
|
||||
Some(triple) => Some((triple.to_string(), TargetInfo::from_str(triple)?)),
|
||||
None => None,
|
||||
};
|
||||
let features = if matches.is_present("features") {
|
||||
Some(
|
||||
matches
|
||||
.values_of("features")
|
||||
.expect("Couldn't get the features")
|
||||
.map(|s| s.to_string())
|
||||
.collect(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let cargo_settings = CargoSettings::load(¤t_dir)?;
|
||||
let package = match cargo_settings.package {
|
||||
Some(package_info) => package_info,
|
||||
None => bail!("No 'package' info found in 'Cargo.toml'"),
|
||||
};
|
||||
let workspace_dir = Settings::get_workspace_dir(¤t_dir);
|
||||
let target_dir = Settings::get_target_dir(&workspace_dir, &target, is_release, &build_artifact);
|
||||
let bundle_settings = if let Some(bundle_settings) = package
|
||||
.metadata
|
||||
.as_ref()
|
||||
.and_then(|metadata| metadata.bundle.as_ref())
|
||||
{
|
||||
bundle_settings.clone()
|
||||
} else {
|
||||
bail!("No [package.metadata.bundle] section in Cargo.toml");
|
||||
};
|
||||
let (bundle_settings, binary_name) = match build_artifact {
|
||||
BuildArtifact::Main => (bundle_settings, package.name.clone()),
|
||||
BuildArtifact::Bin(ref name) => (
|
||||
bundle_settings_from_table(&bundle_settings.bin, "bin", name)?,
|
||||
name.clone(),
|
||||
),
|
||||
BuildArtifact::Example(ref name) => (
|
||||
bundle_settings_from_table(&bundle_settings.example, "example", name)?,
|
||||
name.clone(),
|
||||
),
|
||||
};
|
||||
let binary_name = if cfg!(windows) {
|
||||
format!("{}.{}", &binary_name, "exe")
|
||||
} else {
|
||||
binary_name
|
||||
};
|
||||
let binary_path = target_dir.join(&binary_name);
|
||||
|
||||
let bundle_settings = add_external_bin(bundle_settings)?;
|
||||
|
||||
Ok(Settings {
|
||||
package,
|
||||
package_type,
|
||||
target,
|
||||
features,
|
||||
build_artifact,
|
||||
is_release,
|
||||
project_out_directory: target_dir,
|
||||
binary_path,
|
||||
binary_name,
|
||||
bundle_settings,
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
The target_dir where binaries will be compiled to by cargo can vary:
|
||||
- this directory is a member of a workspace project
|
||||
- overridden by CARGO_TARGET_DIR environment variable
|
||||
- specified in build.target-dir configuration key
|
||||
- if the build is a 'release' or 'debug' build
|
||||
|
||||
This function determines where 'target' dir is and suffixes it with 'release' or 'debug'
|
||||
to determine where the compiled binary will be located.
|
||||
*/
|
||||
fn get_target_dir(
|
||||
project_root_dir: &PathBuf,
|
||||
target: &Option<(String, TargetInfo)>,
|
||||
is_release: bool,
|
||||
build_artifact: &BuildArtifact,
|
||||
) -> PathBuf {
|
||||
let mut path = project_root_dir.join("target");
|
||||
if let &Some((ref triple, _)) = target {
|
||||
path.push(triple);
|
||||
}
|
||||
path.push(if is_release { "release" } else { "debug" });
|
||||
if let &BuildArtifact::Example(_) = build_artifact {
|
||||
path.push("examples");
|
||||
}
|
||||
path
|
||||
}
|
||||
|
||||
/*
|
||||
The specification of the Cargo.toml Manifest that covers the "workspace" section is here:
|
||||
https://doc.rust-lang.org/cargo/reference/manifest.html#the-workspace-section
|
||||
|
||||
Determining if the current project folder is part of a workspace:
|
||||
- Walk up the file system, looking for a Cargo.toml file.
|
||||
- Stop at the first one found.
|
||||
- If one is found before reaching "/" then this folder belongs to that parent workspace,
|
||||
if it contains a [workspace] entry and the project crate name is listed on the "members" array
|
||||
*/
|
||||
pub fn get_workspace_dir(current_dir: &PathBuf) -> PathBuf {
|
||||
let mut dir = current_dir.clone();
|
||||
let project_name = CargoSettings::load(&dir).unwrap().package.unwrap().name;
|
||||
|
||||
while dir.pop() {
|
||||
match CargoSettings::load(&dir) {
|
||||
Ok(cargo_settings) => match cargo_settings.workspace {
|
||||
Some(workspace_settings) => {
|
||||
if workspace_settings.members.is_some()
|
||||
&& workspace_settings
|
||||
.members
|
||||
.expect("Couldn't get members")
|
||||
.iter()
|
||||
.any(|member| member.as_str() == project_name)
|
||||
{
|
||||
return dir;
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
},
|
||||
Err(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing found walking up the file system, return the starting directory
|
||||
current_dir.clone()
|
||||
}
|
||||
|
||||
/// Returns the directory where the bundle should be placed.
|
||||
pub fn project_out_directory(&self) -> &Path {
|
||||
&self.project_out_directory
|
||||
}
|
||||
|
||||
/// Returns the architecture for the binary being bundled (e.g. "arm" or
|
||||
/// "x86" or "x86_64").
|
||||
pub fn binary_arch(&self) -> &str {
|
||||
if let Some((_, ref info)) = self.target {
|
||||
info.target_arch()
|
||||
} else {
|
||||
std::env::consts::ARCH
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the file name of the binary being bundled.
|
||||
pub fn binary_name(&self) -> &str {
|
||||
&self.binary_name
|
||||
}
|
||||
|
||||
/// Returns the path to the binary being bundled.
|
||||
pub fn binary_path(&self) -> &Path {
|
||||
&self.binary_path
|
||||
}
|
||||
|
||||
/// If a specific package type was specified by the command-line, returns
|
||||
/// that package type; otherwise, if a target triple was specified by the
|
||||
/// command-line, returns the native package type(s) for that target;
|
||||
/// otherwise, returns the native package type(s) for the host platform.
|
||||
/// Fails if the host/target's native package type is not supported.
|
||||
pub fn package_types(&self) -> crate::Result<Vec<PackageType>> {
|
||||
if let Some(package_type) = self.package_type {
|
||||
Ok(vec![package_type])
|
||||
} else {
|
||||
let target_os = if let Some((_, ref info)) = self.target {
|
||||
info.target_os()
|
||||
} else {
|
||||
std::env::consts::OS
|
||||
};
|
||||
match target_os {
|
||||
"macos" => Ok(vec![PackageType::OsxBundle]),
|
||||
#[cfg(feature = "ios")]
|
||||
"ios" => Ok(vec![PackageType::IosBundle]),
|
||||
"linux" => Ok(vec![PackageType::Deb]), // TODO: Do Rpm too, once it's implemented.
|
||||
#[cfg(target_os = "windows")]
|
||||
"windows" => Ok(vec![PackageType::WindowsMsi]),
|
||||
os => bail!("Native {} bundles not yet supported.", os),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// If the bundle is being cross-compiled, returns the target triple string
|
||||
/// (e.g. `"x86_64-apple-darwin"`). If the bundle is targeting the host
|
||||
/// environment, returns `None`.
|
||||
pub fn target_triple(&self) -> Option<&str> {
|
||||
match self.target {
|
||||
Some((ref triple, _)) => Some(triple.as_str()),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the features that is being built.
|
||||
pub fn build_features(&self) -> Option<Vec<String>> {
|
||||
self.features.to_owned()
|
||||
}
|
||||
|
||||
/// Returns the artifact that is being bundled.
|
||||
pub fn build_artifact(&self) -> &BuildArtifact {
|
||||
&self.build_artifact
|
||||
}
|
||||
|
||||
/// Returns true if the bundle is being compiled in release mode, false if
|
||||
/// it's being compiled in debug mode.
|
||||
pub fn is_release_build(&self) -> bool {
|
||||
self.is_release
|
||||
}
|
||||
|
||||
pub fn bundle_name(&self) -> &str {
|
||||
self
|
||||
.bundle_settings
|
||||
.name
|
||||
.as_ref()
|
||||
.unwrap_or(&self.package.name)
|
||||
}
|
||||
|
||||
pub fn bundle_identifier(&self) -> &str {
|
||||
self
|
||||
.bundle_settings
|
||||
.identifier
|
||||
.as_ref()
|
||||
.map(String::as_str)
|
||||
.unwrap_or("")
|
||||
}
|
||||
|
||||
/// Returns an iterator over the icon files to be used for this bundle.
|
||||
pub fn icon_files(&self) -> ResourcePaths<'_> {
|
||||
match self.bundle_settings.icon {
|
||||
Some(ref paths) => ResourcePaths::new(paths.as_slice(), false),
|
||||
None => ResourcePaths::new(&[], false),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an iterator over the resource files to be included in this
|
||||
/// bundle.
|
||||
pub fn resource_files(&self) -> ResourcePaths<'_> {
|
||||
match self.bundle_settings.resources {
|
||||
Some(ref paths) => ResourcePaths::new(paths.as_slice(), true),
|
||||
None => ResourcePaths::new(&[], true),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an iterator over the external binaries to be included in this
|
||||
/// bundle.
|
||||
pub fn external_binaries(&self) -> ResourcePaths<'_> {
|
||||
match self.bundle_settings.external_bin {
|
||||
Some(ref paths) => ResourcePaths::new(paths.as_slice(), true),
|
||||
None => ResourcePaths::new(&[], true),
|
||||
}
|
||||
}
|
||||
|
||||
// copy external binaries to a path.
|
||||
pub fn copy_binaries(&self, path: &Path) -> crate::Result<()> {
|
||||
for src in self.external_binaries() {
|
||||
let src = src?;
|
||||
let dest = path.join(
|
||||
src
|
||||
.file_name()
|
||||
.expect("failed to extract external binary filename"),
|
||||
);
|
||||
common::copy_file(&src, &dest)
|
||||
.map_err(|_| format!("Failed to copy external binary {:?}", src))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// copy resources to a path
|
||||
pub fn copy_resources(&self, path: &Path) -> crate::Result<()> {
|
||||
for src in self.resource_files() {
|
||||
let src = src?;
|
||||
let dest = path.join(common::resource_relpath(&src));
|
||||
common::copy_file(&src, &dest)
|
||||
.map_err(|_| format!("Failed to copy resource file {:?}", src))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn version_string(&self) -> &str {
|
||||
self
|
||||
.bundle_settings
|
||||
.version
|
||||
.as_ref()
|
||||
.unwrap_or(&self.package.version)
|
||||
}
|
||||
|
||||
pub fn copyright_string(&self) -> Option<&str> {
|
||||
self.bundle_settings.copyright.as_ref().map(String::as_str)
|
||||
}
|
||||
|
||||
pub fn author_names(&self) -> &[String] {
|
||||
match self.package.authors {
|
||||
Some(ref names) => names.as_slice(),
|
||||
None => &[],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn authors_comma_separated(&self) -> Option<String> {
|
||||
let names = self.author_names();
|
||||
if names.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(names.join(", "))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn homepage_url(&self) -> &str {
|
||||
&self
|
||||
.package
|
||||
.homepage
|
||||
.as_ref()
|
||||
.map(String::as_str)
|
||||
.unwrap_or("")
|
||||
}
|
||||
|
||||
pub fn app_category(&self) -> Option<AppCategory> {
|
||||
self.bundle_settings.category
|
||||
}
|
||||
|
||||
pub fn short_description(&self) -> &str {
|
||||
self
|
||||
.bundle_settings
|
||||
.short_description
|
||||
.as_ref()
|
||||
.unwrap_or(&self.package.description)
|
||||
}
|
||||
|
||||
pub fn long_description(&self) -> Option<&str> {
|
||||
self
|
||||
.bundle_settings
|
||||
.long_description
|
||||
.as_ref()
|
||||
.map(String::as_str)
|
||||
}
|
||||
|
||||
pub fn debian_dependencies(&self) -> &[String] {
|
||||
match self.bundle_settings.deb_depends {
|
||||
Some(ref dependencies) => dependencies.as_slice(),
|
||||
None => &[],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn osx_frameworks(&self) -> &[String] {
|
||||
match self.bundle_settings.osx_frameworks {
|
||||
Some(ref frameworks) => frameworks.as_slice(),
|
||||
None => &[],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn osx_minimum_system_version(&self) -> Option<&str> {
|
||||
self
|
||||
.bundle_settings
|
||||
.osx_minimum_system_version
|
||||
.as_ref()
|
||||
.map(String::as_str)
|
||||
}
|
||||
}
|
||||
|
||||
fn bundle_settings_from_table(
|
||||
opt_map: &Option<HashMap<String, BundleSettings>>,
|
||||
map_name: &str,
|
||||
bundle_name: &str,
|
||||
) -> crate::Result<BundleSettings> {
|
||||
if let Some(bundle_settings) = opt_map.as_ref().and_then(|map| map.get(bundle_name)) {
|
||||
Ok(bundle_settings.clone())
|
||||
} else {
|
||||
bail!(
|
||||
"No [package.metadata.bundle.{}.{}] section in Cargo.toml",
|
||||
map_name,
|
||||
bundle_name
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn add_external_bin(bundle_settings: BundleSettings) -> crate::Result<BundleSettings> {
|
||||
let target_triple = target_triple()?;
|
||||
let mut win_paths = Vec::new();
|
||||
let external_bin = match bundle_settings.external_bin {
|
||||
Some(paths) => {
|
||||
for curr_path in paths.iter() {
|
||||
win_paths.push(format!(
|
||||
"{}-{}{}",
|
||||
curr_path,
|
||||
target_triple,
|
||||
if cfg!(windows) { ".exe" } else { "" }
|
||||
));
|
||||
}
|
||||
Some(win_paths)
|
||||
}
|
||||
None => Some(vec![String::from("")]),
|
||||
};
|
||||
|
||||
Ok(BundleSettings {
|
||||
external_bin,
|
||||
..bundle_settings
|
||||
})
|
||||
}
|
||||
|
||||
pub struct ResourcePaths<'a> {
|
||||
pattern_iter: std::slice::Iter<'a, String>,
|
||||
glob_iter: Option<glob::Paths>,
|
||||
walk_iter: Option<walkdir::IntoIter>,
|
||||
allow_walk: bool,
|
||||
}
|
||||
|
||||
impl<'a> ResourcePaths<'a> {
|
||||
fn new(patterns: &'a [String], allow_walk: bool) -> ResourcePaths<'a> {
|
||||
ResourcePaths {
|
||||
pattern_iter: patterns.iter(),
|
||||
glob_iter: None,
|
||||
walk_iter: None,
|
||||
allow_walk,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for ResourcePaths<'a> {
|
||||
type Item = crate::Result<PathBuf>;
|
||||
|
||||
fn next(&mut self) -> Option<crate::Result<PathBuf>> {
|
||||
loop {
|
||||
if let Some(ref mut walk_entries) = self.walk_iter {
|
||||
if let Some(entry) = walk_entries.next() {
|
||||
let entry = match entry {
|
||||
Ok(entry) => entry,
|
||||
Err(error) => return Some(Err(crate::Error::from(error))),
|
||||
};
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
continue;
|
||||
}
|
||||
return Some(Ok(path.to_path_buf()));
|
||||
}
|
||||
}
|
||||
self.walk_iter = None;
|
||||
if let Some(ref mut glob_paths) = self.glob_iter {
|
||||
if let Some(glob_result) = glob_paths.next() {
|
||||
let path = match glob_result {
|
||||
Ok(path) => path,
|
||||
Err(error) => return Some(Err(crate::Error::from(error))),
|
||||
};
|
||||
if path.is_dir() {
|
||||
if self.allow_walk {
|
||||
let walk = walkdir::WalkDir::new(path);
|
||||
self.walk_iter = Some(walk.into_iter());
|
||||
continue;
|
||||
} else {
|
||||
let msg = format!("{:?} is a directory", path);
|
||||
return Some(Err(crate::Error::from(msg)));
|
||||
}
|
||||
}
|
||||
return Some(Ok(path));
|
||||
}
|
||||
}
|
||||
self.glob_iter = None;
|
||||
if let Some(pattern) = self.pattern_iter.next() {
|
||||
let glob = match glob::glob(pattern) {
|
||||
Ok(glob) => glob,
|
||||
Err(error) => return Some(Err(crate::Error::from(error))),
|
||||
};
|
||||
self.glob_iter = Some(glob);
|
||||
continue;
|
||||
}
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{AppCategory, BundleSettings, CargoSettings};
|
||||
use toml;
|
||||
|
||||
#[test]
|
||||
fn parse_cargo_toml() {
|
||||
let toml_str = "\
|
||||
[package]\n\
|
||||
name = \"example\"\n\
|
||||
version = \"0.1.0\"\n\
|
||||
authors = [\"Jane Doe\"]\n\
|
||||
license = \"MIT\"\n\
|
||||
description = \"An example application.\"\n\
|
||||
build = \"build.rs\"\n\
|
||||
\n\
|
||||
[package.metadata.bundle]\n\
|
||||
name = \"Example Application\"\n\
|
||||
identifier = \"com.example.app\"\n\
|
||||
resources = [\"data\", \"foo/bar\"]\n\
|
||||
category = \"Puzzle Game\"\n\
|
||||
long_description = \"\"\"\n\
|
||||
This is an example of a\n\
|
||||
simple application.\n\
|
||||
\"\"\"\n\
|
||||
\n\
|
||||
[dependencies]\n\
|
||||
rand = \"0.4\"\n";
|
||||
let cargo_settings: CargoSettings = toml::from_str(toml_str).unwrap();
|
||||
let package = cargo_settings.package.expect("Couldn't get package");
|
||||
assert_eq!(package.name, "example");
|
||||
assert_eq!(package.version, "0.1.0");
|
||||
assert_eq!(package.description, "An example application.");
|
||||
assert_eq!(package.homepage, None);
|
||||
assert_eq!(package.authors, Some(vec!["Jane Doe".to_string()]));
|
||||
assert!(package.metadata.is_some());
|
||||
let metadata = package
|
||||
.metadata
|
||||
.as_ref()
|
||||
.expect("Failed to get metadata ref");
|
||||
assert!(metadata.bundle.is_some());
|
||||
let bundle = metadata.bundle.as_ref().expect("Failed to get bundle ref");
|
||||
assert_eq!(bundle.name, Some("Example Application".to_string()));
|
||||
assert_eq!(bundle.identifier, Some("com.example.app".to_string()));
|
||||
assert_eq!(bundle.icon, None);
|
||||
assert_eq!(bundle.version, None);
|
||||
assert_eq!(
|
||||
bundle.resources,
|
||||
Some(vec!["data".to_string(), "foo/bar".to_string()])
|
||||
);
|
||||
assert_eq!(bundle.category, Some(AppCategory::PuzzleGame));
|
||||
assert_eq!(
|
||||
bundle.long_description,
|
||||
Some(
|
||||
"This is an example of a\n\
|
||||
simple application.\n"
|
||||
.to_string()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_bin_and_example_bundles() {
|
||||
let toml_str = "\
|
||||
[package]\n\
|
||||
name = \"example\"\n\
|
||||
version = \"0.1.0\"\n\
|
||||
description = \"An example application.\"\n\
|
||||
\n\
|
||||
[package.metadata.bundle.bin.foo]\n\
|
||||
name = \"Foo App\"\n\
|
||||
\n\
|
||||
[package.metadata.bundle.bin.bar]\n\
|
||||
name = \"Bar App\"\n\
|
||||
\n\
|
||||
[package.metadata.bundle.example.baz]\n\
|
||||
name = \"Baz Example\"\n\
|
||||
\n\
|
||||
[[bin]]\n\
|
||||
name = \"foo\"\n
|
||||
\n\
|
||||
[[bin]]\n\
|
||||
name = \"bar\"\n
|
||||
\n\
|
||||
[[example]]\n\
|
||||
name = \"baz\"\n";
|
||||
let cargo_settings: CargoSettings = toml::from_str(toml_str).expect("Failed to read from toml");
|
||||
assert!(cargo_settings.package.is_some());
|
||||
let package = cargo_settings
|
||||
.package
|
||||
.as_ref()
|
||||
.expect("Failed to get package ref");
|
||||
assert!(package.metadata.is_some());
|
||||
let metadata = package
|
||||
.metadata
|
||||
.as_ref()
|
||||
.expect("Failed to get metadata ref");
|
||||
assert!(metadata.bundle.is_some());
|
||||
let bundle = metadata.bundle.as_ref().expect("Failed to get bundle ref");
|
||||
assert!(bundle.example.is_some());
|
||||
|
||||
let bins = bundle.bin.as_ref().expect("Failed to get bin ref");
|
||||
assert!(bins.contains_key("foo"));
|
||||
let foo: &BundleSettings = bins.get("foo").expect("Failed to get foo bundle settings");
|
||||
assert_eq!(foo.name, Some("Foo App".to_string()));
|
||||
assert!(bins.contains_key("bar"));
|
||||
let bar: &BundleSettings = bins.get("bar").expect("Failed to get bar bundle settings");
|
||||
assert_eq!(bar.name, Some("Bar App".to_string()));
|
||||
|
||||
let examples = bundle.example.as_ref().expect("Failed to get example ref");
|
||||
assert!(examples.contains_key("baz"));
|
||||
let baz: &BundleSettings = examples
|
||||
.get("baz")
|
||||
.expect("Failed to get baz bundle settings");
|
||||
assert_eq!(baz.name, Some("Baz Example".to_string()));
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
mkdir -p {{app_name}}.AppDir
|
||||
cp -r bundle/deb/{{bundle_name}}/data/usr {{app_name}}.AppDir
|
||||
cp {{app_name}} {{app_name}}.AppDir/AppRun
|
||||
|
||||
cd {{app_name}}.AppDir
|
||||
|
||||
cp usr/share/icons/hicolor/256x265/apps/{{app_name}}.png {{app_name}}.png
|
||||
|
||||
echo '[Desktop Entry]' > {{app_name}}.desktop
|
||||
echo 'Version=1.0' >> {{app_name}}.desktop
|
||||
echo 'Comment=A Tauri App' >> {{app_name}}.desktop
|
||||
echo 'Exec={{app_name}}' >> {{app_name}}.desktop
|
||||
echo 'Icon={{app_name}}' >> {{app_name}}.desktop
|
||||
echo 'Name={{app_name_uppercase}}' >> {{app_name}}.desktop
|
||||
echo 'Terminal=false' >> {{app_name}}.desktop
|
||||
echo 'Type=Application' >> {{app_name}}.desktop
|
||||
echo 'Categories=X-Web;' >> {{app_name}}.desktop
|
||||
|
||||
cp {{app_name}}.desktop {{app_name}}.AppDir/usr/share/applications/{{app_name}}.desktop
|
||||
|
||||
cd ..
|
||||
|
||||
mksquashfs {{app_name}}.AppDir {{app_name}}.squashfs -root-owned -noappend
|
||||
# cat runtime >> {{app_name}}.AppImage
|
||||
cat {{app_name}}.squashfs >> {{app_name}}.AppImage
|
||||
chmod a+x {{app_name}}.AppImage
|
||||
@@ -1,144 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
# using sh because modern MacOS ships with zsh not bash
|
||||
# some patterns "lifted" from:
|
||||
# - https://github.com/andreyvit/create-dmg/blob/master/create-dmg
|
||||
# - https://stackoverflow.com/a/97025
|
||||
# - https://github.com/sindresorhus/create-dmg/blob/master/cli.js
|
||||
|
||||
set -e
|
||||
MACOS_APP_NAME="{{app_name_upcase}}"
|
||||
MACOS_APP_DIR="bundle/osx/{{app_name}}.app"
|
||||
ICNS_FILE="app.app/Contents/Resources/icon.icns"
|
||||
DMG_TEMP_NAME=/tmp/temp.dmg
|
||||
MOUNT_DIR="/Volumes/${MACOS_APP_NAME}"
|
||||
|
||||
# Create the image
|
||||
echo "Creating disk image..."
|
||||
test -f "${DMG_TEMP_NAME}" && rm -f "${DMG_TEMP_NAME}"
|
||||
test -f "${MACOS_APP_NAME}.dmg" && rm -f "${MACOS_APP_NAME}.dmg"
|
||||
|
||||
hdiutil create ${DMG_TEMP_NAME} -srcfolder bundle/osx -volname "${MACOS_APP_NAME}" -ov -format UDRW -size 10000k -fs HFS+ -fsargs "-c c=64,a=16,e=16" -anyowners -nospotlight
|
||||
|
||||
# mount it
|
||||
echo "Mounting disk image..."
|
||||
# try unmount dmg if it was mounted previously (e.g. developer mounted dmg, installed app and forgot to unmount it)
|
||||
DEV_NAME=$(hdiutil info | egrep --color=never '^/dev/' | sed 1q | awk '{print $1}')
|
||||
test -d "${MOUNT_DIR}" && hdiutil detach "${DEV_NAME}"
|
||||
|
||||
device=$(hdiutil attach -readwrite -noverify -noautoopen "${DMG_TEMP_NAME}" | \
|
||||
egrep '^/dev/' | sed 1q | awk '{print $1}')
|
||||
|
||||
sleep 5
|
||||
|
||||
|
||||
bless --folder "${MOUNT_DIR}" -label "${MACOS_APP_NAME}"
|
||||
|
||||
test -d "${MOUNT_DIR}/.background" || mkdir "${MOUNT_DIR}/.background"
|
||||
test -f ../../icons/bg.png && cp ../../icons/bg.png "${MOUNT_DIR}"/.background/bg.png
|
||||
|
||||
# I couldn't get DeRez and Rez to work. Leaving it here in case its helpful in the future.
|
||||
### DeRez -only icns "${MOUNT_DIR}/.VolumeIcon.icns" > "${MOUNT_DIR}/.VolumeIcon.rsrc"
|
||||
### Rez -append "${MOUNT_DIR}/.VolumeIcon.rsrc" -o my_app.dmg
|
||||
|
||||
# This Works
|
||||
cp '../../icons/icon.icns' "${MOUNT_DIR}/.VolumeIcon.icns"
|
||||
VERSION=$(sw_vers -productVersion | cut -d'.' -f2)
|
||||
|
||||
if [ "$VERSION" -gt 12 ]; then
|
||||
echo "Using SIPS v10.13+"
|
||||
sips -i "${MOUNT_DIR}/.VolumeIcon.icns" # 10.13+
|
||||
else
|
||||
echo "Using SIPS v10.12-"
|
||||
sips --addIcon "${MOUNT_DIR}/.VolumeIcon.icns"
|
||||
fi
|
||||
|
||||
SetFile -c icnC "${MOUNT_DIR}/.VolumeIcon.icns"
|
||||
SetFile -a C "${MOUNT_DIR}"
|
||||
|
||||
# Doesn't stick after the renaming
|
||||
### ./seticon "${MOUNT_DIR}/${ICNS_FILE}" "${DMG_TEMP_NAME}"
|
||||
|
||||
# This does not work
|
||||
### echo "read 'icns' (-16455) \"${MOUNT_DIR}/.VolumeIcon.icns\";" >> Icon.rsrc
|
||||
### Rez -a Icon.rsrc -o FileName.ext
|
||||
### DeRez -only icns "${MOUNT_DIR}/.VolumeIcon.icns" > "${MOUNT_DIR}/.Icon.rsrc"
|
||||
### Rez -a Icon.rsrc -o "${MOUNT_DIR}/Icon$'\r'"
|
||||
### SetFile -c icnC "${MOUNT_DIR}/.Icon.rsrc"
|
||||
### SetFile -a C "${MOUNT_DIR}"
|
||||
### SetFile -a "${MOUNT_DIR}/Icon$'\r'"
|
||||
|
||||
|
||||
# this is from
|
||||
# https://stackoverflow.com/questions/8371790/how-to-set-icon-on-file-or-directory-using-cli-on-os-x#8375093
|
||||
### iconSource='../../icons/icon.icns'
|
||||
### iconDestination="${MOUNT_DIR}"
|
||||
### icon=/tmp/$(basename $iconSource)
|
||||
### rsrc=/tmp/icon.rsrc
|
||||
|
||||
# Create icon from the iconSource
|
||||
### cp $iconSource $icon
|
||||
|
||||
# Add icon to image file, meaning use itself as the icon
|
||||
### sips -i $icon
|
||||
|
||||
# Take that icon and put it into a rsrc file
|
||||
### DeRez -only icns $icon > $rsrc
|
||||
|
||||
# Apply the rsrc file to
|
||||
### SetFile -a C "${iconDestination}"
|
||||
|
||||
# Create the magical Icon\r file
|
||||
### touch "${iconDestination}/Icon$'\r'"
|
||||
### Rez -append $rsrc -o "${iconDestination}/Icon?"
|
||||
### SetFile -a V "${iconDestination}/Icon$'\r'"
|
||||
|
||||
echo '
|
||||
tell application "Finder"
|
||||
tell disk "'${MACOS_APP_NAME}'"
|
||||
open
|
||||
set current view of container window to icon view
|
||||
set toolbar visible of container window to false
|
||||
set statusbar visible of container window to false
|
||||
set the bounds of container window to {400, 100, 885, 430}
|
||||
set theViewOptions to the icon view options of container window
|
||||
set arrangement of theViewOptions to not arranged
|
||||
set icon size of theViewOptions to 110
|
||||
set background picture of theViewOptions to file ".background:'bg.png'"
|
||||
make new alias file at container window to POSIX file "/Applications" with properties {name:"Applications"}
|
||||
set position of item "'app.app'" of container window to {100, 100}
|
||||
set position of item "Applications" of container window to {375, 100}
|
||||
update without registering applications
|
||||
delay 5
|
||||
close
|
||||
end tell
|
||||
end tell
|
||||
' | osascript
|
||||
chmod -Rf go-w /Volumes/"${MACOS_APP_NAME}"
|
||||
|
||||
sync
|
||||
sync
|
||||
|
||||
hdiutil detach ${device}
|
||||
|
||||
# Some variations on format:
|
||||
## UDZO – Compressed image (default)
|
||||
## UDRO – Read-only image
|
||||
## UDBZ – Better compressed image
|
||||
## UDRW – Read/Write image
|
||||
|
||||
hdiutil convert "${DMG_TEMP_NAME}" -format UDBZ -imagekey zlib-level=9 -o "${MACOS_APP_NAME}"
|
||||
|
||||
./seticon ../../icons/icon.icns "${MACOS_APP_NAME}.dmg"
|
||||
|
||||
rm -f ${DMG_TEMP_NAME}
|
||||
|
||||
# This seems not to work as well at maintaining the icons :(
|
||||
zip -r "${MACOS_APP_NAME}.dmg.zip" "${MACOS_APP_NAME}.dmg"
|
||||
|
||||
|
||||
# FUTURE WORK
|
||||
# SIGNER=$(security find-identity -v -p codesigning)
|
||||
# codesign --sign 'Mac Developer' "${DMG_TEMP_NAME}"
|
||||
|
||||
# Add a license with Rez
|
||||
# http://www.owsiak.org/adding-license-to-a-dmg-file-in-5-minutes-or-less/
|
||||
@@ -1,114 +0,0 @@
|
||||
<?if $(sys.BUILDARCH)="x86"?>
|
||||
<?define Win64 = "no" ?>
|
||||
<?define PlatformProgramFilesFolder = "ProgramFilesFolder" ?>
|
||||
<?elseif $(sys.BUILDARCH)="x64"?>
|
||||
<?define Win64 = "yes" ?>
|
||||
<?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
|
||||
<?else?>
|
||||
<?error Unsupported value of sys.BUILDARCH=$(sys.BUILDARCH)?>
|
||||
<?endif?>
|
||||
|
||||
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
|
||||
|
||||
<Product
|
||||
Id="*"
|
||||
Name="{{{product_name}}}"
|
||||
UpgradeCode="{{{upgrade_code}}}"
|
||||
Language="1033"
|
||||
Codepage="1252"
|
||||
Manufacturer="{{{manufacturer}}}"
|
||||
Version="{{{version}}}">
|
||||
|
||||
<Package Id="*"
|
||||
Keywords="Installer"
|
||||
InstallerVersion="450"
|
||||
Languages="1033"
|
||||
Compressed="yes"
|
||||
InstallScope="perMachine"
|
||||
SummaryCodepage="1252"/>
|
||||
|
||||
<Media Id="1" Cabinet="app.cab" EmbedCab="yes" />
|
||||
|
||||
<WixVariable Id="WixUIBannerBmp" Value="{{{icon_path}}}" />
|
||||
|
||||
<Icon Id="ProductIcon" SourceFile="{{{icon_path}}}"/>
|
||||
<Property Id="ARPPRODUCTICON" Value="ProductIcon" />
|
||||
<Property Id="ARPNOREPAIR" Value="yes" Secure="yes" /> <!-- Remove repair -->
|
||||
<Property Id="ARPNOMODIFY" Value="yes" Secure="yes" /> <!-- Remove modify -->
|
||||
|
||||
|
||||
<Directory Id="TARGETDIR" Name="SourceDir">
|
||||
<Directory Id="$(var.PlatformProgramFilesFolder)" Name="PFiles">
|
||||
<Directory Id="APPLICATIONFOLDER" Name="{{{product_name}}}">
|
||||
<Component Id="Path" Guid="{{{path_component_guid}}}" Win64="$(var.Win64)" KeyPath="yes">
|
||||
<File Id="PathFile" Source="{{{app_exe_source}}}" />
|
||||
</Component>
|
||||
{{#each external_binaries as |external_bin| ~}}
|
||||
<Component Id="{{ external_bin.id }}" Guid="{{external_bin.guid}}" Win64="$(var.Win64)" KeyPath="yes">
|
||||
<File Id="PathFile_{{ external_bin.id }}" Source="{{external_bin.path}}" />
|
||||
</Component>
|
||||
{{/each~}}
|
||||
{{{resources}}}
|
||||
</Directory>
|
||||
</Directory>
|
||||
</Directory>
|
||||
|
||||
<DirectoryRef Id="APPLICATIONFOLDER">
|
||||
<Component Id="CMP_ReadFileShortcut"
|
||||
Guid="1AF06B42-CD42-4AED-959F-36DB5E512046">
|
||||
|
||||
<Shortcut Id="UninstallShortcut"
|
||||
Name="Uninstall {{{product_name}}}"
|
||||
Description="Uninstalls {{{product_name}}}"
|
||||
Target="[System64Folder]msiexec.exe"
|
||||
Arguments="/x [ProductCode]" />
|
||||
|
||||
<RemoveFolder Id="RemoveDIR_Shortcuts"
|
||||
On="uninstall" />
|
||||
|
||||
<RegistryValue Root="HKCU"
|
||||
Key="Software\{{{manufacturer}}}\{{{product_name}}}"
|
||||
Name="installed"
|
||||
Type="integer"
|
||||
Value="1"
|
||||
KeyPath="yes" />
|
||||
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
|
||||
<Feature
|
||||
Id="MainProgram"
|
||||
Title="Application"
|
||||
Description="Installs {{{product_name}}}."
|
||||
Level="1"
|
||||
ConfigurableDirectory="APPLICATIONFOLDER"
|
||||
AllowAdvertise="no"
|
||||
Display="expand"
|
||||
Absent="disallow">
|
||||
|
||||
{{#each resource_file_ids as |resource_file_id| ~}}
|
||||
<ComponentRef Id="{{ resource_file_id }}"/>
|
||||
{{/each~}}
|
||||
|
||||
<Feature Id="ShortcutsFeature"
|
||||
Title="Shortcuts"
|
||||
Level="1">
|
||||
<ComponentRef Id="CMP_ReadFileShortcut" />
|
||||
</Feature>
|
||||
|
||||
<Feature
|
||||
Id="Environment"
|
||||
Title="PATH Environment Variable"
|
||||
Description="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."
|
||||
Level="1"
|
||||
Absent="allow">
|
||||
<ComponentRef Id="Path"/>
|
||||
{{#each external_binaries as |external_bin| ~}}
|
||||
<ComponentRef Id="{{ external_bin.id }}"/>
|
||||
{{/each~}}
|
||||
</Feature>
|
||||
</Feature>
|
||||
|
||||
<SetProperty Id="ARPINSTALLLOCATION" Value="[APPLICATIONFOLDER]" After="CostFinalize"/>
|
||||
</Product>
|
||||
</Wix>
|
||||
Binary file not shown.
@@ -1,608 +0,0 @@
|
||||
use super::common;
|
||||
use super::path_utils::{copy, Options};
|
||||
use super::settings::Settings;
|
||||
|
||||
use handlebars::{to_json, Handlebars};
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use serde::Serialize;
|
||||
use sha2::Digest;
|
||||
use uuid::Uuid;
|
||||
use zip::ZipArchive;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::fs::{create_dir_all, remove_dir_all, write, File};
|
||||
use std::io::{BufRead, BufReader, Cursor, Read, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
// URLS for the WIX toolchain. Can be used for crossplatform compilation.
|
||||
pub const WIX_URL: &str =
|
||||
"https://github.com/wixtoolset/wix3/releases/download/wix3111rtm/wix311-binaries.zip";
|
||||
pub const WIX_SHA256: &str = "37f0a533b0978a454efb5dc3bd3598becf9660aaf4287e55bf68ca6b527d051d";
|
||||
|
||||
// For Cross Platform Complilation.
|
||||
|
||||
// const VC_REDIST_X86_URL: &str =
|
||||
// "https://download.visualstudio.microsoft.com/download/pr/c8edbb87-c7ec-4500-a461-71e8912d25e9/99ba493d660597490cbb8b3211d2cae4/vc_redist.x86.exe";
|
||||
|
||||
// const VC_REDIST_X86_SHA256: &str =
|
||||
// "3a43e8a55a3f3e4b73d01872c16d47a19dd825756784f4580187309e7d1fcb74";
|
||||
|
||||
// const VC_REDIST_X64_URL: &str =
|
||||
// "https://download.visualstudio.microsoft.com/download/pr/9e04d214-5a9d-4515-9960-3d71398d98c3/1e1e62ab57bbb4bf5199e8ce88f040be/vc_redist.x64.exe";
|
||||
|
||||
// const VC_REDIST_X64_SHA256: &str =
|
||||
// "d6cd2445f68815fe02489fafe0127819e44851e26dfbe702612bc0d223cbbc2b";
|
||||
|
||||
// A v4 UUID that was generated specifically for cargo-bundle, to be used as a
|
||||
// namespace for generating v5 UUIDs from bundle identifier strings.
|
||||
const UUID_NAMESPACE: [u8; 16] = [
|
||||
0xfd, 0x85, 0x95, 0xa8, 0x17, 0xa3, 0x47, 0x4e, 0xa6, 0x16, 0x76, 0x14, 0x8d, 0xfa, 0x0c, 0x7b,
|
||||
];
|
||||
|
||||
// setup for the main.wxs template file using handlebars. Dynamically changes the template on compilation based on the application metadata.
|
||||
lazy_static! {
|
||||
static ref HANDLEBARS: Handlebars<'static> = {
|
||||
let mut handlebars = Handlebars::new();
|
||||
|
||||
handlebars
|
||||
.register_template_string("main.wxs", include_str!("templates/main.wxs"))
|
||||
.or_else(|e| Err(e.to_string()))
|
||||
.expect("Failed to setup handlebar template");
|
||||
handlebars
|
||||
};
|
||||
}
|
||||
|
||||
type ResourceMap = BTreeMap<String, ResourceDirectory>;
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct ExternalBinary {
|
||||
guid: String,
|
||||
id: String,
|
||||
path: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
struct ResourceFile {
|
||||
guid: String,
|
||||
id: String,
|
||||
path: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct ResourceDirectory {
|
||||
name: String,
|
||||
files: Vec<ResourceFile>,
|
||||
directories: Vec<ResourceDirectory>,
|
||||
}
|
||||
|
||||
impl ResourceDirectory {
|
||||
fn add_file(&mut self, file: ResourceFile) {
|
||||
self.files.push(file);
|
||||
}
|
||||
|
||||
// generates the wix XML string to bundle this directory resources recursively
|
||||
fn get_wix_data(self) -> crate::Result<(String, Vec<String>)> {
|
||||
let mut files = String::from("");
|
||||
let mut file_ids = Vec::new();
|
||||
for file in self.files {
|
||||
file_ids.push(file.id.clone());
|
||||
files.push_str(
|
||||
format!(
|
||||
r#"<Component Id="{id}" Guid="{guid}" Win64="$(var.Win64)" KeyPath="yes"><File Id="PathFile_{id}" Source="{path}" /></Component>"#,
|
||||
id = file.id,
|
||||
guid = file.guid,
|
||||
path = file.path
|
||||
).as_str()
|
||||
);
|
||||
}
|
||||
let mut directories = String::from("");
|
||||
for directory in self.directories {
|
||||
let (wix_string, ids) = directory.get_wix_data()?;
|
||||
for id in ids {
|
||||
file_ids.push(id)
|
||||
}
|
||||
directories.push_str(wix_string.as_str());
|
||||
}
|
||||
let wix_string = format!(
|
||||
r#"<Directory Id="{name}" Name="{name}">{contents}</Directory>"#,
|
||||
name = self.name,
|
||||
contents = format!("{}{}", files, directories)
|
||||
);
|
||||
|
||||
Ok((wix_string, file_ids))
|
||||
}
|
||||
}
|
||||
|
||||
fn copy_icons(settings: &Settings) -> crate::Result<PathBuf> {
|
||||
let base_dir = settings.binary_path();
|
||||
let base_dir = base_dir.parent().expect("Failed to get dir");
|
||||
|
||||
let resource_dir = base_dir.join("resources");
|
||||
|
||||
let mut image_path = PathBuf::from(settings.project_out_directory());
|
||||
|
||||
// pop off till in tauri_src dir
|
||||
image_path.pop();
|
||||
image_path.pop();
|
||||
|
||||
// get icon dir and icon file.
|
||||
let image_path = image_path.join("icons");
|
||||
let opts = super::path_utils::Options::default();
|
||||
|
||||
copy(
|
||||
image_path,
|
||||
&resource_dir,
|
||||
&Options {
|
||||
copy_files: true,
|
||||
overwrite: true,
|
||||
..opts
|
||||
},
|
||||
)
|
||||
.or_else(|e| Err(e.to_string()))?;
|
||||
|
||||
Ok(resource_dir)
|
||||
}
|
||||
|
||||
// Function used to download Wix and VC_REDIST. Checks SHA256 to verify the download.
|
||||
fn download_and_verify(url: &str, hash: &str) -> crate::Result<Vec<u8>> {
|
||||
common::print_info(format!("Downloading {}", url).as_str())?;
|
||||
|
||||
let response = attohttpc::get(url).send().or_else(|e| Err(e.to_string()))?;
|
||||
|
||||
let data: Vec<u8> = response.bytes().or_else(|e| Err(e.to_string()))?;
|
||||
|
||||
common::print_info("validating hash")?;
|
||||
|
||||
let mut hasher = sha2::Sha256::new();
|
||||
hasher.input(&data);
|
||||
|
||||
let url_hash = hasher.result().to_vec();
|
||||
let expected_hash = hex::decode(hash).or_else(|e| Err(e.to_string()))?;
|
||||
|
||||
if expected_hash == url_hash {
|
||||
Ok(data)
|
||||
} else {
|
||||
Err(crate::Error::from("hash mismatch of downloaded file"))
|
||||
}
|
||||
}
|
||||
|
||||
fn app_installer_dir(settings: &Settings) -> crate::Result<PathBuf> {
|
||||
let arch = match settings.binary_arch() {
|
||||
"x86_64" => "x86",
|
||||
"x64" => "x64",
|
||||
target => {
|
||||
return Err(crate::Error::from(format!(
|
||||
"Unsupported architecture: {}",
|
||||
target
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(settings.project_out_directory().to_path_buf().join(format!(
|
||||
"{}.{}.msi",
|
||||
settings.bundle_name(),
|
||||
arch
|
||||
)))
|
||||
}
|
||||
|
||||
// Extracts the zips from Wix and VC_REDIST into a useable path.
|
||||
fn extract_zip(data: &Vec<u8>, path: &Path) -> crate::Result<()> {
|
||||
let cursor = Cursor::new(data);
|
||||
|
||||
let mut zipa = ZipArchive::new(cursor).or_else(|e| Err(e.to_string()))?;
|
||||
|
||||
for i in 0..zipa.len() {
|
||||
let mut file = zipa.by_index(i).or_else(|e| Err(e.to_string()))?;
|
||||
let dest_path = path.join(file.name());
|
||||
let parent = dest_path.parent().expect("Failed to get parent");
|
||||
|
||||
if !parent.exists() {
|
||||
create_dir_all(parent).or_else(|e| Err(e.to_string()))?;
|
||||
}
|
||||
|
||||
let mut buff: Vec<u8> = Vec::new();
|
||||
file
|
||||
.read_to_end(&mut buff)
|
||||
.or_else(|e| Err(e.to_string()))?;
|
||||
let mut fileout = File::create(dest_path).expect("Failed to open file");
|
||||
|
||||
fileout.write_all(&buff).or_else(|e| Err(e.to_string()))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Generates the UUID for the Wix template.
|
||||
fn generate_package_guid(settings: &Settings) -> Uuid {
|
||||
generate_guid(settings.bundle_identifier().as_bytes())
|
||||
}
|
||||
|
||||
fn generate_guid(key: &[u8]) -> Uuid {
|
||||
let namespace = Uuid::from_bytes(UUID_NAMESPACE);
|
||||
Uuid::new_v5(&namespace, key)
|
||||
}
|
||||
|
||||
// Specifically goes and gets Wix and verifies the download via Sha256
|
||||
|
||||
pub fn get_and_extract_wix(path: &Path) -> crate::Result<()> {
|
||||
common::print_info("Verifying wix package")?;
|
||||
|
||||
let data = download_and_verify(WIX_URL, WIX_SHA256)?;
|
||||
|
||||
common::print_info("extracting WIX")?;
|
||||
|
||||
extract_zip(&data, path)
|
||||
}
|
||||
|
||||
// For if bundler needs DLL files.
|
||||
|
||||
// fn run_heat_exe(
|
||||
// wix_toolset_path: &Path,
|
||||
// build_path: &Path,
|
||||
// harvest_dir: &Path,
|
||||
// platform: &str,
|
||||
// ) -> Result<(), String> {
|
||||
// let mut args = vec!["dir"];
|
||||
|
||||
// let harvest_str = harvest_dir.display().to_string();
|
||||
|
||||
// args.push(&harvest_str);
|
||||
// args.push("-platform");
|
||||
// args.push(platform);
|
||||
// args.push("-cg");
|
||||
// args.push("AppFiles");
|
||||
// args.push("-dr");
|
||||
// args.push("APPLICATIONFOLDER");
|
||||
// args.push("-gg");
|
||||
// args.push("-srd");
|
||||
// args.push("-out");
|
||||
// args.push("appdir.wxs");
|
||||
// args.push("-var");
|
||||
// args.push("var.SourceDir");
|
||||
|
||||
// let heat_exe = wix_toolset_path.join("heat.exe");
|
||||
|
||||
// let mut cmd = Command::new(&heat_exe)
|
||||
// .args(&args)
|
||||
// .stdout(Stdio::piped())
|
||||
// .current_dir(build_path)
|
||||
// .spawn()
|
||||
// .expect("error running heat.exe");
|
||||
|
||||
// {
|
||||
// let stdout = cmd.stdout.as_mut().unwrap();
|
||||
// let reader = BufReader::new(stdout);
|
||||
|
||||
// for line in reader.lines() {
|
||||
// info!(logger, "{}", line.unwrap());
|
||||
// }
|
||||
// }
|
||||
|
||||
// let status = cmd.wait().unwrap();
|
||||
// if status.success() {
|
||||
// Ok(())
|
||||
// } else {
|
||||
// Err("error running heat.exe".to_string())
|
||||
// }
|
||||
// }
|
||||
|
||||
// Runs the Candle.exe executable for Wix. Candle parses the wxs file and generates the code for building the installer.
|
||||
fn run_candle(
|
||||
settings: &Settings,
|
||||
wix_toolset_path: &Path,
|
||||
build_path: &Path,
|
||||
wxs_file_name: &str,
|
||||
) -> crate::Result<()> {
|
||||
let arch = match settings.binary_arch() {
|
||||
"x86_64" => "x64",
|
||||
"x86" => "x86",
|
||||
target => {
|
||||
return Err(crate::Error::from(format!(
|
||||
"unsupported target: {}",
|
||||
target
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
let args = vec![
|
||||
"-arch".to_string(),
|
||||
arch.to_string(),
|
||||
wxs_file_name.to_string(),
|
||||
format!("-dSourceDir={}", settings.binary_path().display()),
|
||||
];
|
||||
|
||||
let candle_exe = wix_toolset_path.join("candle.exe");
|
||||
common::print_info(format!("running candle for {}", wxs_file_name).as_str())?;
|
||||
|
||||
let mut cmd = Command::new(&candle_exe)
|
||||
.args(&args)
|
||||
.stdout(Stdio::piped())
|
||||
.current_dir(build_path)
|
||||
.spawn()
|
||||
.expect("error running candle.exe");
|
||||
{
|
||||
let stdout = cmd.stdout.as_mut().expect("Failed to get stdout handle");
|
||||
let reader = BufReader::new(stdout);
|
||||
|
||||
for line in reader.lines() {
|
||||
common::print_info(line.expect("Failed to get line").as_str())?;
|
||||
}
|
||||
}
|
||||
|
||||
let status = cmd.wait()?;
|
||||
if status.success() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(crate::Error::from("error running candle.exe"))
|
||||
}
|
||||
}
|
||||
|
||||
// Runs the Light.exe file. Light takes the generated code from Candle and produces an MSI Installer.
|
||||
fn run_light(
|
||||
wix_toolset_path: &Path,
|
||||
build_path: &Path,
|
||||
wixobjs: &[&str],
|
||||
output_path: &Path,
|
||||
) -> crate::Result<PathBuf> {
|
||||
let light_exe = wix_toolset_path.join("light.exe");
|
||||
|
||||
let mut args: Vec<String> = vec!["-o".to_string(), output_path.display().to_string()];
|
||||
|
||||
for p in wixobjs {
|
||||
args.push(p.to_string());
|
||||
}
|
||||
|
||||
common::print_info(format!("running light to produce {}", output_path.display()).as_str())?;
|
||||
|
||||
let mut cmd = Command::new(&light_exe)
|
||||
.args(&args)
|
||||
.stdout(Stdio::piped())
|
||||
.current_dir(build_path)
|
||||
.spawn()
|
||||
.expect("error running light.exe");
|
||||
{
|
||||
let stdout = cmd.stdout.as_mut().expect("Failed to get stdout handle");
|
||||
let reader = BufReader::new(stdout);
|
||||
|
||||
for line in reader.lines() {
|
||||
common::print_info(line.expect("Failed to get line").as_str())?;
|
||||
}
|
||||
}
|
||||
|
||||
let status = cmd.wait()?;
|
||||
if status.success() {
|
||||
Ok(output_path.to_path_buf())
|
||||
} else {
|
||||
Err(crate::Error::from("error running light.exe"))
|
||||
}
|
||||
}
|
||||
|
||||
// fn get_icon_data() -> crate::Result<()> {
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
// Entry point for bundling and creating the MSI installer. For now the only supported platform is Windows x64.
|
||||
pub fn build_wix_app_installer(
|
||||
settings: &Settings,
|
||||
wix_toolset_path: &Path,
|
||||
) -> crate::Result<PathBuf> {
|
||||
let arch = match settings.binary_arch() {
|
||||
"x86_64" => "x64",
|
||||
"x86" => "x86",
|
||||
target => {
|
||||
return Err(crate::Error::from(format!(
|
||||
"unsupported target: {}",
|
||||
target
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
// common::print_warning("Only x64 supported")?;
|
||||
// target only supports x64.
|
||||
common::print_info(format!("Target: {}", arch).as_str())?;
|
||||
|
||||
let output_path = settings.project_out_directory().join("wix").join(arch);
|
||||
|
||||
let mut data = BTreeMap::new();
|
||||
|
||||
data.insert("product_name", to_json(settings.bundle_name()));
|
||||
data.insert("version", to_json(settings.version_string()));
|
||||
let manufacturer = settings.bundle_identifier().to_string();
|
||||
data.insert("manufacturer", to_json(manufacturer.as_str()));
|
||||
let upgrade_code = Uuid::new_v5(
|
||||
&Uuid::NAMESPACE_DNS,
|
||||
format!("{}.app.x64", &settings.binary_name()).as_bytes(),
|
||||
)
|
||||
.to_string();
|
||||
|
||||
data.insert("upgrade_code", to_json(&upgrade_code.as_str()));
|
||||
|
||||
let path_guid = generate_package_guid(settings).to_string();
|
||||
data.insert("path_component_guid", to_json(&path_guid.as_str()));
|
||||
|
||||
let shortcut_guid = generate_package_guid(settings).to_string();
|
||||
data.insert("shortcut_guid", to_json(&shortcut_guid.as_str()));
|
||||
|
||||
let app_exe_name = settings.binary_name().to_string();
|
||||
data.insert("app_exe_name", to_json(&app_exe_name));
|
||||
|
||||
let external_binaries = generate_external_binary_data(&settings)?;
|
||||
|
||||
let external_binaries_json = to_json(&external_binaries);
|
||||
data.insert("external_binaries", external_binaries_json);
|
||||
|
||||
let resources = generate_resource_data(&settings)?;
|
||||
let mut resources_wix_string = String::from("");
|
||||
let mut files_ids = Vec::new();
|
||||
for (_, dir) in resources {
|
||||
let (wix_string, ids) = dir.get_wix_data()?;
|
||||
resources_wix_string.push_str(wix_string.as_str());
|
||||
for id in ids {
|
||||
files_ids.push(id);
|
||||
}
|
||||
}
|
||||
|
||||
data.insert("resources", to_json(resources_wix_string));
|
||||
data.insert("resource_file_ids", to_json(files_ids));
|
||||
|
||||
let app_exe_source = settings.binary_path().display().to_string();
|
||||
|
||||
data.insert("app_exe_source", to_json(&app_exe_source));
|
||||
|
||||
// copy icons from icons folder to resource folder near msi
|
||||
let image_path = copy_icons(&settings)?;
|
||||
|
||||
let path = image_path.join("icon.ico").display().to_string();
|
||||
|
||||
data.insert("icon_path", to_json(path.as_str()));
|
||||
|
||||
let temp = HANDLEBARS
|
||||
.render("main.wxs", &data)
|
||||
.or_else(|e| Err(e.to_string()))?;
|
||||
|
||||
if output_path.exists() {
|
||||
remove_dir_all(&output_path).or_else(|e| Err(e.to_string()))?;
|
||||
}
|
||||
|
||||
create_dir_all(&output_path).or_else(|e| Err(e.to_string()))?;
|
||||
|
||||
let main_wxs_path = output_path.join("main.wxs");
|
||||
write(&main_wxs_path, temp).or_else(|e| Err(e.to_string()))?;
|
||||
|
||||
let input_basenames = vec!["main"];
|
||||
|
||||
for basename in &input_basenames {
|
||||
let wxs = format!("{}.wxs", basename);
|
||||
run_candle(settings, &wix_toolset_path, &output_path, &wxs)?;
|
||||
}
|
||||
|
||||
let wixobjs = vec!["main.wixobj"];
|
||||
let target = run_light(
|
||||
&wix_toolset_path,
|
||||
&output_path,
|
||||
&wixobjs,
|
||||
&app_installer_dir(settings)?,
|
||||
)?;
|
||||
|
||||
Ok(target)
|
||||
}
|
||||
|
||||
fn generate_external_binary_data(settings: &Settings) -> crate::Result<Vec<ExternalBinary>> {
|
||||
let mut external_binaries = Vec::new();
|
||||
let regex = Regex::new(r"[^\w\d\.]")?;
|
||||
let cwd = std::env::current_dir()?;
|
||||
for src in settings.external_binaries() {
|
||||
let src = src?;
|
||||
let filename = src
|
||||
.file_name()
|
||||
.expect("failed to extract external binary filename")
|
||||
.to_os_string()
|
||||
.into_string()
|
||||
.expect("failed to convert external binary filename to string");
|
||||
|
||||
let guid = generate_guid(filename.as_bytes()).to_string();
|
||||
|
||||
external_binaries.push(ExternalBinary {
|
||||
guid: guid,
|
||||
path: cwd
|
||||
.join(src)
|
||||
.into_os_string()
|
||||
.into_string()
|
||||
.expect("failed to read external binary path"),
|
||||
id: regex.replace_all(&filename, "").to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(external_binaries)
|
||||
}
|
||||
|
||||
// generates the data required for the resource bundling on wix
|
||||
fn generate_resource_data(settings: &Settings) -> crate::Result<ResourceMap> {
|
||||
let mut resources = ResourceMap::new();
|
||||
let regex = Regex::new(r"[^\w\d\.]")?;
|
||||
let cwd = std::env::current_dir()?;
|
||||
for src in settings.resource_files() {
|
||||
let src = src?;
|
||||
|
||||
let filename = src
|
||||
.file_name()
|
||||
.expect("failed to extract resource filename")
|
||||
.to_os_string()
|
||||
.into_string()
|
||||
.expect("failed to convert resource filename to string");
|
||||
|
||||
let resource_path = cwd
|
||||
.join(src.clone())
|
||||
.into_os_string()
|
||||
.into_string()
|
||||
.expect("failed to read resource path");
|
||||
|
||||
let resource_entry = ResourceFile {
|
||||
guid: generate_guid(filename.as_bytes()).to_string(),
|
||||
path: resource_path,
|
||||
id: regex.replace_all(&filename, "").to_string(),
|
||||
};
|
||||
|
||||
// split the resource path directories
|
||||
let mut directories = src
|
||||
.components()
|
||||
.filter(|component| {
|
||||
let comp = component.as_os_str();
|
||||
comp != "." && comp != ".."
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
directories.truncate(directories.len() - 1);
|
||||
// transform the directory structure to a chained vec structure
|
||||
for directory in directories {
|
||||
let directory_name = directory
|
||||
.as_os_str()
|
||||
.to_os_string()
|
||||
.into_string()
|
||||
.expect("failed to read resource folder name");
|
||||
|
||||
// if the directory is already on the map
|
||||
if resources.contains_key(&directory_name) {
|
||||
let directory_entry = &mut resources
|
||||
.get_mut(&directory_name)
|
||||
.expect("Unable to handle resources");
|
||||
if directory_entry.name == directory_name {
|
||||
// the directory entry is the root of the chain
|
||||
directory_entry.add_file(resource_entry.clone());
|
||||
} else {
|
||||
let index = directory_entry
|
||||
.directories
|
||||
.iter()
|
||||
.position(|f| f.name == directory_name);
|
||||
if index.is_some() {
|
||||
// the directory entry is already a part of the chain
|
||||
let dir = directory_entry
|
||||
.directories
|
||||
.get_mut(index.expect("Unable to get index"))
|
||||
.expect("Unable to get directory");
|
||||
dir.add_file(resource_entry.clone());
|
||||
} else {
|
||||
// push it to the chain
|
||||
directory_entry.directories.push(ResourceDirectory {
|
||||
name: directory_name.clone(),
|
||||
directories: vec![],
|
||||
files: vec![resource_entry.clone()],
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
resources.insert(
|
||||
directory_name.clone(),
|
||||
ResourceDirectory {
|
||||
name: directory_name.clone(),
|
||||
directories: vec![],
|
||||
files: vec![resource_entry.clone()],
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(resources)
|
||||
}
|
||||
@@ -1,157 +0,0 @@
|
||||
mod bundle;
|
||||
|
||||
use crate::bundle::{bundle_project, check_icons, BuildArtifact, PackageType, Settings};
|
||||
|
||||
use clap::{crate_version, App, AppSettings, Arg, SubCommand};
|
||||
use error_chain::{bail, error_chain};
|
||||
|
||||
use std::env;
|
||||
use std::process;
|
||||
|
||||
error_chain! {
|
||||
foreign_links {
|
||||
Glob(::glob::GlobError);
|
||||
GlobPattern(::glob::PatternError);
|
||||
Io(::std::io::Error);
|
||||
Image(::image::ImageError);
|
||||
Target(::target_build_utils::Error);
|
||||
Term(::term::Error);
|
||||
Toml(::toml::de::Error);
|
||||
Walkdir(::walkdir::Error);
|
||||
StripError(std::path::StripPrefixError);
|
||||
ConvertError(std::num::TryFromIntError);
|
||||
PlatformError(::tauri_utils::Error);
|
||||
RegexError(::regex::Error) #[cfg(windows)];
|
||||
HttpError(::attohttpc::Error) #[cfg(windows)];
|
||||
}
|
||||
errors {}
|
||||
}
|
||||
|
||||
/// Runs `cargo build` to make sure the binary file is up-to-date.
|
||||
fn build_project_if_unbuilt(settings: &Settings) -> crate::Result<()> {
|
||||
let mut args = vec!["build".to_string()];
|
||||
if let Some(triple) = settings.target_triple() {
|
||||
args.push(format!("--target={}", triple));
|
||||
}
|
||||
match settings.build_artifact() {
|
||||
&BuildArtifact::Main => {}
|
||||
&BuildArtifact::Bin(ref name) => {
|
||||
args.push(format!("--bin={}", name));
|
||||
}
|
||||
&BuildArtifact::Example(ref name) => {
|
||||
args.push(format!("--example={}", name));
|
||||
}
|
||||
}
|
||||
if settings.is_release_build() {
|
||||
args.push("--release".to_string());
|
||||
}
|
||||
|
||||
match settings.build_features() {
|
||||
Some(features) => {
|
||||
args.push(format!("--features={}", features.join(" ")));
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
|
||||
let status = process::Command::new("cargo").args(args).status()?;
|
||||
if !status.success() {
|
||||
bail!(
|
||||
"Result of `cargo build` operation was unsuccessful: {}",
|
||||
status
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run() -> crate::Result<()> {
|
||||
let all_formats: Vec<&str> = PackageType::all()
|
||||
.iter()
|
||||
.map(PackageType::short_name)
|
||||
.collect();
|
||||
let m = App::new("cargo-tauri-bundler")
|
||||
.version(format!("v{}", crate_version!()).as_str())
|
||||
.bin_name("cargo")
|
||||
.setting(AppSettings::GlobalVersion)
|
||||
.setting(AppSettings::SubcommandRequired)
|
||||
.subcommand(
|
||||
SubCommand::with_name("tauri-bundler")
|
||||
.author("George Burton <burtonageo@gmail.com>, Lucas Fernandes Gonçalves Nogueira <lucas@quasar.dev>, Daniel Thompson-Yvetot <denjell@sfosc.org>")
|
||||
.about("Bundle Rust executables into OS bundles")
|
||||
.setting(AppSettings::DisableVersion)
|
||||
.setting(AppSettings::UnifiedHelpMessage)
|
||||
.arg(
|
||||
Arg::with_name("bin")
|
||||
.long("bin")
|
||||
.value_name("NAME")
|
||||
.help("Bundle the specified binary"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("example")
|
||||
.long("example")
|
||||
.value_name("NAME")
|
||||
.conflicts_with("bin")
|
||||
.help("Bundle the specified example"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("format")
|
||||
.long("format")
|
||||
.value_name("FORMAT")
|
||||
.possible_values(&all_formats)
|
||||
.help("Which bundle format to produce"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("release")
|
||||
.long("release")
|
||||
.help("Build a bundle from a target built in release mode"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("target")
|
||||
.long("target")
|
||||
.value_name("TRIPLE")
|
||||
.help("Build a bundle for the target triple"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("features")
|
||||
.long("features")
|
||||
.value_name("FEATURES")
|
||||
.multiple(true)
|
||||
.help("Which features to build"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("version")
|
||||
.long("version")
|
||||
.short("v")
|
||||
.help("Read the version of the bundler"),
|
||||
),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
if let Some(m) = m.subcommand_matches("tauri-bundler") {
|
||||
if m.is_present("version") {
|
||||
println!("{}", crate_version!());
|
||||
} else {
|
||||
let output_paths = env::current_dir()
|
||||
.map_err(From::from)
|
||||
.and_then(|d| Settings::new(d, m))
|
||||
.and_then(|s| {
|
||||
if check_icons(&s)? {
|
||||
build_project_if_unbuilt(&s)?;
|
||||
Ok(s)
|
||||
} else {
|
||||
Err(crate::Error::from(
|
||||
"Could not find Icon Paths. Please make sure they exist and are in your Cargo.toml's icon key.",
|
||||
))
|
||||
}
|
||||
})
|
||||
.and_then(bundle_project)?;
|
||||
bundle::print_finished(&output_paths)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if let Err(error) = run() {
|
||||
bundle::print_error(&error).expect("Failed to call print error in main");
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
NODE_ENV=test
|
||||
@@ -1 +0,0 @@
|
||||
/src-tauri
|
||||
@@ -1,61 +0,0 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
|
||||
env: {
|
||||
node: true,
|
||||
jest: true
|
||||
},
|
||||
|
||||
parser: '@typescript-eslint/parser',
|
||||
|
||||
extends: [
|
||||
'standard-with-typescript',
|
||||
'plugin:@typescript-eslint/recommended-requiring-type-checking',
|
||||
'plugin:lodash-template/recommended'
|
||||
// TODO: make this work with typescript
|
||||
// 'plugin:node/recommended'
|
||||
],
|
||||
|
||||
plugins: ['@typescript-eslint', 'node', 'security'],
|
||||
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: './tsconfig.json'
|
||||
},
|
||||
|
||||
globals: {
|
||||
__statics: true,
|
||||
process: true
|
||||
},
|
||||
|
||||
// add your custom rules here
|
||||
rules: {
|
||||
// allow console.log during development only
|
||||
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
||||
// allow debugger during development only
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
||||
'no-process-exit': 'off',
|
||||
'security/detect-non-literal-fs-filename': 'warn',
|
||||
'security/detect-unsafe-regex': 'error',
|
||||
'security/detect-buffer-noassert': 'error',
|
||||
'security/detect-child-process': 'warn',
|
||||
'security/detect-disable-mustache-escape': 'error',
|
||||
'security/detect-eval-with-expression': 'error',
|
||||
'security/detect-no-csrf-before-method-override': 'error',
|
||||
'security/detect-non-literal-regexp': 'error',
|
||||
'security/detect-non-literal-require': 'warn',
|
||||
'security/detect-object-injection': 'warn',
|
||||
'security/detect-possible-timing-attacks': 'error',
|
||||
'security/detect-pseudoRandomBytes': 'error',
|
||||
'space-before-function-paren': 'off',
|
||||
'@typescript-eslint/strict-boolean-expressions': 0,
|
||||
'@typescript-eslint/space-before-function-paren': [
|
||||
'error',
|
||||
{
|
||||
asyncArrow: 'always',
|
||||
anonymous: 'never',
|
||||
named: 'never'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
# Webpack output
|
||||
dist
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Typescript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
/.vs
|
||||
.DS_Store
|
||||
.Thumbs.db
|
||||
*.sublime*
|
||||
.idea/
|
||||
debug.log
|
||||
package-lock.json
|
||||
.vscode/settings.json
|
||||
|
||||
# Tauri output
|
||||
bundle.json
|
||||
config.json
|
||||
|
||||
# rust compiled folders
|
||||
target
|
||||
|
||||
|
||||
# doing this because of how our tests currently (naively) drop the tauri.conf.json in that folder
|
||||
# todo: needs a proper fic
|
||||
tauri.conf.json
|
||||
src-tauri
|
||||
@@ -1,6 +0,0 @@
|
||||
test
|
||||
node_modules
|
||||
.git
|
||||
.github
|
||||
.idea
|
||||
SECURITY.md
|
||||
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 - Present - Tauri Apps Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,39 +0,0 @@
|
||||
# tauri
|
||||
<img align="right" src="app-icon.png" height="200" width="200">
|
||||
|
||||
## A fresh take on creating cross-platform apps.
|
||||
[](https://github.com/quasarframework/quasar/tree/tauri)
|
||||
[](https://discord.gg/SpmNs4S)
|
||||
[](https://dev.to/tauri)
|
||||
|
||||

|
||||
[](https://tauri.studio)
|
||||
|
||||
[](https://good-labs.github.io/greater-good-affirmation)
|
||||
[](https://opencollective.com/tauri)
|
||||
|
||||
|
||||
Tauri is a tool for building tiny, blazing fast binaries for all major desktop platforms. You can use any front-end framework that compiles to HTML,JS and CSS for building your interface.
|
||||
|
||||
| Component | Version |
|
||||
|-----------|---------|
|
||||
| tauri.js | 
|
||||
|
||||
|
||||
Please visit the main readme for further information about contributing.
|
||||
|
||||
## Installation
|
||||
|
||||
The preferred method is to install this module globally:
|
||||
```
|
||||
$ npm install --global tauri
|
||||
```
|
||||
|
||||
You can also add it to your project and use it locally - maybe helpful for some CI pipelines:
|
||||
```
|
||||
$ yarn add --dev tauri
|
||||
```
|
||||
|
||||
## License
|
||||
MIT
|
||||
@@ -1,56 +0,0 @@
|
||||
const cache = {}
|
||||
let initialized = false
|
||||
|
||||
const proxy = new Proxy({
|
||||
__consume () {
|
||||
for (const key in cache) {
|
||||
if (key in window.tauri) {
|
||||
const queue = cache[key]
|
||||
for (const call of queue) {
|
||||
try {
|
||||
const ret = window.tauri[key].apply(window.tauri, call.arguments)
|
||||
if (ret instanceof Promise) {
|
||||
ret.then(call.resolve, call.reject)
|
||||
} else {
|
||||
call.resolve()
|
||||
}
|
||||
} catch (e) {
|
||||
call.reject(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
initialized = true
|
||||
}
|
||||
}, {
|
||||
get (obj, prop) {
|
||||
if (prop === '__consume') {
|
||||
return obj[prop]
|
||||
}
|
||||
|
||||
if (initialized && 'tauri' in window) {
|
||||
return window.tauri[prop].bind(window.tauri)
|
||||
}
|
||||
|
||||
if (!(prop in cache)) {
|
||||
cache[prop] = []
|
||||
}
|
||||
return function () {
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
cache[prop].push({
|
||||
arguments: arguments,
|
||||
resolve,
|
||||
reject
|
||||
})
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
window.onTauriInit = () => {
|
||||
proxy.__consume()
|
||||
}
|
||||
|
||||
export default proxy
|
||||
@@ -1,25 +0,0 @@
|
||||
const parseArgs = require('minimist')
|
||||
|
||||
const argv = parseArgs(process.argv.slice(2), {
|
||||
alias: {
|
||||
h: 'help',
|
||||
d: 'debug'
|
||||
},
|
||||
boolean: ['h', 'd']
|
||||
})
|
||||
|
||||
if (argv.help) {
|
||||
console.log(`
|
||||
Description
|
||||
Tauri build.
|
||||
Usage
|
||||
$ tauri build
|
||||
Options
|
||||
--help, -h Displays this message
|
||||
`)
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
const build = require('../dist/api/build')
|
||||
|
||||
build({ ctx: { debug: argv.debug } })
|
||||
@@ -1,24 +0,0 @@
|
||||
const parseArgs = require('minimist')
|
||||
|
||||
const argv = parseArgs(process.argv.slice(2), {
|
||||
alias: {
|
||||
h: 'help'
|
||||
},
|
||||
boolean: ['h']
|
||||
})
|
||||
|
||||
if (argv.help) {
|
||||
console.log(`
|
||||
Description
|
||||
Tauri dev.
|
||||
Usage
|
||||
$ tauri dev
|
||||
Options
|
||||
--help, -h Displays this message
|
||||
`)
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
const dev = require('../dist/api/dev')
|
||||
|
||||
dev()
|
||||
@@ -1,58 +0,0 @@
|
||||
const parseArgs = require('minimist')
|
||||
const { tauricon } = require('../dist/api/tauricon')
|
||||
|
||||
/**
|
||||
* @type {object}
|
||||
* @property {boolean} h
|
||||
* @property {boolean} help
|
||||
* @property {string|boolean} f
|
||||
* @property {string|boolean} force
|
||||
* @property {boolean} l
|
||||
* @property {boolean} log
|
||||
* @property {boolean} c
|
||||
* @property {boolean} config
|
||||
* @property {boolean} s
|
||||
* @property {boolean} source
|
||||
* @property {boolean} t
|
||||
* @property {boolean} target
|
||||
*/
|
||||
const argv = parseArgs(process.argv.slice(2), {
|
||||
alias: {
|
||||
h: 'help',
|
||||
l: 'log',
|
||||
c: 'config',
|
||||
s: 'source',
|
||||
t: 'target'
|
||||
},
|
||||
boolean: ['h', 'l']
|
||||
})
|
||||
|
||||
if (argv.help) {
|
||||
console.log(`
|
||||
Description
|
||||
Create all the icons you need for your Tauri app.
|
||||
|
||||
Usage
|
||||
$ tauri icon
|
||||
|
||||
Options
|
||||
--help, -h Displays this message
|
||||
--log, l Logging [boolean]
|
||||
--icon, i Source icon (png, 1240x1240 with transparency)
|
||||
--target, t Target folder (default: 'src-tauri/icons')
|
||||
--compression, c Compression type [pngquant|optipng|zopfli]
|
||||
`)
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
tauricon.make(
|
||||
argv.i,
|
||||
argv.t,
|
||||
argv.c || 'optipng'
|
||||
).then(() => {
|
||||
// TODO: use logger module for prettier output
|
||||
console.log('app:tauri (tauricon) Completed')
|
||||
}).catch(e => {
|
||||
// TODO: use logger module for prettier output
|
||||
console.error('app:tauri (icon)', e)
|
||||
})
|
||||
@@ -1,3 +0,0 @@
|
||||
const info = require('../dist/api/info')
|
||||
|
||||
info()
|
||||
@@ -1,49 +0,0 @@
|
||||
const parseArgs = require('minimist')
|
||||
|
||||
/**
|
||||
* @type {object}
|
||||
* @property {boolean} h
|
||||
* @property {boolean} help
|
||||
* @property {string|boolean} f
|
||||
* @property {string|boolean} force
|
||||
* @property {boolean} l
|
||||
* @property {boolean} log
|
||||
* @property {boolean} d
|
||||
* @property {boolean} directory
|
||||
*/
|
||||
const argv = parseArgs(process.argv.slice(2), {
|
||||
alias: {
|
||||
h: 'help',
|
||||
f: 'force',
|
||||
l: 'log',
|
||||
d: 'directory',
|
||||
t: 'tauri-path'
|
||||
},
|
||||
boolean: ['h', 'l']
|
||||
})
|
||||
|
||||
if (argv.help) {
|
||||
console.log(`
|
||||
Description
|
||||
Inits the Tauri template. If Tauri cannot find the tauri.conf.json
|
||||
it will create one.
|
||||
Usage
|
||||
$ tauri init
|
||||
Options
|
||||
--help, -h Displays this message
|
||||
--force, -f Force init to overwrite [conf|template|all]
|
||||
--log, -l Logging [boolean]
|
||||
--directory, -d Set target directory for init
|
||||
--tauri-path, -t Path of the Tauri project to use (relative to the cwd)
|
||||
`)
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
const init = require('../dist/api/init')
|
||||
|
||||
init({
|
||||
directory: argv.d || process.cwd(),
|
||||
force: argv.f || null,
|
||||
logging: argv.l || null,
|
||||
tauriPath: argv.t || null
|
||||
})
|
||||
@@ -1,50 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const cmds = ['init', 'dev', 'build', 'help', 'icon', 'info']
|
||||
|
||||
const cmd = process.argv[2]
|
||||
/**
|
||||
* @description This is the bootstrapper that in turn calls subsequent
|
||||
* Tauri Commands
|
||||
*
|
||||
* @param {string|array} command
|
||||
*/
|
||||
const tauri = function (command) {
|
||||
if (typeof command === 'object') { // technically we just care about an array
|
||||
command = command[0]
|
||||
}
|
||||
|
||||
if (!command || command === '-h' || command === '--help' || command === 'help') {
|
||||
console.log(`
|
||||
Description
|
||||
This is the Tauri CLI.
|
||||
Usage
|
||||
$ tauri ${cmds.join('|')}
|
||||
Options
|
||||
--help, -h Displays this message
|
||||
--version, -v Displays the Tauri CLI version
|
||||
`)
|
||||
process.exit(0)
|
||||
// eslint-disable-next-line no-unreachable
|
||||
return false // do this for node consumers and tests
|
||||
}
|
||||
|
||||
if (command === '-v' || command === '--version') {
|
||||
console.log(require('../package.json').version)
|
||||
return false // do this for node consumers and tests
|
||||
}
|
||||
|
||||
if (cmds.includes(command)) {
|
||||
if (process.argv && !process.env.test) {
|
||||
process.argv.splice(2, 1)
|
||||
}
|
||||
console.log(`[tauri]: running ${command}`)
|
||||
// eslint-disable-next-line security/detect-non-literal-require
|
||||
require(`./tauri-${command}`)
|
||||
} else {
|
||||
console.log(`Invalid command ${command}. Use one of ${cmds.join(',')}.`)
|
||||
}
|
||||
}
|
||||
module.exports = { tauri }
|
||||
|
||||
tauri(cmd)
|
||||
@@ -1,42 +0,0 @@
|
||||
module.exports = {
|
||||
globals: {
|
||||
__DEV__: true
|
||||
},
|
||||
setupFilesAfterEnv: ['<rootDir>/test/jest/jest.setup.js'],
|
||||
// noStackTrace: true,
|
||||
// bail: true,
|
||||
// cache: false,
|
||||
// verbose: true,
|
||||
// watch: true,
|
||||
collectCoverage: true,
|
||||
coverageDirectory: '<rootDir>/test/jest/coverage',
|
||||
collectCoverageFrom: [
|
||||
'<rootDir>/bin/**/*.js',
|
||||
'<rootDir>/helpers/**/*.js',
|
||||
'<rootDir>/api/**/*.js'
|
||||
],
|
||||
coverageReporters: ['json-summary', 'text', 'lcov'],
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
// branches: 50,
|
||||
// functions: 50,
|
||||
// lines: 50,
|
||||
// statements: 50
|
||||
}
|
||||
},
|
||||
testMatch: [
|
||||
'<rootDir>/test/jest/__tests__/**/*.spec.js',
|
||||
'<rootDir>/test/jest/__tests__/**/*.test.js'
|
||||
],
|
||||
moduleFileExtensions: ['js', 'json'],
|
||||
moduleNameMapper: {
|
||||
'^~/(.*)$': '<rootDir>/$1',
|
||||
'^bin/(.*)$': '<rootDir>/bin/$1',
|
||||
'^helpers/(.*)$': '<rootDir>/helpers/$1',
|
||||
'^api/(.*)$': '<rootDir>/api/$1',
|
||||
'^templates/(.*)$': '<rootDir>/templates/$1',
|
||||
'^test/(.*)$': '<rootDir>/test/$1',
|
||||
'../../package.json': '<rootDir>/package.json'
|
||||
},
|
||||
transform: {}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
{
|
||||
"name": "tauri",
|
||||
"version": "0.4.0",
|
||||
"description": "Multi-binding collection of libraries and templates for building Tauri apps",
|
||||
"bin": {
|
||||
"tauri": "./bin/tauri.js"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/tauri"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "webpack --progress",
|
||||
"test": "jest --runInBand --no-cache",
|
||||
"prepare": "yarn build",
|
||||
"pretest": "yarn build",
|
||||
"test:mac-local": "jest --runInBand",
|
||||
"lint": "eslint --ext ts ./src/**/*.ts",
|
||||
"lint-fix": "eslint --fix --ext ts ./src/**/*.ts",
|
||||
"lint:lockfile": "lockfile-lint --path yarn.lock --type yarn --validate-https --allowed-hosts npm yarn",
|
||||
"build:tauri[rust]": "cd ../tauri && TAURI_DIST_DIR=../../test/fixture/dist TAURI_DIR=../test/fixture cargo publish --dry-run --allow-dirty"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/tauri-apps/tauri.git"
|
||||
},
|
||||
"contributors": [
|
||||
"Tauri Team <team@tauri-apps.org> (https://tauri.studio)",
|
||||
"Daniel Thompson-Yvetot <denjell@sfosc.org>",
|
||||
"Lucas Fernandes Gonçalves Nogueira <lucas@quasar.dev>"
|
||||
],
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/tauri-apps/tauri/issues"
|
||||
},
|
||||
"homepage": "https://github.com/tauri-apps/tauri#readme",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.17.0",
|
||||
"npm": ">= 6.6.0",
|
||||
"yarn": ">= 1.19.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tauri-apps/tauri-inliner": "1.14.0",
|
||||
"@tauri-apps/toml": "2.2.4",
|
||||
"chalk": "3.0.0",
|
||||
"chokidar": "3.3.1",
|
||||
"cross-spawn": "7.0.1",
|
||||
"fast-glob": "3.1.1",
|
||||
"fs-extra": "8.1.0",
|
||||
"imagemin": "7.0.1",
|
||||
"imagemin-optipng": "7.1.0",
|
||||
"imagemin-pngquant": "8.0.0",
|
||||
"imagemin-zopfli": "6.0.0",
|
||||
"is-png": "2.0.0",
|
||||
"isbinaryfile": "4.0.4",
|
||||
"jsdom": "16.1.0",
|
||||
"lodash": "4.17.15",
|
||||
"minimist": "1.2.0",
|
||||
"ms": "2.1.2",
|
||||
"png2icons": "2.0.1",
|
||||
"read-chunk": "3.2.0",
|
||||
"sharp": "0.24.0",
|
||||
"webpack-merge": "4.2.2",
|
||||
"webpack-shell-plugin": "0.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cross-spawn": "6.0.1",
|
||||
"@types/fs-extra": "8.0.1",
|
||||
"@types/imagemin": "7.0.0",
|
||||
"@types/imagemin-optipng": "5.2.0",
|
||||
"@types/jsdom": "12.2.4",
|
||||
"@types/lodash": "4.14.149",
|
||||
"@types/ms": "0.7.31",
|
||||
"@types/sharp": "0.24.0",
|
||||
"@types/webpack-merge": "4.1.5",
|
||||
"@typescript-eslint/eslint-plugin": "2.19.0",
|
||||
"@typescript-eslint/parser": "2.19.0",
|
||||
"dotenv": "8.2.0",
|
||||
"eslint": "6.8.0",
|
||||
"eslint-config-standard-with-typescript": "12.0.1",
|
||||
"eslint-plugin-import": "2.20.1",
|
||||
"eslint-plugin-lodash-template": "0.15.0",
|
||||
"eslint-plugin-node": "11.0.0",
|
||||
"eslint-plugin-promise": "4.2.1",
|
||||
"eslint-plugin-security": "1.4.0",
|
||||
"eslint-plugin-standard": "4.0.1",
|
||||
"husky": "4.2.1",
|
||||
"jest": "25.1.0",
|
||||
"jest-mock-process": "1.3.2",
|
||||
"lint-staged": "10.0.7",
|
||||
"lockfile-lint": "3.0.12",
|
||||
"promise": "8.0.3",
|
||||
"raw-loader": "4.0.0",
|
||||
"ts-loader": "6.2.1",
|
||||
"typescript": "3.7.5",
|
||||
"webpack": "4.41.5",
|
||||
"webpack-cli": "3.3.10",
|
||||
"webpack-node-externals": "1.7.2"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "lint-staged"
|
||||
}
|
||||
},
|
||||
"lint-staged": [
|
||||
"eslint --fix",
|
||||
"git add"
|
||||
]
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
import { TauriConfig } from 'types'
|
||||
import merge from 'webpack-merge'
|
||||
import Runner from '../runner'
|
||||
import getTauriConfig from '../helpers/tauri-config'
|
||||
|
||||
module.exports = async (config: TauriConfig): Promise<void> => {
|
||||
const tauri = new Runner()
|
||||
const tauriConfig = getTauriConfig(
|
||||
merge(
|
||||
{
|
||||
ctx: {
|
||||
prod: true
|
||||
}
|
||||
} as any,
|
||||
config as any
|
||||
) as TauriConfig
|
||||
)
|
||||
|
||||
return tauri.build(tauriConfig)
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import { TauriConfig } from 'types'
|
||||
import merge from 'webpack-merge'
|
||||
import Runner from '../runner'
|
||||
import getTauriConfig from '../helpers/tauri-config'
|
||||
|
||||
module.exports = async (config: TauriConfig): Promise<void> => {
|
||||
const tauri = new Runner()
|
||||
const tauriConfig = getTauriConfig(
|
||||
merge(
|
||||
{
|
||||
ctx: {
|
||||
debug: true,
|
||||
dev: true
|
||||
}
|
||||
} as any,
|
||||
config as any
|
||||
) as TauriConfig
|
||||
)
|
||||
|
||||
return tauri.run(tauriConfig)
|
||||
}
|
||||
@@ -1,186 +0,0 @@
|
||||
import toml from '@tauri-apps/toml'
|
||||
import chalk from 'chalk'
|
||||
import { sync as spawn } from 'cross-spawn'
|
||||
import fs from 'fs'
|
||||
import os from 'os'
|
||||
import path from 'path'
|
||||
import { appDir, tauriDir } from '../helpers/app-paths'
|
||||
import { TauriConfig } from './../types/config'
|
||||
|
||||
interface DirInfo {
|
||||
path: string
|
||||
name: string
|
||||
type?: 'folder' | 'file'
|
||||
children?: DirInfo[]
|
||||
}
|
||||
|
||||
/* eslint-disable security/detect-non-literal-fs-filename */
|
||||
function dirTree(filename: string): DirInfo {
|
||||
const stats = fs.lstatSync(filename)
|
||||
const info: DirInfo = {
|
||||
path: filename,
|
||||
name: path.basename(filename)
|
||||
}
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
info.type = 'folder'
|
||||
info.children = fs.readdirSync(filename).map(function(child: string) {
|
||||
return dirTree(filename + '/' + child)
|
||||
})
|
||||
} else {
|
||||
info.type = 'file'
|
||||
}
|
||||
|
||||
return info
|
||||
}
|
||||
|
||||
function getVersion(
|
||||
command: string,
|
||||
args: string[] = [],
|
||||
formatter?: (output: string) => string
|
||||
): string {
|
||||
try {
|
||||
const child = spawn(command, [...args, '--version'])
|
||||
if (child.status === 0) {
|
||||
const output = String(child.output[1])
|
||||
return chalk
|
||||
.green(formatter === undefined ? output : formatter(output))
|
||||
.replace('\n', '')
|
||||
}
|
||||
return chalk.red('Not installed')
|
||||
} catch (err) {
|
||||
return chalk.red('Not installed')
|
||||
}
|
||||
}
|
||||
|
||||
interface Info {
|
||||
section?: boolean
|
||||
key: string
|
||||
value?: string
|
||||
}
|
||||
|
||||
function printInfo(info: Info): void {
|
||||
console.log(
|
||||
`${info.section ? '\n' : ''}${info.key}${
|
||||
info.value === undefined ? '' : ' - ' + info.value
|
||||
}`
|
||||
)
|
||||
}
|
||||
|
||||
function printAppInfo(tauriDir: string): void {
|
||||
printInfo({ key: 'App', section: true })
|
||||
|
||||
try {
|
||||
const tomlPath = path.join(tauriDir, 'Cargo.toml')
|
||||
const tomlFile = fs.readFileSync(tomlPath)
|
||||
// @ts-ignore
|
||||
const tomlContents = toml.parse(tomlFile)
|
||||
|
||||
const tauriVersion = (): string => {
|
||||
const tauri = tomlContents.dependencies.tauri
|
||||
if (tauri) {
|
||||
if (tauri.version) {
|
||||
return chalk.green(tauri.version)
|
||||
}
|
||||
if (tauri.path) {
|
||||
try {
|
||||
const tauriTomlPath = path.resolve(
|
||||
tauriDir,
|
||||
tauri.path,
|
||||
'Cargo.toml'
|
||||
)
|
||||
const tauriTomlFile = fs.readFileSync(tauriTomlPath)
|
||||
// @ts-ignore
|
||||
const tauriTomlContents = toml.parse(tauriTomlFile)
|
||||
return chalk.green(
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
`${tauriTomlContents.package.version} (from source)`
|
||||
)
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
return chalk.red('unknown')
|
||||
}
|
||||
|
||||
printInfo({ key: ' tauri', value: tauriVersion() })
|
||||
} catch (_) {}
|
||||
|
||||
try {
|
||||
const tauriMode = (config: TauriConfig): string => {
|
||||
if (config.tauri.embeddedServer) {
|
||||
return chalk.green(
|
||||
config.tauri.embeddedServer.active ? 'embedded-server' : 'no-server'
|
||||
)
|
||||
}
|
||||
return chalk.red('unset')
|
||||
}
|
||||
const configPath = path.join(tauriDir, 'tauri.conf.json')
|
||||
const config = __non_webpack_require__(configPath) as TauriConfig
|
||||
printInfo({ key: ' mode', value: tauriMode(config) })
|
||||
printInfo({
|
||||
key: ' build-type',
|
||||
value:
|
||||
config.tauri.bundle && config.tauri.bundle.active ? 'bundle' : 'build'
|
||||
})
|
||||
printInfo({
|
||||
key: ' CSP',
|
||||
value: config.tauri.security ? config.tauri.security.csp : 'unset'
|
||||
})
|
||||
printInfo({
|
||||
key: ' Windows',
|
||||
value: config.tauri.edge && config.tauri.edge.active ? 'Edge' : 'MSHTML'
|
||||
})
|
||||
printInfo({
|
||||
key: ' distDir',
|
||||
value: config.build
|
||||
? chalk.green(config.build.distDir)
|
||||
: chalk.red('unset')
|
||||
})
|
||||
printInfo({
|
||||
key: ' devPath',
|
||||
value: config.build
|
||||
? chalk.green(config.build.devPath)
|
||||
: chalk.red('unset')
|
||||
})
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
module.exports = () => {
|
||||
printInfo({
|
||||
key: 'Operating System',
|
||||
value: chalk.green(
|
||||
`${os.type()}(${os.release()}) - ${os.platform()}/${os.arch()}`
|
||||
),
|
||||
section: true
|
||||
})
|
||||
printInfo({ key: 'Node.js environment', section: true })
|
||||
printInfo({ key: ' Node.js', value: chalk.green(process.version.slice(1)) })
|
||||
printInfo({
|
||||
key: ' tauri.js',
|
||||
value: chalk.green(require('../../package.json').version)
|
||||
})
|
||||
printInfo({ key: 'Rust environment', section: true })
|
||||
printInfo({
|
||||
key: ' rustc',
|
||||
value: getVersion('rustc', [], output => output.split(' ')[1])
|
||||
})
|
||||
printInfo({
|
||||
key: ' cargo',
|
||||
value: getVersion('cargo', [], output => output.split(' ')[1])
|
||||
})
|
||||
printInfo({ key: ' tauri-bundler', value: getVersion('cargo', ['tauri-bundler']) })
|
||||
printInfo({ key: 'Global packages', section: true })
|
||||
printInfo({ key: ' NPM', value: getVersion('npm') })
|
||||
printInfo({ key: ' yarn', value: getVersion('yarn') })
|
||||
printInfo({ key: 'App directory structure', section: true })
|
||||
|
||||
const tree = dirTree(appDir)
|
||||
for (const artifact of tree.children ?? []) {
|
||||
if (artifact.type === 'folder') {
|
||||
console.log(`/${artifact.name}`)
|
||||
}
|
||||
}
|
||||
printAppInfo(tauriDir)
|
||||
}
|
||||
|
||||
/* eslint-enable security/detect-non-literal-fs-filename */
|
||||
@@ -1,21 +0,0 @@
|
||||
import { TauriConfig } from 'types'
|
||||
import { inject } from '../template'
|
||||
|
||||
module.exports = (args: {
|
||||
directory: string
|
||||
force: false | 'conf' | 'template' | 'all'
|
||||
logging: boolean
|
||||
tauriPath?: string
|
||||
customConfig?: Partial<TauriConfig>
|
||||
}): boolean => {
|
||||
return inject(
|
||||
args.directory,
|
||||
'all',
|
||||
{
|
||||
force: args.force,
|
||||
logging: args.logging,
|
||||
tauriPath: args.tauriPath
|
||||
},
|
||||
args.customConfig
|
||||
)
|
||||
}
|
||||
@@ -1,491 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* This is a module that takes an original image and resizes
|
||||
* it to common icon sizes and will put them in a folder.
|
||||
* It will retain transparency and can make special file
|
||||
* types. You can control the settings.
|
||||
*
|
||||
* @module tauricon
|
||||
* @exports tauricon
|
||||
* @author Daniel Thompson-Yvetot
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { access, ensureDir, ensureFileSync, writeFileSync } from 'fs-extra'
|
||||
import imagemin, { Plugin } from 'imagemin'
|
||||
import optipng from 'imagemin-optipng'
|
||||
import pngquant from 'imagemin-pngquant'
|
||||
import zopfli from 'imagemin-zopfli'
|
||||
import isPng from 'is-png'
|
||||
import path from 'path'
|
||||
import * as png2icons from 'png2icons'
|
||||
import readChunk from 'read-chunk'
|
||||
import sharp from 'sharp'
|
||||
import { appDir, tauriDir } from '../helpers/app-paths'
|
||||
import logger from '../helpers/logger'
|
||||
import * as settings from '../helpers/tauricon.config'
|
||||
|
||||
const log = logger('app:spawn')
|
||||
const warn = logger('app:spawn', 'red')
|
||||
|
||||
let image: boolean | sharp.Sharp = false
|
||||
const spinnerInterval = false
|
||||
|
||||
const exists = async function(file: string | Buffer): Promise<boolean> {
|
||||
try {
|
||||
await access(file)
|
||||
return true
|
||||
} catch (err) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the first call that attempts to memoize the sharp(src).
|
||||
* If the source image cannot be found or if it is not a png, it
|
||||
* is a failsafe that will exit or throw.
|
||||
*
|
||||
* @param {string} src - a folder to target
|
||||
* @exits {error} if not a png, if not an image
|
||||
*/
|
||||
const checkSrc = async (src: string): Promise<boolean | sharp.Sharp> => {
|
||||
if (image !== false) {
|
||||
return image
|
||||
} else {
|
||||
const srcExists = await exists(src)
|
||||
if (!srcExists) {
|
||||
image = false
|
||||
if (spinnerInterval) clearInterval(spinnerInterval)
|
||||
warn('[ERROR] Source image for tauricon not found')
|
||||
process.exit(1)
|
||||
} else {
|
||||
const buffer = await readChunk(src, 0, 8)
|
||||
if (isPng(buffer) === true) {
|
||||
return (image = sharp(src))
|
||||
} else {
|
||||
image = false
|
||||
if (spinnerInterval) clearInterval(spinnerInterval)
|
||||
warn('[ERROR] Source image for tauricon is not a png')
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort the folders in the current job for unique folders.
|
||||
*
|
||||
* @param {object} options - a subset of the settings
|
||||
* @returns {array} folders
|
||||
*/
|
||||
// TODO: proper type of options and folders
|
||||
const uniqueFolders = (options: { [index: string]: any }): any[] => {
|
||||
let folders = []
|
||||
for (const type in options) {
|
||||
const option = options[String(type)]
|
||||
if (option.folder) {
|
||||
folders.push(option.folder)
|
||||
}
|
||||
}
|
||||
// TODO: is compare argument required?
|
||||
// eslint-disable-next-line @typescript-eslint/require-array-sort-compare
|
||||
folders = folders.sort().filter((x, i, a) => !i || x !== a[i - 1])
|
||||
return folders
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn a hex color (like #212342) into r,g,b values
|
||||
*
|
||||
* @param {string} hex - hex colour
|
||||
* @returns {array} r,g,b
|
||||
*/
|
||||
const hexToRgb = (
|
||||
hex: string
|
||||
): { r: number, g: number, b: number } | undefined => {
|
||||
// https://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb
|
||||
// Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
|
||||
const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i
|
||||
hex = hex.replace(shorthandRegex, function(
|
||||
m: string,
|
||||
r: string,
|
||||
g: string,
|
||||
b: string
|
||||
) {
|
||||
return r + r + g + g + b + b
|
||||
})
|
||||
|
||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
|
||||
return result
|
||||
? {
|
||||
r: parseInt(result[1], 16),
|
||||
g: parseInt(result[2], 16),
|
||||
b: parseInt(result[3], 16)
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* validate image and directory
|
||||
*/
|
||||
const validate = async (
|
||||
src: string,
|
||||
target: string
|
||||
): Promise<boolean | sharp.Sharp> => {
|
||||
if (target !== undefined) {
|
||||
await ensureDir(target)
|
||||
}
|
||||
return checkSrc(src)
|
||||
}
|
||||
|
||||
// TODO: should take end param?
|
||||
/**
|
||||
* Log progress in the command line
|
||||
*
|
||||
* @param {boolean} end
|
||||
*/
|
||||
const progress = (msg: string): void => {
|
||||
process.stdout.write(` ${msg} \r`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a spinner on the command line
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* const spinnerInterval = spinner()
|
||||
* // later
|
||||
* clearInterval(spinnerInterval)
|
||||
*/
|
||||
const spinner = (): NodeJS.Timeout => {
|
||||
return setInterval(() => {
|
||||
process.stdout.write('/ \r')
|
||||
setTimeout(() => {
|
||||
process.stdout.write('- \r')
|
||||
setTimeout(() => {
|
||||
process.stdout.write('\\ \r')
|
||||
setTimeout(() => {
|
||||
process.stdout.write('| \r')
|
||||
}, 100)
|
||||
}, 100)
|
||||
}, 100)
|
||||
}, 500)
|
||||
}
|
||||
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
const tauricon = (exports.tauricon = {
|
||||
validate: async function(src: string, target: string) {
|
||||
await validate(src, target)
|
||||
return typeof image === 'object'
|
||||
},
|
||||
version: function() {
|
||||
return __non_webpack_require__('../../package.json').version
|
||||
},
|
||||
make: async function(
|
||||
src: string = path.resolve(appDir, 'app-icon.png'),
|
||||
target: string = path.resolve(tauriDir, 'icons'),
|
||||
strategy: string,
|
||||
// TODO: proper type for options
|
||||
options: { [index: string]: any }
|
||||
) {
|
||||
const spinnerInterval = spinner()
|
||||
options = options || settings.options.tauri
|
||||
await this.validate(src, target)
|
||||
progress('Building Tauri icns and ico')
|
||||
await this.icns(src, target, options, strategy)
|
||||
progress('Building Tauri png icons')
|
||||
await this.build(src, target, options)
|
||||
if (strategy) {
|
||||
progress(`Minifying assets with ${strategy}`)
|
||||
await this.minify(target, options, strategy, 'batch')
|
||||
} else {
|
||||
log('no minify strategy')
|
||||
}
|
||||
progress('Tauricon Finished')
|
||||
clearInterval(spinnerInterval)
|
||||
return true
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates a set of images according to the subset of options it knows about.
|
||||
*
|
||||
* @param {string} src - image location
|
||||
* @param {string} target - where to drop the images
|
||||
* @param {object} options - js object that defines path and sizes
|
||||
*/
|
||||
build: async function(
|
||||
src: string,
|
||||
target: string,
|
||||
// TODO: proper type for options
|
||||
options: { [index: string]: any }
|
||||
) {
|
||||
await this.validate(src, target)
|
||||
const sharpSrc = sharp(src) // creates the image object
|
||||
const buildify2 = async function(
|
||||
pvar: [string, number, number]
|
||||
): Promise<void> {
|
||||
try {
|
||||
const pngImage = sharpSrc.resize(pvar[1], pvar[1])
|
||||
if (pvar[2]) {
|
||||
const rgb = hexToRgb(options.background_color) ?? {
|
||||
r: undefined,
|
||||
g: undefined,
|
||||
b: undefined
|
||||
}
|
||||
pngImage.flatten({
|
||||
background: { r: rgb.r, g: rgb.g, b: rgb.b, alpha: 1 }
|
||||
})
|
||||
}
|
||||
pngImage.png()
|
||||
await pngImage.toFile(pvar[0])
|
||||
} catch (err) {
|
||||
warn(err)
|
||||
}
|
||||
}
|
||||
|
||||
let output
|
||||
const folders = uniqueFolders(options)
|
||||
// eslint-disable-next-line @typescript-eslint/no-for-in-array
|
||||
for (const n in folders) {
|
||||
const folder = folders[Number(n)]
|
||||
// make the folders first
|
||||
// TODO: should this be ensureDirSync?
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
ensureDir(`${target}${path.sep}${folder}`)
|
||||
}
|
||||
for (const optionKey in options) {
|
||||
const option = options[String(optionKey)]
|
||||
// chain up the transforms
|
||||
for (const sizeKey in option.sizes) {
|
||||
const size = option.sizes[String(sizeKey)]
|
||||
if (!option.splash) {
|
||||
const dest = `${target}/${option.folder}`
|
||||
if (option.infix === true) {
|
||||
output = `${dest}${path.sep}${option.prefix}${size}x${size}${option.suffix}`
|
||||
} else {
|
||||
output = `${dest}${path.sep}${option.prefix}${option.suffix}`
|
||||
}
|
||||
const pvar: [string, number, number] = [
|
||||
output,
|
||||
size,
|
||||
option.background
|
||||
]
|
||||
await buildify2(pvar)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Creates a set of splash images (COMING SOON!!!)
|
||||
*
|
||||
* @param {string} src - icon location
|
||||
* @param {string} splashSrc - splashscreen location
|
||||
* @param {string} target - where to drop the images
|
||||
* @param {object} options - js object that defines path and sizes
|
||||
*/
|
||||
splash: async function(
|
||||
src: string,
|
||||
splashSrc: string,
|
||||
target: string,
|
||||
// TODO: proper type for options
|
||||
options: { [index: string]: any }
|
||||
) {
|
||||
let output
|
||||
let block = false
|
||||
const rgb = hexToRgb(options.background_color) ?? {
|
||||
r: undefined,
|
||||
g: undefined,
|
||||
b: undefined
|
||||
}
|
||||
|
||||
// three options
|
||||
// options: splashscreen_type [generate | overlay | pure]
|
||||
// - generate (icon + background color) DEFAULT
|
||||
// - overlay (icon + splashscreen)
|
||||
// - pure (only splashscreen)
|
||||
|
||||
let sharpSrc
|
||||
if (splashSrc === src) {
|
||||
// prevent overlay or pure
|
||||
block = true
|
||||
}
|
||||
if (block === true || options.splashscreen_type === 'generate') {
|
||||
await this.validate(src, target)
|
||||
if (!image) {
|
||||
process.exit(1)
|
||||
}
|
||||
sharpSrc = sharp(src)
|
||||
sharpSrc
|
||||
.extend({
|
||||
top: 726,
|
||||
bottom: 726,
|
||||
left: 726,
|
||||
right: 726,
|
||||
background: {
|
||||
r: rgb.r,
|
||||
g: rgb.g,
|
||||
b: rgb.b,
|
||||
alpha: 1
|
||||
}
|
||||
})
|
||||
.flatten({ background: { r: rgb.r, g: rgb.g, b: rgb.b, alpha: 1 } })
|
||||
} else if (options.splashscreen_type === 'overlay') {
|
||||
sharpSrc = sharp(splashSrc)
|
||||
.flatten({ background: { r: rgb.r, g: rgb.g, b: rgb.b, alpha: 1 } })
|
||||
.composite([
|
||||
{
|
||||
input: src
|
||||
// blend: 'multiply' <= future work, maybe just a gag
|
||||
}
|
||||
])
|
||||
} else if (options.splashscreen_type === 'pure') {
|
||||
sharpSrc = sharp(splashSrc).flatten({
|
||||
background: { r: rgb.r, g: rgb.g, b: rgb.b, alpha: 1 }
|
||||
})
|
||||
}
|
||||
// TODO: determine if this really could be undefined
|
||||
// @ts-ignore
|
||||
const data = await sharpSrc.toBuffer()
|
||||
|
||||
for (const optionKey in options) {
|
||||
const option = options[String(optionKey)]
|
||||
for (const sizeKey in option.sizes) {
|
||||
const size = option.sizes[String(sizeKey)]
|
||||
if (option.splash) {
|
||||
const dest = `${target}${path.sep}${option.folder}`
|
||||
await ensureDir(dest)
|
||||
|
||||
if (option.infix === true) {
|
||||
output = `${dest}${path.sep}${option.prefix}${size}x${size}${option.suffix}`
|
||||
} else {
|
||||
output = `${dest}${path.sep}${option.prefix}${option.suffix}`
|
||||
}
|
||||
const pvar = [output, size]
|
||||
let sharpData = sharp(data)
|
||||
sharpData = sharpData.resize(pvar[1][0], pvar[1][1])
|
||||
await sharpData.toFile(pvar[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Minifies a set of images
|
||||
*
|
||||
* @param {string} target - image location
|
||||
* @param {object} options - where to drop the images
|
||||
* @param {string} strategy - which minify strategy to use
|
||||
* @param {string} mode - singlefile or batch
|
||||
*/
|
||||
minify: async function(
|
||||
target: string,
|
||||
// TODO: proper type for options
|
||||
options: { [index: string]: any },
|
||||
strategy: string,
|
||||
mode: string
|
||||
) {
|
||||
let cmd: Plugin
|
||||
const minify = settings.options.minify
|
||||
if (!minify.available.find(x => x === strategy)) {
|
||||
strategy = minify.type
|
||||
}
|
||||
switch (strategy) {
|
||||
case 'pngquant':
|
||||
// TODO: is minify.pngquantOptions the proper format?
|
||||
// @ts-ignore
|
||||
cmd = pngquant(minify.pngquantOptions)
|
||||
break
|
||||
case 'optipng':
|
||||
cmd = optipng(minify.optipngOptions)
|
||||
break
|
||||
case 'zopfli':
|
||||
cmd = zopfli(minify.zopfliOptions)
|
||||
break
|
||||
}
|
||||
|
||||
const __minifier = async (pvar: string[]): Promise<string | void> => {
|
||||
await imagemin([pvar[0]], {
|
||||
destination: pvar[1],
|
||||
plugins: [cmd]
|
||||
}).catch(err => {
|
||||
warn(err)
|
||||
})
|
||||
}
|
||||
switch (mode) {
|
||||
case 'singlefile':
|
||||
// TODO: the __minifier function only accepts one arg, why is cmd passed?
|
||||
// @ts-ignore
|
||||
await __minifier([target, path.dirname(target)], cmd)
|
||||
break
|
||||
case 'batch':
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
const folders = uniqueFolders(options)
|
||||
// eslint-disable-next-line @typescript-eslint/no-for-in-array
|
||||
for (const n in folders) {
|
||||
const folder = folders[Number(n)]
|
||||
// TODO: The log argument doesn't accept multiple args, should this be fixed?
|
||||
// @ts-ignore
|
||||
log('batch minify:', folder)
|
||||
await __minifier(
|
||||
[
|
||||
`${target}${path.sep}${folder}${path.sep}*.png`,
|
||||
`${target}${path.sep}${folder}`
|
||||
],
|
||||
// TODO: the __minifier function only accepts one arg, why is this here?
|
||||
// @ts-ignore
|
||||
cmd
|
||||
)
|
||||
}
|
||||
break
|
||||
default:
|
||||
warn('[ERROR] Minify mode must be one of [ singlefile | batch]')
|
||||
process.exit(1)
|
||||
}
|
||||
return 'minified'
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates special icns and ico filetypes
|
||||
*
|
||||
* @param {string} src - image location
|
||||
* @param {string} target - where to drop the images
|
||||
* @param {object} options
|
||||
* @param {string} strategy
|
||||
*/
|
||||
icns: async function(
|
||||
src: string,
|
||||
target: string,
|
||||
// TODO: proper type for options
|
||||
options: { [index: string]: any },
|
||||
strategy: string
|
||||
) {
|
||||
try {
|
||||
if (!image) {
|
||||
process.exit(1)
|
||||
}
|
||||
await this.validate(src, target)
|
||||
|
||||
const sharpSrc = sharp(src)
|
||||
const buf = await sharpSrc.toBuffer()
|
||||
|
||||
const out = png2icons.createICNS(buf, png2icons.BICUBIC, 0)
|
||||
ensureFileSync(path.join(target, '/icon.icns'))
|
||||
writeFileSync(path.join(target, '/icon.icns'), out)
|
||||
|
||||
const out2 = png2icons.createICO(buf, png2icons.BICUBIC, 0, true)
|
||||
ensureFileSync(path.join(target, '/icon.ico'))
|
||||
writeFileSync(path.join(target, '/icon.ico'), out2)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
})
|
||||
/* eslint-enable @typescript-eslint/restrict-template-expressions */
|
||||
|
||||
if (typeof exports !== 'undefined') {
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
exports = module.exports = tauricon
|
||||
}
|
||||
exports.tauricon = tauricon
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import { ensureDirSync, writeFileSync } from 'fs-extra'
|
||||
import { template } from 'lodash'
|
||||
import path from 'path'
|
||||
import { TauriConfig } from './types/config'
|
||||
|
||||
export const generate = (outDir: string, cfg: TauriConfig): void => {
|
||||
// this MUST be from the templates repo
|
||||
const apiTemplate = require('!!raw-loader!!../templates/tauri.js').default
|
||||
const compiledApi = template(apiTemplate)
|
||||
|
||||
ensureDirSync(outDir)
|
||||
writeFileSync(path.join(outDir, 'tauri.js'), compiledApi(cfg), 'utf-8')
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import { existsSync } from 'fs'
|
||||
import { join, normalize, resolve, sep } from 'path'
|
||||
|
||||
const getAppDir = (): string => {
|
||||
let dir = process.cwd()
|
||||
let count = 0
|
||||
|
||||
// only go up three folders max
|
||||
while (dir.length > 0 && dir.endsWith(sep) && count <= 2) {
|
||||
if (existsSync(join(dir, 'tauri.conf.json'))) {
|
||||
return dir
|
||||
}
|
||||
count++
|
||||
dir = normalize(join(dir, '..'))
|
||||
}
|
||||
|
||||
// just return the current directory
|
||||
return process.cwd()
|
||||
}
|
||||
|
||||
const appDir = getAppDir()
|
||||
const tauriDir = resolve(appDir, 'src-tauri')
|
||||
|
||||
const resolveDir = {
|
||||
app: (dir: string) => resolve(appDir, dir),
|
||||
tauri: (dir: string) => resolve(tauriDir, dir)
|
||||
}
|
||||
|
||||
export { appDir, tauriDir, resolveDir as resolve }
|
||||
@@ -1,56 +0,0 @@
|
||||
// forked from https://github.com/quasarframework/quasar/blob/master/app/lib/app-extension/Extension.js
|
||||
import fglob from 'fast-glob'
|
||||
import fs from 'fs-extra'
|
||||
import { isBinaryFileSync as isBinary } from 'isbinaryfile'
|
||||
import { template } from 'lodash'
|
||||
import { join, resolve } from 'path'
|
||||
|
||||
const copyTemplates = ({
|
||||
source,
|
||||
target,
|
||||
scope
|
||||
}: {
|
||||
source: string
|
||||
target: string
|
||||
scope?: object
|
||||
}): void => {
|
||||
const files = fglob.sync(['**/*'], {
|
||||
cwd: source
|
||||
})
|
||||
|
||||
for (const rawPath of files) {
|
||||
const targetRelativePath = rawPath
|
||||
.split('/')
|
||||
.map(name => {
|
||||
// dotfiles are ignored when published to npm, therefore in templates
|
||||
// we need to use underscore instead (e.g. "_gitignore")
|
||||
if (name.startsWith('_') && name.charAt(1) !== '_') {
|
||||
return `.${name.slice(1)}`
|
||||
}
|
||||
if (name.startsWith('_') && name.charAt(1) === '_') {
|
||||
return `${name.slice(1)}`
|
||||
}
|
||||
return name
|
||||
})
|
||||
.join('/')
|
||||
|
||||
const targetPath = join(target, targetRelativePath)
|
||||
const sourcePath = resolve(source, rawPath)
|
||||
|
||||
fs.ensureFileSync(targetPath)
|
||||
|
||||
if (isBinary(sourcePath)) {
|
||||
fs.copyFileSync(sourcePath, targetPath)
|
||||
} else {
|
||||
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
||||
const rawContent = fs.readFileSync(sourcePath, 'utf-8')
|
||||
const compiled = template(rawContent, {
|
||||
interpolate: /<%=([\s\S]+?)%>/g
|
||||
})
|
||||
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
||||
fs.writeFileSync(targetPath, compiled(scope), 'utf-8')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default copyTemplates
|
||||
@@ -1,24 +0,0 @@
|
||||
import chalk from 'chalk'
|
||||
import ms from 'ms'
|
||||
|
||||
let prevTime: number
|
||||
|
||||
export default (banner: string, color: string = 'green') => {
|
||||
return (msg?: string) => {
|
||||
const curr = +new Date()
|
||||
const diff = curr - (prevTime || curr)
|
||||
|
||||
prevTime = curr
|
||||
|
||||
if (msg) {
|
||||
console.log(
|
||||
// TODO: proper typings for color and banner
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
` ${chalk[String(color)](String(banner))} ${msg} ${chalk.green(`+${ms(diff)}`)}`
|
||||
)
|
||||
} else {
|
||||
console.log()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
export default (fn: () => void): void => {
|
||||
const cleanup = (): void => {
|
||||
try {
|
||||
fn()
|
||||
} finally {
|
||||
process.exit()
|
||||
}
|
||||
}
|
||||
|
||||
process.on('exit', cleanup)
|
||||
process.on('SIGINT', cleanup)
|
||||
process.on('SIGTERM', cleanup)
|
||||
process.on('SIGHUP', cleanup)
|
||||
process.on('SIGBREAK', cleanup)
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
import crossSpawn from 'cross-spawn'
|
||||
import logger from './logger'
|
||||
|
||||
const log = logger('app:spawn')
|
||||
const warn = logger('app:spawn', 'red')
|
||||
|
||||
/*
|
||||
Returns pid, takes onClose
|
||||
*/
|
||||
export const spawn = (
|
||||
cmd: string,
|
||||
params: string[],
|
||||
cwd: string,
|
||||
onClose: (code: number) => void
|
||||
): number => {
|
||||
log(`Running "${cmd} ${params.join(' ')}"`)
|
||||
log()
|
||||
|
||||
// TODO: move to execa?
|
||||
const runner = crossSpawn(cmd, params, {
|
||||
stdio: 'inherit',
|
||||
cwd
|
||||
})
|
||||
|
||||
runner.on('close', code => {
|
||||
log()
|
||||
if (code) {
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
log(`Command "${cmd}" failed with exit code: ${code}`)
|
||||
}
|
||||
|
||||
onClose?.(code)
|
||||
})
|
||||
|
||||
return runner.pid
|
||||
}
|
||||
|
||||
/*
|
||||
Returns nothing, takes onFail
|
||||
*/
|
||||
export const spawnSync = (
|
||||
cmd: string,
|
||||
params: string[],
|
||||
cwd: string,
|
||||
onFail: () => void
|
||||
): void => {
|
||||
log(`[sync] Running "${cmd} ${params.join(' ')}"`)
|
||||
log()
|
||||
|
||||
const runner = crossSpawn.sync(cmd, params, {
|
||||
stdio: 'inherit',
|
||||
cwd
|
||||
})
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||
if (runner.status || runner.error) {
|
||||
warn()
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
warn(`⚠️ Command "${cmd}" failed with exit code: ${runner.status}`)
|
||||
if (runner.status === null) {
|
||||
warn(`⚠️ Please globally install "${cmd}"`)
|
||||
}
|
||||
onFail?.()
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
import { existsSync } from 'fs-extra'
|
||||
import { resolve } from 'path'
|
||||
import { TauriConfig } from 'types'
|
||||
import merge from 'webpack-merge'
|
||||
import logger from '../helpers/logger'
|
||||
import * as appPaths from './app-paths'
|
||||
|
||||
const error = logger('ERROR:', 'red')
|
||||
|
||||
const getTauriConfig = (cfg: Partial<TauriConfig>): TauriConfig => {
|
||||
const pkgPath = appPaths.resolve.app('package.json')
|
||||
const tauriConfPath = appPaths.resolve.tauri('tauri.conf.json')
|
||||
if (!existsSync(pkgPath)) {
|
||||
error("Could not find a package.json in your app's directory.")
|
||||
process.exit(1)
|
||||
}
|
||||
if (!existsSync(tauriConfPath)) {
|
||||
error(
|
||||
"Could not find a tauri config (tauri.conf.json) in your app's directory."
|
||||
)
|
||||
process.exit(1)
|
||||
}
|
||||
const tauriConf = __non_webpack_require__(tauriConfPath)
|
||||
const pkg = __non_webpack_require__(pkgPath)
|
||||
|
||||
const config = merge(
|
||||
{
|
||||
build: {},
|
||||
ctx: {},
|
||||
tauri: {
|
||||
embeddedServer: {
|
||||
active: true
|
||||
},
|
||||
bundle: {
|
||||
active: true
|
||||
},
|
||||
whitelist: {
|
||||
all: false
|
||||
},
|
||||
window: {
|
||||
title: pkg.productName
|
||||
},
|
||||
security: {
|
||||
csp: "default-src blob: data: filesystem: ws: http: https: 'unsafe-eval' 'unsafe-inline'"
|
||||
},
|
||||
edge: {
|
||||
active: true
|
||||
},
|
||||
inliner: {
|
||||
active: true
|
||||
}
|
||||
}
|
||||
} as any,
|
||||
tauriConf,
|
||||
cfg as any
|
||||
) as TauriConfig
|
||||
|
||||
const runningDevServer = config.build.devPath && config.build.devPath.startsWith('http')
|
||||
if (!runningDevServer) {
|
||||
config.build.devPath = resolve(appPaths.tauriDir, config.build.devPath)
|
||||
}
|
||||
if (config.build.distDir) {
|
||||
config.build.distDir = resolve(appPaths.tauriDir, config.build.distDir)
|
||||
}
|
||||
|
||||
process.env.TAURI_DIST_DIR = appPaths.resolve.app(config.build.distDir)
|
||||
process.env.TAURI_DIR = appPaths.tauriDir
|
||||
process.env.TAURI_CONFIG = JSON.stringify(config)
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
export default getTauriConfig
|
||||
@@ -1,74 +0,0 @@
|
||||
export const options = {
|
||||
// folder determines in which path to drop the generated file
|
||||
// prefix is the first part of the generated file's name
|
||||
// infix adds e.g. '44x44' based on the size in sizes to the generated file's name
|
||||
// suffix adds a file-ending to the generated file's name
|
||||
// sizes determines the pixel width and height to use
|
||||
background_color: '#000074',
|
||||
theme_color: '#02aa9b',
|
||||
sharp: 'kernel: sharp.kernel.lanczos3', // one of [nearest|cubic|lanczos2|lanczos3]
|
||||
minify: {
|
||||
batch: false,
|
||||
overwrite: true,
|
||||
available: ['pngquant', 'optipng', 'zopfli'],
|
||||
type: 'pngquant',
|
||||
pngcrushOptions: {
|
||||
reduce: true
|
||||
},
|
||||
pngquantOptions: {
|
||||
quality: [0.6, 0.8],
|
||||
floyd: 0.1, // 0.1 - 1
|
||||
speed: 10 // 1 - 10
|
||||
},
|
||||
optipngOptions: {
|
||||
optimizationLevel: 4,
|
||||
bitDepthReduction: true,
|
||||
colorTypeReduction: true,
|
||||
paletteReduction: true
|
||||
},
|
||||
zopfliOptions: {
|
||||
transparent: true,
|
||||
more: true
|
||||
}
|
||||
},
|
||||
splash_type: 'generate',
|
||||
tauri: {
|
||||
linux: {
|
||||
folder: '.',
|
||||
prefix: '',
|
||||
infix: true,
|
||||
suffix: '.png',
|
||||
sizes: [32, 128]
|
||||
},
|
||||
linux_2x: {
|
||||
folder: '.',
|
||||
prefix: '128x128@2x',
|
||||
infix: false,
|
||||
suffix: '.png',
|
||||
sizes: [256]
|
||||
},
|
||||
defaults: {
|
||||
folder: '.',
|
||||
prefix: 'icon',
|
||||
infix: false,
|
||||
suffix: '.png',
|
||||
sizes: [512]
|
||||
},
|
||||
appx_logo: {
|
||||
folder: '.',
|
||||
prefix: 'StoreLogo',
|
||||
infix: false,
|
||||
suffix: '.png',
|
||||
sizes: [50]
|
||||
},
|
||||
appx_square: {
|
||||
folder: '.',
|
||||
prefix: 'Square',
|
||||
infix: true,
|
||||
suffix: 'Logo.png',
|
||||
sizes: [30, 44, 71, 89, 107, 142, 150, 284, 310]
|
||||
}
|
||||
// todo: look at capacitor and cordova for insight into what icons
|
||||
// we need for those distribution targets
|
||||
}
|
||||
}
|
||||
@@ -1,337 +0,0 @@
|
||||
import Inliner from '@tauri-apps/tauri-inliner'
|
||||
import toml from '@tauri-apps/toml'
|
||||
import chokidar, { FSWatcher } from 'chokidar'
|
||||
import { existsSync, readFileSync, writeFileSync } from 'fs-extra'
|
||||
import { JSDOM } from 'jsdom'
|
||||
import { debounce, template } from 'lodash'
|
||||
import path from 'path'
|
||||
import * as entry from './entry'
|
||||
import { appDir, tauriDir } from './helpers/app-paths'
|
||||
import logger from './helpers/logger'
|
||||
import onShutdown from './helpers/on-shutdown'
|
||||
import { spawn } from './helpers/spawn'
|
||||
const getTauriConfig = require('./helpers/tauri-config')
|
||||
import { TauriConfig } from './types/config'
|
||||
|
||||
const log = logger('app:tauri', 'green')
|
||||
const warn = logger('app:tauri (template)', 'red')
|
||||
|
||||
class Runner {
|
||||
pid: number
|
||||
tauriWatcher?: FSWatcher
|
||||
devPath?: string
|
||||
killPromise?: Function
|
||||
|
||||
constructor() {
|
||||
this.pid = 0
|
||||
this.tauriWatcher = undefined
|
||||
onShutdown(() => {
|
||||
this.stop().catch(e => {
|
||||
throw e
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async run(cfg: TauriConfig): Promise<void> {
|
||||
const devPath = cfg.build.devPath
|
||||
|
||||
if (this.pid) {
|
||||
if (this.devPath !== devPath) {
|
||||
await this.stop()
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
this.__manipulateToml(toml => {
|
||||
this.__whitelistApi(cfg, toml)
|
||||
})
|
||||
|
||||
entry.generate(tauriDir, cfg)
|
||||
|
||||
const runningDevServer = devPath.startsWith('http')
|
||||
let inlinedAssets: string[] = []
|
||||
|
||||
if (!runningDevServer) {
|
||||
inlinedAssets = await this.__parseHtml(cfg, devPath)
|
||||
}
|
||||
|
||||
process.env.TAURI_INLINED_ASSSTS = inlinedAssets.join('|')
|
||||
|
||||
this.devPath = devPath
|
||||
|
||||
const features = runningDevServer ? ['dev-server'] : []
|
||||
|
||||
const startDevTauri = async (): Promise<void> => {
|
||||
return this.__runCargoCommand({
|
||||
cargoArgs: ['run'].concat(
|
||||
features.length ? ['--features', ...features] : []
|
||||
),
|
||||
dev: true
|
||||
})
|
||||
}
|
||||
|
||||
// Start watching for tauri app changes
|
||||
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
||||
this.tauriWatcher = chokidar
|
||||
.watch(
|
||||
[
|
||||
path.join(tauriDir, 'src'),
|
||||
path.join(tauriDir, 'Cargo.toml'),
|
||||
path.join(tauriDir, 'build.rs'),
|
||||
path.join(appDir, 'tauri.conf.js')
|
||||
],
|
||||
{
|
||||
ignoreInitial: true
|
||||
}
|
||||
)
|
||||
.on(
|
||||
'change',
|
||||
debounce((path: string) => {
|
||||
this.__stopCargo()
|
||||
.then(() => {
|
||||
if (path.includes('tauri.conf.js')) {
|
||||
this.run(getTauriConfig({ ctx: cfg.ctx })).catch(e => {
|
||||
throw e
|
||||
})
|
||||
} else {
|
||||
startDevTauri().catch(e => {
|
||||
throw e
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
warn(err)
|
||||
process.exit(1)
|
||||
})
|
||||
}, 1000)
|
||||
)
|
||||
|
||||
return startDevTauri()
|
||||
}
|
||||
|
||||
async build(cfg: TauriConfig): Promise<void> {
|
||||
this.__manipulateToml(toml => {
|
||||
this.__whitelistApi(cfg, toml)
|
||||
})
|
||||
|
||||
entry.generate(tauriDir, cfg)
|
||||
|
||||
const inlinedAssets = await this.__parseHtml(cfg, cfg.build.distDir)
|
||||
process.env.TAURI_INLINED_ASSSTS = inlinedAssets.join('|')
|
||||
|
||||
const features = [
|
||||
cfg.tauri.embeddedServer.active ? 'embedded-server' : 'no-server'
|
||||
]
|
||||
|
||||
const buildFn = async (target?: string): Promise<void> =>
|
||||
this.__runCargoCommand({
|
||||
cargoArgs: [
|
||||
cfg.tauri.bundle.active ? 'tauri-bundler' : 'build',
|
||||
'--features',
|
||||
...features
|
||||
]
|
||||
.concat(cfg.ctx.debug ? [] : ['--release'])
|
||||
.concat(target ? ['--target', target] : [])
|
||||
})
|
||||
|
||||
if (cfg.ctx.debug || !cfg.ctx.targetName) {
|
||||
// on debug mode or if no target specified,
|
||||
// build only for the current platform
|
||||
await buildFn()
|
||||
} else {
|
||||
const targets = cfg.ctx.target.split(',')
|
||||
|
||||
for (const target of targets) {
|
||||
await buildFn(target)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async __parseHtml(cfg: TauriConfig, indexDir: string): Promise<string[]> {
|
||||
const inlinedAssets: string[] = []
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const indexPath = path.join(indexDir, 'index.html')
|
||||
if (!existsSync(indexPath)) {
|
||||
warn(
|
||||
`Error: cannot find index.html in "${indexDir}". Did you forget to build your web code or update the build.distDir in tauri.conf.json?`
|
||||
)
|
||||
reject(new Error('Could not find index.html in dist dir.'))
|
||||
}
|
||||
|
||||
const rewriteHtml = (html: string, interceptor?: (dom: JSDOM) => void) => {
|
||||
const dom = new JSDOM(html)
|
||||
const document = dom.window.document
|
||||
if (interceptor !== undefined) {
|
||||
interceptor(dom)
|
||||
}
|
||||
|
||||
if (!((cfg.ctx.dev && cfg.build.devPath.startsWith('http')) || cfg.tauri.embeddedServer.active)) {
|
||||
const mutationObserverTemplate = require('!!raw-loader!!../templates/mutation-observer').default
|
||||
const compiledMutationObserver = template(mutationObserverTemplate)
|
||||
|
||||
const bodyMutationObserverScript = document.createElement('script')
|
||||
bodyMutationObserverScript.text = compiledMutationObserver({
|
||||
target: 'body',
|
||||
inlinedAssets: JSON.stringify(inlinedAssets)
|
||||
})
|
||||
document.body.insertBefore(bodyMutationObserverScript, document.body.firstChild)
|
||||
|
||||
const headMutationObserverScript = document.createElement('script')
|
||||
headMutationObserverScript.text = compiledMutationObserver({
|
||||
target: 'head',
|
||||
inlinedAssets: JSON.stringify(inlinedAssets)
|
||||
})
|
||||
document.head.insertBefore(headMutationObserverScript, document.head.firstChild)
|
||||
}
|
||||
|
||||
const tauriScript = document.createElement('script')
|
||||
// @ts-ignore
|
||||
tauriScript.text = readFileSync(path.join(tauriDir, 'tauri.js'))
|
||||
document.body.insertBefore(tauriScript, document.body.firstChild)
|
||||
|
||||
const csp = cfg.tauri.security.csp
|
||||
if (csp) {
|
||||
const cspTag = document.createElement('meta')
|
||||
cspTag.setAttribute('http-equiv', 'Content-Security-Policy')
|
||||
cspTag.setAttribute('content', csp)
|
||||
document.head.appendChild(cspTag)
|
||||
}
|
||||
writeFileSync(
|
||||
path.join(indexDir, 'index.tauri.html'),
|
||||
dom.serialize()
|
||||
)
|
||||
}
|
||||
|
||||
const domInterceptor = cfg.tauri.embeddedServer.active ? undefined : (dom: JSDOM) => {
|
||||
const document = dom.window.document
|
||||
document.querySelectorAll('link').forEach((link: HTMLLinkElement) => {
|
||||
link.removeAttribute('rel')
|
||||
link.removeAttribute('as')
|
||||
})
|
||||
}
|
||||
|
||||
if (cfg.tauri.embeddedServer.active || !cfg.tauri.inliner.active) {
|
||||
rewriteHtml(readFileSync(indexPath).toString(), domInterceptor)
|
||||
resolve(inlinedAssets)
|
||||
} else {
|
||||
new Inliner(indexPath, (err: Error, html: string) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
rewriteHtml(html, domInterceptor)
|
||||
resolve(inlinedAssets)
|
||||
}
|
||||
}).on('progress', (event: string) => {
|
||||
const match = event.match(/([\S\d]+)\.([\S\d]+)/g)
|
||||
match && inlinedAssets.push(match[0])
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async stop(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.tauriWatcher && this.tauriWatcher.close()
|
||||
this.__stopCargo()
|
||||
.then(resolve)
|
||||
.catch(e => {
|
||||
console.error(e)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async __runCargoCommand({
|
||||
cargoArgs,
|
||||
extraArgs,
|
||||
dev = false
|
||||
}: {
|
||||
cargoArgs: string[]
|
||||
extraArgs?: string[]
|
||||
dev?: boolean
|
||||
}): Promise<void> {
|
||||
return new Promise(resolve => {
|
||||
this.pid = spawn(
|
||||
'cargo',
|
||||
|
||||
extraArgs ? cargoArgs.concat(['--']).concat(extraArgs) : cargoArgs,
|
||||
|
||||
tauriDir,
|
||||
|
||||
code => {
|
||||
if (code) {
|
||||
warn()
|
||||
warn('⚠️ [FAIL] Cargo CLI has failed')
|
||||
warn()
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (this.killPromise) {
|
||||
this.killPromise()
|
||||
this.killPromise = undefined
|
||||
} else if (dev) {
|
||||
warn()
|
||||
warn('Cargo process was killed. Exiting...')
|
||||
warn()
|
||||
process.exit(0)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
resolve()
|
||||
})
|
||||
}
|
||||
|
||||
async __stopCargo(): Promise<void> {
|
||||
const pid = this.pid
|
||||
|
||||
if (!pid) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
log('Shutting down tauri process...')
|
||||
this.pid = 0
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.killPromise = resolve
|
||||
process.kill(pid)
|
||||
})
|
||||
}
|
||||
|
||||
__manipulateToml(callback: (tomlContents: object) => void): void {
|
||||
const tomlPath = path.join(tauriDir, 'Cargo.toml')
|
||||
const tomlFile = readFileSync(tomlPath)
|
||||
// @ts-ignore
|
||||
const tomlContents = toml.parse(tomlFile)
|
||||
|
||||
callback(tomlContents)
|
||||
|
||||
const output = toml.stringify(tomlContents)
|
||||
writeFileSync(tomlPath, output)
|
||||
}
|
||||
|
||||
__whitelistApi(
|
||||
cfg: TauriConfig,
|
||||
tomlContents: { [index: string]: any }
|
||||
): void {
|
||||
const tomlFeatures = []
|
||||
|
||||
if (cfg.tauri.whitelist.all) {
|
||||
tomlFeatures.push('all-api')
|
||||
} else {
|
||||
const whitelist = Object.keys(cfg.tauri.whitelist).filter(
|
||||
w => cfg.tauri.whitelist[String(w)] === true
|
||||
)
|
||||
tomlFeatures.push(...whitelist)
|
||||
}
|
||||
|
||||
if (cfg.tauri.edge.active) {
|
||||
tomlFeatures.push('edge')
|
||||
}
|
||||
|
||||
tomlContents.dependencies.tauri.features = tomlFeatures
|
||||
}
|
||||
}
|
||||
|
||||
export default Runner
|
||||
@@ -1,30 +0,0 @@
|
||||
export default {
|
||||
build: {
|
||||
distDir: '../dist',
|
||||
devPath: 'http://localhost:4000'
|
||||
},
|
||||
ctx: {},
|
||||
tauri: {
|
||||
embeddedServer: {
|
||||
active: true
|
||||
},
|
||||
bundle: {
|
||||
active: true
|
||||
},
|
||||
whitelist: {
|
||||
all: true
|
||||
},
|
||||
window: {
|
||||
title: 'Tauri App'
|
||||
},
|
||||
security: {
|
||||
csp: "default-src blob: data: filesystem: ws: http: https: 'unsafe-eval' 'unsafe-inline'"
|
||||
},
|
||||
edge: {
|
||||
active: true
|
||||
},
|
||||
inliner: {
|
||||
active: true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
import { existsSync, removeSync, writeFileSync } from 'fs-extra'
|
||||
import { join, normalize, resolve } from 'path'
|
||||
import { TauriConfig } from 'types'
|
||||
import merge from 'webpack-merge'
|
||||
import copyTemplates from '../helpers/copy-templates'
|
||||
import logger from '../helpers/logger'
|
||||
import defaultConfig from './defaultConfig'
|
||||
|
||||
const log = logger('app:tauri', 'green')
|
||||
const warn = logger('app:tauri (template)', 'red')
|
||||
|
||||
interface InjectOptions {
|
||||
force: false | InjectionType
|
||||
logging: boolean
|
||||
tauriPath?: string
|
||||
}
|
||||
type InjectionType = 'conf' | 'template' | 'all'
|
||||
|
||||
const injectConfFile = (
|
||||
injectPath: string,
|
||||
{ force, logging }: InjectOptions,
|
||||
customConfig: Partial<TauriConfig> = {}
|
||||
): boolean | undefined => {
|
||||
const path = join(injectPath, 'tauri.conf.json')
|
||||
if (existsSync(path) && force !== 'conf' && force !== 'all') {
|
||||
warn(`tauri.conf.json found in ${path}
|
||||
Run \`tauri init --force conf\` to overwrite.`)
|
||||
if (!force) return false
|
||||
} else {
|
||||
try {
|
||||
removeSync(path)
|
||||
const finalConf = merge(defaultConfig as any, customConfig as any) as {
|
||||
[index: string]: any
|
||||
}
|
||||
Object.keys(finalConf).forEach(key => {
|
||||
// Options marked `null` should be removed
|
||||
/* eslint-disable security/detect-object-injection */
|
||||
if (finalConf[key] === null) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||
delete finalConf[key]
|
||||
}
|
||||
/* eslint-enable security/detect-object-injection */
|
||||
})
|
||||
writeFileSync(path, JSON.stringify(finalConf, undefined, 2))
|
||||
} catch (e) {
|
||||
if (logging) console.log(e)
|
||||
return false
|
||||
} finally {
|
||||
if (logging) log('Successfully wrote tauri.conf.json')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const injectTemplate = (
|
||||
injectPath: string,
|
||||
{ force, logging, tauriPath }: InjectOptions
|
||||
): boolean | undefined => {
|
||||
const dir = normalize(join(injectPath, 'src-tauri'))
|
||||
if (existsSync(dir) && force !== 'template' && force !== 'all') {
|
||||
warn(`Tauri dir (${dir}) not empty.
|
||||
Run \`tauri init --force template\` to overwrite.`)
|
||||
if (!force) return false
|
||||
}
|
||||
|
||||
const tauriDep = tauriPath
|
||||
? `{ path = "${join('..', tauriPath, 'tauri')}" }`
|
||||
: null
|
||||
|
||||
try {
|
||||
removeSync(dir)
|
||||
copyTemplates({
|
||||
source: resolve(__dirname, '../../templates/src-tauri'),
|
||||
scope: {
|
||||
tauriDep
|
||||
},
|
||||
target: dir
|
||||
})
|
||||
} catch (e) {
|
||||
if (logging) console.log(e)
|
||||
return false
|
||||
} finally {
|
||||
if (logging) log('Successfully wrote src-tauri')
|
||||
}
|
||||
}
|
||||
|
||||
const inject = (
|
||||
injectPath: string,
|
||||
type: InjectionType,
|
||||
{ force = false, logging = false, tauriPath }: InjectOptions,
|
||||
customConfig?: Partial<TauriConfig>
|
||||
): boolean => {
|
||||
if (typeof type !== 'string' || typeof injectPath !== 'string') {
|
||||
warn('- internal error. Required params missing.')
|
||||
return false
|
||||
}
|
||||
if (type === 'template' || type === 'all') {
|
||||
injectTemplate(injectPath, { force, logging, tauriPath })
|
||||
}
|
||||
if (type === 'conf' || type === 'all') {
|
||||
injectConfFile(join(injectPath, 'src-tauri'), { force, logging }, customConfig)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
export { inject }
|
||||
@@ -1,46 +0,0 @@
|
||||
// TODO: Clean up types, properly mark which ones are optional
|
||||
// May need to have different types for each stage of config generation process
|
||||
|
||||
export interface TauriConfig {
|
||||
build: {
|
||||
distDir: string
|
||||
devPath: string
|
||||
}
|
||||
ctx: {
|
||||
prod?: boolean
|
||||
dev?: boolean
|
||||
target: string
|
||||
debug?: boolean
|
||||
targetName: string
|
||||
}
|
||||
bundle: {}
|
||||
tauri: {
|
||||
inlinedAssets: string[]
|
||||
devPath: string
|
||||
embeddedServer: {
|
||||
active: boolean
|
||||
}
|
||||
bundle: {
|
||||
active: boolean
|
||||
}
|
||||
whitelist: {
|
||||
all: boolean
|
||||
[index: string]: boolean
|
||||
}
|
||||
window: {
|
||||
title: string
|
||||
width: number
|
||||
height: number
|
||||
resizable: boolean
|
||||
}
|
||||
security: {
|
||||
csp: string
|
||||
}
|
||||
edge: {
|
||||
active: boolean
|
||||
}
|
||||
inliner: {
|
||||
active: boolean
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from './config'
|
||||
@@ -1,5 +0,0 @@
|
||||
declare module '@tauri-apps/tauri-inliner'
|
||||
declare module 'imagemin-zopfli'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
declare const __non_webpack_require__: Function
|
||||
@@ -1,45 +0,0 @@
|
||||
(function () {
|
||||
function loadAsset(path, type) {
|
||||
if (path) {
|
||||
if (window.tauri !== void 0) {
|
||||
window.tauri.loadAsset(path, type)
|
||||
} else {
|
||||
if (window.__TAURI_INIT_HOOKS === void 0) {
|
||||
window.__TAURI_INIT_HOOKS = []
|
||||
}
|
||||
window.__TAURI_INIT_HOOKS.push(function () {
|
||||
window.tauri.loadAsset(path, type)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var observer = new MutationObserver(mutation => {
|
||||
mutation.forEach(function (mutationRecord) {
|
||||
var addedNodes = mutationRecord.addedNodes
|
||||
addedNodes.forEach(function (node) {
|
||||
if (node.nodeType === 1) {
|
||||
if (node.tagName === 'SCRIPT') {
|
||||
node.onload = node.onerror = null
|
||||
loadAsset(node.src)
|
||||
} else if (node.tagName === 'LINK') {
|
||||
if (node.type === 'text/css' || (node.href && node.href.endsWith('.css'))) {
|
||||
loadAsset(node.href, 'stylesheet')
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
<% if (target === 'body') { %>
|
||||
var target = document.documentElement
|
||||
<% } else { %>
|
||||
var target = document.head
|
||||
<% } %>
|
||||
|
||||
observer.observe(target, {
|
||||
childList: true,
|
||||
subtree: true
|
||||
})
|
||||
})()
|
||||
@@ -1,363 +0,0 @@
|
||||
/* eslint-disable */
|
||||
|
||||
/**
|
||||
* * THIS FILE IS GENERATED AUTOMATICALLY.
|
||||
* DO NOT EDIT.
|
||||
*
|
||||
* Please whitelist these API functions in tauri.conf.json
|
||||
*
|
||||
**/
|
||||
|
||||
// open <a href="..."> links with the Tauri API
|
||||
|
||||
/**
|
||||
* @module tauri
|
||||
* @description This API interface makes powerful interactions available
|
||||
* to be run on client side applications. They are opt-in features, and
|
||||
* must be enabled in tauri.conf.json
|
||||
*
|
||||
* Each binding MUST provide these interfaces in order to be compliant,
|
||||
* and also whitelist them based upon the developer's settings.
|
||||
*/
|
||||
|
||||
function s4() {
|
||||
return Math.floor((1 + Math.random()) * 0x10000)
|
||||
.toString(16)
|
||||
.substring(1)
|
||||
}
|
||||
|
||||
const uid = function () {
|
||||
return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
|
||||
s4() + '-' + s4() + s4() + s4()
|
||||
}
|
||||
|
||||
<% if (ctx.dev) { %>
|
||||
/**
|
||||
* @name __whitelistWarning
|
||||
* @description Present a stylish warning to the developer that their API
|
||||
* call has not been whitelisted in tauri.conf.json
|
||||
* @param {String} func - function name to warn
|
||||
* @private
|
||||
*/
|
||||
const __whitelistWarning = function (func) {
|
||||
console.warn('%c[Tauri] Danger \ntauri.' + func + ' not whitelisted 💣\n%c\nAdd to tauri.conf.json: \n\ntauri: \n whitelist: { \n ' + func + ': true \n\nReference: https://github.com/tauri-apps/tauri/wiki' + func , 'background: red; color: white; font-weight: 800; padding: 2px; font-size:1.5em', ' ')
|
||||
}
|
||||
<% } %>
|
||||
|
||||
<% if (ctx.dev) { %>
|
||||
/**
|
||||
* @name __reject
|
||||
* @description is a private promise used to deflect un-whitelisted tauri API calls
|
||||
* Its only purpose is to maintain thenable structure in client code without
|
||||
* breaking the application
|
||||
* * @type {Promise<any>}
|
||||
* @private
|
||||
*/
|
||||
<% } %>
|
||||
const __reject = new Promise((reject) => { reject })
|
||||
|
||||
window.tauri = {
|
||||
<% if (ctx.dev) { %>
|
||||
/**
|
||||
* @name invoke
|
||||
* @description Calls a Tauri Core feature, such as setTitle
|
||||
* @param {Object} args
|
||||
*/
|
||||
<% } %>
|
||||
invoke (args) {
|
||||
Object.freeze(args)
|
||||
window.external.invoke(JSON.stringify(args))
|
||||
},
|
||||
|
||||
<% if (ctx.dev) { %>
|
||||
/**
|
||||
* @name listen
|
||||
* @description Add an event listener to Tauri backend
|
||||
* @param {String} event
|
||||
* @param {Function} handler
|
||||
* @param {Boolean} once
|
||||
*/
|
||||
<% } %>
|
||||
listen (event, handler, once = false) {
|
||||
this.invoke({
|
||||
cmd: 'listen',
|
||||
event,
|
||||
handler: this.transformCallback(handler, once),
|
||||
once
|
||||
})
|
||||
},
|
||||
|
||||
<% if (ctx.dev) { %>
|
||||
/**
|
||||
* @name emit
|
||||
* @description Emits an evt to the Tauri back end
|
||||
* @param {String} evt
|
||||
* @param {Object} payload
|
||||
*/
|
||||
<% } %>
|
||||
emit (evt, payload) {
|
||||
this.invoke({
|
||||
cmd: 'emit',
|
||||
event: evt,
|
||||
payload
|
||||
})
|
||||
},
|
||||
|
||||
<% if (ctx.dev) { %>
|
||||
/**
|
||||
* @name transformCallback
|
||||
* @description Registers a callback with a uid
|
||||
* @param {Function} callback
|
||||
* @param {Boolean} once
|
||||
* @returns {*}
|
||||
*/
|
||||
<% } %>
|
||||
transformCallback (callback, once = true) {
|
||||
const identifier = Object.freeze(uid())
|
||||
window[identifier] = (result) => {
|
||||
if (once) {
|
||||
delete window[identifier]
|
||||
}
|
||||
return callback && callback(result)
|
||||
}
|
||||
return identifier
|
||||
},
|
||||
|
||||
<% if (ctx.dev) { %>
|
||||
/**
|
||||
* @name promisified
|
||||
* @description Turns a request into a chainable promise
|
||||
* @param {Object} args
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
<% } %>
|
||||
promisified (args) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.invoke({
|
||||
callback: this.transformCallback(resolve),
|
||||
error: this.transformCallback(reject),
|
||||
...args
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
<% if (ctx.dev) { %>
|
||||
/**
|
||||
* @name readTextFile
|
||||
* @description Accesses a non-binary file on the user's filesystem
|
||||
* and returns the content. Permissions based on the app's PID owner
|
||||
* @param {String} path
|
||||
* @returns {*|Promise<any>|Promise}
|
||||
*/
|
||||
<% } %>
|
||||
readTextFile (path) {
|
||||
<% if (tauri.whitelist.readTextFile === true || tauri.whitelist.all === true) { %>
|
||||
Object.freeze(path)
|
||||
return this.promisified({ cmd: 'readTextFile', path })
|
||||
<% } else { %>
|
||||
<% if (ctx.dev) { %>
|
||||
__whitelistWarning('readTextFile')
|
||||
<% } %>
|
||||
return __reject
|
||||
<% } %>
|
||||
},
|
||||
|
||||
<% if (ctx.dev) { %>
|
||||
/**
|
||||
* @name readBinaryFile
|
||||
* @description Accesses a binary file on the user's filesystem
|
||||
* and returns the content. Permissions based on the app's PID owner
|
||||
* @param {String} path
|
||||
* @returns {*|Promise<any>|Promise}
|
||||
*/
|
||||
<% } %>
|
||||
readBinaryFile (path) {
|
||||
<% if (tauri.whitelist.readBinaryFile === true || tauri.whitelist.all === true) { %>
|
||||
Object.freeze(path)
|
||||
return this.promisified({ cmd: 'readBinaryFile', path })
|
||||
<% } else { %>
|
||||
<% if (ctx.dev) { %>
|
||||
__whitelistWarning('readBinaryFile')
|
||||
<% } %>
|
||||
return __reject
|
||||
<% } %>
|
||||
},
|
||||
|
||||
<% if (ctx.dev) { %>
|
||||
/**
|
||||
* @name writeFile
|
||||
* @description Write a file to the Local Filesystem.
|
||||
* Permissions based on the app's PID owner
|
||||
* @param {Object} cfg
|
||||
* @param {String} cfg.file
|
||||
* @param {String|Binary} cfg.contents
|
||||
*/
|
||||
<% } %>
|
||||
writeFile (cfg) {
|
||||
<% if (tauri.whitelist.writeFile === true || tauri.whitelist.all === true) { %>
|
||||
Object.freeze(cfg)
|
||||
this.invoke({ cmd: 'writeFile', file: cfg.file, contents: cfg.contents })
|
||||
<% } else { %>
|
||||
<% if (ctx.dev) { %>
|
||||
__whitelistWarning('writeFile')
|
||||
<% } %>
|
||||
return __reject
|
||||
<% } %>
|
||||
},
|
||||
|
||||
<% if (ctx.dev) { %>
|
||||
/**
|
||||
* @name listFiles
|
||||
* @description Get the files in a path.
|
||||
* Permissions based on the app's PID owner
|
||||
* @param {String} path
|
||||
* @returns {*|Promise<any>|Promise}
|
||||
*/
|
||||
<% } %>
|
||||
listFiles (path) {
|
||||
<% if (tauri.whitelist.listFiles === true || tauri.whitelist.all === true) { %>
|
||||
Object.freeze(path)
|
||||
return this.promisified({ cmd: 'listFiles', path })
|
||||
<% } else { %>
|
||||
<% if (ctx.dev) { %>
|
||||
__whitelistWarning('listFiles')
|
||||
<% } %>
|
||||
return __reject
|
||||
<% } %>
|
||||
},
|
||||
|
||||
<% if (ctx.dev) { %>
|
||||
/**
|
||||
* @name listDirs
|
||||
* @description Get the directories in a path.
|
||||
* Permissions based on the app's PID owner
|
||||
* @param {String} path
|
||||
* @returns {*|Promise<any>|Promise}
|
||||
*/
|
||||
<% } %>
|
||||
listDirs (path) {
|
||||
<% if (tauri.whitelist.listDirs === true || tauri.whitelist.all === true) { %>
|
||||
Object.freeze(path)
|
||||
return this.promisified({ cmd: 'listDirs', path })
|
||||
<% } else { %>
|
||||
<% if (ctx.dev) { %>
|
||||
__whitelistWarning('listDirs')
|
||||
<% } %>
|
||||
return __reject
|
||||
<% } %>
|
||||
},
|
||||
|
||||
<% if (ctx.dev) { %>
|
||||
/**
|
||||
* @name setTitle
|
||||
* @description Set the application's title
|
||||
* @param {String} title
|
||||
*/
|
||||
<% } %>
|
||||
setTitle (title) {
|
||||
<% if (tauri.whitelist.setTitle === true || tauri.whitelist.all === true) { %>
|
||||
Object.freeze(title)
|
||||
this.invoke({ cmd: 'setTitle', title })
|
||||
<% } else { %>
|
||||
<% if (ctx.dev) { %>
|
||||
__whitelistWarning('setTitle')
|
||||
<% } %>
|
||||
return __reject
|
||||
<% } %>
|
||||
},
|
||||
|
||||
<% if (ctx.dev) { %>
|
||||
/**
|
||||
* @name open
|
||||
* @description Open an URI
|
||||
* @param {String} uri
|
||||
*/
|
||||
<% } %>
|
||||
open (uri) {
|
||||
<% if (tauri.whitelist.open === true || tauri.whitelist.all === true) { %>
|
||||
Object.freeze(uri)
|
||||
this.invoke({ cmd: 'open', uri })
|
||||
<% } else { %>
|
||||
<% if (ctx.dev) { %>
|
||||
__whitelistWarning('open')
|
||||
<% } %>
|
||||
return __reject
|
||||
<% } %>
|
||||
},
|
||||
|
||||
<% if (ctx.dev) { %>
|
||||
/**
|
||||
* @name execute
|
||||
* @description Execute a program with arguments.
|
||||
* Permissions based on the app's PID owner
|
||||
* @param {String} command
|
||||
* @param {String|Array} args
|
||||
* @returns {*|Promise<any>|Promise}
|
||||
*/
|
||||
<% } %>
|
||||
execute (command, args) {
|
||||
<% if (tauri.whitelist.execute === true || tauri.whitelist.all === true) { %>
|
||||
Object.freeze(command)
|
||||
if (typeof args === 'string' || typeof args === 'object') {
|
||||
Object.freeze(args)
|
||||
}
|
||||
return this.promisified({ cmd: 'execute', command, args: typeof (args) === 'string' ? [args] : args })
|
||||
<% } else { %>
|
||||
<% if (ctx.dev) { %>
|
||||
__whitelistWarning('execute')
|
||||
<% } %>
|
||||
return __reject
|
||||
<% } %>
|
||||
},
|
||||
|
||||
<% if (ctx.dev) { %>
|
||||
/**
|
||||
* @name bridge
|
||||
* @description Securely pass a message to the backend.
|
||||
* @example
|
||||
* this.$q.tauri.bridge('QBP/1/ping/client-1', 'pingback')
|
||||
* @param {String} command - a compressed, slash-delimited and
|
||||
* versioned API call to the backend.
|
||||
* @param {String|Object}payload
|
||||
* @returns {*|Promise<any>|Promise}
|
||||
*/
|
||||
<% } %>
|
||||
bridge (command, payload) {
|
||||
<% if (tauri.whitelist.bridge === true || tauri.whitelist.all === true) { %>
|
||||
Object.freeze(command)
|
||||
if (typeof payload === 'string' || typeof payload === 'object') {
|
||||
Object.freeze(payload)
|
||||
}
|
||||
return this.promisified({ cmd: 'bridge', command, payload: typeof (payload) === 'object' ? [payload] : payload })
|
||||
<% } else { %>
|
||||
<% if (ctx.dev) { %>
|
||||
__whitelistWarning('bridge')
|
||||
<% } %>
|
||||
return __reject
|
||||
<% } %>
|
||||
},
|
||||
|
||||
<% if (ctx.dev) { %>
|
||||
/**
|
||||
* @name setup
|
||||
* @description Inform Rust that the webview has initialized and is
|
||||
* ready for communication
|
||||
*/
|
||||
<% } %>
|
||||
setup () {
|
||||
document.querySelector('body').addEventListener('click', function (e) {
|
||||
let target = e.target
|
||||
while (target != null) {
|
||||
if (target.matches ? target.matches('a') : target.msMatchesSelector('a')) {
|
||||
tauri.open(target.href)
|
||||
break
|
||||
}
|
||||
target = target.parentElement
|
||||
}
|
||||
}, true)
|
||||
|
||||
tauri.invoke({
|
||||
cmd: 'init'
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,460 +0,0 @@
|
||||
/* eslint-disable */
|
||||
|
||||
/**
|
||||
* * THIS FILE IS GENERATED AUTOMATICALLY.
|
||||
* DO NOT EDIT.
|
||||
*
|
||||
* Please whitelist these API functions in tauri.conf.json
|
||||
*
|
||||
**/
|
||||
|
||||
/**
|
||||
* @module tauri
|
||||
* @description This API interface makes powerful interactions available
|
||||
* to be run on client side applications. They are opt-in features, and
|
||||
* must be enabled in tauri.conf.json
|
||||
*
|
||||
* Each binding MUST provide these interfaces in order to be compliant,
|
||||
* and also whitelist them based upon the developer's settings.
|
||||
*/
|
||||
|
||||
// makes the window.external.invoke API available after window.location.href changes
|
||||
if (navigator.platform != "Win64" && navigator.plaform != "Win32") {
|
||||
window.external = this
|
||||
if (navigator.platform == "MacIntel") {
|
||||
invoke = function (x) {
|
||||
webkit.messageHandlers.invoke.postMessage(x);
|
||||
}
|
||||
} else {
|
||||
invoke = function (x) {
|
||||
window.webkit.messageHandlers.external.postMessage(x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function s4() {
|
||||
return Math.floor((1 + Math.random()) * 0x10000)
|
||||
.toString(16)
|
||||
.substring(1)
|
||||
}
|
||||
|
||||
var uid = function () {
|
||||
return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
|
||||
s4() + '-' + s4() + s4() + s4()
|
||||
}
|
||||
|
||||
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
|
||||
|
||||
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
|
||||
|
||||
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
||||
|
||||
|
||||
function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
|
||||
|
||||
<% if (ctx.dev) { %>
|
||||
/**
|
||||
* @name return __whitelistWarning
|
||||
* @description Present a stylish warning to the developer that their API
|
||||
* call has not been whitelisted in tauri.conf.json
|
||||
* @param {String} func - function name to warn
|
||||
* @private
|
||||
*/
|
||||
var __whitelistWarning = function (func) {
|
||||
console.warn('%c[Tauri] Danger \ntauri.' + func + ' not whitelisted 💣\n%c\nAdd to tauri.conf.json: \n\ntauri: \n whitelist: { \n ' + func + ': true \n\nReference: https://github.com/tauri-apps/tauri/wiki' + func , 'background: red; color: white; font-weight: 800; padding: 2px; font-size:1.5em', ' ')
|
||||
return __reject()
|
||||
}
|
||||
<% } %>
|
||||
|
||||
<% if (ctx.dev) { %>
|
||||
/**
|
||||
* @name __reject
|
||||
* @description generates a promise used to deflect un-whitelisted tauri API calls
|
||||
* Its only purpose is to maintain thenable structure in client code without
|
||||
* breaking the application
|
||||
* * @type {Promise<any>}
|
||||
* @private
|
||||
*/
|
||||
<% } %>
|
||||
var __reject = function () {
|
||||
return new Promise(function (_, reject) {
|
||||
reject();
|
||||
});
|
||||
}
|
||||
|
||||
window.tauri = {
|
||||
<% if (ctx.dev) { %>
|
||||
/**
|
||||
* @name invoke
|
||||
* @description Calls a Tauri Core feature, such as setTitle
|
||||
* @param {Object} args
|
||||
*/
|
||||
<% } %>
|
||||
invoke: function invoke(args) {
|
||||
window.external.invoke(JSON.stringify(args));
|
||||
},
|
||||
|
||||
<% if (ctx.dev) { %>
|
||||
/**
|
||||
* @name listen
|
||||
* @description Add an event listener to Tauri backend
|
||||
* @param {String} event
|
||||
* @param {Function} handler
|
||||
* @param {Boolean} once
|
||||
*/
|
||||
<% } %>
|
||||
listen: function listen(event, handler) {
|
||||
<% if (tauri.whitelist.event === true || tauri.whitelist.all === true) { %>
|
||||
var once = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
||||
this.invoke({
|
||||
cmd: 'listen',
|
||||
event: event,
|
||||
handler: window.tauri.transformCallback(handler, once),
|
||||
once: once
|
||||
});
|
||||
<% } else { %>
|
||||
<% if (ctx.dev) { %>
|
||||
return __whitelistWarning('event')
|
||||
<% } %>
|
||||
return __reject()
|
||||
<% } %>
|
||||
},
|
||||
|
||||
<% if (ctx.dev) { %>
|
||||
/**
|
||||
* @name emit
|
||||
* @description Emits an evt to the Tauri back end
|
||||
* @param {String} evt
|
||||
* @param {Object} payload
|
||||
*/
|
||||
<% } %>
|
||||
emit: function emit(evt, payload) {
|
||||
<% if (tauri.whitelist.event === true || tauri.whitelist.all === true) { %>
|
||||
this.invoke({
|
||||
cmd: 'emit',
|
||||
event: evt,
|
||||
payload: payload || ''
|
||||
});
|
||||
<% } else { %>
|
||||
<% if (ctx.dev) { %>
|
||||
return __whitelistWarning('event')
|
||||
<% } %>
|
||||
return __reject()
|
||||
<% } %>
|
||||
},
|
||||
|
||||
<% if (ctx.dev) { %>
|
||||
/**
|
||||
* @name transformCallback
|
||||
* @description Registers a callback with a uid
|
||||
* @param {Function} callback
|
||||
* @param {Boolean} once
|
||||
* @returns {*}
|
||||
*/
|
||||
<% } %>
|
||||
transformCallback: function transformCallback(callback) {
|
||||
var once = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
|
||||
var identifier = Object.freeze(uid());
|
||||
|
||||
window[identifier] = function (result) {
|
||||
if (once) {
|
||||
delete window[identifier];
|
||||
}
|
||||
|
||||
return callback && callback(result);
|
||||
};
|
||||
|
||||
return identifier;
|
||||
},
|
||||
|
||||
<% if (ctx.dev) { %>
|
||||
/**
|
||||
* @name promisified
|
||||
* @description Turns a request into a chainable promise
|
||||
* @param {Object} args
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
<% } %>
|
||||
promisified: function promisified(args) {
|
||||
var _this = this;
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
_this.invoke(_objectSpread({
|
||||
callback: _this.transformCallback(resolve),
|
||||
error: _this.transformCallback(reject)
|
||||
}, args));
|
||||
});
|
||||
},
|
||||
|
||||
<% if (ctx.dev) { %>
|
||||
/**
|
||||
* @name readTextFile
|
||||
* @description Accesses a non-binary file on the user's filesystem
|
||||
* and returns the content. Permissions based on the app's PID owner
|
||||
* @param {String} path
|
||||
* @returns {*|Promise<any>|Promise}
|
||||
*/
|
||||
<% } %>
|
||||
readTextFile: function readTextFile(path) {
|
||||
<% if (tauri.whitelist.readTextFile === true || tauri.whitelist.all === true) { %>
|
||||
Object.freeze(path);
|
||||
return this.promisified({
|
||||
cmd: 'readTextFile',
|
||||
path: path
|
||||
});
|
||||
<% } else { %>
|
||||
<% if (ctx.dev) { %>
|
||||
return __whitelistWarning('readTextFile')
|
||||
<% } %>
|
||||
return __reject()
|
||||
<% } %>
|
||||
},
|
||||
|
||||
<% if (ctx.dev) { %>
|
||||
/**
|
||||
* @name readBinaryFile
|
||||
* @description Accesses a binary file on the user's filesystem
|
||||
* and returns the content. Permissions based on the app's PID owner
|
||||
* @param {String} path
|
||||
* @returns {*|Promise<any>|Promise}
|
||||
*/
|
||||
<% } %>
|
||||
readBinaryFile: function readBinaryFile(path) {
|
||||
<% if (tauri.whitelist.readBinaryFile === true || tauri.whitelist.all === true) { %>
|
||||
Object.freeze(path);
|
||||
return this.promisified({
|
||||
cmd: 'readBinaryFile',
|
||||
path: path
|
||||
});
|
||||
<% } else { %>
|
||||
<% if (ctx.dev) { %>
|
||||
return __whitelistWarning('readBinaryFile')
|
||||
<% } %>
|
||||
return __reject()
|
||||
<% } %>
|
||||
},
|
||||
|
||||
<% if (ctx.dev) { %>
|
||||
/**
|
||||
* @name writeFile
|
||||
* @description Write a file to the Local Filesystem.
|
||||
* Permissions based on the app's PID owner
|
||||
* @param {Object} cfg
|
||||
* @param {String} cfg.file
|
||||
* @param {String|Binary} cfg.contents
|
||||
*/
|
||||
<% } %>
|
||||
writeFile: function writeFile(cfg) {
|
||||
<% if (tauri.whitelist.writeFile === true || tauri.whitelist.all === true) { %>
|
||||
Object.freeze(cfg);
|
||||
this.invoke({
|
||||
cmd: 'writeFile',
|
||||
file: cfg.file,
|
||||
contents: cfg.contents
|
||||
});
|
||||
<% } else { %>
|
||||
<% if (ctx.dev) { %>
|
||||
return __whitelistWarning('writeFile')
|
||||
<% } %>
|
||||
return __reject()
|
||||
<% } %>
|
||||
},
|
||||
|
||||
<% if (ctx.dev) { %>
|
||||
/**
|
||||
* @name listFiles
|
||||
* @description Get the files in a path.
|
||||
* Permissions based on the app's PID owner
|
||||
* @param {String} path
|
||||
* @returns {*|Promise<any>|Promise}
|
||||
*/
|
||||
<% } %>
|
||||
listFiles: function listFiles(path) {
|
||||
<% if (tauri.whitelist.listFiles === true || tauri.whitelist.all === true) { %>
|
||||
|
||||
Object.freeze(path);
|
||||
return this.promisified({
|
||||
cmd: 'listFiles',
|
||||
path: path
|
||||
});
|
||||
<% } else { %>
|
||||
<% if (ctx.dev) { %>
|
||||
return __whitelistWarning('listDirs')
|
||||
<% } %>
|
||||
return __reject()
|
||||
<% } %>
|
||||
},
|
||||
|
||||
<% if (ctx.dev) { %>
|
||||
/**
|
||||
* @name listDirs
|
||||
* @description Get the directories in a path.
|
||||
* Permissions based on the app's PID owner
|
||||
* @param {String} path
|
||||
* @returns {*|Promise<any>|Promise}
|
||||
*/
|
||||
<% } %>
|
||||
listDirs: function listDirs(path) {
|
||||
<% if (tauri.whitelist.listDirs === true || tauri.whitelist.all === true) { %>
|
||||
Object.freeze(path);
|
||||
return this.promisified({
|
||||
cmd: 'listDirs',
|
||||
path: path
|
||||
});
|
||||
<% } else { %>
|
||||
<% if (ctx.dev) { %>
|
||||
return __whitelistWarning('listDirs')
|
||||
<% } %>
|
||||
return __reject()
|
||||
<% } %>
|
||||
},
|
||||
|
||||
<% if (ctx.dev) { %>
|
||||
/**
|
||||
* @name setTitle
|
||||
* @description Set the application's title
|
||||
* @param {String} title
|
||||
*/
|
||||
<% } %>
|
||||
setTitle: function setTitle(title) {
|
||||
<% if (tauri.whitelist.setTitle === true || tauri.whitelist.all === true) { %>
|
||||
Object.freeze(title);
|
||||
this.invoke({
|
||||
cmd: 'setTitle',
|
||||
title: title
|
||||
});
|
||||
<% } else { %>
|
||||
<% if (ctx.dev) { %>
|
||||
return __whitelistWarning('setTitle')
|
||||
<% } %>
|
||||
return __reject()
|
||||
<% } %>
|
||||
},
|
||||
|
||||
<% if (ctx.dev) { %>
|
||||
/**
|
||||
* @name open
|
||||
* @description Open an URI
|
||||
* @param {String} uri
|
||||
*/
|
||||
<% } %>
|
||||
open: function open(uri) {
|
||||
<% if (tauri.whitelist.open === true || tauri.whitelist.all === true) { %>
|
||||
Object.freeze(uri);
|
||||
this.invoke({
|
||||
cmd: 'open',
|
||||
uri: uri
|
||||
});
|
||||
<% } else { %>
|
||||
<% if (ctx.dev) { %>
|
||||
return __whitelistWarning('open')
|
||||
<% } %>
|
||||
return __reject()
|
||||
<% } %>
|
||||
},
|
||||
|
||||
<% if (ctx.dev) { %>
|
||||
/**
|
||||
* @name execute
|
||||
* @description Execute a program with arguments.
|
||||
* Permissions based on the app's PID owner
|
||||
* @param {String} command
|
||||
* @param {String|Array} args
|
||||
* @returns {*|Promise<any>|Promise}
|
||||
*/
|
||||
<% } %>
|
||||
execute: function execute(command, args) {
|
||||
<% if (tauri.whitelist.execute === true || tauri.whitelist.all === true) { %>
|
||||
|
||||
Object.freeze(command);
|
||||
|
||||
if (typeof args === 'string' || _typeof(args) === 'object') {
|
||||
Object.freeze(args);
|
||||
}
|
||||
|
||||
return this.promisified({
|
||||
cmd: 'execute',
|
||||
command: command,
|
||||
args: typeof args === 'string' ? [args] : args
|
||||
});
|
||||
<% } else { %>
|
||||
<% if (ctx.dev) { %>
|
||||
return __whitelistWarning('execute')
|
||||
<% } %>
|
||||
return __reject()
|
||||
<% } %>
|
||||
},
|
||||
|
||||
bridge: function bridge(command, payload) {
|
||||
<% if (tauri.whitelist.bridge === true || tauri.whitelist.all === true) { %>
|
||||
|
||||
Object.freeze(command);
|
||||
|
||||
if (typeof payload === 'string' || _typeof(payload) === 'object') {
|
||||
Object.freeze(payload);
|
||||
}
|
||||
|
||||
return this.promisified({
|
||||
cmd: 'bridge',
|
||||
command: command,
|
||||
payload: _typeof(payload) === 'object' ? [payload] : payload
|
||||
});
|
||||
<% } else { %>
|
||||
<% if (ctx.dev) { %>
|
||||
return __whitelistWarning('bridge')
|
||||
<% } %>
|
||||
return __reject()
|
||||
<% } %>
|
||||
},
|
||||
|
||||
loadAsset: function loadAsset(assetName, assetType) {
|
||||
return this.promisified({
|
||||
cmd: 'loadAsset',
|
||||
asset: assetName,
|
||||
asset_type: assetType || 'unknown'
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
// init tauri API
|
||||
try {
|
||||
window.tauri.invoke({
|
||||
cmd: 'init'
|
||||
})
|
||||
} catch (e) {
|
||||
window.addEventListener('DOMContentLoaded', function () {
|
||||
window.tauri.invoke({
|
||||
cmd: 'init'
|
||||
})
|
||||
}, true)
|
||||
}
|
||||
|
||||
document.addEventListener('error', function (e) {
|
||||
var target = e.target
|
||||
while (target != null) {
|
||||
if (target.matches ? target.matches('img') : target.msMatchesSelector('img')) {
|
||||
window.tauri.loadAsset(target.src, 'image')
|
||||
.then(img => {
|
||||
target.src = img
|
||||
})
|
||||
break
|
||||
}
|
||||
target = target.parentElement
|
||||
}
|
||||
}, true)
|
||||
|
||||
window.addEventListener('DOMContentLoaded', function () {
|
||||
// open <a href="..."> links with the Tauri API
|
||||
document.querySelector('body').addEventListener('click', function (e) {
|
||||
var target = e.target
|
||||
while (target != null) {
|
||||
if (target.matches ? target.matches('a') : target.msMatchesSelector('a')) {
|
||||
if (target.href && target.href.startsWith('http')) {
|
||||
window.tauri.open(target.href)
|
||||
}
|
||||
break
|
||||
}
|
||||
target = target.parentElement
|
||||
}
|
||||
}, true)
|
||||
}, true)
|
||||
@@ -1,69 +0,0 @@
|
||||
use crate::tauri::process::{ProcessExt, Signal, SystemExt};
|
||||
|
||||
fn update() -> Result<(), String> {
|
||||
let target = tauri::platform::target_triple().map_err(|_| "Could not determine target")?;
|
||||
let github_release = tauri::updater::github::get_latest_release("jaemk", "self_update")
|
||||
.map_err(|_| "Could not fetch latest release")?;
|
||||
match github_release.asset_for(&target) {
|
||||
Some(github_release_asset) => {
|
||||
let release = tauri::updater::Release {
|
||||
version: github_release.tag.trim_start_matches('v').to_string(),
|
||||
download_url: github_release_asset.download_url,
|
||||
asset_name: github_release_asset.name,
|
||||
};
|
||||
|
||||
let status = tauri::updater::Update::configure()
|
||||
.unwrap()
|
||||
.release(release)
|
||||
.bin_path_in_archive("github")
|
||||
.bin_name("app")
|
||||
.bin_install_path(&tauri::command::command_path("app".to_string()).unwrap())
|
||||
.show_download_progress(true)
|
||||
.current_version(env!("CARGO_PKG_VERSION"))
|
||||
.build()
|
||||
.unwrap()
|
||||
.update()
|
||||
.unwrap();
|
||||
|
||||
println!("found release: {}", status.version());
|
||||
|
||||
/*let tmp_dir = tauri::dir::with_temp_dir(|dir| {
|
||||
let file_path = dir.path().join("my-temporary-note.pdf");
|
||||
let mut tmp_archive = std::fs::File::create(file_path).unwrap();
|
||||
tauri::http::download(&"https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf".to_string(), &mut tmp_archive, true).unwrap();
|
||||
});*/
|
||||
|
||||
Ok(())
|
||||
}
|
||||
None => Err(format!("Could not find release for target {}", target)),
|
||||
}
|
||||
}
|
||||
|
||||
fn restart_app(app_command: String) -> Result<(), String> {
|
||||
let mut system = tauri::process::System::new();
|
||||
let parent_process = tauri::process::get_parent_process(&mut system)
|
||||
.map_err(|_| "Could not determine parent process")?;
|
||||
if parent_process.name() == "app" {
|
||||
parent_process.kill(Signal::Kill);
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
std::process::Command::new(app_command)
|
||||
.spawn()
|
||||
.map_err(|_| "Could not start app")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_updater() -> Result<(), String> {
|
||||
let app_command = tauri::command::relative_command("app".to_string())
|
||||
.map_err(|_| "Could not determine app path")?;
|
||||
update()?;
|
||||
restart_app(app_command)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
match run_updater() {
|
||||
Ok(_) => {}
|
||||
Err(err) => panic!(err),
|
||||
};
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
// eslint-disable-next-line node/no-missing-require
|
||||
const { tauri } = require('bin/tauri')
|
||||
|
||||
describe('[CLI] tauri.js', () => {
|
||||
it('displays a help message', async () => {
|
||||
jest.spyOn(console, 'log')
|
||||
jest.spyOn(process, 'exit').mockImplementation(() => true)
|
||||
tauri('help')
|
||||
console.log(process.exit.mock.calls[0][0])
|
||||
expect(process.exit.mock.calls[0][0]).toBe(0)
|
||||
expect(!!console.log.mock.calls[0][0]).toBe(true)
|
||||
tauri('--help')
|
||||
expect(!!console.log.mock.calls[2][0]).toBe(true)
|
||||
tauri('-h')
|
||||
expect(!!console.log.mock.calls[3][0]).toBe(true)
|
||||
tauri(['help'])
|
||||
expect(!!console.log.mock.calls[4][0]).toBe(true)
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it('will not run an unavailable command', async () => {
|
||||
jest.spyOn(console, 'log')
|
||||
tauri('foo')
|
||||
expect(console.log.mock.calls[0][0].split('.')[0]).toBe('Invalid command foo')
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it('will pass on an available command', async () => {
|
||||
jest.spyOn(console, 'log')
|
||||
tauri('init')
|
||||
expect(console.log.mock.calls[0][0].split('.')[0]).toBe('[tauri]: running init')
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
it('gets you help', async () => {
|
||||
jest.spyOn(console, 'log')
|
||||
const tests = ['--help', '-h', 'invalid command']
|
||||
for (const test of tests) {
|
||||
tauri([test])
|
||||
expect(!!console.log.mock.calls[0][0]).toBe(true)
|
||||
jest.clearAllMocks()
|
||||
}
|
||||
})
|
||||
it('gets you version', async () => {
|
||||
jest.spyOn(console, 'log')
|
||||
const tests = ['--version', '-v']
|
||||
const version = require('../../../package.json').version
|
||||
for (const test of tests) {
|
||||
tauri([test])
|
||||
expect(console.log.mock.calls[0][0]).toBe(version)
|
||||
jest.clearAllMocks()
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -1,63 +0,0 @@
|
||||
const tauricon = require('~/dist/api/tauricon.js')
|
||||
|
||||
describe('[CLI] tauri-icon internals', () => {
|
||||
it('tells you the version', () => {
|
||||
const version = tauricon.version()
|
||||
expect(!!version).toBe(true)
|
||||
})
|
||||
|
||||
it('will not validate a non-file', async () => {
|
||||
jest.spyOn(process, 'exit').mockImplementation(() => true)
|
||||
await tauricon.validate('test/jest/fixtures/doesnotexist.png', 'test/jest/fixtures/')
|
||||
expect(process.exit.mock.calls[0][0]).toBe(1)
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
it('will not validate a non-png', async () => {
|
||||
jest.spyOn(process, 'exit').mockImplementation(() => true)
|
||||
await tauricon.validate('test/jest/fixtures/notAMeme.jpg', 'test/jest/fixtures/')
|
||||
expect(process.exit.mock.calls[0][0]).toBe(1)
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
it('can validate an image as PNG', async () => {
|
||||
const valid = await tauricon.validate('test/jest/fixtures/tauri-logo.png', 'test/jest/fixtures/')
|
||||
expect(valid).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('[CLI] tauri-icon builder', () => {
|
||||
it('will still use default compression if missing compression chosen', async () => {
|
||||
const valid = await tauricon.make('test/jest/fixtures/tauri-logo.png', 'test/jest/tmp/missing', 'missing')
|
||||
expect(valid).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('[CLI] tauri-icon builder', () => {
|
||||
it('will not validate a non-file', async () => {
|
||||
try {
|
||||
await tauricon.make('test/jest/fixtures/tauri-foo-not-found.png', 'test/jest/tmp/pngquant', 'pngquant')
|
||||
} catch (e) {
|
||||
expect(e.message).toBe('Input file is missing')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('[CLI] tauri-icon builder', () => {
|
||||
it('makes a set of icons with pngquant', async () => {
|
||||
const valid = await tauricon.make('test/jest/fixtures/tauri-logo.png', 'test/jest/tmp/pngquant', 'pngquant')
|
||||
expect(valid).toBe(true)
|
||||
})
|
||||
|
||||
it('makes a set of icons with optipng', async () => {
|
||||
const valid = await tauricon.make('test/jest/fixtures/tauri-logo.png', 'test/jest/tmp/optipng', 'optipng')
|
||||
expect(valid).toBe(true)
|
||||
})
|
||||
|
||||
/*
|
||||
TURNED OFF BECAUSE IT TAKES FOREVER
|
||||
it('makes a set of icons with zopfli', async () => {
|
||||
jest.setTimeout(120000)
|
||||
const valid = await tauricon.make('test/jest/fixtures/tauri-logo.png', 'test/jest/tmp/zopfli', 'zopfli')
|
||||
expect(valid).toBe(true)
|
||||
})
|
||||
*/
|
||||
})
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 52 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 123 KiB |
@@ -1,9 +0,0 @@
|
||||
jest.setTimeout(50000)
|
||||
|
||||
global.Promise = require('promise')
|
||||
|
||||
setTimeout(() => {
|
||||
// do nothing
|
||||
}, 1)
|
||||
|
||||
require('dotenv').config({ path: '.env.jest' })
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist/",
|
||||
"strict": true,
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"allowJs": true,
|
||||
"esModuleInterop": true,
|
||||
"moduleResolution": "node",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"types": ["src/types"]
|
||||
}
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
const path = require('path')
|
||||
const nodeExternals = require('webpack-node-externals')
|
||||
|
||||
module.exports = {
|
||||
entry: {
|
||||
'api/build': './src/api/build.ts',
|
||||
'api/dev': './src/api/dev.ts',
|
||||
'api/init': './src/api/init.ts',
|
||||
'api/tauricon': './src/api/tauricon.ts',
|
||||
'helpers/tauri-config': './src/helpers/tauri-config.ts',
|
||||
'api/info': './src/api/info.ts'
|
||||
},
|
||||
mode: process.env.NODE_ENV || 'development',
|
||||
devtool: 'source-map',
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: 'ts-loader',
|
||||
exclude: /node_modules/
|
||||
}
|
||||
]
|
||||
},
|
||||
node: false,
|
||||
resolve: {
|
||||
extensions: ['.ts', '.js']
|
||||
},
|
||||
output: {
|
||||
library: 'tauri',
|
||||
libraryTarget: 'umd',
|
||||
filename: '[name].js',
|
||||
path: path.resolve(__dirname, 'dist')
|
||||
},
|
||||
externals: [nodeExternals()],
|
||||
target: 'node'
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,2 +0,0 @@
|
||||
# compiling on windows donwloads WixTools to src-tauri
|
||||
**/src-tauri/WixTools/
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user