diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 795b6b96..de73a149 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,7 +43,7 @@ jobs: - name: Build Package run: npm run build - name: Integration Tests - run: npm run test + run: WASMER_TOKEN=${{ secrets.WAPM_DEV_TOKEN }} WASMER_TEST_OWNER=ciuser npm run test examples: name: Build Examples diff --git a/.gitignore b/.gitignore index 3e15d65d..7ad9b119 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ node_modules/ dist/ *.log pkg/ +.env diff --git a/Cargo.lock b/Cargo.lock index c85cd7b8..9761e444 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,6 +37,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "aliasable" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" + [[package]] name = "android-tzdata" version = "0.1.1" @@ -60,9 +66,21 @@ checksum = "70033777eb8b5124a81a1889416543dddef2de240019b674c81285a2635a7e1e" [[package]] name = "anyhow" -version = "1.0.79" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" + +[[package]] +name = "arrayref" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" + +[[package]] +name = "arrayvec" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "async-trait" @@ -84,6 +102,12 @@ dependencies = [ "critical-section", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.1.0" @@ -111,6 +135,18 @@ version = "0.21.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c79fed4cdb43e993fcdadc7e58a09fd0e3e649c4436fa11da71c9f1f3ee7feb9" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "beef" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" + [[package]] name = "bincode" version = "1.3.3" @@ -144,6 +180,19 @@ dependencies = [ "wyz", ] +[[package]] +name = "blake3" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30cca6d3674597c30ddf2c587bf8d9d65c9a84d2326d941cc79c9842dfe0ef52" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -153,6 +202,16 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bstr" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "bumpalo" version = "3.14.0" @@ -196,6 +255,15 @@ dependencies = [ "serde", ] +[[package]] +name = "bytesize" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e368af43e418a04d52505cf3dbc23dda4e3407ae2fa99fd0e4f308ce546acc" +dependencies = [ + "serde", +] + [[package]] name = "cc" version = "1.0.83" @@ -235,12 +303,28 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "constant_time_eq" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" + [[package]] name = "cooked-waker" version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147be55d677052dabc6b22252d5dd0fd4c29c8c27aa4f2fbef0f94aa003b406f" +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.6" @@ -260,6 +344,15 @@ dependencies = [ "windows-sys 0.33.0", ] +[[package]] +name = "counter" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d458e66999348f56fd3ffcfbb7f7951542075ca8359687c703de6500c1ddccd" +dependencies = [ + "num-traits", +] + [[package]] name = "cpufeatures" version = "0.2.12" @@ -284,6 +377,25 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216" +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-queue" version = "0.3.11" @@ -309,6 +421,62 @@ dependencies = [ "typenum", ] +[[package]] +name = "cynic" +version = "3.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7335114540697c7b1c1a0131cbe0e983fdb1e646f881234afe9e2a66133ac99a" +dependencies = [ + "cynic-proc-macros", + "ref-cast", + "reqwest 0.11.27", + "serde", + "serde_json", + "static_assertions", + "thiserror", +] + +[[package]] +name = "cynic-codegen" +version = "3.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c0ec86f960a00ce087e96ff6f073f6ff28b6876d69ce8caa06c03fb4143981c" +dependencies = [ + "counter", + "cynic-parser", + "darling 0.20.3", + "once_cell", + "ouroboros", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.48", + "thiserror", +] + +[[package]] +name = "cynic-parser" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "718f6cd8c54ae5249fd42b0c86639df0100b8a86eea2e5f1b915cde2e1481453" +dependencies = [ + "indexmap 2.2.6", + "lalrpop-util", + "logos", +] + +[[package]] +name = "cynic-proc-macros" +version = "3.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a69ecdf4aa110fed1c0c8de290bc8ccb2835388733cf2f418f0abdf6ff3899" +dependencies = [ + "cynic-codegen", + "darling 0.20.3", + "quote", + "syn 2.0.48", +] + [[package]] name = "darling" version = "0.14.4" @@ -353,6 +521,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", + "strsim", "syn 2.0.48", ] @@ -398,6 +567,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", + "serde", ] [[package]] @@ -452,6 +622,45 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "document-features" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef5282ad69563b5fc40319526ba27e0e7363d552a896f0297d54f767717f9b95" +dependencies = [ + "litrs", +] + +[[package]] +name = "dyn-clone" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" + +[[package]] +name = "edge-schema" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0966f1fd49610cc67a835124e6fb4d00a36104e1aa34383c5ef5a265ca00ea2a" +dependencies = [ + "anyhow", + "bytesize", + "once_cell", + "parking_lot", + "rand_chacha", + "rand_core", + "schemars", + "serde", + "serde_json", + "serde_path_to_error", + "serde_yaml 0.8.26", + "sparx", + "time", + "url", + "uuid", + "wcgi-host", +] + [[package]] name = "educe" version = "0.4.23" @@ -464,6 +673,21 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "encoding_rs" +version = "0.8.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +dependencies = [ + "cfg-if", +] + [[package]] name = "enum-iterator" version = "0.7.0" @@ -574,6 +798,21 @@ 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.2.1" @@ -690,9 +929,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.12" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "js-sys", @@ -707,12 +946,69 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +[[package]] +name = "globset" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata 0.4.3", + "regex-syntax 0.8.2", +] + +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.11", + "indexmap 2.2.6", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "h2" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.1.0", + "indexmap 2.2.6", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "half" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" +[[package]] +name = "harsh" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6fce2283849822530a18d7d8eeb1719ac65a27cfb6649c0dc8dfd2d2cc5edfb" + [[package]] name = "hash32" version = "0.2.1" @@ -759,6 +1055,12 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "hex" version = "0.4.3" @@ -777,77 +1079,257 @@ dependencies = [ ] [[package]] -name = "iana-time-zone" -version = "0.1.59" +name = "http" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows-core", + "bytes", + "fnv", + "itoa", ] [[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" +name = "http-body" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ - "cc", + "bytes", + "http 0.2.11", + "pin-project-lite", ] [[package]] -name = "id-arena" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" - -[[package]] -name = "ident_case" -version = "1.0.1" +name = "http-body" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +dependencies = [ + "bytes", + "http 1.1.0", +] [[package]] -name = "idna" -version = "0.5.0" +name = "http-body-util" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "pin-project-lite", ] [[package]] -name = "indexmap" -version = "1.9.3" +name = "http-serde" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +checksum = "6f560b665ad9f1572cfcaf034f7fb84338a7ce945216d64a90fd81f046a3caee" dependencies = [ - "autocfg", - "hashbrown 0.12.3", + "http 0.2.11", "serde", ] [[package]] -name = "indexmap" -version = "2.1.0" +name = "httparse" +version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" -dependencies = [ - "equivalent", - "hashbrown 0.14.3", - "serde", -] +checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" [[package]] -name = "instant" -version = "0.1.12" +name = "httpdate" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f361cde2f109281a220d4307746cdfd5ee3f410da58a70377762396775634b33" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.3.26", + "http 0.2.11", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4fe55fb7a772d59a5ff1dfbff4fe0258d19b89fec4b233e75d35d5d2316badc" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2 0.4.5", + "http 1.1.0", + "http-body 1.0.0", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" +dependencies = [ + "futures-util", + "http 1.1.0", + "hyper 1.4.0", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper 1.4.0", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab92f4f49ee4fb4f997c784b7a2e0fa70050211e0b6a287f898c3c9785ca956" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "hyper 1.4.0", + "pin-project-lite", + "socket2", + "tokio", + "tower", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "id-arena" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "ignore" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b46810df39e66e925525d6e38ce1e7f6e1d208f72dc39757880fcb66e2c58af1" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata 0.4.3", + "same-file", + "walkdir", + "winapi-util", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown 0.14.3", + "serde", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if", "js-sys", @@ -855,6 +1337,21 @@ dependencies = [ "web-sys", ] +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.10" @@ -870,6 +1367,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lalrpop-util" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" +dependencies = [ + "regex-automata 0.4.3", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -893,9 +1399,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.152" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "linked-hash-map" @@ -918,6 +1424,12 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" +[[package]] +name = "litrs" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" + [[package]] name = "lock_api" version = "0.4.11" @@ -934,6 +1446,39 @@ version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +[[package]] +name = "logos" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "161971eb88a0da7ae0c333e1063467c5b5727e7fb6b710b8db4814eade3a42e8" +dependencies = [ + "logos-derive", +] + +[[package]] +name = "logos-codegen" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e31badd9de5131fdf4921f6473d457e3dd85b11b7f091ceb50e4df7c3eeb12a" +dependencies = [ + "beef", + "fnv", + "lazy_static", + "proc-macro2", + "quote", + "regex-syntax 0.8.2", + "syn 2.0.48", +] + +[[package]] +name = "logos-derive" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c2a69b3eb68d5bd595107c9ee58d7e07fe2bb5e360cc85b0f084dedac80de0a" +dependencies = [ + "logos-codegen", +] + [[package]] name = "lz4_flex" version = "0.11.1" @@ -952,6 +1497,12 @@ dependencies = [ "libc", ] +[[package]] +name = "managed" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d" + [[package]] name = "matchers" version = "0.1.0" @@ -994,6 +1545,22 @@ dependencies = [ "autocfg", ] +[[package]] +name = "merge-streams" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f84f6452969abd246e7ac1fe4fe75906c76e8ec88d898df9aef37e0f3b6a7c2" +dependencies = [ + "futures-core", + "pin-project", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "miniz_oxide" version = "0.7.1" @@ -1003,12 +1570,40 @@ dependencies = [ "adler", ] +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.48.0", +] + [[package]] name = "more-asserts" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" +[[package]] +name = "native-tls" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -1085,12 +1680,91 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "openssl" +version = "0.10.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" +dependencies = [ + "bitflags 2.4.1", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[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.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "ouroboros" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "944fa20996a25aded6b4795c6d63f10014a7a83f8be9828a11860b08c5fc4a67" +dependencies = [ + "aliasable", + "ouroboros_macro", + "static_assertions", +] + +[[package]] +name = "ouroboros_macro" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39b0deead1528fd0e5947a8546a9642a9777c25f6e1e26f34c97b204bbb465bd" +dependencies = [ + "heck 0.4.1", + "itertools", + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn 2.0.48", +] + [[package]] name = "overload" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + [[package]] name = "parking_lot_core" version = "0.9.9" @@ -1123,7 +1797,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" dependencies = [ "fixedbitset", - "indexmap 2.1.0", + "indexmap 2.2.6", ] [[package]] @@ -1158,6 +1832,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + [[package]] name = "powerfmt" version = "0.2.0" @@ -1213,6 +1893,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", + "version_check", + "yansi", +] + [[package]] name = "ptr_meta" version = "0.1.4" @@ -1298,6 +1991,26 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "ref-cast" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf0a6f84d5f1d581da8b41b47ec8600871962f2a528115b542b362d4b744931" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "regex" version = "1.10.2" @@ -1369,6 +2082,102 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3a8614ee435691de62bcffcf4a66d91b3594bf1428a5722e79103249a095690" +[[package]] +name = "reqwest" +version = "0.11.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +dependencies = [ + "base64 0.21.6", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.3.26", + "http 0.2.11", + "http-body 0.4.6", + "hyper 0.14.29", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 0.1.2", + "system-configuration", + "tokio", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg 0.50.0", +] + +[[package]] +name = "reqwest" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.4.5", + "http 1.1.0", + "http-body 1.0.0", + "http-body-util", + "hyper 1.4.0", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 1.0.1", + "system-configuration", + "tokio", + "tokio-native-tls", + "tokio-util", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "winreg 0.52.0", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "rkyv" version = "0.7.43" @@ -1416,9 +2225,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.28" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ "bitflags 2.4.1", "errno", @@ -1427,6 +2236,46 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustls" +version = "0.23.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05cff451f60db80f490f3c182b77c35260baace73209e9cdbbe526bfe3a4d402" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +dependencies = [ + "base64 0.22.1", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" + +[[package]] +name = "rustls-webpki" +version = "0.102.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9a6fccd794a42c2c105b513a2f62bc3fd8f3ba57a4593677ceb0bd035164d78" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "ryu" version = "1.0.16" @@ -1442,6 +2291,41 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "schemars" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" +dependencies = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", + "url", + "uuid", +] + +[[package]] +name = "schemars_derive" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.48", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -1460,6 +2344,29 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" +[[package]] +name = "security-framework" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "self_cell" version = "1.0.3" @@ -1536,6 +2443,17 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "serde_json" version = "1.0.111" @@ -1547,12 +2465,34 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" +dependencies = [ + "itoa", + "serde", +] + [[package]] name = "serde_spanned" -version = "0.6.5" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ + "form_urlencoded", + "itoa", + "ryu", "serde", ] @@ -1570,11 +2510,11 @@ dependencies = [ [[package]] name = "serde_yaml" -version = "0.9.30" +version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1bf28c79a99f70ee1f1d83d10c875d2e70618417fda01ad1785e027579d9d38" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.1.0", + "indexmap 2.2.6", "itoa", "ryu", "serde", @@ -1628,9 +2568,39 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.2" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "smoltcp" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee34c1e1bfc7e9206cc0fb8030a90129b4e319ab53856249bb27642cab914fb3" +dependencies = [ + "bitflags 1.3.2", + "byteorder", + "managed", +] + +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "sparx" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" +checksum = "cc2257c28eacecfc38c658124ed239e7ecfc9b89082c0794b0672420b63b84c6" +dependencies = [ + "byteorder", +] [[package]] name = "spin" @@ -1659,6 +2629,12 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "1.0.109" @@ -1681,6 +2657,39 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tap" version = "1.0.1" @@ -1706,25 +2715,24 @@ checksum = "69758bda2e78f098e4ccb393021a0963bb3442eac05f135c30f61b7370bbafae" [[package]] name = "tempfile" -version = "3.9.0" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand", - "redox_syscall", "rustix", "windows-sys 0.52.0", ] [[package]] -name = "term_size" -version = "0.3.2" +name = "terminal_size" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9" +checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" dependencies = [ - "libc", - "winapi", + "rustix", + "windows-sys 0.48.0", ] [[package]] @@ -1818,19 +2826,44 @@ checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" dependencies = [ "backtrace", "bytes", + "libc", + "mio", "pin-project-lite", + "socket2", "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", ] [[package]] -name = "tokio-macros" -version = "2.2.0" +name = "tokio-rustls" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.48", + "rustls", + "rustls-pki-types", + "tokio", ] [[package]] @@ -1849,48 +2882,47 @@ dependencies = [ ] [[package]] -name = "tokio-util" -version = "0.6.10" +name = "tokio-stream" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" dependencies = [ - "bytes", "futures-core", - "futures-sink", - "log", "pin-project-lite", "tokio", + "tokio-util", ] [[package]] -name = "toml" -version = "0.7.8" +name = "tokio-util" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit 0.19.15", + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", ] [[package]] name = "toml" -version = "0.8.8" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35" +checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.21.0", + "toml_edit 0.22.14", ] [[package]] name = "toml_datetime" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" dependencies = [ "serde", ] @@ -1901,26 +2933,51 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.1.0", - "serde", - "serde_spanned", + "indexmap 2.2.6", "toml_datetime", - "winnow", + "winnow 0.5.33", ] [[package]] name = "toml_edit" -version = "0.21.0" +version = "0.22.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" +checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38" dependencies = [ - "indexmap 2.1.0", + "indexmap 2.2.6", "serde", "serde_spanned", "toml_datetime", - "winnow", + "winnow 0.6.13", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", ] +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[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.40" @@ -1983,6 +3040,12 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "twox-hash" version = "1.6.3" @@ -2049,9 +3112,15 @@ checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[package]] name = "unsafe-libyaml" -version = "0.2.10" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] +name = "untrusted" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" @@ -2073,9 +3142,13 @@ checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" [[package]] name = "uuid" -version = "1.6.1" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560" +checksum = "5de17fd2f7da591098415cff336e12965a28061ddace43b59cb3c430179c9439" +dependencies = [ + "getrandom", + "serde", +] [[package]] name = "valuable" @@ -2083,6 +3156,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" @@ -2091,8 +3170,9 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "virtual-fs" -version = "0.11.0" -source = "git+https://github.com/wasmerio/wasmer?branch=master#0cf0f5b26debffa1d4e615732ab1b55b991ef687" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2be050a12999526f637afa40f010f3657827e63a804020bdb1be2e9fd5367a1" dependencies = [ "anyhow", "async-trait", @@ -2114,8 +3194,9 @@ dependencies = [ [[package]] name = "virtual-mio" -version = "0.3.0" -source = "git+https://github.com/wasmerio/wasmer?branch=master#0cf0f5b26debffa1d4e615732ab1b55b991ef687" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff8026c9d7575dc9afd8a0907357acb7aa55ec262097fbccae5da42f67773b3c" dependencies = [ "async-trait", "bytes", @@ -2128,12 +3209,13 @@ dependencies = [ [[package]] name = "virtual-net" -version = "0.6.2" -source = "git+https://github.com/wasmerio/wasmer?branch=master#0cf0f5b26debffa1d4e615732ab1b55b991ef687" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74aa69bbb19e531d274ba1aa730028f6fcd2117513ff6696d020af05188dfe92" dependencies = [ "anyhow", "async-trait", - "base64", + "base64 0.21.6", "bincode", "bytecheck", "bytes", @@ -2143,6 +3225,7 @@ dependencies = [ "pin-project-lite", "rkyv", "serde", + "smoltcp", "thiserror", "tokio", "tokio-serde", @@ -2167,7 +3250,7 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19bc05e8380515c4337c40ef03b2ff233e391315b178a320de8640703d522efe" dependencies = [ - "heck", + "heck 0.3.3", "wai-bindgen-gen-core", ] @@ -2177,18 +3260,7 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6f35ce5e74086fac87f3a7bd50f643f00fe3559adb75c88521ecaa01c8a6199" dependencies = [ - "heck", - "wai-bindgen-gen-core", - "wai-bindgen-gen-rust", -] - -[[package]] -name = "wai-bindgen-gen-wasmer" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f61484185d8c520a86d5a7f7f8265f446617c2f9774b2e20a52de19b6e53432" -dependencies = [ - "heck", + "heck 0.3.3", "wai-bindgen-gen-core", "wai-bindgen-gen-rust", ] @@ -2215,32 +3287,6 @@ dependencies = [ "wai-bindgen-gen-rust-wasm", ] -[[package]] -name = "wai-bindgen-wasmer" -version = "0.18.0" -source = "git+https://github.com/wasmerio/wasmer?branch=master#0cf0f5b26debffa1d4e615732ab1b55b991ef687" -dependencies = [ - "anyhow", - "bitflags 1.3.2", - "once_cell", - "thiserror", - "tracing", - "wai-bindgen-wasmer-impl", - "wasmer", -] - -[[package]] -name = "wai-bindgen-wasmer-impl" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b3488ed88d4dd0e3bf85bad4e27dac6cb31aae5d122a5dda2424803c8dc863a" -dependencies = [ - "proc-macro2", - "syn 1.0.109", - "wai-bindgen-gen-core", - "wai-bindgen-gen-wasmer", -] - [[package]] name = "wai-parser" version = "0.2.3" @@ -2270,6 +3316,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -2278,9 +3333,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.89" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -2288,9 +3343,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.89" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", @@ -2337,9 +3392,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.89" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2347,9 +3402,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.89" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", @@ -2360,9 +3415,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.89" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "wasm-bindgen-test" @@ -2398,10 +3453,24 @@ dependencies = [ "leb128", ] +[[package]] +name = "wasm-streams" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "wasmer" -version = "4.2.5" -source = "git+https://github.com/wasmerio/wasmer?branch=master#0cf0f5b26debffa1d4e615732ab1b55b991ef687" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1852ee143a2d8143265bfee017c43bf690702d6c2b45a763a2f13e669f5b7ec" dependencies = [ "bytes", "cfg-if", @@ -2421,16 +3490,41 @@ dependencies = [ "wasmer-derive", "wasmer-types", "wasmer-vm", - "wasmparser 0.83.0", - "wasmparser 0.95.0", + "wasmparser 0.121.2", "wat", "winapi", ] +[[package]] +name = "wasmer-api" +version = "0.0.30" +source = "git+https://github.com/wasmerio/wasmer?branch=backend-api-wasm32#17b77586782c657610e2cc329b1c4b6ae0924bed" +dependencies = [ + "anyhow", + "cynic", + "edge-schema", + "futures", + "getrandom", + "harsh", + "merge-streams", + "pin-project-lite", + "reqwest 0.11.27", + "serde", + "serde_json", + "serde_path_to_error", + "time", + "tokio", + "tracing", + "url", + "wasmer-config 0.4.0 (git+https://github.com/wasmerio/wasmer?branch=backend-api-wasm32)", + "webc", +] + [[package]] name = "wasmer-compiler" -version = "4.2.5" -source = "git+https://github.com/wasmerio/wasmer?branch=master#0cf0f5b26debffa1d4e615732ab1b55b991ef687" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b4f157d715f3bb60c2c9d7b9e48299a30e9209f87f4484f79f9cd586b40b6ee" dependencies = [ "backtrace", "bytes", @@ -2452,12 +3546,57 @@ dependencies = [ "wasmer-types", "wasmer-vm", "winapi", + "xxhash-rust", +] + +[[package]] +name = "wasmer-config" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b4a632496950fde9ad821e195ef1a301440076f7c7d80de55239a140359bcbd" +dependencies = [ + "anyhow", + "bytesize", + "derive_builder", + "hex", + "indexmap 2.2.6", + "schemars", + "semver", + "serde", + "serde_cbor", + "serde_json", + "serde_yaml 0.9.34+deprecated", + "thiserror", + "toml", + "url", +] + +[[package]] +name = "wasmer-config" +version = "0.4.0" +source = "git+https://github.com/wasmerio/wasmer?branch=backend-api-wasm32#17b77586782c657610e2cc329b1c4b6ae0924bed" +dependencies = [ + "anyhow", + "bytesize", + "derive_builder", + "hex", + "indexmap 2.2.6", + "schemars", + "semver", + "serde", + "serde_cbor", + "serde_json", + "serde_yaml 0.9.34+deprecated", + "thiserror", + "toml", + "url", ] [[package]] name = "wasmer-derive" -version = "4.2.5" -source = "git+https://github.com/wasmerio/wasmer?branch=master#0cf0f5b26debffa1d4e615732ab1b55b991ef687" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32cd5732ff64370e98986f9753cce13b91cc9d3c4b649e31b0d08d5db69164ea" dependencies = [ "proc-macro-error", "proc-macro2", @@ -2467,12 +3606,13 @@ dependencies = [ [[package]] name = "wasmer-journal" -version = "0.1.0" -source = "git+https://github.com/wasmerio/wasmer?branch=master#0cf0f5b26debffa1d4e615732ab1b55b991ef687" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4f51818afcb61d608414a1eee90e9d368753376ff2340c63deb548a6aa0c0c7" dependencies = [ "anyhow", "async-trait", - "base64", + "base64 0.21.6", "bincode", "bytecheck", "bytes", @@ -2484,6 +3624,7 @@ dependencies = [ "serde_json", "thiserror", "tracing", + "virtual-fs", "virtual-net", "wasmer", "wasmer-wasix-types", @@ -2500,13 +3641,17 @@ dependencies = [ "console_error_panic_hook", "derivative", "futures", - "http", + "http 0.2.11", "instant", "js-sys", "once_cell", + "reqwest 0.12.5", "serde", "serde-wasm-bindgen 0.6.3", + "sha2", + "tempfile", "tokio", + "toml", "tracing", "tracing-subscriber", "url", @@ -2517,50 +3662,42 @@ dependencies = [ "wasm-bindgen-futures", "wasm-bindgen-test", "wasmer", + "wasmer-api", + "wasmer-config 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "wasmer-types", "wasmer-wasix", "web-sys", "webc", ] -[[package]] -name = "wasmer-toml" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d21472954ee9443235ca32522b17fc8f0fe58e2174556266a0d9766db055cc52" -dependencies = [ - "anyhow", - "derive_builder", - "indexmap 2.1.0", - "semver", - "serde", - "serde_cbor", - "serde_json", - "serde_yaml 0.9.30", - "thiserror", - "toml 0.8.8", -] - [[package]] name = "wasmer-types" -version = "4.2.5" -source = "git+https://github.com/wasmerio/wasmer?branch=master#0cf0f5b26debffa1d4e615732ab1b55b991ef687" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c890fd0dbda40df03977b899d1ad7113deba3c225f2cc7b88deb7633044d3e07" dependencies = [ "bytecheck", "enum-iterator", "enumset", + "getrandom", + "hex", "indexmap 1.9.3", "more-asserts", "rkyv", "serde", "serde_bytes", + "sha2", "target-lexicon", "thiserror", + "webc", + "xxhash-rust", ] [[package]] name = "wasmer-vm" -version = "4.2.5" -source = "git+https://github.com/wasmerio/wasmer?branch=master#0cf0f5b26debffa1d4e615732ab1b55b991ef687" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e0dc60ab800cf0bd44e2d35d88422d256d2470b00c72778f91bfb826c42dbd0" dependencies = [ "backtrace", "cc", @@ -2587,13 +3724,15 @@ dependencies = [ [[package]] name = "wasmer-wasix" -version = "0.18.0" -source = "git+https://github.com/wasmerio/wasmer?branch=master#0cf0f5b26debffa1d4e615732ab1b55b991ef687" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff24faf9bc4a34e7281db698c8cd0e6449f0dfabc4cf0e8c08f2599da70d4da7" dependencies = [ "anyhow", "async-trait", - "base64", + "base64 0.21.6", "bincode", + "blake3", "bytecheck", "bytes", "cfg-if", @@ -2605,7 +3744,7 @@ dependencies = [ "getrandom", "heapless", "hex", - "http", + "http 0.2.11", "js-sys", "lazy_static", "libc", @@ -2622,25 +3761,26 @@ dependencies = [ "serde_cbor", "serde_derive", "serde_json", - "serde_yaml 0.8.26", + "serde_yaml 0.9.34+deprecated", "sha2", "shared-buffer", "tempfile", - "term_size", + "terminal_size", "termios", "thiserror", "tokio", + "tokio-stream", "tracing", "url", "urlencoding", "virtual-fs", "virtual-mio", "virtual-net", - "wai-bindgen-wasmer", "waker-fn", "wasm-bindgen", "wasm-bindgen-futures", "wasmer", + "wasmer-config 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "wasmer-journal", "wasmer-types", "wasmer-wasix-types", @@ -2653,8 +3793,9 @@ dependencies = [ [[package]] name = "wasmer-wasix-types" -version = "0.18.0" -source = "git+https://github.com/wasmerio/wasmer?branch=master#0cf0f5b26debffa1d4e615732ab1b55b991ef687" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e6f8e53945f532947482311efb95e7724197f0c4780eeea3b56e0bca79d0bfc" dependencies = [ "anyhow", "bitflags 1.3.2", @@ -2674,12 +3815,6 @@ dependencies = [ "wasmer-types", ] -[[package]] -name = "wasmparser" -version = "0.83.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a" - [[package]] name = "wasmparser" version = "0.95.0" @@ -2690,6 +3825,17 @@ dependencies = [ "url", ] +[[package]] +name = "wasmparser" +version = "0.121.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dbe55c8f9d0dbd25d9447a5a889ff90c0cc3feaa7395310d3d826b2c703eaab" +dependencies = [ + "bitflags 2.4.1", + "indexmap 2.2.6", + "semver", +] + [[package]] name = "wast" version = "64.0.0" @@ -2711,6 +3857,33 @@ dependencies = [ "wast", ] +[[package]] +name = "wcgi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ca8f334eec3a8197bd25a612c74f415b8691d219ee11f1acd20f15a3e2bf77" +dependencies = [ + "http 0.2.11", + "http-serde", + "serde", + "serde_json", + "url", +] + +[[package]] +name = "wcgi-host" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a762cf2b0ed389a2a2fb591d63a398c1a4c0f8bef938cfd040285a3c63b695cc" +dependencies = [ + "http 0.2.11", + "schemars", + "serde", + "tokio", + "wasmparser 0.95.0", + "wcgi", +] + [[package]] name = "web-sys" version = "0.3.66" @@ -2723,18 +3896,21 @@ dependencies = [ [[package]] name = "webc" -version = "5.8.1" +version = "6.0.0-rc2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "973ca5a91b4fb3e4bb37cfebe03ef9364d0aff2765256abefdb7e79dc9188483" +checksum = "fb3e2ccb43d303c5bd48f31db7a129481a9aaa5343d623f92951751df190df81" dependencies = [ "anyhow", - "base64", - "byteorder", + "base64 0.22.1", "bytes", + "cfg-if", + "document-features", "flate2", + "ignore", "indexmap 1.9.3", "leb128", "lexical-sort", + "libc", "once_cell", "path-clean", "rand", @@ -2747,10 +3923,9 @@ dependencies = [ "tar", "tempfile", "thiserror", - "toml 0.7.8", + "toml", "url", - "walkdir", - "wasmer-toml", + "wasmer-config 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2812,6 +3987,15 @@ dependencies = [ "windows_x86_64_msvc 0.33.0", ] +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -2974,6 +4158,35 @@ dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "wyz" version = "0.5.1" @@ -2996,9 +4209,9 @@ dependencies = [ [[package]] name = "xxhash-rust" -version = "0.8.8" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53be06678ed9e83edb1745eb72efc0bbcd7b5c3c35711a860906aed827a13d61" +checksum = "63658493314859b4dfdf3fb8c1defd61587839def09582db50b8a4e93afca6bb" [[package]] name = "yaml-rust" @@ -3008,3 +4221,15 @@ checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" dependencies = [ "linked-hash-map", ] + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/Cargo.toml b/Cargo.toml index b2dc7958..e9cbac03 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,21 +20,28 @@ http = "0.2" instant = { version = "0.1", features = ["wasm-bindgen"] } js-sys = "0.3" once_cell = "1" +reqwest = { version = "0.12.5", features = ["stream"] } serde = { version = "1", features = ["derive"] } serde-wasm-bindgen = "0.6" +sha2 = "0.10.8" +tempfile = "3.10.1" tokio = { version = "1", features = ["sync"], default_features = false } +toml = "0.8.14" tracing = { version = "0.1", features = ["log", "release_max_level_debug"] } tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } url = "2.4.0" -virtual-fs = { version = "0.11.0", default-features = false } -virtual-net = { version = "0.6.0", default-features = false, features = ["remote"] } +virtual-fs = { version = "0.13.0", default-features = false } +virtual-net = { version = "0.6.7", default-features = false, features = ["remote"] } wasm-bindgen = { version = "0.2" } wasm-bindgen-derive = "0.2.1" wasm-bindgen-futures = "0.4" wasm-bindgen-test = "0.3.37" -wasmer = { version = "4.2.5", default-features = false, features = ["js", "js-default", "tracing", "wasm-types-polyfill", "enable-serde"] } -wasmer-wasix = { version = "0.18", default-features = false, features = ["js", "js-default"] } -webc = "5.3.0" +wasmer = { version = "4.3.2", default-features = false, features = ["js", "js-default", "wasm-types-polyfill", "enable-serde"] } +wasmer-api = { version = "0.0.30", git = "https://github.com/wasmerio/wasmer", branch = "backend-api-wasm32"} +wasmer-config = "0.4.0" +wasmer-types = "4.3.2" +wasmer-wasix = { version = "0.22.0", default-features = false, features = ["js", "js-default"] } +webc = "6.0.0-rc2" [dependencies.web-sys] version = "0.3" @@ -95,10 +102,11 @@ dwarf-debug-info = false wasm-opt = ["--enable-threads", "--enable-bulk-memory", "-Oz"] [patch.crates-io] -virtual-net = { git = "https://github.com/wasmerio/wasmer", branch = "master" } -virtual-fs = { git = "https://github.com/wasmerio/wasmer", branch = "master" } -wasmer-wasix = { git = "https://github.com/wasmerio/wasmer", branch = "master" } -wasmer = { git = "https://github.com/wasmerio/wasmer", branch = "master" } +#webc = {git = "https://github.com/wasmerio/pirita", branch = "in-memory-manifest"} +#virtual-net = { git = "https://github.com/wasmerio/wasmer", branch = "master" } +#virtual-fs = { git = "https://github.com/wasmerio/wasmer", branch = "master" } +#wasmer-wasix = { git = "https://github.com/wasmerio/wasmer", branch = "master" } +#wasmer = { git = "https://github.com/wasmerio/wasmer", branch = "master" } # virtual-net = { path = "../wasmer/lib/virtual-net" } # virtual-fs = { path = "../wasmer/lib/virtual-fs" } # wasmer-wasix = { path = "../wasmer/lib/wasix" } diff --git a/WasmerSDK.ts b/WasmerSDK.ts index b5d9b908..74b25fe2 100644 --- a/WasmerSDK.ts +++ b/WasmerSDK.ts @@ -1,39 +1,72 @@ export * from "./pkg/wasmer_js"; // @ts-ignore -import load, { InitInput, InitOutput, ThreadPoolWorker, setWorkerUrl } from "./pkg/wasmer_js"; +import load, { + InitInput, + InitOutput, + // @ts-ignore + ThreadPoolWorker, + setWorkerUrl, +} from "./pkg/wasmer_js"; + +export type WasmerInitInput = { + module?: InitInput | Promise; + memory?: WebAssembly.Memory; + registryUrl?: string; + token?: string; +}; /** * Initialize the underlying WebAssembly module. */ -export const init = async (module_or_path?: InitInput | Promise, maybe_memory?: WebAssembly.Memory): Promise => { - if (!module_or_path) { - // This will be replaced by the rollup bundler at the SDK build time - // to point to a valid http location of the SDK using unpkg.com. - let wasmUrl = (globalThis as any)["wasmUrl"]; - if (wasmUrl) { - module_or_path = new URL(wasmUrl); - } +export const init = async (initValue: WasmerInitInput | undefined): Promise => { + + if (!initValue) { + initValue = {}; + } + + if (!initValue.module) { + // This will be replaced by the rollup bundler at the SDK build time + // to point to a valid http location of the SDK using unpkg.com. + let wasmUrl = (globalThis as any)["wasmUrl"]; + if (wasmUrl) { + initValue.module = new URL(wasmUrl); } - return load(module_or_path, maybe_memory); -} + } + + (globalThis as any)["__WASMER_REGISTRY__"] = { + registryUrl: initValue.registryUrl, + token: initValue.token, + }; + + return load(initValue.module, initValue.memory); +}; /** * Set a deafult working Worker Url. Which in this case will be * an unpkg url that is set up at the SDK build time. */ export const setDefaultWorkerUrl = () => { - let workerUrl = (globalThis as any)["workerUrl"]; - if (workerUrl) { - setWorkerUrl(workerUrl) - } -} + let workerUrl = (globalThis as any)["workerUrl"]; + if (workerUrl) { + setWorkerUrl(workerUrl); + } +}; // HACK: We save these to the global scope because it's the most reliable way to // make sure worker.js gets access to them. Normal exports are removed when // using a bundler. (globalThis as any)["__WASMER_INTERNALS__"] = { ThreadPoolWorker, init }; +(globalThis as any)["__WASMER_INIT__"] = true; // HACK: some bundlers such as webpack uses this on dev mode. // We add this functions to allow dev mode work in those bundlers. -(globalThis as any).$RefreshReg$ = (globalThis as any).$RefreshReg$ || function () {/**/ }; -(globalThis as any).$RefreshSig$ = (globalThis as any).$RefreshSig$ || function () { return function () { } }; +(globalThis as any).$RefreshReg$ = + (globalThis as any).$RefreshReg$ || + function () { + /**/ + }; +(globalThis as any).$RefreshSig$ = + (globalThis as any).$RefreshSig$ || + function () { + return function () {}; + }; diff --git a/WasmerSDKBundled.ts b/WasmerSDKBundled.ts index cdf0e343..2745021e 100644 --- a/WasmerSDKBundled.ts +++ b/WasmerSDKBundled.ts @@ -1,5 +1,10 @@ export * from "./WasmerSDK"; -import { init as load, InitInput, InitOutput } from "./WasmerSDK"; +import { + init as load, + InitInput, + InitOutput, + WasmerInitInput, +} from "./WasmerSDK"; // @ts-ignore import wasm_bytes from "./pkg/wasmer_js_bg.wasm"; @@ -7,10 +12,14 @@ import wasm_bytes from "./pkg/wasmer_js_bg.wasm"; * Initialize the underlying WebAssembly module, defaulting to an embedded * copy of the `*.wasm` file. */ -export const init = async (module_or_path?: InitInput | Promise, maybe_memory?: WebAssembly.Memory): Promise => { - if (!module_or_path) { - // @ts-ignore - module_or_path = await wasm_bytes(); - } - return load(module_or_path, maybe_memory); -} +export const init = async (initValue: WasmerInitInput | undefined): Promise => { + if (!initValue) { + initValue = {} + } + + if (!initValue.module) { + // @ts-ignore + initValue.module = await wasm_bytes(); + } + return load(initValue); +}; diff --git a/examples/cdn-coi-serviceworker/coi-serviceworker.js b/examples/cdn-coi-serviceworker/coi-serviceworker.js index 45e36b97..49eecc7a 100644 --- a/examples/cdn-coi-serviceworker/coi-serviceworker.js +++ b/examples/cdn-coi-serviceworker/coi-serviceworker.js @@ -1,147 +1,167 @@ // File taken from: https://github.com/gzuidhof/coi-serviceworker/tree/master /*! coi-serviceworker v0.1.7 - Guido Zuidhof and contributors, licensed under MIT */ let coepCredentialless = false; -if (typeof window === 'undefined') { - self.addEventListener("install", () => self.skipWaiting()); - self.addEventListener("activate", (event) => event.waitUntil(self.clients.claim())); - - self.addEventListener("message", (ev) => { - if (!ev.data) { - return; - } else if (ev.data.type === "deregister") { - self.registration - .unregister() - .then(() => { - return self.clients.matchAll(); - }) - .then(clients => { - clients.forEach((client) => client.navigate(client.url)); - }); - } else if (ev.data.type === "coepCredentialless") { - coepCredentialless = ev.data.value; - } - }); - - self.addEventListener("fetch", function (event) { - const r = event.request; - if (r.cache === "only-if-cached" && r.mode !== "same-origin") { - return; - } - - const request = (coepCredentialless && r.mode === "no-cors") - ? new Request(r, { - credentials: "omit", - }) - : r; - event.respondWith( - fetch(request) - .then((response) => { - if (response.status === 0) { - return response; - } - - const newHeaders = new Headers(response.headers); - newHeaders.set("Cross-Origin-Embedder-Policy", - coepCredentialless ? "credentialless" : "require-corp" - ); - if (!coepCredentialless) { - newHeaders.set("Cross-Origin-Resource-Policy", "cross-origin"); - } - newHeaders.set("Cross-Origin-Opener-Policy", "same-origin"); - - return new Response(response.body, { - status: response.status, - statusText: response.statusText, - headers: newHeaders, - }); - }) - .catch((e) => console.error(e)) - ); - }); - +if (typeof window === "undefined") { + self.addEventListener("install", () => self.skipWaiting()); + self.addEventListener("activate", event => + event.waitUntil(self.clients.claim()), + ); + + self.addEventListener("message", ev => { + if (!ev.data) { + return; + } else if (ev.data.type === "deregister") { + self.registration + .unregister() + .then(() => { + return self.clients.matchAll(); + }) + .then(clients => { + clients.forEach(client => client.navigate(client.url)); + }); + } else if (ev.data.type === "coepCredentialless") { + coepCredentialless = ev.data.value; + } + }); + + self.addEventListener("fetch", function (event) { + const r = event.request; + if (r.cache === "only-if-cached" && r.mode !== "same-origin") { + return; + } + + const request = + coepCredentialless && r.mode === "no-cors" + ? new Request(r, { + credentials: "omit", + }) + : r; + event.respondWith( + fetch(request) + .then(response => { + if (response.status === 0) { + return response; + } + + const newHeaders = new Headers(response.headers); + newHeaders.set( + "Cross-Origin-Embedder-Policy", + coepCredentialless ? "credentialless" : "require-corp", + ); + if (!coepCredentialless) { + newHeaders.set("Cross-Origin-Resource-Policy", "cross-origin"); + } + newHeaders.set("Cross-Origin-Opener-Policy", "same-origin"); + + return new Response(response.body, { + status: response.status, + statusText: response.statusText, + headers: newHeaders, + }); + }) + .catch(e => console.error(e)), + ); + }); } else { - (() => { - const reloadedBySelf = window.sessionStorage.getItem("coiReloadedBySelf"); - window.sessionStorage.removeItem("coiReloadedBySelf"); - const coepDegrading = (reloadedBySelf == "coepdegrade"); - - // You can customize the behavior of this script through a global `coi` variable. - const coi = { - shouldRegister: () => !reloadedBySelf, - shouldDeregister: () => false, - coepCredentialless: () => true, - coepDegrade: () => true, - doReload: () => window.location.reload(), - quiet: false, - ...window.coi - }; - - const n = navigator; - const controlling = n.serviceWorker && n.serviceWorker.controller; - - // Record the failure if the page is served by serviceWorker. - if (controlling && !window.crossOriginIsolated) { - window.sessionStorage.setItem("coiCoepHasFailed", "true"); - } - const coepHasFailed = window.sessionStorage.getItem("coiCoepHasFailed"); - - if (controlling) { - // Reload only on the first failure. - const reloadToDegrade = coi.coepDegrade() && !( - coepDegrading || window.crossOriginIsolated + (() => { + const reloadedBySelf = window.sessionStorage.getItem("coiReloadedBySelf"); + window.sessionStorage.removeItem("coiReloadedBySelf"); + const coepDegrading = reloadedBySelf == "coepdegrade"; + + // You can customize the behavior of this script through a global `coi` variable. + const coi = { + shouldRegister: () => !reloadedBySelf, + shouldDeregister: () => false, + coepCredentialless: () => true, + coepDegrade: () => true, + doReload: () => window.location.reload(), + quiet: false, + ...window.coi, + }; + + const n = navigator; + const controlling = n.serviceWorker && n.serviceWorker.controller; + + // Record the failure if the page is served by serviceWorker. + if (controlling && !window.crossOriginIsolated) { + window.sessionStorage.setItem("coiCoepHasFailed", "true"); + } + const coepHasFailed = window.sessionStorage.getItem("coiCoepHasFailed"); + + if (controlling) { + // Reload only on the first failure. + const reloadToDegrade = + coi.coepDegrade() && !(coepDegrading || window.crossOriginIsolated); + n.serviceWorker.controller.postMessage({ + type: "coepCredentialless", + value: + reloadToDegrade || (coepHasFailed && coi.coepDegrade()) + ? false + : coi.coepCredentialless(), + }); + if (reloadToDegrade) { + !coi.quiet && console.log("Reloading page to degrade COEP."); + window.sessionStorage.setItem("coiReloadedBySelf", "coepdegrade"); + coi.doReload("coepdegrade"); + } + + if (coi.shouldDeregister()) { + n.serviceWorker.controller.postMessage({ type: "deregister" }); + } + } + + // If we're already coi: do nothing. Perhaps it's due to this script doing its job, or COOP/COEP are + // already set from the origin server. Also if the browser has no notion of crossOriginIsolated, just give up here. + if (window.crossOriginIsolated !== false || !coi.shouldRegister()) return; + + if (!window.isSecureContext) { + !coi.quiet && + console.log( + "COOP/COEP Service Worker not registered, a secure context is required.", + ); + return; + } + + // In some environments (e.g. Firefox private mode) this won't be available + if (!n.serviceWorker) { + !coi.quiet && + console.error( + "COOP/COEP Service Worker not registered, perhaps due to private mode.", + ); + return; + } + + n.serviceWorker.register(window.document.currentScript.src).then( + registration => { + !coi.quiet && + console.log( + "COOP/COEP Service Worker registered", + registration.scope, + ); + + registration.addEventListener("updatefound", () => { + !coi.quiet && + console.log( + "Reloading page to make use of updated COOP/COEP Service Worker.", ); - n.serviceWorker.controller.postMessage({ - type: "coepCredentialless", - value: (reloadToDegrade || coepHasFailed && coi.coepDegrade()) - ? false - : coi.coepCredentialless(), - }); - if (reloadToDegrade) { - !coi.quiet && console.log("Reloading page to degrade COEP."); - window.sessionStorage.setItem("coiReloadedBySelf", "coepdegrade"); - coi.doReload("coepdegrade"); - } - - if (coi.shouldDeregister()) { - n.serviceWorker.controller.postMessage({ type: "deregister" }); - } - } - - // If we're already coi: do nothing. Perhaps it's due to this script doing its job, or COOP/COEP are - // already set from the origin server. Also if the browser has no notion of crossOriginIsolated, just give up here. - if (window.crossOriginIsolated !== false || !coi.shouldRegister()) return; - - if (!window.isSecureContext) { - !coi.quiet && console.log("COOP/COEP Service Worker not registered, a secure context is required."); - return; - } - - // In some environments (e.g. Firefox private mode) this won't be available - if (!n.serviceWorker) { - !coi.quiet && console.error("COOP/COEP Service Worker not registered, perhaps due to private mode."); - return; + window.sessionStorage.setItem("coiReloadedBySelf", "updatefound"); + coi.doReload(); + }); + + // If the registration is active, but it's not controlling the page + if (registration.active && !n.serviceWorker.controller) { + !coi.quiet && + console.log( + "Reloading page to make use of COOP/COEP Service Worker.", + ); + window.sessionStorage.setItem("coiReloadedBySelf", "notcontrolling"); + coi.doReload(); } - - n.serviceWorker.register(window.document.currentScript.src).then( - (registration) => { - !coi.quiet && console.log("COOP/COEP Service Worker registered", registration.scope); - - registration.addEventListener("updatefound", () => { - !coi.quiet && console.log("Reloading page to make use of updated COOP/COEP Service Worker."); - window.sessionStorage.setItem("coiReloadedBySelf", "updatefound"); - coi.doReload(); - }); - - // If the registration is active, but it's not controlling the page - if (registration.active && !n.serviceWorker.controller) { - !coi.quiet && console.log("Reloading page to make use of COOP/COEP Service Worker."); - window.sessionStorage.setItem("coiReloadedBySelf", "notcontrolling"); - coi.doReload(); - } - }, - (err) => { - !coi.quiet && console.error("COOP/COEP Service Worker failed to register:", err); - } - ); - })(); -} \ No newline at end of file + }, + err => { + !coi.quiet && + console.error("COOP/COEP Service Worker failed to register:", err); + }, + ); + })(); +} diff --git a/examples/cdn-coi-serviceworker/index.html b/examples/cdn-coi-serviceworker/index.html index 1e9754b4..d7430116 100644 --- a/examples/cdn-coi-serviceworker/index.html +++ b/examples/cdn-coi-serviceworker/index.html @@ -1,44 +1,45 @@ - + - - - - + + + Wasmer JavaScript SDK - + - +

- - + diff --git a/examples/cdn/index.html b/examples/cdn/index.html index 1151b2be..66009d44 100644 --- a/examples/cdn/index.html +++ b/examples/cdn/index.html @@ -1,43 +1,44 @@ - + - - - - + + + Wasmer JavaScript SDK - + - +

- - + diff --git a/examples/ffmpeg-react/index.html b/examples/ffmpeg-react/index.html index 1bddefeb..b2ea281e 100644 --- a/examples/ffmpeg-react/index.html +++ b/examples/ffmpeg-react/index.html @@ -1,4 +1,4 @@ - + diff --git a/examples/ffmpeg-react/postcss.config.js b/examples/ffmpeg-react/postcss.config.js index 2e7af2b7..2aa7205d 100644 --- a/examples/ffmpeg-react/postcss.config.js +++ b/examples/ffmpeg-react/postcss.config.js @@ -3,4 +3,4 @@ export default { tailwindcss: {}, autoprefixer: {}, }, -} +}; diff --git a/examples/ffmpeg-react/src/App.tsx b/examples/ffmpeg-react/src/App.tsx index 45b730d1..c63bb6ad 100644 --- a/examples/ffmpeg-react/src/App.tsx +++ b/examples/ffmpeg-react/src/App.tsx @@ -4,260 +4,257 @@ import { useDropzone } from "react-dropzone"; import { useWasmerPackage, useWasmerSdk } from "./hooks"; interface VideoProps { - preview: boolean; - fileSrc?: ReturnType; - file: File | null; + preview: boolean; + fileSrc?: ReturnType; + file: File | null; } enum PROCESSING_STATUS { - NOT_STARTED, - RUNNING, - FINISHED, - FAILED, + NOT_STARTED, + RUNNING, + FINISHED, + FAILED, } export default function App() { - const sdk = useWasmerSdk(); - const pkg = useWasmerPackage("wasmer/ffmpeg"); - console.log(pkg); + const sdk = useWasmerSdk(); + const pkg = useWasmerPackage("wasmer/ffmpeg"); + console.log(pkg); - const [userVideo, setUserVideo] = useState({ - preview: false, - fileSrc: "", - file: null, - }); - - const [outputVideo, setOutputVideo] = useState({ - preview: false, - fileSrc: "", - file: null, - }); + const [userVideo, setUserVideo] = useState({ + preview: false, + fileSrc: "", + file: null, + }); - const [processingStatus, setProcessingStatus] = useState( - PROCESSING_STATUS.NOT_STARTED, - ); + const [outputVideo, setOutputVideo] = useState({ + preview: false, + fileSrc: "", + file: null, + }); - const previewVideoRef = useRef(null); - const outputVideoRef = useRef(null); + const [processingStatus, setProcessingStatus] = useState( + PROCESSING_STATUS.NOT_STARTED, + ); - const [fileU8Arr, setFileU8Arr] = useState(null); + const previewVideoRef = useRef(null); + const outputVideoRef = useRef(null); - const onDrop = useCallback((acceptedFiles: File[]) => { - const file = acceptedFiles[0]; - console.log(file); - setUserVideo({ - preview: true, - fileSrc: URL.createObjectURL(file), - file, - }); - }, []); + const [fileU8Arr, setFileU8Arr] = useState(null); - const { getRootProps, getInputProps } = useDropzone({ - maxFiles: 1, - accept: { - "video/*": [], - }, - onDrop, + const onDrop = useCallback((acceptedFiles: File[]) => { + const file = acceptedFiles[0]; + console.log(file); + setUserVideo({ + preview: true, + fileSrc: URL.createObjectURL(file), + file, }); + }, []); - useEffect(() => { - if (!userVideo.fileSrc || !previewVideoRef.current) return; - previewVideoRef.current?.load(); - }, [userVideo.fileSrc]); + const { getRootProps, getInputProps } = useDropzone({ + maxFiles: 1, + accept: { + "video/*": [], + }, + onDrop, + }); - useEffect(() => { - if (!outputVideo.fileSrc || !outputVideoRef.current) return; - outputVideoRef.current?.load(); - }, [outputVideo.fileSrc]); + useEffect(() => { + if (!userVideo.fileSrc || !previewVideoRef.current) return; + previewVideoRef.current?.load(); + }, [userVideo.fileSrc]); + useEffect(() => { + if (!outputVideo.fileSrc || !outputVideoRef.current) return; + outputVideoRef.current?.load(); + }, [outputVideo.fileSrc]); - useEffect(() => { - if (!userVideo.file) { - return; - } - const reader = new FileReader(); + useEffect(() => { + if (!userVideo.file) { + return; + } + const reader = new FileReader(); - reader.onload = function (e: ProgressEvent) { - if (!e.target) return; - const arrayBuffer = e.target.result as ArrayBuffer; - const u8arr = new Uint8Array(arrayBuffer); + reader.onload = function (e: ProgressEvent) { + if (!e.target) return; + const arrayBuffer = e.target.result as ArrayBuffer; + const u8arr = new Uint8Array(arrayBuffer); - setFileU8Arr(u8arr); - }; + setFileU8Arr(u8arr); + }; - reader.readAsArrayBuffer(userVideo.file); - }, [userVideo.file]); + reader.readAsArrayBuffer(userVideo.file); + }, [userVideo.file]); - useEffect(() => { - if (processingStatus === PROCESSING_STATUS.FAILED) { - setTimeout(() => { - setProcessingStatus(PROCESSING_STATUS.NOT_STARTED); - }, 4000); - } - }, [processingStatus]); - const runFfmpegProcessing = async () => { - if (!fileU8Arr || sdk.state != "loaded") return; + useEffect(() => { + if (processingStatus === PROCESSING_STATUS.FAILED) { + setTimeout(() => { + setProcessingStatus(PROCESSING_STATUS.NOT_STARTED); + }, 4000); + } + }, [processingStatus]); + const runFfmpegProcessing = async () => { + if (!fileU8Arr || sdk.state != "loaded") return; - setProcessingStatus(PROCESSING_STATUS.RUNNING); + setProcessingStatus(PROCESSING_STATUS.RUNNING); - const tmp = new sdk.Directory(); - await tmp.writeFile("input.mp4", fileU8Arr); + const tmp = new sdk.Directory(); + await tmp.writeFile("input.mp4", fileU8Arr); - if (pkg.state != "loaded" || !pkg.pkg.entrypoint) return; + if (pkg.state != "loaded" || !pkg.pkg.entrypoint) return; - const instance = await pkg.entrypoint!.run({ - args: [ - "-i", - "/videos/input.mp4", - "-vf", - "format=gray", - "/videos/output.mp4", - ], - mount: { "/videos": tmp }, - }); + const instance = await pkg.entrypoint!.run({ + args: [ + "-i", + "/videos/input.mp4", + "-vf", + "format=gray", + "/videos/output.mp4", + ], + mount: { "/videos": tmp }, + }); - await instance.stdin?.close(); - let output = await instance.wait(); + await instance.stdin?.close(); + let output = await instance.wait(); - if (output.ok) { - console.log(output.stderr); - const contents = await tmp.readFile("output.mp4"); + if (output.ok) { + console.log(output.stderr); + const contents = await tmp.readFile("output.mp4"); - const u8arr = new Uint8Array(contents.buffer); - const file = new File([u8arr], "output.mp4", { - type: "video/mp4", - }); - setOutputVideo({ - preview: true, - fileSrc: URL.createObjectURL(file), - file, - }); - setProcessingStatus(PROCESSING_STATUS.FINISHED); - } else { - console.log(output.stderr); - setProcessingStatus(PROCESSING_STATUS.FAILED); - } - }; + const u8arr = new Uint8Array(contents.buffer); + const file = new File([u8arr], "output.mp4", { + type: "video/mp4", + }); + setOutputVideo({ + preview: true, + fileSrc: URL.createObjectURL(file), + file, + }); + setProcessingStatus(PROCESSING_STATUS.FINISHED); + } else { + console.log(output.stderr); + setProcessingStatus(PROCESSING_STATUS.FAILED); + } + }; - return ( -
- {!!userVideo.fileSrc ? ( -
-
- - Input video - - -
+ return ( +
+ {!!userVideo.fileSrc ? ( +
+
+ + Input video + + +
- {!!outputVideo.fileSrc && - processingStatus === PROCESSING_STATUS.FINISHED && ( -
- - Output Video - - -
- )} -
- ) : ( -
- -
-
-
-
-
+ {!!outputVideo.fileSrc && + processingStatus === PROCESSING_STATUS.FINISHED && ( +
+ + Output Video + + +
)} +
+ ) : ( +
+ +
+
+
+
+
+ )} - + - { - outputVideo.fileSrc && - Download - } -
- ); + {outputVideo.fileSrc && ( + + Download + + )} + + ); } function ProcessingStatus({ status }: { status: PROCESSING_STATUS }) { - switch (status) { - case PROCESSING_STATUS.FAILED: - return ( -
- Processing failed. Please try again. -
- ); - case PROCESSING_STATUS.FINISHED: - return
Processing finished.
; - case PROCESSING_STATUS.RUNNING: - return ( -
- FFmpeg is processing. Please wait. -
- ); - case PROCESSING_STATUS.NOT_STARTED: - return
Run FFmpeg Processing
; - } + switch (status) { + case PROCESSING_STATUS.FAILED: + return ( +
Processing failed. Please try again.
+ ); + case PROCESSING_STATUS.FINISHED: + return
Processing finished.
; + case PROCESSING_STATUS.RUNNING: + return ( +
+ FFmpeg is processing. Please wait. +
+ ); + case PROCESSING_STATUS.NOT_STARTED: + return
Run FFmpeg Processing
; + } } diff --git a/examples/ffmpeg-react/src/hooks.tsx b/examples/ffmpeg-react/src/hooks.tsx index 3e1fd5d6..bcfe3a67 100644 --- a/examples/ffmpeg-react/src/hooks.tsx +++ b/examples/ffmpeg-react/src/hooks.tsx @@ -1,17 +1,26 @@ import React, { useContext, useEffect, useState } from "react"; -import type { Command, Runtime, Wasmer, init, initializeLogger } from "@wasmer/sdk"; +import type { + Command, + Runtime, + Wasmer, + init, + initializeLogger, +} from "@wasmer/sdk"; type LoadedSdkState = { state: "loaded" } & typeof import("@wasmer/sdk"); -export type WasmerSdkState = LoadedSdkState | { state: "loading" } | { state: "error", error: any }; +export type WasmerSdkState = + | LoadedSdkState + | { state: "loading" } + | { state: "error"; error: any }; export type WasmerSdkProps = { - /** - * The filter passed to {@link initializeLogger}. - */ - log?: string, - wasm?: Parameters[0], - children: React.ReactElement, -} + /** + * The filter passed to {@link initializeLogger}. + */ + log?: string; + wasm?: Parameters[0]; + children: React.ReactElement; +}; const Context = React.createContext(null); @@ -24,89 +33,101 @@ let pending: Promise | undefined = undefined; * A wrapper component which will automatically initialize the Wasmer SDK. */ export function WasmerSdk(props?: WasmerSdkProps) { - const [state, setState] = useState(); - - useEffect(() => { - if (typeof pending == "undefined") { - pending = (async function () { - console.log("Importing @wasmer/sdk"); - const imported = await import("@wasmer/sdk/dist/WasmerSDKBundled"); - console.log("Imported @wasmer/sdk"); - await imported.init(props?.wasm); - imported.initializeLogger(props?.log); - return imported; - })() - } - - pending - .then(sdk => setState({ state: "loaded", ...sdk })) - .catch(e => setState({ state: "error", error: e })); - }, []) - - return ( - - {props?.children} - - ) + const [state, setState] = useState(); + + useEffect(() => { + if (typeof pending == "undefined") { + pending = (async function () { + console.log("Importing @wasmer/sdk"); + const imported = await import("@wasmer/sdk/dist/WasmerSDKBundled"); + console.log("Imported @wasmer/sdk"); + await imported.init({module: props?.wasm}); + imported.initializeLogger(props?.log); + return imported; + })(); + } + + pending + .then(sdk => setState({ state: "loaded", ...sdk })) + .catch(e => setState({ state: "error", error: e })); + }, []); + + return ( + + {props?.children} + + ); } export function useWasmerSdk(): WasmerSdkState { - const ctx = useContext(Context); + const ctx = useContext(Context); - if (ctx == null) { - throw new Error("Attempting to use the Wasmer SDK outside of a component"); - } + if (ctx == null) { + throw new Error( + "Attempting to use the Wasmer SDK outside of a component", + ); + } - return ctx; + return ctx; } type LoadingPackageState = - { state: "loading-package" } - | { - state: "loaded", pkg: Wasmer, - commands: Record, - entrypoint?: Command, + | { state: "loading-package" } + | { + state: "loaded"; + pkg: Wasmer; + commands: Record; + entrypoint?: Command; } - | { state: "error", error: any }; + | { state: "error"; error: any }; export type UseWasmerPackageState = - | { state: "loading-sdk" } - | { state: "sdk-error", error: any } - | LoadingPackageState; - -export function useWasmerPackage(pkg: string | Uint8Array, runtime?: Runtime): UseWasmerPackageState { - const sdk = useWasmerSdk(); - const [state, setState] = useState(); - - // We can't do anything until the SDK has been loaded - switch (sdk.state) { - case "error": - return { state: "sdk-error", error: sdk.error }; - case "loading": - return { state: "loading-sdk" }; - case "loaded": - break; - default: - throw new Error(`Unknown SDK state: ${sdk}`); - } - - if (typeof state != "undefined") { - return state; - } - - const newState = { state: "loading-package" } as const; - setState(newState); - - console.warn("Loading pkg", pkg, state); - - const pending = (typeof pkg == "string") - ? sdk.Wasmer.fromRegistry(pkg, runtime) - : sdk.Wasmer.fromFile(pkg, runtime); - - pending - .then(pkg => { - setState({ state: "loaded", pkg, commands: pkg.commands, entrypoint: pkg.entrypoint }); - }) - .catch(error => setState({ state: "error", error })); - - return newState; + | { state: "loading-sdk" } + | { state: "sdk-error"; error: any } + | LoadingPackageState; + +export function useWasmerPackage( + pkg: string | Uint8Array, + runtime?: Runtime, +): UseWasmerPackageState { + const sdk = useWasmerSdk(); + const [state, setState] = useState(); + + // We can't do anything until the SDK has been loaded + switch (sdk.state) { + case "error": + return { state: "sdk-error", error: sdk.error }; + case "loading": + return { state: "loading-sdk" }; + case "loaded": + break; + default: + throw new Error(`Unknown SDK state: ${sdk}`); + } + + if (typeof state != "undefined") { + return state; + } + + const newState = { state: "loading-package" } as const; + setState(newState); + + console.warn("Loading pkg", pkg, state); + + const pending = + typeof pkg == "string" + ? sdk.Wasmer.fromRegistry(pkg, runtime) + : sdk.Wasmer.fromFile(pkg, runtime); + + pending + .then(pkg => { + setState({ + state: "loaded", + pkg, + commands: pkg.commands, + entrypoint: pkg.entrypoint, + }); + }) + .catch(error => setState({ state: "error", error })); + + return newState; } diff --git a/examples/ffmpeg-react/src/main.tsx b/examples/ffmpeg-react/src/main.tsx index a4a8d1bb..8f431b1c 100644 --- a/examples/ffmpeg-react/src/main.tsx +++ b/examples/ffmpeg-react/src/main.tsx @@ -1,13 +1,13 @@ -import React from 'react' -import ReactDOM from 'react-dom/client' -import App from './App.tsx' -import './index.css' -import { WasmerSdk } from './hooks.tsx' +import React from "react"; +import ReactDOM from "react-dom/client"; +import App from "./App.tsx"; +import "./index.css"; +import { WasmerSdk } from "./hooks.tsx"; -ReactDOM.createRoot(document.getElementById('root')!).render( +ReactDOM.createRoot(document.getElementById("root")!).render( , -) +); diff --git a/examples/ffmpeg-react/vite.config.ts b/examples/ffmpeg-react/vite.config.ts index 61b63a98..8a5f140e 100644 --- a/examples/ffmpeg-react/vite.config.ts +++ b/examples/ffmpeg-react/vite.config.ts @@ -10,7 +10,7 @@ export default defineConfig({ "Cross-Origin-Embedder-Policy": "require-corp", }, fs: { - allow: ['../..'] - } + allow: ["../.."], + }, }, }); diff --git a/examples/markdown-editor-improved/index.html b/examples/markdown-editor-improved/index.html index fc2da4e9..4d3d5b00 100644 --- a/examples/markdown-editor-improved/index.html +++ b/examples/markdown-editor-improved/index.html @@ -1,19 +1,20 @@ - - - - + + + Wasmer Markdown Editor - - + + - +
- - + +
- - + diff --git a/examples/markdown-editor-improved/index.ts b/examples/markdown-editor-improved/index.ts index 15bbb976..df58a1fc 100644 --- a/examples/markdown-editor-improved/index.ts +++ b/examples/markdown-editor-improved/index.ts @@ -1,43 +1,51 @@ import { init, Wasmer, Command } from "@wasmer/sdk"; async function initialize() { - await init(); - return await Wasmer.fromRegistry("wasmer-examples/markdown-renderer"); + await init(); + return await Wasmer.fromRegistry("wasmer-examples/markdown-renderer"); } -function debounce(func: (...args: any[]) => void, delay: number): (...args: any[]) => void { - let debounceTimer: ReturnType; +function debounce( + func: (...args: any[]) => void, + delay: number, +): (...args: any[]) => void { + let debounceTimer: ReturnType; - return function(...args: any[]) { - clearTimeout(debounceTimer); - debounceTimer = setTimeout(() => func(...args), delay); - }; + return function (...args: any[]) { + clearTimeout(debounceTimer); + debounceTimer = setTimeout(() => func(...args), delay); + }; } async function renderMarkdown(cmd: Command, markdown: string) { - const instance = await cmd.run(); - const stdin = instance.stdin.getWriter(); - const encoder = new TextEncoder(); - await stdin.write(encoder.encode(markdown)); - await stdin.close(); - - const result = await instance.wait(); - return result.ok ? result.stdout : null; + const instance = await cmd.run(); + const stdin = instance.stdin.getWriter(); + const encoder = new TextEncoder(); + await stdin.write(encoder.encode(markdown)); + await stdin.close(); + + const result = await instance.wait(); + return result.ok ? result.stdout : null; } async function main() { - const pkg = await initialize(); - const output = document.getElementById("html-output") as HTMLIFrameElement; - const markdownInput = document.getElementById("markdown-input") as HTMLTextAreaElement; - - const debouncedRender = debounce(async () => { - const renderedHtml = await renderMarkdown(pkg.entrypoint!, markdownInput.value); - if (renderedHtml) { - output.srcdoc = renderedHtml; - } - }, 500); // 500 milliseconds debounce period - - markdownInput.addEventListener("input", debouncedRender); + const pkg = await initialize(); + const output = document.getElementById("html-output") as HTMLIFrameElement; + const markdownInput = document.getElementById( + "markdown-input", + ) as HTMLTextAreaElement; + + const debouncedRender = debounce(async () => { + const renderedHtml = await renderMarkdown( + pkg.entrypoint!, + markdownInput.value, + ); + if (renderedHtml) { + output.srcdoc = renderedHtml; + } + }, 500); // 500 milliseconds debounce period + + markdownInput.addEventListener("input", debouncedRender); } main(); diff --git a/examples/markdown-editor-improved/style.css b/examples/markdown-editor-improved/style.css index 0408dded..4732b68b 100644 --- a/examples/markdown-editor-improved/style.css +++ b/examples/markdown-editor-improved/style.css @@ -1,40 +1,41 @@ body { - font-family: Arial, sans-serif; - margin: 0; - padding: 0; - display: flex; - justify-content: center; - align-items: center; - height: 100vh; - background-color: #f4f4f4; + font-family: Arial, sans-serif; + margin: 0; + padding: 0; + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + background-color: #f4f4f4; } .editor-container { - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 20px; - width: 80%; - max-width: 1200px; - margin: auto; - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); - padding: 20px; - background: white; - border-radius: 8px; + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 20px; + width: 80%; + max-width: 1200px; + margin: auto; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + padding: 20px; + background: white; + border-radius: 8px; } -#markdown-input, #html-output { - border: 1px solid #ddd; - padding: 10px; - height: 400px; - overflow: auto; +#markdown-input, +#html-output { + border: 1px solid #ddd; + padding: 10px; + height: 400px; + overflow: auto; } #markdown-input { - resize: none; + resize: none; } #html-output { - background-color: #fff; - width: 100%; - border: none; + background-color: #fff; + width: 100%; + border: none; } diff --git a/examples/markdown-editor-improved/vite.config.js b/examples/markdown-editor-improved/vite.config.js index 732fa10a..b3ba1e7d 100644 --- a/examples/markdown-editor-improved/vite.config.js +++ b/examples/markdown-editor-improved/vite.config.js @@ -1,11 +1,10 @@ import { defineConfig } from "vite"; export default defineConfig({ - server: { - headers: { - "Cross-Origin-Opener-Policy": "same-origin", - "Cross-Origin-Embedder-Policy": "require-corp", - }, + server: { + headers: { + "Cross-Origin-Opener-Policy": "same-origin", + "Cross-Origin-Embedder-Policy": "require-corp", }, + }, }); - diff --git a/examples/markdown-editor/index.html b/examples/markdown-editor/index.html index fc2da4e9..4d3d5b00 100644 --- a/examples/markdown-editor/index.html +++ b/examples/markdown-editor/index.html @@ -1,19 +1,20 @@ - - - - + + + Wasmer Markdown Editor - - + + - +
- - + +
- - + diff --git a/examples/markdown-editor/index.ts b/examples/markdown-editor/index.ts index ba8127b8..282a5dd6 100644 --- a/examples/markdown-editor/index.ts +++ b/examples/markdown-editor/index.ts @@ -2,45 +2,49 @@ import { init, runWasix } from "@wasmer/sdk"; import markdownRendererUrl from "./markdown-renderer/target/wasm32-wasi/release/markdown-renderer.wasm?url"; async function initialize() { - await init(); - return WebAssembly.compileStreaming(fetch(markdownRendererUrl)); + await init(); + return WebAssembly.compileStreaming(fetch(markdownRendererUrl)); } -function debounce(func: (...args: any[]) => void, delay: number): (...args: any[]) => void { - let debounceTimer: ReturnType; +function debounce( + func: (...args: any[]) => void, + delay: number, +): (...args: any[]) => void { + let debounceTimer: ReturnType; - return function(...args: any[]) { - clearTimeout(debounceTimer); - debounceTimer = setTimeout(() => func(...args), delay); - }; + return function (...args: any[]) { + clearTimeout(debounceTimer); + debounceTimer = setTimeout(() => func(...args), delay); + }; } - async function renderMarkdown(module: WebAssembly.Module, markdown: string) { - const instance = await runWasix(module, {}); - const stdin = instance.stdin.getWriter(); - const encoder = new TextEncoder(); + const instance = await runWasix(module, {}); + const stdin = instance.stdin.getWriter(); + const encoder = new TextEncoder(); - await stdin.write(encoder.encode(markdown)); - await stdin.close(); + await stdin.write(encoder.encode(markdown)); + await stdin.close(); - const result = await instance.wait(); - return result.ok ? result.stdout : null; + const result = await instance.wait(); + return result.ok ? result.stdout : null; } async function main() { - const module = await initialize(); - const output = document.getElementById("html-output") as HTMLIFrameElement; - const markdownInput = document.getElementById("markdown-input") as HTMLTextAreaElement; - - const debouncedRender = debounce(async () => { - const renderedHtml = await renderMarkdown(module, markdownInput.value); - if (renderedHtml) { - output.srcdoc = renderedHtml; - } - }, 500); // 500 milliseconds debounce period - - markdownInput.addEventListener("input", debouncedRender); + const module = await initialize(); + const output = document.getElementById("html-output") as HTMLIFrameElement; + const markdownInput = document.getElementById( + "markdown-input", + ) as HTMLTextAreaElement; + + const debouncedRender = debounce(async () => { + const renderedHtml = await renderMarkdown(module, markdownInput.value); + if (renderedHtml) { + output.srcdoc = renderedHtml; + } + }, 500); // 500 milliseconds debounce period + + markdownInput.addEventListener("input", debouncedRender); } main(); diff --git a/examples/markdown-editor/style.css b/examples/markdown-editor/style.css index 0408dded..4732b68b 100644 --- a/examples/markdown-editor/style.css +++ b/examples/markdown-editor/style.css @@ -1,40 +1,41 @@ body { - font-family: Arial, sans-serif; - margin: 0; - padding: 0; - display: flex; - justify-content: center; - align-items: center; - height: 100vh; - background-color: #f4f4f4; + font-family: Arial, sans-serif; + margin: 0; + padding: 0; + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + background-color: #f4f4f4; } .editor-container { - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 20px; - width: 80%; - max-width: 1200px; - margin: auto; - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); - padding: 20px; - background: white; - border-radius: 8px; + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 20px; + width: 80%; + max-width: 1200px; + margin: auto; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + padding: 20px; + background: white; + border-radius: 8px; } -#markdown-input, #html-output { - border: 1px solid #ddd; - padding: 10px; - height: 400px; - overflow: auto; +#markdown-input, +#html-output { + border: 1px solid #ddd; + padding: 10px; + height: 400px; + overflow: auto; } #markdown-input { - resize: none; + resize: none; } #html-output { - background-color: #fff; - width: 100%; - border: none; + background-color: #fff; + width: 100%; + border: none; } diff --git a/examples/markdown-editor/vite.config.js b/examples/markdown-editor/vite.config.js index c911c64f..4a183245 100644 --- a/examples/markdown-editor/vite.config.js +++ b/examples/markdown-editor/vite.config.js @@ -2,32 +2,31 @@ import { defineConfig } from "vite"; import { exec } from "node:child_process"; export default defineConfig({ - server: { - headers: { - "Cross-Origin-Opener-Policy": "same-origin", - "Cross-Origin-Embedder-Policy": "require-corp", - }, + server: { + headers: { + "Cross-Origin-Opener-Policy": "same-origin", + "Cross-Origin-Embedder-Policy": "require-corp", }, - plugins: [ - { - name: "cargo-build", - buildStart: () => { - return new Promise((resolve, reject) => { - exec( - "cargo build --target=wasm32-wasi --manifest-path=markdown-renderer/Cargo.toml --release --quiet", - (err, stdout, stderr) => { - if (err) { - console.log("Stdout:", stdout); - console.log("Stderr:", stderr); - reject(err); - } else { - resolve(); - } - } - ); - }); - } - }, - ] + }, + plugins: [ + { + name: "cargo-build", + buildStart: () => { + return new Promise((resolve, reject) => { + exec( + "cargo build --target=wasm32-wasi --manifest-path=markdown-renderer/Cargo.toml --release --quiet", + (err, stdout, stderr) => { + if (err) { + console.log("Stdout:", stdout); + console.log("Stderr:", stderr); + reject(err); + } else { + resolve(); + } + }, + ); + }); + }, + }, + ], }); - diff --git a/examples/simplimage/index.tsx b/examples/simplimage/index.tsx index 6cb92c6e..db802a03 100644 --- a/examples/simplimage/index.tsx +++ b/examples/simplimage/index.tsx @@ -20,12 +20,12 @@ function ImageEditor(props: { file: File }) { const onSubmit = () => { if ((resizeWidth ?? 0) > 0 && (resizeHeight ?? 0) > 0) { resizeImage(file, resizeWidth ?? 0, resizeHeight ?? 0) - .then((blob) => { + .then(blob => { const url = URL.createObjectURL(blob); setOutputBlob(blob); setOutputBlobUrl(url); }) - .catch((e) => { + .catch(e => { setError(e.message); }); } @@ -64,7 +64,7 @@ function ImageEditor(props: { file: File }) { placeholder="200" aria-label="width" value={resizeWidth ?? ""} - onInput={(e) => { + onInput={e => { try { const value = parseInt(e.currentTarget.value); setResizeWidth(value); @@ -78,7 +78,7 @@ function ImageEditor(props: { file: File }) { placeholder="400" aria-label="height" value={resizeHeight ?? ""} - onInput={(e) => { + onInput={e => { try { const value = parseInt(e.currentTarget.value); setResizeHeight(value); @@ -143,7 +143,7 @@ function App() { type="file" id="img-input" accept="image/jpeg,image/jpg,image/png" - onChange={(e) => { + onChange={e => { const file = e.currentTarget.files?.item(0); if (!file) { return; @@ -186,7 +186,7 @@ async function resizeImage( height: number, ): Promise { const { init, runWasix, initializeLogger } = await import("@wasmer/sdk"); - await init(wasmSdkUrl); + await init({module: wasmSdkUrl}); if (!MODULE) { await initialize(); @@ -199,7 +199,13 @@ async function resizeImage( const stdin = await file.arrayBuffer(); const instance = await runWasix(MODULE, { - args: ["resize", "--width", width.toString(), "--height", height.toString()], + args: [ + "resize", + "--width", + width.toString(), + "--height", + height.toString(), + ], stdin, }); diff --git a/examples/wasmer.sh/index.html b/examples/wasmer.sh/index.html index bd8e63c5..410ac142 100644 --- a/examples/wasmer.sh/index.html +++ b/examples/wasmer.sh/index.html @@ -1,27 +1,28 @@ - - - - + + + Wasmer Shell - + - +
- - + diff --git a/examples/wasmer.sh/index.ts b/examples/wasmer.sh/index.ts index e1fb791f..b23e8fad 100644 --- a/examples/wasmer.sh/index.ts +++ b/examples/wasmer.sh/index.ts @@ -14,35 +14,39 @@ const args = params.getAll("arg"); const logFilter = params.get("log") || "warn"; async function main() { - // Note: We dynamically import the Wasmer SDK to make sure the bundler puts - // it in its own chunk. This works around an issue where just importing - // xterm.js runs top-level code which accesses the DOM, and if it's in the - // same chunk as @wasmer/sdk, each Web Worker will try to run this code and - // crash. - // See https://github.com/wasmerio/wasmer-js/issues/373 - const { Wasmer, init, initializeLogger } = await import("@wasmer/sdk"); - - await init(wasmerSDKUrl); - initializeLogger(logFilter); - - const term = new Terminal({ cursorBlink: true, convertEol: true }); - const fit = new FitAddon(); - term.loadAddon(fit); - term.open(document.getElementById("terminal")!); - fit.fit(); - - term.writeln("Starting..."); - const pkg = await Wasmer.fromRegistry(packageName); - term.reset(); - const instance = await pkg.entrypoint!.run({ args, uses }); - connectStreams(instance, term); + // Note: We dynamically import the Wasmer SDK to make sure the bundler puts + // it in its own chunk. This works around an issue where just importing + // xterm.js runs top-level code which accesses the DOM, and if it's in the + // same chunk as @wasmer/sdk, each Web Worker will try to run this code and + // crash. + // See https://github.com/wasmerio/wasmer-js/issues/373 + const { Wasmer, init, initializeLogger } = await import("@wasmer/sdk"); + + await init({module: wasmerSDKUrl}); + initializeLogger(logFilter); + + const term = new Terminal({ cursorBlink: true, convertEol: true }); + const fit = new FitAddon(); + term.loadAddon(fit); + term.open(document.getElementById("terminal")!); + fit.fit(); + + term.writeln("Starting..."); + const pkg = await Wasmer.fromRegistry(packageName); + term.reset(); + const instance = await pkg.entrypoint!.run({ args, uses }); + connectStreams(instance, term); } function connectStreams(instance: Instance, term: Terminal) { - const stdin = instance.stdin?.getWriter(); - term.onData(data => stdin?.write(encoder.encode(data))); - instance.stdout.pipeTo(new WritableStream({ write: chunk => term.write(chunk) })); - instance.stderr.pipeTo(new WritableStream({ write: chunk => term.write(chunk) })); + const stdin = instance.stdin?.getWriter(); + term.onData(data => stdin?.write(encoder.encode(data))); + instance.stdout.pipeTo( + new WritableStream({ write: chunk => term.write(chunk) }), + ); + instance.stderr.pipeTo( + new WritableStream({ write: chunk => term.write(chunk) }), + ); } main(); diff --git a/examples/wasmer.sh/vite.config.js b/examples/wasmer.sh/vite.config.js index 0e34ac70..55b150d7 100644 --- a/examples/wasmer.sh/vite.config.js +++ b/examples/wasmer.sh/vite.config.js @@ -1,15 +1,15 @@ import { defineConfig } from "vite"; export default defineConfig({ - server: { - headers: { - "Cross-Origin-Opener-Policy": "same-origin", - "Cross-Origin-Embedder-Policy": "require-corp", - }, + server: { + headers: { + "Cross-Origin-Opener-Policy": "same-origin", + "Cross-Origin-Embedder-Policy": "require-corp", }, - build: { - modulePreload: { - polyfill: false, - }, + }, + build: { + modulePreload: { + polyfill: false, }, + }, }); diff --git a/package-lock.json b/package-lock.json index 178dd6a2..0e5102ea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,9 @@ "name": "@wasmer/sdk", "version": "0.6.0", "license": "MIT", + "dependencies": { + "dotenv": "^16.4.5" + }, "devDependencies": { "@babel/preset-env": "^7.23.3", "@esm-bundle/chai": "^4.3.4-fix.0", @@ -16,6 +19,7 @@ "@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-typescript": "^11.1.5", "@rollup/plugin-wasm": "^6.2.2", + "@types/mocha": "^10.0.7", "@web/dev-server-esbuild": "^1.0.1", "@web/test-runner": "^0.18.0", "prettier": "^3.1.0", @@ -2808,6 +2812,12 @@ "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", "dev": true }, + "node_modules/@types/mocha": { + "version": "10.0.7", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.7.tgz", + "integrity": "sha512-GN8yJ1mNTcFcah/wKEFIJckJx9iJLoMSzWcfRRuxz/Jk+U6KQNnml+etbtxFK8lPjzOw3zp4Ha/kjSst9fsHYw==", + "dev": true + }, "node_modules/@types/node": { "version": "20.10.5", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.5.tgz", @@ -4180,6 +4190,18 @@ "node": ">=8" } }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", diff --git a/package.json b/package.json index 686ed961..8d3cfb0a 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "build:dev": "wasm-pack build --dev --target=web --weak-refs --no-pack && rollup -c --environment BUILD:development", "dev": "rollup -c -w", "test": "web-test-runner --node-resolve --esbuild-target auto --config ./web-dev-server.config.mjs", + "test-reg": "web-test-runner --node-resolve --esbuild-target auto --config ./web-dev-server.config.mjs --group reg", "docs": "typedoc --options docs/typedoc.json", "doc:watch": "typedoc --watch", "fmt": "prettier . --write", @@ -49,16 +50,19 @@ "@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-typescript": "^11.1.5", "@rollup/plugin-wasm": "^6.2.2", + "@types/mocha": "^10.0.7", "@web/dev-server-esbuild": "^1.0.1", "@web/test-runner": "^0.18.0", "prettier": "^3.1.0", "rimraf": "^5.0.5", + "rollup": "^4.9.1", "rollup-plugin-copy": "^3.5.0", "rollup-plugin-dts": "^6.1.0", - "rollup": "^4.8.0", "typedoc": "^0.25.4", "typescript": "^5.3.3" }, "browserslist": "> 0.5%, last 2 versions, Firefox ESR, not dead", - "dependencies": {} + "dependencies": { + "dotenv": "^16.4.5" + } } diff --git a/rollup.config.mjs b/rollup.config.mjs index 42870056..0a5f62fd 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -3,8 +3,8 @@ import pkg from "./package.json" assert { type: "json" }; import dts from "rollup-plugin-dts"; import typescript from "@rollup/plugin-typescript"; import replace from "@rollup/plugin-replace"; -import copy from 'rollup-plugin-copy'; -import { wasm } from '@rollup/plugin-wasm'; +import copy from "rollup-plugin-copy"; +import { wasm } from "@rollup/plugin-wasm"; const LIBRARY_NAME = "WasmerSDK"; // Change with your library's name const EXTERNAL = []; // Indicate which modules should be treated as external @@ -22,79 +22,85 @@ const banner = `/*! */`; const makeConfig = (env = "development", input, name, plugins = []) => { - const config = { - input, - external: EXTERNAL, - output: [ - { - banner, - name: name, - file: `dist/${name}.umd.js`, - format: "umd", - exports: "auto", - globals: GLOBALS, - }, - { - banner, - file: `dist/${name}.cjs`, - format: "cjs", - exports: "auto", - globals: GLOBALS, - }, - { - banner, - file: `dist/${name}.js`, - format: "es", - exports: "named", - globals: GLOBALS, - }, + const config = { + input, + external: EXTERNAL, + output: [ + { + banner, + name: name, + file: `dist/${name}.umd.js`, + format: "umd", + exports: "auto", + globals: GLOBALS, + }, + { + banner, + file: `dist/${name}.cjs`, + format: "cjs", + exports: "auto", + globals: GLOBALS, + }, + { + banner, + file: `dist/${name}.js`, + format: "es", + exports: "named", + globals: GLOBALS, + }, + ], + plugins: [ + typescript(), + ...plugins, + copy({ + targets: [ + { + src: ["pkg/wasmer_js_bg.wasm", "pkg/wasmer_js_bg.wasm.d.ts"], + dest: "dist", + }, ], - plugins: [ - typescript(), - ...plugins, - copy({ - targets: [ - { src: ['pkg/wasmer_js_bg.wasm', 'pkg/wasmer_js_bg.wasm.d.ts'], dest: 'dist' }, - ] - }) - ], - }; + }), + ], + }; - if (env === "production") { - config.plugins.push( - terser({ - output: { - comments: /^!/, - }, - }), - ); - } + if (env === "production") { config.plugins.push( - replace({ - values: { - "globalThis.wasmUrl": `"https://unpkg.com/${pkg.name}@${pkg.version}/dist/wasmer_js_bg.wasm"`, - "globalThis.workerUrl": `"https://unpkg.com/${pkg.name}@${pkg.version}/dist/WasmerSDK.js"`, - }, - preventAssignment: true, - }), + terser({ + output: { + comments: /^!/, + }, + }), ); + } + config.plugins.push( + replace({ + values: { + "globalThis.wasmUrl": `"https://unpkg.com/${pkg.name}@${pkg.version}/dist/wasmer_js_bg.wasm"`, + "globalThis.workerUrl": `"https://unpkg.com/${pkg.name}@${pkg.version}/dist/WasmerSDK.js"`, + }, + preventAssignment: true, + }), + ); - return config; + return config; }; export default commandLineArgs => { - let env = commandLineArgs.environment === "BUILD:production" ? "production" : null; - const configs = [ - makeConfig(env, "WasmerSDK.ts", LIBRARY_NAME), - makeConfig(env, "WasmerSDKBundled.ts", `${LIBRARY_NAME}Bundled`, [wasm({ - maxFileSize: 100 * 1024 * 1024, - })]), - { - input: "./pkg/wasmer_js.d.ts", - output: [{ file: "dist/pkg/wasmer_js.d.ts", format: "es" }], - plugins: [dts()], - }, - ]; + let env = + commandLineArgs.environment === "BUILD:production" ? "production" : null; + const configs = [ + makeConfig(env, "WasmerSDK.ts", LIBRARY_NAME), + makeConfig(env, "WasmerSDKBundled.ts", `${LIBRARY_NAME}Bundled`, [ + wasm({ + maxFileSize: 100 * 1024 * 1024, + }), + ]), + { + input: "./pkg/wasmer_js.d.ts", + output: [{ file: "dist/pkg/wasmer_js.d.ts", format: "es" }], + plugins: [dts()], + }, + ]; - return configs; + return configs; }; diff --git a/src/fs/directory.rs b/src/fs/directory.rs index 12d27ed7..f0b0d2ef 100644 --- a/src/fs/directory.rs +++ b/src/fs/directory.rs @@ -201,6 +201,16 @@ impl FileSystem for Directory { fn new_open_options(&self) -> virtual_fs::OpenOptions { virtual_fs::OpenOptions::new(self) } + + #[tracing::instrument(level = "trace", skip(self))] + fn readlink(&self, path: &Path) -> virtual_fs::Result { + self.0.readlink(path) + } + + #[tracing::instrument(level = "trace", skip(self))] + fn symlink_metadata(&self, path: &Path) -> virtual_fs::Result { + self.0.symlink_metadata(path) + } } impl virtual_fs::FileOpener for Directory { diff --git a/src/lib.rs b/src/lib.rs index ab4e5951..52681af9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +#![feature(once_cell_try)] + #[cfg(test)] wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); @@ -10,6 +12,7 @@ mod logging; mod net; mod options; mod package_loader; +pub mod registry; mod run; mod runtime; mod streams; @@ -26,6 +29,7 @@ pub use crate::{ js_runtime::{JsRuntime, RuntimeOptions}, logging::initialize_logger, options::{RunOptions, SpawnOptions}, + registry::RegistryConfig, run::run_wasix, utils::StringOrBytes, wasmer::Wasmer, diff --git a/src/package_loader.rs b/src/package_loader.rs index 5d50efc1..deedb4a3 100644 --- a/src/package_loader.rs +++ b/src/package_loader.rs @@ -94,8 +94,7 @@ impl wasmer_wasix::runtime::package_loader::PackageLoader for PackageLoader { #[tracing::instrument( skip_all, fields( - pkg.name=summary.pkg.name.as_str(), - pkg.version=%summary.pkg.version, + pkg=format!("{:?}", summary.pkg.id), pkg.url=summary.dist.webc.as_str(), ), )] diff --git a/src/registry/app.rs b/src/registry/app.rs new file mode 100644 index 00000000..14625565 --- /dev/null +++ b/src/registry/app.rs @@ -0,0 +1,140 @@ +use anyhow::{anyhow, bail}; +use js_sys::Reflect::{get, has, set}; +use wasm_bindgen::{convert::TryFromJsValue, prelude::wasm_bindgen, JsValue}; +use wasmer_api::types::{DeployAppVersion, PublishDeployAppVars}; +use wasmer_config::{app::AppConfigV1, package::PackageBuilder}; + +use crate::{ + utils::{self, Error}, + wasmer::UserPackageDefinition, + Wasmer, +}; + +#[wasm_bindgen(getter_with_clone)] +#[derive(Debug, Clone)] +pub struct DeployedApp { + pub id: String, + pub created_at: String, + pub version: String, + pub description: Option, + pub yaml_config: String, + pub user_yaml_config: String, + pub config: String, + pub json_config: String, + pub url: String, +} + +impl From for DeployedApp { + fn from(value: DeployAppVersion) -> Self { + Self { + id: value.id.inner().to_string(), + created_at: value.created_at.0, + version: value.version, + description: value.description, + yaml_config: value.yaml_config, + user_yaml_config: value.user_yaml_config, + config: value.config, + json_config: value.json_config, + url: value.url, + } + } +} + +async fn resolve_pkg(app_config: &JsValue) -> anyhow::Result<()> { + // The app config must have a 'package' field. + // + // The package field can either be a raw string or a [`Wasmer`]. + + let package_key = JsValue::from_str("package"); + let package = get(app_config, &package_key) + .map_err(|e| anyhow!("While trying to get the package field from the app config: {e:?}"))?; + + if package.is_undefined() { + bail!("While trying to get the package field from the app config: undefined field name") + } + + let pkg_key = JsValue::from_str("pkg"); + + if package.is_string() { + // Do nothing + Ok(()) + } else if has(&package, &pkg_key).map_err(|e| anyhow!("{e:?}"))? { + let pkg = get(&package, &pkg_key).unwrap(); + let mut u = UserPackageDefinition::try_from_js_value(pkg).map_err(|e| { + anyhow!("Error while casting 'pkg' field back to inner rust type: {e:?}") + })?; + + // Set the app to use the package hash to deploy the app. + set(app_config, &package_key, &JsValue::from(u.hash.clone())) + .map_err(|e| anyhow!("{e:?}"))?; + + if u.manifest.package.is_none() || u.manifest.package.and_then(|v| v.name).is_none() { + // The package was left unnamed - we need to publish it! + let owner_key = JsValue::from_str("owner"); + if has(app_config, &owner_key).map_err(|e| anyhow!("{e:?}"))? { + let owner = get(app_config, &owner_key).map_err(|e| anyhow!("{e:?}"))?; + if !owner.is_string() { + anyhow::bail!("'owner' in the provided app config is not a string!") + } + + let owner = owner.as_string().unwrap(); + u.manifest.package = Some( + PackageBuilder::default() + .name(format!("{owner}/")) + .build()?, + ); + + Wasmer::publish_package_inner(&u.hash, u.manifest.clone(), u.data.clone()) + .await + .map_err(|e| anyhow!("{e:?}"))?; + } else { + anyhow::bail!("app config has no owner specified! Specify one with 'owner'") + } + } + + Ok(()) + } else { + bail!("no package information provided! Set the 'package' field!") + } +} + +#[wasm_bindgen] +impl Wasmer { + /// Deploy an app to the registry. + #[wasm_bindgen(js_name = "deployApp")] + #[allow(non_snake_case)] + pub async fn deploy_app(appConfig: JsValue) -> Result { + resolve_pkg(&appConfig).await?; + + let default = get(&appConfig, &(String::from("default").into())) + .map_err(utils::js_error)? + .as_bool(); + + let app_config = serde_wasm_bindgen::from_value(appConfig) + .map_err(|e| anyhow!("while deserializing the app config: {e:?}"))?; + Wasmer::deploy_app_inner(app_config, default).await + } +} + +impl Wasmer { + async fn deploy_app_inner( + app_config: AppConfigV1, + make_default: Option, + ) -> Result { + let client = Wasmer::get_client()?; + let config = app_config.clone().to_yaml()?; + + wasmer_api::query::publish_deploy_app( + client, + PublishDeployAppVars { + config, + name: app_config.name.into(), + owner: app_config.owner.map(Into::into), + make_default, + }, + ) + .await + .map(|v| v.into()) + .map_err(|e| utils::Error::Rust(anyhow!("while deploying the app: {e:?}"))) + } +} diff --git a/src/registry/mod.rs b/src/registry/mod.rs new file mode 100644 index 00000000..70a31311 --- /dev/null +++ b/src/registry/mod.rs @@ -0,0 +1,87 @@ +pub mod app; +pub mod package; + +use anyhow::anyhow; +use js_sys::Reflect::{get, has}; +use wasm_bindgen::{convert::TryFromJsValue, JsValue}; +use wasmer_api::WasmerClient; + +use crate::{utils::Error, Wasmer}; + +static WASMER_CLIENT: std::sync::OnceLock = std::sync::OnceLock::new(); + +#[derive(Debug, Default, Clone)] +pub struct RegistryConfig { + pub registry_url: Option, + pub token: Option, +} + +impl TryFromJsValue for RegistryConfig { + type Error = JsValue; + + fn try_from_js_value(value: wasm_bindgen::prelude::JsValue) -> Result { + let token_key = JsValue::from_str("token"); + let registry_url_key = JsValue::from_str("registryUrl"); + let token = if has(&value, &token_key)? { + let token = get(&value, &token_key)?; + if let Some(token) = token.as_string() { + Some(token) + } else { + return Err(JsValue::from_str( + "Cannot create token from non-string object!", + )); + } + } else { + None + }; + + let registry_url = if has(&value, ®istry_url_key)? { + let registry_url = get(&value, ®istry_url_key)?; + if let Some(registry_url) = registry_url.as_string() { + Some(registry_url) + } else if registry_url.is_null() || registry_url.is_undefined() { + Some(crate::DEFAULT_REGISTRY.into()) + } else { + return Err(JsValue::from_str( + "Cannot create registry url from non-string object!", + )); + } + } else { + None + }; + + Ok(Self { + registry_url, + token, + }) + } +} + +impl Wasmer { + pub fn get_client() -> Result<&'static WasmerClient, Error> { + WASMER_CLIENT.get_or_try_init(|| { + let registry_input = if let Some(registry_info) = + web_sys::window().and_then(|w| w.get("__WASMER_REGISTRY__")) + { + RegistryConfig::try_from_js_value(registry_info.into()) + .map_err(|e| anyhow!("while reading registry configuration: {e:?}"))? + } else { + RegistryConfig::default() + }; + + let mut client = wasmer_api::WasmerClient::new( + url::Url::parse( + ®istry_input + .registry_url + .unwrap_or(crate::DEFAULT_REGISTRY.into()), + )?, + "Wasmer JS SDK", + )?; + if let Some(token) = registry_input.token { + client = client.with_auth_token(token); + } + + Ok(client) + }) + } +} diff --git a/src/registry/package/mod.rs b/src/registry/package/mod.rs new file mode 100644 index 00000000..05d4bb44 --- /dev/null +++ b/src/registry/package/mod.rs @@ -0,0 +1,153 @@ +mod package_utils; + +use crate::{ + utils::{self, Error}, + wasmer::OptionalRuntime, + Wasmer, +}; +use js_sys::Math::random; +use package_utils::*; +use std::path::PathBuf; +use wasm_bindgen::prelude::wasm_bindgen; +use wasmer_config::package::Manifest; +use webc::wasmer_package::Package; +use webc::wasmer_package::Strictness; + +#[wasm_bindgen(getter_with_clone)] +#[derive(Debug, Clone)] +pub struct WasmerPackage { + pub manifest: js_sys::Object, + pub data: Vec, +} + +#[wasm_bindgen(getter_with_clone)] +#[derive(Debug, Clone)] +pub struct Volume {} + +#[wasm_bindgen(getter_with_clone)] +#[derive(Debug, Clone)] +pub struct Atom {} + +#[wasm_bindgen(getter_with_clone)] +#[derive(Debug, Clone)] +pub struct PublishPackageOutput { + pub manifest: wasm_bindgen::JsValue, + pub hash: String, +} + +#[wasm_bindgen] +impl Wasmer { + /// Create a `WasmerPackage`. + #[wasm_bindgen(js_name = "createPackage")] + #[allow(non_snake_case)] + pub async fn createPackage(manifest: js_sys::Object) -> Result { + let base_dir = PathBuf::from("/"); + let volumes = package_utils::create_volumes(&manifest, &base_dir)?; + + let metadata = package_utils::create_metadata(&manifest, &base_dir)?; + + let atoms = package_utils::create_atoms(&manifest)?; + + let wasmer_manifest: Manifest = serde_wasm_bindgen::from_value(manifest.clone().into()) + .map_err(|e| anyhow::anyhow!("While parsing the manifest: {e}"))?; + wasmer_manifest.validate()?; + + let pkg: Package = webc::wasmer_package::Package::from_in_memory( + wasmer_manifest.clone(), + volumes, + atoms, + metadata, + Strictness::default(), + ) + .map_err(|e| anyhow::anyhow!("{e:?}"))?; + + let runtime = OptionalRuntime::default().resolve()?.into_inner(); + Wasmer::from_user_package(pkg, wasmer_manifest, runtime).await + } + + /// Publish a package to the registry. + #[wasm_bindgen(js_name = "publishPackage")] + #[allow(non_snake_case)] + pub async fn publishPackage(wasmerPackage: &Wasmer) -> Result { + match &wasmerPackage.pkg { + Some(p) => { + Wasmer::publish_package_inner(&p.hash, p.manifest.clone(), p.data.clone()).await + } + None => Err(Error::Rust(anyhow::anyhow!( + "The selected package has no container!" + ))), + } + } +} + +impl Wasmer { + pub(super) async fn publish_package_inner( + hash: &str, + manifest: Manifest, + bytes: bytes::Bytes, + ) -> Result { + let client = Wasmer::get_client()?; + + if wasmer_api::query::get_package_release(client, hash) + .await? + .is_some() + { + // The package was already published. + return Ok(PublishPackageOutput { + manifest: serde_wasm_bindgen::to_value(&manifest) + .map_err(|e| anyhow::anyhow!("{e:?}"))?, + hash: hash.to_string(), + }); + } + + let signed_url = wasmer_api::query::get_signed_url_for_package_upload( + client, + Some(60 * 30), + Some(format!("js-{}", random()).replace('.', "-")).as_deref(), + None, + None, + ) + .await? + .ok_or_else(|| anyhow::anyhow!("No signed url!"))? + .url; + + upload(bytes, &signed_url).await?; + + let (namespace, name) = + if let Some(full_name) = manifest.package.as_ref().and_then(|p| p.name.clone()) { + let splits: Vec = full_name.split('/').map(|s| s.to_string()).collect(); + ( + splits + .first() + .ok_or_else(|| anyhow::anyhow!("No namespace provided!"))? + .clone(), + splits.get(1).cloned(), + ) + } else { + return Err(utils::Error::Rust(anyhow::anyhow!( + "No namespace provided!" + ))); + }; + + let out = wasmer_api::query::push_package_release( + client, + name.as_deref(), + &namespace, + &signed_url, + manifest.package.as_ref().map(|p| p.private), + ) + .await + .map_err(|e| anyhow::anyhow!("{e:?}"))? + .ok_or_else(|| anyhow::anyhow!("Backend returned no data!"))?; + + Ok(PublishPackageOutput { + manifest: serde_wasm_bindgen::to_value(&manifest) + .map_err(|e| anyhow::anyhow!("{e:?}"))?, + hash: out + .package_webc + .and_then(|p| p.webc_v3) + .map(|c| c.webc_sha256) + .ok_or_else(|| anyhow::anyhow!("No package was published!"))?, + }) + } +} diff --git a/src/registry/package/package_utils/mem_volumes.rs b/src/registry/package/package_utils/mem_volumes.rs new file mode 100644 index 00000000..9558357f --- /dev/null +++ b/src/registry/package/package_utils/mem_volumes.rs @@ -0,0 +1,273 @@ +use instant::Duration; +use js_sys::{ + JsString, Object, + Reflect::{delete_property, get, set}, +}; +use std::{collections::BTreeMap, path::Path, time::SystemTime}; +use wasm_bindgen::{JsCast, JsValue}; +use webc::wasmer_package::{MemoryDir, MemoryFile, MemoryNode, MemoryVolume}; + +use crate::utils; + +fn to_systime(modified: &JsValue) -> anyhow::Result { + let millis = if let Some(date) = modified.dyn_ref::() { + date.get_time() + } else if let Some(millis) = modified.as_f64() { + millis + } else { + anyhow::bail!("'modified' is neither a 'Date' or a number!") + }; + + SystemTime::UNIX_EPOCH + .checked_add(Duration::from_secs_f64(millis)) + .ok_or_else(|| anyhow::anyhow!("Error creating the timestamp")) +} + +fn create_node(value: JsValue) -> anyhow::Result { + // We don't know if the value represents a node or a dir, and the two can share a common + // pre-structure ({data: , meta: -}) + + let data_key = JsString::from("data"); + let modified_key = JsString::from("modified"); + + let (data, modified) = + if let (Ok(data), Ok(modified)) = (get(&value, &data_key), get(&value, &modified_key)) { + if !data.is_undefined() && !modified.is_undefined() { + (data, to_systime(&modified)?) + } else { + (value, SystemTime::UNIX_EPOCH) + } + } else { + (value, SystemTime::UNIX_EPOCH) + }; + + Ok( + if data.is_string() || data.is_instance_of::() { + MemoryNode::File(create_file(data, modified)?) + } else { + MemoryNode::Dir(create_dir(data, modified)?) + }, + ) +} + +fn create_file(data: JsValue, modified: SystemTime) -> anyhow::Result { + let data = if data.is_string() { + data.as_string().unwrap().as_bytes().to_vec() + } else if data.is_instance_of::() { + js_sys::Uint8Array::from(data.clone()).to_vec() + } else { + anyhow::bail!("The embedded file is not a string or a 'Uint8Array") + }; + + Ok(MemoryFile { modified, data }) +} + +fn create_dir(data: JsValue, modified: SystemTime) -> anyhow::Result { + // The assumption is that 'data' is in the form + // := { + // 'ident1' : '', + // 'ident2' : '', + // ... + // } + + let mut nodes = BTreeMap::new(); + + let entries = utils::object_entries(&data.dyn_into::().unwrap()) + .map_err(|e| anyhow::anyhow!("{e}"))? + .into_iter(); + + for (key, value) in entries { + nodes.insert(key.as_string().unwrap(), create_node(value)?); + } + + Ok(MemoryDir { modified, nodes }) +} + +fn create_volume(value: JsValue) -> anyhow::Result { + // Each volume is specified on the user-side as an embedded directory. + // + // Users can specify directories in two forms: + // + // 1. Explicitly setting metadata + // { + // data: '', + // modified: '' + // } + // + // 2. Using default metadata + // '' + // + // where is an object mapping node names to node contents: + // + // := { + // 'ident1' : '', + // 'ident2' : '', + // ... + // } + + let data_key = JsString::from("data"); + let modified_key = JsString::from("modified"); + + if let (Ok(data), Ok(modified)) = (get(&value, &data_key), get(&value, &modified_key)) { + if !data.is_undefined() && !modified.is_undefined() { + return Ok(MemoryVolume { + node: create_dir(data, to_systime(&modified)?)?, + }); + } + } + // Assume it's directly an ''. + Ok(MemoryVolume { + node: create_dir(value, SystemTime::UNIX_EPOCH)?, + }) +} + +pub fn create_volumes( + manifest: &js_sys::Object, + base_dir: &Path, +) -> anyhow::Result> { + let fs_key = JsString::from("fs"); + + if let Ok(fs) = get(manifest, &fs_key) { + if !fs.is_undefined() && fs.is_object() { + // Remove the original 'fs' property on the manifest object. + delete_property(manifest, &fs_key) + .map_err(|e| anyhow::anyhow!("while deleting fs property: {e:?}"))?; + + // The 'fs' property's value must be a map in the form + // + // { + // '' : '' + // } + + let fs = fs + .dyn_into::() + .map_err(|e| anyhow::anyhow!("{e:?}"))?; + + let mut volumes = BTreeMap::new(); + let new_fs = js_sys::Object::new(); + + let entries = utils::object_entries(&fs) + .map_err(|e| anyhow::anyhow!("{e}"))? + .into_iter(); + + for (key, value) in entries { + let key_str = key.as_string().unwrap(); + volumes.insert( + base_dir.join(key_str).display().to_string(), + create_volume(value)?, + ); + set(&new_fs, &key, &key) + .map_err(|e| anyhow::anyhow!("while setting fs property: {e:?}"))?; + } + + set(manifest, &fs_key, &new_fs) + .map_err(|e| anyhow::anyhow!("while deleting fs property: {e:?}"))?; + + return Ok(volumes); + } + } + + Ok(BTreeMap::new()) +} + +pub fn create_atoms( + manifest: &js_sys::Object, +) -> anyhow::Result, webc::compat::SharedBytes)>> { + // [todo]: + // [module] + // name = .sdasd + // source = [] + // + // -> + // + // [module] + // name = .sdasda + // source = .sdasda + let module_key = JsString::from("module"); + let mut atoms_data = BTreeMap::new(); + + if let Ok(modules) = get(manifest, &module_key) { + if !modules.is_undefined() { + if !modules.is_array() { + anyhow::bail!("'module' must be an array!") + } else { + let modules = js_sys::Array::from(&modules); + let name_key = JsString::from("name"); + let source_key = JsString::from("source"); + for o in modules.to_vec().into_iter() { + let (name, source) = ( + get(&o, &name_key).map_err(|e| anyhow::anyhow!("{e:?}"))?, + get(&o, &source_key).map_err(|e| anyhow::anyhow!("{e:?}"))?, + ); + + if name.is_undefined() + || name.is_null() + || source.is_undefined() + || source.is_null() + { + anyhow::bail!("'name', or 'source' undefined") + } + + if !name.is_string() { + anyhow::bail!("'name' must be a string") + } + + let name = name.as_string().unwrap(); + + let MemoryFile { data, .. } = match create_node(source)? { + MemoryNode::File(f) => f, + MemoryNode::Dir(_) => anyhow::bail!("The atom must be a file!"), + }; + + atoms_data.insert( + name.clone(), + (None, webc::compat::SharedBytes::from_bytes(data)), + ); + set(&o, &source_key, &JsValue::from_str(&name)) + .map_err(|e| anyhow::anyhow!("While setting object values: {e:?}"))?; + } + } + } + } + + Ok(atoms_data) +} + +pub fn create_metadata(manifest: &js_sys::Object, base_dir: &Path) -> anyhow::Result { + let pkg_key = JsString::from("package"); + let readme_key = JsString::from("readme"); + let license_key = JsString::from("license"); + + let guest_readme_key = JsString::from(base_dir.join("README.md").display().to_string()); + let guest_license_key = JsString::from(base_dir.join("License").display().to_string()); + + let meta_obj = js_sys::Object::new(); + + let dir = if let Ok(pkg) = get(manifest, &pkg_key) { + if let Ok(readme_file) = get(&pkg, &readme_key) { + if !readme_file.is_undefined() { + set(&meta_obj, &guest_readme_key, &readme_file) + .map_err(|e| anyhow::anyhow!("{e:?}"))?; + set(&pkg, &readme_key, &guest_readme_key) + .map_err(|e| anyhow::anyhow!("While setting object values: {e:?}"))?; + } + } + if let Ok(license_file) = get(&pkg, &license_key) { + if !license_file.is_undefined() { + set(&meta_obj, &guest_license_key, &license_file) + .map_err(|e| anyhow::anyhow!("{e:?}"))?; + set(&pkg, &license_key, &guest_license_key) + .map_err(|e| anyhow::anyhow!("While setting object values: {e:?}"))?; + } + } + + create_dir(meta_obj.into(), SystemTime::UNIX_EPOCH)? + } else { + MemoryDir { + modified: SystemTime::UNIX_EPOCH, + nodes: BTreeMap::default(), + } + }; + + Ok(MemoryVolume { node: dir }) +} diff --git a/src/registry/package/package_utils/mod.rs b/src/registry/package/package_utils/mod.rs new file mode 100644 index 00000000..831b8540 --- /dev/null +++ b/src/registry/package/package_utils/mod.rs @@ -0,0 +1,5 @@ +mod mem_volumes; +mod upload; + +pub use mem_volumes::*; +pub use upload::*; diff --git a/src/registry/package/package_utils/upload.rs b/src/registry/package/package_utils/upload.rs new file mode 100644 index 00000000..8a96a18b --- /dev/null +++ b/src/registry/package/package_utils/upload.rs @@ -0,0 +1,99 @@ +use bytes::Bytes; +use std::collections::BTreeMap; +use tokio::io::AsyncBufReadExt as _; + +// Upload a package to a signed url. +pub async fn upload(bytes: Bytes, signed_url: &str) -> anyhow::Result { + let client = reqwest::Client::builder() + .default_headers(reqwest::header::HeaderMap::default()) + .build() + .unwrap(); + + let res = client + .request(reqwest::Method::POST, signed_url) + .header(reqwest::header::CONTENT_LENGTH, "0") + .header(reqwest::header::CONTENT_TYPE, "application/octet-stream") + .header("x-goog-resumable", "start") + .header(reqwest::header::ACCEPT, "*/*") + .header(reqwest::header::ACCESS_CONTROL_ALLOW_HEADERS, "*") + .header(reqwest::header::ACCESS_CONTROL_ALLOW_ORIGIN, "*") + .header(reqwest::header::ACCESS_CONTROL_ALLOW_METHODS, "*") + .header(reqwest::header::ACCESS_CONTROL_MAX_AGE, "43200"); + + let result = res.send().await?; + + if result.status() != reqwest::StatusCode::from_u16(201).unwrap() { + return Err(anyhow::anyhow!( + "Uploading package failed: got HTTP {:?} when uploading", + result.status() + )); + } + + let headers = result + .headers() + .into_iter() + .filter_map(|(k, v)| { + let k = k.to_string(); + let v = v.to_str().ok()?.to_string(); + Some((k.to_lowercase(), v)) + }) + .collect::>(); + + let session_uri = headers + .get("location") + .ok_or_else(|| { + anyhow::anyhow!("The upload server did not provide the upload URL correctly") + })? + .clone(); + + //* XXX: If the package is large this line may result in + // * a surge in memory use. + // * + // * In the future, we might want a way to stream bytes + // * from the webc instead of a complete in-memory + // * representation. + // */ + let total_bytes = bytes.len(); + + let chunk_size = 2_097_152; // 2MB + + let mut reader = tokio::io::BufReader::with_capacity(chunk_size, &bytes[..]); + let mut cursor = 0; + + while let Some(chunk) = reader.fill_buf().await.ok().map(|s| s.to_vec()) { + let n = chunk.len(); + + if chunk.is_empty() { + break; + } + + let start = cursor; + let end = cursor + chunk.len().saturating_sub(1); + let content_range = format!("bytes {start}-{end}/{total_bytes}"); + + let res = client + .put(&session_uri) + .header(reqwest::header::CONTENT_TYPE, "application/octet-stream") + .header(reqwest::header::CONTENT_LENGTH, format!("{}", chunk.len())) + .header("Content-Range".to_string(), content_range) + .body(chunk.to_vec()); + + let res = res.send().await; + res.map(|response| response.error_for_status()) + .map_err(|e| { + anyhow::anyhow!( + "cannot send request to {session_uri} (chunk {}..{}): {e}", + cursor, + cursor + chunk_size + ) + })??; + + if n < chunk_size { + break; + } + + reader.consume(n); + cursor += n; + } + Ok(signed_url.to_string()) +} diff --git a/src/runtime.rs b/src/runtime.rs index 22b75a18..4d1400ba 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -3,13 +3,14 @@ use std::sync::{atomic::AtomicBool, Arc, Mutex, Weak}; use http::HeaderValue; use once_cell::sync::Lazy; use virtual_net::VirtualNetworking; +use wasmer_config::package::PackageSource; use wasmer_wasix::{ http::{HttpClient, WebHttpClient}, os::{TtyBridge, TtyOptions}, runtime::{ module_cache::ThreadLocalCache, package_loader::PackageLoader, - resolver::{PackageSpecifier, PackageSummary, QueryError, Source, WapmSource}, + resolver::{PackageSummary, QueryError, Source, WapmSource}, }, VirtualTaskManager, WasiTtyState, }; @@ -167,19 +168,11 @@ impl wasmer_wasix::runtime::Runtime for Runtime { self.module_cache.clone() } - fn load_module_sync(&self, wasm: &[u8]) -> Result { + fn load_module_sync(&self, wasm: &[u8]) -> Result { let wasm = unsafe { js_sys::Uint8Array::view(wasm) }; - let module = js_sys::WebAssembly::Module::new(&wasm).map_err(crate::utils::js_error)?; - // Note: We need to use this From impl because it will use the - // wasm-types-polyfill to parse the *.wasm file's import section. - // - // The browser doesn't give you any way to inspect the imports at the - // moment, so without the polyfill we'll always assume the module wants - // a minimum of 1 page of memory. This causes modules that want more - // memory by default (e.g. sharrattj/bash) to fail with an instantiation - // error. - // - // https://github.com/wasmerio/wasmer/blob/8ec4f1d76062e2a612ac2f70f4a73eaf59f8fe9f/lib/api/src/js/module.rs#L323-L328 + let module = js_sys::WebAssembly::Module::new(&wasm) + .map_err(|x| wasmer_wasix::SpawnError::Other(crate::utils::js_error(x).into()))?; + Ok(wasmer::Module::from((module, wasm.to_vec()))) } @@ -236,7 +229,7 @@ struct UnsupportedSource; #[async_trait::async_trait] impl Source for UnsupportedSource { - async fn query(&self, _package: &PackageSpecifier) -> Result, QueryError> { + async fn query(&self, _package: &PackageSource) -> Result, QueryError> { Err(QueryError::Unsupported) } } diff --git a/src/tasks/post_message_payload.rs b/src/tasks/post_message_payload.rs index b9127ee3..a18c4e70 100644 --- a/src/tasks/post_message_payload.rs +++ b/src/tasks/post_message_payload.rs @@ -1,7 +1,7 @@ use derivative::Derivative; use js_sys::WebAssembly; use wasm_bindgen::JsValue; -use wasmer_wasix::runtime::module_cache::ModuleHash; +use wasmer_types::ModuleHash; use crate::tasks::{ interop::Serializer, task_wasm::SpawnWasm, AsyncTask, BlockingModuleTask, BlockingTask, @@ -126,7 +126,11 @@ impl PostMessagePayload { consts::TYPE_CACHE_MODULE => { let module = de.js(consts::MODULE)?; let hash = de.string(consts::MODULE_HASH)?; - let hash = ModuleHash::parse_hex(&hash)?; + let hash = if let Ok(hash) = ModuleHash::sha256_parse_hex(&hash) { + hash + } else { + ModuleHash::xxhash_parse_hex(&hash)? + }; Ok(PostMessagePayload::Notification( Notification::CacheModule { hash, module }, @@ -170,10 +174,7 @@ mod tests { use wasm_bindgen::JsCast; use wasm_bindgen_test::wasm_bindgen_test; use wasmer::AsJs; - use wasmer_wasix::{ - runtime::{module_cache::ModuleHash, task_manager::TaskWasm}, - WasiEnvBuilder, - }; + use wasmer_wasix::{runtime::task_manager::TaskWasm, WasiEnvBuilder}; use crate::{ runtime::Runtime, @@ -276,7 +277,7 @@ mod tests { let engine = wasmer::Engine::default(); let module = wasmer::Module::new(&engine, wasm).unwrap(); let msg = PostMessagePayload::Notification(Notification::CacheModule { - hash: ModuleHash::hash(wasm), + hash: ModuleHash::xxhash(wasm), module: module.into(), }); @@ -285,7 +286,7 @@ mod tests { match round_tripped { PostMessagePayload::Notification(Notification::CacheModule { hash, module: _ }) => { - assert_eq!(hash, ModuleHash::hash(wasm)); + assert_eq!(hash, ModuleHash::xxhash(wasm)); } _ => unreachable!(), }; diff --git a/src/tasks/scheduler.rs b/src/tasks/scheduler.rs index 63924368..59718f11 100644 --- a/src/tasks/scheduler.rs +++ b/src/tasks/scheduler.rs @@ -10,7 +10,7 @@ use tokio::sync::mpsc::{self}; use tracing::Instrument; use wasm_bindgen::{JsCast, JsValue}; use wasmer::AsJs; -use wasmer_wasix::runtime::module_cache::ModuleHash; +use wasmer_types::ModuleHash; use crate::tasks::{ AsyncJob, BlockingJob, Notification, PostMessagePayload, SchedulerMessage, WorkerHandle, diff --git a/src/tasks/scheduler_message.rs b/src/tasks/scheduler_message.rs index ad96b949..6ccf1bd3 100644 --- a/src/tasks/scheduler_message.rs +++ b/src/tasks/scheduler_message.rs @@ -4,7 +4,7 @@ use derivative::Derivative; use js_sys::WebAssembly; use wasm_bindgen::JsValue; use wasmer::AsJs; -use wasmer_wasix::runtime::module_cache::ModuleHash; +use wasmer_types::ModuleHash; use crate::{ tasks::{ @@ -82,7 +82,11 @@ impl SchedulerMessage { } consts::TYPE_CACHE_MODULE => { let hash = de.string(consts::MODULE_HASH)?; - let hash = ModuleHash::parse_hex(&hash)?; + let hash = if let Ok(hash) = ModuleHash::sha256_parse_hex(&hash) { + hash + } else { + ModuleHash::xxhash_parse_hex(&hash)? + }; let module: WebAssembly::Module = de.js(consts::MODULE)?; Ok(SchedulerMessage::CacheModule { hash, diff --git a/src/tasks/task_wasm.rs b/src/tasks/task_wasm.rs index 344f50ee..5dfd050c 100644 --- a/src/tasks/task_wasm.rs +++ b/src/tasks/task_wasm.rs @@ -16,7 +16,7 @@ use wasmer_wasix::{ SpawnMemoryType, }, wasmer_wasix_types::wasi::ExitCode, - InstanceSnapshot, WasiEnv, WasiFunctionEnv, WasiThreadError, + StoreSnapshot, WasiEnv, WasiFunctionEnv, WasiThreadError, }; use crate::tasks::SchedulerMessage; @@ -28,15 +28,14 @@ pub(crate) fn to_scheduler_message( run, env, module, - snapshot, spawn_type, trigger, update_layout, recycle, + globals, } = task; let module_bytes = module.serialize().unwrap(); - let snapshot = snapshot.map(InstanceSnapshot::clone); let (memory_ty, memory, run_type) = match spawn_type { wasmer_wasix::runtime::SpawnMemoryType::CreateMemory => { @@ -84,6 +83,7 @@ pub(crate) fn to_scheduler_message( } }); + let store_snapshot = globals.cloned(); let spawn_wasm = SpawnWasm { trigger: trigger.map(|trigger| WasmRunTrigger { run: trigger, @@ -94,10 +94,10 @@ pub(crate) fn to_scheduler_message( run_type, env, module_bytes, - snapshot, update_layout, result: None, recycle, + store_snapshot, }; Ok(SchedulerMessage::SpawnWithModuleAndMemory { @@ -190,8 +190,8 @@ pub(crate) struct SpawnWasm { /// The raw bytes for the WebAssembly module being run. #[derivative(Debug(format_with = "crate::utils::hidden"))] module_bytes: Bytes, - /// A snapshot of the instance, if we are forking an existing instance. - snapshot: Option, + /// A snapshot of the instance store, used to fork from existing instances. + store_snapshot: Option, /// An asynchronous callback which is used to run asyncify methods. The /// returned value is used in [`wasmer_wasix::rewind()`] or instant /// responses. @@ -242,11 +242,11 @@ impl ReadySpawnWasm { run_type, env, module_bytes, - snapshot, update_layout, result, trigger: _, recycle, + store_snapshot, }) = self; // Invoke the callback which will run the web assembly module @@ -255,8 +255,8 @@ impl ReadySpawnWasm { wasm_memory, module_bytes, env, + store_snapshot, run_type, - snapshot, update_layout, ) .context("Unable to initialize the context and store")?; @@ -278,8 +278,8 @@ fn build_ctx_and_store( memory: JsValue, module_bytes: Bytes, env: WasiEnv, + store_snapshot: Option, run_type: WasmMemoryType, - snapshot: Option, update_layout: bool, ) -> Option<(WasiFunctionEnv, Store)> { // Compile the web assembly module @@ -302,17 +302,21 @@ fn build_ctx_and_store( } }; - let snapshot = snapshot.as_ref(); - let (ctx, store) = - match WasiFunctionEnv::new_with_store(module, env, snapshot, spawn_type, update_layout) { - Ok(a) => a, - Err(err) => { - tracing::error!( - error = &err as &dyn std::error::Error, - "Failed to crate wasi context", - ); - return None; - } - }; + let (ctx, store) = match WasiFunctionEnv::new_with_store( + module, + env, + store_snapshot.as_ref(), + spawn_type, + update_layout, + ) { + Ok(a) => a, + Err(err) => { + tracing::error!( + error = &err as &dyn std::error::Error, + "Failed to crate wasi context", + ); + return None; + } + }; Some((ctx, store)) } diff --git a/src/tasks/worker.js b/src/tasks/worker.js index 18c5b979..7f1b7635 100644 --- a/src/tasks/worker.js +++ b/src/tasks/worker.js @@ -4,54 +4,56 @@ globalThis.onerror = console.error; let pendingMessages = []; let worker = undefined; let handleMessage = async data => { - if (worker) { - await worker.handle(data); - } else { - // We start off by buffering up all messages until we finish initializing. - pendingMessages.push(data); - } + if (worker) { + await worker.handle(data); + } else { + // We start off by buffering up all messages until we finish initializing. + pendingMessages.push(data); + } }; globalThis.onmessage = async ev => { - if (ev.data.type == "init") { - const { memory, module, id, import_url } = ev.data; - const imported = await import( - new URL(import_url, self.location.origin) - ); + if (ev.data.type == "init") { + const { memory, module, id, import_url } = ev.data; + const imported = await import(new URL(import_url, self.location.origin)); + + // HACK: How we load our imports will change depending on how the code + // is deployed. If we are being used in "wasm-pack test" then we can + // access the things we want from the imported object. Otherwise, if we + // are being used from a bundler, chances are those things are no longer + // directly accessible and we need to get them from the + // __WASMER_INTERNALS__ object stashed on the global scope when the + // package was imported. + let init; + let ThreadPoolWorker; + if ("ThreadPoolWorker" in imported) { + if ("default" in imported) { + init = imported.default; + } else if ("init" in imported) { + init = imported.init; + } + ThreadPoolWorker = imported.ThreadPoolWorker; - // HACK: How we load our imports will change depending on how the code - // is deployed. If we are being used in "wasm-pack test" then we can - // access the things we want from the imported object. Otherwise, if we - // are being used from a bundler, chances are those things are no longer - // directly accessible and we need to get them from the - // __WASMER_INTERNALS__ object stashed on the global scope when the - // package was imported. - let init; - let ThreadPoolWorker; - if ("ThreadPoolWorker" in imported) { - if ("default" in imported) { - init = imported.default; - } - else if ("init" in imported) { - init = imported.init; - } - ThreadPoolWorker = imported.ThreadPoolWorker; - } else { - init = globalThis["__WASMER_INTERNALS__"].init; - ThreadPoolWorker = - globalThis["__WASMER_INTERNALS__"].ThreadPoolWorker; - } + //await init(module, memory); + } else { + init = globalThis["__WASMER_INTERNALS__"].init; + ThreadPoolWorker = globalThis["__WASMER_INTERNALS__"].ThreadPoolWorker; + } - await init(module, memory); + if (globalThis["__WASMER_INIT__"]) { + await init({ module: module, memory: memory }); + } else { + await init(module, memory); + } - worker = new ThreadPoolWorker(id); + worker = new ThreadPoolWorker(id); - // Now that we're initialized, we need to handle any buffered messages - for (const msg of pendingMessages.splice(0, pendingMessages.length)) { - await worker.handle(msg); - } - } else { - // Handle the message like normal. - await handleMessage(ev.data); + // Now that we're initialized, we need to handle any buffered messages + for (const msg of pendingMessages.splice(0, pendingMessages.length)) { + await worker.handle(msg); } + } else { + // Handle the message like normal. + await handleMessage(ev.data); + } }; diff --git a/src/wasmer.rs b/src/wasmer.rs index be5adaf9..8f5bcfa8 100644 --- a/src/wasmer.rs +++ b/src/wasmer.rs @@ -1,19 +1,22 @@ -use std::sync::Arc; +use std::{str::FromStr, sync::Arc}; -use bytes::BytesMut; +use anyhow::Context; +use bytes::{Bytes, BytesMut}; use futures::{channel::oneshot, TryStreamExt}; use js_sys::{JsString, Reflect, Uint8Array}; +use sha2::Digest; use tracing::Instrument; use virtual_fs::{AsyncReadExt, Pipe}; use wasm_bindgen::{prelude::wasm_bindgen, JsValue, UnwrapThrowExt}; +use wasmer_config::package::PackageSource; use wasmer_wasix::{ bin_factory::BinaryPackage, os::{Tty, TtyOptions}, runners::{wasi::WasiRunner, Runner}, - runtime::resolver::PackageSpecifier, Runtime as _, }; use web_sys::{ReadableStream, WritableStream}; +use webc::wasmer_package::Package; use crate::{ instance::ExitCondition, @@ -38,7 +41,7 @@ use crate::{ /// throw new Error(`Python exited with ${code}: ${stderr}`); /// } /// ``` -#[derive(Debug, Clone)] +#[derive(Debug, Clone, wasm_bindgen_derive::TryFromJsValue)] #[wasm_bindgen] pub struct Wasmer { /// The package's entrypoint. @@ -48,6 +51,18 @@ pub struct Wasmer { /// dependencies). #[wasm_bindgen(getter_with_clone)] pub commands: Commands, + + #[wasm_bindgen(getter_with_clone)] + pub pkg: Option, +} + +#[derive(Debug, Clone, wasm_bindgen_derive::TryFromJsValue)] +#[wasm_bindgen] +pub struct UserPackageDefinition { + pub(crate) manifest: wasmer_config::package::Manifest, + pub(crate) data: bytes::Bytes, + #[wasm_bindgen(getter_with_clone)] + pub hash: String, } #[wasm_bindgen] @@ -78,7 +93,7 @@ impl Wasmer { specifier: &str, runtime: Option, ) -> Result { - let specifier = PackageSpecifier::parse(specifier)?; + let specifier = PackageSource::from_str(specifier)?; let runtime = runtime.unwrap_or_default().resolve()?.into_inner(); let pkg = BinaryPackage::from_registry(&specifier, &*runtime).await?; @@ -117,8 +132,34 @@ impl Wasmer { Ok(Wasmer { entrypoint, commands, + pkg: None, }) } + + pub(crate) async fn from_user_package( + pkg: Package, + manifest: wasmer_config::package::Manifest, + runtime: Arc, + ) -> Result { + let data: Bytes = pkg + .serialize() + .context("While validating the package")? + .to_vec() + .into(); + + let hash = sha2::Sha256::digest(&data).into(); + let hash = wasmer_config::package::PackageHash::from_sha256_bytes(hash); + let hash = hash.to_string(); + let container = webc::Container::from_bytes(data.clone())?; + let bin_pkg = BinaryPackage::from_webc(&container, &*runtime).await?; + let mut ret = Wasmer::from_package(bin_pkg, runtime)?; + ret.pkg = Some(UserPackageDefinition { + manifest, + data, + hash, + }); + Ok(ret) + } } /// A runnable WASIX command. @@ -183,7 +224,7 @@ extern "C" { } impl OptionalRuntime { - fn resolve(&self) -> Result { + pub(crate) fn resolve(&self) -> Result { let js_value: &JsValue = self.as_ref(); if js_value.is_undefined() { @@ -216,23 +257,23 @@ pub(crate) async fn configure_runner( Error, > { let args = options.parse_args()?; - runner.set_args(args); + runner.with_args(args); let env = options.parse_env()?; - runner.set_envs(env); + runner.with_envs(env); for (dest, dir) in options.mounted_directories()? { - runner.mount(dest, Arc::new(dir)); + runner.with_mount(dest, Arc::new(dir)); } if let Some(uses) = options.uses() { let uses = crate::utils::js_string_array(uses)?; let packages = load_injected_packages(uses, runtime).await?; - runner.add_injected_packages(packages); + runner.with_injected_packages(packages); } let (stderr_pipe, stderr_stream) = crate::streams::output_pipe(); - runner.set_stderr(Box::new(stderr_pipe)); + runner.with_stderr(Box::new(stderr_pipe)); let tty_options = runtime.tty_options().clone(); match setup_tty(options, tty_options) { @@ -243,16 +284,16 @@ pub(crate) async fn configure_runner( stdin_stream, } => { tracing::debug!("Setting up interactive TTY"); - runner.set_stdin(Box::new(stdin_pipe)); - runner.set_stdout(Box::new(stdout_pipe)); + runner.with_stdin(Box::new(stdin_pipe)); + runner.with_stdout(Box::new(stdout_pipe)); runtime.set_connected_to_tty(true); Ok((Some(stdin_stream), stdout_stream, stderr_stream)) } TerminalMode::NonInteractive { stdin } => { tracing::debug!("Setting up non-interactive TTY"); let (stdout_pipe, stdout_stream) = crate::streams::output_pipe(); - runner.set_stdin(Box::new(stdin)); - runner.set_stdout(Box::new(stdout_pipe)); + runner.with_stdin(Box::new(stdin)); + runner.with_stdout(Box::new(stdout_pipe)); // HACK: Make sure we don't report stdin as interactive. This // doesn't belong here because now it'll affect every other @@ -402,7 +443,7 @@ async fn load_injected_packages( #[tracing::instrument(level = "debug", skip(runtime))] async fn load_package(pkg: &str, runtime: &Runtime) -> Result { - let specifier: PackageSpecifier = pkg.parse()?; + let specifier: PackageSource = pkg.parse()?; let pkg = BinaryPackage::from_registry(&specifier, runtime).await?; Ok(pkg) diff --git a/tests/directory.test.ts b/tests/directory.test.ts index 9dfc3955..38b1a087 100644 --- a/tests/directory.test.ts +++ b/tests/directory.test.ts @@ -5,58 +5,60 @@ const decoder = new TextDecoder("utf-8"); const encoder = new TextEncoder(); const initialized = (async () => { - await init(new URL("../dist/wasmer_js_bg.wasm", import.meta.url)); - initializeLogger("warn"); + await init({ + module: new URL("../dist/wasmer_js_bg.wasm", import.meta.url), + }); + initializeLogger("warn"); })(); describe("In-Memory Directory", function () { - this.timeout("60s").beforeAll(async () => await initialized); + this.timeout("60s").beforeAll(async () => await initialized); - it("read empty dir", async () => { - const dir = new Directory(); + it("read empty dir", async () => { + const dir = new Directory(); - const contents = await dir.readDir("/"); + const contents = await dir.readDir("/"); - expect(contents.length).to.equal(0); - }); - - it("can round-trip a file", async () => { - const dir = new Directory(); + expect(contents.length).to.equal(0); + }); - await dir.writeFile("/file.txt", encoder.encode("Hello, World!")); - const contents = await dir.readFile("/file.txt"); + it("can round-trip a file", async () => { + const dir = new Directory(); - expect(decoder.decode(contents)).to.equal("Hello, World!"); - }); + await dir.writeFile("/file.txt", encoder.encode("Hello, World!")); + const contents = await dir.readFile("/file.txt"); - it("read dir with file", async () => { - const dir = new Directory(); + expect(decoder.decode(contents)).to.equal("Hello, World!"); + }); - await dir.writeFile("/file.txt", new Uint8Array()); - const contents = await dir.readDir("/"); + it("read dir with file", async () => { + const dir = new Directory(); - expect(contents).to.deep.equal([{ name: "file.txt", type: "file" }]); - }); + await dir.writeFile("/file.txt", new Uint8Array()); + const contents = await dir.readDir("/"); - it("create child dir", async () => { - const dir = new Directory(); + expect(contents).to.deep.equal([{ name: "file.txt", type: "file" }]); + }); - await dir.createDir("/tmp/"); + it("create child dir", async () => { + const dir = new Directory(); - expect(await dir.readDir("/")).to.deep.equal([ - { name: "tmp", type: "dir" }, - ]); - }); + await dir.createDir("/tmp/"); - it("can be created with DirectoryInit", async () => { - const dir = new Directory({ - "/file.txt": "file", - "/another/nested/file.txt": "another", - }); + expect(await dir.readDir("/")).to.deep.equal([ + { name: "tmp", type: "dir" }, + ]); + }); - expect(await dir.readTextFile("/file.txt")).to.equal("file"); - expect(await dir.readTextFile("/another/nested/file.txt")).to.equal( - "another", - ); + it("can be created with DirectoryInit", async () => { + const dir = new Directory({ + "/file.txt": "file", + "/another/nested/file.txt": "another", }); + + expect(await dir.readTextFile("/file.txt")).to.equal("file"); + expect(await dir.readTextFile("/another/nested/file.txt")).to.equal( + "another", + ); + }); }); diff --git a/tests/integration.test.ts b/tests/integration.test.ts index 2ab6de6b..6646e7a1 100644 --- a/tests/integration.test.ts +++ b/tests/integration.test.ts @@ -5,371 +5,371 @@ const encoder = new TextEncoder(); const decoder = new TextDecoder("utf-8"); const initialized = (async () => { - await init(new URL("../dist/wasmer_js_bg.wasm", import.meta.url)); - initializeLogger("warn"); + await init({ + module: new URL("../dist/wasmer_js_bg.wasm", import.meta.url), + }); + initializeLogger("warn"); })(); const ansiEscapeCode = /\u001B\[[\d;]*[JDm]/g; describe("Wasmer.spawn", function () { - this.timeout("120s").beforeAll(async () => { - await initialized; + this.timeout("120s").beforeAll(async () => { + await initialized; + }); + + it("Can run quickjs", async () => { + const pkg = await Wasmer.fromRegistry("saghul/quickjs@0.0.3"); + const instance = await pkg.commands["quickjs"].run({ + args: ["--eval", "console.log('Hello, World!')"], }); - - it("Can run quickjs", async () => { - const pkg = await Wasmer.fromRegistry("saghul/quickjs@0.0.3"); - const instance = await pkg.commands["quickjs"].run({ - args: ["--eval", "console.log('Hello, World!')"], - }); - const output = await instance.wait(); - - expect(output.code).to.equal(0); - expect(output.ok).to.be.true; - expect(output.stdout).to.equal("Hello, World!\n"); - expect(output.stderr.length).to.equal(0); - }); - - it("Can capture exit codes", async () => { - const pkg = await Wasmer.fromRegistry("saghul/quickjs"); - const instance = await pkg.commands["quickjs"].run({ - args: ["--std", "--eval", "std.exit(42)"], - }); - const output = await instance.wait(); - - expect(output.code).to.equal(42); - expect(output.ok).to.be.false; - expect(output.stdout.length).to.equal(0); - expect(output.stderr.length).to.equal(0); + const output = await instance.wait(); + + expect(output.code).to.equal(0); + expect(output.ok).to.be.true; + expect(output.stdout).to.equal("Hello, World!\n"); + expect(output.stderr.length).to.equal(0); + }); + + it("Can capture exit codes", async () => { + const pkg = await Wasmer.fromRegistry("saghul/quickjs"); + const instance = await pkg.commands["quickjs"].run({ + args: ["--std", "--eval", "std.exit(42)"], }); - - it("Can pass stdin to a dumb echo program", async () => { - const pkg = await Wasmer.fromRegistry( - "christoph/wasix-test-stdinout@0.1.1", - ); - const instance = await pkg.commands["stdinout-loop"].run({ - stdin: "Hello\nWorld!\n", - }); - - const output = await instance.wait(); - - expect(output.ok).to.be.true; - expect(output.code).to.equal(0); - expect(output.stderr).to.equal("Hello\n\nWorld!\n\n"); + const output = await instance.wait(); + + expect(output.code).to.equal(42); + expect(output.ok).to.be.false; + expect(output.stdout.length).to.equal(0); + expect(output.stderr.length).to.equal(0); + }); + + it("Can pass stdin to a dumb echo program", async () => { + const pkg = await Wasmer.fromRegistry( + "christoph/wasix-test-stdinout@0.1.1", + ); + const instance = await pkg.commands["stdinout-loop"].run({ + stdin: "Hello\nWorld!\n", }); - it("Can communicate with a dumb echo program", async () => { - // First, start our program in the background - const pkg = await Wasmer.fromRegistry( - "christoph/wasix-test-stdinout@0.1.1", - ); - const instance = await pkg.commands["stdinout-loop"].run(); - - const stdin = instance.stdin!.getWriter(); - const stdout = new BufReader(instance.stdout); - - await stdin.write(encoder.encode("Hello,")); - await stdin.write(encoder.encode(" World!\n")); - // Note: The program is reading line-by-line, so we can't do - // stdout.readLine() before the "\n" was sent - expect(await stdout.readLine()).to.equal("Hello, World!\n"); - await stdin.write(encoder.encode("Done\n")); - expect(await stdout.readLine()).to.equal("Done\n"); - - // Closing stdin will break out of the reading loop - await stdin.close(); - // And wait for the program to exit - const output = await instance.wait(); - - expect(output.ok).to.be.true; - expect(output.code).to.equal(0); + const output = await instance.wait(); + + expect(output.ok).to.be.true; + expect(output.code).to.equal(0); + expect(output.stderr).to.equal("Hello\n\nWorld!\n\n"); + }); + + it("Can communicate with a dumb echo program", async () => { + // First, start our program in the background + const pkg = await Wasmer.fromRegistry( + "christoph/wasix-test-stdinout@0.1.1", + ); + const instance = await pkg.commands["stdinout-loop"].run(); + + const stdin = instance.stdin!.getWriter(); + const stdout = new BufReader(instance.stdout); + + await stdin.write(encoder.encode("Hello,")); + await stdin.write(encoder.encode(" World!\n")); + // Note: The program is reading line-by-line, so we can't do + // stdout.readLine() before the "\n" was sent + expect(await stdout.readLine()).to.equal("Hello, World!\n"); + await stdin.write(encoder.encode("Done\n")); + expect(await stdout.readLine()).to.equal("Done\n"); + + // Closing stdin will break out of the reading loop + await stdin.close(); + // And wait for the program to exit + const output = await instance.wait(); + + expect(output.ok).to.be.true; + expect(output.code).to.equal(0); + }); + + it("Can communicate with a TTY-aware program", async () => { + // First, start QuickJS up in the background + const pkg = await Wasmer.fromRegistry("saghul/quickjs@0.0.3"); + const instance = await pkg.commands["quickjs"].run({ + args: ["--interactive", "--std"], }); - it("Can communicate with a TTY-aware program", async () => { - // First, start QuickJS up in the background - const pkg = await Wasmer.fromRegistry("saghul/quickjs@0.0.3"); - const instance = await pkg.commands["quickjs"].run({ - args: ["--interactive", "--std"], - }); - - const stdin = new RealisticWriter(instance.stdin!); - const stdout = new BufReader(instance.stdout); - - // QuickJS prints a prompt when it first starts up. Let's read it. - expect(await stdout.readLine()).to.equal( - 'QuickJS - Type "\\h" for help\n', - ); - - // Then, send a command to the REPL - await stdin.writeln("console.log('Hello, World!')"); - // The TTY echoes back a bunch of escape codes and stuff. - expect(await stdout.readAnsiLine()).to.equal( - "qjs > console.log('Hello, World!')\n", - ); - // Random newline. - expect(await stdout.readLine()).to.equal("\n"); - // QuickJS also echoes your input back. Because reasons. - expect(await stdout.readAnsiLine()).to.equal( - "console.log('Hello, World!')\n", - ); - // We get the text we asked for. - expect(await stdout.readLine()).to.equal("Hello, World!\n"); - // console.log() evaluates to undefined - expect(await stdout.readAnsiLine()).to.equal("undefined\n"); - - // Now that the first command is done, QuickJS will show the prompt - // again - expect(await stdout.readAnsiLine()).to.equal("qjs > \n"); - - // We're all done. Tell the command to exit. - await stdin.writeln("std.exit(42)"); - // Our input gets echoed by the TTY - expect(await stdout.readLine()).to.equal("qjs > std.exit(42)\n"); - // Random newline. - expect(await stdout.readLine()).to.equal("\n"); - // QuickJS printed the command we just ran. - expect(await stdout.readAnsiLine()).to.equal("std.exit(42)\n"); - - // Wait for the instance to shut down. - await stdin.close(); - const output = await instance.wait(); - - expect(output.code).to.equal(42); - expect(output.stderr).to.equal(""); + const stdin = new RealisticWriter(instance.stdin!); + const stdout = new BufReader(instance.stdout); + + // QuickJS prints a prompt when it first starts up. Let's read it. + expect(await stdout.readLine()).to.equal('QuickJS - Type "\\h" for help\n'); + + // Then, send a command to the REPL + await stdin.writeln("console.log('Hello, World!')"); + // The TTY echoes back a bunch of escape codes and stuff. + expect(await stdout.readAnsiLine()).to.equal( + "qjs > console.log('Hello, World!')\n", + ); + // Random newline. + expect(await stdout.readLine()).to.equal("\n"); + // QuickJS also echoes your input back. Because reasons. + expect(await stdout.readAnsiLine()).to.equal( + "console.log('Hello, World!')\n", + ); + // We get the text we asked for. + expect(await stdout.readLine()).to.equal("Hello, World!\n"); + // console.log() evaluates to undefined + expect(await stdout.readAnsiLine()).to.equal("undefined\n"); + + // Now that the first command is done, QuickJS will show the prompt + // again + expect(await stdout.readAnsiLine()).to.equal("qjs > \n"); + + // We're all done. Tell the command to exit. + await stdin.writeln("std.exit(42)"); + // Our input gets echoed by the TTY + expect(await stdout.readLine()).to.equal("qjs > std.exit(42)\n"); + // Random newline. + expect(await stdout.readLine()).to.equal("\n"); + // QuickJS printed the command we just ran. + expect(await stdout.readAnsiLine()).to.equal("std.exit(42)\n"); + + // Wait for the instance to shut down. + await stdin.close(); + const output = await instance.wait(); + + expect(output.code).to.equal(42); + expect(output.stderr).to.equal(""); + }); + + it("can communicate with a subprocess interactively", async () => { + const pkg = await Wasmer.fromRegistry("sharrattj/bash"); + const instance = await pkg.commands["bash"].run({ + uses: ["christoph/wasix-test-stdinout@0.1.1"], }); - it("can communicate with a subprocess interactively", async () => { - const pkg = await Wasmer.fromRegistry("sharrattj/bash"); - const instance = await pkg.commands["bash"].run({ - uses: ["christoph/wasix-test-stdinout@0.1.1"], - }); - - const stdin = new RealisticWriter(instance.stdin!); - const stdout = new BufReader(instance.stdout); - - // Start the stdinout-loop program - await stdin.writeln("stdinout-loop"); - // echo from the TTY - expect(await stdout.readLine()).to.equal("stdinout-loop\n"); - // The stdinout-loop program should be running now. Let's send it - // something - await stdin.writeln("First"); - // It printed back our input - expect(await stdout.readLine()).to.equal("\n"); - expect(await stdout.readLine()).to.equal("First\n"); - // Write the next line of input - await stdin.writeln("Second"); - // Echo from program - expect(await stdout.readLine()).to.equal("\n"); - expect(await stdout.readLine()).to.equal("Second\n"); - - await stdin.close(); - const output = await instance.wait(); - - expect(output.code).to.equal(0); - // It looks like bash does its own TTY echoing, except it printed to - // stderr instead of stdout like wasmer_wasix::os::Tty - expect(output.stderr).to.equal( - "bash-5.1# stdinout-loop\n\n\nFirst\n\n\n\nSecond\n\n\n\nbash-5.1# exit\n", - ); + const stdin = new RealisticWriter(instance.stdin!); + const stdout = new BufReader(instance.stdout); + + // Start the stdinout-loop program + await stdin.writeln("stdinout-loop"); + // echo from the TTY + expect(await stdout.readLine()).to.equal("stdinout-loop\n"); + // The stdinout-loop program should be running now. Let's send it + // something + await stdin.writeln("First"); + // It printed back our input + expect(await stdout.readLine()).to.equal("\n"); + expect(await stdout.readLine()).to.equal("First\n"); + // Write the next line of input + await stdin.writeln("Second"); + // Echo from program + expect(await stdout.readLine()).to.equal("\n"); + expect(await stdout.readLine()).to.equal("Second\n"); + + await stdin.close(); + const output = await instance.wait(); + + expect(output.code).to.equal(0); + // It looks like bash does its own TTY echoing, except it printed to + // stderr instead of stdout like wasmer_wasix::os::Tty + expect(output.stderr).to.equal( + "bash-5.1# stdinout-loop\n\n\nFirst\n\n\n\nSecond\n\n\n\nbash-5.1# exit\n", + ); + }); + + it("Can communicate with Python", async () => { + // First, start python up in the background + const pkg = await Wasmer.fromRegistry("python/python@0.1.0"); + const instance = await pkg.entrypoint!.run(); + + const stdin = new RealisticWriter(instance.stdin!); + const stdout = new BufReader(instance.stdout); + const stderr = new BufReader(instance.stderr); + + // First, we'll read the prompt + expect(await stderr.readLine()).to.equal( + "Python 3.6.7 (default, Feb 14 2020, 03:17:48) \n", + ); + expect(await stderr.readLine()).to.equal( + "[Wasm WASI vClang 9.0.0 (https://github.com/llvm/llvm-project 0399d5a9682b3cef7 on generic\n", + ); + expect(await stderr.readLine()).to.equal( + 'Type "help", "copyright", "credits" or "license" for more information.\n', + ); + + // Then, send the command to the REPL + await stdin.writeln("import sys"); + // TTY echo + expect(await stdout.readLine()).to.equal("import sys\n"); + await stdin.writeln("print(1 + 1)"); + // TTY echo + expect(await stdout.readLine()).to.equal("\n"); + expect(await stdout.readLine()).to.equal("print(1 + 1)\n"); + // Our output + expect(await stdout.readLine()).to.equal("\n"); + expect(await stdout.readLine()).to.equal("2\n"); + // We've done what we want, so let's shut it down + await stdin.writeln("sys.exit(42)"); + // TTY echo + expect(await stdout.readLine()).to.equal("sys.exit(42)\n"); + expect(await stdout.readLine()).to.equal("\n"); + + // Wait for the instance to shut down. + await stdin.close(); + await stdout.close(); + await stderr.close(); + const output = await instance.wait(); + + expect(output.ok).to.be.false; + expect(output.code).to.equal(42); + expect(output.stdout).to.equal(""); + // Python prints the prompts to stderr, but our TTY handling prints + // echoed characters to stdout + expect(output.stderr).to.equal(">>> >>> >>> >>> >>> "); + }); + + it("can see a mounted directory", async () => { + const dir = new Directory(); + const pkg = await Wasmer.fromRegistry("sharrattj/coreutils"); + + const instance = await pkg.commands["ls"].run({ + args: ["/"], + mount: { "/mounted": dir }, }); - - it("Can communicate with Python", async () => { - // First, start python up in the background - const pkg = await Wasmer.fromRegistry("python/python@0.1.0"); - const instance = await pkg.entrypoint!.run(); - - const stdin = new RealisticWriter(instance.stdin!); - const stdout = new BufReader(instance.stdout); - const stderr = new BufReader(instance.stderr); - - // First, we'll read the prompt - expect(await stderr.readLine()).to.equal( - "Python 3.6.7 (default, Feb 14 2020, 03:17:48) \n", - ); - expect(await stderr.readLine()).to.equal( - "[Wasm WASI vClang 9.0.0 (https://github.com/llvm/llvm-project 0399d5a9682b3cef7 on generic\n", - ); - expect(await stderr.readLine()).to.equal( - 'Type "help", "copyright", "credits" or "license" for more information.\n', - ); - - // Then, send the command to the REPL - await stdin.writeln("import sys"); - // TTY echo - expect(await stdout.readLine()).to.equal("import sys\n"); - await stdin.writeln("print(1 + 1)"); - // TTY echo - expect(await stdout.readLine()).to.equal("\n"); - expect(await stdout.readLine()).to.equal("print(1 + 1)\n"); - // Our output - expect(await stdout.readLine()).to.equal("\n"); - expect(await stdout.readLine()).to.equal("2\n"); - // We've done what we want, so let's shut it down - await stdin.writeln("sys.exit(42)"); - // TTY echo - expect(await stdout.readLine()).to.equal("sys.exit(42)\n"); - expect(await stdout.readLine()).to.equal("\n"); - - // Wait for the instance to shut down. - await stdin.close(); - await stdout.close(); - await stderr.close(); - const output = await instance.wait(); - - expect(output.ok).to.be.false; - expect(output.code).to.equal(42); - expect(output.stdout).to.equal(""); - // Python prints the prompts to stderr, but our TTY handling prints - // echoed characters to stdout - expect(output.stderr).to.equal(">>> >>> >>> >>> >>> "); + const output = await instance.wait(); + + const stdout = output.stdout; + expect(stdout).to.contain("mounted"); + expect(output.ok).to.be.true; + }); + + it("can see files in a mounted directory", async () => { + const dir = new Directory(); + await dir.writeFile("/file.txt", new Uint8Array()); + const pkg = await Wasmer.fromRegistry("sharrattj/coreutils"); + + const instance = await pkg.commands["ls"].run({ + stdin: "", + args: ["/mounted"], + mount: { "/mounted": dir }, }); + const output = await instance.wait(); - it("can see a mounted directory", async () => { - const dir = new Directory(); - const pkg = await Wasmer.fromRegistry("sharrattj/coreutils"); + expect(output.ok).to.be.true; + expect(output.stdout).to.equal("file.txt\n"); + expect(output.stderr).to.equal(""); + }); - const instance = await pkg.commands["ls"].run({ - args: ["/"], - mount: { "/mounted": dir }, - }); - const output = await instance.wait(); + it("can read from a mounted file", async () => { + const dir = new Directory(); + await dir.writeFile("/file.txt", encoder.encode("Hello, World!")); + const pkg = await Wasmer.fromRegistry("sharrattj/coreutils"); - const stdout = output.stdout; - expect(stdout).to.contain("mounted"); - expect(output.ok).to.be.true; + const instance = await pkg.commands["cat"].run({ + args: ["/mounted/file.txt"], + mount: { "/mounted": dir }, }); + const output = await instance.wait(); - it("can see files in a mounted directory", async () => { - const dir = new Directory(); - await dir.writeFile("/file.txt", new Uint8Array()); - const pkg = await Wasmer.fromRegistry("sharrattj/coreutils"); - - const instance = await pkg.commands["ls"].run({ - stdin: "", - args: ["/mounted"], - mount: { "/mounted": dir }, - }); - const output = await instance.wait(); - - expect(output.ok).to.be.true; - expect(output.stdout).to.equal("file.txt\n"); - expect(output.stderr).to.equal(""); - }); + const stdout = output.stdout; + expect(stdout).to.equal("Hello, World!"); + expect(output.ok).to.be.true; + }); - it("can read from a mounted file", async () => { - const dir = new Directory(); - await dir.writeFile("/file.txt", encoder.encode("Hello, World!")); - const pkg = await Wasmer.fromRegistry("sharrattj/coreutils"); + it("can delete files from a mounted directory", async () => { + const dir = new Directory(); + await dir.writeFile("/file.txt", encoder.encode("Hello, World!")); + const pkg = await Wasmer.fromRegistry("sharrattj/coreutils"); - const instance = await pkg.commands["cat"].run({ - args: ["/mounted/file.txt"], - mount: { "/mounted": dir }, - }); - const output = await instance.wait(); - - const stdout = output.stdout; - expect(stdout).to.equal("Hello, World!"); - expect(output.ok).to.be.true; + const instance = await pkg.commands["rm"].run({ + args: ["/mounted/file.txt"], + mount: { "/mounted": dir }, }); + const output = await instance.wait(); - it("can delete files from a mounted directory", async () => { - const dir = new Directory(); - await dir.writeFile("/file.txt", encoder.encode("Hello, World!")); - const pkg = await Wasmer.fromRegistry("sharrattj/coreutils"); + expect(dir.readDir("/")).to.be.empty; + expect(output.ok).to.be.true; + }); - const instance = await pkg.commands["rm"].run({ - args: ["/mounted/file.txt"], - mount: { "/mounted": dir }, - }); - const output = await instance.wait(); + it("can delete directories from a mounted directory", async () => { + const dir = new Directory(); + await dir.createDir("/nested-dir"); + const pkg = await Wasmer.fromRegistry("sharrattj/coreutils"); - expect(dir.readDir("/")).to.be.empty; - expect(output.ok).to.be.true; + const instance = await pkg.commands["rmdir"].run({ + args: ["/mounted/nested-dir"], + mount: { "/mounted": dir }, }); + const output = await instance.wait(); - it("can delete directories from a mounted directory", async () => { - const dir = new Directory(); - await dir.createDir("/nested-dir"); - const pkg = await Wasmer.fromRegistry("sharrattj/coreutils"); + expect(dir.readDir("/")).to.be.empty; + expect(output.ok).to.be.true; + }); - const instance = await pkg.commands["rmdir"].run({ - args: ["/mounted/nested-dir"], - mount: { "/mounted": dir }, - }); - const output = await instance.wait(); + it("can write to a mounted directory", async () => { + const dir = new Directory(); + const pkg = await Wasmer.fromRegistry("sharrattj/bash"); - expect(dir.readDir("/")).to.be.empty; - expect(output.ok).to.be.true; + const instance = await pkg.commands["bash"].run({ + args: ["-c", "echo 'Something else' > /mounted/another-file.txt"], + mount: { "/mounted": dir }, }); + await instance.wait(); - it("can write to a mounted directory", async () => { - const dir = new Directory(); - const pkg = await Wasmer.fromRegistry("sharrattj/bash"); - - const instance = await pkg.commands["bash"].run({ - args: ["-c", "echo 'Something else' > /mounted/another-file.txt"], - mount: { "/mounted": dir }, - }); - await instance.wait(); - - expect(await dir.readTextFile("/another-file.txt")).to.equal( - "Something else\n", - ); - }); + expect(await dir.readTextFile("/another-file.txt")).to.equal( + "Something else\n", + ); + }); }); // FIXME: Re-enable these test and move it to the "Wasmer.spawn" test suite // when we fix TTY handling with static inputs. describe.skip("failing tty handling tests", function () { - let wasmer: Wasmer; + let wasmer: Wasmer; - this.timeout("120s").beforeAll(async () => { - await initialized; + this.timeout("120s").beforeAll(async () => { + await initialized; - // Note: technically we should use a separate Wasmer instance so tests can't - // interact with each other, but in this case the caching benefits mean we - // complete in tens of seconds rather than several minutes. - wasmer = new Wasmer(); - }); + // Note: technically we should use a separate Wasmer instance so tests can't + // interact with each other, but in this case the caching benefits mean we + // complete in tens of seconds rather than several minutes. + wasmer = new Wasmer(); + }); - it("can run a bash session non-interactively", async () => { - const pkg = await Wasmer.fromRegistry("sharrattj/bash"); + it("can run a bash session non-interactively", async () => { + const pkg = await Wasmer.fromRegistry("sharrattj/bash"); - const instance = await pkg.commands["bash"].run({ - stdin: "ls / && exit 42\n", - }); - console.log("Spawned"); + const instance = await pkg.commands["bash"].run({ + stdin: "ls / && exit 42\n", + }); + console.log("Spawned"); - const { code, stdout, stderr } = await instance.wait(); + const { code, stdout, stderr } = await instance.wait(); - expect(code).to.equal(42); - expect(stdout).to.equal("bin\nlib\ntmp\n"); - expect(stderr).to.equal(""); - }); + expect(code).to.equal(42); + expect(stdout).to.equal("bin\nlib\ntmp\n"); + expect(stderr).to.equal(""); + }); - it.skip("can communicate with a subprocess", async () => { - const pkg = await Wasmer.fromRegistry("sharrattj/bash"); + it.skip("can communicate with a subprocess", async () => { + const pkg = await Wasmer.fromRegistry("sharrattj/bash"); - const instance = await pkg.commands["bash"].run({ - uses: ["christoph/wasix-test-stdinout@0.1.1"], - }); + const instance = await pkg.commands["bash"].run({ + uses: ["christoph/wasix-test-stdinout@0.1.1"], + }); - const stdin = instance.stdin!.getWriter(); - const stdout = new BufReader(instance.stdout); + const stdin = instance.stdin!.getWriter(); + const stdout = new BufReader(instance.stdout); - await stdin.write(encoder.encode("stdinout-loop\n")); - // the stdinout-loop program should be running now - await stdin.write(encoder.encode("First\n")); - expect(await stdout.readLine()).to.equal("First\n"); - await stdin.write(encoder.encode("Second\n")); - expect(await stdout.readLine()).to.equal("Second\n"); + await stdin.write(encoder.encode("stdinout-loop\n")); + // the stdinout-loop program should be running now + await stdin.write(encoder.encode("First\n")); + expect(await stdout.readLine()).to.equal("First\n"); + await stdin.write(encoder.encode("Second\n")); + expect(await stdout.readLine()).to.equal("Second\n"); - await stdin.close(); - const output = await instance.wait(); + await stdin.close(); + const output = await instance.wait(); - console.log(output); - expect(output.code).to.equal(0); - }); + console.log(output); + expect(output.code).to.equal(0); + }); }); /** @@ -380,216 +380,216 @@ describe.skip("failing tty handling tests", function () { * the other end. */ class RealisticWriter { - private encoder = new TextEncoder(); - constructor(readonly stream: WritableStream) {} + private encoder = new TextEncoder(); + constructor(readonly stream: WritableStream) {} - async writeln(text: string): Promise { - await this.write(text + "\r\n"); - } + async writeln(text: string): Promise { + await this.write(text + "\r\n"); + } - async write(text: string): Promise { - const writer = this.stream.getWriter(); - - try { - const message = this.encoder.encode(text); - - for (const byte of message) { - await writer.ready; - await writer.write(Uint8Array.of(byte)); - } - } finally { - // Note: wait for all bytes to be flushed before returning. - await writer.ready; - writer.releaseLock(); - } - } + async write(text: string): Promise { + const writer = this.stream.getWriter(); - async close(): Promise { - await this.stream.close(); + try { + const message = this.encoder.encode(text); + + for (const byte of message) { + await writer.ready; + await writer.write(Uint8Array.of(byte)); + } + } finally { + // Note: wait for all bytes to be flushed before returning. + await writer.ready; + writer.releaseLock(); } + } + + async close(): Promise { + await this.stream.close(); + } } /** * A streams adapter to simplify consuming them interactively. */ class BufReader { - private buffer?: Uint8Array; - private decoder = new TextDecoder(); - private chunks: AsyncGenerator; - - constructor( - stream: ReadableStream, - private verbose: boolean = false, - ) { - this.chunks = chunks(stream); - } + private buffer?: Uint8Array; + private decoder = new TextDecoder(); + private chunks: AsyncGenerator; - /** - * Consume data until the next newline character or EOF. - */ - async readLine(): Promise { - const pieces: Uint8Array[] = []; - - while ((await this.fillBuffer()) && this.buffer) { - const ASCII_NEWLINE = 0x0a; - const position = this.buffer.findIndex(b => b == ASCII_NEWLINE); - - this.log({ buffer: this.peek(), position }); - - if (position < 0) { - // Consume the entire chunk. - pieces.push(this.consume()); - } else { - // Looks like we've found the newline. Consume everything up to - // and including it, and stop reading. - pieces.push(this.consume(position + 1)); - break; - } - } - - const line = pieces.map(piece => this.decoder.decode(piece)).join(""); - this.log({ line }); - return line; + constructor( + stream: ReadableStream, + private verbose: boolean = false, + ) { + this.chunks = chunks(stream); + } + + /** + * Consume data until the next newline character or EOF. + */ + async readLine(): Promise { + const pieces: Uint8Array[] = []; + + while ((await this.fillBuffer()) && this.buffer) { + const ASCII_NEWLINE = 0x0a; + const position = this.buffer.findIndex(b => b == ASCII_NEWLINE); + + this.log({ buffer: this.peek(), position }); + + if (position < 0) { + // Consume the entire chunk. + pieces.push(this.consume()); + } else { + // Looks like we've found the newline. Consume everything up to + // and including it, and stop reading. + pieces.push(this.consume(position + 1)); + break; + } } - /** - * Read a line of text, interpreting the ANSI escape codes for clearing the - * line and stripping any other formatting. - */ - async readAnsiLine(): Promise { - const rawLine = await this.readLine(); - - // Note: QuickJS uses the "move left by n columns" escape code for - // clearing the line. - const pieces = rawLine.split(/\x1b\[\d+D/); - const lastPiece = pieces.pop() || rawLine; - return lastPiece.replace(ansiEscapeCode, ""); + const line = pieces.map(piece => this.decoder.decode(piece)).join(""); + this.log({ line }); + return line; + } + + /** + * Read a line of text, interpreting the ANSI escape codes for clearing the + * line and stripping any other formatting. + */ + async readAnsiLine(): Promise { + const rawLine = await this.readLine(); + + // Note: QuickJS uses the "move left by n columns" escape code for + // clearing the line. + const pieces = rawLine.split(/\x1b\[\d+D/); + const lastPiece = pieces.pop() || rawLine; + return lastPiece.replace(ansiEscapeCode, ""); + } + + async readToEnd(): Promise { + // Note: We want to merge all chunks into a single buffer and decode in + // one hit. Otherwise we'll have O(n²) performance issues and run the + // risk of chunks not being aligned to UTF-8 code point boundaries when + // we decode them. + + const chunks: Uint8Array[] = []; + + while (await this.fillBuffer()) { + this.log({ + len: chunks.length + 1, + nextChunk: this.peek(), + }); + chunks.push(this.consume()); } - async readToEnd(): Promise { - // Note: We want to merge all chunks into a single buffer and decode in - // one hit. Otherwise we'll have O(n²) performance issues and run the - // risk of chunks not being aligned to UTF-8 code point boundaries when - // we decode them. - - const chunks: Uint8Array[] = []; - - while (await this.fillBuffer()) { - this.log({ - len: chunks.length + 1, - nextChunk: this.peek(), - }); - chunks.push(this.consume()); - } - - const totalByteCount = chunks.reduce( - (accumulator, element) => accumulator + element.byteLength, - 0, - ); - const buffer = new Uint8Array(totalByteCount); - let offset = 0; - - for (const chunk of chunks) { - buffer.set(chunk, offset); - offset += chunk.byteLength; - } - - const text = this.decoder.decode(buffer); - this.log({ text }); - return text; - } + const totalByteCount = chunks.reduce( + (accumulator, element) => accumulator + element.byteLength, + 0, + ); + const buffer = new Uint8Array(totalByteCount); + let offset = 0; - async close() { - await this.chunks.return(undefined); + for (const chunk of chunks) { + buffer.set(chunk, offset); + offset += chunk.byteLength; } - peek(): string | undefined { - if (this.buffer) { - return this.decoder.decode(this.buffer); - } - } + const text = this.decoder.decode(buffer); + this.log({ text }); + return text; + } - /** - * Try to read more bytes into the buffer if it was previously empty. - * @returns whether the buffer was filled. - */ - private async fillBuffer() { - if (this.buffer && this.buffer.byteLength > 0) { - return true; - } - - const chunk = await this.chunks.next(); - - if (chunk.value && chunk.value.byteLength > 0) { - this.buffer = chunk.value; - return true; - } else { - this.buffer = undefined; - return false; - } + async close() { + await this.chunks.return(undefined); + } + + peek(): string | undefined { + if (this.buffer) { + return this.decoder.decode(this.buffer); + } + } + + /** + * Try to read more bytes into the buffer if it was previously empty. + * @returns whether the buffer was filled. + */ + private async fillBuffer() { + if (this.buffer && this.buffer.byteLength > 0) { + return true; } - /** - * Remove some bytes from the front of `this.buffer`, returning the bytes - * that were removed. The buffer will be set to `undefined` if all bytes - * have been consumed. - * - * @param amount The number of bytes to remove - * @returns The removed bytes - * @throws If the buffer was `undefined` or more bytes were requested than - * are available - */ - private consume(amount?: number): Uint8Array { - if (!this.buffer) { - throw new Error(); - } - - if (amount) { - if (amount > this.buffer.byteLength) { - throw new Error(); - } - - const before = this.buffer.slice(0, amount); - const rest = this.buffer.slice(amount); - this.buffer = rest.length > 0 ? rest : undefined; - - return before; - } else { - const buffer = this.buffer; - this.buffer = undefined; - return buffer; - } + const chunk = await this.chunks.next(); + + if (chunk.value && chunk.value.byteLength > 0) { + this.buffer = chunk.value; + return true; + } else { + this.buffer = undefined; + return false; + } + } + + /** + * Remove some bytes from the front of `this.buffer`, returning the bytes + * that were removed. The buffer will be set to `undefined` if all bytes + * have been consumed. + * + * @param amount The number of bytes to remove + * @returns The removed bytes + * @throws If the buffer was `undefined` or more bytes were requested than + * are available + */ + private consume(amount?: number): Uint8Array { + if (!this.buffer) { + throw new Error(); } - /** - * Log a piece of information if the `verbose` flag is set. - */ - private log(value: any) { - if (this.verbose) { - console.log(value); - } + if (amount) { + if (amount > this.buffer.byteLength) { + throw new Error(); + } + + const before = this.buffer.slice(0, amount); + const rest = this.buffer.slice(amount); + this.buffer = rest.length > 0 ? rest : undefined; + + return before; + } else { + const buffer = this.buffer; + this.buffer = undefined; + return buffer; + } + } + + /** + * Log a piece of information if the `verbose` flag is set. + */ + private log(value: any) { + if (this.verbose) { + console.log(value); } + } } /** * Turn a ReadableStream into an async generator. */ async function* chunks( - stream: ReadableStream, + stream: ReadableStream, ): AsyncGenerator { - const reader = stream.getReader(); + const reader = stream.getReader(); - try { - let chunk: ReadableStreamReadResult; + try { + let chunk: ReadableStreamReadResult; - do { - chunk = await reader.read(); + do { + chunk = await reader.read(); - if (chunk.value) { - yield chunk.value; - } - } while (!chunk.done); - } finally { - reader.releaseLock(); - } + if (chunk.value) { + yield chunk.value; + } + } while (!chunk.done); + } finally { + reader.releaseLock(); + } } diff --git a/tests/registry.test.ts b/tests/registry.test.ts new file mode 100644 index 00000000..c56a9501 --- /dev/null +++ b/tests/registry.test.ts @@ -0,0 +1,525 @@ +import { assert, expect } from "@esm-bundle/chai"; +import { init, initializeLogger, Wasmer } from ".."; + +const pkg_name = "test-js-sdk-pkg"; +const app_name = "test-js-sdk-app"; + +describe("Registry", function () { + this.timeout("60s").beforeAll(async () => { + await init({ + module: new URL("../dist/wasmer_js_bg.wasm", import.meta.url), + registryUrl: "https://registry.wasmer.wtf/graphql", + token: process.env.WASMER_TOKEN, + }); + + initializeLogger("error"); + }); + + const WASMER_TEST_OWNER = process.env.WASMER_TEST_OWNER; + + it("has global context", async () => { + let v = (globalThis as any)["__WASMER_REGISTRY__"]; + expect(typeof v != "undefined"); + }); + + it("can deploy apps", async () => { + let appConfig = { + name: app_name, + owner: WASMER_TEST_OWNER, + package: + "sha256:34a3b5f5a9108c2b258eb51e9d0978b6778a3696b9c7e713adab33293fb5e4f1", + env: [["test", "new_value"]], + default: true, + }; + + await Wasmer.deployApp(appConfig); + }); + + it("can create a package with atoms", async () => { + let manifest = { + module: [ + { + name: "test", + abi: "wasi", + source: new Uint8Array([ + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, + ]), + }, + { + name: "other-test", + source: new Uint8Array([ + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, + ]), + }, + ], + command: [ + { + module: "wasmer/python:python", + name: "hello", + runner: "wasi", + annotations: { + wasi: { + "main-args": [ + "-c", + "import os; print([f for f in os.walk('/public')]); ", + ], + }, + }, + }, + ], + dependencies: { + "wasmer/python": "3.12.5+build.7", + }, + + fs: { + public: { + "index.html": "Hello, js!", + "index_timestamp.html": { + data: "Hello, js!", + modified: 987656789, + }, + "index_date.html": { + data: "Hello, js!", + modified: new Date(), + }, + }, + }, + }; + await Wasmer.createPackage(manifest); + }); + + it("can create an unnamed package", async () => { + let manifest = { + command: [ + { + module: "wasmer/python:python", + name: "hello", + runner: "wasi", + annotations: { + wasi: { + "main-args": [ + "-c", + "import os; print([f for f in os.walk('/public')]); ", + ], + }, + }, + }, + ], + dependencies: { + "wasmer/python": "3.12.5+build.7", + }, + + fs: { + public: { + "index.html": "Hello, js!", + "index_timestamp.html": { + data: "Hello, js!", + modified: 987656789, + }, + "index_date.html": { + data: "Hello, js!", + modified: new Date(), + }, + }, + }, + }; + + await Wasmer.createPackage(manifest); + }); + + it("can use unnamed packages twice", async () => { + let manifest = { + command: [ + { + module: "wasmer/python:python", + name: "hello", + runner: "wasi", + annotations: { + wasi: { + "main-args": [ + "-c", + "import os; print([f for f in os.walk('/public')]); ", + ], + }, + }, + }, + ], + dependencies: { + "wasmer/python": "3.12.5+build.7", + }, + + fs: { + public: { + "index.html": "Hello, js!", + }, + }, + }; + + let wasmerPackage = await Wasmer.createPackage(manifest); + + let appConfig = { + name: app_name, + owner: WASMER_TEST_OWNER, + package: wasmerPackage, + env: [["test", "new_value"]], + default: true, + }; + + await Wasmer.deployApp(appConfig); + + let appConfig2 = { + name: app_name + "2", + owner: WASMER_TEST_OWNER, + package: wasmerPackage, + env: [["test", "new_value"]], + default: true, + }; + + await Wasmer.deployApp(appConfig2); + }); + + it("can deploy unnamed packages", async () => { + let manifest = { + command: [ + { + module: "wasmer/python:python", + name: "hello", + runner: "wasi", + annotations: { + wasi: { + "main-args": [ + "-c", + "import os; print([f for f in os.walk('/public')]); ", + ], + }, + }, + }, + ], + dependencies: { + "wasmer/python": "3.12.5+build.7", + }, + + fs: { + public: { + "index.html": "Hello, js!", + }, + }, + }; + + let wasmerPackage = await Wasmer.createPackage(manifest); + + let appConfig = { + name: app_name, + owner: WASMER_TEST_OWNER, + package: wasmerPackage, + env: [["test", "new_value"]], + default: true, + }; + + await Wasmer.deployApp(appConfig); + }); + + it("can't publish unnamed packages", async () => { + let manifest = { + command: [ + { + module: "wasmer/python:python", + name: "hello", + runner: "wasi", + annotations: { + wasi: { + "main-args": [ + "-c", + "import os; print([f for f in os.walk('/public')]); ", + ], + }, + }, + }, + ], + dependencies: { + "wasmer/python": "3.12.5+build.7", + }, + + fs: { + public: { + "index.html": "Hello, js!", + }, + }, + }; + + let wasmerPackage = await Wasmer.createPackage(manifest); + try { + await Wasmer.publishPackage(wasmerPackage); + assert.fail("publishes the package", "should not publish the package"); + } catch { + return; + } + }); + + it("can publish named packages", async () => { + let manifest = { + package: { + name: WASMER_TEST_OWNER + "/" + pkg_name, + }, + command: [ + { + module: "wasmer/python:python", + name: "hello", + runner: "wasi", + annotations: { + wasi: { + "main-args": [ + "-c", + "import os; print([f for f in os.walk('/public')]); ", + ], + }, + }, + }, + ], + dependencies: { + "wasmer/python": "3.12.5+build.7", + }, + + fs: { + public: { + "index.html": "Hello, js!", + }, + }, + }; + + let wasmerPackage = await Wasmer.createPackage(manifest); + await Wasmer.publishPackage(wasmerPackage); + }); + + it("fails deploying apps with unpublished packages", async () => { + let manifest = { + package: { + name: WASMER_TEST_OWNER + "/" + pkg_name, + }, + command: [ + { + module: "wasmer/python:python", + name: "hello", + runner: "wasi", + annotations: { + wasi: { + "main-args": [ + "-c", + "import os; print([f for f in os.walk('/public')]); ", + ], + }, + }, + }, + ], + dependencies: { + "wasmer/python": "3.12.5+build.7", + }, + + fs: { + public: { + "index.html": "A totally new file!", + }, + }, + }; + + let wasmerPackage = await Wasmer.createPackage(manifest); + + let appConfig = { + name: app_name, + owner: WASMER_TEST_OWNER, + package: wasmerPackage, + env: [["test", "new_value"]], + default: true, + }; + + try { + await Wasmer.deployApp(appConfig); + assert.fail("deploys the app", "should not deploy the app"); + } catch { + return; + } + }); + + it("can deploy apps with user-created packages", async () => { + let manifest = { + package: { + name: WASMER_TEST_OWNER + "/" + pkg_name, + }, + command: [ + { + module: "wasmer/python:python", + name: "hello", + runner: "wasi", + annotations: { + wasi: { + "main-args": [ + "-c", + "import os; print([f for f in os.walk('/public')]); ", + ], + }, + }, + }, + ], + dependencies: { + "wasmer/python": "3.12.5+build.7", + }, + + fs: { + public: { + "index.html": "Hello, js!", + }, + }, + }; + + let wasmerPackage = await Wasmer.createPackage(manifest); + await Wasmer.publishPackage(wasmerPackage); + + let appConfig = { + name: app_name, + owner: WASMER_TEST_OWNER, + package: wasmerPackage, + env: [["test", "new_value"]], + default: true, + }; + + await Wasmer.deployApp(appConfig); + }); + + it("can run user-created packages", async () => { + let manifest = { + command: [ + { + module: "wasmer/python:python", + name: "hello", + runner: "wasi", + annotations: { + wasi: { + "main-args": ["-c", 'print("hello, js!")'], + }, + }, + }, + ], + dependencies: { + "wasmer/python": "3.12.5+build.7", + }, + }; + + let pkg = await Wasmer.createPackage(manifest); + let instance = await pkg.commands["hello"].run(); + + const output = await instance.wait(); + assert(output.stdout === "hello, js!\n"); + }); + + it("can mount fs", async () => { + let manifest = { + command: [ + { + module: "wasmer/python:python", + name: "hello", + runner: "wasi", + annotations: { + wasi: { + "main-args": [ + "-c", + "import os; print([f for f in os.walk('/public')]); ", + ], + }, + }, + }, + ], + dependencies: { + "wasmer/python": "3.12.5+build.7", + }, + + fs: { + public: { + "index.html": "Hello, js!", + }, + }, + }; + + let pkg = await Wasmer.createPackage(manifest); + let instance = await pkg.commands["hello"].run(); + + const output = await instance.wait(); + assert(output.stdout.includes("index.html")); + }); + + it("can read metadata", async () => { + let manifest = { + package: { + readme: "This is my readme!", + license: { data: "This is my license!", modified: new Date() }, + }, + command: [ + { + module: "wasmer/python:python", + name: "hello", + runner: "wasi", + annotations: { + wasi: { + "main-args": [ + "-c", + "import os; print([f for f in os.walk('/public')]); ", + ], + }, + }, + }, + ], + dependencies: { + "wasmer/python": "3.12.5+build.7", + }, + + fs: { + public: { + "index.html": "Hello, js!", + }, + }, + }; + + let pkg = await Wasmer.createPackage(manifest); + let instance = await pkg.commands["hello"].run(); + + const output = await instance.wait(); + assert(output.stdout.includes("index.html")); + }); + + it("can deploy a php app", async () => { + let manifest = { + package: { name: WASMER_TEST_OWNER + "/" }, + command: [ + { + module: "php/php:php", + name: "run", + runner: "wasi", + annotations: { + wasi: { + "main-args": ["-t", "/app", "-S", "localhost:8080"], + }, + }, + }, + ], + dependencies: { + "php/php": "=8.3.4", + }, + fs: { + "/app": { + "index.php": " { - await init(new URL("../dist/wasmer_js_bg.wasm", import.meta.url)); - initializeLogger("warn"); + await init({ + module: new URL("../dist/wasmer_js_bg.wasm", import.meta.url), + }); + initializeLogger("warn"); })(); describe("run", function () { - this.timeout("60s").beforeAll(async () => await initialized); + this.timeout("60s").beforeAll(async () => await initialized); - it("can execute a noop program", async () => { - const noop = `( + it("can execute a noop program", async () => { + const noop = `( module (memory $memory 0) (export "memory" (memory $memory)) (func (export "_start") nop) )`; - const wasm = wat2wasm(noop); - const module = await WebAssembly.compile(wasm); + const wasm = wat2wasm(noop); + const module = await WebAssembly.compile(wasm); - const instance = await runWasix(module, { program: "noop" }); - const output = await instance.wait(); + const instance = await runWasix(module, { program: "noop" }); + const output = await instance.wait(); - expect(output.ok).to.be.true; - expect(output.code).to.equal(0); - }); - - it("can start quickjs", async () => { - const pkg = await Wasmer.fromRegistry("saghul/quickjs@0.0.3"); - const quickjs = pkg.commands["quickjs"].binary(); + expect(output.ok).to.be.true; + expect(output.code).to.equal(0); + }); - const instance = await runWasix(quickjs, { - program: "quickjs", - args: ["--eval", "console.log('Hello, World!')"], - }); - const output = await instance.wait(); + it("can start quickjs", async () => { + const pkg = await Wasmer.fromRegistry("saghul/quickjs@0.0.3"); + const quickjs = pkg.commands["quickjs"].binary(); - expect(output.ok).to.be.true; - expect(output.code).to.equal(0); - expect(output.stdout).to.contain("Hello, World!"); - expect(output.stderr).to.be.empty; + const instance = await runWasix(quickjs, { + program: "quickjs", + args: ["--eval", "console.log('Hello, World!')"], }); - - it("can read directories", async () => { - const dir = new Directory(); - await dir.writeFile("/file.txt", ""); - const pkg = await Wasmer.fromRegistry("saghul/quickjs@0.0.3"); - const quickjs = pkg.commands["quickjs"].binary(); - - const instance = await runWasix(quickjs, { - program: "quickjs", - args: [ - "--std", - "--eval", - `[dirs] = os.readdir("/"); console.log(dirs.join("\\n"))`, - ], - mount: { - "/mount": dir, - }, - }); - const output = await instance.wait(); - - expect(output.ok).to.be.true; - expect(output.code).to.equal(0); - expect(output.stdout).to.equal(".\n..\nmount\n"); - expect(output.stderr).to.be.empty; + const output = await instance.wait(); + + expect(output.ok).to.be.true; + expect(output.code).to.equal(0); + expect(output.stdout).to.contain("Hello, World!"); + expect(output.stderr).to.be.empty; + }); + + it("can read directories", async () => { + const dir = new Directory(); + await dir.writeFile("/file.txt", ""); + const pkg = await Wasmer.fromRegistry("saghul/quickjs@0.0.3"); + const quickjs = pkg.commands["quickjs"].binary(); + + const instance = await runWasix(quickjs, { + program: "quickjs", + args: [ + "--std", + "--eval", + `[dirs] = os.readdir("/"); console.log(dirs.join("\\n"))`, + ], + mount: { + "/mount": dir, + }, }); - - it("can read files", async () => { - const tmp = new Directory(); - await tmp.writeFile("/file.txt", "Hello, World!"); - const pkg = await Wasmer.fromRegistry("saghul/quickjs@0.0.3"); - const quickjs = pkg.commands["quickjs"].binary(); - - const instance = await runWasix(quickjs, { - program: "quickjs", - args: [ - "--std", - "--eval", - `console.log(std.open('/tmp/file.txt', "r").readAsString())`, - ], - mount: { - "/tmp": tmp, - }, - }); - const output = await instance.wait(); - - expect(output.ok).to.be.true; - expect(output.code).to.equal(0); - expect(output.stdout).to.equal("Hello, World!\n"); - expect(output.stderr).to.be.empty; + const output = await instance.wait(); + + expect(output.ok).to.be.true; + expect(output.code).to.equal(0); + expect(output.stdout).to.equal(".\n..\nmount\n"); + expect(output.stderr).to.be.empty; + }); + + it("can read files", async () => { + const tmp = new Directory(); + await tmp.writeFile("/file.txt", "Hello, World!"); + const pkg = await Wasmer.fromRegistry("saghul/quickjs@0.0.3"); + const quickjs = pkg.commands["quickjs"].binary(); + + const instance = await runWasix(quickjs, { + program: "quickjs", + args: [ + "--std", + "--eval", + `console.log(std.open('/tmp/file.txt', "r").readAsString())`, + ], + mount: { + "/tmp": tmp, + }, }); - - it("can read files mounted using DirectoryInit", async () => { - const pkg = await Wasmer.fromRegistry("saghul/quickjs@0.0.3"); - const quickjs = pkg.commands["quickjs"].binary(); - - const instance = await runWasix(quickjs, { - program: "quickjs", - args: [ - "--std", - "--eval", - `console.log(std.open('/tmp/file.txt', "r").readAsString())`, - ], - mount: { - "/tmp": { - "file.txt": "Hello, World!", - }, - }, - }); - const output = await instance.wait(); - - expect(output.ok).to.be.true; - expect(output.code).to.equal(0); - expect(output.stdout).to.equal("Hello, World!\n"); - expect(output.stderr).to.be.empty; + const output = await instance.wait(); + + expect(output.ok).to.be.true; + expect(output.code).to.equal(0); + expect(output.stdout).to.equal("Hello, World!\n"); + expect(output.stderr).to.be.empty; + }); + + it("can read files mounted using DirectoryInit", async () => { + const pkg = await Wasmer.fromRegistry("saghul/quickjs@0.0.3"); + const quickjs = pkg.commands["quickjs"].binary(); + + const instance = await runWasix(quickjs, { + program: "quickjs", + args: [ + "--std", + "--eval", + `console.log(std.open('/tmp/file.txt', "r").readAsString())`, + ], + mount: { + "/tmp": { + "file.txt": "Hello, World!", + }, + }, }); - - it("can write files", async () => { - const dir = new Directory(); - const pkg = await Wasmer.fromRegistry("saghul/quickjs@0.0.3"); - const quickjs = pkg.commands["quickjs"].binary(); - const script = ` + const output = await instance.wait(); + + expect(output.ok).to.be.true; + expect(output.code).to.equal(0); + expect(output.stdout).to.equal("Hello, World!\n"); + expect(output.stderr).to.be.empty; + }); + + it("can write files", async () => { + const dir = new Directory(); + const pkg = await Wasmer.fromRegistry("saghul/quickjs@0.0.3"); + const quickjs = pkg.commands["quickjs"].binary(); + const script = ` const f = std.open('/mount/file.txt', 'w'); f.puts('Hello, World!'); f.close(); `; - const instance = await runWasix(quickjs, { - program: "quickjs", - args: ["--std", "--eval", script], - mount: { - "/mount": dir, - }, - }); - const output = await instance.wait(); - - expect(output.ok).to.be.true; - expect(await dir.readTextFile("/file.txt")).to.equal("Hello, World!"); + const instance = await runWasix(quickjs, { + program: "quickjs", + args: ["--std", "--eval", script], + mount: { + "/mount": dir, + }, }); + const output = await instance.wait(); - it("can accept strings as stdin", async () => { - const pkg = await Wasmer.fromRegistry("saghul/quickjs@0.0.3"); - const quickjs = pkg.commands["quickjs"].binary(); + expect(output.ok).to.be.true; + expect(await dir.readTextFile("/file.txt")).to.equal("Hello, World!"); + }); - const instance = await runWasix(quickjs, { - program: "quickjs", - args: ["--interactive", "--std"], - stdin: "console.log('Hello, World!');\nstd.exit(42)\n", - }); - const output = await instance.wait(); + it("can accept strings as stdin", async () => { + const pkg = await Wasmer.fromRegistry("saghul/quickjs@0.0.3"); + const quickjs = pkg.commands["quickjs"].binary(); - expect(output.code).to.equal(42); - expect(output.stdout).to.contain("Hello, World!\n"); - expect(output.stderr).to.be.empty; + const instance = await runWasix(quickjs, { + program: "quickjs", + args: ["--interactive", "--std"], + stdin: "console.log('Hello, World!');\nstd.exit(42)\n", }); -}) + const output = await instance.wait(); + + expect(output.code).to.equal(42); + expect(output.stdout).to.contain("Hello, World!\n"); + expect(output.stderr).to.be.empty; + }); +}); diff --git a/web-dev-server.config.mjs b/web-dev-server.config.mjs index 7c59abbd..bd7f08c8 100644 --- a/web-dev-server.config.mjs +++ b/web-dev-server.config.mjs @@ -1,16 +1,39 @@ import { chromeLauncher } from "@web/test-runner"; import { esbuildPlugin } from "@web/dev-server-esbuild"; +import { fromRollup } from "@web/dev-server-rollup"; +import rollupReplace from "@rollup/plugin-replace"; +import dotenv from "dotenv"; +dotenv.config(); async function add_headers(ctx, next) { - ctx.set("Cross-Origin-Opener-Policy", "same-origin"); - ctx.set("Cross-Origin-Embedder-Policy", "require-corp"); - await next(ctx); + ctx.set("Cross-Origin-Opener-Policy", "same-origin"); + ctx.set("Cross-Origin-Embedder-Policy", "require-corp"); + await next(ctx); } +const envPlugin = fromRollup(rollupReplace)({ + preventAssignment: true, + values: { + "process.env.WASMER_TOKEN": JSON.stringify(process.env.WASMER_TOKEN), + "process.env.WASMER_TEST_OWNER": JSON.stringify( + process.env.WASMER_TEST_OWNER, + ), + }, +}); + export default { - files: ["tests/**/*.test.ts"], - plugins: [esbuildPlugin({ ts: true })], - middlewares: [add_headers], - browsers: [chromeLauncher({ launchOptions: { devtools: true } })], - testsFinishTimeout: 10 * 60 * 1000, + files: ["tests/**/*.test.ts"], + plugins: [esbuildPlugin({ ts: true }), envPlugin], + middlewares: [add_headers], + browsers: [chromeLauncher({ launchOptions: { devtools: true } })], + testsFinishTimeout: 10 * 60 * 1000, + environmentVariables: { + API_URL: process.env.HELLO, + }, + groups: [ + { + name: "reg", + files: ["tests/registry.test.ts"], + }, + ], };