refactor(cli.rs): signer and plugin subcommands, use new clap derive syntax (#2928)

Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
This commit is contained in:
Jonas Kruckenberg 2021-12-09 16:21:33 +01:00 committed by GitHub
parent 28aaec87e2
commit 1458ab3c53
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1438 additions and 1926 deletions

View File

@ -0,0 +1,5 @@
---
"cli.rs": patch
---
The `generate` and `sign` commands are now available under a `signer` subcommand.

View File

@ -293,7 +293,7 @@
//! To generate your keys you need to use the Tauri cli.
//!
//! ```bash
//! tauri sign -g -w ~/.tauri/myapp.key
//! tauri signer sign -g -w ~/.tauri/myapp.key
//! ```
//!
//! You have multiple options available
@ -301,7 +301,7 @@
//! Tauri updates signer.
//!
//! USAGE:
//! tauri sign [FLAGS] [OPTIONS]
//! tauri signer sign [FLAGS] [OPTIONS]
//!
//! FLAGS:
//! --force Overwrite private key even if it exists on the specified path

View File

@ -280,14 +280,14 @@ The *Private key* (privkey) is used to sign your update and should NEVER be shar
To generate your keys you need to use the Tauri cli.
```bash
tauri sign -g -w ~/.tauri/myapp.key
tauri signer sign -g -w ~/.tauri/myapp.key
```
You have multiple options available
```bash
Tauri updates signer.
USAGE:
tauri sign [FLAGS] [OPTIONS]
tauri signer sign [FLAGS] [OPTIONS]
FLAGS:
--force Overwrite private key even if it exists on the specified path
-g, --generate Generate keypair to sign files

View File

@ -431,7 +431,7 @@ pub fn build_wix_app_installer(
\pard\sa200\sl276\slmult1\f0\fs22\lang9 {}\par
}}
"#,
license_contents.replace("\n", "\\par ")
license_contents.replace('\n', "\\par ")
);
let rtf_output_path = settings
.project_out_directory()

268
tooling/cli.rs/Cargo.lock generated Executable file → Normal file
View File

@ -25,9 +25,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.43"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28ae2b3dec75a406790005a200b1bd89785afc02517a00ca99ecfe093ee9e6cf"
checksum = "38d9ff5d688f1c13395289f67db01d4826b46dd694e7580accdc3e8430f2d98e"
[[package]]
name = "ar"
@ -75,9 +75,9 @@ checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
[[package]]
name = "bitflags"
version = "1.3.1"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2da1976d75adbe5fbc88130ecd119529cf1cc6a93ae1546d8696ee66f0d21af1"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitness"
@ -132,9 +132,9 @@ dependencies = [
[[package]]
name = "bumpalo"
version = "3.7.0"
version = "3.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631"
checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c"
[[package]]
name = "byte-tools"
@ -156,9 +156,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "bytes"
version = "1.0.1"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040"
checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
[[package]]
name = "bzip2"
@ -183,9 +183,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.0.69"
version = "1.0.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2"
checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee"
[[package]]
name = "cfg-if"
@ -229,9 +229,9 @@ dependencies = [
[[package]]
name = "clap"
version = "3.0.0-beta.5"
version = "3.0.0-rc.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "feff3878564edb93745d58cf63e17b63f24142506e7a20c87a5521ed7bfb1d63"
checksum = "79b70f999da60e6619a29b131739d2211ed4d4301f40372e94a8081422e9d6c7"
dependencies = [
"atty",
"bitflags",
@ -242,15 +242,13 @@ dependencies = [
"strsim",
"termcolor",
"textwrap",
"unicase",
"yaml-rust",
]
[[package]]
name = "clap_derive"
version = "3.0.0-beta.5"
version = "3.0.0-rc.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b15c6b4f786ffb6192ffe65a36855bc1fc2444bcd0945ae16748dcd6ed7d0d3"
checksum = "fe8c0f28022faaef0387fa54f8e33fee22b804a88bbd91303197da2ff8ca6a5d"
dependencies = [
"heck",
"proc-macro-error",
@ -278,9 +276,9 @@ dependencies = [
[[package]]
name = "combine"
version = "4.6.0"
version = "4.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2d47c1b11006b87e492b53b313bb699ce60e16613c4dddaa91f8f7c220ab2fa"
checksum = "b2b2f5d0ee456f3928812dfc8c6d9a1d592b98678f6d56db9b0cd2b7bc6c8db5"
dependencies = [
"bytes",
"memchr",
@ -303,9 +301,9 @@ dependencies = [
[[package]]
name = "core-foundation"
version = "0.9.1"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62"
checksum = "6888e10551bb93e424d8df1d07f1a8b4fceb0001a3a4b048bfc47554946f47b3"
dependencies = [
"core-foundation-sys",
"libc",
@ -313,18 +311,9 @@ dependencies = [
[[package]]
name = "core-foundation-sys"
version = "0.8.2"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b"
[[package]]
name = "cpufeatures"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66c99696f6c9dd7f35d486b9d04d7e6e202aa3e8c40d553f2fdf5e7e0c6a71ef"
dependencies = [
"libc",
]
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
[[package]]
name = "cpufeatures"
@ -552,9 +541,9 @@ dependencies = [
[[package]]
name = "flate2"
version = "1.0.20"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0"
checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f"
dependencies = [
"cfg-if 1.0.0",
"crc32fast",
@ -671,9 +660,9 @@ dependencies = [
[[package]]
name = "gif"
version = "0.11.2"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a668f699973d0f573d15749b7002a9ac9e1f9c6b220e7b165601334c173d8de"
checksum = "c3a7187e78088aead22ceedeee99779455b23fc231fe13ec443f99bb71694e5b"
dependencies = [
"color_quant",
"weezl",
@ -687,9 +676,9 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]]
name = "handlebars"
version = "4.1.2"
version = "4.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd85ecabdb47308d28d3a4113224fefcab2510ccb4e463aee0a1362eb84c756a"
checksum = "8ad84da8f63da982543fc85fcabaee2ad1fdd809d99d64a48887e2e942ddfe46"
dependencies = [
"log",
"pest",
@ -741,9 +730,9 @@ dependencies = [
[[package]]
name = "http"
version = "0.2.4"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "527e8c9ac747e28542699a951517aa9a6945af506cd1f2e1b53a576c17b6cc11"
checksum = "1323096b05d41827dadeaee54c9981958c0f94e670bc94ed80037d1a7b8b186b"
dependencies = [
"bytes",
"fnv",
@ -798,18 +787,18 @@ dependencies = [
[[package]]
name = "include_dir"
version = "0.7.0"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d7d2a17a8653f96bc15a7840946857891072b950c1c318473fa46bb4c1ac6cc"
checksum = "7fe7734d776eb702d33f1b68730696db57c87facfd526d2044a308a0c8466318"
dependencies = [
"include_dir_macros",
]
[[package]]
name = "include_dir_macros"
version = "0.7.0"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d551dc625a699489a6903cd41dd91aef674a5126f3d28799a316d14e7b15fcf5"
checksum = "253ba5156abc78673208f900dd686e1e000e6edc5633231d309acded2b66026d"
dependencies = [
"proc-macro2",
"quote",
@ -865,9 +854,9 @@ dependencies = [
[[package]]
name = "itoa"
version = "0.4.7"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
[[package]]
name = "jpeg-decoder"
@ -880,9 +869,9 @@ dependencies = [
[[package]]
name = "js-sys"
version = "0.3.52"
version = "0.3.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce791b7ca6638aae45be056e068fc756d871eb3b3b10b8efa62d1c9cec616752"
checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84"
dependencies = [
"wasm-bindgen",
]
@ -929,9 +918,9 @@ dependencies = [
[[package]]
name = "kstring"
version = "1.0.5"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e8d7e992938cc9078c8db5fd5bdc400e7f9da6efa384c280902a8922b676221"
checksum = "8b310ccceade8121d7d77fee406160e457c2f4e7c7982d589da3499bc7ea4526"
dependencies = [
"serde",
]
@ -956,9 +945,9 @@ checksum = "f98a04dce437184842841303488f70d0188c5f51437d2a834dc097eafa909a01"
[[package]]
name = "libflate"
version = "1.1.0"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d87eae36b3f680f7f01645121b782798b56ef33c53f83d1c66ba3a22b60bfe3"
checksum = "16364af76ebb39b5869bb32c81fa93573267cd8c62bb3474e28d78fac3fb141e"
dependencies = [
"adler32",
"crc32fast",
@ -974,12 +963,6 @@ dependencies = [
"rle-decode-fast",
]
[[package]]
name = "linked-hash-map"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
[[package]]
name = "log"
version = "0.4.14"
@ -1009,9 +992,9 @@ checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
[[package]]
name = "memchr"
version = "2.4.0"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[package]]
name = "memoffset"
@ -1213,9 +1196,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "openssl"
version = "0.10.35"
version = "0.10.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "549430950c79ae24e6d02e0b7404534ecf311d94cc9f861e9e4020187d13d885"
checksum = "0c7ae222234c30df141154f159066c5093ff73b63204dcda7121eb082fc56a95"
dependencies = [
"bitflags",
"cfg-if 1.0.0",
@ -1233,9 +1216,9 @@ checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a"
[[package]]
name = "openssl-sys"
version = "0.9.65"
version = "0.9.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a7907e3bfa08bb85105209cdfcb6c63d109f8f6c1ed6ca318fff5c1853fbc1d"
checksum = "7df13d165e607909b363a4757a6f133f8a818a74e9d3a98d09c6128e15fa4c73"
dependencies = [
"autocfg",
"cc",
@ -1257,9 +1240,9 @@ dependencies = [
[[package]]
name = "os_str_bytes"
version = "4.2.0"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "addaa943333a514159c80c97ff4a93306530d965d27e139188283cd13e06a799"
checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
dependencies = [
"memchr",
]
@ -1362,9 +1345,9 @@ dependencies = [
[[package]]
name = "pkg-config"
version = "0.3.19"
version = "0.3.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
checksum = "12295df4f294471248581bc09bef3c38a5e46f1e36d6a37353621a0c6c357e1f"
[[package]]
name = "png"
@ -1380,9 +1363,9 @@ dependencies = [
[[package]]
name = "ppv-lite86"
version = "0.2.10"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba"
[[package]]
name = "proc-macro-error"
@ -1410,9 +1393,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.28"
version = "1.0.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612"
checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43"
dependencies = [
"unicode-xid",
]
@ -1435,9 +1418,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]]
name = "quote"
version = "1.0.9"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05"
dependencies = [
"proc-macro2",
]
@ -1636,9 +1619,9 @@ dependencies = [
[[package]]
name = "rustls"
version = "0.20.0"
version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b5ac6078ca424dc1d3ae2328526a76787fecc7f8011f520e3276730e711fc95"
checksum = "dac4581f0fc0e0efd529d069e8189ec7b90b8e7680e21beb35141bdc45f36040"
dependencies = [
"log",
"ring",
@ -1688,9 +1671,9 @@ dependencies = [
[[package]]
name = "schemars"
version = "0.8.3"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc6ab463ae35acccb5cba66c0084c985257b797d288b6050cc2f6ac1b266cb78"
checksum = "271ac0c667b8229adf70f0f957697c96fafd7486ab7481e15dc5e45e3e6a4368"
dependencies = [
"dyn-clone",
"schemars_derive",
@ -1700,9 +1683,9 @@ dependencies = [
[[package]]
name = "schemars_derive"
version = "0.8.3"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "902fdfbcf871ae8f653bddf4b2c05905ddaabc08f69d32a915787e3be0d31356"
checksum = "6ebda811090b257411540779860bc09bf321bc587f58d2c5864309d1566214e7"
dependencies = [
"proc-macro2",
"quote",
@ -1731,7 +1714,7 @@ dependencies = [
"hmac",
"pbkdf2",
"salsa20",
"sha2 0.9.5",
"sha2 0.9.8",
]
[[package]]
@ -1746,9 +1729,9 @@ dependencies = [
[[package]]
name = "security-framework"
version = "2.3.1"
version = "2.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23a2ac85147a3a11d77ecf1bc7166ec0b92febfa4461c37944e180f319ece467"
checksum = "525bc1abfda2e1998d152c45cf13e696f76d0a4972310b22fac1658b05df7c87"
dependencies = [
"bitflags",
"core-foundation",
@ -1759,9 +1742,9 @@ dependencies = [
[[package]]
name = "security-framework-sys"
version = "2.3.0"
version = "2.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e4effb91b4b8b6fb7732e670b6cee160278ff8e6bf485c7805d9e319d76e284"
checksum = "a9dd14d83160b528b7bfd66439110573efcfbe281b17fc2ca9f39f550d619c7e"
dependencies = [
"core-foundation-sys",
"libc",
@ -1775,18 +1758,18 @@ checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012"
[[package]]
name = "serde"
version = "1.0.127"
version = "1.0.130"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f03b9878abf6d14e6779d3f24f07b2cfa90352cfec4acc5aab8f1ac7f146fae8"
checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.127"
version = "1.0.130"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a024926d3432516606328597e0f224a51355a493b49fdd67e9209187cbe55ecc"
checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b"
dependencies = [
"proc-macro2",
"quote",
@ -1806,9 +1789,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.66"
version = "1.0.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "336b10da19a12ad094b59d870ebde26a45402e5b470add4b5fd03c5048a32127"
checksum = "063bf466a64011ac24040a49009724ee60a57da1b437617ceb32e53ad61bfb19"
dependencies = [
"itoa",
"ryu",
@ -1828,9 +1811,9 @@ dependencies = [
[[package]]
name = "serde_with_macros"
version = "1.5.0"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98c1fcca18d55d1763e1c16873c4bde0ac3ef75179a28c7b372917e0494625be"
checksum = "12e47be9471c72889ebafb5e14d5ff930d89ae7a67bbdb5f8abb564f845a927e"
dependencies = [
"darling",
"proc-macro2",
@ -1858,13 +1841,13 @@ checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d"
[[package]]
name = "sha2"
version = "0.9.5"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b362ae5752fd2137731f9fa25fd4d9058af34666ca1966fb969119cc35719f12"
checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa"
dependencies = [
"block-buffer 0.9.0",
"cfg-if 1.0.0",
"cpufeatures 0.1.5",
"cpufeatures",
"digest 0.9.0",
"opaque-debug 0.3.0",
]
@ -1876,7 +1859,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "900d964dd36bb15bcf2f2b35694c072feab74969a54f2bbeec7a2d725d2bdcb6"
dependencies = [
"cfg-if 1.0.0",
"cpufeatures 0.2.1",
"cpufeatures",
"digest 0.10.0",
]
@ -1892,15 +1875,15 @@ dependencies = [
[[package]]
name = "siphasher"
version = "0.3.6"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "729a25c17d72b06c68cb47955d44fda88ad2d3e7d77e025663fdd69b93dd71a1"
checksum = "533494a8f9b724d33625ab53c6c4800f7cc445895924a8ef649222dcb76e938b"
[[package]]
name = "slab"
version = "0.4.4"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c307a32c1c5c437f38c7fd45d753050587732ba8628319fbdf12a7e289ccc590"
checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5"
[[package]]
name = "spin"
@ -1922,9 +1905,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
[[package]]
name = "syn"
version = "1.0.74"
version = "1.0.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c"
checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966"
dependencies = [
"proc-macro2",
"quote",
@ -1933,9 +1916,9 @@ dependencies = [
[[package]]
name = "sysctl"
version = "0.4.2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "963488c73b34185a9028742c2be0219ed1d8558e59f85c3b466a4f54affba936"
checksum = "feb3f7a32e17639e3705d2e05da40f485877cb97fdf0f3240e519e525e6cdb4d"
dependencies = [
"bitflags",
"byteorder",
@ -2066,24 +2049,21 @@ name = "textwrap"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80"
dependencies = [
"unicode-width",
]
[[package]]
name = "thiserror"
version = "1.0.26"
version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2"
checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.26"
version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745"
checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
dependencies = [
"proc-macro2",
"quote",
@ -2113,9 +2093,9 @@ dependencies = [
[[package]]
name = "tinyvec"
version = "1.3.1"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "848a1e1181b9f6753b5e96a092749e29b11d19ede67dfbbd6c7dc7e0f49b5338"
checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2"
dependencies = [
"tinyvec_macros",
]
@ -2158,9 +2138,9 @@ dependencies = [
[[package]]
name = "typenum"
version = "1.13.0"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"
checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec"
[[package]]
name = "ucd-trie"
@ -2177,20 +2157,11 @@ dependencies = [
"libc",
]
[[package]]
name = "unicase"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
dependencies = [
"version_check",
]
[[package]]
name = "unicode-bidi"
version = "0.3.6"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "246f4c42e67e7a4e3c6106ff716a5d067d4132a642840b242e357e468a2a0085"
checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f"
[[package]]
name = "unicode-normalization"
@ -2209,9 +2180,9 @@ checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
[[package]]
name = "unicode-width"
version = "0.1.8"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
[[package]]
name = "unicode-xid"
@ -2227,9 +2198,9 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]]
name = "ureq"
version = "2.3.0"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dd912a3d096959150c4d71ac752e13f1683085922658c205b89b40fe8ebe07f"
checksum = "c5c448dcb78ec38c7d59ec61f87f70a98ea19171e06c139357e012ee226fec90"
dependencies = [
"base64",
"chunked_transfer",
@ -2331,9 +2302,9 @@ checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]]
name = "wasm-bindgen"
version = "0.2.75"
version = "0.2.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b608ecc8f4198fe8680e2ed18eccab5f0cd4caaf3d83516fa5fb2e927fda2586"
checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce"
dependencies = [
"cfg-if 1.0.0",
"wasm-bindgen-macro",
@ -2341,9 +2312,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.75"
version = "0.2.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "580aa3a91a63d23aac5b6b267e2d13cb4f363e31dce6c352fca4752ae12e479f"
checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b"
dependencies = [
"bumpalo",
"lazy_static",
@ -2356,9 +2327,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.75"
version = "0.2.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "171ebf0ed9e1458810dfcb31f2e766ad6b3a89dbda42d8901f2b268277e5f09c"
checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@ -2366,9 +2337,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.75"
version = "0.2.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c2657dd393f03aa2a659c25c6ae18a13a4048cebd220e147933ea837efc589f"
checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab"
dependencies = [
"proc-macro2",
"quote",
@ -2379,15 +2350,15 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.75"
version = "0.2.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e0c4a743a309662d45f4ede961d7afa4ba4131a59a639f29b0069c3798bbcc2"
checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc"
[[package]]
name = "web-sys"
version = "0.3.52"
version = "0.3.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01c70a82d842c9979078c772d4a1344685045f1a5628f677c2b2eab4dd7d2696"
checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb"
dependencies = [
"js-sys",
"wasm-bindgen",
@ -2495,20 +2466,11 @@ dependencies = [
"libc",
]
[[package]]
name = "yaml-rust"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
dependencies = [
"linked-hash-map",
]
[[package]]
name = "zeroize"
version = "1.4.1"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "377db0846015f7ae377174787dd452e1c5f5a9050bc6f954911d01f116daa0cd"
checksum = "d68d9dcec5f9b43a30d38c49f91dfedfaac384cb8f085faca366c26207dd1619"
[[package]]
name = "zip"

View File

@ -18,7 +18,7 @@ name = "cargo-tauri"
path = "src/main.rs"
[dependencies]
clap = { version = "3.0.0-beta.5", features = [ "yaml" ] }
clap = { version = "3.0.0-rc.0", features = [ "derive" ] }
anyhow = "1.0"
tauri-bundler = { version = "1.0.0-beta.4", path = "../bundler" }
colored = "2.0"

View File

@ -2,11 +2,6 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use anyhow::Context;
#[cfg(target_os = "linux")]
use heck::KebabCase;
use tauri_bundler::bundle::{bundle_project, PackageType};
use crate::helpers::{
app_paths::{app_dir, tauri_dir},
command_env,
@ -16,220 +11,227 @@ use crate::helpers::{
updater_signature::sign_file_from_env_variables,
Logger,
};
use crate::Result;
use anyhow::Context;
use clap::Parser;
#[cfg(target_os = "linux")]
use heck::KebabCase;
use std::{env::set_current_dir, fs::rename, path::PathBuf, process::Command};
use tauri_bundler::bundle::{bundle_project, PackageType};
#[derive(Default)]
pub struct Build {
#[derive(Debug, Parser)]
#[clap(about = "Tauri build")]
pub struct Options {
/// Binary to use to build the application
#[clap(short, long)]
runner: Option<String>,
/// Builds with the debug flag
#[clap(short, long)]
debug: bool,
/// Enables verbose logging
#[clap(short, long)]
verbose: bool,
/// Target triple to build against
#[clap(short, long)]
target: Option<String>,
/// List of cargo features to activate
#[clap(short, long)]
features: Option<Vec<String>>,
/// List of bundles to package
#[clap(short, long)]
bundles: Option<Vec<String>>,
/// JSON string or path to JSON file to merge with tauri.conf.json
#[clap(short, long)]
config: Option<String>,
}
impl Build {
pub fn new() -> Self {
Default::default()
}
pub fn command(options: Options) -> Result<()> {
let logger = Logger::new("tauri:build");
let merge_config = if let Some(config) = &options.config {
Some(if config.starts_with('{') {
config.to_string()
} else {
std::fs::read_to_string(&config)?
})
} else {
None
};
let config = get_config(merge_config.as_deref())?;
pub fn debug(mut self) -> Self {
self.debug = true;
self
}
let tauri_path = tauri_dir();
set_current_dir(&tauri_path).with_context(|| "failed to change current working directory")?;
pub fn verbose(mut self) -> Self {
self.verbose = true;
self
}
let manifest = rewrite_manifest(config.clone())?;
pub fn runner(mut self, runner: String) -> Self {
self.runner.replace(runner);
self
}
let config_guard = config.lock().unwrap();
let config_ = config_guard.as_ref().unwrap();
pub fn target(mut self, target: String) -> Self {
self.target.replace(target);
self
}
pub fn features(mut self, features: Vec<String>) -> Self {
self.features.replace(features);
self
}
pub fn bundles(mut self, bundles: Vec<String>) -> Self {
self.bundles.replace(bundles);
self
}
pub fn config(mut self, config: String) -> Self {
self.config.replace(config);
self
}
pub fn run(self) -> crate::Result<()> {
let logger = Logger::new("tauri:build");
let config = get_config(self.config.as_deref())?;
let tauri_path = tauri_dir();
set_current_dir(&tauri_path).with_context(|| "failed to change current working directory")?;
let manifest = rewrite_manifest(config.clone())?;
let config_guard = config.lock().unwrap();
let config_ = config_guard.as_ref().unwrap();
if let Some(before_build) = &config_.build.before_build_command {
if !before_build.is_empty() {
logger.log(format!("Running `{}`", before_build));
#[cfg(target_os = "windows")]
execute_with_output(
Command::new("cmd")
.arg("/C")
.arg(before_build)
.current_dir(app_dir())
.envs(command_env(self.debug)),
)
.with_context(|| format!("failed to run `{}` with `cmd /C`", before_build))?;
#[cfg(not(target_os = "windows"))]
execute_with_output(
Command::new("sh")
.arg("-c")
.arg(before_build)
.current_dir(app_dir())
.envs(command_env(self.debug)),
)
.with_context(|| format!("failed to run `{}` with `sh -c`", before_build))?;
}
if let Some(before_build) = &config_.build.before_build_command {
if !before_build.is_empty() {
logger.log(format!("Running `{}`", before_build));
#[cfg(target_os = "windows")]
execute_with_output(
Command::new("cmd")
.arg("/C")
.arg(before_build)
.current_dir(app_dir())
.envs(command_env(options.debug)),
)
.with_context(|| format!("failed to run `{}` with `cmd /C`", before_build))?;
#[cfg(not(target_os = "windows"))]
execute_with_output(
Command::new("sh")
.arg("-c")
.arg(before_build)
.current_dir(app_dir())
.envs(command_env(options.debug)),
)
.with_context(|| format!("failed to run `{}` with `sh -c`", before_build))?;
}
}
if let AppUrl::Url(url) = &config_.build.dist_dir {
let web_asset_path = PathBuf::from(url);
if !web_asset_path.exists() {
return Err(anyhow::anyhow!(
if let AppUrl::Url(url) = &config_.build.dist_dir {
let web_asset_path = PathBuf::from(url);
if !web_asset_path.exists() {
return Err(anyhow::anyhow!(
"Unable to find your web assets, did you forget to build your web app? Your distDir is set to \"{:?}\".",
web_asset_path
));
}
if web_asset_path.canonicalize()?.file_name() == Some(std::ffi::OsStr::new("src-tauri")) {
return Err(anyhow::anyhow!(
}
if web_asset_path.canonicalize()?.file_name() == Some(std::ffi::OsStr::new("src-tauri")) {
return Err(anyhow::anyhow!(
"The configured distDir is the `src-tauri` folder.
Please isolate your web assets on a separate folder and update `tauri.conf.json > build > distDir`.",
));
}
}
let mut out_folders = Vec::new();
for folder in &["node_modules", "src-tauri", "target"] {
if web_asset_path.join(folder).is_dir() {
out_folders.push(folder.to_string());
}
let mut out_folders = Vec::new();
for folder in &["node_modules", "src-tauri", "target"] {
if web_asset_path.join(folder).is_dir() {
out_folders.push(folder.to_string());
}
if !out_folders.is_empty() {
return Err(anyhow::anyhow!(
}
if !out_folders.is_empty() {
return Err(anyhow::anyhow!(
"The configured distDir includes the `{:?}` {}. Please isolate your web assets on a separate folder and update `tauri.conf.json > build > distDir`.",
out_folders,
if out_folders.len() == 1 { "folder" }else { "folders" }
)
);
}
}
}
let runner_from_config = config_.build.runner.clone();
let runner = self
.runner
.or(runner_from_config)
.unwrap_or_else(|| "cargo".to_string());
let runner_from_config = config_.build.runner.clone();
let runner = options
.runner
.or(runner_from_config)
.unwrap_or_else(|| "cargo".to_string());
let mut cargo_features = config_.build.features.clone().unwrap_or_default();
if let Some(features) = self.features {
cargo_features.extend(features);
}
let mut cargo_features = config_.build.features.clone().unwrap_or_default();
if let Some(features) = options.features {
cargo_features.extend(features);
}
crate::interface::rust::build_project(runner, &self.target, cargo_features, self.debug)
.with_context(|| "failed to build app")?;
crate::interface::rust::build_project(runner, &options.target, cargo_features, options.debug)
.with_context(|| "failed to build app")?;
let app_settings = crate::interface::rust::AppSettings::new(config_)?;
let app_settings = crate::interface::rust::AppSettings::new(config_)?;
let out_dir = app_settings
.get_out_dir(self.target.clone(), self.debug)
.with_context(|| "failed to get project out directory")?;
if let Some(product_name) = config_.package.product_name.clone() {
let bin_name = app_settings
.cargo_package_settings()
.name
.clone()
.expect("Cargo manifest must have the `package.name` field");
#[cfg(windows)]
let (bin_path, product_path) = {
(
out_dir.join(format!("{}.exe", bin_name)),
out_dir.join(format!("{}.exe", product_name)),
)
};
#[cfg(target_os = "macos")]
let (bin_path, product_path) = { (out_dir.join(bin_name), out_dir.join(product_name)) };
#[cfg(target_os = "linux")]
let (bin_path, product_path) = {
(
out_dir.join(bin_name),
out_dir.join(product_name.to_kebab_case()),
)
};
rename(&bin_path, &product_path).with_context(|| {
format!(
"failed to rename `{}` to `{}`",
bin_path.display(),
product_path.display(),
)
})?;
}
let out_dir = app_settings
.get_out_dir(options.target.clone(), options.debug)
.with_context(|| "failed to get project out directory")?;
if let Some(product_name) = config_.package.product_name.clone() {
let bin_name = app_settings
.cargo_package_settings()
.name
.clone()
.expect("Cargo manifest must have the `package.name` field");
#[cfg(windows)]
let (bin_path, product_path) = {
(
out_dir.join(format!("{}.exe", bin_name)),
out_dir.join(format!("{}.exe", product_name)),
)
};
#[cfg(target_os = "macos")]
let (bin_path, product_path) = { (out_dir.join(bin_name), out_dir.join(product_name)) };
#[cfg(target_os = "linux")]
let (bin_path, product_path) = {
(
out_dir.join(bin_name),
out_dir.join(product_name.to_kebab_case()),
)
};
rename(&bin_path, &product_path).with_context(|| {
format!(
"failed to rename `{}` to `{}`",
bin_path.display(),
product_path.display(),
)
})?;
}
if config_.tauri.bundle.active {
// move merge modules to the out dir so the bundler can load it
#[cfg(windows)]
{
let arch = if let Some(t) = &self.target {
if t.starts_with("x86_64") {
"x86_64"
} else if t.starts_with('i') {
"x86"
} else if t.starts_with("arm") {
"arm"
} else if t.starts_with("aarch64") {
"aarch64"
} else {
panic!("Unexpected target triple {}", t)
}
} else if cfg!(target_arch = "x86") {
"x86"
} else {
if config_.tauri.bundle.active {
// move merge modules to the out dir so the bundler can load it
#[cfg(windows)]
{
let arch = if let Some(t) = &options.target {
if t.starts_with("x86_64") {
"x86_64"
};
let (filename, vcruntime_msm) = if arch == "x86" {
let _ = std::fs::remove_file(out_dir.join("Microsoft_VC142_CRT_x64.msm"));
(
"Microsoft_VC142_CRT_x86.msm",
include_bytes!("../MergeModules/Microsoft_VC142_CRT_x86.msm").to_vec(),
)
} else if t.starts_with('i') {
"x86"
} else if t.starts_with("arm") {
"arm"
} else if t.starts_with("aarch64") {
"aarch64"
} else {
let _ = std::fs::remove_file(out_dir.join("Microsoft_VC142_CRT_x86.msm"));
(
"Microsoft_VC142_CRT_x64.msm",
include_bytes!("../MergeModules/Microsoft_VC142_CRT_x64.msm").to_vec(),
)
};
std::fs::write(out_dir.join(filename), vcruntime_msm)?;
}
panic!("Unexpected target triple {}", t)
}
} else if cfg!(target_arch = "x86") {
"x86"
} else {
"x86_64"
};
let (filename, vcruntime_msm) = if arch == "x86" {
let _ = std::fs::remove_file(out_dir.join("Microsoft_VC142_CRT_x64.msm"));
(
"Microsoft_VC142_CRT_x86.msm",
include_bytes!("../MergeModules/Microsoft_VC142_CRT_x86.msm").to_vec(),
)
} else {
let _ = std::fs::remove_file(out_dir.join("Microsoft_VC142_CRT_x86.msm"));
(
"Microsoft_VC142_CRT_x64.msm",
include_bytes!("../MergeModules/Microsoft_VC142_CRT_x64.msm").to_vec(),
)
};
std::fs::write(out_dir.join(filename), vcruntime_msm)?;
}
let package_types = if let Some(names) = self.bundles {
let mut types = vec![];
for name in names {
if name == "none" {
break;
let package_types = if let Some(names) = options.bundles {
let mut types = vec![];
for name in names {
if name == "none" {
break;
}
match PackageType::from_short_name(&name) {
Some(package_type) => {
types.push(package_type);
}
None => {
return Err(anyhow::anyhow!(format!(
"Unsupported bundle format: {}",
name
)));
}
}
}
Some(types)
} else if let Some(targets) = &config_.tauri.bundle.targets {
let mut types = vec![];
let targets = targets.to_vec();
if !targets.contains(&"all".into()) {
for name in targets {
match PackageType::from_short_name(&name) {
Some(package_type) => {
types.push(package_type);
@ -243,71 +245,52 @@ impl Build {
}
}
Some(types)
} else if let Some(targets) = &config_.tauri.bundle.targets {
let mut types = vec![];
let targets = targets.to_vec();
if !targets.contains(&"all".into()) {
for name in targets {
match PackageType::from_short_name(&name) {
Some(package_type) => {
types.push(package_type);
}
None => {
return Err(anyhow::anyhow!(format!(
"Unsupported bundle format: {}",
name
)));
}
}
}
Some(types)
} else {
None
}
} else {
None
};
}
} else {
None
};
let settings = crate::interface::get_bundler_settings(
app_settings,
self.target.clone(),
&manifest,
config_,
&out_dir,
self.verbose,
package_types,
)
.with_context(|| "failed to build bundler settings")?;
let settings = crate::interface::get_bundler_settings(
app_settings,
options.target.clone(),
&manifest,
config_,
&out_dir,
options.verbose,
package_types,
)
.with_context(|| "failed to build bundler settings")?;
settings.copy_resources(&out_dir)?;
settings.copy_binaries(&out_dir)?;
settings.copy_resources(&out_dir)?;
settings.copy_binaries(&out_dir)?;
let bundles = bundle_project(settings).with_context(|| "failed to bundle project")?;
let bundles = bundle_project(settings).with_context(|| "failed to bundle project")?;
// If updater is active and pubkey is available
if config_.tauri.updater.active && config_.tauri.updater.pubkey.is_some() {
// make sure we have our package builts
let mut signed_paths = Vec::new();
for elem in bundles
.iter()
.filter(|bundle| bundle.package_type == PackageType::Updater)
{
// we expect to have only one path in the vec but we iter if we add
// another type of updater package who require multiple file signature
for path in elem.bundle_paths.iter() {
// sign our path from environment variables
let (signature_path, _signature) = sign_file_from_env_variables(path)?;
signed_paths.append(&mut vec![signature_path]);
}
}
if !signed_paths.is_empty() {
print_signed_updater_archive(&signed_paths)?;
// If updater is active and pubkey is available
if config_.tauri.updater.active && config_.tauri.updater.pubkey.is_some() {
// make sure we have our package builts
let mut signed_paths = Vec::new();
for elem in bundles
.iter()
.filter(|bundle| bundle.package_type == PackageType::Updater)
{
// we expect to have only one path in the vec but we iter if we add
// another type of updater package who require multiple file signature
for path in elem.bundle_paths.iter() {
// sign our path from environment variables
let (signature_path, _signature) = sign_file_from_env_variables(path)?;
signed_paths.append(&mut vec![signature_path]);
}
}
if !signed_paths.is_empty() {
print_signed_updater_archive(&signed_paths)?;
}
}
Ok(())
}
Ok(())
}
fn print_signed_updater_archive(output_paths: &[PathBuf]) -> crate::Result<()> {

View File

@ -1,206 +0,0 @@
name: cargo-tauri
bin_name: cargo tauri
author: Tauri Programme within The Commons Conservancy.
about: The Tauri command line interface.
subcommands:
- dev:
about: Tauri dev.
setting: TrailingVarArg
args:
- runner:
short: r
long: runner
about: binary to use to run the application
takes_value: true
- config:
short: c
long: config
about: JSON string or path to JSON file to merge with tauri.conf.json
takes_value: true
- exit-on-panic:
short: e
long: exit-on-panic
about: Exit on panic
- target:
short: t
long: target
about: target triple to build against
takes_value: true
multiple_values: true
- features:
short: f
long: features
about: list of cargo features to activate
takes_value: true
multiple_values: true
- args:
about: Args passed to the binary
index: 1
takes_value: true
multiple_values: true
- release:
long: release
about: Run the code in release mode
- build:
about: Tauri build.
args:
- runner:
short: r
long: runner
about: binary to use to build the application
takes_value: true
- debug:
short: d
long: debug
about: Builds with the debug flag
- verbose:
short: v
long: verbose
about: Enables verbose logging
- bundle:
short: b
long: bundle
about: list of bundles to package
takes_value: true
multiple_values: true
- config:
short: c
long: config
about: JSON string or path to JSON file to merge with tauri.conf.json
takes_value: true
- target:
short: t
long: target
about: target triple to build against
takes_value: true
multiple_values: true
- features:
short: f
long: features
about: list of cargo features to activate
takes_value: true
multiple_values: true
- sign:
about: Tauri updates signer.
args:
- generate:
short: g
long: generate
about: Generate keypair to sign files
- sign-file:
long: sign-file
about: Sign the specified file
takes_value: true
- private-key-path:
short: f
long: private-key-path
about: Load the private key from a file
takes_value: true
conflicts_with: private-key
- private-key:
short: k
long: private-key
about: Load the private key from a string
takes_value: true
conflicts_with: private-key-path
requires: sign-file
- write-keys:
short: w
long: write-keys
about: Write private key to a file
takes_value: true
requires: generate
- password:
short: p
long: password
about: Set private key password when signing
takes_value: true
conflicts_with: no-password
- no-password:
long: no-password
about: Set empty password for your private key
conflicts_with: password
- force:
long: force
about: Overwrite private key even if it exists on the specified path
requires: generate
- info:
about: Shows information about Tauri dependencies and project configuration.
- init:
about: Initializes a Tauri project.
args:
- ci:
long: ci
about: Skip prompting for values
- force:
short: f
long: force
about: Force init to overwrite the src-tauri folder
- log:
short: l
long: log
about: Enables logging
- directory:
short: d
long: directory
about: Set target directory for init
takes_value: true
- tauri-path:
short: t
long: tauri-path
about: Path of the Tauri project to use (relative to the cwd)
takes_value: true
- app-name:
short: A
long: app-name
about: Name of your Tauri application
takes_value: true
- window-title:
short: W
long: window-title
about: Window title of your Tauri application
takes_value: true
- dist-dir:
short: D
long: dist-dir
about: Web assets location, relative to <project-dir>/src-tauri
takes_value: true
- dev-path:
short: P
long: dev-path
about: Url of your dev server
takes_value: true
- plugin:
about: Manage Tauri plugins.
subcommands:
- init:
about: Initializes a Tauri plugin project.
args:
- name:
short: n
long: name
about: Name of your Tauri plugin
takes_value: true
required: true
- directory:
short: d
long: directory
about: Set target directory for init
takes_value: true
- tauri-path:
short: t
long: tauri-path
about: Path of the Tauri project to use (relative to the cwd)
takes_value: true
- api:
short: a
long: api
about: Initializes a Tauri plugin with TypeScript API.
- author:
long: author
about: Author name.
takes_value: true
- tauri:
long: tauri
about: Initializes a Tauri core plugin (internal usage).
setting: Hidden

View File

@ -2,13 +2,17 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use crate::helpers::{
app_paths::{app_dir, tauri_dir},
command_env,
config::{get as get_config, reload as reload_config},
manifest::{get_workspace_members, rewrite_manifest},
Logger,
use crate::{
helpers::{
app_paths::{app_dir, tauri_dir},
command_env,
config::{get as get_config, reload as reload_config},
manifest::{get_workspace_members, rewrite_manifest},
Logger,
},
Result,
};
use clap::{AppSettings, Parser};
use anyhow::Context;
use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
@ -28,15 +32,201 @@ use std::{
static BEFORE_DEV: OnceCell<Mutex<Child>> = OnceCell::new();
#[derive(Debug, Parser)]
#[clap(about = "Tauri dev")]
#[clap(setting(AppSettings::TrailingVarArg))]
pub struct Options {
/// Binary to use to run the application
#[clap(short, long)]
runner: Option<String>,
/// Target triple to build against
#[clap(short, long)]
target: Option<String>,
/// List of cargo features to activate
#[clap(short, long)]
features: Option<Vec<String>>,
/// Exit on panic
#[clap(short, long)]
exit_on_panic: bool,
/// JSON string or path to JSON file to merge with tauri.conf.json
#[clap(short, long)]
config: Option<String>,
/// Run the code in release mode
#[clap(short, long)]
release_mode: bool,
/// Args passed to the binary
args: Vec<String>,
}
pub fn command(options: Options) -> Result<()> {
let logger = Logger::new("tauri:dev");
let tauri_path = tauri_dir();
set_current_dir(&tauri_path).with_context(|| "failed to change current working directory")?;
let merge_config = if let Some(config) = &options.config {
Some(if config.starts_with('{') {
config.to_string()
} else {
std::fs::read_to_string(&config)?
})
} else {
None
};
let config = get_config(merge_config.as_deref())?;
let (settings, out_dir) = {
let config_guard = config.lock().unwrap();
let config_ = config_guard.as_ref().unwrap();
let app_settings = crate::interface::rust::AppSettings::new(config_)?;
let out_dir = app_settings
.get_out_dir(options.target.clone(), true)
.with_context(|| "failed to get project out directory")?;
let settings = crate::interface::get_bundler_settings(
app_settings,
options.target.clone(),
&Default::default(),
config_,
&out_dir,
false,
None,
)
.with_context(|| "failed to build bundler settings")?;
(settings, out_dir)
};
settings.copy_resources(&out_dir)?;
settings.copy_binaries(&out_dir)?;
if let Some(before_dev) = &config
.lock()
.unwrap()
.as_ref()
.unwrap()
.build
.before_dev_command
{
if !before_dev.is_empty() {
logger.log(format!("Running `{}`", before_dev));
#[cfg(target_os = "windows")]
let child = Command::new("cmd")
.arg("/C")
.arg(before_dev)
.current_dir(app_dir())
.envs(command_env(true)) // development build always includes debug information
.spawn()
.with_context(|| format!("failed to run `{}` with `cmd /C`", before_dev))?;
#[cfg(not(target_os = "windows"))]
let child = Command::new("sh")
.arg("-c")
.arg(before_dev)
.current_dir(app_dir())
.envs(command_env(true)) // development build always includes debug information
.spawn()
.with_context(|| format!("failed to run `{}` with `sh -c`", before_dev))?;
BEFORE_DEV.set(Mutex::new(child)).unwrap();
}
}
let runner_from_config = config
.lock()
.unwrap()
.as_ref()
.unwrap()
.build
.runner
.clone();
let runner = options
.runner
.clone()
.or(runner_from_config)
.unwrap_or_else(|| "cargo".to_string());
{
let (tx, rx) = channel();
let mut watcher = watcher(tx, Duration::from_secs(1)).unwrap();
watcher.watch(tauri_path.join("Cargo.toml"), RecursiveMode::Recursive)?;
rewrite_manifest(config.clone())?;
loop {
if let Ok(DebouncedEvent::NoticeWrite(_)) = rx.recv() {
break;
}
}
}
let mut cargo_features = config
.lock()
.unwrap()
.as_ref()
.unwrap()
.build
.features
.clone()
.unwrap_or_default();
if let Some(features) = &options.features {
cargo_features.extend(features.clone());
}
let (child_wait_tx, child_wait_rx) = channel();
let child_wait_rx = Arc::new(Mutex::new(child_wait_rx));
let mut process = start_app(&options, &runner, &cargo_features, child_wait_rx.clone());
let (tx, rx) = channel();
let mut watcher = watcher(tx, Duration::from_secs(1)).unwrap();
watcher.watch(tauri_path.join("src"), RecursiveMode::Recursive)?;
watcher.watch(tauri_path.join("Cargo.toml"), RecursiveMode::Recursive)?;
watcher.watch(tauri_path.join("tauri.conf.json"), RecursiveMode::Recursive)?;
for member in get_workspace_members()? {
let workspace_path = tauri_path.join(member);
watcher.watch(workspace_path.join("src"), RecursiveMode::Recursive)?;
watcher.watch(workspace_path.join("Cargo.toml"), RecursiveMode::Recursive)?;
}
loop {
if let Ok(event) = rx.recv() {
let event_path = match event {
DebouncedEvent::Create(path) => Some(path),
DebouncedEvent::Remove(path) => Some(path),
DebouncedEvent::Rename(_, dest) => Some(dest),
DebouncedEvent::Write(path) => Some(path),
_ => None,
};
if let Some(event_path) = event_path {
if event_path.file_name() == Some(OsStr::new("tauri.conf.json")) {
reload_config(merge_config.as_deref())?;
rewrite_manifest(config.clone())?;
} else {
// When tauri.conf.json is changed, rewrite_manifest will be called
// which will trigger the watcher again
// So the app should only be started when a file other than tauri.conf.json is changed
let _ = child_wait_tx.send(());
process
.kill()
.with_context(|| "failed to kill app process")?;
// wait for the process to exit
loop {
if let Ok(Some(_)) = process.try_wait() {
break;
}
}
process = start_app(&options, &runner, &cargo_features, child_wait_rx.clone());
}
}
}
}
}
fn kill_before_dev_process() {
if let Some(child) = BEFORE_DEV.get() {
let mut child = child.lock().unwrap();
#[cfg(windows)]
let _ = Command::new("powershell")
.arg("-NoProfile")
.arg("-Command")
.arg(format!("function Kill-Tree {{ Param([int]$ppid); Get-CimInstance Win32_Process | Where-Object {{ $_.ParentProcessId -eq $ppid }} | ForEach-Object {{ Kill-Tree $_.ProcessId }}; Stop-Process -Id $ppid }}; Kill-Tree {}", child.id()))
.status();
let _ = Command::new("powershell")
.arg("-NoProfile")
.arg("-Command")
.arg(format!("function Kill-Tree {{ Param([int]$ppid); Get-CimInstance Win32_Process | Where-Object {{ $_.ParentProcessId -eq $ppid }} | ForEach-Object {{ Kill-Tree $_.ProcessId }}; Stop-Process -Id $ppid }}; Kill-Tree {}", child.id()))
.status();
#[cfg(not(windows))]
let _ = Command::new("pkill")
.args(&["-TERM", "-P"])
@ -46,262 +236,59 @@ fn kill_before_dev_process() {
}
}
#[derive(Default)]
pub struct Dev {
runner: Option<String>,
target: Option<String>,
features: Option<Vec<String>>,
exit_on_panic: bool,
config: Option<String>,
args: Vec<String>,
release_mode: bool,
}
fn start_app(
options: &Options,
runner: &str,
features: &[String],
child_wait_rx: Arc<Mutex<Receiver<()>>>,
) -> Arc<SharedChild> {
let mut command = Command::new(runner);
command.args(&["run", "--no-default-features"]);
impl Dev {
pub fn new() -> Self {
Default::default()
if options.release_mode {
command.args(&["--release"]);
}
pub fn runner(mut self, runner: String) -> Self {
self.runner.replace(runner);
self
if let Some(target) = &options.target {
command.args(&["--target", target]);
}
pub fn target(mut self, target: String) -> Self {
self.target.replace(target);
self
if !features.is_empty() {
command.args(&["--features", &features.join(",")]);
}
pub fn features(mut self, features: Vec<String>) -> Self {
self.features.replace(features);
self
if !options.args.is_empty() {
command.arg("--").args(&options.args);
}
pub fn config(mut self, config: String) -> Self {
self.config.replace(config);
self
}
let child =
SharedChild::spawn(&mut command).unwrap_or_else(|_| panic!("failed to run {}", runner));
let child_arc = Arc::new(child);
pub fn exit_on_panic(mut self, exit_on_panic: bool) -> Self {
self.exit_on_panic = exit_on_panic;
self
}
pub fn args(mut self, args: Vec<String>) -> Self {
self.args = args;
self
}
pub fn release_mode(mut self, release_mode: bool) -> Self {
self.release_mode = release_mode;
self
}
pub fn run(self) -> crate::Result<()> {
let logger = Logger::new("tauri:dev");
let tauri_path = tauri_dir();
set_current_dir(&tauri_path).with_context(|| "failed to change current working directory")?;
let merge_config = self.config.clone();
let config = get_config(merge_config.as_deref())?;
let (settings, out_dir) = {
let config_guard = config.lock().unwrap();
let config_ = config_guard.as_ref().unwrap();
let app_settings = crate::interface::rust::AppSettings::new(config_)?;
let out_dir = app_settings
.get_out_dir(self.target.clone(), true)
.with_context(|| "failed to get project out directory")?;
let settings = crate::interface::get_bundler_settings(
app_settings,
self.target.clone(),
&Default::default(),
config_,
&out_dir,
false,
None,
)
.with_context(|| "failed to build bundler settings")?;
(settings, out_dir)
};
settings.copy_resources(&out_dir)?;
settings.copy_binaries(&out_dir)?;
if let Some(before_dev) = &config
.lock()
.unwrap()
.as_ref()
.unwrap()
.build
.before_dev_command
{
if !before_dev.is_empty() {
logger.log(format!("Running `{}`", before_dev));
#[cfg(target_os = "windows")]
let child = Command::new("cmd")
.arg("/C")
.arg(before_dev)
.current_dir(app_dir())
.envs(command_env(true)) // development build always includes debug information
.spawn()
.with_context(|| format!("failed to run `{}` with `cmd /C`", before_dev))?;
#[cfg(not(target_os = "windows"))]
let child = Command::new("sh")
.arg("-c")
.arg(before_dev)
.current_dir(app_dir())
.envs(command_env(true)) // development build always includes debug information
.spawn()
.with_context(|| format!("failed to run `{}` with `sh -c`", before_dev))?;
BEFORE_DEV.set(Mutex::new(child)).unwrap();
}
}
let runner_from_config = config
.lock()
.unwrap()
.as_ref()
.unwrap()
.build
.runner
.clone();
let runner = self
.runner
.clone()
.or(runner_from_config)
.unwrap_or_else(|| "cargo".to_string());
{
let (tx, rx) = channel();
let mut watcher = watcher(tx, Duration::from_secs(1)).unwrap();
watcher.watch(tauri_path.join("Cargo.toml"), RecursiveMode::Recursive)?;
rewrite_manifest(config.clone())?;
loop {
if let Ok(DebouncedEvent::NoticeWrite(_)) = rx.recv() {
break;
}
}
}
let mut cargo_features = config
.lock()
.unwrap()
.as_ref()
.unwrap()
.build
.features
.clone()
.unwrap_or_default();
if let Some(features) = &self.features {
cargo_features.extend(features.clone());
}
let (child_wait_tx, child_wait_rx) = channel();
let child_wait_rx = Arc::new(Mutex::new(child_wait_rx));
let mut process = self.start_app(&runner, &cargo_features, child_wait_rx.clone());
let (tx, rx) = channel();
let mut watcher = watcher(tx, Duration::from_secs(1)).unwrap();
watcher.watch(tauri_path.join("src"), RecursiveMode::Recursive)?;
watcher.watch(tauri_path.join("Cargo.toml"), RecursiveMode::Recursive)?;
watcher.watch(tauri_path.join("tauri.conf.json"), RecursiveMode::Recursive)?;
for member in get_workspace_members()? {
let workspace_path = tauri_path.join(member);
watcher.watch(workspace_path.join("src"), RecursiveMode::Recursive)?;
watcher.watch(workspace_path.join("Cargo.toml"), RecursiveMode::Recursive)?;
}
loop {
if let Ok(event) = rx.recv() {
let event_path = match event {
DebouncedEvent::Create(path) => Some(path),
DebouncedEvent::Remove(path) => Some(path),
DebouncedEvent::Rename(_, dest) => Some(dest),
DebouncedEvent::Write(path) => Some(path),
_ => None,
};
if let Some(event_path) = event_path {
if event_path.file_name() == Some(OsStr::new("tauri.conf.json")) {
reload_config(merge_config.as_deref())?;
rewrite_manifest(config.clone())?;
} else {
// When tauri.conf.json is changed, rewrite_manifest will be called
// which will trigger the watcher again
// So the app should only be started when a file other than tauri.conf.json is changed
let _ = child_wait_tx.send(());
process
.kill()
.with_context(|| "failed to kill app process")?;
// wait for the process to exit
loop {
if let Ok(Some(_)) = process.try_wait() {
break;
}
}
process = self.start_app(&runner, &cargo_features, child_wait_rx.clone());
}
}
}
}
}
fn start_app(
&self,
runner: &str,
features: &[String],
child_wait_rx: Arc<Mutex<Receiver<()>>>,
) -> Arc<SharedChild> {
let mut command = Command::new(runner);
command.args(&["run", "--no-default-features"]);
if self.release_mode {
command.args(&["--release"]);
}
if let Some(target) = &self.target {
command.args(&["--target", target]);
}
if !features.is_empty() {
command.args(&["--features", &features.join(",")]);
}
if !self.args.is_empty() {
command.arg("--").args(&self.args);
}
let child =
SharedChild::spawn(&mut command).unwrap_or_else(|_| panic!("failed to run {}", runner));
let child_arc = Arc::new(child);
let child_clone = child_arc.clone();
let exit_on_panic = self.exit_on_panic;
std::thread::spawn(move || {
let status = child_clone.wait().expect("failed to wait on child");
if exit_on_panic {
// we exit if the status is a success code (app closed) or code is 101 (compilation error)
// if the process wasn't killed by the file watcher
if (status.success() || status.code() == Some(101))
let child_clone = child_arc.clone();
let exit_on_panic = options.exit_on_panic;
std::thread::spawn(move || {
let status = child_clone.wait().expect("failed to wait on child");
if exit_on_panic {
// we exit if the status is a success code (app closed) or code is 101 (compilation error)
// if the process wasn't killed by the file watcher
if (status.success() || status.code() == Some(101))
// `child_wait_rx` indicates that the process was killed by the file watcher
&& child_wait_rx
.lock()
.expect("failed to get child_wait_rx lock")
.try_recv()
.is_err()
{
kill_before_dev_process();
exit(0);
}
} else if status.success() {
// if we're no exiting on panic, we only exit if the status is a success code (app closed)
{
kill_before_dev_process();
exit(0);
}
});
} else if status.success() {
// if we're no exiting on panic, we only exit if the status is a success code (app closed)
kill_before_dev_process();
exit(0);
}
});
child_arc
}
child_arc
}

View File

@ -7,6 +7,8 @@ use crate::helpers::{
config::get as get_config,
framework::infer_from_package_json as infer_framework,
};
use crate::Result;
use clap::Parser;
use serde::Deserialize;
use std::{
@ -76,8 +78,9 @@ enum PackageManager {
Yarn,
}
#[derive(Default)]
pub struct Info;
#[derive(Debug, Parser)]
#[clap(about = "Shows information about Tauri dependencies and project configuration")]
pub struct Options;
fn crate_latest_version(name: &str) -> Option<String> {
let url = format!("https://docs.rs/crate/{}/", name);
@ -269,19 +272,19 @@ fn get_version(command: &str, args: &[&str]) -> crate::Result<Option<String>> {
#[cfg(windows)]
fn webview2_version() -> crate::Result<Option<String>> {
let output = Command::new("powershell")
.args(&["-NoProfile", "-Command"])
.arg("Get-ItemProperty -Path 'HKLM:\\SOFTWARE\\WOW6432Node\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}' | ForEach-Object {$_.pv}")
.output()?;
.args(&["-NoProfile", "-Command"])
.arg("Get-ItemProperty -Path 'HKLM:\\SOFTWARE\\WOW6432Node\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}' | ForEach-Object {$_.pv}")
.output()?;
let version = if output.status.success() {
Some(String::from_utf8_lossy(&output.stdout).replace("\n", ""))
Some(String::from_utf8_lossy(&output.stdout).replace('\n', ""))
} else {
// check 32bit installation
let output = Command::new("powershell")
.args(&["-NoProfile", "-Command"])
.arg("Get-ItemProperty -Path 'HKLM:\\SOFTWARE\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}' | ForEach-Object {$_.pv}")
.output()?;
.args(&["-NoProfile", "-Command"])
.arg("Get-ItemProperty -Path 'HKLM:\\SOFTWARE\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}' | ForEach-Object {$_.pv}")
.output()?;
if output.status.success() {
Some(String::from_utf8_lossy(&output.stdout).replace("\n", ""))
Some(String::from_utf8_lossy(&output.stdout).replace('\n', ""))
} else {
None
}
@ -345,8 +348,8 @@ fn get_active_rust_toolchain() -> crate::Result<Option<String>> {
let toolchain = if output.status.success() {
Some(
String::from_utf8_lossy(&output.stdout)
.replace("\n", "")
.replace("\r", ""),
.replace('\n', "")
.replace('\r', ""),
)
} else {
None
@ -444,297 +447,315 @@ impl VersionBlock {
}
}
impl Info {
pub fn new() -> Self {
Default::default()
pub fn command(_options: Options) -> Result<()> {
let os_info = os_info::get();
InfoBlock {
section: true,
key: "Operating System",
value: Some(format!(
"{}, version {} {:?}",
os_info.os_type(),
os_info.version(),
os_info.bitness()
)),
suffix: None,
}
.display();
#[cfg(windows)]
VersionBlock::new("Webview2", webview2_version().unwrap_or_default()).display();
#[cfg(windows)]
VersionBlock::new(
"Visual Studio Build Tools",
build_tools_version()
.map(|r| {
let required_string = "(>= 2019 required)";
let multiple_string =
"(multiple versions might conflict; keep only 2019 if build errors occur)";
r.map(|v| match v.len() {
1 if v[0].as_str() < "2019" => format!("{} {}", v[0], required_string),
1 if v[0].as_str() >= "2019" => v[0].clone(),
_ if v.contains(&"2019".into()) => {
format!("{} {}", v.join(", "), multiple_string)
}
_ => format!("{} {} {}", v.join(", "), required_string, multiple_string),
})
})
.unwrap_or_default(),
)
.display();
let hook = panic::take_hook();
panic::set_hook(Box::new(|_info| {
// do nothing
}));
let app_dir = panic::catch_unwind(app_dir).map(Some).unwrap_or_default();
panic::set_hook(hook);
let mut package_manager = PackageManager::Npm;
if let Some(app_dir) = &app_dir {
let file_names = read_dir(app_dir)
.unwrap()
.filter(|e| {
e.as_ref()
.unwrap()
.metadata()
.unwrap()
.file_type()
.is_file()
})
.map(|e| e.unwrap().file_name().to_string_lossy().into_owned())
.collect::<Vec<String>>();
package_manager = get_package_manager(&file_names)?;
}
pub fn run(self) -> crate::Result<()> {
let os_info = os_info::get();
InfoBlock {
section: true,
key: "Operating System",
value: Some(format!(
"{}, version {} {:?}",
os_info.os_type(),
os_info.version(),
os_info.bitness()
)),
suffix: None,
}
.display();
#[cfg(windows)]
VersionBlock::new("Webview2", webview2_version().unwrap_or_default()).display();
#[cfg(windows)]
if let Some(node_version) = get_version("node", &[]).unwrap_or_default() {
InfoBlock::new("Node.js environment").section().display();
let metadata = serde_json::from_str::<VersionMetadata>(include_str!("../metadata.json"))?;
VersionBlock::new(
"Visual Studio Build Tools",
build_tools_version()
.map(|r| {
let required_string = "(>= 2019 required)";
let multiple_string =
"(multiple versions might conflict; keep only 2019 if build errors occur)";
r.map(|v| match v.len() {
1 if v[0].as_str() < "2019" => format!("{} {}", v[0], required_string),
1 if v[0].as_str() >= "2019" => v[0].clone(),
_ if v.contains(&"2019".into()) => format!("{} {}", v.join(", "), multiple_string),
_ => format!("{} {} {}", v.join(", "), required_string, multiple_string),
})
})
.unwrap_or_default(),
" Node.js",
node_version.chars().skip(1).collect::<String>(),
)
.target_version(metadata.js_cli.node.replace(">= ", ""))
.display();
let hook = panic::take_hook();
panic::set_hook(Box::new(|_info| {
// do nothing
}));
let app_dir = panic::catch_unwind(app_dir).map(Some).unwrap_or_default();
panic::set_hook(hook);
let mut package_manager = PackageManager::Npm;
if let Some(app_dir) = &app_dir {
let file_names = read_dir(app_dir)
.unwrap()
.filter(|e| {
e.as_ref()
.unwrap()
.metadata()
.unwrap()
.file_type()
.is_file()
})
.map(|e| e.unwrap().file_name().to_string_lossy().into_owned())
.collect::<Vec<String>>();
package_manager = get_package_manager(&file_names)?;
}
if let Some(node_version) = get_version("node", &[]).unwrap_or_default() {
InfoBlock::new("Node.js environment").section().display();
let metadata = serde_json::from_str::<VersionMetadata>(include_str!("../metadata.json"))?;
VersionBlock::new(
" Node.js",
node_version.chars().skip(1).collect::<String>(),
)
.target_version(metadata.js_cli.node.replace(">= ", ""))
VersionBlock::new(" @tauri-apps/cli", metadata.js_cli.version)
.target_version(npm_latest_version(&package_manager, "@tauri-apps/cli").unwrap_or_default())
.display();
if let Some(app_dir) = &app_dir {
VersionBlock::new(
" @tauri-apps/api",
npm_package_version(&package_manager, "@tauri-apps/api", app_dir).unwrap_or_default(),
)
.target_version(npm_latest_version(&package_manager, "@tauri-apps/api").unwrap_or_default())
.display();
VersionBlock::new(" @tauri-apps/cli", metadata.js_cli.version)
.target_version(npm_latest_version(&package_manager, "@tauri-apps/cli").unwrap_or_default())
.display();
if let Some(app_dir) = &app_dir {
VersionBlock::new(
" @tauri-apps/api",
npm_package_version(&package_manager, "@tauri-apps/api", app_dir).unwrap_or_default(),
)
.target_version(npm_latest_version(&package_manager, "@tauri-apps/api").unwrap_or_default())
.display();
}
InfoBlock::new("Global packages").section().display();
VersionBlock::new(" npm", get_version("npm", &[]).unwrap_or_default()).display();
VersionBlock::new(" pnpm", get_version("pnpm", &[]).unwrap_or_default()).display();
VersionBlock::new(" yarn", get_version("yarn", &[]).unwrap_or_default()).display();
}
InfoBlock::new("Rust environment").section().display();
VersionBlock::new(
" rustup",
get_version("rustup", &[]).unwrap_or_default().map(|v| {
let mut s = v.split(' ');
s.next();
s.next().unwrap().to_string()
}),
)
.display();
VersionBlock::new(
" rustc",
get_version("rustc", &[]).unwrap_or_default().map(|v| {
let mut s = v.split(' ');
s.next();
s.next().unwrap().to_string()
}),
)
.display();
VersionBlock::new(
" cargo",
get_version("cargo", &[]).unwrap_or_default().map(|v| {
let mut s = v.split(' ');
s.next();
s.next().unwrap().to_string()
}),
)
.display();
VersionBlock::new(
" toolchain",
get_active_rust_toolchain().unwrap_or_default(),
)
.display();
InfoBlock::new("Global packages").section().display();
if let Some(app_dir) = app_dir {
InfoBlock::new("App directory structure")
.section()
.display();
for entry in read_dir(app_dir)? {
let entry = entry?;
if entry.path().is_dir() {
println!("/{}", entry.path().file_name().unwrap().to_string_lossy());
}
}
InfoBlock::new("App").section().display();
let tauri_dir = tauri_dir();
let manifest: Option<CargoManifest> =
if let Ok(manifest_contents) = read_to_string(tauri_dir.join("Cargo.toml")) {
toml::from_str(&manifest_contents).ok()
} else {
None
};
let lock: Option<CargoLock> =
if let Ok(lock_contents) = read_to_string(tauri_dir.join("Cargo.lock")) {
toml::from_str(&lock_contents).ok()
} else {
None
};
let tauri_lock_packages: Vec<CargoLockPackage> = lock
.as_ref()
.map(|lock| {
lock
.package
.iter()
.filter(|p| p.name == "tauri")
.cloned()
.collect()
})
.unwrap_or_default();
let (tauri_version_string, found_tauri_versions) =
match (&manifest, &lock, tauri_lock_packages.len()) {
(Some(_manifest), Some(_lock), 1) => {
let tauri_lock_package = tauri_lock_packages.first().unwrap();
(
tauri_lock_package.version.clone(),
vec![tauri_lock_package.version.clone()],
)
}
(None, Some(_lock), 1) => {
let tauri_lock_package = tauri_lock_packages.first().unwrap();
(
format!("{} (no manifest)", tauri_lock_package.version),
vec![tauri_lock_package.version.clone()],
)
}
_ => {
let mut found_tauri_versions = Vec::new();
let manifest_version = match manifest.and_then(|m| m.dependencies.get("tauri").cloned())
{
Some(tauri) => match tauri {
CargoManifestDependency::Version(v) => {
found_tauri_versions.push(v.clone());
v
}
CargoManifestDependency::Package(p) => {
if let Some(v) = p.version {
found_tauri_versions.push(v.clone());
v
} else if let Some(p) = p.path {
let manifest_path = tauri_dir.join(&p).join("Cargo.toml");
let v = match read_to_string(&manifest_path)
.map_err(|_| ())
.and_then(|m| toml::from_str::<CargoManifest>(&m).map_err(|_| ()))
{
Ok(manifest) => manifest.package.version,
Err(_) => "unknown version".to_string(),
};
format!("path:{:?} [{}]", p, v)
} else {
"unknown manifest".to_string()
}
}
},
None => "no manifest".to_string(),
};
let lock_version = match (lock, tauri_lock_packages.is_empty()) {
(Some(_lock), true) => tauri_lock_packages
.iter()
.map(|p| p.version.clone())
.collect::<Vec<String>>()
.join(", "),
(Some(_lock), false) => "unknown lockfile".to_string(),
_ => "no lockfile".to_string(),
};
(
format!("{} ({})", manifest_version, lock_version),
found_tauri_versions,
)
}
};
let tauri_version = found_tauri_versions
.into_iter()
.map(|v| semver::Version::parse(&v).unwrap())
.max();
let suffix = match (tauri_version, crate_latest_version("tauri")) {
(Some(version), Some(target_version)) => {
let target_version = semver::Version::parse(&target_version).unwrap();
if version < target_version {
Some(format!(" (outdated, latest: {})", target_version))
} else {
None
}
}
_ => None,
};
InfoBlock::new(" tauri.rs")
.value(tauri_version_string)
.suffix(suffix)
.display();
if let Ok(config) = get_config(None) {
let config_guard = config.lock().unwrap();
let config = config_guard.as_ref().unwrap();
InfoBlock::new(" build-type")
.value(if config.tauri.bundle.active {
"bundle".to_string()
} else {
"build".to_string()
})
.display();
InfoBlock::new(" CSP")
.value(if let Some(security) = &config.tauri.security {
security.csp.clone().unwrap_or_else(|| "unset".to_string())
} else {
"unset".to_string()
})
.display();
InfoBlock::new(" distDir")
.value(config.build.dist_dir.to_string())
.display();
InfoBlock::new(" devPath")
.value(config.build.dev_path.to_string())
.display();
}
if let Ok(package_json) = read_to_string(app_dir.join("package.json")) {
let (framework, bundler) = infer_framework(&package_json);
if let Some(framework) = framework {
InfoBlock::new(" framework")
.value(framework.to_string())
.display();
}
if let Some(bundler) = bundler {
InfoBlock::new(" bundler")
.value(bundler.to_string())
.display();
}
} else {
println!("package.json not found");
}
}
Ok(())
VersionBlock::new(" npm", get_version("npm", &[]).unwrap_or_default()).display();
VersionBlock::new(" pnpm", get_version("pnpm", &[]).unwrap_or_default()).display();
VersionBlock::new(" yarn", get_version("yarn", &[]).unwrap_or_default()).display();
}
InfoBlock::new("Rust environment").section().display();
VersionBlock::new(
" rustc",
get_version("rustc", &[]).unwrap_or_default().map(|v| {
let mut s = v.split(' ');
s.next();
s.next().unwrap().to_string()
}),
)
.display();
VersionBlock::new(
" cargo",
get_version("cargo", &[]).unwrap_or_default().map(|v| {
let mut s = v.split(' ');
s.next();
s.next().unwrap().to_string()
}),
)
.display();
InfoBlock::new("Rust environment").section().display();
VersionBlock::new(
" rustup",
get_version("rustup", &[]).unwrap_or_default().map(|v| {
let mut s = v.split(' ');
s.next();
s.next().unwrap().to_string()
}),
)
.display();
VersionBlock::new(
" rustc",
get_version("rustc", &[]).unwrap_or_default().map(|v| {
let mut s = v.split(' ');
s.next();
s.next().unwrap().to_string()
}),
)
.display();
VersionBlock::new(
" cargo",
get_version("cargo", &[]).unwrap_or_default().map(|v| {
let mut s = v.split(' ');
s.next();
s.next().unwrap().to_string()
}),
)
.display();
VersionBlock::new(
" toolchain",
get_active_rust_toolchain().unwrap_or_default(),
)
.display();
if let Some(app_dir) = app_dir {
InfoBlock::new("App directory structure")
.section()
.display();
for entry in read_dir(app_dir)? {
let entry = entry?;
if entry.path().is_dir() {
println!("/{}", entry.path().file_name().unwrap().to_string_lossy());
}
}
}
InfoBlock::new("App").section().display();
let tauri_dir = tauri_dir();
let manifest: Option<CargoManifest> =
if let Ok(manifest_contents) = read_to_string(tauri_dir.join("Cargo.toml")) {
toml::from_str(&manifest_contents).ok()
} else {
None
};
let lock: Option<CargoLock> =
if let Ok(lock_contents) = read_to_string(tauri_dir.join("Cargo.lock")) {
toml::from_str(&lock_contents).ok()
} else {
None
};
let tauri_lock_packages: Vec<CargoLockPackage> = lock
.as_ref()
.map(|lock| {
lock
.package
.iter()
.filter(|p| p.name == "tauri")
.cloned()
.collect()
})
.unwrap_or_default();
let (tauri_version_string, found_tauri_versions) =
match (&manifest, &lock, tauri_lock_packages.len()) {
(Some(_manifest), Some(_lock), 1) => {
let tauri_lock_package = tauri_lock_packages.first().unwrap();
(
tauri_lock_package.version.clone(),
vec![tauri_lock_package.version.clone()],
)
}
(None, Some(_lock), 1) => {
let tauri_lock_package = tauri_lock_packages.first().unwrap();
(
format!("{} (no manifest)", tauri_lock_package.version),
vec![tauri_lock_package.version.clone()],
)
}
_ => {
let mut found_tauri_versions = Vec::new();
let manifest_version = match manifest.and_then(|m| m.dependencies.get("tauri").cloned()) {
Some(tauri) => match tauri {
CargoManifestDependency::Version(v) => {
found_tauri_versions.push(v.clone());
v
}
CargoManifestDependency::Package(p) => {
if let Some(v) = p.version {
found_tauri_versions.push(v.clone());
v
} else if let Some(p) = p.path {
let manifest_path = tauri_dir.join(&p).join("Cargo.toml");
let v = match read_to_string(&manifest_path)
.map_err(|_| ())
.and_then(|m| toml::from_str::<CargoManifest>(&m).map_err(|_| ()))
{
Ok(manifest) => manifest.package.version,
Err(_) => "unknown version".to_string(),
};
format!("path:{:?} [{}]", p, v)
} else {
"unknown manifest".to_string()
}
}
},
None => "no manifest".to_string(),
};
let lock_version = match (lock, tauri_lock_packages.is_empty()) {
(Some(_lock), true) => tauri_lock_packages
.iter()
.map(|p| p.version.clone())
.collect::<Vec<String>>()
.join(", "),
(Some(_lock), false) => "unknown lockfile".to_string(),
_ => "no lockfile".to_string(),
};
(
format!("{} ({})", manifest_version, lock_version),
found_tauri_versions,
)
}
};
let tauri_version = found_tauri_versions
.into_iter()
.map(|v| semver::Version::parse(&v).unwrap())
.max();
let suffix = match (tauri_version, crate_latest_version("tauri")) {
(Some(version), Some(target_version)) => {
let target_version = semver::Version::parse(&target_version).unwrap();
if version < target_version {
Some(format!(" (outdated, latest: {})", target_version))
} else {
None
}
}
_ => None,
};
InfoBlock::new(" tauri.rs")
.value(tauri_version_string)
.suffix(suffix)
.display();
if let Ok(config) = get_config(None) {
let config_guard = config.lock().unwrap();
let config = config_guard.as_ref().unwrap();
InfoBlock::new(" build-type")
.value(if config.tauri.bundle.active {
"bundle".to_string()
} else {
"build".to_string()
})
.display();
InfoBlock::new(" CSP")
.value(if let Some(security) = &config.tauri.security {
security.csp.clone().unwrap_or_else(|| "unset".to_string())
} else {
"unset".to_string()
})
.display();
InfoBlock::new(" distDir")
.value(config.build.dist_dir.to_string())
.display();
InfoBlock::new(" devPath")
.value(config.build.dev_path.to_string())
.display();
}
if let Some(app_dir) = app_dir {
if let Ok(package_json) = read_to_string(app_dir.join("package.json")) {
let (framework, bundler) = infer_framework(&package_json);
if let Some(framework) = framework {
InfoBlock::new(" framework")
.value(framework.to_string())
.display();
}
if let Some(bundler) = bundler {
InfoBlock::new(" bundler")
.value(bundler.to_string())
.display();
}
} else {
println!("package.json not found");
}
}
Ok(())
}
fn get_package_manager<T: AsRef<str>>(file_names: &[T]) -> crate::Result<PackageManager> {
@ -771,9 +792,9 @@ fn get_package_manager<T: AsRef<str>>(file_names: &[T]) -> crate::Result<Package
if found.len() > 1 {
return Err(anyhow::anyhow!(
"only one package mangager should be used, but found {}\nplease remove unused package manager lock files",
found.join(" and ")
));
"only one package mangager should be used, but found {}\nplease remove unused package manager lock files",
found.join(" and ")
));
}
if use_npm {
@ -784,70 +805,3 @@ fn get_package_manager<T: AsRef<str>>(file_names: &[T]) -> crate::Result<Package
Ok(PackageManager::Yarn)
}
}
#[cfg(test)]
mod tests {
use crate::info::get_package_manager;
#[test]
fn no_package_manager_lock_file() -> crate::Result<()> {
let file_names = vec!["package.json"];
let pm = get_package_manager(&file_names);
match pm {
Ok(_) => Ok(()),
Err(m) => Err(m),
}
}
#[test]
fn package_managers_npm_and_yarn() -> crate::Result<()> {
let file_names = vec!["package.json", "package-lock.json", "yarn.lock"];
let pm = get_package_manager(&file_names);
match pm {
Ok(_) => panic!("expected error"),
Err(m) => assert_eq!(
m.to_string().as_str(),
"only one package mangager should be used, but found npm and yarn\nplease remove unused package manager lock files"
),
}
Ok(())
}
#[test]
fn package_managers_npm_and_pnpm() -> crate::Result<()> {
let file_names = vec!["package.json", "package-lock.json", "pnpm-lock.yaml"];
let pm = get_package_manager(&file_names);
match pm {
Ok(_) => panic!("expected error"),
Err(m) => assert_eq!(
m.to_string().as_str(),
"only one package mangager should be used, but found npm and pnpm\nplease remove unused package manager lock files"
),
}
Ok(())
}
#[test]
fn package_managers_pnpm_and_yarn() -> crate::Result<()> {
let file_names = vec!["package.json", "pnpm-lock.yaml", "yarn.lock"];
let pm = get_package_manager(&file_names);
match pm {
Ok(_) => panic!("expected error"),
Err(m) => assert_eq!(
m.to_string().as_str(),
"only one package mangager should be used, but found pnpm and yarn\nplease remove unused package manager lock files"
),
}
Ok(())
}
#[test]
fn package_managers_yarn() -> crate::Result<()> {
let file_names = vec!["package.json", "yarn.lock"];
let pm = get_package_manager(&file_names);
match pm {
Ok(_) => Ok(()),
Err(m) => Err(m),
}
}
}

View File

@ -2,144 +2,205 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::{collections::BTreeMap, env::current_dir, fs::remove_dir_all, path::PathBuf};
use crate::{
helpers::{resolve_tauri_path, template, Logger},
helpers::{
framework::{infer_from_package_json as infer_framework, Framework},
resolve_tauri_path, template, Logger,
},
VersionMetadata,
};
use std::{
collections::BTreeMap,
env::current_dir,
fmt::Display,
fs::{read_to_string, remove_dir_all},
path::PathBuf,
str::FromStr,
};
use crate::Result;
use anyhow::Context;
use clap::Parser;
use dialoguer::Input;
use handlebars::{to_json, Handlebars};
use include_dir::{include_dir, Dir};
use serde::Deserialize;
const TEMPLATE_DIR: Dir<'_> = include_dir!("templates/app");
pub struct Init {
#[derive(Debug, Parser)]
#[clap(about = "Initializes a Tauri project")]
pub struct Options {
/// Skip prompting for values
#[clap(long)]
ci: bool,
/// Force init to overwrite the src-tauri folder
#[clap(short, long)]
force: bool,
directory: PathBuf,
/// Enables logging
#[clap(short, long)]
log: bool,
/// Set target directory for init
#[clap(short, long)]
#[clap(default_value_t = current_dir().expect("failed to read cwd").display().to_string())]
directory: String,
/// Path of the Tauri project to use (relative to the cwd)
#[clap(short, long)]
tauri_path: Option<PathBuf>,
/// Name of your Tauri application
#[clap(short = 'A', long)]
app_name: Option<String>,
/// Window title of your Tauri application
#[clap(short = 'W', long)]
window_title: Option<String>,
/// Web assets location, relative to <project-dir>/src-tauri
#[clap(short = 'D', long)]
dist_dir: Option<String>,
/// Url of your dev server
#[clap(short = 'P', long)]
dev_path: Option<String>,
}
impl Default for Init {
fn default() -> Self {
Self {
force: false,
directory: current_dir().expect("failed to read cwd"),
tauri_path: None,
app_name: None,
window_title: None,
dist_dir: None,
dev_path: None,
}
}
#[derive(Deserialize)]
struct PackageJson {
name: Option<String>,
product_name: Option<String>,
}
impl Init {
pub fn new() -> Self {
Default::default()
}
#[derive(Default)]
struct InitDefaults {
app_name: Option<String>,
framework: Option<Framework>,
}
pub fn force(mut self) -> Self {
self.force = true;
self
}
impl Options {
fn load(mut self) -> Result<Self> {
self.ci = self.ci || std::env::var("CI").is_ok();
let package_json_path = PathBuf::from(&self.directory).join("package.json");
pub fn directory(mut self, directory: impl Into<PathBuf>) -> Self {
self.directory = directory.into();
self
}
pub fn tauri_path(mut self, tauri_path: impl Into<PathBuf>) -> Self {
self.tauri_path = Some(tauri_path.into());
self
}
pub fn app_name(mut self, app_name: impl Into<String>) -> Self {
self.app_name = Some(app_name.into());
self
}
pub fn window_title(mut self, window_title: impl Into<String>) -> Self {
self.window_title = Some(window_title.into());
self
}
pub fn dist_dir(mut self, dist_dir: impl Into<String>) -> Self {
self.dist_dir = Some(dist_dir.into());
self
}
pub fn dev_path(mut self, dev_path: impl Into<String>) -> Self {
self.dev_path = Some(dev_path.into());
self
}
pub fn run(self) -> crate::Result<()> {
let logger = Logger::new("tauri:init");
let template_target_path = self.directory.join("src-tauri");
let metadata = serde_json::from_str::<VersionMetadata>(include_str!("../metadata.json"))?;
if template_target_path.exists() && !self.force {
logger.warn(format!(
"Tauri dir ({:?}) not empty. Run `init --force` to overwrite.",
template_target_path
));
let init_defaults = if package_json_path.exists() {
let package_json_text = read_to_string(package_json_path)?;
let package_json: PackageJson = serde_json::from_str(&package_json_text)?;
let (framework, _) = infer_framework(&package_json_text);
InitDefaults {
app_name: package_json.product_name.or(package_json.name),
framework,
}
} else {
let (tauri_dep, tauri_build_dep) = if let Some(tauri_path) = self.tauri_path {
(
format!(
r#"{{ path = {:?}, features = [ "api-all" ] }}"#,
resolve_tauri_path(&tauri_path, "core/tauri")
),
format!(
"{{ path = {:?} }}",
resolve_tauri_path(&tauri_path, "core/tauri-build")
),
)
} else {
(
format!(
r#"{{ version = "{}", features = [ "api-all" ] }}"#,
metadata.tauri
),
format!(r#"{{ version = "{}" }}"#, metadata.tauri_build),
)
};
Default::default()
};
let _ = remove_dir_all(&template_target_path);
let handlebars = Handlebars::new();
self.app_name = self.app_name.or(request_input(
"What is your app name?",
init_defaults.app_name.clone(),
self.ci,
)?);
let mut data = BTreeMap::new();
data.insert("tauri_dep", to_json(tauri_dep));
data.insert("tauri_build_dep", to_json(tauri_build_dep));
data.insert(
"dist_dir",
to_json(self.dist_dir.unwrap_or_else(|| "../dist".to_string())),
);
data.insert(
"dev_path",
to_json(
self
.dev_path
.unwrap_or_else(|| "http://localhost:4000".to_string()),
),
);
data.insert(
"app_name",
to_json(self.app_name.unwrap_or_else(|| "Tauri App".to_string())),
);
data.insert(
"window_title",
to_json(self.window_title.unwrap_or_else(|| "Tauri".to_string())),
);
self.window_title = self.window_title.or(request_input(
"What should the window title be?",
init_defaults.app_name.clone(),
self.ci,
)?);
template::render(&handlebars, &data, &TEMPLATE_DIR, &self.directory)
.with_context(|| "failed to render Tauri template")?;
}
self.dist_dir = self.dist_dir
.or(request_input(
r#"Whe re are your web assets (HTML/CSS/JS) located, relative to the "<current dir>/src-tauri/tauri.conf.json" file that will be created?"#,
init_defaults.framework.as_ref().map(|f| f.dist_dir()),
self.ci)?);
Ok(())
self.dev_path = self.dev_path.or(request_input(
"What is the url of your dev server?",
init_defaults.framework.map(|f| f.dev_path()),
self.ci,
)?);
Ok(self)
}
}
pub fn command(mut options: Options) -> Result<()> {
options = options.load()?;
let logger = Logger::new("tauri:init");
let template_target_path = PathBuf::from(&options.directory).join("src-tauri");
let metadata = serde_json::from_str::<VersionMetadata>(include_str!("../metadata.json"))?;
if template_target_path.exists() && !options.force {
logger.warn(format!(
"Tauri dir ({:?}) not empty. Run `init --force` to overwrite.",
template_target_path
));
} else {
let (tauri_dep, tauri_build_dep) = if let Some(tauri_path) = options.tauri_path {
(
format!(
r#"{{ path = {:?}, features = [ "api-all" ] }}"#,
resolve_tauri_path(&tauri_path, "core/tauri")
),
format!(
"{{ path = {:?} }}",
resolve_tauri_path(&tauri_path, "core/tauri-build")
),
)
} else {
(
format!(
r#"{{ version = "{}", features = [ "api-all" ] }}"#,
metadata.tauri
),
format!(r#"{{ version = "{}" }}"#, metadata.tauri_build),
)
};
let _ = remove_dir_all(&template_target_path);
let handlebars = Handlebars::new();
let mut data = BTreeMap::new();
data.insert("tauri_dep", to_json(tauri_dep));
data.insert("tauri_build_dep", to_json(tauri_build_dep));
data.insert(
"dist_dir",
to_json(options.dist_dir.unwrap_or_else(|| "../dist".to_string())),
);
data.insert(
"dev_path",
to_json(
options
.dev_path
.unwrap_or_else(|| "http://localhost:4000".to_string()),
),
);
data.insert(
"app_name",
to_json(options.app_name.unwrap_or_else(|| "Tauri App".to_string())),
);
data.insert(
"window_title",
to_json(options.window_title.unwrap_or_else(|| "Tauri".to_string())),
);
template::render(&handlebars, &data, &TEMPLATE_DIR, &options.directory)
.with_context(|| "failed to render Tauri template")?;
}
Ok(())
}
fn request_input<T>(prompt: &str, default: Option<T>, skip: bool) -> Result<Option<T>>
where
T: Clone + FromStr + Display,
T::Err: Display + std::fmt::Debug,
{
if skip {
Ok(default)
} else {
let mut builder = Input::new();
builder.with_prompt(prompt);
if let Some(v) = default {
builder.default(v);
}
builder.interact_text().map(Some).map_err(Into::into)
}
}

View File

@ -358,12 +358,12 @@ pub fn get_workspace_dir(current_dir: &Path) -> PathBuf {
}
Err(e) => {
logger.warn(format!(
"Found `{}`, which may define a parent workspace, but \
failed to parse it. If this is indeed a parent workspace, undefined behavior may occur: \
\n {:#}",
dir.display(),
e
));
"Found `{}`, which may define a parent workspace, but \
failed to parse it. If this is indeed a parent workspace, undefined behavior may occur: \
\n {:#}",
dir.display(),
e
));
}
}
}

View File

@ -3,9 +3,6 @@
// SPDX-License-Identifier: MIT
pub use anyhow::Result;
use clap::{crate_version, load_yaml, App, AppSettings, ArgMatches};
use dialoguer::Input;
use serde::Deserialize;
mod build;
mod dev;
@ -14,324 +11,74 @@ mod info;
mod init;
mod interface;
mod plugin;
mod sign;
mod signer;
use helpers::framework::{infer_from_package_json as infer_framework, Framework};
use clap::{AppSettings, FromArgMatches, IntoApp, Parser, Subcommand};
use std::{env::current_dir, fs::read_to_string, path::PathBuf};
#[derive(Deserialize)]
#[derive(serde::Deserialize)]
pub struct VersionMetadata {
tauri: String,
#[serde(rename = "tauri-build")]
tauri_build: String,
}
#[derive(Deserialize)]
struct PackageJson {
name: Option<String>,
product_name: Option<String>,
#[derive(Parser)]
#[clap(author, version, about, bin_name("cargo tauri"))]
#[clap(global_setting(AppSettings::PropagateVersion))]
#[clap(global_setting(AppSettings::UseLongFormatForHelpSubcommand))]
#[clap(setting(AppSettings::SubcommandRequiredElseHelp))]
struct Cli {
#[clap(subcommand)]
command: Commands,
}
#[derive(Default)]
struct InitDefaults {
app_name: Option<String>,
framework: Option<Framework>,
#[derive(Subcommand)]
enum Commands {
Build(build::Options),
Dev(dev::Options),
Info(info::Options),
Init(init::Options),
Plugin(plugin::Cli),
Signer(signer::Cli),
}
macro_rules! value_or_prompt {
($init_runner: ident, $setter_fn: ident, $value: ident, $ci: ident, $prompt_message: expr, $prompt_default: expr) => {{
let mut init_runner = $init_runner;
if let Some(value) = $value {
init_runner = init_runner.$setter_fn(value);
} else if !$ci {
let mut builder = Input::<String>::new();
builder.with_prompt($prompt_message);
if let Some(default) = $prompt_default {
builder.default(default);
}
let input = builder.interact_text()?;
init_runner = init_runner.$setter_fn(input);
}
init_runner
}};
}
fn get_config(config: &str) -> Result<String> {
if config.starts_with('{') {
Ok(config.into())
} else {
std::fs::read_to_string(&config).map_err(Into::into)
}
}
fn plugin_command(matches: &ArgMatches) -> Result<()> {
if let Some(matches) = matches.subcommand_matches("init") {
let api = matches.is_present("api");
let plugin_name = matches.value_of("name").expect("name is required");
let directory = matches.value_of("directory");
let tauri_path = matches.value_of("tauri-path");
let tauri = matches.is_present("tauri");
let author = matches
.value_of("author")
.map(|p| p.to_string())
.unwrap_or_else(|| {
if tauri {
"Tauri Programme within The Commons Conservancy".into()
} else {
"You".into()
}
});
let mut plugin_runner = plugin::Plugin::new()
.plugin_name(plugin_name.to_string())
.author(author);
if api {
plugin_runner = plugin_runner.api();
}
if tauri {
plugin_runner = plugin_runner.tauri();
}
if let Some(directory) = directory {
plugin_runner = plugin_runner.directory(directory);
}
if let Some(tauri_path) = tauri_path {
plugin_runner = plugin_runner.tauri_path(tauri_path);
}
plugin_runner.run()
} else {
Ok(())
}
}
fn init_command(matches: &ArgMatches) -> Result<()> {
let force = matches.is_present("force");
let directory = matches.value_of("directory");
let tauri_path = matches.value_of("tauri-path");
let app_name = matches.value_of("app-name");
let window_title = matches.value_of("window-title");
let dist_dir = matches.value_of("dist-dir");
let dev_path = matches.value_of("dev-path");
let ci = matches.is_present("ci") || std::env::var("CI").is_ok();
let mut init_runner = init::Init::new();
if force {
init_runner = init_runner.force();
}
let base_directory = if let Some(directory) = directory {
init_runner = init_runner.directory(directory);
PathBuf::from(directory)
} else {
current_dir().expect("failed to read cwd")
};
if let Some(tauri_path) = tauri_path {
init_runner = init_runner.tauri_path(tauri_path);
}
let package_json_path = base_directory.join("package.json");
let init_defaults = if package_json_path.exists() {
let package_json_text = read_to_string(package_json_path)?;
let package_json: PackageJson = serde_json::from_str(&package_json_text)?;
let (framework, _) = infer_framework(&package_json_text);
InitDefaults {
app_name: package_json.product_name.or(package_json.name),
framework,
}
} else {
Default::default()
};
init_runner = value_or_prompt!(
init_runner,
app_name,
app_name,
ci,
"What is your app name?",
init_defaults.app_name.clone()
);
init_runner = value_or_prompt!(
init_runner,
window_title,
window_title,
ci,
"What should the window title be?",
init_defaults.app_name.clone()
);
init_runner = value_or_prompt!(
init_runner,
dist_dir,
dist_dir,
ci,
r#"Where are your web assets (HTML/CSS/JS) located, relative to the "<current dir>/src-tauri/tauri.conf.json" file that will be created?"#,
init_defaults.framework.as_ref().map(|f| f.dist_dir())
);
init_runner = value_or_prompt!(
init_runner,
dev_path,
dev_path,
ci,
"What is the url of your dev server?",
init_defaults.framework.map(|f| f.dev_path())
);
init_runner.run()
}
fn dev_command(matches: &ArgMatches) -> Result<()> {
let runner = matches.value_of("runner");
let target = matches.value_of("target");
let features: Vec<String> = matches
.values_of("features")
.map(|a| a.into_iter().map(|v| v.to_string()).collect())
.unwrap_or_default();
let exit_on_panic = matches.is_present("exit-on-panic");
let config = matches.value_of("config");
let args: Vec<String> = matches
.values_of("args")
.map(|a| a.into_iter().map(|v| v.to_string()).collect())
.unwrap_or_default();
let release_mode = matches.is_present("release");
let mut dev_runner = dev::Dev::new()
.exit_on_panic(exit_on_panic)
.args(args)
.features(features)
.release_mode(release_mode);
if let Some(runner) = runner {
dev_runner = dev_runner.runner(runner.to_string());
}
if let Some(target) = target {
dev_runner = dev_runner.target(target.to_string());
}
if let Some(config) = config {
dev_runner = dev_runner.config(get_config(config)?);
}
dev_runner.run()
}
fn build_command(matches: &ArgMatches) -> Result<()> {
let runner = matches.value_of("runner");
let target = matches.value_of("target");
let features: Vec<String> = matches
.values_of("features")
.map(|a| a.into_iter().map(|v| v.to_string()).collect())
.unwrap_or_default();
let debug = matches.is_present("debug");
let verbose = matches.is_present("verbose");
let bundles = matches.values_of_lossy("bundle");
let config = matches.value_of("config");
let mut build_runner = build::Build::new().features(features);
if let Some(runner) = runner {
build_runner = build_runner.runner(runner.to_string());
}
if let Some(target) = target {
build_runner = build_runner.target(target.to_string());
}
if debug {
build_runner = build_runner.debug();
}
if verbose {
build_runner = build_runner.verbose();
}
if let Some(bundles) = bundles {
build_runner = build_runner.bundles(bundles);
}
if let Some(config) = config {
build_runner = build_runner.config(get_config(config)?);
}
build_runner.run()
}
fn info_command() -> Result<()> {
info::Info::new().run()
}
fn sign_command(matches: &ArgMatches) -> Result<()> {
let private_key = matches.value_of("private-key");
let private_key_path = matches.value_of("private-key-path");
let file = matches.value_of("sign-file");
let password = matches.value_of("password");
let no_password = matches.is_present("no-password");
let write_keys = matches.value_of("write-keys");
let force = matches.is_present("force");
// generate keypair
if matches.is_present("generate") {
let mut keygen_runner = sign::KeyGenerator::new();
if no_password {
keygen_runner = keygen_runner.empty_password();
}
if force {
keygen_runner = keygen_runner.force();
}
if let Some(write_keys) = write_keys {
keygen_runner = keygen_runner.output_path(write_keys);
}
if let Some(password) = password {
keygen_runner = keygen_runner.password(password);
}
return keygen_runner.generate_keys();
}
// sign our binary / archive
let mut sign_runner = sign::Signer::new();
if let Some(private_key) = private_key {
sign_runner = sign_runner.private_key(private_key);
}
if let Some(private_key_path) = private_key_path {
sign_runner = sign_runner.private_key_path(private_key_path);
}
if let Some(file) = file {
sign_runner = sign_runner.file_to_sign(file);
}
if let Some(password) = password {
sign_runner = sign_runner.password(password);
}
if no_password {
sign_runner = sign_runner.empty_password();
}
sign_runner.run()
fn format_error<I: IntoApp>(err: clap::Error) -> clap::Error {
let mut app = I::into_app();
err.format(&mut app)
}
fn main() -> Result<()> {
let yaml = load_yaml!("cli.yml");
let app = App::from(yaml)
.version(crate_version!())
.setting(AppSettings::ArgRequiredElseHelp)
.setting(AppSettings::PropagateVersion)
.setting(AppSettings::SubcommandRequired)
.arg(clap::Arg::new("cargo").hidden(true).possible_value("tauri"));
let matches = app.get_matches();
let matches = <Cli as IntoApp>::into_app()
.arg(clap::Arg::new("cargo").hide(true).possible_value("tauri"))
.get_matches();
let res = <Cli as FromArgMatches>::from_arg_matches(&matches).map_err(format_error::<Cli>);
let cli = match res {
Ok(s) => s,
Err(e) => e.exit(),
};
if let Some(matches) = matches.subcommand_matches("init") {
init_command(matches)?;
} else if let Some(matches) = matches.subcommand_matches("plugin") {
plugin_command(matches)?;
} else if let Some(matches) = matches.subcommand_matches("dev") {
dev_command(matches)?;
} else if let Some(matches) = matches.subcommand_matches("build") {
build_command(matches)?;
} else if matches.subcommand_matches("info").is_some() {
info_command()?;
} else if let Some(matches) = matches.subcommand_matches("sign") {
sign_command(matches)?;
match cli.command {
Commands::Build(options) => build::command(options)?,
Commands::Dev(options) => dev::command(options)?,
Commands::Info(options) => info::command(options)?,
Commands::Init(options) => init::command(options)?,
Commands::Plugin(cli) => plugin::command(cli)?,
Commands::Signer(cli) => signer::command(cli)?,
}
/*if let Some(matches) = matches.subcommand_matches("dev") {
dev::command(matches)?;
} else if let Some(matches) = matches.subcommand_matches("build") {
build::command(matches)?;
} else if let Some(matches) = matches.subcommand_matches("signer") {
signer::command(matches)?;
} else if let Some(_) = matches.subcommand_matches("info") {
info::command()?;
} else if let Some(matches) = matches.subcommand_matches("init") {
init::command(matches)?;
} else if let Some(matches) = matches.subcommand_matches("plugin") {
plugin::command(matches)?;
}*/
Ok(())
}

View File

@ -2,167 +2,29 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use crate::{
helpers::{resolve_tauri_path, template, Logger},
VersionMetadata,
};
use anyhow::Context;
use handlebars::{to_json, Handlebars};
use heck::{KebabCase, SnakeCase};
use include_dir::{include_dir, Dir};
use clap::{AppSettings, Parser, Subcommand};
use std::{collections::BTreeMap, env::current_dir, fs::remove_dir_all, path::PathBuf};
use crate::Result;
const BACKEND_PLUGIN_DIR: Dir<'_> = include_dir!("templates/plugin/backend");
const API_PLUGIN_DIR: Dir<'_> = include_dir!("templates/plugin/with-api");
mod init;
pub struct Plugin {
plugin_name: String,
api: bool,
tauri: bool,
directory: PathBuf,
tauri_path: Option<PathBuf>,
author: String,
#[derive(Parser)]
#[clap(author, version, about = "Manage Tauri plugins")]
#[clap(setting(AppSettings::SubcommandRequiredElseHelp))]
pub struct Cli {
#[clap(subcommand)]
command: Commands,
}
impl Default for Plugin {
fn default() -> Self {
Self {
plugin_name: "".into(),
api: false,
tauri: false,
directory: current_dir().expect("failed to read cwd"),
tauri_path: None,
author: "".into(),
}
}
#[derive(Subcommand)]
enum Commands {
Init(init::Options),
}
impl Plugin {
pub fn new() -> Self {
Default::default()
pub fn command(cli: Cli) -> Result<()> {
match cli.command {
Commands::Init(options) => init::command(options)?,
}
pub fn plugin_name(mut self, plugin_name: String) -> Self {
self.plugin_name = plugin_name;
self
}
pub fn api(mut self) -> Self {
self.api = true;
self
}
pub fn tauri(mut self) -> Self {
self.tauri = true;
self
}
pub fn directory(mut self, directory: impl Into<PathBuf>) -> Self {
self.directory = directory.into();
self
}
pub fn tauri_path(mut self, tauri_path: impl Into<PathBuf>) -> Self {
self.tauri_path = Some(tauri_path.into());
self
}
pub fn author(mut self, author: String) -> Self {
self.author = author;
self
}
pub fn run(self) -> crate::Result<()> {
let logger = Logger::new("tauri:init:plugin");
let template_target_path = self.directory.join(&format!(
"tauri-plugin-{}",
self.plugin_name.to_kebab_case()
));
let metadata = serde_json::from_str::<VersionMetadata>(include_str!("../metadata.json"))?;
if template_target_path.exists() {
logger.warn(format!(
"Plugin dir ({:?}) not empty.",
template_target_path
));
} else {
let (tauri_dep, tauri_example_dep, tauri_build_dep) =
if let Some(tauri_path) = self.tauri_path {
(
format!(
r#"{{ path = {:?} }}"#,
resolve_tauri_path(&tauri_path, "core/tauri")
),
format!(
r#"{{ path = {:?}, features = [ "api-all" ] }}"#,
resolve_tauri_path(&tauri_path, "core/tauri")
),
format!(
"{{ path = {:?} }}",
resolve_tauri_path(&tauri_path, "core/tauri-build")
),
)
} else {
(
format!(r#"{{ version = "{}" }}"#, metadata.tauri),
format!(
r#"{{ version = "{}", features = [ "api-all" ] }}"#,
metadata.tauri
),
format!(r#"{{ version = "{}" }}"#, metadata.tauri_build),
)
};
let _ = remove_dir_all(&template_target_path);
let handlebars = Handlebars::new();
let mut data = BTreeMap::new();
data.insert("plugin_name_original", to_json(&self.plugin_name));
data.insert("plugin_name", to_json(self.plugin_name.to_kebab_case()));
data.insert(
"plugin_name_snake_case",
to_json(self.plugin_name.to_snake_case()),
);
data.insert("tauri_dep", to_json(tauri_dep));
data.insert("tauri_example_dep", to_json(tauri_example_dep));
data.insert("tauri_build_dep", to_json(tauri_build_dep));
data.insert("author", to_json(self.author));
if self.tauri {
data.insert(
"license_template",
to_json(
"// Copyright {20\\d{2}(-20\\d{2})?} Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT\n\n"
.replace(" ", "")
.replace(" //", "//"),
),
);
data.insert(
"license_header",
to_json(
"// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT\n\n"
.replace(" ", "")
.replace(" //", "//"),
),
);
}
template::render(
&handlebars,
&data,
if self.api {
&API_PLUGIN_DIR
} else {
&BACKEND_PLUGIN_DIR
},
&template_target_path,
)
.with_context(|| "failed to render Tauri template")?;
}
Ok(())
}
Ok(())
}

View File

@ -0,0 +1,149 @@
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use crate::Result;
use crate::{
helpers::{resolve_tauri_path, template, Logger},
VersionMetadata,
};
use anyhow::Context;
use clap::{ArgSettings, Parser};
use handlebars::{to_json, Handlebars};
use heck::{KebabCase, SnakeCase};
use include_dir::{include_dir, Dir};
use std::{collections::BTreeMap, env::current_dir, fs::remove_dir_all, path::PathBuf};
const BACKEND_PLUGIN_DIR: Dir<'_> = include_dir!("templates/plugin/backend");
const API_PLUGIN_DIR: Dir<'_> = include_dir!("templates/plugin/with-api");
#[derive(Debug, Parser)]
#[clap(about = "Initializes a Tauri plugin project")]
pub struct Options {
/// Name of your Tauri plugin
#[clap(short = 'n', long = "name")]
plugin_name: String,
/// Initializes a Tauri plugin with TypeScript API
#[clap(short, long)]
api: bool,
/// Initializes a Tauri core plugin (internal usage)
#[clap(short, long, hide(true))]
#[clap(setting(ArgSettings::Hidden))]
tauri: bool,
/// Set target directory for init
#[clap(short, long)]
#[clap(default_value_t = current_dir().expect("failed to read cwd").display().to_string())]
directory: String,
/// Path of the Tauri project to use (relative to the cwd)
#[clap(short, long)]
tauri_path: Option<PathBuf>,
/// Author name
#[clap(short, long)]
author: Option<String>,
}
impl Options {
fn load(&mut self) {
if self.author.is_none() {
self.author.replace(if self.tauri {
"Tauri Programme within The Commons Conservancy".into()
} else {
"You".into()
});
}
}
}
pub fn command(mut options: Options) -> Result<()> {
options.load();
let logger = Logger::new("tauri:init:plugin");
let template_target_path = PathBuf::from(options.directory).join(&format!(
"tauri-plugin-{}",
options.plugin_name.to_kebab_case()
));
let metadata = serde_json::from_str::<VersionMetadata>(include_str!("../../metadata.json"))?;
if template_target_path.exists() {
logger.warn(format!(
"Plugin dir ({:?}) not empty.",
template_target_path
));
} else {
let (tauri_dep, tauri_example_dep, tauri_build_dep) =
if let Some(tauri_path) = options.tauri_path {
(
format!(
r#"{{ path = {:?} }}"#,
resolve_tauri_path(&tauri_path, "core/tauri")
),
format!(
r#"{{ path = {:?}, features = [ "api-all" ] }}"#,
resolve_tauri_path(&tauri_path, "core/tauri")
),
format!(
"{{ path = {:?} }}",
resolve_tauri_path(&tauri_path, "core/tauri-build")
),
)
} else {
(
format!(r#"{{ version = "{}" }}"#, metadata.tauri),
format!(
r#"{{ version = "{}", features = [ "api-all" ] }}"#,
metadata.tauri
),
format!(r#"{{ version = "{}" }}"#, metadata.tauri_build),
)
};
let _ = remove_dir_all(&template_target_path);
let handlebars = Handlebars::new();
let mut data = BTreeMap::new();
data.insert("plugin_name_original", to_json(&options.plugin_name));
data.insert("plugin_name", to_json(options.plugin_name.to_kebab_case()));
data.insert(
"plugin_name_snake_case",
to_json(options.plugin_name.to_snake_case()),
);
data.insert("tauri_dep", to_json(tauri_dep));
data.insert("tauri_example_dep", to_json(tauri_example_dep));
data.insert("tauri_build_dep", to_json(tauri_build_dep));
data.insert("author", to_json(options.author));
if options.tauri {
data.insert(
"license_template",
to_json(
"// Copyright {20\\d{2}(-20\\d{2})?} Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT\n\n"
.replace(" ", "")
.replace(" //", "//"),
),
);
data.insert(
"license_header",
to_json(
"// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT\n\n"
.replace(" ", "")
.replace(" //", "//"),
),
);
}
template::render(
&handlebars,
&data,
if options.api {
&API_PLUGIN_DIR
} else {
&BACKEND_PLUGIN_DIR
},
&template_target_path,
)
.with_context(|| "failed to render Tauri template")?;
}
Ok(())
}

View File

@ -1,140 +0,0 @@
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use crate::helpers::updater_signature::{
generate_key, read_key_from_file, save_keypair, sign_file,
};
use std::path::{Path, PathBuf};
use anyhow::Context;
#[derive(Default)]
pub struct Signer {
private_key: Option<String>,
password: Option<String>,
file: Option<PathBuf>,
}
impl Signer {
pub fn new() -> Self {
Default::default()
}
pub fn private_key(mut self, private_key: &str) -> Self {
self.private_key = Some(private_key.to_owned());
self
}
pub fn password(mut self, password: &str) -> Self {
self.password = Some(password.to_owned());
self
}
pub fn empty_password(mut self) -> Self {
self.password = Some("".to_owned());
self
}
pub fn file_to_sign(mut self, file_path: &str) -> Self {
self.file = Some(Path::new(file_path).to_path_buf());
self
}
pub fn private_key_path(mut self, private_key: &str) -> Self {
self.private_key =
Some(read_key_from_file(Path::new(private_key)).expect("Unable to extract private key"));
self
}
pub fn run(self) -> crate::Result<()> {
if self.private_key.is_none() {
return Err(anyhow::anyhow!(
"Key generation aborted: Unable to find the private key".to_string(),
));
}
if self.password.is_none() {
return Err(anyhow::anyhow!(
"Please use --no-password to set empty password or add --password <password> if your private key have a password.".to_string(),
));
}
let (manifest_dir, signature) = sign_file(
self.private_key.unwrap(),
self.password.unwrap(),
self.file.unwrap(),
)
.with_context(|| "failed to sign file")?;
println!(
"\nYour file was signed successfully, You can find the signature here:\n{}\n\nPublic signature:\n{}\n\nMake sure to include this into the signature field of your update server.",
manifest_dir.display(),
signature
);
Ok(())
}
}
#[derive(Default)]
pub struct KeyGenerator {
password: Option<String>,
output_path: Option<PathBuf>,
force: bool,
}
impl KeyGenerator {
pub fn new() -> Self {
Default::default()
}
pub fn empty_password(mut self) -> Self {
self.password = Some("".to_owned());
self
}
pub fn force(mut self) -> Self {
self.force = true;
self
}
pub fn password(mut self, password: &str) -> Self {
self.password = Some(password.to_owned());
self
}
pub fn output_path(mut self, output_path: &str) -> Self {
self.output_path = Some(Path::new(output_path).to_path_buf());
self
}
pub fn generate_keys(self) -> crate::Result<()> {
let keypair = generate_key(self.password).expect("Failed to generate key");
if let Some(output_path) = self.output_path {
let (secret_path, public_path) =
save_keypair(self.force, output_path, &keypair.sk, &keypair.pk)
.expect("Unable to write keypair");
println!(
"\nYour keypair was generated successfully\nPrivate: {} (Keep it secret!)\nPublic: {}\n---------------------------",
secret_path.display(),
public_path.display()
)
} else {
println!(
"\nYour secret key was generated successfully - Keep it secret!\n{}\n\n",
keypair.sk
);
println!(
"Your public key was generated successfully:\n{}\n\nAdd the public key in your tauri.conf.json\n---------------------------\n",
keypair.pk
);
}
println!("\nEnvironment variabled used to sign:\n`TAURI_PRIVATE_KEY` Path or String of your private key\n`TAURI_KEY_PASSWORD` Your private key password (optional)\n\nATTENTION: If you lose your private key OR password, you'll not be able to sign your update package and updates will not works.\n---------------------------\n");
Ok(())
}
}

View File

@ -0,0 +1,31 @@
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use crate::Result;
use clap::{AppSettings, Parser, Subcommand};
mod generate;
mod sign;
#[derive(Parser)]
#[clap(author, version, about = "Tauri updater signer")]
#[clap(setting(AppSettings::SubcommandRequiredElseHelp))]
pub struct Cli {
#[clap(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
Sign(sign::Options),
Generate(generate::Options),
}
pub fn command(cli: Cli) -> Result<()> {
match cli.command {
Commands::Sign(options) => sign::command(options)?,
Commands::Generate(options) => generate::command(options)?,
}
Ok(())
}

View File

@ -0,0 +1,56 @@
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use crate::{
helpers::updater_signature::{generate_key, save_keypair},
Result,
};
use clap::Parser;
use std::path::PathBuf;
#[derive(Debug, Parser)]
#[clap(about = "Generate keypair to sign files")]
pub struct Options {
/// Set private key password when signing
#[clap(short, long)]
password: Option<String>,
/// Write private key to a file
#[clap(short, long)]
write_keys: Option<PathBuf>,
/// Overwrite private key even if it exists on the specified path
#[clap(short, long)]
force: bool,
}
pub fn command(options: Options) -> Result<()> {
if options.password.is_none() {
println!("Generating new private key without password.")
}
let keypair = generate_key(options.password).expect("Failed to generate key");
if let Some(output_path) = options.write_keys {
let (secret_path, public_path) =
save_keypair(options.force, output_path, &keypair.sk, &keypair.pk)
.expect("Unable to write keypair");
println!(
"\nYour keypair was generated successfully\nPrivate: {} (Keep it secret!)\nPublic: {}\n---------------------------",
secret_path.display(),
public_path.display()
)
} else {
println!(
"\nYour secret key was generated successfully - Keep it secret!\n{}\n\n",
keypair.sk
);
println!(
"Your public key was generated successfully:\n{}\n\nAdd the public key in your tauri.conf.json\n---------------------------\n",
keypair.pk
);
}
println!("\nEnvironment variabled used to sign:\n`TAURI_PRIVATE_KEY` Path or String of your private key\n`TAURI_KEY_PASSWORD` Your private key password (optional)\n\nATTENTION: If you lose your private key OR password, you'll not be able to sign your update package and updates will not works.\n---------------------------\n");
Ok(())
}

View File

@ -0,0 +1,61 @@
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::path::{Path, PathBuf};
use crate::{
helpers::updater_signature::{read_key_from_file, sign_file},
Result,
};
use anyhow::Context;
use clap::Parser;
#[derive(Debug, Parser)]
#[clap(about = "Sign a file")]
pub struct Options {
/// Load the private key from a file
#[clap(short = 'k', long, conflicts_with("private_key_path"))]
private_key: Option<String>,
/// Load the private key from a string
#[clap(short = 'f', long, conflicts_with("private_key"))]
private_key_path: Option<PathBuf>,
/// Set private key password when signing
#[clap(short, long)]
password: Option<String>,
/// Sign the specified file
#[clap(short, long)]
file: Option<PathBuf>,
}
pub fn command(mut options: Options) -> Result<()> {
options.private_key = if let Some(private_key) = options.private_key_path {
Some(read_key_from_file(Path::new(&private_key)).expect("Unable to extract private key"))
} else {
options.private_key
};
if options.private_key.is_none() {
return Err(anyhow::anyhow!(
"Key generation aborted: Unable to find the private key".to_string(),
));
}
if options.password.is_none() {
println!("Signing without password.");
}
let (manifest_dir, signature) = sign_file(
options.private_key.unwrap(),
options.password.unwrap(),
options.file.unwrap(),
)
.with_context(|| "failed to sign file")?;
println!(
"\nYour file was signed successfully, You can find the signature here:\n{}\n\nPublic signature:\n{}\n\nMake sure to include this into the signature field of your update server.",
manifest_dir.display(),
signature
);
Ok(())
}