diff --git a/Cargo.lock b/Cargo.lock index 58f63d68a0f..b266eb81fba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -46,6 +46,12 @@ dependencies = [ "libc", ] +[[package]] +name = "ansi_term" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30275ad0ad84ec1c06dde3b3f7d23c6006b7d76d61a85e7060b426b747eff70d" + [[package]] name = "anyhow" version = "1.0.65" @@ -54,9 +60,9 @@ checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602" [[package]] name = "arbitrary" -version = "1.1.6" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f44124848854b941eafdb34f05b3bcf59472f643c7e151eba7c2b69daa469ed5" +checksum = "d86fd10d912cab78764cc44307d9cd5f164e09abbeb87fb19fb6d95937e8da5f" dependencies = [ "derive_arbitrary", ] @@ -73,6 +79,12 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" +[[package]] +name = "ascii" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eab1c04a571841102f5345a8fc0f6bb3d31c315dec879b5c6e42e40ce7ffa34e" + [[package]] name = "assert_cmd" version = "1.0.8" @@ -258,7 +270,7 @@ version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6358dedf60f4d9b8db43ad187391afe959746101346fe51bb978126bec61dfb" dependencies = [ - "clap 3.2.21", + "clap 3.2.22", "heck 0.4.0", "indexmap", "log", @@ -319,9 +331,9 @@ dependencies = [ [[package]] name = "clap" -version = "3.2.21" +version = "3.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ed5341b2301a26ab80be5cbdced622e80ed808483c52e45e3310a877d3b37d7" +checksum = "86447ad904c7fb335a790c9d7fe3d0d971dc523b8ccd1561a520de9a85302750" dependencies = [ "atty", "bitflags", @@ -331,7 +343,7 @@ dependencies = [ "once_cell", "strsim", "termcolor", - "textwrap 0.15.0", + "textwrap 0.15.1", ] [[package]] @@ -387,6 +399,19 @@ dependencies = [ "winapi", ] +[[package]] +name = "combine" +version = "3.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3da6baa321ec19e1cc41d31bf599f00c783d0517095cdaf0332e3fe8d20680" +dependencies = [ + "ascii", + "byteorder", + "either", + "memchr", + "unreachable", +] + [[package]] name = "compiler-test-derive" version = "0.0.1" @@ -448,6 +473,16 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb4a24b1aaf0fd0ce8b45161144d6f42cd91677fd5940fd431183eb023b3a2b8" +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.3" @@ -603,26 +638,24 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "045ebe27666471bb549370b4b0b3e51b07f56325befa4284db65fc89c02511b1" +checksum = "f916dfc5d356b0ed9dae65f1db9fc9770aa2851d2662b988ccf4fe3516e86348" dependencies = [ "autocfg", "cfg-if 1.0.0", "crossbeam-utils", "memoffset", - "once_cell", "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" +checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac" dependencies = [ "cfg-if 1.0.0", - "once_cell", ] [[package]] @@ -675,9 +708,9 @@ checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" [[package]] name = "darling" -version = "0.13.4" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +checksum = "4529658bdda7fd6769b8614be250cdcfc3aeb0ee72fe66f9e41e5e5eb73eac02" dependencies = [ "darling_core", "darling_macro", @@ -685,9 +718,9 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.13.4" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +checksum = "649c91bc01e8b1eac09fb91e8dbc7d517684ca6be8ebc75bb9cafc894f9fdb6f" dependencies = [ "fnv", "ident_case", @@ -698,9 +731,9 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.13.4" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +checksum = "ddfc69c5bfcbd2fc09a0f38451d2daf0e372e367986a83906d1b0dbc88134fb5" dependencies = [ "darling_core", "quote", @@ -743,15 +776,25 @@ checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" [[package]] name = "digest" -version = "0.10.3" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" dependencies = [ "block-buffer", "crypto-common", "subtle", ] +[[package]] +name = "dirs" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" +dependencies = [ + "cfg-if 0.1.10", + "dirs-sys", +] + [[package]] name = "dirs" version = "4.0.0" @@ -858,6 +901,21 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + +[[package]] +name = "encoding_rs" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +dependencies = [ + "cfg-if 1.0.0", +] + [[package]] name = "enum-iterator" version = "0.7.0" @@ -880,18 +938,18 @@ dependencies = [ [[package]] name = "enumset" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4799cdb24d48f1f8a7a98d06b7fde65a85a2d1e42b25a889f5406aa1fbefe074" +checksum = "19be8061a06ab6f3a6cf21106c873578bf01bd42ad15e0311a9c76161cb1c753" dependencies = [ "enumset_derive", ] [[package]] name = "enumset_derive" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea83a3fbdc1d999ccfbcbee717eab36f8edf2d71693a23ce0d7cca19e085304c" +checksum = "03e7b551eba279bf0fa88b83a46330168c1560a52a94f5126f892f0b364ab3e0" dependencies = [ "darling", "proc-macro2", @@ -955,6 +1013,16 @@ dependencies = [ "windows-sys 0.36.1", ] +[[package]] +name = "flate2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "float-cmp" version = "0.9.0" @@ -970,6 +1038,30 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +dependencies = [ + "percent-encoding", +] + [[package]] name = "futures" version = "0.3.24" @@ -1138,6 +1230,64 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +[[package]] +name = "graphql-introspection-query" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f2a4732cf5140bd6c082434494f785a19cfb566ab07d1382c3671f5812fed6d" +dependencies = [ + "serde", +] + +[[package]] +name = "graphql-parser" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ebc8013b4426d5b81a4364c419a95ed0b404af2b82e2457de52d9348f0e474" +dependencies = [ + "combine", + "thiserror", +] + +[[package]] +name = "graphql_client" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fc16d75d169fddb720d8f1c7aed6413e329e1584079b9734ff07266a193f5bc" +dependencies = [ + "graphql_query_derive", + "serde", + "serde_json", +] + +[[package]] +name = "graphql_client_codegen" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f290ecfa3bea3e8a157899dc8a1d96ee7dd6405c18c8ddd213fc58939d18a0e9" +dependencies = [ + "graphql-introspection-query", + "graphql-parser", + "heck 0.4.0", + "lazy_static", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn", +] + +[[package]] +name = "graphql_query_derive" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a755cc59cda2641ea3037b4f9f7ef40471c329f55c1fa2db6fa0bb7ae6c1f7ce" +dependencies = [ + "graphql_client_codegen", + "proc-macro2", + "syn", +] + [[package]] name = "gumdrop" version = "0.8.1" @@ -1158,6 +1308,25 @@ dependencies = [ "syn", ] +[[package]] +name = "h2" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca32592cf21ac7ccab1825cd87f6c9b3d9022c44d086172ed0966bec8af30be" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "half" version = "1.8.2" @@ -1212,6 +1381,28 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "http" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +dependencies = [ + "bytes", + "fnv", + "itoa 1.0.4", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + [[package]] name = "http_req" version = "0.8.1" @@ -1224,16 +1415,64 @@ dependencies = [ "webpki-roots", ] +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "hyper" +version = "0.14.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa 1.0.4", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "iana-time-zone" -version = "0.1.48" +version = "0.1.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "237a0714f28b1ee39ccec0770ccb544eb02c9ef2c82bb096230eefcffa6468b0" +checksum = "fd911b35d940d2bd0bea0f9100068e5b97b51a1cbe13d13382f132e0365257a0" dependencies = [ "android_system_properties", "core-foundation-sys", "js-sys", - "once_cell", "wasm-bindgen", "winapi", ] @@ -1250,6 +1489,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "indexmap" version = "1.9.1" @@ -1337,11 +1586,17 @@ dependencies = [ "ghost", ] +[[package]] +name = "ipnet" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" + [[package]] name = "itertools" -version = "0.10.4" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8bf247779e67a9082a4790b45e71ac7cfd1321331a5c856a74a9faebdab78d0" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] @@ -1354,15 +1609,15 @@ checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] name = "itoa" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" +checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" [[package]] name = "jobserver" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" +checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b" dependencies = [ "libc", ] @@ -1390,9 +1645,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.132" +version = "0.2.134" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" +checksum = "329c933548736bc49fd575ee68c89e8be4d260064184389a5b77517cddd99ffb" [[package]] name = "libfuzzer-sys" @@ -1415,11 +1670,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "llvm-sys" -version = "120.2.4" +version = "120.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b716322964966a62377cf86e64f00ca7043505fdf27bd2ec7d41ae6682d1e7" +checksum = "a1c9655eec036faf512507746ce70765bda72ed98e52b4328f0d7b93e970c6d8" dependencies = [ "cc", "lazy_static", @@ -1430,9 +1691,9 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f80bf5aacaf25cbfc8210d1cfb718f2bf3b11c4c54e5afe36c236853a8ec390" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" dependencies = [ "autocfg", "scopeguard", @@ -1499,6 +1760,22 @@ dependencies = [ "autocfg", ] +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "minifb" version = "0.23.0" @@ -1540,6 +1817,18 @@ dependencies = [ "adler", ] +[[package]] +name = "mio" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.36.1", +] + [[package]] name = "miow" version = "0.3.7" @@ -1555,6 +1844,24 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" +[[package]] +name = "native-tls" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "nix" version = "0.24.2" @@ -1649,9 +1956,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0" +checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" [[package]] name = "oorandom" @@ -1659,11 +1966,56 @@ version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +[[package]] +name = "openssl" +version = "0.10.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12fc0523e3bd51a692c8850d075d74dc062ccf251c0110668cbd921917118a13" +dependencies = [ + "bitflags", + "cfg-if 1.0.0", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5230151e44c0f05157effb743e8d517472843121cf9243e8b81393edb5acd9ce" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "orbclient" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d3aa1482d3a9cb7547932f54a20910090073e81b3b7b236277c91698a10f83e" +checksum = "38f2f4f4738333cd181e9b52431ee415fa6d3646f0fe5c9572852218ec2cfe18" dependencies = [ "libc", "raw-window-handle 0.3.4", @@ -1720,11 +2072,17 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + [[package]] name = "pest" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb779fcf4bb850fbbb0edc96ff6cf34fd90c4b1a112ce042653280d9a7364048" +checksum = "dbc7bc69c062e492337d74d59b120c274fd3d261b6bf6d3207d499b4b379c41a" dependencies = [ "thiserror", "ucd-trie", @@ -1824,6 +2182,20 @@ dependencies = [ "yansi", ] +[[package]] +name = "prettytable-rs" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f375cb74c23b51d23937ffdeb48b1fbf5b6409d4b9979c1418c1de58bc8f801" +dependencies = [ + "atty", + "csv", + "encode_unicode", + "lazy_static", + "term 0.7.0", + "unicode-width", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -1856,9 +2228,9 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" [[package]] name = "proc-macro2" -version = "1.0.43" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" +checksum = "94e2ef8dbfc347b10c094890f778ee2e36ca9bb4262e86dc99cd217e35f3470b" dependencies = [ "unicode-ident", ] @@ -2094,6 +2466,44 @@ dependencies = [ "bytecheck", ] +[[package]] +name = "reqwest" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "431949c384f4e2ae07605ccaa56d1d9d2ecdb5cadd4f9577ccfab29f2e5149fc" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "mime_guess", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + [[package]] name = "ring" version = "0.16.20" @@ -2214,6 +2624,16 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" +dependencies = [ + "lazy_static", + "windows-sys 0.36.1", +] + [[package]] name = "scoped-tls" version = "1.0.0" @@ -2267,6 +2687,29 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" +[[package]] +name = "security-framework" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23a2ac85147a3a11d77ecf1bc7166ec0b92febfa4461c37944e180f319ece467" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "0.9.0" @@ -2290,6 +2733,9 @@ name = "semver" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" +dependencies = [ + "serde", +] [[package]] name = "semver-parser" @@ -2308,9 +2754,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.144" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860" +checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b" dependencies = [ "serde_derive", ] @@ -2347,9 +2793,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.144" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00" +checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c" dependencies = [ "proc-macro2", "quote", @@ -2362,11 +2808,35 @@ version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" dependencies = [ - "itoa 1.0.3", + "itoa 1.0.4", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa 1.0.4", "ryu", "serde", ] +[[package]] +name = "serde_yaml" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b" +dependencies = [ + "indexmap", + "ryu", + "serde", + "yaml-rust", +] + [[package]] name = "serial_test" version = "0.5.1" @@ -2430,9 +2900,19 @@ checksum = "03b634d87b960ab1a38c4fe143b508576f075e7c978bfad18217645ebfdfa2ec" [[package]] name = "smallvec" -version = "1.9.0" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "socket2" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +dependencies = [ + "libc", + "winapi", +] [[package]] name = "spin" @@ -2440,6 +2920,16 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "spinner" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e3a7cd01625b7e43e62815677d692cb59b221c2fdc2853d1eb86a260ee0c272" +dependencies = [ + "ansi_term", + "term 0.6.1", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -2518,15 +3008,26 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.99" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" +checksum = "3fcd952facd492f9be3ef0d0b7032a6e442ee9b361d4acc2b1d0c4aaa5f613a1" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "tar" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6" +dependencies = [ + "filetime", + "libc", + "xattr", +] + [[package]] name = "target-lexicon" version = "0.11.2" @@ -2553,6 +3054,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "term" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0863a3345e70f61d613eab32ee046ccd1bcc5f9105fe402c61fcd0c13eeb8b5" +dependencies = [ + "dirs 2.0.2", + "winapi", +] + [[package]] name = "term" version = "0.7.0" @@ -2608,7 +3119,7 @@ dependencies = [ "getopts", "libc", "num_cpus", - "term", + "term 0.7.0", ] [[package]] @@ -2622,24 +3133,24 @@ dependencies = [ [[package]] name = "textwrap" -version = "0.15.0" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" +checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16" [[package]] name = "thiserror" -version = "1.0.35" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c53f98874615aea268107765aa1ed8f6116782501d18e53d08b471733bea6c85" +checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.35" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8b463991b4eab2d801e724172285ec4195c650e8ec79b149e6c2a8e6dd3f783" +checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" dependencies = [ "proc-macro2", "quote", @@ -2718,6 +3229,47 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +[[package]] +name = "tokio" +version = "1.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" +dependencies = [ + "autocfg", + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "pin-project-lite", + "socket2", + "winapi", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + [[package]] name = "toml" version = "0.5.9" @@ -2727,11 +3279,17 @@ dependencies = [ "serde", ] +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + [[package]] name = "tracing" -version = "0.1.36" +version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if 1.0.0", "log", @@ -2742,9 +3300,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" dependencies = [ "proc-macro2", "quote", @@ -2753,18 +3311,18 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.29" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" dependencies = [ "once_cell", ] [[package]] name = "tracing-subscriber" -version = "0.3.15" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60db860322da191b40952ad9affe65ea23e7dd6a5c442c2c42865810c6ab8e6b" +checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" dependencies = [ "matchers", "once_cell", @@ -2786,11 +3344,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + [[package]] name = "trybuild" -version = "1.0.64" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7f408301c7480f9e6294eb779cfc907f54bd901a9660ef24d7f233ed5376485" +checksum = "9004c888f43705ebcb34f01d8c6f76616e3cf253cdb356e5de3bb0a7aa11aa5c" dependencies = [ "glob", "once_cell", @@ -2846,6 +3410,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "unicode-bidi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + [[package]] name = "unicode-ident" version = "1.0.4" @@ -2885,12 +3455,38 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35abed4630bb800f02451a7428205d1f37b8e125001471bfab259beee6a587ed" +[[package]] +name = "unreachable" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" +dependencies = [ + "void", +] + [[package]] name = "untrusted" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +[[package]] +name = "url" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version-compare" version = "0.1.0" @@ -2903,6 +3499,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + [[package]] name = "wait-timeout" version = "0.2.0" @@ -2923,6 +3525,33 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "wapm-toml" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de1aa82bb6aa2eb2f904cba634e93294436fa28746f3c72f1a24f827f9d4413" +dependencies = [ + "anyhow", + "semver 1.0.14", + "serde", + "serde_cbor", + "serde_derive", + "serde_json", + "serde_yaml", + "thiserror", + "toml", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -3050,9 +3679,9 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.16.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d443c5a7daae71697d97ec12ad70b4fe8766d3a0f4db16158ac8b781365892f7" +checksum = "c64ac98d5d61192cc45c701b7e4bd0b9aff91e2edfc7a088406cfe2288581e2c" dependencies = [ "leb128", ] @@ -3185,17 +3814,25 @@ dependencies = [ "atty", "bytesize", "cfg-if 1.0.0", - "clap 3.2.21", + "chrono", + "clap 3.2.22", "colored 2.0.0", - "dirs", + "dirs 4.0.0", "distance", "fern", "http_req", "log", + "prettytable-rs", + "regex", "serde_json", + "spinner", "target-lexicon 0.12.4", "tempfile", + "toml", "unix_mode", + "url", + "walkdir", + "wapm-toml", "wasmer", "wasmer-cache", "wasmer-compiler", @@ -3204,6 +3841,7 @@ dependencies = [ "wasmer-compiler-singlepass", "wasmer-emscripten", "wasmer-object", + "wasmer-registry", "wasmer-types", "wasmer-vfs", "wasmer-vm", @@ -3246,7 +3884,7 @@ dependencies = [ "atty", "bytesize", "cfg-if 1.0.0", - "clap 3.2.21", + "clap 3.2.22", "colored 2.0.0", "distance", "fern", @@ -3404,6 +4042,26 @@ dependencies = [ "wasmer-types", ] +[[package]] +name = "wasmer-registry" +version = "3.0.0-beta.2" +dependencies = [ + "anyhow", + "dirs 4.0.0", + "flate2", + "graphql_client", + "reqwest", + "semver 1.0.14", + "serde", + "serde_json", + "tar", + "thiserror", + "toml", + "url", + "wapm-toml", + "whoami", +] + [[package]] name = "wasmer-types" version = "3.0.0-beta.2" @@ -3661,21 +4319,21 @@ checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a" [[package]] name = "wasmparser" -version = "0.89.1" +version = "0.92.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5d3e08b13876f96dd55608d03cd4883a0545884932d5adf11925876c96daef" +checksum = "7da34cec2a8c23db906cdf8b26e988d7a7f0d549eb5d51299129647af61a1b37" dependencies = [ "indexmap", ] [[package]] name = "wasmprinter" -version = "0.2.39" +version = "0.2.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa9e5ee2f56cc8a5da489558114e8c118e5a8416d96aefe63dcf1b5b05b858c6" +checksum = "ca4374ec27194a12b85aa0e1681a42d5800e97f11c036fa85dea8087c8ccb10b" dependencies = [ "anyhow", - "wasmparser 0.89.1", + "wasmparser 0.92.0", ] [[package]] @@ -3698,23 +4356,23 @@ dependencies = [ [[package]] name = "wast" -version = "46.0.0" +version = "47.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea0ab19660e3ea6891bba69167b9be40fad00fb1fe3dd39c5eebcee15607131b" +checksum = "02b98502f3978adea49551e801a6687678e6015317d7d9470a67fe813393f2a8" dependencies = [ "leb128", "memchr", "unicode-width", - "wasm-encoder 0.16.0", + "wasm-encoder 0.18.0", ] [[package]] name = "wat" -version = "1.0.48" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f775282def4d5bffd94d60d6ecd57bfe6faa46171cdbf8d32bd5458842b1e3e" +checksum = "7aab4e20c60429fbba9670a6cae0fff9520046ba0aa3e6d0b1cd2653bea14898" dependencies = [ - "wast 46.0.0", + "wast 47.0.1", ] [[package]] @@ -3819,6 +4477,17 @@ dependencies = [ "webpki", ] +[[package]] +name = "whoami" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6631b6a2fd59b1841b622e8f1a7ad241ef0a46f2d580464ce8140ac94cbd571" +dependencies = [ + "bumpalo", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "winapi" version = "0.3.9" @@ -3936,6 +4605,15 @@ version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + [[package]] name = "x11-dl" version = "2.20.0" @@ -3947,6 +4625,15 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "xattr" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc" +dependencies = [ + "libc", +] + [[package]] name = "xcursor" version = "0.3.4" @@ -3962,6 +4649,15 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" +[[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 = "yansi" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index 783aa175f5e..b39c61c750b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,6 +51,7 @@ members = [ "lib/c-api/tests/wasmer-c-api-test-runner", "lib/c-api/examples/wasmer-capi-examples-runner", "lib/types", + "lib/registry", "tests/wasi-wast", "tests/lib/wast", "tests/lib/compiler-test-derive", diff --git a/lib/c-api/examples/assets/qjs-wapm.toml b/lib/c-api/examples/assets/qjs-wapm.toml new file mode 100644 index 00000000000..53751a20d8a --- /dev/null +++ b/lib/c-api/examples/assets/qjs-wapm.toml @@ -0,0 +1,13 @@ +[package] +name = "adamz/qjs" +version = "0.0.1" +description = "https://github.com/bellard/quickjs" +license = "MIT" + +[[command]] +name = "qjs" +module = "qjs" + +[[module]] +name = "qjs" +source = "qjs.wasm" diff --git a/lib/cli/Cargo.toml b/lib/cli/Cargo.toml index f26d91ef2bb..cb5e9e2a0ea 100644 --- a/lib/cli/Cargo.toml +++ b/lib/cli/Cargo.toml @@ -37,12 +37,14 @@ wasmer-wasi-experimental-io-devices = { version = "=3.0.0-beta.2", path = "../wa wasmer-wast = { version = "=3.0.0-beta.2", path = "../../tests/lib/wast", optional = true } wasmer-cache = { version = "=3.0.0-beta.2", path = "../cache", optional = true } wasmer-types = { version = "=3.0.0-beta.2", path = "../types" } +wasmer-registry = { version = "=3.0.0-beta.2", path = "../registry" } wasmer-object = { version = "=3.0.0-beta.2", path = "../object", optional = true } wasmer-vfs = { version = "=3.0.0-beta.2", path = "../vfs", default-features = false, features = ["host-fs"] } atty = "0.2" colored = "2.0" anyhow = "1.0" -clap = { version = "3.1", features = ["derive"] } +spinner = "0.5.0" +clap = { version = "3.2.22", features = ["derive"] } # For the function names autosuggestion distance = "0.4" # For the inspect subcommand @@ -56,6 +58,15 @@ http_req = { version="^0.8", default-features = false, features = ["rust-tls"], dirs = { version = "4.0", optional = true } serde_json = { version = "1.0", optional = true } target-lexicon = { version = "0.12", features = ["std"] } +prettytable-rs = "0.9.0" +wapm-toml = "0.1.2" +walkdir = "2.3.2" +regex = "1.6.0" +toml = "0.5.9" +url = "2.3.1" + +[build-dependencies] +chrono = { version = "^0.4", default-features = false, features = [ "std", "clock" ] } [target.'cfg(target_os = "linux")'.dependencies] unix_mode = "0.1.3" diff --git a/lib/cli/build.rs b/lib/cli/build.rs index 36fe64763ed..f9aecaaf01c 100644 --- a/lib/cli/build.rs +++ b/lib/cli/build.rs @@ -1,4 +1,26 @@ +use chrono::prelude::*; +use std::process::Command; + pub fn main() { - println!("cargo:rerun-if-changed=build.rs"); - println!("cargo:rerun-if-env-changed=WASMER_INSTALL_PREFIX"); + // Set WASMER_GIT_HASH + let git_hash = Command::new("git") + .args(&["rev-parse", "HEAD"]) + .output() + .ok() + .and_then(|output| String::from_utf8(output.stdout).ok()) + .unwrap_or_default(); + println!("cargo:rustc-env=WASMER_BUILD_GIT_HASH={}", git_hash); + + if git_hash.len() > 5 { + println!( + "cargo:rustc-env=WASMER_BUILD_GIT_HASH_SHORT={}", + &git_hash[..5] + ); + } else { + println!("cargo:rustc-env=WASMER_BUILD_GIT_HASH_SHORT=??????"); + } + + let utc: DateTime = Utc::now(); + let date = utc.format("%Y-%m-%d").to_string(); + println!("cargo:rustc-env=WASMER_BUILD_DATE={}", date); } diff --git a/lib/cli/src/cli.rs b/lib/cli/src/cli.rs index 8a5972fd084..b276fc6a74e 100644 --- a/lib/cli/src/cli.rs +++ b/lib/cli/src/cli.rs @@ -10,13 +10,12 @@ use crate::commands::CreateExe; use crate::commands::CreateObj; #[cfg(feature = "wast")] use crate::commands::Wast; -use crate::commands::{Cache, Config, Inspect, Run, SelfUpdate, Validate}; +use crate::commands::{Cache, Config, Inspect, List, Run, SelfUpdate, Validate}; use crate::error::PrettyError; -use anyhow::Result; +use clap::{CommandFactory, ErrorKind, Parser}; +use std::fmt; -use clap::{ErrorKind, Parser}; - -#[derive(Parser)] +#[derive(Parser, Debug)] #[cfg_attr( not(feature = "headless"), clap( @@ -37,6 +36,10 @@ use clap::{ErrorKind, Parser}; )] /// The options for the wasmer Command Line Interface enum WasmerCLIOptions { + /// List all locally installed packages + #[clap(name = "list")] + List(List), + /// Run a WebAssembly file. Formats accepted: wasm, wat #[clap(name = "run")] Run(Run), @@ -146,7 +149,7 @@ enum WasmerCLIOptions { } impl WasmerCLIOptions { - fn execute(&self) -> Result<()> { + fn execute(&self) -> Result<(), anyhow::Error> { match self { Self::Run(options) => options.execute(), Self::SelfUpdate(options) => options.execute(), @@ -160,6 +163,7 @@ impl WasmerCLIOptions { Self::CreateObj(create_obj) => create_obj.execute(), Self::Config(config) => config.execute(), Self::Inspect(inspect) => inspect.execute(), + Self::List(list) => list.execute(), #[cfg(feature = "wast")] Self::Wast(wast) => wast.execute(), #[cfg(target_os = "linux")] @@ -174,6 +178,10 @@ pub fn wasmer_main() { #[cfg(windows)] colored::control::set_virtual_terminal(true).unwrap(); + PrettyError::report(wasmer_main_inner()) +} + +fn wasmer_main_inner() -> Result<(), anyhow::Error> { // We try to run wasmer with the normal arguments. // Eg. `wasmer ` // In case that fails, we fallback trying the Run subcommand directly. @@ -183,13 +191,36 @@ pub fn wasmer_main() { // we assume that we're registered via binfmt_misc let args = std::env::args().collect::>(); let binpath = args.get(0).map(|s| s.as_ref()).unwrap_or(""); + + let firstarg = args.get(1).map(|s| s.as_str()); + let secondarg = args.get(2).map(|s| s.as_str()); + + match (firstarg, secondarg) { + (None, _) | (Some("help"), _) | (Some("--help"), _) => { + return print_help(true); + } + (Some("-h"), _) => { + return print_help(false); + } + (Some("-vV"), _) + | (Some("version"), Some("--verbose")) + | (Some("--version"), Some("--verbose")) => { + return print_version(true); + } + + (Some("-v"), _) | (Some("-V"), _) | (Some("version"), _) | (Some("--version"), _) => { + return print_version(false); + } + _ => {} + } + let command = args.get(1); let options = if cfg!(target_os = "linux") && binpath.ends_with("wasmer-binfmt-interpreter") { WasmerCLIOptions::Run(Run::from_binfmt_args()) } else { match command.unwrap_or(&"".to_string()).as_ref() { "cache" | "compile" | "config" | "create-exe" | "help" | "inspect" | "run" - | "self-update" | "validate" | "wast" | "binfmt" => WasmerCLIOptions::parse(), + | "self-update" | "validate" | "wast" | "binfmt" | "list" => WasmerCLIOptions::parse(), _ => { WasmerCLIOptions::try_parse_from(args.iter()).unwrap_or_else(|e| { match e.kind() { @@ -204,5 +235,243 @@ pub fn wasmer_main() { } }; - PrettyError::report(options.execute()); + // Check if the file is a package name + if let WasmerCLIOptions::Run(r) = &options { + return crate::commands::try_run_package_or_file(&args, r); + } + + options.execute() +} + +#[derive(Debug, Clone, PartialEq, Default)] +pub(crate) struct SplitVersion { + pub(crate) original: String, + pub(crate) registry: Option, + pub(crate) package: String, + pub(crate) version: Option, + pub(crate) command: Option, +} + +impl fmt::Display for SplitVersion { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let version = self.version.as_deref().unwrap_or("latest"); + let command = self + .command + .as_ref() + .map(|s| format!(":{s}")) + .unwrap_or_default(); + write!(f, "{}@{version}{command}", self.package) + } +} + +#[test] +fn test_split_version() { + assert_eq!( + SplitVersion::new("registry.wapm.io/graphql/python/python").unwrap(), + SplitVersion { + original: "registry.wapm.io/graphql/python/python".to_string(), + registry: Some("https://registry.wapm.io/graphql".to_string()), + package: "python/python".to_string(), + version: None, + command: None, + } + ); + assert_eq!( + SplitVersion::new("registry.wapm.io/python/python").unwrap(), + SplitVersion { + original: "registry.wapm.io/python/python".to_string(), + registry: Some("https://registry.wapm.io/graphql".to_string()), + package: "python/python".to_string(), + version: None, + command: None, + } + ); + assert_eq!( + SplitVersion::new("namespace/name@version:command").unwrap(), + SplitVersion { + original: "namespace/name@version:command".to_string(), + registry: None, + package: "namespace/name".to_string(), + version: Some("version".to_string()), + command: Some("command".to_string()), + } + ); + assert_eq!( + SplitVersion::new("namespace/name@version").unwrap(), + SplitVersion { + original: "namespace/name@version".to_string(), + registry: None, + package: "namespace/name".to_string(), + version: Some("version".to_string()), + command: None, + } + ); + assert_eq!( + SplitVersion::new("namespace/name").unwrap(), + SplitVersion { + original: "namespace/name".to_string(), + registry: None, + package: "namespace/name".to_string(), + version: None, + command: None, + } + ); + assert_eq!( + SplitVersion::new("registry.wapm.io/namespace/name").unwrap(), + SplitVersion { + original: "registry.wapm.io/namespace/name".to_string(), + registry: Some("https://registry.wapm.io/graphql".to_string()), + package: "namespace/name".to_string(), + version: None, + command: None, + } + ); + assert_eq!( + format!("{}", SplitVersion::new("namespace").unwrap_err()), + "Invalid package version: \"namespace\"".to_string(), + ); +} + +impl SplitVersion { + pub fn new(s: &str) -> Result { + let command = WasmerCLIOptions::command(); + let mut prohibited_package_names = command.get_subcommands().map(|s| s.get_name()); + + let re1 = regex::Regex::new(r#"(.*)/(.*)@(.*):(.*)"#).unwrap(); + let re2 = regex::Regex::new(r#"(.*)/(.*)@(.*)"#).unwrap(); + let re3 = regex::Regex::new(r#"(.*)/(.*)"#).unwrap(); + let re4 = regex::Regex::new(r#"(.*)/(.*):(.*)"#).unwrap(); + + let mut no_version = false; + + let captures = if re1.is_match(s) { + re1.captures(s) + .map(|c| { + c.iter() + .flatten() + .map(|m| m.as_str().to_owned()) + .collect::>() + }) + .unwrap_or_default() + } else if re2.is_match(s) { + re2.captures(s) + .map(|c| { + c.iter() + .flatten() + .map(|m| m.as_str().to_owned()) + .collect::>() + }) + .unwrap_or_default() + } else if re4.is_match(s) { + no_version = true; + re4.captures(s) + .map(|c| { + c.iter() + .flatten() + .map(|m| m.as_str().to_owned()) + .collect::>() + }) + .unwrap_or_default() + } else if re3.is_match(s) { + re3.captures(s) + .map(|c| { + c.iter() + .flatten() + .map(|m| m.as_str().to_owned()) + .collect::>() + }) + .unwrap_or_default() + } else { + return Err(anyhow::anyhow!("Invalid package version: {s:?}")); + }; + + let mut namespace = match captures.get(1).cloned() { + Some(s) => s, + None => { + return Err(anyhow::anyhow!( + "Invalid package version: {s:?}: no namespace" + )) + } + }; + + let name = match captures.get(2).cloned() { + Some(s) => s, + None => return Err(anyhow::anyhow!("Invalid package version: {s:?}: no name")), + }; + + let mut registry = None; + if namespace.contains('/') { + let (r, n) = namespace.rsplit_once('/').unwrap(); + let mut real_registry = r.to_string(); + if !real_registry.ends_with("graphql") { + real_registry = format!("{real_registry}/graphql"); + } + if !real_registry.contains("://") { + real_registry = format!("https://{real_registry}"); + } + registry = Some(real_registry); + namespace = n.to_string(); + } + + let sv = SplitVersion { + original: s.to_string(), + registry, + package: format!("{namespace}/{name}"), + version: if no_version { + None + } else { + captures.get(3).cloned() + }, + command: captures.get(if no_version { 3 } else { 4 }).cloned(), + }; + + let svp = sv.package.clone(); + anyhow::ensure!( + !prohibited_package_names.any(|s| s == sv.package.trim()), + "Invalid package name {svp:?}" + ); + + Ok(sv) + } +} + +fn print_help(verbose: bool) -> Result<(), anyhow::Error> { + let mut cmd = WasmerCLIOptions::command(); + if verbose { + let _ = cmd.print_long_help(); + } else { + let _ = cmd.print_help(); + } + Ok(()) +} + +#[allow(unused_mut, clippy::vec_init_then_push)] +fn print_version(verbose: bool) -> Result<(), anyhow::Error> { + if !verbose { + println!("wasmer {}", env!("CARGO_PKG_VERSION")); + } else { + println!( + "wasmer {} ({} {})", + env!("CARGO_PKG_VERSION"), + env!("WASMER_BUILD_GIT_HASH_SHORT"), + env!("WASMER_BUILD_DATE") + ); + println!("binary: {}", env!("CARGO_PKG_NAME")); + println!("commit-hash: {}", env!("WASMER_BUILD_GIT_HASH")); + println!("commit-date: {}", env!("WASMER_BUILD_DATE")); + println!("host: {}", target_lexicon::HOST); + println!("compiler: {}", { + let mut s = Vec::<&'static str>::new(); + + #[cfg(feature = "singlepass")] + s.push("singlepass"); + #[cfg(feature = "cranelift")] + s.push("cranelift"); + #[cfg(feature = "llvm")] + s.push("llvm"); + + s.join(",") + }); + } + Ok(()) } diff --git a/lib/cli/src/commands.rs b/lib/cli/src/commands.rs index 33194feee94..ad5dc013572 100644 --- a/lib/cli/src/commands.rs +++ b/lib/cli/src/commands.rs @@ -10,6 +10,7 @@ mod create_exe; #[cfg(feature = "static-artifact-create")] mod create_obj; mod inspect; +mod list; mod run; mod self_update; mod validate; @@ -26,7 +27,7 @@ pub use create_exe::*; pub use create_obj::*; #[cfg(feature = "wast")] pub use wast::*; -pub use {cache::*, config::*, inspect::*, run::*, self_update::*, validate::*}; +pub use {cache::*, config::*, inspect::*, list::*, run::*, self_update::*, validate::*}; /// The kind of object format to emit. #[derive(Debug, Copy, Clone, clap::Parser)] diff --git a/lib/cli/src/commands/binfmt.rs b/lib/cli/src/commands/binfmt.rs index e403b17be45..c6cedd9143f 100644 --- a/lib/cli/src/commands/binfmt.rs +++ b/lib/cli/src/commands/binfmt.rs @@ -8,7 +8,7 @@ use std::os::unix::fs::MetadataExt; use std::path::{Path, PathBuf}; use Action::*; -#[derive(Parser, Clone, Copy)] +#[derive(Debug, Parser, Clone, Copy)] enum Action { /// Register wasmer as binfmt interpreter Register, @@ -22,7 +22,7 @@ enum Action { /// /// Check the wasmer repository for a systemd service definition example /// to automate the process at start-up. -#[derive(Parser)] +#[derive(Debug, Parser)] pub struct Binfmt { // Might be better to traverse the mount list /// Mount point of binfmt_misc fs diff --git a/lib/cli/src/commands/list.rs b/lib/cli/src/commands/list.rs new file mode 100644 index 00000000000..332129b3912 --- /dev/null +++ b/lib/cli/src/commands/list.rs @@ -0,0 +1,43 @@ +use clap::Parser; + +/// Subcommand for listing packages +#[derive(Debug, Copy, Clone, Parser)] +pub struct List {} + +impl List { + /// execute [List] + pub fn execute(&self) -> Result<(), anyhow::Error> { + use prettytable::{format, row, Table}; + + let rows = wasmer_registry::get_all_local_packages(None) + .into_iter() + .filter_map(|pkg| { + let package_root_path = pkg.get_path().ok()?; + let (manifest, _) = + wasmer_registry::get_executable_file_from_path(&package_root_path, None) + .ok()?; + let commands = manifest + .command + .unwrap_or_default() + .iter() + .map(|c| c.get_name()) + .collect::>() + .join(" \r\n"); + + Some(row![pkg.registry, pkg.name, pkg.version, commands]) + }) + .collect::>(); + + let empty_table = rows.is_empty(); + let mut table = Table::init(rows); + table.set_titles(row!["Registry", "Package", "Version", "Commands"]); + table.set_format(*format::consts::FORMAT_NO_LINESEP_WITH_TITLE); + table.set_format(*format::consts::FORMAT_NO_COLSEP); + if empty_table { + table.add_empty_row(); + } + let _ = table.printstd(); + + Ok(()) + } +} diff --git a/lib/cli/src/commands/run.rs b/lib/cli/src/commands/run.rs index c6e0fde6ee8..569b37394bc 100644 --- a/lib/cli/src/commands/run.rs +++ b/lib/cli/src/commands/run.rs @@ -5,6 +5,8 @@ use crate::store::{CompilerType, StoreOptions}; use crate::suggestions::suggest_function_exports; use crate::warning; use anyhow::{anyhow, Context, Result}; +use std::collections::HashMap; +use std::ops::Deref; use std::path::PathBuf; use std::str::FromStr; use wasmer::FunctionEnv; @@ -13,7 +15,9 @@ use wasmer::*; use wasmer_cache::{Cache, FileSystemCache, Hash}; use wasmer_types::Type as ValueType; +use crate::cli::SplitVersion; use clap::Parser; +use wasmer_registry::PackageDownloadInfo; #[cfg(feature = "wasi")] mod wasi; @@ -21,18 +25,18 @@ mod wasi; #[cfg(feature = "wasi")] use wasi::Wasi; +/// Same as `wasmer run`, but without the required `path` argument (injected previously) #[derive(Debug, Parser, Clone, Default)] -/// The options for the `wasmer run` subcommand -pub struct Run { +pub struct RunWithoutFile { + /// When installing packages with `wasmer $package`, force re-downloading the package + #[clap(long = "force", short = 'f')] + pub(crate) force_install: bool, + /// Disable the cache #[cfg(feature = "cache")] #[clap(long = "disable-cache")] disable_cache: bool, - /// File to run - #[clap(name = "FILE", parse(from_os_str))] - path: PathBuf, - /// Invoke a specified function #[clap(long = "invoke", short = 'i')] invoke: Option, @@ -41,7 +45,7 @@ pub struct Run { /// to the wasm program. This is used in wapm to provide nicer output in /// help commands and error messages of the running wasm program #[clap(long = "command-name", hide = true)] - command_name: Option, + pub(crate) command_name: Option, /// A prehashed string, used to speed up start times by avoiding hashing the /// wasm module. If the specified hash is not found, Wasmer will hash the module @@ -69,14 +73,97 @@ pub struct Run { debug: bool, #[cfg(feature = "debug")] - #[clap(short, long, parse(from_occurrences))] - verbose: u8, + #[clap(long = "verbose")] + verbose: Option, /// Application arguments #[clap(value_name = "ARGS")] args: Vec, } +#[allow(dead_code)] +fn is_dir(e: &walkdir::DirEntry) -> bool { + let meta = match e.metadata() { + Ok(o) => o, + Err(_) => return false, + }; + meta.is_dir() +} + +impl RunWithoutFile { + /// Given a local path, returns the `Run` command (overriding the `--path` argument). + pub fn into_run_args( + mut self, + package_root_dir: PathBuf, // <- package dir + command: Option<&str>, + ) -> Result { + let (manifest, pathbuf) = + wasmer_registry::get_executable_file_from_path(&package_root_dir, command)?; + + #[cfg(feature = "wasi")] + { + let default = HashMap::default(); + let fs = manifest.fs.as_ref().unwrap_or(&default); + for (alias, real_dir) in fs.iter() { + let real_dir = package_root_dir.join(&real_dir); + if !real_dir.exists() { + println!( + "warning: cannot map {alias:?} to {}: directory does not exist", + real_dir.display() + ); + continue; + } + + self.wasi.map_dir(alias, real_dir.clone()); + } + } + + Ok(Run { + path: pathbuf, + options: RunWithoutFile { + force_install: self.force_install, + #[cfg(feature = "cache")] + disable_cache: self.disable_cache, + invoke: self.invoke, + // If the RunWithoutFile was constructed via a package name, + // the correct syntax is "package:command-name" (--command-name would be + // interpreted as a CLI argument for the .wasm file) + command_name: None, + #[cfg(feature = "cache")] + cache_key: self.cache_key, + store: self.store, + #[cfg(feature = "wasi")] + wasi: self.wasi, + #[cfg(feature = "io-devices")] + enable_experimental_io_devices: self.enable_experimental_io_devices, + #[cfg(feature = "debug")] + debug: self.debug, + #[cfg(feature = "debug")] + verbose: self.verbose.unwrap_or(0), + args: self.args, + }, + }) + } +} + +#[derive(Debug, Parser, Clone, Default)] +/// The options for the `wasmer run` subcommand +pub struct Run { + /// File to run + #[clap(name = "FILE", parse(from_os_str))] + pub(crate) path: PathBuf, + + #[clap(flatten)] + pub(crate) options: RunWithoutFile, +} + +impl Deref for Run { + type Target = RunWithoutFile; + fn deref(&self) -> &Self::Target { + &self.options + } +} + impl Run { /// Execute the run command pub fn execute(&self) -> Result<()> { @@ -138,12 +225,18 @@ impl Run { }; // TODO: refactor this if is_emscripten_module(&module) { + let em_env = EmEnv::new(); + for (k, v) in self.wasi.env_vars.iter() { + em_env.set_env_var(k, v); + } // create an EmEnv with default global - let env = FunctionEnv::new(&mut store, EmEnv::new()); + let env = FunctionEnv::new(&mut store, em_env); let mut emscripten_globals = EmscriptenGlobals::new(&mut store, &env, &module) .map_err(|e| anyhow!("{}", e))?; - env.as_mut(&mut store) - .set_data(&emscripten_globals.data, Default::default()); + env.as_mut(&mut store).set_data( + &emscripten_globals.data, + self.wasi.mapped_dirs.clone().into_iter().collect(), + ); let import_object = generate_emscripten_env(&mut store, &env, &mut emscripten_globals); let mut instance = match Instance::new(&mut store, &module, &import_object) { @@ -483,12 +576,14 @@ impl Run { let store = StoreOptions::default(); // TODO: store.compiler.features.all = true; ? Ok(Self { - args, path: executable.into(), - command_name: Some(original_executable), - store, - wasi: Wasi::for_binfmt_interpreter()?, - ..Self::default() + options: RunWithoutFile { + args, + command_name: Some(original_executable), + store, + wasi: Wasi::for_binfmt_interpreter()?, + ..Default::default() + }, }) } #[cfg(not(target_os = "linux"))] @@ -496,3 +591,190 @@ impl Run { bail!("binfmt_misc is only available on linux.") } } + +fn start_spinner(msg: String) -> spinner::SpinnerHandle { + spinner::SpinnerBuilder::new(msg) + .spinner(vec![ + "⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷", " ", "⠁", "⠂", "⠄", "⡀", "⢀", "⠠", "⠐", "⠈", + ]) + .start() +} + +pub(crate) fn try_autoinstall_package( + args: &[String], + sv: &SplitVersion, + package: Option, + force_install: bool, +) -> Result<(), anyhow::Error> { + use std::io::Write; + let sp = start_spinner(format!("Installing package {} ...", sv.package)); + let v = sv.version.as_deref(); + let result = wasmer_registry::install_package( + sv.registry.as_deref(), + &sv.package, + v, + package, + force_install, + ); + sp.close(); + print!("\r"); + let _ = std::io::stdout().flush(); + let (_, package_dir) = match result { + Ok(o) => o, + Err(e) => { + return Err(anyhow::anyhow!("{e}")); + } + }; + + // Try auto-installing the remote package + let mut args_without_package = args.to_vec(); + args_without_package.remove(1); + + let mut run_args = RunWithoutFile::try_parse_from(args_without_package.iter())?; + run_args.command_name = sv.command.clone(); + + run_args + .into_run_args(package_dir, sv.command.as_deref())? + .execute() +} + +// We need to distinguish between errors that happen +// before vs. during execution +enum ExecuteLocalPackageError { + BeforeExec(anyhow::Error), + DuringExec(anyhow::Error), +} + +fn try_execute_local_package( + args: &[String], + sv: &SplitVersion, +) -> Result<(), ExecuteLocalPackageError> { + let package = wasmer_registry::get_local_package(None, &sv.package, sv.version.as_deref()) + .ok_or_else(|| { + ExecuteLocalPackageError::BeforeExec(anyhow::anyhow!("no local package {sv:?} found")) + })?; + + let package_dir = package + .get_path() + .map_err(|e| ExecuteLocalPackageError::BeforeExec(anyhow::anyhow!("{e}")))?; + + // Try finding the local package + let mut args_without_package = args.to_vec(); + + // remove either "run" or $package + args_without_package.remove(1); + + // "wasmer package arg1 arg2" => "wasmer arg1 arg2" + if (args_without_package.get(1).is_some() && args_without_package[1].starts_with(&sv.original)) + || (sv.command.is_some() && args_without_package[1].ends_with(sv.command.as_ref().unwrap())) + { + args_without_package.remove(1); + } + + RunWithoutFile::try_parse_from(args_without_package.iter()) + .map_err(|e| ExecuteLocalPackageError::DuringExec(e.into()))? + .into_run_args(package_dir, sv.command.as_deref()) + .map_err(ExecuteLocalPackageError::DuringExec)? + .execute() + .map_err(|e| ExecuteLocalPackageError::DuringExec(e.context(anyhow::anyhow!("{}", sv)))) +} + +fn try_lookup_command(sv: &mut SplitVersion) -> Result { + use std::io::Write; + let sp = start_spinner(format!("Looking up command {} ...", sv.package)); + + for registry in wasmer_registry::get_all_available_registries().unwrap_or_default() { + let result = wasmer_registry::query_command_from_registry(®istry, &sv.package); + print!("\r"); + let _ = std::io::stdout().flush(); + let command = sv.package.clone(); + if let Ok(o) = result { + sv.package = o.package.clone(); + sv.version = Some(o.version.clone()); + sv.command = Some(command); + return Ok(o); + } + } + + sp.close(); + print!("\r"); + let _ = std::io::stdout().flush(); + Err(anyhow::anyhow!("command {sv} not found")) +} + +pub(crate) fn try_run_package_or_file(args: &[String], r: &Run) -> Result<(), anyhow::Error> { + // Check "r.path" is a file or a package / command name + if r.path.exists() { + if r.path.is_dir() && r.path.join("wapm.toml").exists() { + let mut args_without_package = args.to_vec(); + if args_without_package.get(1) == Some(&format!("{}", r.path.display())) { + let _ = args_without_package.remove(1); + } else if args_without_package.get(2) == Some(&format!("{}", r.path.display())) { + let _ = args_without_package.remove(1); + let _ = args_without_package.remove(1); + } + return RunWithoutFile::try_parse_from(args_without_package.iter())? + .into_run_args(r.path.clone(), r.command_name.as_deref())? + .execute(); + } + return r.execute(); + } + + let package = format!("{}", r.path.display()); + + let mut is_fake_sv = false; + let mut sv = match SplitVersion::new(&package) { + Ok(o) => o, + Err(_) => { + let mut fake_sv = SplitVersion { + original: package.to_string(), + registry: None, + package: package.to_string(), + version: None, + command: None, + }; + is_fake_sv = true; + match try_lookup_command(&mut fake_sv) { + Ok(o) => SplitVersion { + original: format!("{}@{}", o.package, o.version), + registry: None, + package: o.package, + version: Some(o.version), + command: r.command_name.clone(), + }, + Err(e) => { + return Err( + anyhow::anyhow!("No package for command {package:?} found, file {package:?} not found either") + .context(e) + .context(anyhow::anyhow!("{}", r.path.display())) + ); + } + } + } + }; + + if sv.command.is_none() { + sv.command = r.command_name.clone(); + } + + if sv.command.is_none() && is_fake_sv { + sv.command = Some(package); + } + + let mut package_download_info = None; + if !sv.package.contains('/') { + if let Ok(o) = try_lookup_command(&mut sv) { + package_download_info = Some(o); + } + } + + match try_execute_local_package(args, &sv) { + Ok(o) => return Ok(o), + Err(ExecuteLocalPackageError::DuringExec(e)) => return Err(e), + _ => {} + } + + println!("finding local package {} failed", sv); + // else: local package not found - try to download and install package + try_autoinstall_package(args, &sv, package_download_info, r.force_install) +} diff --git a/lib/cli/src/commands/run/wasi.rs b/lib/cli/src/commands/run/wasi.rs index 2fd31666bed..ffc70c42036 100644 --- a/lib/cli/src/commands/run/wasi.rs +++ b/lib/cli/src/commands/run/wasi.rs @@ -15,7 +15,7 @@ use clap::Parser; pub struct Wasi { /// WASI pre-opened directory #[clap(long = "dir", name = "DIR", group = "wasi")] - pre_opened_directories: Vec, + pub(crate) pre_opened_directories: Vec, /// Map a host directory to a different location for the Wasm module #[clap( @@ -23,7 +23,7 @@ pub struct Wasi { name = "GUEST_DIR:HOST_DIR", parse(try_from_str = parse_mapdir), )] - mapped_dirs: Vec<(String, PathBuf)>, + pub(crate) mapped_dirs: Vec<(String, PathBuf)>, /// Pass custom environment variables #[clap( @@ -31,7 +31,7 @@ pub struct Wasi { name = "KEY=VALUE", parse(try_from_str = parse_envvar), )] - env_vars: Vec<(String, String)>, + pub(crate) env_vars: Vec<(String, String)>, /// Enable experimental IO devices #[cfg(feature = "experimental-io-devices")] @@ -52,6 +52,14 @@ pub struct Wasi { #[allow(dead_code)] impl Wasi { + pub fn map_dir(&mut self, alias: &str, target_on_disk: PathBuf) { + self.mapped_dirs.push((alias.to_string(), target_on_disk)); + } + + pub fn set_env(&mut self, key: &str, value: &str) { + self.env_vars.push((key.to_string(), value.to_string())); + } + /// Gets the WASI version (if any) for the provided module pub fn get_versions(module: &Module) -> Option> { // Get the wasi version in strict mode, so no other imports are diff --git a/lib/emscripten/src/env/unix/mod.rs b/lib/emscripten/src/env/unix/mod.rs index f559468dc28..a638d31be21 100644 --- a/lib/emscripten/src/env/unix/mod.rs +++ b/lib/emscripten/src/env/unix/mod.rs @@ -1,9 +1,6 @@ /// NOTE: These syscalls only support wasm_32 for now because they take u32 offset -use libc::{ - c_int, getenv, getgrnam as libc_getgrnam, getpwnam as libc_getpwnam, putenv, setenv, sysconf, - unsetenv, -}; -use std::ffi::CStr; +use libc::{c_int, getgrnam as libc_getgrnam, getpwnam as libc_getpwnam, sysconf}; +use std::ffi::{CStr, CString}; use std::mem; use std::os::raw::c_char; @@ -17,55 +14,84 @@ use wasmer::{FunctionEnvMut, WasmPtr}; pub fn _getenv(mut ctx: FunctionEnvMut, name: i32) -> u32 { debug!("emscripten::_getenv"); - let memory = ctx.data().memory(0); + let em_env = ctx.data(); + let memory = em_env.memory(0); let name_addr = emscripten_memory_pointer!(memory.view(&ctx), name) as *const c_char; debug!("=> name({:?})", unsafe { CStr::from_ptr(name_addr) }); - let c_str = unsafe { getenv(name_addr) }; - if c_str.is_null() { - return 0; - } - - unsafe { copy_cstr_into_wasm(&mut ctx, c_str) } + let c_string = unsafe { CStr::from_ptr(name_addr) }; + let c_string = c_string.to_string_lossy(); + let env_var = em_env.get_env_var(c_string.as_ref()); + let env_var = match env_var { + Some(s) => s, + None => return 0, + }; + let new_env_var = match CString::new(env_var) { + Ok(s) => s, + Err(_) => return 0, + }; + unsafe { copy_cstr_into_wasm(&mut ctx, new_env_var.as_ptr()) } } /// emscripten: _setenv // (name: *const char, name: *const value, overwrite: int); pub fn _setenv(ctx: FunctionEnvMut, name: c_int, value: c_int, overwrite: c_int) -> c_int { debug!("emscripten::_setenv"); - let memory = ctx.data().memory(0); + let em_env = ctx.data(); + let memory = em_env.memory(0); let name_addr = emscripten_memory_pointer!(memory.view(&ctx), name) as *const c_char; let value_addr = emscripten_memory_pointer!(memory.view(&ctx), value) as *const c_char; - debug!("=> name({:?})", unsafe { CStr::from_ptr(name_addr) }); - debug!("=> value({:?})", unsafe { CStr::from_ptr(value_addr) }); + let name = unsafe { CStr::from_ptr(name_addr) }.to_string_lossy(); + let value = unsafe { CStr::from_ptr(value_addr) }.to_string_lossy(); + + debug!("=> name({:?})", name); + debug!("=> value({:?})", value); + + let previous_entry = em_env.set_env_var(name.as_ref(), value.as_ref()); + + if let (0, Some(prev)) = (overwrite, previous_entry) { + let _ = em_env.set_env_var(name.as_ref(), prev.as_ref()); + } - unsafe { setenv(name_addr, value_addr, overwrite) } + 0 } /// emscripten: _putenv // (name: *const char); pub fn _putenv(ctx: FunctionEnvMut, name: c_int) -> c_int { debug!("emscripten::_putenv"); - let memory = ctx.data().memory(0); + let em_env = ctx.data(); + let memory = em_env.memory(0); let name_addr = emscripten_memory_pointer!(memory.view(&ctx), name) as *const c_char; - debug!("=> name({:?})", unsafe { CStr::from_ptr(name_addr) }); + let name = unsafe { CStr::from_ptr(name_addr) }.to_string_lossy(); + debug!("=> name({:?})", name); - unsafe { putenv(name_addr as _) } + em_env.set_env_var(name.as_ref(), ""); + + 0 } /// emscripten: _unsetenv // (name: *const char); -pub fn _unsetenv(ctx: FunctionEnvMut, name: c_int) -> c_int { +pub fn _unsetenv(mut ctx: FunctionEnvMut, name: c_int) -> c_int { debug!("emscripten::_unsetenv"); - let memory = ctx.data().memory(0); - let name_addr = emscripten_memory_pointer!(memory.view(&ctx), name) as *const c_char; + let name = { + let em_env = ctx.data(); + let memory = em_env.memory(0); + let name_addr = emscripten_memory_pointer!(memory.view(&ctx), name) as *const c_char; + unsafe { CStr::from_ptr(name_addr) } + .to_string_lossy() + .to_string() + }; - debug!("=> name({:?})", unsafe { CStr::from_ptr(name_addr) }); + debug!("=> name({:?})", name); + let em_env = ctx.data_mut(); + em_env.remove_env_var(name.as_ref()); - unsafe { unsetenv(name_addr) } + 0 } #[allow(clippy::cast_ptr_alignment)] diff --git a/lib/emscripten/src/lib.rs b/lib/emscripten/src/lib.rs index 216171e3510..f60f0cc72dc 100644 --- a/lib/emscripten/src/lib.rs +++ b/lib/emscripten/src/lib.rs @@ -74,12 +74,35 @@ pub use self::utils::{ get_emscripten_table_size, is_emscripten_module, }; +/// State of the emscripten environment (environment variables, CLI args) +#[derive(Debug, Clone)] +pub struct EmscriptenState { + /// Environment variables in a [key -> value] mapping + pub env_vars: HashMap, + /// Command line arguments that this module received + pub cli_args: Vec, +} + +impl Default for EmscriptenState { + fn default() -> Self { + Self { + env_vars: std::env::vars_os() + .filter_map(|(k, v)| Some((k.to_str()?.to_string(), v.to_str()?.to_string()))) + .collect(), + cli_args: Vec::new(), + } + } +} + #[derive(Clone)] /// The environment provided to the Emscripten imports. pub struct EmEnv { memory: Arc>>, data: Arc>>, funcs: Arc>, + // State that is passed to the wasm module (environment variables, CLI args, ...) + #[allow(dead_code)] + state: Arc>, } impl Default for EmEnv { @@ -95,6 +118,16 @@ impl EmEnv { memory: Arc::new(RwLock::new(None)), data: Arc::new(Mutex::new(None)), funcs: Arc::new(Mutex::new(EmscriptenFunctions::new())), + state: Arc::new(Mutex::new(EmscriptenState::default())), + } + } + + pub fn new_with_state(emstate: EmscriptenState) -> Self { + Self { + memory: Arc::new(RwLock::new(None)), + data: Arc::new(Mutex::new(None)), + funcs: Arc::new(Mutex::new(EmscriptenFunctions::new())), + state: Arc::new(Mutex::new(emstate)), } } @@ -120,6 +153,37 @@ impl EmEnv { let mut w = self.data.lock().unwrap(); *w = Some(EmscriptenData::new(data.clone(), mapped_dirs)); } + + pub fn get_env_var(&self, key: &str) -> Option { + let w = self.state.lock().ok()?; + let result = w.env_vars.get(key).cloned(); + result + } + + pub fn get_env_vars_len(&self) -> usize { + let w = self.state.lock().unwrap(); + w.env_vars.len() + } + + pub fn set_env_var(&self, key: &str, value: &str) -> Option { + let mut w = self.state.lock().ok()?; + w.env_vars.insert(key.to_string(), value.to_string()) + } + + pub fn remove_env_var(&self, key: &str) -> Option { + let mut w = self.state.lock().ok()?; + w.env_vars.remove(key) + } + + pub fn get_args_size(&self) -> usize { + let w = self.state.lock().unwrap(); + w.cli_args.len() + } + + pub fn get_args(&self) -> Vec { + let w = self.state.lock().unwrap(); + w.cli_args.clone() + } } #[derive(Debug, Clone)] diff --git a/lib/emscripten/src/utils.rs b/lib/emscripten/src/utils.rs index a8e7cd82b76..41c23fe5e3a 100644 --- a/lib/emscripten/src/utils.rs +++ b/lib/emscripten/src/utils.rs @@ -76,14 +76,18 @@ pub fn get_emscripten_metadata(module: &Module) -> Result, St &module.info().global_initializers[max_idx], &module.info().global_initializers[snd_max_idx], ) { - let dynamic_base = (*dynamic_base as u32).checked_sub(32).ok_or(format!( - "emscripten unexpected dynamic_base {}", - *dynamic_base as u32 - ))?; - let dynamictop_ptr = (*dynamictop_ptr as u32).checked_sub(32).ok_or(format!( - "emscripten unexpected dynamictop_ptr {}", - *dynamictop_ptr as u32 - ))?; + let dynamic_base = (*dynamic_base as u32).checked_sub(32).ok_or_else(|| { + format!( + "emscripten unexpected dynamic_base {}", + *dynamic_base as u32 + ) + })?; + let dynamictop_ptr = (*dynamictop_ptr as u32).checked_sub(32).ok_or_else(|| { + format!( + "emscripten unexpected dynamictop_ptr {}", + *dynamictop_ptr as u32 + ) + })?; Ok(Some(( align_memory(dynamic_base), align_memory(dynamictop_ptr), diff --git a/lib/registry/Cargo.toml b/lib/registry/Cargo.toml new file mode 100644 index 00000000000..f91f1e7e1f2 --- /dev/null +++ b/lib/registry/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "wasmer-registry" +version = "3.0.0-beta.2" +edition = "2021" + +[dependencies] +dirs = "4.0.0" +graphql_client = "0.11.0" +serde = { version = "1.0.145", features = ["derive"] } +anyhow = "1.0.65" +reqwest = { version = "0.11.12", features = ["blocking", "multipart", "json"] } +whoami = "1.2.3" +serde_json = "1.0.85" +url = "2.3.1" +thiserror = "1.0.37" +toml = "0.5.9" +wapm-toml = "0.1.1" +tar = "0.4.38" +flate2 = "1.0.24" +semver = "1.0.14" \ No newline at end of file diff --git a/lib/registry/graphql/queries/get_package_by_command.graphql b/lib/registry/graphql/queries/get_package_by_command.graphql new file mode 100644 index 00000000000..36c64eb19bb --- /dev/null +++ b/lib/registry/graphql/queries/get_package_by_command.graphql @@ -0,0 +1,16 @@ +query GetPackageByCommandQuery ($commandName: String!) { + getCommand(name: $commandName) { + command + packageVersion { + version + isLastVersion + manifest + distribution { + downloadUrl + } + package { + displayName + } + } + } +} \ No newline at end of file diff --git a/lib/registry/graphql/queries/get_package_version.graphql b/lib/registry/graphql/queries/get_package_version.graphql new file mode 100644 index 00000000000..6fdd84c9ba4 --- /dev/null +++ b/lib/registry/graphql/queries/get_package_version.graphql @@ -0,0 +1,13 @@ +query GetPackageVersionQuery ($name: String!, $version: String) { + packageVersion: getPackageVersion(name:$name, version:$version) { + package { + name + } + version + isLastVersion + distribution { + downloadUrl + } + manifest + } +} \ No newline at end of file diff --git a/lib/registry/graphql/queries/get_packages.graphql b/lib/registry/graphql/queries/get_packages.graphql new file mode 100644 index 00000000000..9bbd6a0e227 --- /dev/null +++ b/lib/registry/graphql/queries/get_packages.graphql @@ -0,0 +1,14 @@ +query GetPackagesQuery ($names: [String!]!) { + package: getPackages(names:$names) { + name + private + versions { + version + isLastVersion + distribution { + downloadUrl + } + manifest + } + } +} diff --git a/lib/registry/graphql/queries/test_if_registry_present.graphql b/lib/registry/graphql/queries/test_if_registry_present.graphql new file mode 100644 index 00000000000..7b1e4d33a56 --- /dev/null +++ b/lib/registry/graphql/queries/test_if_registry_present.graphql @@ -0,0 +1,3 @@ +query TestIfRegistryPresent { + __typename +} \ No newline at end of file diff --git a/lib/registry/graphql/schema.graphql b/lib/registry/graphql/schema.graphql new file mode 100644 index 00000000000..07687311d1c --- /dev/null +++ b/lib/registry/graphql/schema.graphql @@ -0,0 +1,1401 @@ +type APIToken { + createdAt: DateTime! + id: ID! + identifier: String + lastUsedAt: DateTime + revokedAt: DateTime + user: User! +} + +type APITokenConnection { + # Contains the nodes in this connection. + edges: [APITokenEdge]! + + # Pagination data for this connection. + pageInfo: PageInfo! +} + +# A Relay edge containing a `APIToken` and its cursor. +type APITokenEdge { + # A cursor for use in pagination + cursor: String! + + # The item at the end of the edge + node: APIToken +} + +input AcceptNamespaceCollaboratorInviteInput { + clientMutationId: String + inviteId: ID! +} + +type AcceptNamespaceCollaboratorInvitePayload { + clientMutationId: String + namespaceCollaboratorInvite: NamespaceCollaboratorInvite! +} + +input AcceptPackageCollaboratorInviteInput { + clientMutationId: String + inviteId: ID! +} + +type AcceptPackageCollaboratorInvitePayload { + clientMutationId: String + packageCollaboratorInvite: PackageCollaboratorInvite! +} + +input AcceptPackageTransferRequestInput { + clientMutationId: String + packageTransferRequestId: ID! +} + +type AcceptPackageTransferRequestPayload { + clientMutationId: String + package: Package! + packageTransferRequest: PackageTransferRequest! +} + +type ActivityEvent implements Node { + actorIcon: String! + body: ActivityEventBody! + createdAt: DateTime! + + # The ID of the object + id: ID! +} + +type ActivityEventBody { + ranges: [NodeBodyRange!]! + text: String! +} + +type ActivityEventConnection { + # Contains the nodes in this connection. + edges: [ActivityEventEdge]! + + # Pagination data for this connection. + pageInfo: PageInfo! +} + +# A Relay edge containing a `ActivityEvent` and its cursor. +type ActivityEventEdge { + # A cursor for use in pagination + cursor: String! + + # The item at the end of the edge + node: ActivityEvent +} + +input ArchivePackageInput { + clientMutationId: String + packageId: ID! +} + +type ArchivePackagePayload { + clientMutationId: String + package: Package! +} + +input ChangePackageVersionArchivedStatusInput { + clientMutationId: String + isArchived: Boolean + packageVersionId: ID! +} + +type ChangePackageVersionArchivedStatusPayload { + clientMutationId: String + packageVersion: PackageVersion! +} + +input ChangeUserEmailInput { + clientMutationId: String + newEmail: String! +} + +type ChangeUserEmailPayload { + clientMutationId: String + user: User! +} + +input ChangeUserPasswordInput { + clientMutationId: String + password: String! + + # The token associated to change the password. If not existing it will use the request user by default + token: String +} + +type ChangeUserPasswordPayload { + clientMutationId: String + token: String +} + +input ChangeUserUsernameInput { + clientMutationId: String + + # The new user username + username: String! +} + +type ChangeUserUsernamePayload { + clientMutationId: String + token: String + user: User +} + +input CheckUserExistsInput { + clientMutationId: String + + # The user + user: String! +} + +type CheckUserExistsPayload { + clientMutationId: String + exists: Boolean! + + # The user is only returned if the user input was the username + user: User +} + +type Command { + command: String! + module: PackageVersionModule! + packageVersion: PackageVersion! +} + +input CreateNamespaceInput { + # The namespace avatar + avatar: String + clientMutationId: String + + # The namespace description + description: String + + # The namespace display name + displayName: String + name: String! +} + +type CreateNamespacePayload { + clientMutationId: String + namespace: Namespace! + user: User! +} + +# The `DateTime` scalar type represents a DateTime +# value as specified by +# [iso8601](https://en.wikipedia.org/wiki/ISO_8601). +scalar DateTime + +input DeleteNamespaceInput { + clientMutationId: String + namespaceId: ID! +} + +type DeleteNamespacePayload { + clientMutationId: String + success: Boolean! +} + +type ErrorType { + field: String! + messages: [String!]! +} + +input GenerateAPITokenInput { + clientMutationId: String + identifier: String +} + +type GenerateAPITokenPayload { + clientMutationId: String + token: APIToken + tokenRaw: String + user: User +} + +# The `GenericScalar` scalar type represents a generic +# GraphQL scalar value that could be: +# String, Boolean, Int, Float, List or Object. +scalar GenericScalar + +type GetPasswordResetToken { + user: User + valid: Boolean! +} + +union GlobalObject = Namespace | User + +input InputSignature { + data: String! + publicKeyKeyId: String! +} + +type Interface implements Node { + createdAt: DateTime! + description: String! + displayName: String! + homepage: String + icon: String + + # The ID of the object + id: ID! + lastVersion: InterfaceVersion + name: String! + updatedAt: DateTime! + versions(after: String = null, before: String = null, first: Int = null, last: Int = null, offset: Int = null): InterfaceVersionConnection! +} + +type InterfaceVersion implements Node { + content: String! + createdAt: DateTime! + + # The ID of the object + id: ID! + interface: Interface! + packageVersions(after: String = null, before: String = null, first: Int = null, last: Int = null, offset: Int = null): PackageVersionConnection! + publishedBy: User! + updatedAt: DateTime! + version: String! +} + +type InterfaceVersionConnection { + # Contains the nodes in this connection. + edges: [InterfaceVersionEdge]! + + # Pagination data for this connection. + pageInfo: PageInfo! +} + +# A Relay edge containing a `InterfaceVersion` and its cursor. +type InterfaceVersionEdge { + # A cursor for use in pagination + cursor: String! + + # The item at the end of the edge + node: InterfaceVersion +} + +input InviteNamespaceCollaboratorInput { + clientMutationId: String + email: String + namespaceId: ID! + role: Role! + username: String +} + +type InviteNamespaceCollaboratorPayload { + clientMutationId: String + invite: NamespaceCollaboratorInvite! + namespace: Namespace! +} + +input InvitePackageCollaboratorInput { + clientMutationId: String + email: String + packageName: String! + role: Role! + username: String +} + +type InvitePackageCollaboratorPayload { + clientMutationId: String + invite: PackageCollaboratorInvite! + package: Package! +} + +input LikePackageInput { + clientMutationId: String + packageId: ID! +} + +type LikePackagePayload { + clientMutationId: String + package: Package! +} + +interface Likeable { + id: ID! + likersCount: Int! + viewerHasLiked: Boolean! +} + +type Mutation { + acceptNamespaceCollaboratorInvite(input: AcceptNamespaceCollaboratorInviteInput!): AcceptNamespaceCollaboratorInvitePayload + acceptPackageCollaboratorInvite(input: AcceptPackageCollaboratorInviteInput!): AcceptPackageCollaboratorInvitePayload + acceptPackageTransferRequest(input: AcceptPackageTransferRequestInput!): AcceptPackageTransferRequestPayload + archivePackage(input: ArchivePackageInput!): ArchivePackagePayload + changePackageVersionArchivedStatus(input: ChangePackageVersionArchivedStatusInput!): ChangePackageVersionArchivedStatusPayload + changeUserEmail(input: ChangeUserEmailInput!): ChangeUserEmailPayload + changeUserPassword(input: ChangeUserPasswordInput!): ChangeUserPasswordPayload + changeUserUsername(input: ChangeUserUsernameInput!): ChangeUserUsernamePayload + checkUserExists(input: CheckUserExistsInput!): CheckUserExistsPayload + createNamespace(input: CreateNamespaceInput!): CreateNamespacePayload + deleteNamespace(input: DeleteNamespaceInput!): DeleteNamespacePayload + generateApiToken(input: GenerateAPITokenInput!): GenerateAPITokenPayload + inviteNamespaceCollaborator(input: InviteNamespaceCollaboratorInput!): InviteNamespaceCollaboratorPayload + invitePackageCollaborator(input: InvitePackageCollaboratorInput!): InvitePackageCollaboratorPayload + likePackage(input: LikePackageInput!): LikePackagePayload + publishPackage(input: PublishPackageInput!): PublishPackagePayload + publishPublicKey(input: PublishPublicKeyInput!): PublishPublicKeyPayload + readNotification(input: ReadNotificationInput!): ReadNotificationPayload + refreshToken(input: RefreshInput!): RefreshPayload + registerUser(input: RegisterUserInput!): RegisterUserPayload + removeNamespaceCollaborator(input: RemoveNamespaceCollaboratorInput!): RemoveNamespaceCollaboratorPayload + removeNamespaceCollaboratorInvite(input: RemoveNamespaceCollaboratorInviteInput!): RemoveNamespaceCollaboratorInvitePayload + removePackageCollaborator(input: RemovePackageCollaboratorInput!): RemovePackageCollaboratorPayload + removePackageCollaboratorInvite(input: RemovePackageCollaboratorInviteInput!): RemovePackageCollaboratorInvitePayload + removePackageTransferRequest(input: RemovePackageTransferRequestInput!): RemovePackageTransferRequestPayload + requestPackageTransfer(input: RequestPackageTransferInput!): RequestPackageTransferPayload + requestPasswordReset(input: RequestPasswordResetInput!): RequestPasswordResetPayload + requestValidationEmail(input: RequestValidationEmailInput!): RequestValidationEmailPayload + revokeApiToken(input: RevokeAPITokenInput!): RevokeAPITokenPayload + seePendingNotifications(input: SeePendingNotificationsInput!): SeePendingNotificationsPayload + + # Social Auth for JSON Web Token (JWT) + socialAuth(input: SocialAuthJWTInput!): SocialAuthJWTPayload + + # Obtain JSON Web Token mutation + tokenAuth(input: ObtainJSONWebTokenInput!): ObtainJSONWebTokenPayload + unlikePackage(input: UnlikePackageInput!): UnlikePackagePayload + unwatchPackage(input: UnwatchPackageInput!): UnwatchPackagePayload + updateNamespace(input: UpdateNamespaceInput!): UpdateNamespacePayload + updateNamespaceCollaboratorRole(input: UpdateNamespaceCollaboratorRoleInput!): UpdateNamespaceCollaboratorRolePayload + updatePackage(input: UpdatePackageInput!): UpdatePackagePayload + updatePackageCollaboratorRole(input: UpdatePackageCollaboratorRoleInput!): UpdatePackageCollaboratorRolePayload + updateUserInfo(input: UpdateUserInfoInput!): UpdateUserInfoPayload + validateUserEmail(input: ValidateUserEmailInput!): ValidateUserEmailPayload + validateUserPassword(input: ValidateUserPasswordInput!): ValidateUserPasswordPayload + verifyToken(input: VerifyInput!): VerifyPayload + watchPackage(input: WatchPackageInput!): WatchPackagePayload +} + +type Namespace implements Node & PackageOwner { + avatar: String! + avatarUpdatedAt: DateTime + collaborators(after: String = null, before: String = null, first: Int = null, last: Int = null): NamespaceCollaboratorConnection + createdAt: DateTime! + description: String! + displayName: String + globalName: String! + + # The ID of the object + id: ID! + maintainerInvites: [NamespaceCollaboratorInvite!]! + maintainersWithRoles(after: String = null, before: String = null, first: Int = null, last: Int = null, offset: Int = null): NamespaceMaintainerConnection! + name: String! + packageVersions(after: String = null, before: String = null, first: Int = null, last: Int = null): PackageVersionConnection + packages(after: String = null, before: String = null, first: Int = null, last: Int = null): PackageConnection + pendingInvites(after: String = null, before: String = null, first: Int = null, last: Int = null): NamespaceCollaboratorInviteConnection + publicActivity(after: String = null, before: String = null, first: Int = null, last: Int = null): ActivityEventConnection! + updatedAt: DateTime! + userSet(after: String = null, before: String = null, first: Int = null, last: Int = null, offset: Int = null): UserConnection! + viewerHasRole(role: Role!): Boolean! +} + +type NamespaceCollaborator { + createdAt: DateTime! + id: ID! + invite: NamespaceCollaboratorInvite + namespace: Namespace! + role: RegistryNamespaceMaintainerRoleChoices! + updatedAt: DateTime! + user: User! +} + +type NamespaceCollaboratorConnection { + # Contains the nodes in this connection. + edges: [NamespaceCollaboratorEdge]! + + # Pagination data for this connection. + pageInfo: PageInfo! +} + +# A Relay edge containing a `NamespaceCollaborator` and its cursor. +type NamespaceCollaboratorEdge { + # A cursor for use in pagination + cursor: String! + + # The item at the end of the edge + node: NamespaceCollaborator +} + +type NamespaceCollaboratorInvite { + accepted: NamespaceMaintainer + approvedBy: User + closedAt: DateTime + createdAt: DateTime! + declinedBy: User + expiresAt: DateTime! + id: ID! + inviteEmail: String + namespace: Namespace! + requestedBy: User! + role: RegistryNamespaceMaintainerInviteRoleChoices! + user: User +} + +type NamespaceCollaboratorInviteConnection { + # Contains the nodes in this connection. + edges: [NamespaceCollaboratorInviteEdge]! + + # Pagination data for this connection. + pageInfo: PageInfo! +} + +# A Relay edge containing a `NamespaceCollaboratorInvite` and its cursor. +type NamespaceCollaboratorInviteEdge { + # A cursor for use in pagination + cursor: String! + + # The item at the end of the edge + node: NamespaceCollaboratorInvite +} + +type NamespaceConnection { + # Contains the nodes in this connection. + edges: [NamespaceEdge]! + + # Pagination data for this connection. + pageInfo: PageInfo! +} + +# A Relay edge containing a `Namespace` and its cursor. +type NamespaceEdge { + # A cursor for use in pagination + cursor: String! + + # The item at the end of the edge + node: Namespace +} + +type NamespaceMaintainer implements Node { + createdAt: DateTime! + + # The ID of the object + id: ID! + invite: NamespaceCollaboratorInvite + namespace: Namespace! + role: RegistryNamespaceMaintainerRoleChoices! + updatedAt: DateTime! + user: User! +} + +type NamespaceMaintainerConnection { + # Contains the nodes in this connection. + edges: [NamespaceMaintainerEdge]! + + # Pagination data for this connection. + pageInfo: PageInfo! +} + +# A Relay edge containing a `NamespaceMaintainer` and its cursor. +type NamespaceMaintainerEdge { + # A cursor for use in pagination + cursor: String! + + # The item at the end of the edge + node: NamespaceMaintainer +} + +# An object with an ID +interface Node { + # The ID of the object + id: ID! +} + +type NodeBodyRange { + entity: Node! + length: Int! + offset: Int! +} + +input ObtainJSONWebTokenInput { + clientMutationId: String + password: String! + username: String! +} + +# Obtain JSON Web Token mutation +type ObtainJSONWebTokenPayload { + clientMutationId: String + payload: GenericScalar! + refreshExpiresIn: Int! + refreshToken: String! + token: String! +} + +type Package implements Likeable & Node & PackageOwner { + alias: String + + # The app icon. It should be formatted in the same way as Apple icons + appIcon: String! @deprecated(reason: "Please use icon instead") + collaborators(after: String = null, before: String = null, first: Int = null, last: Int = null): PackageCollaboratorConnection + createdAt: DateTime! + curated: Boolean! + displayName: String! + + # The total number of downloads of the package + downloadsCount: Int + globalName: String! + + # The app icon. It should be formatted in the same way as Apple icons + icon: String! + iconUpdatedAt: DateTime + + # The ID of the object + id: ID! + isTransferring: Boolean! + lastVersion: PackageVersion + likeCount: Int! + likersCount: Int! + maintainers: [User]! @deprecated(reason: "Please use collaborators instead") + name: String! + namespace: String + owner: PackageOwner + ownerObjectId: Int! + + # The name of the package without the owner + packageName: String! + pendingInvites(after: String = null, before: String = null, first: Int = null, last: Int = null): PackageCollaboratorInviteConnection + private: Boolean! + + # The public keys for all the published versions + publicKeys: [PublicKey!]! + updatedAt: DateTime! + versions: [PackageVersion] + viewerHasLiked: Boolean! + viewerHasRole(role: Role!): Boolean! + viewerIsWatching: Boolean! + watchCount: Int! +} + +type PackageCollaborator implements Node { + createdAt: DateTime! + + # The ID of the object + id: ID! + invite: PackageCollaboratorInvite + package: Package! + role: RegistryPackageMaintainerRoleChoices! + updatedAt: DateTime! + user: User! +} + +type PackageCollaboratorConnection { + # Contains the nodes in this connection. + edges: [PackageCollaboratorEdge]! + + # Pagination data for this connection. + pageInfo: PageInfo! +} + +# A Relay edge containing a `PackageCollaborator` and its cursor. +type PackageCollaboratorEdge { + # A cursor for use in pagination + cursor: String! + + # The item at the end of the edge + node: PackageCollaborator +} + +type PackageCollaboratorInvite implements Node { + accepted: PackageCollaborator + approvedBy: User + closedAt: DateTime + createdAt: DateTime! + declinedBy: User + expiresAt: DateTime! + + # The ID of the object + id: ID! + inviteEmail: String + package: Package! + requestedBy: User! + role: RegistryPackageMaintainerInviteRoleChoices! + user: User +} + +type PackageCollaboratorInviteConnection { + # Contains the nodes in this connection. + edges: [PackageCollaboratorInviteEdge]! + + # Pagination data for this connection. + pageInfo: PageInfo! +} + +# A Relay edge containing a `PackageCollaboratorInvite` and its cursor. +type PackageCollaboratorInviteEdge { + # A cursor for use in pagination + cursor: String! + + # The item at the end of the edge + node: PackageCollaboratorInvite +} + +type PackageConnection { + # Contains the nodes in this connection. + edges: [PackageEdge]! + + # Pagination data for this connection. + pageInfo: PageInfo! +} + +type PackageDistribution { + downloadUrl: String! + size: Int! +} + +# A Relay edge containing a `Package` and its cursor. +type PackageEdge { + # A cursor for use in pagination + cursor: String! + + # The item at the end of the edge + node: Package +} + +interface PackageOwner { + globalName: String! +} + +type PackageTransferRequest implements Node { + approvedBy: User + closedAt: DateTime + createdAt: DateTime! + declinedBy: User + expiresAt: DateTime! + + # The ID of the object + id: ID! + newOwnerObjectId: Int! + package: Package! + previousOwnerObjectId: Int! + requestedBy: User! +} + +type PackageTransferRequestConnection { + # Contains the nodes in this connection. + edges: [PackageTransferRequestEdge]! + + # Pagination data for this connection. + pageInfo: PageInfo! +} + +# A Relay edge containing a `PackageTransferRequest` and its cursor. +type PackageTransferRequestEdge { + # A cursor for use in pagination + cursor: String! + + # The item at the end of the edge + node: PackageTransferRequest +} + +type PackageVersion implements Node { + bindings: [PackageVersionBinding]! + commands: [Command!]! + createdAt: DateTime! + description: String! + distribution: PackageDistribution! + file: String! + fileSize: Int! + filesystem: [PackageVersionFilesystem]! + homepage: String + + # The ID of the object + id: ID! + isArchived: Boolean! + isLastVersion: Boolean! + isSigned: Boolean! + license: String + licenseFile: String + manifest: String! + moduleInterfaces: [InterfaceVersion!]! + modules: [PackageVersionModule!]! + package: Package! + publishedBy: User! + readme: String + repository: String + signature: Signature + updatedAt: DateTime! + version: String! +} + +interface PackageVersionBinding { + # The module these bindings are associated with. + module: String! +} + +type PackageVersionNPMBinding implements PackageVersionBinding { + npmDefaultInstallPackageName: String! +} + +type PackageVersionPythonBinding implements PackageVersionBinding { + pythonDefaultInstallPackageName: String! +} + +type PackageVersionConnection { + # Contains the nodes in this connection. + edges: [PackageVersionEdge]! + + # Pagination data for this connection. + pageInfo: PageInfo! +} + +# A Relay edge containing a `PackageVersion` and its cursor. +type PackageVersionEdge { + # A cursor for use in pagination + cursor: String! + + # The item at the end of the edge + node: PackageVersion +} + +type PackageVersionFilesystem { + host: String! + wasm: String! +} + +type PackageVersionModule { + abi: String + name: String! + publicUrl: String! + source: String! +} + +# The Relay compliant `PageInfo` type, containing data necessary to paginate this connection. +type PageInfo { + # When paginating forwards, the cursor to continue. + endCursor: String + + # When paginating forwards, are there more items? + hasNextPage: Boolean! + + # When paginating backwards, are there more items? + hasPreviousPage: Boolean! + + # When paginating backwards, the cursor to continue. + startCursor: String +} + +type PublicKey implements Node { + # The ID of the object + id: ID! + key: String! + keyId: String! + owner: User! + revoked: Boolean! + revokedAt: DateTime + uploadedAt: DateTime! + verifyingSignature: Signature +} + +input PublishPackageInput { + clientMutationId: String + description: String! + file: String + homepage: String + + # The package icon + icon: String + license: String + licenseFile: String + manifest: String! + name: String! + readme: String + repository: String + signature: InputSignature + version: String! +} + +type PublishPackagePayload { + clientMutationId: String + packageVersion: PackageVersion! + success: Boolean! +} + +input PublishPublicKeyInput { + clientMutationId: String + key: String! + keyId: String! + verifyingSignatureId: String +} + +type PublishPublicKeyPayload { + clientMutationId: String + publicKey: PublicKey! + success: Boolean! +} + +type SignedUrl { + url: String! +} + +type Query { + getCommand(name: String!): Command + getCommands(names: [String!]!): [Command] + getContract(name: String!): Interface @deprecated(reason: "Please use getInterface instead") + getContractVersion(name: String!, version: String = null): InterfaceVersion @deprecated(reason: "Please use getInterfaceVersion instead") + getContracts(names: [String!]!): [Interface]! @deprecated(reason: "Please use getInterfaces instead") + getGlobalObject(slug: String!): GlobalObject + getInterface(name: String!): Interface + getInterfaceVersion(name: String!, version: String = "latest"): InterfaceVersion + getInterfaces(names: [String!]!): [Interface]! + getNamespace(name: String!): Namespace + getPackage(name: String!): Package + getPackageVersion(name: String!, version: String = "latest"): PackageVersion + getPackageVersions(names: [String!]!): [PackageVersion] + getPackages(names: [String!]!): [Package]! + getPasswordResetToken(token: String!): GetPasswordResetToken + getSignedUrlForPackageUpload(name:String!,version:String!): SignedUrl + getUser(username: String!): User + node( + # The ID of the object + id: ID! + ): Node + packages(after: String = null, before: String = null, first: Int = null, last: Int = null): PackageConnection + recentPackageVersions(after: String = null, before: String = null, curated: Boolean = null, first: Int = null, last: Int = null, offset: Int = null): PackageVersionConnection + search(after: String = null, before: String = null, curated: Boolean = null, first: Int = null, hasBindings: Boolean = null, isStandalone: Boolean = null, kind: [SearchKind!] = null, last: Int = null, orderBy: SearchOrderBy = null, publishDate: SearchPublishDate = null, query: String!, sort: SearchOrderSort = null, withInterfaces: [String!] = null): SearchConnection! + searchAutocomplete(after: String = null, before: String = null, first: Int = null, kind: [SearchKind!] = null, last: Int = null, query: String!): SearchConnection! + viewer: User +} + +input ReadNotificationInput { + clientMutationId: String + notificationId: ID! +} + +type ReadNotificationPayload { + clientMutationId: String + notification: UserNotification +} + +input RefreshInput { + clientMutationId: String + refreshToken: String +} + +type RefreshPayload { + clientMutationId: String + payload: GenericScalar! + refreshExpiresIn: Int! + refreshToken: String! + token: String! +} + +input RegisterUserInput { + clientMutationId: String + email: String! + fullName: String! + password: String! + username: String! +} + +type RegisterUserPayload { + clientMutationId: String + token: String +} + +# An enumeration. +enum RegistryNamespaceMaintainerInviteRoleChoices { + # Admin + ADMIN + + # Editor + EDITOR + + # Viewer + VIEWER +} + +# An enumeration. +enum RegistryNamespaceMaintainerRoleChoices { + # Admin + ADMIN + + # Editor + EDITOR + + # Viewer + VIEWER +} + +# An enumeration. +enum RegistryPackageMaintainerInviteRoleChoices { + # Admin + ADMIN + + # Editor + EDITOR + + # Viewer + VIEWER +} + +# An enumeration. +enum RegistryPackageMaintainerRoleChoices { + # Admin + ADMIN + + # Editor + EDITOR + + # Viewer + VIEWER +} + +input RemoveNamespaceCollaboratorInput { + clientMutationId: String + namespaceCollaboratorId: ID! +} + +input RemoveNamespaceCollaboratorInviteInput { + clientMutationId: String + inviteId: ID! +} + +type RemoveNamespaceCollaboratorInvitePayload { + clientMutationId: String + namespace: Namespace! +} + +type RemoveNamespaceCollaboratorPayload { + clientMutationId: String + namespace: Namespace! +} + +input RemovePackageCollaboratorInput { + clientMutationId: String + packageCollaboratorId: ID! +} + +input RemovePackageCollaboratorInviteInput { + clientMutationId: String + inviteId: ID! +} + +type RemovePackageCollaboratorInvitePayload { + clientMutationId: String + package: Package! +} + +type RemovePackageCollaboratorPayload { + clientMutationId: String + package: Package! +} + +input RemovePackageTransferRequestInput { + clientMutationId: String + packageTransferRequestId: ID! +} + +type RemovePackageTransferRequestPayload { + clientMutationId: String + package: Package! +} + +input RequestPackageTransferInput { + clientMutationId: String + newOwnerId: ID! + packageId: ID! +} + +type RequestPackageTransferPayload { + clientMutationId: String + package: Package! +} + +input RequestPasswordResetInput { + clientMutationId: String + email: String! +} + +type RequestPasswordResetPayload { + clientMutationId: String + email: String! + errors: [ErrorType] +} + +input RequestValidationEmailInput { + clientMutationId: String + + # The user id + userId: ID +} + +type RequestValidationEmailPayload { + clientMutationId: String + success: Boolean! + user: User +} + +input RevokeAPITokenInput { + clientMutationId: String + + # The API token ID + tokenId: ID! +} + +type RevokeAPITokenPayload { + clientMutationId: String + success: Boolean + token: APIToken +} + +enum Role { + ADMIN + EDITOR + VIEWER +} + +type SearchConnection { + # Contains the nodes in this connection. + edges: [SearchEdge]! + + # Pagination data for this connection. + pageInfo: PageInfo! +} + +# A Relay edge containing a `Search` and its cursor. +type SearchEdge { + # A cursor for use in pagination + cursor: String! + + # The item at the end of the edge + node: SearchResult +} + +enum SearchKind { + NAMESPACE + PACKAGE + USER +} + +enum SearchOrderBy { + ALPHABETICALLY + PUBLISHED_DATE + SIZE + TOTAL_DOWNLOADS +} + +enum SearchOrderSort { + ASC + DESC +} + +enum SearchPublishDate { + LAST_DAY + LAST_MONTH + LAST_WEEK + LAST_YEAR +} + +union SearchResult = Namespace | PackageVersion | User + +input SeePendingNotificationsInput { + clientMutationId: String +} + +type SeePendingNotificationsPayload { + clientMutationId: String + success: Boolean +} + +type Signature { + createdAt: DateTime! + data: String! + id: ID! + publicKey: PublicKey! +} + +input SocialAuthJWTInput { + accessToken: String! + clientMutationId: String + provider: String! +} + +# Social Auth for JSON Web Token (JWT) +type SocialAuthJWTPayload { + clientMutationId: String + social: SocialNode + token: String +} + +scalar SocialCamelJSON + +type SocialNode implements Node { + created: DateTime! + extraData: SocialCamelJSON + + # The ID of the object + id: ID! + modified: DateTime! + provider: String! + uid: String! + user: User! +} + +type Subscription { + packageVersionCreated(ownerId: ID = null, publishedBy: ID = null): PackageVersion! + userNotificationCreated(userId: ID!): UserNotificationCreated! +} + +input UnlikePackageInput { + clientMutationId: String + packageId: ID! +} + +type UnlikePackagePayload { + clientMutationId: String + package: Package! +} + +input UnwatchPackageInput { + clientMutationId: String + packageId: ID! +} + +type UnwatchPackagePayload { + clientMutationId: String + package: Package! +} + +input UpdateNamespaceCollaboratorRoleInput { + clientMutationId: String + namespaceCollaboratorId: ID! + role: Role! +} + +type UpdateNamespaceCollaboratorRolePayload { + clientMutationId: String + collaborator: NamespaceCollaborator! +} + +input UpdateNamespaceInput { + # The namespace avatar + avatar: String + clientMutationId: String + + # The namespace description + description: String + + # The namespace display name + displayName: String + + # The namespace slug name + name: String + namespaceId: ID! +} + +type UpdateNamespacePayload { + clientMutationId: String + namespace: Namespace! +} + +input UpdatePackageCollaboratorRoleInput { + clientMutationId: String + packageCollaboratorId: ID! + role: Role! +} + +type UpdatePackageCollaboratorRolePayload { + clientMutationId: String + collaborator: PackageCollaborator! +} + +input UpdatePackageInput { + clientMutationId: String + + # The package icon + icon: String + packageId: ID! +} + +type UpdatePackagePayload { + clientMutationId: String + package: Package! +} + +input UpdateUserInfoInput { + # The user avatar + avatar: String + + # The user bio + bio: String + clientMutationId: String + + # The user full name + fullName: String + + # The user Github (it can be the url, or the handle with or without the @) + github: String + + # The user location + location: String + + # The user Twitter (it can be the url, or the handle with or without the @) + twitter: String + + # The user id + userId: ID + + # The user website (it must be a valid url) + websiteUrl: String +} + +type UpdateUserInfoPayload { + clientMutationId: String + user: User +} + +type User implements Node & PackageOwner { + apiTokens(after: String = null, before: String = null, first: Int = null, last: Int = null): APITokenConnection + avatar(size: Int = 80): String! + bio: String + dateJoined: DateTime! + email: String! + firstName: String! + fullName: String! + githubUrl: String + globalName: String! + + # The ID of the object + id: ID! + isEmailValidated: Boolean! + isViewer: Boolean! + lastName: String! + location: String + namespaceInvitesIncoming(after: String = null, before: String = null, first: Int = null, last: Int = null): NamespaceCollaboratorInviteConnection + namespaces(after: String = null, before: String = null, first: Int = null, last: Int = null): NamespaceConnection + notifications(after: String = null, before: String = null, first: Int = null, last: Int = null): UserNotificationConnection + packageInvitesIncoming(after: String = null, before: String = null, first: Int = null, last: Int = null): PackageCollaboratorInviteConnection + packageTransfersIncoming(after: String = null, before: String = null, first: Int = null, last: Int = null): PackageTransferRequestConnection + packageVersions(after: String = null, before: String = null, first: Int = null, last: Int = null): PackageVersionConnection + packages(after: String = null, before: String = null, collaborating: Boolean = null, first: Int = null, last: Int = null): PackageConnection + publicActivity(after: String = null, before: String = null, first: Int = null, last: Int = null): ActivityEventConnection! + twitterUrl: String + + # Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only. + username: String! + websiteUrl: String +} + +type UserConnection { + # Contains the nodes in this connection. + edges: [UserEdge]! + + # Pagination data for this connection. + pageInfo: PageInfo! +} + +# A Relay edge containing a `User` and its cursor. +type UserEdge { + # A cursor for use in pagination + cursor: String! + + # The item at the end of the edge + node: User +} + +type UserNotification implements Node { + body: UserNotificationBody! + createdAt: DateTime! + icon: String + + # The ID of the object + id: ID! + kind: UserNotificationKind + seenState: UserNotificationSeenState! +} + +type UserNotificationBody { + ranges: [NodeBodyRange]! + text: String! +} + +type UserNotificationConnection { + # Contains the nodes in this connection. + edges: [UserNotificationEdge]! + hasPendingNotifications: Boolean! + + # Pagination data for this connection. + pageInfo: PageInfo! +} + +type UserNotificationCreated { + notification: UserNotification + notificationDeletedId: ID +} + +# A Relay edge containing a `UserNotification` and its cursor. +type UserNotificationEdge { + # A cursor for use in pagination + cursor: String! + + # The item at the end of the edge + node: UserNotification +} + +union UserNotificationKind = UserNotificationKindIncomingPackageInvite | UserNotificationKindIncomingPackageTransfer | UserNotificationKindPublishedPackageVersion + +type UserNotificationKindIncomingPackageInvite { + packageInvite: PackageCollaboratorInvite! +} + +type UserNotificationKindIncomingPackageTransfer { + packageTransferRequest: PackageTransferRequest! +} + +type UserNotificationKindPublishedPackageVersion { + packageVersion: PackageVersion! +} + +enum UserNotificationSeenState { + SEEN + SEEN_AND_READ + UNSEEN +} + +input ValidateUserEmailInput { + challenge: String! + clientMutationId: String + + # The user id + userId: ID +} + +type ValidateUserEmailPayload { + clientMutationId: String + user: User +} + +input ValidateUserPasswordInput { + clientMutationId: String + password: String! +} + +type ValidateUserPasswordPayload { + clientMutationId: String + success: Boolean +} + +input VerifyInput { + clientMutationId: String + token: String +} + +type VerifyPayload { + clientMutationId: String + payload: GenericScalar! +} + +input WatchPackageInput { + clientMutationId: String + packageId: ID! +} + +type WatchPackagePayload { + clientMutationId: String + package: Package! +} diff --git a/lib/registry/src/lib.rs b/lib/registry/src/lib.rs new file mode 100644 index 00000000000..61d4cd86fcd --- /dev/null +++ b/lib/registry/src/lib.rs @@ -0,0 +1,1146 @@ +use std::collections::BTreeMap; +use std::env; +use std::path::{Path, PathBuf}; +use std::time::Duration; + +use serde::Deserialize; +use serde::Serialize; + +pub mod graphql { + + use graphql_client::*; + #[cfg(not(target_os = "wasi"))] + use reqwest::{ + blocking::{multipart::Form, Client}, + header::USER_AGENT, + }; + use std::env; + use std::time::Duration; + #[cfg(target_os = "wasi")] + use {wasm_bus_reqwest::prelude::header::*, wasm_bus_reqwest::prelude::*}; + + mod proxy { + //! Code for dealing with setting things up to proxy network requests + use thiserror::Error; + + #[derive(Debug, Error)] + pub enum ProxyError { + #[error("Failed to parse URL from {}: {}", url_location, error_message)] + UrlParseError { + url_location: String, + error_message: String, + }, + + #[error("Could not connect to proxy: {0}")] + ConnectionError(String), + } + + /// Tries to set up a proxy + /// + /// This function reads from wapm config's `proxy.url` first, then checks + /// `ALL_PROXY`, `HTTPS_PROXY`, and `HTTP_PROXY` environment variables, in both + /// upper case and lower case, in that order. + /// + /// If a proxy is specified in wapm config's `proxy.url`, it is assumed + /// to be a general proxy + /// + /// A return value of `Ok(None)` means that there was no attempt to set up a proxy, + /// `Ok(Some(proxy))` means that the proxy was set up successfully, and `Err(e)` that + /// there was a failure while attempting to set up the proxy. + pub fn maybe_set_up_proxy() -> anyhow::Result> { + use std::env; + let proxy = if let Ok(proxy_url) = + env::var("ALL_PROXY").or_else(|_| env::var("all_proxy")) + { + reqwest::Proxy::all(&proxy_url).map(|proxy| (proxy_url, proxy, "ALL_PROXY")) + } else if let Ok(https_proxy_url) = + env::var("HTTPS_PROXY").or_else(|_| env::var("https_proxy")) + { + reqwest::Proxy::https(&https_proxy_url) + .map(|proxy| (https_proxy_url, proxy, "HTTPS_PROXY")) + } else if let Ok(http_proxy_url) = + env::var("HTTP_PROXY").or_else(|_| env::var("http_proxy")) + { + reqwest::Proxy::http(&http_proxy_url) + .map(|proxy| (http_proxy_url, proxy, "http_proxy")) + } else { + return Ok(None); + } + .map_err(|e| ProxyError::ConnectionError(e.to_string())) + .and_then( + |(proxy_url_str, proxy, url_location): (String, _, &'static str)| { + url::Url::parse(&proxy_url_str) + .map_err(|e| ProxyError::UrlParseError { + url_location: url_location.to_string(), + error_message: e.to_string(), + }) + .map(|url| { + if !(url.username().is_empty()) && url.password().is_some() { + proxy.basic_auth(url.username(), url.password().unwrap_or_default()) + } else { + proxy + } + }) + }, + )?; + + Ok(Some(proxy)) + } + } + + #[derive(GraphQLQuery)] + #[graphql( + schema_path = "graphql/schema.graphql", + query_path = "graphql/queries/get_package_version.graphql", + response_derives = "Debug" + )] + pub(crate) struct GetPackageVersionQuery; + + #[derive(GraphQLQuery)] + #[graphql( + schema_path = "graphql/schema.graphql", + query_path = "graphql/queries/get_package_by_command.graphql", + response_derives = "Debug" + )] + pub(crate) struct GetPackageByCommandQuery; + + #[derive(GraphQLQuery)] + #[graphql( + schema_path = "graphql/schema.graphql", + query_path = "graphql/queries/get_packages.graphql", + response_derives = "Debug" + )] + pub(crate) struct GetPackagesQuery; + + #[derive(GraphQLQuery)] + #[graphql( + schema_path = "graphql/schema.graphql", + query_path = "graphql/queries/test_if_registry_present.graphql", + response_derives = "Debug" + )] + pub(crate) struct TestIfRegistryPresent; + + #[cfg(target_os = "wasi")] + pub fn whoami_distro() -> String { + whoami::os().to_lowercase() + } + + #[cfg(not(target_os = "wasi"))] + pub fn whoami_distro() -> String { + whoami::distro().to_lowercase() + } + + pub fn execute_query_modifier_inner_check_json( + registry_url: &str, + login_token: &str, + query: &QueryBody, + timeout: Option, + form_modifier: F, + ) -> anyhow::Result<()> + where + V: serde::Serialize, + F: FnOnce(Form) -> Form, + { + let client = { + let builder = Client::builder(); + + #[cfg(not(target_os = "wasi"))] + let builder = if let Some(proxy) = proxy::maybe_set_up_proxy()? { + builder.proxy(proxy) + } else { + builder + }; + builder.build()? + }; + + let vars = serde_json::to_string(&query.variables).unwrap(); + + let form = Form::new() + .text("query", query.query.to_string()) + .text("operationName", query.operation_name.to_string()) + .text("variables", vars); + + let form = form_modifier(form); + + let user_agent = format!( + "wapm/{} {} {}", + env!("CARGO_PKG_VERSION"), + whoami::platform(), + whoami_distro(), + ); + + let mut res = client + .post(registry_url) + .multipart(form) + .bearer_auth( + env::var("WAPM_REGISTRY_TOKEN").unwrap_or_else(|_| login_token.to_string()), + ) + .header(USER_AGENT, user_agent); + + if let Some(t) = timeout { + res = res.timeout(t); + } + + let res = res.send()?; + + let _: Response = res.json()?; + + Ok(()) + } + + pub fn execute_query_modifier_inner( + registry_url: &str, + login_token: &str, + query: &QueryBody, + timeout: Option, + form_modifier: F, + ) -> anyhow::Result + where + for<'de> R: serde::Deserialize<'de>, + V: serde::Serialize, + F: FnOnce(Form) -> Form, + { + let client = { + let builder = Client::builder(); + + #[cfg(not(target_os = "wasi"))] + let builder = if let Some(proxy) = proxy::maybe_set_up_proxy()? { + builder.proxy(proxy) + } else { + builder + }; + builder.build()? + }; + + let vars = serde_json::to_string(&query.variables).unwrap(); + + let form = Form::new() + .text("query", query.query.to_string()) + .text("operationName", query.operation_name.to_string()) + .text("variables", vars); + + let form = form_modifier(form); + + let user_agent = format!( + "wapm/{} {} {}", + env!("CARGO_PKG_VERSION"), + whoami::platform(), + whoami_distro(), + ); + + let mut res = client + .post(registry_url) + .multipart(form) + .bearer_auth( + env::var("WAPM_REGISTRY_TOKEN").unwrap_or_else(|_| login_token.to_string()), + ) + .header(USER_AGENT, user_agent); + + if let Some(t) = timeout { + res = res.timeout(t); + } + + let res = res.send()?; + let response_body: Response = res.json()?; + if let Some(errors) = response_body.errors { + let error_messages: Vec = errors.into_iter().map(|err| err.message).collect(); + return Err(anyhow::anyhow!("{}", error_messages.join(", "))); + } + Ok(response_body.data.expect("missing response data")) + } + + pub fn execute_query( + registry_url: &str, + login_token: &str, + query: &QueryBody, + ) -> anyhow::Result + where + for<'de> R: serde::Deserialize<'de>, + V: serde::Serialize, + { + execute_query_modifier_inner(registry_url, login_token, query, None, |f| f) + } + + pub fn execute_query_with_timeout( + registry_url: &str, + login_token: &str, + timeout: Duration, + query: &QueryBody, + ) -> anyhow::Result + where + for<'de> R: serde::Deserialize<'de>, + V: serde::Serialize, + { + execute_query_modifier_inner(registry_url, login_token, query, Some(timeout), |f| f) + } +} + +pub static GLOBAL_CONFIG_FILE_NAME: &str = if cfg!(target_os = "wasi") { + "/.private/wapm.toml" +} else { + "wapm.toml" +}; + +#[derive(Deserialize, Default, Serialize, Debug, PartialEq)] +pub struct PartialWapmConfig { + /// The number of seconds to wait before checking the registry for a new + /// version of the package. + #[serde(default = "wax_default_cooldown")] + pub wax_cooldown: i32, + + /// The registry that wapm will connect to. + pub registry: Registries, + + /// Whether or not telemetry is enabled. + #[cfg(feature = "telemetry")] + #[serde(default)] + pub telemetry: Telemetry, + + /// Whether or not updated notifications are enabled. + #[cfg(feature = "update-notifications")] + #[serde(default)] + pub update_notifications: UpdateNotifications, + + /// The proxy to use when connecting to the Internet. + #[serde(default)] + pub proxy: Proxy, +} + +pub const fn wax_default_cooldown() -> i32 { + 5 * 60 +} + +#[derive(Deserialize, Serialize, Debug, PartialEq, Default)] +pub struct Proxy { + pub url: Option, +} + +#[derive(Deserialize, Serialize, Debug, PartialEq, Default)] +pub struct UpdateNotifications { + pub enabled: String, +} + +#[cfg(feature = "telemetry")] +#[derive(Deserialize, Serialize, Debug, PartialEq)] +pub struct Telemetry { + pub enabled: String, +} + +#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)] +#[serde(untagged)] +pub enum Registries { + Single(Registry), + Multi(MultiRegistry), +} + +#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)] +pub struct MultiRegistry { + /// Currently active registry + pub current: String, + /// Map from "RegistryUrl" to "LoginToken", in order to + /// be able to be able to easily switch between registries + pub tokens: BTreeMap, +} + +impl Default for Registries { + fn default() -> Self { + Registries::Single(Registry { + url: format_graphql("https://registry.wapm.io"), + token: None, + }) + } +} + +#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)] +pub struct Registry { + pub url: String, + pub token: Option, +} + +fn format_graphql(registry: &str) -> String { + if registry.ends_with("/graphql") { + registry.to_string() + } else if registry.ends_with('/') { + format!("{}graphql", registry) + } else { + format!("{}/graphql", registry) + } +} + +impl PartialWapmConfig { + pub fn from_file() -> Result { + let path = Self::get_file_location()?; + + match std::fs::read_to_string(&path) { + Ok(config_toml) => { + toml::from_str(&config_toml).map_err(|e| format!("could not parse {path:?}: {e}")) + } + Err(_e) => Ok(Self::default()), + } + } + + pub fn get_current_dir() -> std::io::Result { + #[cfg(target_os = "wasi")] + if let Some(pwd) = std::env::var("PWD").ok() { + return Ok(PathBuf::from(pwd)); + } + std::env::current_dir() + } + + pub fn get_folder() -> Result { + Ok( + if let Some(folder_str) = env::var("WASMER_DIR").ok().filter(|s| !s.is_empty()) { + let folder = PathBuf::from(folder_str); + std::fs::create_dir_all(folder.clone()) + .map_err(|e| format!("cannot create config directory: {e}"))?; + folder + } else { + #[allow(unused_variables)] + let default_dir = Self::get_current_dir() + .ok() + .unwrap_or_else(|| PathBuf::from("/".to_string())); + #[cfg(feature = "dirs")] + let home_dir = + dirs::home_dir().ok_or(GlobalConfigError::CannotFindHomeDirectory)?; + #[cfg(not(feature = "dirs"))] + let home_dir = std::env::var("HOME") + .ok() + .unwrap_or_else(|| default_dir.to_string_lossy().to_string()); + let mut folder = PathBuf::from(home_dir); + folder.push(".wasmer"); + std::fs::create_dir_all(folder.clone()) + .map_err(|e| format!("cannot create config directory: {e}"))?; + folder + }, + ) + } + + fn get_file_location() -> Result { + Ok(Self::get_folder()?.join(GLOBAL_CONFIG_FILE_NAME)) + } +} + +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord)] +pub struct PackageDownloadInfo { + pub registry: String, + pub package: String, + pub version: String, + pub is_latest_version: bool, + pub commands: String, + pub manifest: String, + pub url: String, +} + +pub fn get_command_local(_name: &str) -> Result { + Err("unimplemented".to_string()) +} + +pub fn get_package_local_dir( + registry_host: &str, + name: &str, + version: &str, +) -> Result { + if !name.contains('/') { + return Err(format!( + "package name has to be in the format namespace/package: {name:?}" + )); + } + let (namespace, name) = name + .split_once('/') + .ok_or_else(|| format!("missing namespace / name for {name:?}"))?; + let install_dir = get_global_install_dir(registry_host) + .ok_or_else(|| format!("no install dir for {name:?}"))?; + Ok(install_dir.join(namespace).join(name).join(version)) +} + +#[derive(Debug, Clone)] +pub struct LocalPackage { + pub registry: String, + pub name: String, + pub version: String, +} + +impl LocalPackage { + pub fn get_path(&self) -> Result { + let host = url::Url::parse(&self.registry) + .ok() + .and_then(|o| o.host_str().map(|s| s.to_string())) + .unwrap_or_else(|| self.registry.clone()); + + get_package_local_dir(&host, &self.name, &self.version) + } +} + +/// Returns the (manifest, .wasm file name), given a package dir +pub fn get_executable_file_from_path( + package_dir: &PathBuf, + command: Option<&str>, +) -> Result<(wapm_toml::Manifest, PathBuf), anyhow::Error> { + let wapm_toml = std::fs::read_to_string(package_dir.join("wapm.toml")) + .map_err(|_| anyhow::anyhow!("Package {package_dir:?} has no wapm.toml"))?; + + let wapm_toml = toml::from_str::(&wapm_toml) + .map_err(|e| anyhow::anyhow!("Could not parse toml for {package_dir:?}: {e}"))?; + + let name = wapm_toml.package.name.clone(); + let version = wapm_toml.package.version.clone(); + + let commands = wapm_toml.command.clone().unwrap_or_default(); + let entrypoint_module = match command { + Some(s) => commands.iter().find(|c| c.get_name() == s).ok_or_else(|| { + anyhow::anyhow!("Cannot run {name}@{version}: package has no command {s:?}") + })?, + None => commands.first().ok_or_else(|| { + anyhow::anyhow!("Cannot run {name}@{version}: package has no commands") + })?, + }; + + let module_name = entrypoint_module.get_module(); + let modules = wapm_toml.module.clone().unwrap_or_default(); + let entrypoint_module = modules + .iter() + .find(|m| m.name == module_name) + .ok_or_else(|| { + anyhow::anyhow!( + "Cannot run {name}@{version}: module {module_name} not found in wapm.toml" + ) + })?; + + let entrypoint_source = package_dir.join(&entrypoint_module.source); + + Ok((wapm_toml, entrypoint_source)) +} + +fn get_all_names_in_dir(dir: &PathBuf) -> Vec<(PathBuf, String)> { + if !dir.is_dir() { + return Vec::new(); + } + + let read_dir = match std::fs::read_dir(dir) { + Ok(o) => o, + Err(_) => return Vec::new(), + }; + + let entries = read_dir + .map(|res| res.map(|e| e.path())) + .collect::, std::io::Error>>(); + + let registry_entries = match entries { + Ok(o) => o, + Err(_) => return Vec::new(), + }; + + registry_entries + .into_iter() + .filter_map(|re| Some((re.clone(), re.file_name()?.to_str()?.to_string()))) + .collect() +} + +/// Returns a list of all locally installed packages +pub fn get_all_local_packages(registry: Option<&str>) -> Vec { + let mut packages = Vec::new(); + let registries = match registry { + Some(s) => vec![s.to_string()], + None => get_all_available_registries().unwrap_or_default(), + }; + + let mut registry_hosts = registries + .into_iter() + .filter_map(|s| url::Url::parse(&s).ok()?.host_str().map(|s| s.to_string())) + .collect::>(); + + let mut registries_in_root_dir = get_checkouts_dir() + .as_ref() + .map(get_all_names_in_dir) + .unwrap_or_default() + .into_iter() + .filter_map(|(path, p)| if path.is_dir() { Some(p) } else { None }) + .collect(); + + registry_hosts.append(&mut registries_in_root_dir); + registry_hosts.sort(); + registry_hosts.dedup(); + + for host in registry_hosts { + let root_dir = match get_global_install_dir(&host) { + Some(o) => o, + None => continue, + }; + + for (username_path, user_name) in get_all_names_in_dir(&root_dir) { + for (package_path, package_name) in get_all_names_in_dir(&username_path) { + for (version_path, package_version) in get_all_names_in_dir(&package_path) { + let _ = match std::fs::read_to_string(version_path.join("wapm.toml")) { + Ok(o) => o, + Err(_) => continue, + }; + packages.push(LocalPackage { + registry: host.clone(), + name: format!("{user_name}/{package_name}"), + version: package_version, + }); + } + } + } + } + + packages +} + +pub fn get_local_package( + registry: Option<&str>, + name: &str, + version: Option<&str>, +) -> Option { + get_all_local_packages(registry) + .iter() + .find(|p| { + if p.name != name { + return false; + } + if let Some(v) = version { + if p.version != v { + return false; + } + } + true + }) + .cloned() +} + +pub fn query_command_from_registry( + registry_url: &str, + command_name: &str, +) -> Result { + use crate::graphql::{execute_query, get_package_by_command_query, GetPackageByCommandQuery}; + use graphql_client::GraphQLQuery; + + let q = GetPackageByCommandQuery::build_query(get_package_by_command_query::Variables { + command_name: command_name.to_string(), + }); + + let response: get_package_by_command_query::ResponseData = execute_query(registry_url, "", &q) + .map_err(|e| format!("Error sending GetPackageByCommandQuery:  {e}"))?; + + let command = response + .get_command + .ok_or_else(|| "GetPackageByCommandQuery: no get_command".to_string())?; + + let package = command.package_version.package.display_name; + let version = command.package_version.version; + let url = command.package_version.distribution.download_url; + + Ok(PackageDownloadInfo { + registry: registry_url.to_string(), + package, + version, + is_latest_version: command.package_version.is_last_version, + manifest: command.package_version.manifest, + commands: command_name.to_string(), + url, + }) +} + +#[derive(Debug, Clone, PartialEq, PartialOrd)] +pub enum QueryPackageError { + AmbigouusName { + name: String, + packages: Vec, + }, + ErrorSendingQuery(String), + NoPackageFound { + name: String, + version: Option, + packages: Vec, + }, +} + +impl QueryPackageError { + pub fn get_packages(&self) -> Vec { + match self { + QueryPackageError::AmbigouusName { name: _, packages } + | QueryPackageError::NoPackageFound { + name: _, + version: _, + packages, + } => packages.clone(), + _ => Vec::new(), + } + } +} + +/// Returns true if a package has a newer version +/// +/// Also returns true if the package is not installed yet. +pub fn get_if_package_has_new_version( + registry_url: &str, + name: &str, + version: Option, + max_timeout: Duration, +) -> Result, String> { + if !test_if_registry_present(registry_url).unwrap_or(false) { + return Ok(None); + } + + let host = match url::Url::parse(registry_url) { + Ok(o) => match o.host_str().map(|s| s.to_string()) { + Some(s) => s, + None => return Err(format!("invalid host: {registry_url}")), + }, + Err(_) => return Err(format!("invalid host: {registry_url}")), + }; + + let (namespace, name) = name + .split_once('/') + .ok_or_else(|| format!("missing namespace / name for {name:?}"))?; + + let package_dir = get_global_install_dir(&host).map(|path| path.join(namespace).join(name)); + + let package_dir = match package_dir { + Some(s) => s, + None => return Err(format!("package dir does not exist {name}/{namespace}")), + }; + + // all installed versions of this package + let mut all_installed_versions = Vec::new(); + + let may_have_newer_version = match version { + Some(ref o) => Some(()) + .map(|_| { + let modified = package_dir.join(&o).metadata().ok()?.modified().ok()?; + let version = semver::Version::parse(o).ok()?; + all_installed_versions.push(version); + if modified.elapsed().ok()? <= max_timeout { + Some(()) + } else { + None + } + }) + .is_some(), + None => { + let read_dir = match std::fs::read_dir(&package_dir) { + Ok(o) => o, + Err(_) => return Err(format!("{}", package_dir.display())), + }; + + // if there is any package younger than $max_timeout, return false + read_dir + .filter_map(|entry| { + let entry = entry.ok()?; + let version = semver::Version::parse(entry.file_name().to_str()?).ok()?; + all_installed_versions.push(version); + let modified = entry.metadata().ok()?.modified().ok()?; + if modified.elapsed().ok()? <= max_timeout { + Some(()) + } else { + None + } + }) + .next() + .is_some() + } + }; + + // If the version is specified, don't check for updates + // (since the package returned from the server would be the same) + if all_installed_versions.is_empty() || (may_have_newer_version && version.is_none()) { + let available_packages = + query_available_packages_from_registry(registry_url, name).unwrap_or_default(); + + for p in available_packages { + if p.is_latest_version { + return Ok(Some(p)); + } + } + } + + Err(format!( + "Cannot find package {name:?} either locally or in registry" + )) +} + +pub fn query_available_packages_from_registry( + registry_url: &str, + name: &str, +) -> Result, QueryPackageError> { + use crate::graphql::{execute_query, get_packages_query, GetPackagesQuery}; + use graphql_client::GraphQLQuery; + + let q = GetPackagesQuery::build_query(get_packages_query::Variables { + names: vec![name.to_string()], + }); + + let response: get_packages_query::ResponseData = + execute_query(registry_url, "", &q).map_err(|e| { + QueryPackageError::ErrorSendingQuery(format!("Error sending GetPackagesQuery: {e}")) + })?; + + let available_packages = response + .package + .iter() + .filter_map(|p| { + let p = p.as_ref()?; + let mut versions = Vec::new(); + + for v in p.versions.iter() { + for v in v.iter() { + let v = match v.as_ref() { + Some(s) => s, + None => continue, + }; + + let manifest = toml::from_str::(&v.manifest).ok()?; + + versions.push(PackageDownloadInfo { + registry: registry_url.to_string(), + package: p.name.clone(), + + version: v.version.clone(), + is_latest_version: v.is_last_version, + manifest: v.manifest.clone(), + + commands: manifest + .command + .unwrap_or_default() + .iter() + .map(|s| s.get_name()) + .collect::>() + .join(", "), + + url: v.distribution.download_url.clone(), + }); + } + } + + Some(versions) + }) + .collect::>() + .into_iter() + .flat_map(|v| v.into_iter()) + .collect::>(); + + Ok(available_packages) +} + +/// Returns the download info of the packages, on error returns all the available packages +/// i.e. (("foo/python", "wapm.io"), ("bar/python" "wapm.io"))) +pub fn query_package_from_registry( + registry_url: &str, + name: &str, + version: Option<&str>, +) -> Result { + let available_packages = query_available_packages_from_registry(registry_url, name)?; + + if !name.contains('/') { + return Err(QueryPackageError::AmbigouusName { + name: name.to_string(), + packages: available_packages, + }); + } + + let mut queried_packages = available_packages + .iter() + .filter(|v| { + if name.contains('/') && v.package != name { + return false; + } + + if version.is_some() && version != Some(&v.version) { + return false; + } + + true + }) + .collect::>(); + + let selected_package = match version { + Some(s) => queried_packages.iter().find(|p| p.version == s), + None => { + if let Some(latest) = queried_packages.iter().find(|s| s.is_latest_version) { + Some(latest) + } else { + // sort package by version, select highest + queried_packages.sort_by_key(|k| semver::Version::parse(&k.version).ok()); + queried_packages.first() + } + } + }; + + match selected_package { + None => { + return Err(QueryPackageError::NoPackageFound { + name: name.to_string(), + version: version.as_ref().map(|s| s.to_string()), + packages: available_packages, + }); + } + Some(s) => Ok((*s).clone()), + } +} + +pub fn get_checkouts_dir() -> Option { + Some(PartialWapmConfig::get_folder().ok()?.join("checkouts")) +} + +/// Returs the path to the directory where all packages on this computer are being stored +pub fn get_global_install_dir(registry_host: &str) -> Option { + Some(get_checkouts_dir()?.join(registry_host)) +} + +pub fn download_and_unpack_targz(url: &str, target_path: &Path) -> Result { + let target_targz_path = target_path.to_path_buf().join("package.tar.gz"); + + let mut resp = + reqwest::blocking::get(url).map_err(|e| format!("failed to download {url}: {e}"))?; + + if !target_targz_path.exists() { + // create all the parent paths, only remove the created directory, not the parent dirs + let _ = std::fs::create_dir_all(&target_targz_path); + let _ = std::fs::remove_dir(&target_targz_path); + } + + { + let mut file = std::fs::File::create(&target_targz_path).map_err(|e| { + format!( + "failed to download {url} into {}: {e}", + target_targz_path.display() + ) + })?; + + reqwest::blocking::get(url).map_err(|e| format!("{e}"))?; + + resp.copy_to(&mut file).map_err(|e| format!("{e}"))?; + } + + let file = std::fs::File::open(&target_targz_path).map_err(|e| { + format!( + "failed to download {url} into {}: {e}", + target_targz_path.display() + ) + })?; + + let gz_decoded = flate2::read::GzDecoder::new(file); + let mut ar = tar::Archive::new(gz_decoded); + ar.unpack(target_path) + .map_err(|e| format!("failed to unpack {}: {e}", target_targz_path.display()))?; + + let _ = std::fs::remove_file(target_targz_path); + + Ok(target_path.to_path_buf()) +} + +/// Given a triple of [registry, name, version], downloads and installs the +/// .tar.gz if it doesn't yet exist, returns the (package dir, entrypoint .wasm file path) +pub fn install_package( + registry: Option<&str>, + name: &str, + version: Option<&str>, + package_download_info: Option, + force_install: bool, +) -> Result<(LocalPackage, PathBuf), String> { + let package_info = match package_download_info { + Some(s) => s, + None => { + let registries = match registry { + Some(s) => vec![s.to_string()], + None => get_all_available_registries()?, + }; + let mut url_of_package = None; + let mut error_packages = Vec::new(); + + let version_str = match version { + None => name.to_string(), + Some(v) => format!("{name}@{v}"), + }; + + let registries_searched = registries + .iter() + .filter_map(|s| url::Url::parse(s).ok()) + .filter_map(|s| Some(s.host_str()?.to_string())) + .collect::>(); + + let mut error_str = + format!("Package {version_str} not found in registries {registries_searched:?}."); + + for r in registries.iter() { + let registry_test = test_if_registry_present(r); + if !registry_test.clone().unwrap_or(false) { + continue; + } + + if !force_install { + let package_has_new_version = get_if_package_has_new_version( + r, + name, + version.map(|s| s.to_string()), + Duration::from_secs(60 * 5), + ); + if let Ok(Some(package)) = package_has_new_version { + url_of_package = Some((r, package)); + break; + } + } + + match query_package_from_registry(r, name, version) { + Ok(o) => { + url_of_package = Some((r, o)); + break; + } + Err(e) => { + error_packages.push(e); + } + } + } + + let mut did_you_mean = error_packages + .iter() + .flat_map(|error| { + if let QueryPackageError::AmbigouusName { name, packages: _ } = error { + error_str = format!("Ambigouus package name {name:?}. Please specify the package in the namespace/name format."); + } + let packages = error.get_packages(); + packages.iter().filter_map(|f| { + let from = url::Url::parse(&f.registry).ok()?.host_str()?.to_string(); + Some(format!(" {}@{} (from {from})", f.package, f.version)) + }) + .collect::>() + .into_iter() + }) + .collect::>(); + + let did_you_mean = if did_you_mean.is_empty() { + String::new() + } else { + did_you_mean.sort(); + did_you_mean.dedup(); + format!("\r\n\r\nDid you mean:\r\n{}\r\n", did_you_mean.join("\r\n")) + }; + + let (_, package_info) = + url_of_package.ok_or_else(|| format!("{error_str}{did_you_mean}"))?; + + package_info + } + }; + + let host = url::Url::parse(&package_info.registry) + .map_err(|e| format!("invalid url: {}: {e}", package_info.registry))? + .host_str() + .ok_or_else(|| format!("invalid url: {}", package_info.registry))? + .to_string(); + + let dir = get_package_local_dir(&host, &package_info.package, &package_info.version)?; + + let version = package_info.version; + let name = package_info.package; + + if !dir.join("wapm.toml").exists() || force_install { + download_and_unpack_targz(&package_info.url, &dir)?; + } + + Ok(( + LocalPackage { + registry: package_info.registry, + name, + version, + }, + dir, + )) +} + +pub fn test_if_registry_present(registry: &str) -> Result { + use crate::graphql::{test_if_registry_present, TestIfRegistryPresent}; + use graphql_client::GraphQLQuery; + + let q = TestIfRegistryPresent::build_query(test_if_registry_present::Variables {}); + let _ = crate::graphql::execute_query_modifier_inner_check_json( + registry, + "", + &q, + Some(Duration::from_secs(1)), + |f| f, + ) + .map_err(|e| format!("{e}"))?; + + Ok(true) +} + +pub fn get_all_available_registries() -> Result, String> { + let config = PartialWapmConfig::from_file()?; + let mut registries = Vec::new(); + match config.registry { + Registries::Single(s) => { + registries.push(format_graphql(&s.url)); + } + Registries::Multi(m) => { + for key in m.tokens.keys() { + registries.push(format_graphql(key)); + } + } + } + Ok(registries) +} + +// TODO: this test is segfaulting only on linux-musl, no other OS +// See https://github.com/wasmerio/wasmer/pull/3215 +#[cfg(not(target_env = "musl"))] +#[test] +fn test_install_package() { + println!("test install package..."); + let registry = "https://registry.wapm.io/graphql"; + if !test_if_registry_present(registry).unwrap_or(false) { + panic!("registry.wapm.io not reachable, test will fail"); + } + println!("registry present"); + + let wabt = query_package_from_registry(registry, "wasmer/wabt", Some("1.0.29")).unwrap(); + + println!("wabt queried: {wabt:#?}"); + + assert_eq!(wabt.registry, registry); + assert_eq!(wabt.package, "wasmer/wabt"); + assert_eq!(wabt.version, "1.0.29"); + assert_eq!( + wabt.commands, + "wat2wasm, wast2json, wasm2wat, wasm-interp, wasm-validate, wasm-strip" + ); + assert_eq!( + wabt.url, + "https://registry-cdn.wapm.io/packages/wasmer/wabt/wabt-1.0.29.tar.gz".to_string() + ); + + let (package, _) = + install_package(Some(registry), "wasmer/wabt", Some("1.0.29"), None, true).unwrap(); + + println!("package installed: {package:#?}"); + + assert_eq!( + package.get_path().unwrap(), + get_global_install_dir("registry.wapm.io") + .unwrap() + .join("wasmer") + .join("wabt") + .join("1.0.29") + ); + + let all_installed_packages = get_all_local_packages(Some(registry)); + + println!("all_installed_packages: {all_installed_packages:#?}"); + + let is_installed = all_installed_packages + .iter() + .any(|p| p.name == "wasmer/wabt" && p.version == "1.0.29"); + + println!("is_installed: {is_installed:#?}"); + + if !is_installed { + let panic_str = get_all_local_packages(Some(registry)) + .iter() + .map(|p| format!("{} {} {}", p.registry, p.name, p.version)) + .collect::>() + .join("\r\n"); + panic!("get all local packages: failed to install:\r\n{panic_str}"); + } + + println!("ok, done"); +} diff --git a/lib/wasi-types/wit-bindgen b/lib/wasi-types/wit-bindgen new file mode 160000 index 00000000000..44a2bf81489 --- /dev/null +++ b/lib/wasi-types/wit-bindgen @@ -0,0 +1 @@ +Subproject commit 44a2bf8148932590479bd64b9f90502b14c3b0d3 diff --git a/tests/examples/test.py b/tests/examples/test.py new file mode 100644 index 00000000000..ce47b771f4f --- /dev/null +++ b/tests/examples/test.py @@ -0,0 +1 @@ +print("hello") \ No newline at end of file diff --git a/tests/integration/cli/tests/run.rs b/tests/integration/cli/tests/run.rs index bb97b67ca04..140f13b8b7a 100644 --- a/tests/integration/cli/tests/run.rs +++ b/tests/integration/cli/tests/run.rs @@ -42,6 +42,129 @@ fn run_wasi_works() -> anyhow::Result<()> { Ok(()) } +#[test] +fn test_wasmer_run_works_with_dir() -> anyhow::Result<()> { + let temp_dir = tempfile::TempDir::new()?; + let qjs_path = temp_dir.path().join("qjs.wasm"); + + std::fs::copy(wasi_test_wasm_path(), &qjs_path)?; + std::fs::copy( + format!("{}/{}", C_ASSET_PATH, "qjs-wapm.toml"), + temp_dir.path().join("wapm.toml"), + )?; + + assert!(temp_dir.path().exists()); + assert!(temp_dir.path().join("wapm.toml").exists()); + assert!(temp_dir.path().join("qjs.wasm").exists()); + + // test with "wasmer qjs.wasm" + let output = Command::new(get_wasmer_path()) + .arg(temp_dir.path()) + .arg("--") + .arg("--quit") + .output()?; + + let stdout = std::str::from_utf8(&output.stdout) + .expect("stdout is not utf8! need to handle arbitrary bytes"); + + if !output.status.success() { + bail!( + "running {} failed with: stdout: {}\n\nstderr: {}", + qjs_path.display(), + stdout, + std::str::from_utf8(&output.stderr) + .expect("stderr is not utf8! need to handle arbitrary bytes") + ); + } + + // test again with "wasmer run qjs.wasm" + let output = Command::new(get_wasmer_path()) + .arg("run") + .arg(temp_dir.path()) + .arg("--") + .arg("--quit") + .output()?; + + let stdout = std::str::from_utf8(&output.stdout) + .expect("stdout is not utf8! need to handle arbitrary bytes"); + + if !output.status.success() { + bail!( + "running {} failed with: stdout: {}\n\nstderr: {}", + qjs_path.display(), + stdout, + std::str::from_utf8(&output.stderr) + .expect("stderr is not utf8! need to handle arbitrary bytes") + ); + } + + Ok(()) +} + +#[cfg(not(target_env = "musl"))] +#[test] +fn test_wasmer_run_works() -> anyhow::Result<()> { + let output = Command::new(get_wasmer_path()) + .arg("registry.wapm.io/python/python") + .arg(format!("--mapdir=.:{}", ASSET_PATH)) + .arg("test.py") + .output()?; + + let stdout = std::str::from_utf8(&output.stdout) + .expect("stdout is not utf8! need to handle arbitrary bytes"); + + if !stdout.ends_with("hello\n") { + bail!( + "1 running python/python failed with: stdout: {}\n\nstderr: {}", + stdout, + std::str::from_utf8(&output.stderr) + .expect("stderr is not utf8! need to handle arbitrary bytes") + ); + } + + // same test again, but this time with "wasmer run ..." + let output = Command::new(get_wasmer_path()) + .arg("run") + .arg("registry.wapm.io/python/python") + .arg(format!("--mapdir=.:{}", ASSET_PATH)) + .arg("test.py") + .output()?; + + let stdout = std::str::from_utf8(&output.stdout) + .expect("stdout is not utf8! need to handle arbitrary bytes"); + + if !stdout.ends_with("hello\n") { + bail!( + "2 running python/python failed with: stdout: {}\n\nstderr: {}", + stdout, + std::str::from_utf8(&output.stderr) + .expect("stderr is not utf8! need to handle arbitrary bytes") + ); + } + + // same test again, but this time without specifying the registry + let output = Command::new(get_wasmer_path()) + .arg("run") + .arg("python/python") + .arg(format!("--mapdir=.:{}", ASSET_PATH)) + .arg("test.py") + .output()?; + + let stdout = std::str::from_utf8(&output.stdout) + .expect("stdout is not utf8! need to handle arbitrary bytes"); + + if !stdout.ends_with("hello\n") { + bail!( + "3 running python/python failed with: stdout: {}\n\nstderr: {}", + stdout, + std::str::from_utf8(&output.stderr) + .expect("stderr is not utf8! need to handle arbitrary bytes") + ); + } + + Ok(()) +} + #[test] fn run_no_imports_wasm_works() -> anyhow::Result<()> { let output = Command::new(get_wasmer_path())