From 1458ab3c535637ada996ab0ff3494cd75fe40bf7 Mon Sep 17 00:00:00 2001 From: Jonas Kruckenberg Date: Thu, 9 Dec 2021 16:21:33 +0100 Subject: [PATCH] refactor(cli.rs): `signer` and `plugin` subcommands, use new clap derive syntax (#2928) Co-authored-by: Lucas Nogueira --- .changes/cli.rs-refactor-signer.md | 5 + core/tauri/src/updater/mod.rs | 4 +- docs/usage/guides/updater.md | 4 +- tooling/bundler/src/bundle/windows/msi/wix.rs | 2 +- tooling/cli.rs/Cargo.lock | 268 +++---- tooling/cli.rs/Cargo.toml | 2 +- tooling/cli.rs/src/build.rs | 449 ++++++------ tooling/cli.rs/src/cli.yml | 206 ------ tooling/cli.rs/src/dev.rs | 485 ++++++------- tooling/cli.rs/src/info.rs | 678 ++++++++---------- tooling/cli.rs/src/init.rs | 287 +++++--- tooling/cli.rs/src/interface/rust.rs | 12 +- tooling/cli.rs/src/main.rs | 355 ++------- tooling/cli.rs/src/plugin.rs | 170 +---- tooling/cli.rs/src/plugin/init.rs | 149 ++++ tooling/cli.rs/src/sign.rs | 140 ---- tooling/cli.rs/src/signer.rs | 31 + tooling/cli.rs/src/signer/generate.rs | 56 ++ tooling/cli.rs/src/signer/sign.rs | 61 ++ 19 files changed, 1438 insertions(+), 1926 deletions(-) create mode 100644 .changes/cli.rs-refactor-signer.md mode change 100755 => 100644 tooling/cli.rs/Cargo.lock delete mode 100644 tooling/cli.rs/src/cli.yml create mode 100644 tooling/cli.rs/src/plugin/init.rs delete mode 100644 tooling/cli.rs/src/sign.rs create mode 100644 tooling/cli.rs/src/signer.rs create mode 100644 tooling/cli.rs/src/signer/generate.rs create mode 100644 tooling/cli.rs/src/signer/sign.rs diff --git a/.changes/cli.rs-refactor-signer.md b/.changes/cli.rs-refactor-signer.md new file mode 100644 index 000000000..fbafd3be5 --- /dev/null +++ b/.changes/cli.rs-refactor-signer.md @@ -0,0 +1,5 @@ +--- +"cli.rs": patch +--- + +The `generate` and `sign` commands are now available under a `signer` subcommand. diff --git a/core/tauri/src/updater/mod.rs b/core/tauri/src/updater/mod.rs index 215aac58c..c31df31ad 100644 --- a/core/tauri/src/updater/mod.rs +++ b/core/tauri/src/updater/mod.rs @@ -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 diff --git a/docs/usage/guides/updater.md b/docs/usage/guides/updater.md index bbd29cf85..3088051f4 100644 --- a/docs/usage/guides/updater.md +++ b/docs/usage/guides/updater.md @@ -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 diff --git a/tooling/bundler/src/bundle/windows/msi/wix.rs b/tooling/bundler/src/bundle/windows/msi/wix.rs index 5c8f0d685..d34c0ad71 100644 --- a/tooling/bundler/src/bundle/windows/msi/wix.rs +++ b/tooling/bundler/src/bundle/windows/msi/wix.rs @@ -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() diff --git a/tooling/cli.rs/Cargo.lock b/tooling/cli.rs/Cargo.lock old mode 100755 new mode 100644 index 617ca9b4a..58895dcd5 --- a/tooling/cli.rs/Cargo.lock +++ b/tooling/cli.rs/Cargo.lock @@ -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" diff --git a/tooling/cli.rs/Cargo.toml b/tooling/cli.rs/Cargo.toml index 21ee050d6..8f02a1a35 100644 --- a/tooling/cli.rs/Cargo.toml +++ b/tooling/cli.rs/Cargo.toml @@ -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" diff --git a/tooling/cli.rs/src/build.rs b/tooling/cli.rs/src/build.rs index 0fe0d3a9c..11570c5d3 100644 --- a/tooling/cli.rs/src/build.rs +++ b/tooling/cli.rs/src/build.rs @@ -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, + /// 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, + /// List of cargo features to activate + #[clap(short, long)] features: Option>, + /// List of bundles to package + #[clap(short, long)] bundles: Option>, + /// JSON string or path to JSON file to merge with tauri.conf.json + #[clap(short, long)] config: Option, } -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) -> Self { - self.features.replace(features); - self - } - - pub fn bundles(mut self, bundles: Vec) -> 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<()> { diff --git a/tooling/cli.rs/src/cli.yml b/tooling/cli.rs/src/cli.yml deleted file mode 100644 index dc262687f..000000000 --- a/tooling/cli.rs/src/cli.yml +++ /dev/null @@ -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 /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 diff --git a/tooling/cli.rs/src/dev.rs b/tooling/cli.rs/src/dev.rs index 7c2475fb6..099a61120 100644 --- a/tooling/cli.rs/src/dev.rs +++ b/tooling/cli.rs/src/dev.rs @@ -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> = 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, + /// Target triple to build against + #[clap(short, long)] + target: Option, + /// List of cargo features to activate + #[clap(short, long)] + features: Option>, + /// 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, + /// Run the code in release mode + #[clap(short, long)] + release_mode: bool, + /// Args passed to the binary + args: Vec, +} + +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, - target: Option, - features: Option>, - exit_on_panic: bool, - config: Option, - args: Vec, - release_mode: bool, -} +fn start_app( + options: &Options, + runner: &str, + features: &[String], + child_wait_rx: Arc>>, +) -> Arc { + 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) -> 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) -> 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>>, - ) -> Arc { - 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 } diff --git a/tooling/cli.rs/src/info.rs b/tooling/cli.rs/src/info.rs index a2074f6c9..907359b21 100644 --- a/tooling/cli.rs/src/info.rs +++ b/tooling/cli.rs/src/info.rs @@ -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 { let url = format!("https://docs.rs/crate/{}/", name); @@ -269,19 +272,19 @@ fn get_version(command: &str, args: &[&str]) -> crate::Result> { #[cfg(windows)] fn webview2_version() -> crate::Result> { 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> { 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::>(); + 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::(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::(), ) + .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::>(); - 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::(include_str!("../metadata.json"))?; - VersionBlock::new( - " Node.js", - node_version.chars().skip(1).collect::(), - ) - .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 = - if let Ok(manifest_contents) = read_to_string(tauri_dir.join("Cargo.toml")) { - toml::from_str(&manifest_contents).ok() - } else { - None - }; - let lock: Option = - 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 = 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::(&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::>() - .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 = + if let Ok(manifest_contents) = read_to_string(tauri_dir.join("Cargo.toml")) { + toml::from_str(&manifest_contents).ok() + } else { + None + }; + let lock: Option = + 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 = 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::(&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::>() + .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>(file_names: &[T]) -> crate::Result { @@ -771,9 +792,9 @@ fn get_package_manager>(file_names: &[T]) -> crate::Result 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>(file_names: &[T]) -> crate::Result 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), - } - } -} diff --git a/tooling/cli.rs/src/init.rs b/tooling/cli.rs/src/init.rs index f31a6cf51..5700f937e 100644 --- a/tooling/cli.rs/src/init.rs +++ b/tooling/cli.rs/src/init.rs @@ -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, + /// Name of your Tauri application + #[clap(short = 'A', long)] app_name: Option, + /// Window title of your Tauri application + #[clap(short = 'W', long)] window_title: Option, + /// Web assets location, relative to /src-tauri + #[clap(short = 'D', long)] dist_dir: Option, + /// Url of your dev server + #[clap(short = 'P', long)] dev_path: Option, } -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, + product_name: Option, } -impl Init { - pub fn new() -> Self { - Default::default() - } +#[derive(Default)] +struct InitDefaults { + app_name: Option, + framework: Option, +} - pub fn force(mut self) -> Self { - self.force = true; - self - } +impl Options { + fn load(mut self) -> Result { + 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) -> Self { - self.directory = directory.into(); - self - } - - pub fn tauri_path(mut self, tauri_path: impl Into) -> Self { - self.tauri_path = Some(tauri_path.into()); - self - } - - pub fn app_name(mut self, app_name: impl Into) -> Self { - self.app_name = Some(app_name.into()); - self - } - - pub fn window_title(mut self, window_title: impl Into) -> Self { - self.window_title = Some(window_title.into()); - self - } - - pub fn dist_dir(mut self, dist_dir: impl Into) -> Self { - self.dist_dir = Some(dist_dir.into()); - self - } - - pub fn dev_path(mut self, dev_path: impl Into) -> 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::(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 "/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::(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(prompt: &str, default: Option, skip: bool) -> Result> +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) } } diff --git a/tooling/cli.rs/src/interface/rust.rs b/tooling/cli.rs/src/interface/rust.rs index 0d1edebe8..bc946d60c 100644 --- a/tooling/cli.rs/src/interface/rust.rs +++ b/tooling/cli.rs/src/interface/rust.rs @@ -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 + )); } } } diff --git a/tooling/cli.rs/src/main.rs b/tooling/cli.rs/src/main.rs index 1b847ef5e..127a01efd 100644 --- a/tooling/cli.rs/src/main.rs +++ b/tooling/cli.rs/src/main.rs @@ -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, - product_name: Option, +#[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, - framework: Option, +#[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::::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 { - 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 "/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 = 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 = 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 = 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(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 = ::into_app() + .arg(clap::Arg::new("cargo").hide(true).possible_value("tauri")) + .get_matches(); + let res = ::from_arg_matches(&matches).map_err(format_error::); + 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(()) } diff --git a/tooling/cli.rs/src/plugin.rs b/tooling/cli.rs/src/plugin.rs index b71e0229a..dcb3c23fb 100644 --- a/tooling/cli.rs/src/plugin.rs +++ b/tooling/cli.rs/src/plugin.rs @@ -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, - 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) -> Self { - self.directory = directory.into(); - self - } - - pub fn tauri_path(mut self, tauri_path: impl Into) -> 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::(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(()) } diff --git a/tooling/cli.rs/src/plugin/init.rs b/tooling/cli.rs/src/plugin/init.rs new file mode 100644 index 000000000..8243b98eb --- /dev/null +++ b/tooling/cli.rs/src/plugin/init.rs @@ -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, + /// Author name + #[clap(short, long)] + author: Option, +} + +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::(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(()) +} diff --git a/tooling/cli.rs/src/sign.rs b/tooling/cli.rs/src/sign.rs deleted file mode 100644 index 1f322ca6a..000000000 --- a/tooling/cli.rs/src/sign.rs +++ /dev/null @@ -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, - password: Option, - file: Option, -} - -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 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, - output_path: Option, - 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(()) - } -} diff --git a/tooling/cli.rs/src/signer.rs b/tooling/cli.rs/src/signer.rs new file mode 100644 index 000000000..78c212e1d --- /dev/null +++ b/tooling/cli.rs/src/signer.rs @@ -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(()) +} diff --git a/tooling/cli.rs/src/signer/generate.rs b/tooling/cli.rs/src/signer/generate.rs new file mode 100644 index 000000000..8ccaa94ab --- /dev/null +++ b/tooling/cli.rs/src/signer/generate.rs @@ -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, + /// Write private key to a file + #[clap(short, long)] + write_keys: Option, + /// 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(()) +} diff --git a/tooling/cli.rs/src/signer/sign.rs b/tooling/cli.rs/src/signer/sign.rs new file mode 100644 index 000000000..99c21b7b3 --- /dev/null +++ b/tooling/cli.rs/src/signer/sign.rs @@ -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, + /// Load the private key from a string + #[clap(short = 'f', long, conflicts_with("private_key"))] + private_key_path: Option, + /// Set private key password when signing + #[clap(short, long)] + password: Option, + /// Sign the specified file + #[clap(short, long)] + file: Option, +} + +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(()) +}